Files
MathModelAgent/backend/app/core/agents/modeler_agent.py
T
MuWinds ac57ad0c18 refactor: 重构 LLM Provider 模式、WebSocket 重连、图表字体跨平台兼容 (#70)
LLM 层重构:
- 将 LLM 调用层重构为 Provider 模式,支持多 API 类型
- 新增 Anthropic、OpenAI Chat、OpenAI Responses 三种 Provider
- 新增 LLM 类型定义和工厂类

WebSocket 重连:
- 添加指数退避自动重连机制(初始 1s,最大 30s,最多 10 次)
- 区分手动关闭与意外断开,仅意外断开触发重连
- 在任务页面顶部添加连接状态指示灯(绿/黄/红)

图表字体跨平台兼容:
- 在 backend/fonts/ 存放 SimHei 字体文件
- create_work_dir() 自动复制字体到任务工作目录
- 两个解释器从工作目录动态加载字体
- E2B 移除 apt-get install 依赖,改用上传字体文件
- 统一三处字体优先级配置

其他:
- 重构注释规范、修复类型错误、添加 lint hook
- 更新 CLAUDE.md 项目文档
2026-05-15 10:04:47 +08:00

113 lines
3.7 KiB
Python

"""建模手 Agent 模块,负责分析问题并制定数学建模方案。"""
from app.core.agents.agent import Agent
from app.core.llm.llm import LLM
from app.core.prompts import MODELER_PROMPT
from app.schemas.A2A import CoordinatorToModeler, ModelerToCoder
from app.utils.log_util import logger
import json
import re
from icecream import ic # type: ignore[import-unresolved]
def repair_json(json_str: str) -> dict | None:
"""尝试修复 LLM 输出的格式错误的 JSON。
Args:
json_str: 可能包含格式错误的 JSON 字符串。
Returns:
修复后的字典,无法修复时返回 None。
"""
json_str = json_str.replace("```json", "").replace("```", "").strip()
# Try direct parse first
try:
return json.loads(json_str)
except json.JSONDecodeError:
pass
# Fix unescaped newlines and quotes inside string values
try:
fixed = re.sub(
r'(?<=: ")(.*?)(?=",\s*\n\s*"|"\s*\n\s*})',
lambda m: m.group(0).replace('"', '\\"'),
json_str,
flags=re.DOTALL,
)
return json.loads(fixed)
except (json.JSONDecodeError, re.error):
pass
# Extract key-value pairs with regex as last resort
try:
pattern = r'"(\w+)"\s*:\s*"((?:[^"\\]|\\.|"(?!,\s*\n)|"(?!\s*\n\s*}))*)"'
matches = re.findall(pattern, json_str, re.DOTALL)
if matches:
return {k: v.replace('\\"', '"') for k, v in matches}
except re.error:
pass
return None
class ModelerAgent(Agent):
"""建模手 Agent,分析问题类型并制定建模方案、求解方法和可视化策略。"""
def __init__(
self,
task_id: str,
model: LLM,
context_window: int = 128000,
) -> None:
super().__init__(task_id, model, context_window)
self.system_prompt = MODELER_PROMPT
async def run(self, coordinator_to_modeler: CoordinatorToModeler) -> ModelerToCoder: # type: ignore[reportIncompatibleMethodOverride]
"""根据协调者拆解的问题生成建模方案。
Args:
coordinator_to_modeler: 协调者传递的结构化问题信息。
Returns:
ModelerToCoder 对象,包含各问题的建模解决方案。
"""
await self.append_chat_history(
{"role": "system", "content": self.system_prompt}
)
await self.append_chat_history(
{
"role": "user",
"content": json.dumps(coordinator_to_modeler.questions),
}
)
attempt = 0
while True:
response = await self.model.chat(
history=self.chat_history,
agent_name=self.__class__.__name__,
)
json_str = response.content
if not json_str:
raise ValueError("返回的 JSON 字符串为空,请检查输入内容。")
questions_solution = repair_json(json_str)
if questions_solution:
ic(questions_solution)
return ModelerToCoder(questions_solution=questions_solution)
attempt += 1
logger.warning(
f"JSON 解析失败 (第{attempt}次),请求模型重新生成"
)
await self.append_chat_history(
{"role": "assistant", "content": json_str}
)
await self.append_chat_history(
{
"role": "user",
"content": "你返回的JSON格式有误,请严格按照JSON格式重新输出,注意字符串值内的双引号必须转义为\\\",不要包含未转义的特殊字符。",
}
)