10 · 踩坑记录与优化清单

12 阅读4分钟

这一篇是"真实做过"和"看过教程"的分水岭。

一、工程与环境

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 回传, 顺序由队列保证,不会乱序。

六、还没做 / 下一步优化清单

优先级事项说明
P0MCP 接入ToolRegistry 已经抽象好,换成 MCP Client 即可
P0向量库切 pgvector已有实现,改环境变量
P1评估集与回归建 30~50 条标注,比较 Recall@5
P1bge-reranker 本地重排替代启发式重排
P1前端分包优化当前 bundle 1.5MB,按需引入 antd
P2多智能体 SupervisorLangGraph 加 supervisor 节点挂子图
P2vLLM 私有化部署内网不出网
P2LangSmith/LangFuse 并行上报适配位已留
P2工具执行超时与熔断目前只有 LLM 层有超时

七、一句话总结

Agent 项目真正的难点不在"能不能跑通 demo", 而在效果可评估、成本可控制、出错可追溯、风险可管控。 这四点,我在项目里分别对应:RAG 阈值兜底 + 引用溯源、迭代上限 + Token 统计、 全链路 Trace 落盘、四层护栏 + HITL。


下一篇:11 · 如何把项目写进简历与面试