029AI Agent阅读记录
第 029 卷

从零开始 AI Agent 实战:这个项目要做什么,以及为什么这样学

把一个真实的钉钉问题群和一堆官方文档,做成能追问、能引用、能转工单的智能客服 Agent。

乌漆嘛黑和 Ahri
第 029 期

这个项目要做什么,以及为什么这样学

本文是专栏总览,不负责把所有概念讲完。你会先看到最终产品,再按问题出现的顺序实现它。

先看这个群

这个专栏的语料不是造出来的。它来自一个真实的桌面客户端问题定位群,五个月,618 条消息:

类型条数能拿到什么
文本463正文
视频59只有占位符
图片46只有占位符
卡片30标题和描述
文件12只有占位符
其他8链接、位置、系统消息

463 条文本里,相当一部分是「+1」「我也是」「收到」。一个问题的结论常常落在第 7 条回复上,而且是「哦,是这个原因」这种脱离上下文就没有意义的短句。有人问完就没人理,问题挂在那里再没有下文。

另一半语料是文档:安装手册、升级说明、排查指南。写得清楚、有标题层级、可以按章节引用——但覆盖不了真实故障的长尾。群里那些问题,多数手册里没有。

这两样东西加起来,就是一个客服 Agent 能拿到的全部知识。 一边是权威但不全的文档,一边是全但脏的聊天记录。

项目背景与技术规划

企业内部的技术支持长期面临一个尴尬的断层:官方手册写得规整严密,但更新慢,且永远覆盖不了线上真实故障的长尾;一线排障群里每天都在产出最鲜活的绕过方案(workaround)与版本踩坑结论,但信息高度交错、信噪比极低,随着刷屏迅速被埋没。

本项目要把这两个来源做成一个真正能在生产运行的智能客服 Agent,技术规划上有三个核心取向:

  1. 双源统一入库,不对群聊做人工蒸馏:不做高成本且无法扩展的人工 QA 问答对抽取,直接将群聊重建为会话线程片段,与官方文档一同落入统一的 pgvector 向量空间,加群即加语料。
  2. 服务端刚性约束高于模型自主权:涉及工单写操作、多租户数据权限、置信度拒答和状态机流转时,一律由服务端代码硬性裁决。模型提出意图,代码执行校验。
  3. 问题驱动的渐进式演进:基础设施不提前铺设,每一个组件(从数据库事务、异步任务队列到 LangGraph 检查点)都必须由前一阶段暴露的真实系统缺陷逼出来。

成品对话

用户:点桌面图标没反应,任务管理器里能看到进程
Agent:这个现象群里出现过几次。先确认两件事:客户端版本,和你的系统版本?

用户:3.1.4,Windows 10
Agent:3.1.4 上有个已知问题,渲染进程起不来但主进程还在。

      官方《排查指南》4.3 节的做法是清理缓存目录后重启。
      不过群里 2026-07-14 有人碰到同样现象,结论是要先结束残留进程再清缓存,
      只清缓存会复发。

      两个说法都给你,按群里的顺序试更稳。

用户:都试了,还是不行
Agent:那这条超出已知范围了,我建一张问题工单转给客服。提交前确认:
      版本=3.1.4,系统=Windows 10,现象=点击无窗口但有进程,
      已尝试=结束残留进程 + 清理缓存
      [确认提交] [修改信息] [取消]

用户:确认提交
Agent:已创建工单 TICKET-000042。

这段对话里,每一步都是工程代码兜底的:模型不知道用户版本就必须,而不是猜;文档和群聊冲突时两边都给并标注时间,而不是挑一个;答不出时转工单,而不是编一个解法;建工单前停下来等确认,而不是直接写库。

Preview
一次提问的两种结局:带引用作答,或诚实地转工单

检索和 Agent 的分界线

上面那段对话,纯检索系统做不到的是第二句:反问

检索系统收到「点桌面图标没反应」,会返回最相似的几条内容,然后结束。它不知道自己缺了版本号这个关键信息,也没有机制去要。用户拿到一堆可能相关的文档,自己去筛。

Agent 是一个受约束的循环:问题 -> 模型决策 -> 工具/检索 -> 结果 -> 模型继续决策。模型负责提出下一步,服务端负责验证和执行下一步。「我需要先知道版本」本身就是一个决策。

