40 天搭"贾维斯"AI 助手,我学到的 6 件事
从 19 个 bug 里提炼出来的工程原则

5 月 25 日,我开始动手做"贾维斯"——一个本地的 AI 助手系统。
到 7 月 2 日,这个系统已经能跑、能听、能看、能用工具、能派子代理、能记笔记。
40 天、40+ 后端文件、196 个测试、19 个有记录的 bug。
我做这件事,不是为了造一个 ChatGPT 替代品,而是想搞懂一件事:
一个 AI 助手系统,到底是怎么把"模型 + 工具 + 记忆 + 上下文"拼到一起,还能稳定跑 40 天的?
40 天下来,我学到了 6 件事。每一件,都是被一个真实 bug 教会的。

1. 一次会话里,每个 LLM 调用都得"挂账"
最早我的 AI 助手是这样工作的:
- • 主对话:用用户选的模型
- • 工具迭代:回退到全局默认
- • Subagent:用
MODELS字典里的"硬编码" - • 主题生成:用另一个 fallback
听起来很"灵活"?实际上,4 个不同的 LLM 调用,4 个不同的模型。
有一天,我用自定义 Provider 接了一个 glm-5.2(不在 MODELS 注册表里),对话跑到一半,subagent 调完,主对话崩了:
Stream chat error: All providers failed: (errors 空)为什么?因为 subagent 调完回来,主对话进入工具迭代,新一帧 LLM 调用把 instance 弄丢了。glm-5.2 不在注册表,fallback chain 也找不到任何 provider,errors 列表就是空的。
教训:一次对话内所有 LLM 调用——主对话、工具迭代、subagent、reduce 阶段、主题生成——必须共用同一个 model + ProviderInstance,任何一处漏传 instance,都会让自定义 Provider 模型掉到 fallback 上然后崩。
怎么修:在 ChatEngine 三处入口解析一次 instance,然后统一传给所有下游。不是每个调用自己"想起来"要传——这迟早会漏。
2. Subagent 没工具循环,就是"思想实验"
我的 CoderSubagent 收到"帮我写个文件"的任务,返回 success=True,但文件根本没创建。
为什么?因为我最初的 BaseSubagent.run() 是单轮 LLM 调用:
LLM 收任务 → 输出 markdown 代码块 → 结束 → 返回 successLLM 写得对不对、写没写、有没有 markdown 包裹——它不验证。它只负责返回文本,然后我把 success=True 塞回去。
文档里我甚至还写了"需要工具循环的子类应重写此方法 (见 ResearcherSubagent)"——但 Researcher 也没重写。
教训:Subagent 是个"完整的、能干的、独立的小助手",不是主对话的影子。它必须有自己的工具循环:
LLM 输出
tool_use→ 执行工具 → 把结果回注 → 继续,直到没有工具或达到max_iterations
否则,subagent 永远只能"想",不能"做"。
怎么修:
- •
BaseSubagent.run()改成多轮:解析tool_use→ 执行 → 格式化结果 → 拼回 messages → 递归 - • 递归保护:子代理里再调 subagent 工具,直接拦死——子代理不递归
- • 白名单:子代理只能调你允许它调的工具,别让一个
Researcher突然去bash删文件
3. 流式 / 非流式行为必须一致
我的主对话用流式(看起来正常),但 subagent 跑完之后的工具迭代走的是非流式。
有一天我用 DeepSeek 代理测试,主对话好好的,subagent 一调就 400:
[Subagent coder] failed: [anthropic] Client error '400 Bad Request'
for url 'https://api.deepseek.com/anthropic/v1/messages'为什么?因为我同一个 AnthropicAdapter 里:
- • 流式
chat_stream_full:payload 里没有temperature字段 - • 非流式
_messages:payload 里有temperature字段
DeepSeek 的非流式端点对 temperature 校验严格(或者根本不接受)。Ollama 端点宽松,没暴露。
教训:同一个 adapter 的流式和非流式,行为必须 byte-by-byte 一致——除了"是不是 streaming"。参数列表、字段命名、字段顺序,有一处不一致,某一天某个 provider 就会抓狂。
我后来把所有 payload 都对齐了,并且保留 HTTP 错误响应体的 debug 日志(前 500 字符)——否则下次 400,又得猜。
4. 安全是默认约束,不是可选
我有段时间,文件读写工具是这么写的:
def read(path):
with open(path) as f:
return f.read()直到我跑测试时传了一个 ../../../../etc/passwd,真的读到了。
这就是路径穿越。我的"工作目录"防不住"相对路径"。
教训:只要工具接收用户输入(就算这输入来自 LLM),路径穿越、命令黑名单、超时上限——这些是默认约束,不是"等出事了再加"。
怎么修:
- • 路径:
Path.resolve()+Path.relative_to(workdir)双重验证 - • 命令:危险命令黑名单(
rm -rf /、shutdown、...) - • 工具:错误响应前必须先
logger.error(),否则你都不知道出过事 - • 同步:用户消息同步到后端,必须有重试——网络抖动一下,消息就没了,用户根本不知道
AI 助手的安全边界,比传统 Web 应用更模糊——攻击面不止 HTTP,还有 LLM 自己生成的参数。
5. 上下文管理不是银弹,是策略
最早我的对话是这样管理的:
history = get_history(limit=10)看起来够用?长对话一跑,第 11 条之后的信息永久丢失。而且 limit=10 是按"消息条数"算的,不是按 token——一条带长 code 的消息可能就 5000 token。
后来我加了 ContextManager,但又走了另一个极端:以为它能解决一切。
实际上,token 预算 + 滑动窗口 + 摘要 + 记忆注入,是 4 个独立的策略,每个有自己的适用场景:
- • 滑动窗口——适合闲聊、客服;不适合需要历史上下文的代码讨论
- • 摘要压缩——适合长会议纪要;不适合细节密集的逻辑推理
- • 记忆注入——适合跨会话的事实;不适合当前会话的临时状态
- • Hybrid(默认)——适合大多数场景;不适合对延迟极敏感的场景
教训:没有"一种上下文管理策略适合所有场景"。先想清楚你要解决的"什么场景下的什么痛点",再选策略。不要看到 token 预算就上——它解决了"长对话崩",但带来了"早期信息被丢",得用摘要补。
我最后默认用 Hybrid,但允许用户指定。关键是日志:每次构建消息,记录 history_in / history_out / dropped / memory / tokens_estimate / budget_available,否则调优全靠猜。
6. 架构模式选对了,少写一半代码
40 天里,我用了 5 个核心模式:
- • Mediator(
JarvisMediator)——所有引擎互不知道对方存在,加新引擎不动老代码 - • Strategy(AI Provider、TaskExecutor、ContextManager)——"换一种实现"不用改业务逻辑
- • Repository(MemoryStore:SQLite + LanceDB)——"换一种存储"不动业务
- • Registry(ProviderRegistry、ToolRegistry)——工具 / provider 注册一次,自动发现
- • Event-Driven(JarvisEvent + SSE)——前端订阅事件,后端不关心前端
听起来像废话?但每加一个引擎,我确实没动过老代码——只要新引擎有 execute(),Mediator 就接得住。
教训:架构模式不是炫技,是"延迟决定"。Mediator 让"以后要不要加 voice 引擎"这个决定晚 2 个月做;Strategy 让"以后要不要换 Qwen3-TTS"这个决定晚半年做。晚做决定 = 少返工。
但模式不是越多越好。我有过一段时间想上 CQRS、Event Sourcing——对单用户本地 AI 助手来说,过度设计。我后来把它们都砍了,留下 5 个刚好够用的。
写在最后:40 天后,我想明白了什么
一个 AI 助手系统,真正难的不是"接上模型"——一行代码的事。
真正难的是:
让"主对话 + 工具迭代 + subagent + 主题生成"共用一个 instance,而不是各自回退到默认值; 让 subagent 有真正的工具循环,而不是只输出想法; 让流式和非流式行为一致,而不是让某个 provider 在某个分支上 400; 让路径穿越防护是默认,而不是事后补; 让上下文管理可策略化,而不是硬编码
limit=10; 让架构模式是"延迟决定"的工具,而不是炫技。
做这件事之前,我以为"AI 助手 = LLM + UI"。
做这件事之后,我发现:
AI 助手 = LLM + UI + 上下文管理 + 工具系统 + Subagent 编排 + 记忆 + 安全 + 跨 provider 一致性 + 196 个测试 + 19 个 bug + 5 个设计模式 + 一堆日志。
模型会变,工具会变,UI 会变。
但这 40 天踩过的坑,值得记一辈子。
作者:会飞的加菲猫 · 一个普通开发者的 AI 助手系统踩坑实录
夜雨聆风