Marek4321 commited on
Commit
172441b
verified
1 Parent(s): 68c99be

Update deepseek_client.py

Browse files
Files changed (1) hide show
  1. deepseek_client.py +211 -1
deepseek_client.py CHANGED
@@ -100,4 +100,214 @@ class DeepSeekClient:
100
  response_data = response.json()
101
 
102
  # Sprawd藕 struktur臋 odpowiedzi
103
- if 'choices' not in response
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
100
  response_data = response.json()
101
 
102
  # Sprawd藕 struktur臋 odpowiedzi
103
+ if 'choices' not in response_data or not response_data['choices']:
104
+ raise ValueError("Nieprawid艂owa struktura odpowiedzi: brak choices")
105
+
106
+ choice = response_data['choices'][0]
107
+ if 'message' not in choice or 'content' not in choice['message']:
108
+ raise ValueError("Nieprawid艂owa struktura odpowiedzi: brak message/content")
109
+
110
+ content = choice['message']['content']
111
+
112
+ # Pr贸ba parsowania JSON
113
+ try:
114
+ parsed_content = json.loads(content)
115
+ return parsed_content
116
+ except json.JSONDecodeError as e:
117
+ self.logger.error(f"Nie uda艂o si臋 sparsowa膰 odpowiedzi jako JSON: {content}")
118
+ raise ValueError(f"Odpowied藕 nie jest prawid艂owym JSON: {str(e)}")
119
+
120
+ except (httpx.HTTPError, ValueError) as e:
121
+ last_error = e
122
+ retries += 1
123
+
124
+ if retries < max_retries:
125
+ # Exponential backoff z jitterem
126
+ wait_time = retry_delay * (2 ** (retries - 1)) * (0.5 + random.random())
127
+ self.logger.warning(
128
+ f"Pr贸ba {retries} nie powiod艂a si臋: {str(e)}. "
129
+ f"Czekam {wait_time:.2f}s przed ponowieniem..."
130
+ )
131
+ await asyncio.sleep(wait_time)
132
+ continue
133
+
134
+ self.logger.error(f"Wszystkie {max_retries} pr贸b nie powiod艂y si臋")
135
+ break
136
+
137
+ raise last_error
138
+
139
+ async def analyze_criteria(self, brief_content: str) -> List[Dict]:
140
+ """
141
+ Analizuje brief/SIWZ w poszukiwaniu kryteri贸w oceny.
142
+
143
+ Args:
144
+ brief_content: Tre艣膰 dokumentu do analizy
145
+
146
+ Returns:
147
+ List[Dict]: Lista wyodr臋bnionych kryteri贸w
148
+
149
+ Raises:
150
+ ValueError: Gdy nie uda艂o si臋 wyodr臋bni膰 kryteri贸w
151
+ """
152
+ prompt = """
153
+ Przeanalizuj poni偶szy dokument przetargowy i zidentyfikuj kryteria oceny ofert.
154
+ Zwr贸膰 odpowied藕 dok艂adnie w poni偶szym formacie JSON (wa偶ne: odpowied藕 musi by膰 poprawnym JSON):
155
+
156
+ {
157
+ "criteria": [
158
+ {
159
+ "name": "nazwa kryterium",
160
+ "weight": liczba,
161
+ "description": "opis",
162
+ "scoring_guide": "wskaz贸wki do oceny"
163
+ }
164
+ ]
165
+ }
166
+
167
+ Upewnij si臋, 偶e:
168
+ - Suma wag wszystkich kryteri贸w wynosi dok艂adnie 100%
169
+ - Ka偶de kryterium ma unikaln膮 nazw臋
170
+ - Opisy s膮 konkretne i jednoznaczne
171
+ - Format JSON jest 艣ci艣le zachowany
172
+ """
173
+
174
+ try:
175
+ response = await self.analyze_text(prompt, brief_content)
176
+
177
+ if not isinstance(response, dict) or 'criteria' not in response:
178
+ raise ValueError("Nieprawid艂owa struktura odpowiedzi od modelu")
179
+
180
+ criteria = response['criteria']
181
+ await self._validate_criteria(criteria)
182
+ return criteria
183
+
184
+ except Exception as e:
185
+ self.logger.error(f"B艂膮d podczas parsowania kryteri贸w: {str(e)}")
186
+ raise
187
+
188
+ async def analyze_offer(
189
+ self,
190
+ offer_content: str,
191
+ criteria: List[Dict],
192
+ brief_content: str
193
+ ) -> Dict:
194
+ """
195
+ Analizuje ofert臋 wzgl臋dem zadanych kryteri贸w.
196
+
197
+ Args:
198
+ offer_content: Tre艣膰 oferty do analizy
199
+ criteria: Lista kryteri贸w oceny
200
+ brief_content: Tre艣膰 briefu/SIWZ
201
+
202
+ Returns:
203
+ Dict: Analiza oferty z ocenami i uzasadnieniami
204
+
205
+ Raises:
206
+ ValueError: Gdy nie uda艂o si臋 przeanalizowa膰 oferty
207
+ """
208
+ prompt = f"""
209
+ Oce艅 poni偶sz膮 ofert臋 wzgl臋dem kryteri贸w. Zwr贸膰 odpowied藕 dok艂adnie w poni偶szym formacie JSON:
210
+
211
+ {{
212
+ "evaluations": [
213
+ {{
214
+ "criterion_name": "nazwa kryterium",
215
+ "score": liczba 0-100,
216
+ "justification": "szczeg贸艂owe uzasadnienie",
217
+ "key_points": ["g艂贸wny punkt 1", "g艂贸wny punkt 2"],
218
+ "evidence": ["cytat/referencja 1", "cytat/referencja 2"]
219
+ }}
220
+ ],
221
+ "strengths": ["mocna strona 1", "mocna strona 2"],
222
+ "weaknesses": ["s艂aba strona 1", "s艂aba strona 2"],
223
+ "summary": "kr贸tkie podsumowanie oceny",
224
+ "recommendations": ["rekomendacja 1", "rekomendacja 2"]
225
+ }}
226
+
227
+ Kryteria oceny:
228
+ {json.dumps(criteria, indent=2, ensure_ascii=False)}
229
+
230
+ Kontekst z briefu/SIWZ:
231
+ {brief_content[:1000]}...
232
+ """
233
+
234
+ response = await self.analyze_text(prompt, offer_content)
235
+ try:
236
+ await self._validate_analysis(response)
237
+ return response
238
+ except Exception as e:
239
+ self.logger.error(f"B艂膮d podczas parsowania analizy oferty: {str(e)}")
240
+ raise
241
+
242
+ async def _validate_criteria(self, criteria: List[Dict]) -> None:
243
+ """
244
+ Waliduje wyodr臋bnione kryteria.
245
+
246
+ Args:
247
+ criteria: Lista kryteri贸w do walidacji
248
+
249
+ Raises:
250
+ ValueError: Gdy kryteria nie spe艂niaj膮 wymaga艅
251
+ """
252
+ if not criteria:
253
+ raise ValueError("Nie znaleziono 偶adnych kryteri贸w")
254
+
255
+ # Sprawd藕 sum臋 wag
256
+ total_weight = sum(c['weight'] for c in criteria)
257
+ if abs(total_weight - 100) > 0.01:
258
+ self.logger.warning(
259
+ f"Suma wag kryteri贸w ({total_weight}%) r贸偶ni si臋 od 100%"
260
+ )
261
+
262
+ # Sprawd藕 unikalno艣膰 nazw
263
+ names = [c['name'] for c in criteria]
264
+ if len(names) != len(set(names)):
265
+ raise ValueError("Znaleziono duplikaty w nazwach kryteri贸w")
266
+
267
+ # Sprawd藕 wymagane pola
268
+ required_fields = {'name', 'weight', 'description'}
269
+ for criterion in criteria:
270
+ missing_fields = required_fields - set(criterion.keys())
271
+ if missing_fields:
272
+ raise ValueError(f"Brakuj膮ce pola w kryterium: {missing_fields}")
273
+
274
+ async def _validate_analysis(self, analysis: Dict) -> None:
275
+ """
276
+ Waliduje analiz臋 oferty.
277
+
278
+ Args:
279
+ analysis: Analiza do walidacji
280
+
281
+ Raises:
282
+ ValueError: Gdy analiza nie spe艂nia wymaga艅
283
+ """
284
+ required_fields = {
285
+ 'evaluations', 'strengths', 'weaknesses',
286
+ 'summary', 'recommendations'
287
+ }
288
+
289
+ missing_fields = required_fields - set(analysis.keys())
290
+ if missing_fields:
291
+ raise ValueError(f"Brakuj膮ce pola w analizie: {missing_fields}")
292
+
293
+ if not analysis['evaluations']:
294
+ raise ValueError("Brak ocen w analizie")
295
+
296
+ for eval in analysis['evaluations']:
297
+ if not (0 <= eval['score'] <= 100):
298
+ raise ValueError(
299
+ f"Nieprawid艂owa ocena: {eval['score']} "
300
+ f"dla kryterium {eval['criterion_name']}"
301
+ )
302
+
303
+ async def close(self):
304
+ """Zamyka klienta HTTP"""
305
+ await self.client.aclose()
306
+
307
+ async def __aenter__(self):
308
+ """Context manager entry"""
309
+ return self
310
+
311
+ async def __aexit__(self, exc_type, exc_val, exc_tb):
312
+ """Context manager exit"""
313
+ await self.close()