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