Multi-Agent:Deep Research实战记录
本周工作总结
项目概述
Deep Research:输入一个问题,4 个 Agent 并行搜索 → 3 轮对抗验证 → 合成 final report。
一、本周主要工作
- 去除 CrewAI 依赖:自研轻量 multi-agent 框架(core/)
- 加固系统鲁棒性:JSON 解析、Pydantic 构造、ReAct 检测全面升级
- 容器化 + Web API:Docker 镜像 + FastAPI 服务
- 云部署上线:腾讯云运行,公网可访问
二、为什么要替换 CrewAI
问题 1: result.raw 行为不稳定
CrewAI 的 crew.kickoff_async() 返回的 result.raw 在不同场景下行为不一致:
- 同一个任务调用不同模型,
.raw有时返回 JSON,有时返回空 - Qwen 在 thinking-mode 下把输出放在
reasoning_content而不是content,.raw拿到的是空字符串 - 模型正常输出了完整 JSON,但 CrewAI 内部多层转换后
.raw丢失了内容
结果:同样的代码跑同一个问题,有时能解析成功有时不能。上一个运行的日志里,technical 研究员的 raw_transcript 里明明有完整的 JSON:
{"perspective": "technical", "research_question": "...", "key_findings": [...]}
但 parse_json_response(raw) 失败——说明 .raw 没把这个 JSON 传回来。
问题 2: Qwen thinking-mode 空 content
Qwen 模型在推理模式下不填充 content 字段:
choice.message.content = None
choice.message.reasoning_content = "完整的 JSON 响应"
CrewAI 读 content 拿到 None,认为 Agent 没输出。只能通过 monkey-patch litellm.completion 修复——如果 LiteLLM 内部 API 改名,patch 就静默失效。
问题 3: 样板代码臃肿
每次让 Agent 执行任务需要:
agent = create_researcher(perspective) # 创建 Agent
task = Task(description=..., agent=agent) # 创建 Task
crew = Crew(agents=[agent], tasks=[task], ...) # 创建 Crew
result = await crew.kickoff_async() # 启动
raw = result.raw # 取值
5 步。自研后:
raw = await agent.run(task_description)
问题 4: Agent 间数据共享靠 SQLite
Phase 1 产出的 ResearchCards 要传给 Phase 2 和 Phase 3,只能通过 SQLite:
Phase 1 → 写 SQLite → Phase 2 → 读 SQLite → Phase 3 → 读 SQLite
每次读写都是 Pydantic → JSON string → SQLite → JSON string → Pydantic 的序列化往返,还引入了 database is locked 的并发风险。
自研后加了 Blackboard(内存共享存储),Agent 间数据传递零延迟。
替换前后对比
| CrewAI 版 | 自研版 | |
|---|---|---|
| Agent 调用 | 5 行样板 | 1 行 |
raw 可靠性 |
不可预测 | 直接返回 agent.run() 结果 |
| 工具系统 | BaseTool._run() |
BuildTool 4 阶段管线(验证→权限→执行→格式化) |
| Agent 间通信 | SQLite | Blackboard 内存共享 |
| 重试 | 每次重建 Agent+Task+Crew | 复用 Agent 实例 |
| 依赖包数 | 30+ | 6 |
三、鲁棒性加固
问题 1: JSON 解析过于脆弱
原因:原始代码的 JSON 提取逻辑只处理 ```json ... ``` 一种 fence 格式,且分散在 3 个文件里各自实现。模型稍微换个输出方式(前缀加 **、不用 fence 直接 {...}、中文格式),系统就崩。
修复:
- 4 级多策略解析器:直接 JSON → fence 提取(支持
```/~~~/''')→ 括号配对正则提取 → 常见错误自动修复(trailing commas、PythonTrue/None等) - 统一为
response_parser.py:3 个文件的重复实现全部删除,单一事实来源
问题 2: Pydantic 构造零容错
原因:模型输出被直接解包为 Pydantic 模型——VerificationEntry(**model_output)。模型多加了一个字段、enum 值大小写不匹配("HIGH" vs "high"),直接 ValidationError。
修复:safe_construct 安全构造层——过滤模型多出的字段、enum 值大小写归一化、ValidationError 时尝试部分字段构造(model_construct),单个元素失败不影响整批。
问题 3: ReAct 检测只匹配英文
原因:_is_valid_output 只检查 "Thought:" 和 "Action:"。中文模型输出"思考:...行动:...",完全检测不到。
修复:多语言 ReAct 模式匹配(中/日/英 + Step N: + Plan: 变体),同时改为不直接拒绝——如果文本里同时有 { 和 },先提取 JSON 再决定。
四、容器化 + Web API + 云部署
架构
浏览器 / curl → http://118.25.102.38:8000/docs → FastAPI → DeepSeek API
7 个 API 端点:
POST /research → 提交问题,立即返回 run_id
GET /research/{id} → 轮询状态
GET /research/{id}/report → 下载报告
GET /research/{id}/evidence → 证据 JSON
GET /research → 历史列表
GET /health → 健康检查
云部署中遇到的问题和解决
| 问题 | 原因 | 解决 |
|---|---|---|
pip install 超时(90 秒+) |
PyPI 官方源 files.pythonhosted.org 从国内服务器访问极慢 |
Dockerfile 加 PIP_INDEX_URL=https://pypi.tuna.tsinghua.edu.cn/simple |
hatchling 构建失败 |
pip 24.0 镜像解析 + 构建系统下载超时 | 跳过 pip install -e .,改用 PYTHONPATH=/app/src 直接导入 |
database is locked |
本地的 data/ 目录通过 scp 上传后,带有本地残留的 SQLite 锁文件 |
删掉 data 目录让 Docker 重建,chmod 777 确保容器用户可写 |
UNIQUE constraint failed |
Web API 和 pipeline.run_research() 各自创建了一次 Run 记录,两者 timestamp 相同导致重复 ID |
pipeline.py 加 run_id 参数,API 预先创建后传入,pipeline 跳过重复创建 |
No module named 'orjson' |
openai 包的内置 JSON 序列化依赖 orjson,Dockerfile 没显式安装 |
Dockerfile 补上 openai 和 orjson |
| Compressor 上下文集压缩失败 | Compressor._summarise() 硬编码 model="openai/deepseek-chat" 但没传 api_key |
Compressor 和 Agent 共享 LLM 配置,自动注入 api_base 和 api_key |
五、最终指标
| 指标 | 数值 |
|---|---|
| 代码文件 | 30 个 |
| 测试 | 35 个,全部通过 |
| 外部依赖 | 0 个重量框架 |
| 一次研究耗时 | 3-5 分钟 |
| 服务器 | 腾讯云 2 核 2GB |
更多推荐

所有评论(0)