Редактировать react.py
This commit is contained in:
@@ -1,11 +1,17 @@
|
|||||||
|
# rag/functions/react.py
|
||||||
"""
|
"""
|
||||||
Цикл ReAct (Reasoning + Acting) для вызова внешних инструментов.
|
Цикл ReAct (Reasoning + Acting) для вызова внешних инструментов.
|
||||||
Модель может генерировать мысль и действие, вызывать инструмент,
|
Модель может генерировать мысль и действие, вызывать инструмент,
|
||||||
получать результат и продолжать, пока не будет готов финальный ответ.
|
получать результат и продолжать, пока не будет готов финальный ответ.
|
||||||
|
|
||||||
|
Улучшен fallback-парсинг JSON:
|
||||||
|
- Если модель возвращает невалидный JSON, пытается извлечь JSON-объект из текста.
|
||||||
|
- Если JSON не найден, ищет поля "final_answer" или "action" через регулярные выражения.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
import re
|
||||||
from typing import List, Dict, Any, Callable, Awaitable, Optional
|
from typing import List, Dict, Any, Callable, Awaitable, Optional
|
||||||
|
|
||||||
from rag.services.giga_client import GigaClient
|
from rag.services.giga_client import GigaClient
|
||||||
@@ -14,6 +20,22 @@ from rag.config_models import AppConfig
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def _extract_json_from_text(text: str) -> Optional[Dict[str, Any]]:
|
||||||
|
"""
|
||||||
|
Пытается извлечь JSON-объект из текста, используя регулярное выражение для поиска
|
||||||
|
фигурных скобок. Если найден валидный JSON, возвращает его, иначе None.
|
||||||
|
"""
|
||||||
|
# Ищем блок текста между первой открывающей и последней закрывающей фигурной скобкой
|
||||||
|
# Жадный поиск, чтобы захватить весь объект
|
||||||
|
match = re.search(r'(\{.*\})', text, re.DOTALL)
|
||||||
|
if match:
|
||||||
|
try:
|
||||||
|
return json.loads(match.group(1))
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
async def react_loop(
|
async def react_loop(
|
||||||
query: str,
|
query: str,
|
||||||
history: List[Dict[str, str]],
|
history: List[Dict[str, str]],
|
||||||
@@ -78,20 +100,49 @@ async def react_loop(
|
|||||||
temperature=getattr(config, 'react_temperature', 0.3),
|
temperature=getattr(config, 'react_temperature', 0.3),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
data = None
|
||||||
|
|
||||||
|
# ---- Попытка 1: стандартный парсинг JSON ----
|
||||||
try:
|
try:
|
||||||
data = json.loads(response.strip())
|
data = json.loads(response.strip())
|
||||||
except json.JSONDecodeError:
|
except json.JSONDecodeError:
|
||||||
logger.error(f"Невалидный JSON от модели: {response[:200]}")
|
logger.debug("Невалидный JSON, пробуем извлечь JSON из текста")
|
||||||
if "final_answer" in response.lower():
|
# ---- Попытка 2: извлечь JSON из текста ----
|
||||||
import re
|
data = _extract_json_from_text(response)
|
||||||
match = re.search(r'"final_answer"\s*:\s*"([^"]+)"', response)
|
|
||||||
if match:
|
|
||||||
return match.group(1)
|
|
||||||
raise RuntimeError(f"Модель вернула невалидный JSON на итерации {iteration}: {response[:200]}")
|
|
||||||
|
|
||||||
|
# ---- Если JSON всё ещё не получен, пробуем регулярки для ключевых полей ----
|
||||||
|
if data is None:
|
||||||
|
logger.debug("Не удалось извлечь JSON, пытаемся найти ключевые поля через regex")
|
||||||
|
final_answer_match = re.search(r'"final_answer"\s*:\s*"([^"]+)"', response)
|
||||||
|
if final_answer_match:
|
||||||
|
return final_answer_match.group(1)
|
||||||
|
action_match = re.search(r'"action"\s*:\s*"([^"]+)"', response)
|
||||||
|
action_input_match = re.search(r'"action_input"\s*:\s*({[^}]*})', response)
|
||||||
|
if action_match and action_input_match:
|
||||||
|
try:
|
||||||
|
action_input = json.loads(action_input_match.group(1))
|
||||||
|
data = {"action": action_match.group(1), "action_input": action_input}
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
# Если action_input невалидный JSON, попробуем извлечь как строку
|
||||||
|
action_input_str_match = re.search(r'"action_input"\s*:\s*"([^"]+)"', response)
|
||||||
|
if action_input_str_match:
|
||||||
|
data = {"action": action_match.group(1), "action_input": action_input_str_match.group(1)}
|
||||||
|
elif action_match:
|
||||||
|
# Если только action, без action_input, пробуем как есть
|
||||||
|
data = {"action": action_match.group(1), "action_input": {}}
|
||||||
|
|
||||||
|
# Если данные всё ещё не получены — ошибка
|
||||||
|
if data is None:
|
||||||
|
logger.error(f"Не удалось распарсить ответ модели: {response[:200]}")
|
||||||
|
internal_history.append({"role": "assistant", "content": response})
|
||||||
|
internal_history.append({"role": "user", "content": "Не удалось распознать действие или финальный ответ. Пожалуйста, выведи JSON с 'action' или 'final_answer'."})
|
||||||
|
continue
|
||||||
|
|
||||||
|
# ---- Проверяем наличие финального ответа ----
|
||||||
if "final_answer" in data:
|
if "final_answer" in data:
|
||||||
return data["final_answer"]
|
return data["final_answer"]
|
||||||
|
|
||||||
|
# ---- Проверяем наличие действия ----
|
||||||
action = data.get("action")
|
action = data.get("action")
|
||||||
action_input = data.get("action_input", {})
|
action_input = data.get("action_input", {})
|
||||||
|
|
||||||
@@ -100,6 +151,7 @@ async def react_loop(
|
|||||||
internal_history.append({"role": "user", "content": "Не удалось распознать действие или финальный ответ. Пожалуйста, выведи JSON с 'action' или 'final_answer'."})
|
internal_history.append({"role": "user", "content": "Не удалось распознать действие или финальный ответ. Пожалуйста, выведи JSON с 'action' или 'final_answer'."})
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
# ---- Находим обработчик инструмента ----
|
||||||
handler = None
|
handler = None
|
||||||
for tool in tools:
|
for tool in tools:
|
||||||
if tool["name"] == action:
|
if tool["name"] == action:
|
||||||
@@ -113,6 +165,7 @@ async def react_loop(
|
|||||||
internal_history.append({"role": "user", "content": f"Ошибка: {error_msg}. Попробуй другой инструмент или дай финальный ответ."})
|
internal_history.append({"role": "user", "content": f"Ошибка: {error_msg}. Попробуй другой инструмент или дай финальный ответ."})
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
# ---- Вызываем обработчик ----
|
||||||
try:
|
try:
|
||||||
result = await handler(action_input)
|
result = await handler(action_input)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
Reference in New Issue
Block a user