Preview
只会搜的检索与 Agent 的受约束循环

边界很明确:模型可以建议动作,但不能直接读数据库、改权限或伪造工具结果。所有副作用都经过工具注册表、参数校验、权限检查、幂等检查和审计。

两条接入管线,一个 chunk 池

Preview
双源语料与协议边界,右侧三处可替换的接缝

群聊和文档的解析和切分策略完全不同,但最终必须落进同一个向量空间,否则检索时没法比较。

聊天客户端本地库                    上传的 PDF / DOCX / TXT / MD
      │  只读、脱敏、稳定引用                  │  解析、清洗、保留标题层级
      ▼                                       ▼
  版本化导出包(JSONL)                    文档记录
      │                                       │
  ── 协议边界:下游不碰原始库、CID、UID ──     │
      │                                       │
  线程重建(按对话单元切)            结构切分(按标题 + 重叠窗口切)
      └───────────────┬───────────────────────┘

          统一 chunk 表(source_type 区分来源)

              Embedding → pgvector

采集侧被单独做成了一个独立的导出工具,只输出版本化的协议包。这条边界一次解决三件事:语言分界(采集是 Node,下游全是 Python)、合规分界(真实会话 ID、用户 ID、媒体地址止步于此)、可复现(你拿一份合成导出包就能跑完第 9 篇之后的全部内容,不需要任何真实聊天数据)。

图里右侧那三处接缝值得先记住。第 5 篇把 JSON 文件换成 PostgreSQL、第 13 篇把手写循环换成 LangGraph,都是整层替换,但因为依赖方向是单向的,改动都没有溢出到相邻层。这是第 1 篇花力气分层的回报。

为什么不是先学一堆基础设施

学习顺序按「问题逼出工具」排列:

阶段先遇到的失败引出的能力
1领域逻辑散落在 API 里分层、Repository、状态机
2一次性等待模型导致界面卡住Provider、SSE、取消
3模型输出了无法执行的调用Schema、工具循环、步数上限
4手测无法证明改进可重复评测基线
5JSON 文件并发撞号PostgreSQL、事务、唯一约束
6所有人都能读到内部排查手册JWT、RBAC、双侧权限校验
7三个人问同一个问题,建出三张工单审批、幂等、审计
8标准 SQLite 打不开聊天客户端的库只读采集、稳定引用、脱敏、增量协议
9302 页 PDF 让请求超时异步任务、重叠窗口切分、状态轮询
10检索群聊,Top-1 返回一句「+1 我也是」线程重建、统一 chunk、双源冲突
11相关内容排在第 6 条混合召回、改写、重排
12找不到答案时模型开始编造排查步骤引用、拒答、转工单、Memory
13审批中服务重启丢状态LangGraph、检查点、恢复
14浏览器断线后重复显示 tokenSSE 去重、前端状态机
15上线后不知道慢在哪里、花多少钱机器人回群、Trace、成本、限流

环境和分支约定

第 1 篇只需要 Python 3.12 和 uv。第 2~4 篇使用 MockProvider,不需要模型 Key。第 5 篇开始需要 Docker;第 9 篇开始需要真实的 Embedding API;第 11 篇开始需要真实模型 API,Mock 只能验证流程,不能证明召回质量。

第 8 篇讲采集,需要一份聊天客户端的数据库副本。仓库提供合成的库和跑好的导出包——你可以直接从第 9 篇开始,不碰任何真实聊天数据。

git clone <ai-agent-guide 仓库地>
cd ai-agent-guide
uv sync
uv run pytest -q

每一篇对应一个 tag:part-01part-15。文章只解释本篇新增的关键代码,完整项目通过 tag 和 git diff 对照,避免读者复制一堆互相矛盾的半成品。

从哪里开始

总览验收标准

本文读完不要求你写代码,但应该能回答:

  1. 哪些动作必须由服务端执行,为什么不能让模型直接执行?
  2. 为什么群聊和文档不能用同一套切分策略,但必须落进同一张表?
  3. 官方文档和群聊结论冲突时,系统应该怎么办?
  4. 当知识库没有答案时,系统是拒答,还是创建工单?判定依据是什么?
  5. 每篇文章会用什么指标证明「变好了」?

下一篇会在空目录中建立第一版项目,并故意让 API 层先变得难以测试,再用领域模型和 Repository 把它拆开。