Commit
·
758bfb1
1
Parent(s):
9043954
Fix: Improve agent response extraction and add better error handling for truncated JSON
Browse files- agents/__init__.py +15 -10
- agents/__pycache__/__init__.cpython-314.pyc +0 -0
- utils/agent_runner.py +86 -10
agents/__init__.py
CHANGED
|
@@ -23,7 +23,7 @@ if not hf_token:
|
|
| 23 |
model = InferenceClientModel(
|
| 24 |
model_id="deepseek-ai/DeepSeek-V3.1-Terminus",
|
| 25 |
token=hf_token,
|
| 26 |
-
max_tokens=
|
| 27 |
)
|
| 28 |
|
| 29 |
final_tool = FinalAnswerTool()
|
|
@@ -35,6 +35,8 @@ parser_agent = ToolCallingAgent(
|
|
| 35 |
instructions="""
|
| 36 |
Ты эксперт по анализу системных логов. Твоя задача - парсить сырые логи и преобразовывать их в структурированный JSON формат.
|
| 37 |
|
|
|
|
|
|
|
| 38 |
Для каждой строки лога определи:
|
| 39 |
- timestamp: временная метка (если есть)
|
| 40 |
- level: уровень логирования (INFO, WARNING, ERROR, CRITICAL, DEBUG, TRACE)
|
|
@@ -48,7 +50,7 @@ parser_agent = ToolCallingAgent(
|
|
| 48 |
|
| 49 |
Временной диапазон (time_range) должен содержать start и end - первую и последнюю временную метку.
|
| 50 |
|
| 51 |
-
Ответ строго верни в JSON через final_answer в следующем
|
| 52 |
{
|
| 53 |
"events": [{"line_number": int, "timestamp": "str|null", "level": "str", "message": "str", "type": "str"}, ...],
|
| 54 |
"errors": [{"line_number": int, "timestamp": "str|null", "level": "str", "message": "str", "type": "str"}, ...],
|
|
@@ -221,17 +223,20 @@ def run_gpt_prompt_agent(structured_data: dict, anomaly_report: dict, recommenda
|
|
| 221 |
}
|
| 222 |
input_json = json.dumps(input_data, ensure_ascii=False, indent=2)
|
| 223 |
|
| 224 |
-
result = run_agent_safely(gpt_prompt_agent, task=input_json)
|
| 225 |
|
| 226 |
-
# Результат должен быть строкой (промпт)
|
| 227 |
-
if isinstance(result,
|
| 228 |
-
return result["answer"]
|
| 229 |
-
elif isinstance(result, str):
|
| 230 |
return result
|
| 231 |
-
|
| 232 |
-
# Если
|
| 233 |
-
if
|
|
|
|
|
|
|
| 234 |
return result["prompt"]
|
|
|
|
|
|
|
|
|
|
| 235 |
return str(result)
|
| 236 |
|
| 237 |
__all__ = [
|
|
|
|
| 23 |
model = InferenceClientModel(
|
| 24 |
model_id="deepseek-ai/DeepSeek-V3.1-Terminus",
|
| 25 |
token=hf_token,
|
| 26 |
+
max_tokens=4096 # Увеличено для больших промптов и JSON
|
| 27 |
)
|
| 28 |
|
| 29 |
final_tool = FinalAnswerTool()
|
|
|
|
| 35 |
instructions="""
|
| 36 |
Ты эксперт по анализу системных логов. Твоя задача - парсить сырые логи и преобразовывать их в структурированный JSON формат.
|
| 37 |
|
| 38 |
+
ВАЖНО: Твой ответ должен быть ПОЛНЫМ и ЗАВЕРШЁННЫМ JSON объектом. Не обрезай ответ!
|
| 39 |
+
|
| 40 |
Для каждой строки лога определи:
|
| 41 |
- timestamp: временная метка (если есть)
|
| 42 |
- level: уровень логирования (INFO, WARNING, ERROR, CRITICAL, DEBUG, TRACE)
|
|
|
|
| 50 |
|
| 51 |
Временной диапазон (time_range) должен содержать start и end - первую и последнюю временную метку.
|
| 52 |
|
| 53 |
+
Ответ строго верни в JSON через final_answer в следующем формате (ОБЯЗАТЕЛЬНО ПОЛНЫЙ JSON):
|
| 54 |
{
|
| 55 |
"events": [{"line_number": int, "timestamp": "str|null", "level": "str", "message": "str", "type": "str"}, ...],
|
| 56 |
"errors": [{"line_number": int, "timestamp": "str|null", "level": "str", "message": "str", "type": "str"}, ...],
|
|
|
|
| 223 |
}
|
| 224 |
input_json = json.dumps(input_data, ensure_ascii=False, indent=2)
|
| 225 |
|
| 226 |
+
result = run_agent_safely(gpt_prompt_agent, task=input_json, return_string=True)
|
| 227 |
|
| 228 |
+
# Результат должен быть строкой (промпт)
|
| 229 |
+
if isinstance(result, str):
|
|
|
|
|
|
|
| 230 |
return result
|
| 231 |
+
elif isinstance(result, dict):
|
| 232 |
+
# Если вернулся словарь, попробуем извлечь текст
|
| 233 |
+
if "answer" in result:
|
| 234 |
+
return result["answer"]
|
| 235 |
+
elif "prompt" in result:
|
| 236 |
return result["prompt"]
|
| 237 |
+
else:
|
| 238 |
+
return json.dumps(result, ensure_ascii=False, indent=2)
|
| 239 |
+
else:
|
| 240 |
return str(result)
|
| 241 |
|
| 242 |
__all__ = [
|
agents/__pycache__/__init__.cpython-314.pyc
CHANGED
|
Binary files a/agents/__pycache__/__init__.cpython-314.pyc and b/agents/__pycache__/__init__.cpython-314.pyc differ
|
|
|
utils/agent_runner.py
CHANGED
|
@@ -1,17 +1,18 @@
|
|
| 1 |
# utils/agent_runner.py
|
| 2 |
import json
|
| 3 |
|
| 4 |
-
def run_agent_safely(agent, max_retries=3, **kwargs):
|
| 5 |
"""
|
| 6 |
Безопасно запускает агента с повторными попытками при ошибках.
|
| 7 |
|
| 8 |
Args:
|
| 9 |
agent: Экземпляр агента smolagents
|
| 10 |
max_retries: Максимальное количество попыток
|
|
|
|
| 11 |
**kwargs: Аргументы для передачи агенту
|
| 12 |
|
| 13 |
Returns:
|
| 14 |
-
dict: Распарсенный JSON ответ от
|
| 15 |
"""
|
| 16 |
last_error = None
|
| 17 |
|
|
@@ -19,21 +20,96 @@ def run_agent_safely(agent, max_retries=3, **kwargs):
|
|
| 19 |
try:
|
| 20 |
result = agent.run(**kwargs)
|
| 21 |
|
| 22 |
-
# Извлекаем ответ из результата
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
raw = result
|
|
|
|
|
|
|
|
|
|
| 27 |
|
| 28 |
if not isinstance(raw, str):
|
| 29 |
-
raise ValueError("Agent output is not a string")
|
| 30 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 31 |
# Парсим JSON
|
| 32 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
|
| 34 |
except Exception as e:
|
| 35 |
last_error = e
|
| 36 |
-
print(f"[{agent.name}]
|
| 37 |
|
| 38 |
raise ValueError(
|
| 39 |
f"[{agent.name}] Failed after {max_retries} attempts.\nLast error: {last_error}"
|
|
|
|
| 1 |
# utils/agent_runner.py
|
| 2 |
import json
|
| 3 |
|
| 4 |
+
def run_agent_safely(agent, max_retries=3, return_string=False, **kwargs):
|
| 5 |
"""
|
| 6 |
Безопасно запускает агента с повторными попытками при ошибках.
|
| 7 |
|
| 8 |
Args:
|
| 9 |
agent: Экземпляр агента smolagents
|
| 10 |
max_retries: Максимальное количество попыток
|
| 11 |
+
return_string: Если True, возвращает строку вместо парсинга JSON
|
| 12 |
**kwargs: Аргументы для передачи агенту
|
| 13 |
|
| 14 |
Returns:
|
| 15 |
+
dict или str: Распарсенный JSON ответ или строка (в зависимости от return_string)
|
| 16 |
"""
|
| 17 |
last_error = None
|
| 18 |
|
|
|
|
| 20 |
try:
|
| 21 |
result = agent.run(**kwargs)
|
| 22 |
|
| 23 |
+
# Извлекаем ответ из результата smolagents
|
| 24 |
+
# smolagents возвращает AgentText объект, который нужно правильно обработать
|
| 25 |
+
raw = None
|
| 26 |
+
|
| 27 |
+
# Если это AgentText объект (из smolagents), получаем полный текст
|
| 28 |
+
# AgentText может содержать обрезанный вывод при str(), но полный текст доступен через другие методы
|
| 29 |
+
if hasattr(result, 'to_string'):
|
| 30 |
+
raw = result.to_string()
|
| 31 |
+
elif hasattr(result, 'text'):
|
| 32 |
+
raw = result.text
|
| 33 |
+
elif hasattr(result, '__getitem__'):
|
| 34 |
+
# Если это последовательность или маппинг, пробуем извлечь
|
| 35 |
+
try:
|
| 36 |
+
if 'answer' in result:
|
| 37 |
+
raw = result['answer']
|
| 38 |
+
except:
|
| 39 |
+
pass
|
| 40 |
+
elif isinstance(result, dict):
|
| 41 |
+
# Ищем ответ в различных возможных полях
|
| 42 |
+
if "answer" in result:
|
| 43 |
+
raw = result["answer"]
|
| 44 |
+
elif "content" in result:
|
| 45 |
+
raw = result["content"]
|
| 46 |
+
elif "text" in result:
|
| 47 |
+
raw = result["text"]
|
| 48 |
+
elif "response" in result:
|
| 49 |
+
raw = result["response"]
|
| 50 |
+
else:
|
| 51 |
+
# Если словарь не содержит текстовых полей, преобразуем в JSON строку
|
| 52 |
+
raw = json.dumps(result, ensure_ascii=False)
|
| 53 |
+
elif isinstance(result, str):
|
| 54 |
raw = result
|
| 55 |
+
else:
|
| 56 |
+
# Пробуем преобразовать в строку
|
| 57 |
+
raw = str(result)
|
| 58 |
|
| 59 |
if not isinstance(raw, str):
|
| 60 |
+
raise ValueError(f"Agent output is not a string, got {type(raw)}")
|
| 61 |
+
|
| 62 |
+
# Удаляем возможные escape-последовательности и пробелы
|
| 63 |
+
raw = raw.strip()
|
| 64 |
+
|
| 65 |
+
# Если raw содержит JSON-строку внутри (например, {"answer": "{\"key\": \"value\"}"})
|
| 66 |
+
# Нужно распарсить внешний JSON, чтобы получить внутренний
|
| 67 |
+
if raw.startswith('{"') and '"answer"' in raw:
|
| 68 |
+
try:
|
| 69 |
+
# Пробуем распарсить как JSON
|
| 70 |
+
outer_json = json.loads(raw)
|
| 71 |
+
if isinstance(outer_json, dict) and "answer" in outer_json:
|
| 72 |
+
raw = outer_json["answer"]
|
| 73 |
+
except json.JSONDecodeError:
|
| 74 |
+
# Если не получается распарсить, возможно JSON обрезан
|
| 75 |
+
# Пробуем найти полный JSON внутри
|
| 76 |
+
pass
|
| 77 |
+
|
| 78 |
+
# Если нужно вернуть строку (для GPT Prompt агента)
|
| 79 |
+
if return_string:
|
| 80 |
+
return raw
|
| 81 |
+
|
| 82 |
+
# Для JSON агентов - пытаемся распарсить
|
| 83 |
+
# Если строка обрезана (начинается с { но не заканчивается }), пробуем исправить
|
| 84 |
+
if raw.startswith('{') and not raw.rstrip().endswith('}'):
|
| 85 |
+
# Пытаемся найти последнюю закрывающ��ю скобку
|
| 86 |
+
last_brace = raw.rfind('}')
|
| 87 |
+
if last_brace > len(raw) * 0.8: # Если скобка не слишком близко к началу
|
| 88 |
+
raw = raw[:last_brace + 1]
|
| 89 |
+
|
| 90 |
# Парсим JSON
|
| 91 |
+
try:
|
| 92 |
+
return json.loads(raw)
|
| 93 |
+
except json.JSONDecodeError as e:
|
| 94 |
+
# Если это не JSON, пробуем найти JSON внутри строки
|
| 95 |
+
# Ищем первую { и последнюю }
|
| 96 |
+
start_idx = raw.find('{')
|
| 97 |
+
end_idx = raw.rfind('}')
|
| 98 |
+
if start_idx >= 0 and end_idx > start_idx:
|
| 99 |
+
json_part = raw[start_idx:end_idx + 1]
|
| 100 |
+
try:
|
| 101 |
+
return json.loads(json_part)
|
| 102 |
+
except:
|
| 103 |
+
pass
|
| 104 |
+
|
| 105 |
+
# Если всё равно не получается, это ошибка для JSON-агентов
|
| 106 |
+
if not raw.startswith('{') and not raw.startswith('['):
|
| 107 |
+
raise ValueError(f"Expected JSON but got plain text (first 200 chars): {raw[:200]}")
|
| 108 |
+
raise ValueError(f"JSON decode error: {e}. Raw (first 500 chars): {raw[:500]}")
|
| 109 |
|
| 110 |
except Exception as e:
|
| 111 |
last_error = e
|
| 112 |
+
print(f"[{agent.name}] Attempt {attempt}/{max_retries} failed: {e}")
|
| 113 |
|
| 114 |
raise ValueError(
|
| 115 |
f"[{agent.name}] Failed after {max_retries} attempts.\nLast error: {last_error}"
|