第一版跑通了,挺有成就感。
接着用就发现问题了。界面对话区太小,长回答只能看两三行。问AI"哪段时间观众反应最热烈",它回了一句"没有消息时间,无法比较时段"。
页面上明明每条评论都标了时间。AI为什么说没有?
页面有时间,不代表AI收到了时间
第一反应是去查代码。翻了存储层,时间字段好好躺在那。翻了页面渲染,时间也正常显示。那AI为什么说没有?
这节课教了一个排查思路:别急着改代码,先沿着数据流走一遍,看信息在哪一步丢的。
一个AI应用从用户提问到拿到回答,中间经过这些环节:
采集与存储 → 程序处理(筛选/截断/统计) → 组装模型请求 → 模型生成 → 页面呈现
每一步负责不同的事,也掌握不同的信息。存储里有时间,不代表模型请求里有时间。页面显示了时间,不代表模型"看到"了时间。
顺着走了一遍,找到了。CommentStore.for_ai_prompt()这个方法,组装发给模型的上下文时,只取了昵称和内容:
yaml
存储里的原始记录:
nickname: 示例观众01, content: 大家晚上好, timestamp: 2026-10-12T20:00:05+08:00
实际传给模型的内容:
示例观众01: 大家晚上好
时间字段在上下文组装这一步被扔掉了。模型没收到,当然回答不了时间相关的问题。
信息在哪丢的,就在哪补。
AI应用的调试,跟传统调试不是一回事
以前写前端,debug基本就这些事:打断点、看变量、查DOM、追调用栈。逻辑是确定的,输入A必然得到输出B。
AI应用多了个不确定因素:模型。
模型那一步你控制不了。你能控制的是:传给模型的信息够不够、任务描述清不清楚、输出格式有没有约束。
所以AI应用的调试要分两层:
程序层------信息有没有正确传递。时间字段有没有进请求?筛选逻辑有没有丢数据?截断之后还剩多少条?这些是确定性的,可以写测试。
模型层------信息够了之后,模型的理解和回答质量行不行。给了时间它能正确统计吗?能区分"评论最多"和"情绪最正面"吗?这些不是确定性的,需要评估集。
这两层混在一起查,很容易搞混。程序层的问题用测试修,模型层的问题用评估集调。方向不一样。
"回答更好"不是一个可执行的需求
修完时间字段,你以为完了?没有。
时间补进去了,但"哪段时间反应最热烈"这个问题本身还有歧义。热烈是指评论条数最多?参与人数最多?还是正面情绪最集中?
不同定义对应不同的统计方式。你得先选定一个可以核对的口径。
课里选了这个:按采集时间,以一分钟为窗口,这批记录中哪一分钟评论条数最多。
定清楚之后,"回答更好"才变成了一个可验证的目标。不然你永远不知道模型的回答算对还是算错。
跟AI协作开发,"更好""更智能""更准确"这些词都是无效需求,得翻译成可核对的标准。
修了一个字段,后面还有一串事
时间字段补上了,接着又是一堆问题:
截断问题。 项目设置了上下文长度上限,超了就只保留最近的评论。如果最热闹的时段在被截掉的前半段,模型的答案还是错的。怎么解决?两种思路:要么回答时明确说"基于当前可见记录",要么让程序先做完整统计再把结果给模型。
历史对话污染。 用户问了第一轮,模型回答了。接着问第二轮,历史对话里带着第一轮的结论。如果两轮用的不是同一批评论,模型可能被上一轮的结论带偏。
时间含义。 存的时间戳是采集器的本地采集时间,不是平台原始发送时间。批量接收的消息可能有相同时间戳。这个时间能说明什么、不能说明什么,得写清楚。
一个字段背后,拖着一串约定。每个约定都得明确,不然模型就会在你没想到的地方出错。
评估集:怎么判断AI做得对不对
传统软件测试,输入确定,预期确定,跑一遍就知道过没过。
AI应用没那么简单。同一个问题问三遍,回答可能都不一样。怎么判断"做得对不对"?
课里的做法是建评估集。不是拿一百条随便问,而是精心设计几条有区分力的案例:
- 单一高峰 vs 并列高峰------能不能正确统计并完整报告
- 时间缺失 vs 时区跨界------能不能说明限制而不是瞎编
- 早期高峰被截断------能不能识别输入范围,不冒充完整历史
- 评论密集但包含质疑------能不能区分"活跃"和"正面"
- 无评论或新旧记录冲突------能不能处理空输入
每条案例都有"必须成立的事实"和"不能推出的结论"。模型回答完之后,对照这些标准打分。
一次回答正确不代表做得好。用固定案例反复跑,才能看出稳定性。
重构的时候怎么保证不改坏
功能能用之后,总想把代码整理一下。但一改就怕把原来能用的搞坏了。
课里用了一个方法:特征测试(Characterization Test)。
思路很简单:不改代码,先跑一遍,把当前的实际行为记下来作为基线。然后开始重构。每改一步,跑一遍测试,看行为有没有变。变了就停下来查原因。
跟TDD的区别在于:TDD的预期来自"需求应该是什么",特征测试的预期来自"旧代码实际做了什么"。
旧代码的行为可能包含bug,没关系,先记下来。重构的目的是改结构不改行为。bug留着后面单独修。
有个关键动作:挑一个关键断言,故意制造偏差,看测试能不能发现。如果测试发现不了,说明这个测试本身不可靠。
一次修改怎么成为下一次开发的起点
这节课最后讲了一个我之前没想过的概念:维护记录。
改完代码不算完。你得把这次改了什么、为什么改、哪些测试保留、哪些期望被替代、实际验证到了什么范围,全部写下来。
不是为了写文档而写文档。是因为下一次你或者Agent接手的时候,需要知道:
- 现在实际能做什么(当前行为)
- 哪些行为必须保持(有效约定)
- 这次改了什么(本轮变化)
- 什么有证据,什么还没确认(验证与遗留)
代码写的是"现在长什么样",不写"为什么长这样"。没有变更记录,接手的人只能猜。Agent也一样,读代码能读出结构,读不出决策原因。
所以Spec不是一次性的东西。需求变了Spec跟着变,Spec变了测试跟着变,测试变了实现跟着变。每次变更都要同步更新,保持三份东西一致。
SpecDD在持续维护阶段的价值:不是帮你写出第一版,是让每一版都能接着往下做。
你在维护AI项目的时候,遇到过最头疼的"改了一个地方,另一个地方莫名其妙坏了"的情况是什么?