这一篇是"真实做过"和"看过教程"的分水岭。
一、工程与环境
1. Python 3.6 装不上现代 AI 库
本机默认 Python 3.6,pip install langgraph 直接失败(还触发了安全删除钩子导致 pip 崩)。
解法:装 Python 3.14 建独立 venv
python -m venv .venv
.\.venv\Scripts\python.exe -m pip install -r requirements.txt
注意:装包时先 $env:PYTHONPATH="",
否则会继承 IDE 注入的 sitecustomize.py 导致 shutil.move 报错。
2. dataclass 可变默认值
# ❌ ValueError: mutable default <class 'list'> for field spans
spans: List[Span] = []
# ✅
spans: List[Span] = field(default_factory=list)
3. FastAPI on_event("startup") 已废弃
改成 lifespan:
@asynccontextmanager
async def lifespan(app: FastAPI):
_startup()
yield
app = FastAPI(lifespan=lifespan)
4. Maven BOM 模式下 java.version 不生效
报 -source 8 中不支持 文本块。
必须显式写 maven.compiler.release(详见第 09 篇)。
二、Agent 编排
5. 同步图 + async 服务 = 事件循环阻塞
LangGraph 的 invoke 是同步的,在 async def 里直接调用会把整个服务卡死。
解法:线程池执行 + loop.call_soon_threadsafe 回传事件
def sink(event): loop.call_soon_threadsafe(queue.put_nowait, event)
asyncio.get_running_loop().run_in_executor(None, worker)
6. 死循环烧 token
早期版本没有迭代上限,遇到"工具一直返回空"时 Agent 无限重试。 解法:三道闸(MAX_ITERATIONS=6 / Reflect 最多补 2 步 / Plan 最多 3 步)。
7. 模型输出的 JSON 不干净
模型经常输出 ```json {...} ```,或者前后带解释文字。
解法:容错解析器(去代码块 → 截取首个 {...} → 兜底返回 {})。
8. 单元测试收不到中间事件
最初测试里 list(runner.run(...)) 只拿到 final。
因为中间事件走的是 sink 回调,不是 yield。
解法:测试里传 sink=events.append。
9. Mock LLM 没被注入到工具里
monkeypatch.setattr(providers, "get_llm", ...) 无效——
因为 tools.py 里是 from ..llm.providers import get_llm,已经绑定了原函数。
解法:直接替换全局单例 providers._llm_instance = FakeLLM()。
三、RAG
10. 中文检索效果差
纯哈希向量对中文短句区分度不足。 改进:中文用**单字 + 相邻双字(bigram)**做 token, 再叠加 BM25 关键词召回,用 RRF 融合。
11. 检索不到时模型硬编
这是幻觉的最大来源。 解法:低于阈值直接返回空,让 Agent 明确说"知识库未覆盖"。
12. 切分把条款切开
按字数硬切会把"定义"和"例外"拆到两个 chunk。 解法:先按 Markdown 标题切块,超长再递归按句切。
四、工具与安全
13. Text2SQL 字段名写错就永远失败
第一版没有把数据库报错回传模型,导致同样的错误一直重犯。
解法:把 lastError 拼进 prompt 重生成一次(实测一次修正成功率明显提升)。
14. 多语句注入
SELECT * FROM t; DROP TABLE x 必须拦。
解法:去掉注释后检测 ; 后面还有内容 → 直接拒绝。
15. eval() 计算器的风险
用 AST 白名单实现,只允许数字常量 + 四则运算 + 幂运算。
五、前端
16. EventSource 不支持 POST
解法:fetch + ReadableStream 手动解析 SSE。
17. 中文被 TCP 分包截断
decoder.decode(value, { stream: true }) // ✅ 必须加 stream: true
buffer = parts.pop() ?? '' // ✅ 保留不完整的尾巴
18. SSE 事件顺序与打字机
后端在线程池里跑图,事件通过 call_soon_threadsafe 回传,
顺序由队列保证,不会乱序。
六、还没做 / 下一步优化清单
| 优先级 | 事项 | 说明 |
|---|---|---|
| P0 | MCP 接入 | ToolRegistry 已经抽象好,换成 MCP Client 即可 |
| P0 | 向量库切 pgvector | 已有实现,改环境变量 |
| P1 | 评估集与回归 | 建 30~50 条标注,比较 Recall@5 |
| P1 | bge-reranker 本地重排 | 替代启发式重排 |
| P1 | 前端分包优化 | 当前 bundle 1.5MB,按需引入 antd |
| P2 | 多智能体 Supervisor | LangGraph 加 supervisor 节点挂子图 |
| P2 | vLLM 私有化部署 | 内网不出网 |
| P2 | LangSmith/LangFuse 并行上报 | 适配位已留 |
| P2 | 工具执行超时与熔断 | 目前只有 LLM 层有超时 |
七、一句话总结
Agent 项目真正的难点不在"能不能跑通 demo", 而在效果可评估、成本可控制、出错可追溯、风险可管控。 这四点,我在项目里分别对应:RAG 阈值兜底 + 引用溯源、迭代上限 + Token 统计、 全链路 Trace 落盘、四层护栏 + HITL。