from langchain_openai import ChatOpenAI from langchain_core.messages import HumanMessage from pydantic import BaseModel, Field, ValidationError from state import ReflectState # Инициализация LLM llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0.7) # Pydantic модель для валидации ответа class ValidAnswer(BaseModel): """Модель для валидации качества ответа""" content: str = Field(description="Ответ на вопрос") is_complete: bool = Field(description="Полнота ответа (должно быть True)") has_examples: bool = Field(description="Наличие примеров (должно быть True)") is_concise: bool = Field(description="Отсутствие воды (должно быть True)") def validate_quality(self) -> tuple[bool, str]: """Проверяет качество ответа по критериям""" issues = [] if not self.is_complete: issues.append("неполный ответ") if not self.has_examples: issues.append("нет конкретных примеров") if not self.is_concise: issues.append("содержит воду/общие фразы") if not self.content or len(self.content.split()) < 50: issues.append("слишком короткий ответ (<50 слов)") if len(self.content.split()) > 300: issues.append("слишком длинный ответ (>300 слов)") if issues: return False, f"Проблемы: {', '.join(issues)}" return True, "OK" # Промпт для генерации с инструкцией по качеству ANSWER_PROMPT = """Ты — эксперт, который дает качественные ответы. Требования к ответу: 1. Полнота: ответ покрывает все аспекты вопроса 2. Конкретика: есть примеры, детали, точные формулировки 3. Краткость: 5-10 предложений, без воды Вопрос: {question} После ответа добавь метаданные в формате JSON: {{ "is_complete": true/false, "has_examples": true/false, "is_concise": true/false }} Сам ответ должен быть в markdown формате. """ def generate_answer(state: ReflectState) -> ReflectState: """ Узел генерации ответа с try/except и валидацией При ошибке заполняет поле error """ print(f"\n📝 [Попытка {state['round']}/{state['max_rounds']}] Генерация ответа...") try: # 1. Генерация ответа prompt = ANSWER_PROMPT.format(question=state["question"]) response = llm.invoke([HumanMessage(content=prompt)]) # 2. Парсинг JSON-метаданных из ответа import json import re # Ищем JSON в конце ответа json_match = re.search(r'\{[^{}]*"is_complete"[^{}]*\}', response.content) if not json_match: raise ValueError("Не найдены метаданные JSON в ответе") metadata = json.loads(json_match.group()) # 3. Создаем объект ValidAnswer answer_obj = ValidAnswer( content=response.content, is_complete=metadata.get("is_complete", False), has_examples=metadata.get("has_examples", False), is_concise=metadata.get("is_concise", False) ) # 4. Валидация качества is_valid, error_message = answer_obj.validate_quality() if not is_valid: raise ValueError(error_message) # 5. Успех - очищаем ошибку print(f" ✅ Ответ прошел валидацию!") return { **state, "draft": answer_obj.content, "error": None } except (ValidationError, ValueError, json.JSONDecodeError) as e: # Перехватываем все ошибки валидации error_msg = str(e) print(f" ❌ Ошибка валидации: {error_msg}") return { **state, "draft": "", # Очищаем невалидный ответ "error": error_msg } except Exception as e: # Другие ошибки (сеть, API и т.д.) print(f" ❌ Критическая ошибка: {str(e)}") return { **state, "draft": "", "error": f"Критическая ошибка: {str(e)}" }