11. 项目驱动学习法
十一、项目驱动学习法¶
先把两个半成品放到同一所校园里¶
只给一个 RAG 项目,练习容易停在”资料查出来,答案能流式返回”这一层。只给一个 Agent 项目,又会一头扎进工具循环和动作执行,资料底座、引用可信和版本更新都被丢在身后。贴近真实工程的练法是在同一个业务世界里同时练两种能力。
场景收在同一所校园里。第一条项目线是校园知识库问答系统,资料来自校历、教务通知、奖助学金说明、宿舍规则、报修流程、社团活动说明和学生事务 FAQ,练的是”怎样把校园资料查对”。第二条项目线是校园论坛求助治理 Agent,面对迟迟没人响应的求助帖、过长的 C2C 撮合链路和越拖越重的长周期跟进对话,练的是”当系统开始主动做事以后,边界会怎么突然变硬”。
两条线不是毫无关系的样板。学生问”宿舍空调坏了怎么报修”,知识库项目回答流程、地点和时间。论坛里有人发帖说”6 号楼空调坏了两天,报修电话打不通”,Agent 项目碰到识别、匹配、回帖、私信、跟进和人工接管。前者处理稳定资料,后者处理活的请求。前者装一套查证能力,后者把系统推向持续运行和动作治理。
同样是校园场景,查资料和推进事情是两种不同的工程手感。
先做一个校园知识库问答系统¶
第一条线做得朴素一点。系统目标是校园知识库问答服务。老师和学生上传校历、制度通知、办事指南和 FAQ,系统处理成可检索内容,另一个接口接收问题并返回答案。入口只留最少一组,POST /documents 负责上传,POST /chat 负责普通问答,POST /chat/stream 负责流式回答。数据库里只放最基础的几个对象,Document、Chunk 和 ChatLog 足够把第一版跑起来。
type Document struct {
ID string
Title string
Source string
RawText string
CreatedAt time.Time
}
type Chunk struct {
ID string
DocumentID string
Position int
Text string
Embedding []float32
}
type ChatLog struct {
ID string
Question string
Answer string
CreatedAt time.Time
}
// POST /documents
// POST /chat
// POST /chat/stream
按最直接的办法,v1 的实现在同步请求里完成解析、切块、embedding 和落库。问答请求先检索 chunks,把命中的几段文本拼进 prompt,让模型组织回答。用户想边看边等时,走 SSE 把模型增量结果持续往前端推。学生问”奖学金申请截止到哪天””宿舍空调坏了怎么报修””晚归要怎么报备”,系统能给出一版可演示的回答。
| 对象 | 现在承载什么 | 读到这里你该开始不安什么 |
|---|---|---|
Document |
文档标题、来源、原始正文和上传时间 | 它现在还说不清哪一份才是当前有效版本 |
Chunk |
被切开的文本、顺序和 embedding | 它能被召回,却还不能证明引用一定可信 |
ChatLog |
问题、答案和最基础的时间信息 | 它能留痕,但还回答不了这次到底错在检索还是生成 |
上传一份奖助学金通知,系统处理完就能被问。学生问”宿舍空调坏了怎么报修”,系统从宿舍办事指南里找出流程,再把答案流式推回来。问题从这里开始出现。
教务处发了一版新的退选时间表,老版本是否已退出召回范围。学生问到的是这学期的奖学金规则,系统是否会顺带把去年旧通知也召回。
文档增多后,上传链开始暴露问题。校历 PDF 还好,层级复杂的教务通知、混着表格和图片的奖助学金说明、带附件的宿舍管理细则会让 POST /documents 请求时间持续拉长。解析中途失败、embedding 超时或数据库写入中断时,这份资料是否算”已经入库”,重传该从头做还是接着做,系统说不清。
在线问答同样面临压力。学生看到的是完整回答,系统内部却在把检索结果、引用、流式连接和模型调用绑在一条链上。答案说”根据学生手册,晚归需要在 22:30 前报备”,哪一部分来自真实命中的 chunk,哪一部分是模型补的语气,无法区分。浏览器断开后,检索、模型流和 goroutine 是否一起停止,还是用户已离开,系统继续烧 token。
| 你先看到的现象 | 背后卡住的是哪类边界 |
|---|---|
| 上传一份大的通知 PDF 要等很久 | 任务状态 |
| 处理中途失败后不知道该不该重传 | 恢复点与幂等 |
| 新旧通知容易一起被召回 | 版本边界 |
| 答案看起来带了依据,但不敢完全信 | 引用装配 |
| 页面关了,流式链路可能还在跑 | 取消传播 |
| 改了切块或检索后,说不清到底变好没有 | 观测与评测 |
第一版能演示后,这些问题不会自行消失。知识库线继续向前推进时,任务化、版本、引用、取消传播和最小评测闭环迟早都要补齐。
这条项目线的重点不是”有没有做出一个问答框”,而是能否分出三个迭代台阶。v1 只要求链路闭环:上传、检索、回答和最基本流式跑通。v2 补版本、引用、异步建库、取消传播和最小评测集。v3 才轮到 Hybrid、rerank、增量索引、缓存和灰度。项目做不深,不是因为不会技术,而是每一版都同时追求所有能力,最后既没有主链,也说不清每一轮解决了哪种系统问题。
再做一个校园论坛求助治理 Agent¶
第二条线换一个更活的场景。校园论坛里每天都会出现求助帖:找二手教材、问宿舍报修、求活动搭子、发失物信息,或把具体困难丢上来求快速回应。响应常常来得太慢,真正有帮助的人和信息隔着一长串无效往返。等事情终于推进起来,帖子下已堆满杂乱回复。
这条项目线不再只是”把资料查对”,而是让系统做一串更主动的动作。先巡逻新帖子和求助会话,识别哪些内容真的是求助,再抽取求助对象、紧急度、是否适合公开暴露的信息。然后找候选资源、候选帮助者或对应校内流程。最后通过插件执行公开回帖、站内信撮合或创建跟进任务。事情没有在一轮里结束时,继续维护状态,把长周期对话压缩成下一轮真正需要的事实。
type HelpPost struct {
ID string
Title string
Body string
Visibility string
AuthorID string
}
type AgentRun struct {
ID string
PostID string
Status string
CurrentStep string
}
type AgentAction struct {
ID string
RunID string
Kind string
TargetID string
Status string
}
type MatchCandidate struct {
ID string
Kind string
Score float64
Reason string
}
type ConversationSummary struct {
ThreadID string
Facts string
OpenIssues string
}
// scan_help_posts
// extract_help_signal
// search_candidates
// reply_post
// send_direct_message
// create_followup_task
这里最小的 v1 能做很具体的事。论坛里有人发帖说”6 号楼空调坏了两天,电话一直打不通”,系统先识别为求助,再从校园知识库里查到宿舍报修流程,决定先公开回一条能帮更多人复用的信息。帖子是”谁有离散数学教材可以转一下,明天急用”,更可能走候选匹配和站内信撮合。帖子里带了敏感信息或已有人接手,切到另一种动作节奏。
| 对象或动作 | 现在在做什么 | 读到这里你该开始不安什么 |
|---|---|---|
HelpPost |
保存求助帖和最初的上下文 | 它还没有天然区分公开信息和不该公开的信息 |
AgentRun |
记录这次自动治理走到了哪一步 | 它一旦中断,谁来接着跑、接着看,还没说清 |
AgentAction |
记录回帖、私信、建任务这些动作 | 动作做了,但是否幂等、是否可追责,还不够稳 |
MatchCandidate |
存候选资源或候选帮助者 | 为什么匹配到这个人或这个资源,解释仍然很薄 |
ConversationSummary |
压缩长对话里的关键事实 | 压缩之后保住的是业务事实,还是只省了 token,还得再看 |
这条链比知识库项目更像”系统会做事了”。帖子一进来,系统能识别、匹配、回帖、私信,必要时还能丢跟进任务给值班同学。拿去演示时,冲击力很容易超过单纯问答。
问题也更快出现。同一条求助帖被巡逻任务扫到两次,系统是否会连续发两封站内信。只该私下提醒的内容,如果被公开回帖说出去,责任归模型、插件还是边界没立住。
论坛求助和纯问答最大的区别在此。系统一旦开始主动回帖、发私信、建跟进任务,问题不再是”答得对不对”。它变成:动作有没有重复触发,公开和私密边界有没有分清,人工接手后 Agent 会不会继续误跑,长对话压缩以后保住了哪些事实,这次撮合有没有真的帮到人。
求助不是一轮结束的。今天公开回了流程,明天有人私信补充情况,后天又有人接手。每一轮都把全量对话重新塞回模型,成本和噪声一起上涨,真正关键的业务事实越来越散。语义压缩不再只是”为了省 token”,而是在问:哪些事实必须保住,哪些寒暄和重复说明该折叠掉。
| 你先看到的现象 | 背后卡住的是哪类边界 |
|---|---|
| 同一条求助帖被重复识别、重复联系 | 去重与幂等 |
| 公开回帖说过了头,暴露了不该公开的信息 | 公开/私密边界 |
| 私信已经发出去了,却说不清是谁触发的 | 审计与 trace |
| 建了跟进任务,但后续没有真正回收结果 | 结果闭环 |
| 对话越拖越长,模型越来越贵也越来越乱 | 长周期状态与语义压缩 |
| 人工接手以后,Agent 还可能继续按旧状态推进 | 人工接管与运行状态 |
这些约束不会只靠调 prompt 解决。Agent 线继续向前推进时,迟早要回答动作边界、插件幂等、私信与回帖的风险分层、人工接管、状态恢复、长对话压缩和结果评估。难点不是”系统会不会说”,而是”系统开始做事以后,谁还在掌控它”。
这条线也适合按迭代台阶练。v1 只做识别、查询知识库和建议动作,不直接触发高风险写操作。v2 引入回帖、私信、建跟进任务,同时补幂等键、审计日志和人工接管。v3 继续做长周期状态压缩、checkpoint、失败恢复和多角色协作。第一版不会被 Agent 全栈复杂度淹没,同时能稳定看到”为什么一旦系统开始主动做事,治理成本会突然陡增”。
让两条项目线一起往前长¶
两个半成品放在同一个校园里练,最大的好处不是题材更统一,而是两种复杂度根本不是一回事。校园知识库项目在处理文档、版本、引用、取消和评测。论坛 Agent 项目在处理动作、状态、审计、人工接管和长周期对话。前者学会把资料查对,后者学会把事情推进。
两条线不是断开的。论坛 Agent 会自然地调用知识库能力,因为很多求助先需要一个靠谱的流程解释,再决定后面要不要回帖、私信或建任务。把资料查对不等于事情能推进。把事情推进起来,也不等于资料底座已经足够稳。两条线互相牵扯,放在一起练更像真实系统。
可能先受不了知识库里旧通知和新通知混着召回,也可能先受不了论坛 Agent 给同一帖子重复发私信。哪边先动手不重要。项目不再被当成”跑通一次就结束”的 demo。反复回到同一个校园场景里,问自己:这一轮补掉了哪一层边界,系统和上一轮相比到底哪里不一样了。
知识库项目有了清楚的版本和引用,论坛 Agent 调到这层能力时动作会更稳一些。论坛 Agent 的回帖、私信、跟进和人工接管能被解释、被追责、被恢复后,知识库项目”只回答问题”的边界也会显得更清楚一些。两个项目一边并列,一边互相抬高难度。
项目驱动学习不再只是”做一个能跑的东西”。它在同一个业务世界里,同时练会查资料的系统和会推进事情的系统。再在一轮轮迭代里,把它们从半成品慢慢做得像系统。