add
This commit is contained in:
@@ -1,120 +1,139 @@
|
||||
from langchain_openai import ChatOpenAI
|
||||
from langchain_core.messages import HumanMessage
|
||||
from pydantic import BaseModel, Field, ValidationError
|
||||
from langchain_core.messages import HumanMessage, SystemMessage
|
||||
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)")
|
||||
# Промпты для узлов
|
||||
DRAFT_PROMPT = """Ты — эксперт, который дает краткие, но содержательные ответы.
|
||||
Ответь на вопрос пользователя в 5-10 предложений.
|
||||
Будь конкретным, избегай общих фраз и "воды".
|
||||
|
||||
def validate_quality(self) -> tuple[bool, str]:
|
||||
"""Проверяет качество ответа по критериям"""
|
||||
issues = []
|
||||
Вопрос: {question}"""
|
||||
|
||||
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 = """Ты — эксперт, который дает качественные ответы.
|
||||
Требования к ответу:
|
||||
REFLECT_PROMPT = """Ты — строгий критик, оценивающий качество ответа.
|
||||
Критерии оценки:
|
||||
1. Полнота: ответ покрывает все аспекты вопроса
|
||||
2. Конкретика: есть примеры, детали, точные формулировки
|
||||
3. Краткость: 5-10 предложений, без воды
|
||||
3. Отсутствие воды: нет общих фраз, повторений, лишних слов
|
||||
|
||||
Ответ для оценки: {draft}
|
||||
|
||||
Твой ответ должен быть в формате:
|
||||
VERDICT: [ok/needs_revision]
|
||||
CRITIQUE: [2-3 конкретных пункта замечаний или похвала]
|
||||
|
||||
Примеры:
|
||||
VERDICT: ok
|
||||
CRITIQUE: Ответ полный, есть примеры, нет воды.
|
||||
|
||||
VERDICT: needs_revision
|
||||
CRITIQUE: 1. Нет конкретных примеров. 2. Слишком много общих фраз. 3. Не раскрыта разница в контексте MCP."""
|
||||
|
||||
REWRITE_PROMPT = """Улучши ответ на вопрос, учитывая замечания критика.
|
||||
Вопрос: {question}
|
||||
Предыдущая версия: {draft}
|
||||
Замечания критика: {critique}
|
||||
|
||||
После ответа добавь метаданные в формате JSON:
|
||||
{{
|
||||
"is_complete": true/false,
|
||||
"has_examples": true/false,
|
||||
"is_concise": true/false
|
||||
}}
|
||||
Дай исправленную версию (5-10 предложений), которая:
|
||||
- Учитывает все замечания
|
||||
- Становится более полной и конкретной
|
||||
- Убирает воду
|
||||
- Сохраняет краткость (5-10 предложений)"""
|
||||
|
||||
Сам ответ должен быть в markdown формате.
|
||||
"""
|
||||
|
||||
def generate_answer(state: ReflectState) -> ReflectState:
|
||||
def draft_answer(state: ReflectState) -> ReflectState:
|
||||
"""
|
||||
Узел генерации ответа с try/except и валидацией
|
||||
Узел: первичная генерация ответа с try/except
|
||||
При ошибке заполняет поле error
|
||||
"""
|
||||
print(f"\n📝 [Попытка {state['round']}/{state['max_rounds']}] Генерация ответа...")
|
||||
print(f"\n📝 [Раунд {state['round']}] Генерация черновика...")
|
||||
|
||||
try:
|
||||
# 1. Генерация ответа
|
||||
prompt = ANSWER_PROMPT.format(question=state["question"])
|
||||
prompt = DRAFT_PROMPT.format(question=state["question"])
|
||||
response = llm.invoke([HumanMessage(content=prompt)])
|
||||
|
||||
# 2. Парсинг JSON-метаданных из ответа
|
||||
import json
|
||||
import re
|
||||
# Проверка качества ответа (базовая валидация)
|
||||
if len(response.content.split()) < 50:
|
||||
raise ValueError("Слишком короткий ответ (<50 слов)")
|
||||
|
||||
# Ищем JSON в конце ответа
|
||||
json_match = re.search(r'\{[^{}]*"is_complete"[^{}]*\}', response.content)
|
||||
if not json_match:
|
||||
raise ValueError("Не найдены метаданные JSON в ответе")
|
||||
if len(response.content.split()) > 300:
|
||||
raise ValueError("Слишком длинный ответ (>300 слов)")
|
||||
|
||||
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
|
||||
}
|
||||
print(f" ✅ Черновик успешно создан")
|
||||
new_state = state.copy()
|
||||
new_state["draft"] = response.content
|
||||
new_state["error"] = None # Очищаем ошибку
|
||||
return new_state
|
||||
|
||||
except Exception as e:
|
||||
# Другие ошибки (сеть, API и т.д.)
|
||||
print(f" ❌ Критическая ошибка: {str(e)}")
|
||||
return {
|
||||
**state,
|
||||
"draft": "",
|
||||
"error": f"Критическая ошибка: {str(e)}"
|
||||
}
|
||||
print(f" ❌ Ошибка генерации: {str(e)}")
|
||||
new_state = state.copy()
|
||||
new_state["draft"] = ""
|
||||
new_state["error"] = str(e)
|
||||
return new_state
|
||||
|
||||
def reflect(state: ReflectState) -> ReflectState:
|
||||
"""Узел: критическая оценка ответа LLM-критиком"""
|
||||
print(f"🔍 [Раунд {state['round']}] Рефлексия критика...")
|
||||
|
||||
try:
|
||||
prompt = REFLECT_PROMPT.format(draft=state["draft"])
|
||||
response = llm.invoke([HumanMessage(content=prompt)])
|
||||
|
||||
# Парсим ответ критика
|
||||
lines = response.content.strip().split('\n')
|
||||
verdict = "needs_revision"
|
||||
critique = response.content
|
||||
|
||||
for line in lines:
|
||||
if line.startswith("VERDICT:"):
|
||||
verdict = line.replace("VERDICT:", "").strip().lower()
|
||||
elif line.startswith("CRITIQUE:"):
|
||||
critique = line.replace("CRITIQUE:", "").strip()
|
||||
|
||||
if verdict not in ["ok", "needs_revision"]:
|
||||
verdict = "needs_revision"
|
||||
|
||||
new_state = state.copy()
|
||||
new_state["verdict"] = verdict
|
||||
new_state["critique"] = critique
|
||||
|
||||
print(f" Вердикт: {verdict.upper()}")
|
||||
if verdict == "needs_revision":
|
||||
print(f" Замечания: {critique[:100]}...")
|
||||
|
||||
return new_state
|
||||
|
||||
except Exception as e:
|
||||
# Если критик сломался, считаем что нужна доработка
|
||||
print(f" ⚠️ Ошибка критика: {str(e)}")
|
||||
new_state = state.copy()
|
||||
new_state["verdict"] = "needs_revision"
|
||||
new_state["critique"] = f"Критик не смог оценить ответ: {str(e)}"
|
||||
return new_state
|
||||
|
||||
def rewrite(state: ReflectState) -> ReflectState:
|
||||
"""Узел: переписывание ответа с учетом замечаний"""
|
||||
print(f"✏️ [Раунд {state['round']}] Переписывание с учетом критики...")
|
||||
|
||||
try:
|
||||
prompt = REWRITE_PROMPT.format(
|
||||
question=state["question"],
|
||||
draft=state["draft"],
|
||||
critique=state["critique"]
|
||||
)
|
||||
response = llm.invoke([HumanMessage(content=prompt)])
|
||||
|
||||
new_state = state.copy()
|
||||
new_state["draft"] = response.content
|
||||
new_state["round"] = state["round"] + 1
|
||||
new_state["error"] = None
|
||||
return new_state
|
||||
|
||||
except Exception as e:
|
||||
print(f" ❌ Ошибка переписывания: {str(e)}")
|
||||
new_state = state.copy()
|
||||
new_state["error"] = str(e)
|
||||
new_state["round"] = state["round"] + 1
|
||||
return new_state
|
||||
Reference in New Issue
Block a user