您可以捐助,支持我们的公益事业。

1元 10元 50元





认证码:  验证码,看不清楚?请点击刷新验证码 必填



  求知 文章 文库 Lib 视频 iPerson 课程 认证 咨询 工具 讲座 Model Center 汽车系统工程   模型库  
会员   
   
OCSMP认证课程:OCSMP-MU
4月9-10日 线上
基于模型的数据治理与数据中台
5月19-20日 北京+线上
网络安全原理与实践
5月21-22日 北京+线上
     
   
 订阅
从零搓出一个Claude Code,一篇超详细的总结!
 
作者:尤逸晖
  127   次浏览      5
 2026-3-23
 
编辑推荐:
文章主要记录了一个 Agent 开发初学者,跟着 Datawhale 的 Hello-Agent 教程一步步学习和实践的过程,希望对你的学习有帮助。
本文来自于微信公众号Datawhale,由火龙果软件Alice编辑,推荐。

写在最前:这篇文章记录了我作为一个 Agent 开发初学者,跟着 Datawhale 的 Hello-Agent 教程一步步学习和实践的过程。文中提到的很多实现方案可能并不完美,甚至可能存在更好的做法,但这些都是我真真切切踩过的坑、流过的汗。

如果你也是刚开始接触 Agent 开发,希望这篇笔记能给你一些参考;如果你已经是大佬,还请不吝赐教。

文中代码和文档地址:

https://github.com/YYHDBL/MyCodeAgent.git

一、为什么我要自己做一个 Code Agent

这两年 AI 编程助手火得一塌糊涂。

Claude Code、 GitHub Copilot、 Cursor 、Codex……工具一个比一个强。 用自然语言描述需求,它就能写代码、改 Bug、跑测试,甚至帮你排查那些以前要绞尽脑汁的线上问题。

有意思的是, Anthropic 、OpenAI 这些前沿团队也在持续公开他们的 Agent 构建经验。虽然他们的模型在国内有门槛,但 Engineering Blog 我一直在追。每次读完都很上头——你会发现,真正拉开差距的不是"提示词写得多花",而是 工程设计是否扎实 。

看了这么多,手痒了。

正好,Datawhale 的 Hello-Agent 教程最后有一个毕业设计: 用学到的知识,做一个自己的智能体应用。

我当时就想,既然日常高强度在用各种 Code Agent,不如就做一个自己的。

说白了,自己手搓一遍,才能真正理解这些产品为什么好用,以及它们到底在工程上做对了什么。

二、先跑起来!用 Hello-Agent 骨架搭出第一个能用的 Code Agent

有了方向,我没急着追什么"最优架构"。

先给自己定了一个很接地气的目标: 用户说一句需求,Agent 能自己去仓库里找证据,给出改动方案,输出补丁,我确认后能真正落盘。

说白了,我要的不是一个会聊天的 Demo,而是一个 能干活的 CLI 。

Hello-Agent 的底子正好够用—— ReActAgent 、ToolRegistry、ContextBuilder 这些核心组件都是现成的。我的策略很简单: 先复用,再改造 。

最初版本的Code Agent代码仓库:

https://github.com/YYHDBL/HelloCodeAgentCli.git

1、起手式:先复用,再改造

我的动作很克制——直接复用 Hello-Agent 的主干,把最短链路先跑通。

核心组件就这几块:

代码层面,我直接在 code_agent.py 里把它们攒起来:

self.terminal_tool = TerminalTool(
    workspace=str(self.paths.repo_root),
    timeout=60,
    confirm_dangerous=True,
    default_shell_mode=True,  # 注意这个
)
self.registry = ToolRegistry()
self.registry.register_tool(self.terminal_tool)

这里有两个关键决策,后面都成了坑:

第一, default_shell_mode=True 。

我当时想的是"先把能力放开",让模型能直接写复杂 shell,效率优先。

第二,工具堆得挺满。

不光有 terminal ,我还把 todo 、 note 、 plan 、 context_fetch 一股脑塞进去,想让它从第一天就能处理连续任务。

现在回头看,这个取舍挺典型 —— 能力上限先拉高,控制边界后补 。

2、V0 怎么算"能用"?我定了四条

不是看"回答像不像人",而是看这四件事能不能连续跑通:

  1. 能稳定多步 —— 不是一句话就结束;
  2. 能在仓库找证据 —— 目录、文件、代码片段;
  3. 能给可执行的补丁 —— 不停在口头建议;
  4. 改动可控 —— 能确认、能备份、能撤回。

为了防跑飞,我给 ReAct 加了最基础的保险:

self.react = ReActAgent(
    name="code_agent",
    llm=self.llm,
    tool_registry=self.registry,
    max_steps=20,  # 最多跑 20 步
    observation_summarizer=_summarize_observation,
    summarize_threshold_chars=1800,
)

max_steps=20 + observation 摘要,当时救了我不少次。

为啥?工具输出一旦太长,下一步提示词就会变脏,模型很容易跑偏。这套配置后来肯定不够,但 V0 阶段,它让问题 可复现、可调试 。

3、单轮流程:土,但管用

我当时最担心:每轮都走完整流程会不会太慢、太贵、太容易失控?

所以没搞"所有输入一股脑丢给 ReAct",而是先分流:

# 闲聊直接短路,不浪费 Token
if self._is_chitchat(user_input):
    return direct_response

# 构建上下文,走主流程
context_text = self.context_builder.build_base(...)
response = self.react.run(context_text, max_tokens=8000)

# 历史只留最近 50 条,粗暴但有效
if len(self.history) > 50:
    self.history = self.history[-50:]

三件小事,当时觉得挺实用:

  • 闲聊直接短路,省 Token;
  • 检测到"分步/计划"关键词,轻量提示让模型先用 todo ;
  • 历史粗暴截断,先控住上下文规模。

你会发现,这套思路已经在往"上下文工程"靠了,只是还比较轻。后面我会彻底重做这部分。

4、最关键的一步:补丁必须能落地

很多 Agent 演示到"建议修改"就结束了。

我明确不接受这种"嘴上会改"的状态,从第一天就把补丁执行链路接上了:

# 从模型回复里提取补丁
patch_text = _extract_patch(response)
if not patch_text:
    continue  # 没补丁,继续聊

# 危险操作要确认
if _patch_requires_confirmation(patch_text):
    if input("confirm> ").strip().lower() not in {"y""yes"}:
        continue

# 执行
res = patch_executor.apply(patch_text)

真正保安全的是执行器,它有几条硬规矩:

  • 路径必须在 repo_root 内,防逃逸;
  • 临时文件 + 原子写入,不半写;
  • 自动备份,能撤回;
  • 限制单次改动规模;
  • 解析失败直接中断,不瞎猜。

这让 V0 至少有个底线: 改动是可追、可控、可撤回的 。

5、兴奋和坑,一起来了

V0 刚跑起来那几天,确实挺爽。

"看结构 → 搜代码 → 给补丁 → 落盘"这条链路能通,而且通得挺顺。我看着它准确的分析了我的需求,使用Todo工具拆分任务,并开始行动的时候,感觉这玩意儿 真的能干活 。

但任务强度一拉高,诸多问题很快冒头:

  • 终端命令越来越长,失败时只能看到最后报错,根本不知道哪步挂了;
  • ReAct 靠字符串解析,模型偶尔多说一句、少个标记,就解析失败;
  • 长任务里上下文开始"变脏",噪声越来越多;
  • 工具返回都是字符串,后续处理很难做强约束。

等等.....

这时候我才真正意识到:

V0 的价值不是"它已经很好",而是"它把问题暴露得足够早"。

没有这个能跑通的版本,后面的重构可能就是拍脑袋。有了它,我能拿着具体代码和复现场景去改,而不是靠感觉。

而第一个真正把我打醒的坑,就是 Terminal Tool。

三、自由是把双刃剑:我的 Te rminal Tool 是怎么失控的

V0 刚跑起来那会儿,我最自豪的其实是 Terminal Tool 。

因为它几乎等于把"人能在终端里做的事"直接交给了模型。我当时甚至觉得——只要白名单、沙盒、危险命令确认做好,模型就能像资深工程师一样操作仓库。

结果很快被现实教育了。

1、我的设计:放开自由度,效率优先

V0 里的终端工具长这样:

self.terminal_tool = TerminalTool(
    workspace=str(self.paths.repo_root),
    timeout=60,
    confirm_dangerous=True,
    default_shell_mode=True,  # 关键参数
)

default_shell_mode=True 意味着模型不只能执行 ls 、 rg 、 cat 这种单条命令,而是可以直接写:

rg -n "foo" src/ | grep "class" | sed -n '1,120p'

这个能力太诱人了。它让模型能用一条命令完成多步操作,效率很高,也很"像人"。

为了安全,我还加了不少限制:

  • 只允许白名单命令
  • 高风险操作要确认
  • 禁止 rm/chmod 这类危险命令
  • 组合命令要经过安全检查

听起来挺严密,对吧?

但真正用起来,问题很快就冒出来了。

2、真实场景:命令开始"不听话"

最典型的一次踩坑:

我让 Agent "搜索某个类里的函数,展示实现片段"。模型给出了一条"很聪明"的命令:

rg -n "TargetFunc" src/ | grep "class Foo" | sed -n '1,120p'

第一眼看,这就是老工程师常用的组合拳。但实际执行时,经常出现这些情况:

  • rg 搜出来太多, grep 过滤后什么都没剩
  • rg 因为权限或编码问题直接失败,管道输入是空的
  • sed 收到空输入,输出也是空的
  • 最终模型看到"空结果",但它根本不知道哪一步出了问题

最坑的是:一条命令完成多步,错误被"压扁"了。

模型只能看到最终结果,看不到中间哪个环节挂了。它也没法修正策略,只能"重新拍一条命令赌运气"。

3、安全策略:越补越长的补丁战

我原以为白名单 + 沙盒 + 确认机制够安全了。但 shell 太灵活,管道、重定向、子命令全是"可绕过点"。

比如:

  • > 重定向会变成写盘
  • $(...) 触发子命令执行
  • cmd1 || cmd2 把失败路径也带进来
  • 即使限制了 rm ,还可能间接改文件

我写了大量安全检查:

SHELL_META_TOKENS = ["|""||""&&"";"">"">>""<""$(""`"]
DANGEROUS_BASE_COMMANDS = {"rm""chmod"}

if needs_allow and not allow_dangerous:
    return "❌ 该命令包含写盘/子命令替换/高风险操作..."

但讲真, 一旦你允许 shell 语法,你就永远在和组合复杂度做对抗 。

逻辑越写越长,模型越容易生成"刚好在边界上"的命令。而你要是全封死,能力也就残了。

4、最致命的:错误不可定位,模型没法纠错

我后来总结,Terminal Tool 最大的问题不是"不安全",而是 没法诊断 。

在 Agent 体系里,模型需要靠"观察"来修正动作。但一个 Action 里塞了 3~5 个子命令时:

  • 观察结果只有一个
  • 失败信息混在一起
  • 模型根本不知道 rg 、 grep 、 sed 谁出了问题
  • 下一步只能瞎猜着重写

这就形成了一个坏循环:

命令越来越长 → 失败越来越多 → 观察越来越模糊 → 修正越来越难

这也是我决定"拆工具"的真正原因。因为在 Agent 里, 可控性比一次性完成更重要 。

5、认知转变:自由不是能力上限

这一坑让我彻底转变了想法。

以前我觉得:给模型自由度 = 提高能力上限

后来我发现: 给模型自由度,往往是在放大不确定性

说白了,模型强是强,但它不是工程师。它会生成看起来很聪明的命令,但并不一定能稳定执行,更难自己定位问题。

这一章的结论:

Terminal Tool 不能只靠"白名单 + 沙盒",更得把复杂行为拆成一组可观测、可控的原子工具。

接下来,我开始把 Terminal Tool 拆开,走向了"工具化"而不是"脚本化"。

四、把"自由命令"拆成"可控工具":我的工具体系重构实录

踩完 Terminal Tool 的坑,我才意识到:问题不是"工具不够多",而是 工具不 够可控 。

当时读到一篇解读 Claude Code 的文章,讲工具分层,说"高频动作更需要确定性"。这一下把我点醒了。

我的问题,恰恰是把所有能力塞进了一个"无限自由"的 Terminal Tool。低频场景确实灵活,但在高频动作上反而成了 噪音制造机 。

是时候重新设计了。

1、重构目标:每一步都有结构化证据

核心原则:

工具化的核心不是"更多工具",而是"每一步都有结构化证据"。

按"频率"和"确定性"重新分类:

说白了,不是为了多造轮子,而是 让每一步都可观察、可判断、可纠错 。

2、先统一协议:所有工具说同一种"语言"

Hello-Agent 里工具返回偏自由文本,模型还得"理解"每个工具的输出结构。

我现在搞的是 标准化信封(Standard Envelope) : status / data / text / stats / context 五件套。

以Glob工具的返回结果为例:

{
  "status""partial",
  "data": {
    "paths": ["core/llm.py""agents/codeAgent.py"],
    "truncated"true
  },
  "text""Found 2 files matching '**/*.py' (Scanned 12000 items, timed out)",
  "stats": {"time_ms"2010"matched"2},
  "context": {"cwd"".""params_input": {"pattern""**/*.py"}}
}

模型不用猜"这是成功还是失败",直接看 status 和 data 就 知道下一步干嘛。

把"工具输出"从"杂乱文本"升级成"可推理信号"——这是质变。

3、ToolRegistry:工具体系的"中枢神经"

工具不是零散功能块,而是统一调度系统。入口是 tools/registry.py ,它干三件关键的事:

Schema 汇总:把工具"变成函数"

每个 Tool 的参数转成 JSON Schema,通过 get_openai_tools() 汇总给模型。

模型不再"自由拼字符串",而是被 schema 约束在合法参数里。

乐观锁自动注入:Write/Edit 不再裸奔

Read 过的文件,ToolRegistry 会缓存 file_mtime_ms 和 file_size_bytes ,后续 Write/Edit/MultiEdit 自动注入:

if name in {"Write""Edit""MultiEdit"}:
    parameters = self._inject_optimistic_lock_params(name, parameters)

不 Read 就不能改,Read 过再改也有冲突检测。

熔断器:工具连续失败会被禁用

tools/circuit_breaker.py 默认 3 次失败熔断、300 秒恢复 。工具进入 open 状态后,ToolRegistry 直接返回结构化错误,防止模型在坏工具上死循环。

4、搜索链路:从"一条大命令"到"原子动作"

V0 里模型可能写一条命令干完所有事:

rg -n "TargetFunc" src/ | grep "class Foo" | sed -n '1,120p'

看着挺酷,但失败时你只看到一个"空结果",根本不知道哪步挂了。

重构后拆成四个原子动作:

Glob 把"找文 件"变成确定动作:

  • pattern 永远相对 path ,不让模型猜"从哪搜"
  • 双熔断:最大 20000 条目、最多 2 秒
  • ** 才能跨目录, * 不跨
  • 返回相对路径,方便后续 Read/Edit 直接接

Grep 把"找符号"变成可推理的证据:

  1. 优 先 rg , 失 败回退 Python(必须标明 fallback)
  2. 输出固定为 file/line/text , 按 mtime 排序

讲真,模型不需要理解"输出是什么意思",只需要判断"下一步读哪一行"。 5、编辑链路:Read → Edit/MultiEdit → Write 工具体系最重要的闭环。设计成"读前约束 + 可预览 + 可回滚":

  • Rea d : 带行号读取指定范围,返回 file_mtime_ms / file_size_bytes 作为乐观锁
  • Edit / MultiEdit : 锚点替换 + diff 预览

编辑变成"可验证动作",而不是"赌命写文件"。

{
  "path""core/llm.py",
  "old_string""def invoke_raw(self, messages: list[dict], **kwargs):",
  "new_string""def invoke_raw(self, messages: list[dict], **kwargs) -> Any:",
  "dry_run"true
}

6、Bash 从"默认"变 成"兜底"

Bash 没被删掉,而是被降级为"最后手段"。

硬约束:

  • 禁止读/搜/列 : ls / cat / head / grep / find / rg
  • 禁止交互 :vim、nano、top、ssh
  • 禁止网络(默认):curl/wget 被禁
  • 黑名单:rm -rf /、sudo/su、mkfs/fdisk
  • 超时与截断:输出过大直接截断

Bash 不会成为"绕过工具体系的后门"。

这套改造让我明白一个道理:Code Agent 的"聪明",不是模型聪明,而是工具设计够不够可控。 从"能跑"到"能稳",靠的不是更严的提示词,而是更清晰的工程边界。

工具可控了,但调用协议还是个大问题——字符串解析太不靠谱,是时候转向 Function Calling 了。

五、从"修作文"到"走协议":我为什么放弃字符串 ReAct

工具体系改造完成后,我以为问题已经解决了一大半。

工具更可控了、返回协议也统一了,但真正跑起来还是会"偶发翻车"。

翻车的根源不在工具,而在 工具是怎么被调用的 。

那时候我沿用的是 Hello-Agent 的 ReAct:模型输出 Thought/Action 字符串,框架用正则解析。工具变精细了,但调用协议还是"自由文本"——只要模型多说一句、少一个符号,链路就断。

1、旧版 Re Act:全 靠"字符串纪律"

Hello-Agent 的 ReAct Prompt 写得很死,要求模型必须输出 Thought 和 Action 两行:

Thought: <你的思考(简短)>
Action: <以下二选一>
- tool_name[tool_input]
- Finish[最终回答]

表面上是强约束,本质还是 字符串协定 。模型多写一句解释、或者把 Action 放错位置,解析就开始出错。

看看解析代码才知道它有多脆:

m = re.search(
    r"(?:\*\*)?(Thought|思考)(?:\*\*)?\s*[::]\s*(.*?)\n"
    r"(?:\*\*)?(Action|行动)(?:\*\*)?\s*[::]\s*(.*)\s*$",
    t,
    flags=re.DOTALL,
)

这个正则已经很"照顾模型"了:兼容中英文、全角冒号、Markdown 加粗。但如果模型输出是多轮 Thought/Action,就会把后面的全部吞进第一个 Action,导致参数乱飞。

于是又加"截断补丁"。这个逻辑挺聪明,但它暴露了一个事实: 我们不是在做协议调用,而是在"修作文"。

2、救火式解析:补丁越打越多,问题还在

当 Action 输入是 JSON 时,麻烦更大。模型一旦输出了 }] 、或者多了个括号,工具就直接报错。

旧版 ToolRegistry 里堆了一堆 JSON 容错:

# 常见模型输出尾部多了一个 ']' 的容错
if obj is None and raw.startswith("{"and raw.endswith("}]"):
    obj = _try_json(raw[:-1].strip())

# 模型输出为数组包裹一个对象
if obj is None and raw.startswith("["and raw.endswith("]"):
    arr = _try_json(raw)
    if isinstance(arr, list) and len(arr) == 1:
        obj = arr[0]

我把它叫作 "救火式解析" 。它能提高成功率,但解决不了根本问题:

输入漂移永远存在,只是被容错暂时遮住了。

3、调研后的顿悟:文本解析 → 结构化协议

后来系统读了一遍主流 Code Agent 的实 现,发现大家几乎都在做同一件事:

把 ReAct 的 Action 从"字符串协议"升级为 Function Calling。

讲真,这不是"把格式写得更严",而是 彻底换了通信方式 ——模型不再输出 tool_name[...] ,而是输出结构化 tool_calls ,框架再把结果和 tool_call_id 精确绑定。

于是开干,把整条链路换成 function calling。

4、改造三件套:Schema → tool_calls → tool_call_id

Schema:参数被强约束

每个 Tool 的参数自动转成 JSON Schema,模型只能在 schema 里填值,而不是自由拼字符串。

tool_calls:模型输出变结构化

不再解析 Action 字符串,直接抽取 tool_calls 。把"文本解析"变成"结构读取"。

tool_call_id:调用和结果强绑定

最关键的一环:每个 tool_call 都有 id,写入 assistant 消息后再执行工具,结果用相同 id 回填。

assistant_msg["tool_calls"] = [{
"id": call_id,
"type""function",
"function": {"name": name, "arguments": args_str},
}]

messages.append({
"role""tool",
"tool_call_id": tool_call_id,
"content": msg.content,
})

每一次工具调用都能精确对应结果,链路不会再断。

回过头看,旧版为了"能跑"写的大量容错补丁,现在都被协议层替代了。 ReAct 的稳定性不是靠"写更严的提示词",而是靠"让模型说协议话" ——标准格式天然可控,省去无数补丁。

调用协议稳定之后,新的问题又冒出来了——长对话一多,模型开始"变笨"。上下文工程,成了下一个要解决的大问题。

六、长 对话的困境:我的上下文工程 改造

调用协议稳定之后,问题很快转移到了另一块: 长对话一多,模型开始"变笨" 。

症状挺明显的——明明刚确认过的约束,过几轮就忘;会话越长,越容易用错工具;工具输出堆成山,最终答案越来越水。

这其实就是业界讨论最多的上下文腐烂问题,在看了Manus、Anthropic、 Langchain 等分享的关于上下文工程的博文之后,我总结出了以下几点:上下文隔离+上下文卸载+上下文压缩。

上下文隔离就是主 Agent 将复杂任务拆分为子任务,派发给专属子 Agent 处理,每个子 Agent 拥有独立的上下文窗口,实现 “关注点分离。

上下文卸载借鉴虚拟内存思想,将大模型中非活跃、大体积、低价值的上下文数据从显存 / 核心内存迁移至磁盘 / SSD 等低速存储,仅保留轻量引用,供模型按需调取。在Code Agent中卸载掉内容便是工具的返回结果了。

上下文压缩是在 不损失关键语义 的前提下,通过算法剔除冗余信息、精简数据量,降低 Token 消耗和内存占用的技术,分为 无损压缩 (仅剔除无意义符号 / 连接词)和 有损压缩 (剔除低价值信息,保留核心语义,大模型场景主流),核心目标是提升上下文的信息密度。

我的改造思路是: 从堆消息到分层治理,从工具输出堆积到统一截断,从无限历史到可控压缩。

1、先把上下文分层:L1 / L2 / L3 的稳定结构

参考上下文工程的分层思路,我把上下文拆成三层:

拼接顺序固定: L1 → L2 → L3 → 当前 user → Todo recap

L1/L2 稳定,L3 才能稳定 ,模型需要知道什 么是永远不变的规则。

2、统一截断:Observation 进历史前必须"瘦身"

上下文膨胀的最大来源,不是用户说的话,而 是 工具输出 。

一次 Grep / Read 打出来几千行,如果不截断,历史很快就被"证据垃圾"淹没。

为什么需要"统一"截断?

最初我给每个工具设计了特化的压缩策略——Read 怎么截、Grep 怎么截、LS 怎么截,各自有各自的逻辑。结果维护成本极高,新增一个工具就要重新设计一套规则,还容易遗漏。

后来我参考了 Opencode 的做法,决定 完全替换为统一截断机制 :

  • 不再为每个工具设计特化压缩
  • 所有工具输出使用同一套截断规则
  • 完整输出落盘保存,随时可回溯

统一截断规则

默认限制(通过环境变量可配置):

  • TOOL_OUTPUT_MAX_LINES = 2000
  • TOOL_OUTPUT_MAX_BYTES = 51200 (50KB)

截断方向有三种:

  • head (默认):保留前 2000 行
  • tail :保留后 2000 行
  • head_tail :保留头尾各 40 行(适合需要看开头和结尾的场景)

处理流程很简单:

  1. 若行数 ≤ 2000 且字节数 ≤ 50KB → 原样返回
  2. 否则 → 截断 + 落盘保存 + 返回提示路径

落盘策略

截断不是丢 弃,而是"另存为":

  • 目录 : tool-output/ (项目根目录下)
  • 文件名 : tool_<timestamp>_<toolname>.json
  • 内容 : 完整工具原始 JSON 输出
  • 清理 : 默认保留 7 天,过期自动删除

这样设计的好处是—— 上下文保持精简,但完整证据始终可查 。

截断后的返回格式

截断后的响应仍然遵循《通用工具响应协议》,但 status 变为 partial ,并附加截断元信息:

{
  "status""partial",
"data": {
    "truncated"true,
    "truncation": {
      "direction""head",
      "max_lines"2000,
      "max_bytes"51200,
      "original_lines"5234,
      "original_bytes"182345,
      "kept_lines"2000,
      "kept_bytes"49872,
      "full_output_path""tool-output/tool_20260113_153045_Grep.json"
    },
    "preview""(截断后的内容预览)"
  },
"text""⚠️ 输出过大已截断,完整 5234 行内容见 tool-output/tool_20260113_153045_Grep.json"
}

模型看到 partial 状态 ,就知道 内容被截断了。如果它发现关键信息可能在被截掉的部分,会收到明确的提示去读取完整文件。

截断后的回查流程

工具输出超长 → 被截断 → 返回 full_output_path → 模型判断 → 用 Read/Grep 回查

举个实际例子:

我让 Agent Grep 搜索 "class" 关键词,结果命中了 5000+ 行。工具自动截断前 2000 行返回,并在 text 中提示:

“⚠️ 输出过大已截断,完整 5234 行内容见 tool-output/tool_20260113_153045_Grep.json”

Agent 分 析后发现:"前面这些都不是我要找的 ,目标可能在后面。"

于是它调用 Read 读取完整落盘文件,或者用更精确的 Grep 在落盘文件里进一步筛选—— 既拿到了结果,又没让上下文爆炸 。

智能提示

当发生截断时,我还会在 text 里附加提示,引导模型 后续行动 :

  • 如果存在 Task 工具:"Use Task to have a subagent process the full output file..."
  • 否则:"Use Read with pagination or Grep to search the full output file..."

这套统一截断策略,让我从"为每个工具写压缩逻辑"的泥潭里解脱出来,同时保持了可追溯性与上下文可控性。

3、压缩触发:明确阈值,自动收敛

历史越积越多,模型会被噪声淹没。

我设定的压缩触发条件:

  • estimated_tokens >= 0.8 * context_window (超过 8k token)
  • messages >= 3
  • 至少保留最近 10 轮完整对话

“保留完整轮次”非常关键:一轮必须从 user 发起到 assistant 完成,中间的工具调用链不能拆。这是保证 Agent 行为连贯性的底线。 4、Summary 归档:旧历史压缩成"记忆卡片"

压缩不是删除,而是归档。我用 Summary 把旧历史提炼成关键信息,让模型既能"看到过去",又不被"过去淹没"。

Summary 的核心定位

Summary 只用于旧历史归档,不包含当前任务进度。

这是个关键区分:

  • Todo Recap : 告诉模型"现在正在做什么"(当前进度)
  • Summary : 告诉模型"之前做过什么"(历史归档)

HistoryManager 把 Summary 作为 role=summary 的消息存入 L3,序列化时变成 system message 注入 上下文。最重要的是: Summary 永远不再压缩 ——它是"旧历史的档案",一旦生成就是只读的"记忆卡片"。

Summary 生成机制

触发时机 : 当历史消息超过 token 阈值(默认 0.8 × context_window)时触发压缩。

生成方式 : 调用新的模型会话(可配置模型)来生成 Summary,输入是待压缩的历史消息 + 专用的 SUMMARY_PROMPT 。

超时与降级 : 生成过程同步阻塞,用户会看到压缩进度提示。如果超过 120 秒还没生成完,就跳过 Summary 生成,仅保留最近 N 轮历史,并提示用户:"Summary generation timed out, keeping recent history only."

固定模板结构

Summary 必须按固定模板生成,确保关键信息不遗漏:

## 📌 Archived Session Summary
*(Contains context from [Start Time] to [Cutoff Time])*

### 🎯 Objectives & Status
* **Original Goal**: [用户最初想做什么]

### 🏗️ Technical Context (Static)
* **Stack**: [语言, 框架, 版本]
* **Environment**: [OS, Shell, 关键环境变量]

### ✅ Completed Milestones (The "Done" Pile)
* [✓] [已完成的任务1] - [简述结果]
* [✓] [已完成的任务2] - [简述结果]

### 🧠 Key Insights & Decisions (Persistent Memory)
* **Decisions**: [关键技术选型或放弃的方案]
* **Learnings**: [特殊配置、API 格式或坑]
* **User Preferences**: [用户强调的习惯]

### 📂 File System State (Snapshot)
*(Modified files in this archive segment)*
* `src/utils/auth.ts`: Implemented login logic.
* `package.json`: Added `zod` dependency.

这个模板的妙处在于: 不同层次的信息分开存放 ,模型能快速定位需要的内容。想查改了哪些文件?看 File System State。想查当时为什么选这个方案?看 Key Insights & Decisions。

一个实际的 Summary 例子

假设用户让 Agent 实现一个登录功能,中间经过了多轮调试和重构,历史膨胀到需要压缩。生成的 Summary 可能是:

## 📌 Archived Session Summary
*(Contains context from 14:32 to 15:47)*

### 🎯 Objectives & Status
* **Original Goal**: 实现 JWT 登录功能,包含登录接口和前端表单

### 🏗️ Technical Context
* **Stack**: Node.js + Express + React + TypeScript
* **Environment**: macOS, Node 18, PostgreSQL 14

### ✅ Completed Milestones
* [✓] 设计数据库 schema(users 表,含 password_hash 字段)
* [✓] 实现 /api/login 接口(JWT 签名,7天过期)
* [✓] 实现密码 bcrypt 加密(salt rounds: 10)
* [✓] 前端登录表单(React Hook Form + Zod 校验)

### 🧠 Key Insights & Decisions
* **Decisions**: 选用 jsonwebtoken 而非 passport-jwt(更轻量)
* **Learnings**: bcrypt 异步方法比同步方法快 3 倍
* **User Preferences**: 用户强调错误信息不要暴露具体字段

### 📂 File System State
* `src/routes/auth.ts`: 新增登录路由
* `src/middleware/jwt.ts`: 新增鉴权中间件
* `src/components/LoginForm.tsx`: 新增登录表单
* `.env`: 新增 JWT_SECRET(未提交)

当这个 Summary 被注入上下文后,模型即使忘记了中间具体的调试过程,也能知道:"哦,登录功能已经做完了,用了 JWT,数据库 schema 也定好了,现在应该在搞其他模块。"

为什么 Summary 不再压缩?

这是个关键设计决策。

如果 Summary 还能被压缩,那就会出现"Summary 的 Summary",信息会层层失真,最后变成一堆没有上下文的碎片。


Summary 是压缩的终点。 它像是一本相册——你把几百张照片(历史消息)精选成一本相册(Summary),以后翻这本相册就够了,不需要再压缩相册本身。

这也解释了为什么 Summary 模板要固定结构:只有结构固定,才能在一次读取中高效提取信息,而不需要反复翻找。

与 Todo Recap 的配合

Summary 和 Todo Recap 是互补的:

上下文结构:
L1 System Prompt
L2 CODE_LAW.md
L3 History:
  - [旧历史] → 被压缩成 Summary(只读档案)
  - [保留区] 最近 10 轮完整对话
  - [当前轮] user 输入 + assistant 思考中...
  - Todo Recap: [1/3] In progress: 实现注册接口

Summary 让模型知道"从哪来",Todo Recap 让模型知道"现在在哪",两者配合,模型才不会迷失。

5、噪声控制:@file 不再直接塞内容

以前做 @file 时,我习惯直接把文件内容拼进上下文:

User: @file:src/main.py 帮我分析一下这个文件
[文件内容300行...]

结果就是:上下文里多了一大段代码,但用户可能只是想问"这个文件是干嘛的"。

模型看着这 300 行代码,头都大了。

现在改成: 只插入 system-reminder,不直接注入内容 。

用户输入: 请看一下 @core/llm.py 和 @agents/codeAgent.py

最终会被改写成:

<system-reminder>
The user mentioned @core/llm.py, @agents/codeAgent.py.
You MUST read these files with the Read tool before answering.
</system-reminder>

上下文里只留下"提醒",而不是"垃圾"。

这一轮改造的效果很明显:工具输出不会无限堆积,历史能在阈值时自动收敛,Summary 把旧历史压成"稳定记忆",@file 不再制造垃圾上下文。 上下文不是塞得越多越好,而是要治理得清晰、可追溯。

但治理好上下文,还有一个更大的痛点——Agent 出错时,我完全不知道它经历了什么。可观测性,成了下一个坎。

七、把黑盒拆开:可观测性、日志与会话回放

工具、协议和上下文都稳定之后,我才真正遇到一个硬伤: Agent 出错时,我无法还原"它到底做了什么" 。

有一次模型连续失败 3 步,最后干脆自己放弃了。我翻控制台日志,只看到一句: tool failed

没有完整参数、没有前后文、也看不到之前几步的决策链。 我只能瞎猜:路径写错了?权限不够?还是工具本身有 bug?

那一刻我才 意识到: 没有审计轨迹的 Agent,就像没有行车记录仪的自动驾驶——出事了你只能干瞪眼。

于是我补上了可观测性这一层:Trace 轨迹 + 会话快照。

1、TraceLogger:会话级审计轨迹

我不再用"散乱的运行日志",而是做了一个会话级轨迹记录器: TraceLogger 。

它只做一件事: 把一次会话的每一步,按顺序完整记下来 。

双格式输出:机器可读 + 人类可读

TraceLogger 同时输出两份文件,放在 memory/traces/ 目录下:

JSONL 格式 (面向机器处理):

  • 路径: memory/traces/trace-s-20260103-201533-a3f2.jsonl
  • 每一步一个 JSON 对象,便于流式追加与后处理
  • 可以写脚本批量分析、统计 token 用量、检测异常模式
{
 "ts""2026-01-19T08:39:41.557283Z",
"session_id""s-20260119-163933-3d33",
"step"0,
"event""message_written",
"payload": {
"role""user",
"content""请快速理解这个仓库是做什么的,并输出一份“项目概览”文档(可以使用subagent,如果有必要的话):
\n  - 简要说明目标、核心模块、主要功能;\n  - 引用真实文件路径作为依据;\n  - 输出一个结构清晰的 Markdown
文件放在 demo/ 项下(文件名你定)。\n  - 可以使用mcp工具来进行搜索同类产品的功能点,进行对比总结"
,
"metadata": {}
 }
}

{
"ts""2026-01-19T08:40:12.926898Z",
"session_id""s-20260119-163933-3d33",
"step"1,
"event""message_written",
"payload": {
"role""assistant",
"content""",
"metadata": {
   "action_type""tool_call",
   "tool_calls": [{
    "id""call_-7965161296454289305",
    "name""TodoWrite",
    "arguments""{\"summary\":\"分析仓库并生成项目概览文档\",\"todos\":[{\"content\": \"探索仓库结构和核心文件\",
\"status\": \"in_progress\"}, {\"content\": \"搜索同类 AI Agent 产品进行对比\", \"status\": \"pending\"},
{\"content\": \"生成项目概览 Markdown 文档\", \"status\": \"pending\"}]}"

   }]
  }
 }
}

HTML 格式 (面向人类审计):

  • 路径: memory/traces/trace-s-20260103-201533-a3f2.html
  • 可直接用浏览器打开,可视化展示每一步
  • 支持点击展开/折叠,像在"逐帧回放"

文件名中 的 s-20260103-201533-a3f2 是会话唯一标识( s-YYYYMMDD-HHMMSS-随机4位 ),方便按时间检索。

统一事件 结构

所有事件都遵循统一结构:

{
  "ts""2026-01-03T20:15:33.112Z",
  "session_id""s-20260103-201533-a3f2",
  "step"4,
  "event""tool_call",
  "payload": {}
}

字段说明 :

  • ts :ISO8601 时间戳,精确到毫秒
  • session_id :会话唯一标识
  • step :ReAct 循环的 step 序号
  • event :事件类型
  • payload :事件数据体

关键事件类型

我定义了以下关键事件类型,覆盖 Agent 运行的完整生命周期:

有了统一结构,问题就不再是"有没有日志",而是"你想看哪一步"。

2、关键证据链:每一步都打点

Trace 的核心价值是"证据链"——每一步都有迹可循,调用和结果能精确对应。

tool_call_id:调用与结果的强绑定

在 Function Calling 架构下,每个 tool_call 都有唯一 ID。TraceLogger 用这个 ID 把"调用"和"结果"串成一条链:

# Step 1: 记录工具调用
trace_logger.log_event(
    "tool_call",
    {
        "tool""Read",
        "args": {"path""core/llm.py"},
        "tool_call_id""call_abc123xyz"# 关键:唯一标识
    },
    step=4,
)

# 执行工具...

# Step 2: 记录工具结果,使用相同的 tool_call_id
trace_logger.log_event(
    "tool_result",
    {
        "tool""Read",
        "tool_call_id""call_abc123xyz",  # 与调用对应
        "result": {
            "status""success",
            "data": {"content""..."},
            "text""Read 120 lines from core/llm.py"
        }
    },
    step=4,
)

在 HTML 可视化中,这两个事件会被渲染成一对可折叠的卡片,一眼就能看出调用和结果的对应关系。

model_output:记录 Token 用量

每次模型输出,Trace 都会记录 usage 信息:

{
  "event""model_output",
  "payload": {
    "raw""...",
    "tool_calls": [...],
    "usage": {
      "prompt_tokens"3456,
      "completion_tokens"892,
      "total_tokens"4348
    }
  }
}

这帮我解决了一个老大难问题: "为什么突然变慢 / 为什么 token 暴涨?"

打开 Trace 一看,就能定位到哪一步 的上下文突然膨胀了。

3、HTML 可视化:把轨迹变成"可读回放"

JSONL 适合程序分析,但人类复盘需要"看得懂"。TraceLogger 会同步生成一份 HTML,可直接用浏览器打开。

HTML 界面结构

点开每一步,就能看到完整的 input / output / token / 截断提示,像在逐帧回放。

脱敏保护

生产环境中,Trace 可能包含敏感信息(API Key、Token、文件路径)。 TraceSanitizer 默认开启( TRACE_SANITIZE=true ):

  • API Key: sk-*** / Bearer ***
  • 敏感字段: api_key 、 token 、 session_id 、 tool_call_id
  • 路径脱敏: /Users/yyhdbl/... → /Users/***/...

排查问题够用,但不会把敏感信息原样写盘。

4、一个真实复盘案例:Trace 怎么救了我

还记得那次“编辑后结果不生效”的问题吗?

当时 Agent Read 完就去 Edit,但 Edit 一直失败,我在控制台只看到 tool failed ,根本不知道原因。

后来我用 Trace 复盘,打开 HTML 一看:

Step 2 - tool_call (Read):

{
    "tool""Read",
    "args": {"path""core/llm.py"},
    "tool_call_id""call_021"
}

Step 3 - tool_call (Edit):

{
    "tool""Read",
    "args": {"path""core/llm.py"},
    "tool_call_id""call_021"
}

Step 3 - tool_result (Edit):

{
    "status""error",
    "error": {
      "code""CONFLICT",
      "message""File changed since last read."
    }
}

Step 4- model_output:

模型没理解冲突原因,继续用旧的 mtime 重试……

这时我才明白:不是 Edit 工具出 bug,而是文件在 Read 后被格式化/自动保存改过了。

有了这条证据链,我在提示词里加了“遇到 CONFLICT 必须重新 Read”这一条,问题立刻消失。

这就是 Trace 的价值: 你不是在猜错误,而是在看证据 。

5、会话快照:断点续跑的秘密

Trace 解决"事后复盘",会话快照解决"断点续跑"。

CLI 操作

# 手动保存
/save my-session-backup

# 自动保存(exit 或 Ctrl+C 时触发)
# 默认路径:memory/sessions/session-latest.json

# 加载恢复
/load my-session-backup

我经常让 Agent 跑 30 分钟的长任务,断网或退出后用 /load 继续跑, 基本不会丢上下文 。

快照存的不只是历史

Session 快照不是简 单的"存对话",而是 存环境 :

{
  "session_id""s-20260103-201533-a3f2",
"messages": [...],
"tool_schema_hash""a3f2d8e...",
"read_cache": {
    "core/llm.py": {"mtime"1234567890"size"3456},
    "agents/codeAgent.py": {"mtime"1234567891"size"5678}
  },
"prompt_hashes": {
    "code_law""b5e7c9d...",
    "skills""f1a2b3c...",
    "mcp""d4e5f6a..."
  }
}
  • tool_schema_hash :工具的 JSON Schema 哈希,检测工具定义是否变化
  • read_cache :乐观锁元信息(mtime + size),确保恢复后能检测文件是否被外部修改
  • prompt_hashes :各种提示词的哈希,确保上下文环境一致

这保证了你恢复会话时,工具版本、上下文状态、读前约束都和之前完全一致。

6、配置与性能

Trace 是可配置的,不会成为性能负担:

# 开关控制
TRACE_ENABLED=true|false          # 默认 true
TRACE_DIR=memory/traces           # 输出目录
TRACE_SANITIZE=true|false         # 脱敏,默认 true
TRACE_HTML_INCLUDE_RAW_RESPONSE=true|false  # 是否包含原始响应,默认 false

性能考虑 :

  • JSONL 追加写入,开销极低
  • 默认开启脱敏,生产环境需配合权限控制
  • HTML 生成在会话结束时批量处理,不影响运行时性能

从"猜错误"到"看证据",这一层是工程化的分水岭。 V0 时 Agent 挂了只能瞎猜,现在打开 Trace 5 分钟就能定位问题。可观测性不是锦上添花,而是复杂系统必备的"行车记录仪"。

八、扩展期:让 Agent 从"单兵作战"变成"专业团队"

工具、协议、上下文、可观测性——这些都让 MyCodeAgent 成了一个"靠谱的员工"。但一个员工再靠谱,终究只有一双手。面对复杂任务时,单打独斗的效率总是有限的。

真正让我眼前一亮的是: 如果 Agent 能像一个"专业团队"那样协作呢?

  • 有人专门负责"向外看"——搜索外部信息
  • 有人专门负责"向内看"——分析代码结构
  • 有人专门负责"盯进度"——确保不偏离主线
  • 还有人随时准备"传授经验"——特定领域知道怎么干

这就是 MCP / Task / Todo / Skills 的出发点。它们不是给 Agent 增加负担,而是帮它 拆解负担、专注专长 。

1、MCP:给 Agent 装上"外部触角"

MCP(Model Context Protocol)是 Anthropic 推出的开放协议,简单说就是 让 Agent 能调用外部工具的通用插槽 。

我第一时间集成它,是因为不想重复造轮子。比如要做竞品调研,自己写搜索工具太麻烦,直接用社区现成的 tavily-mcp 多好。

集成方式很简洁:

// mcp_servers.json
{
  "mcpServers": {
    "tavily": {
      "command""npx",
      "args": ["-y""@tavily/mcp"],
      "env": {"TAVILY_API_KEY""..."}
    }
  }
}

启动时 tools/mcp/loader.py 自动连接、注册,这些外部工具就变成和内置工具一样的标准接口。Agent 不需要知道谁是"亲生的"谁是"外来的",统一用 Function Calling 调用。

MCP 的价值在于:Agent 的能力边界不再受限于我写了多少内置工具。

2、Task:把"脏活累活"外包给子代理

长程任务最头疼的是上下文污染。让主 Agent 一边统筹全局,一边深入细节搜索文件,结果就是 主线思路被打断,上下文被无关信息塞爆 。

Task 工具的设计思路很简单: 像老板派活儿一样,把子任务外包出去 。

# Task 工具调用示例
{
  "description""分析项目核心架构",
  "prompt""深入分析 core/ 目录下的上下文工程实现,包括:\n1. HistoryManager 的压缩策略
\n2. ContextBuilder 的分层设计\n3. 输入预处理器的工作原理\n返回结构化的架构分析报告"
,
  "subagent_type""explore",
  "model""light"
}

子代理启动时,会创建一个 完全独立的会话 :

  • 干净的消息历史,只包含任务相关的上下文
  • 受限的工具集,只能只读(LS/Glob/Grep/Read/TodoWrite)
  • 严格禁止:Task/Write/Edit/MultiEdit/Bash, 以及 MCP 工具

为什么限制这么严格?

DENIED_TOOLS = frozenset({"Task""Write""Edit""MultiEdit""Bash"})
# MCP 工具也不在 ALLOWED_TOOLS 中
ALLOWED_TOOLS = frozenset({"LS""TodoWrite""Glob""Grep""Read"})
  • 禁止 Task : 防止"子生子,孙生孙"无限套娃
  • 禁止写操作 : 防止子代理"越界改代码"
  • 禁止 MCP : 外部搜索涉及网络调用和 API Key,子代理隔离失败可能导致密钥泄露

它只负责 代码分析和信息整理 ,决策权牢牢握在主 Agent 手里。

子代理还支持 四种专业角色 :

  • general :通用型,处理常规任务
  • explore :探索型,擅长代码库分析
  • summary :总结型,擅长信息归纳
  • plan :规划型,擅长任务拆解

以及 双模型路由 :

  • light :轻量模型(默认),省 Token、速度快
  • main :主力模型,复杂任务用

一个典型的外包流程是这样的:

  1. 主 Agent 发现需要深入分析某个模块的代码
  2. 启动 Task,指定 subagent_type=explore ,限定分析范围
  3. 子代理独立运行 10-20 步,只读相关文件,整理结构
  4. 返回结构化报告,主 Agent 继续主线决策

上下文隔离 + 工具限制 + 模型分级 ,这套组合拳让主 Agent 可以"用人不疑"地委派任务。

3、TodoWrite:给 Agent 一张"路线图"

人同时干多件事会手忙脚乱,Agent 也一样。

TodoWrite 不是简单的任务清单,它是一个 状态机 + 进度追踪器 。

核心约束:

  • 声明式覆盖 : 每次提交完整列表,不是 diff
  • 最多 1 个 in_progress : 强制单线程专注
  • 自动 Recap : 把 [2/5] In progress: xxx. Pending: yyy; zzz. 压缩到上下文末尾
  • 完成后自动持久化 : 写入 memory/todos/todoList-YYYYMMDD-HHMMSS.md

为什么强制单线程?因为我发现,一旦允许多个"进行中",Agent 就开始"东一榔头西一棒槌",最后啥都没干完。

Recap 的设计也很关键——它像一张贴在桌角的便利贴,时刻提醒 Agent "你现在该干嘛"。

4、Skills:给 Agent 装上"领域大脑"

工具给了 Agent "手",但 光有手不知道咋干活 。

Skills 解决的是"Know How"问题。它的核心思想是: 把个人经验写成 SOP,让 Agent 按需调用 。

Skills vs MCP:渐进披露 vs 急切加载

MCP 的问题是"急切加载"——一连接就把所有 Schema、文档塞进上下文。Skills 采用 渐进式披露 :

  • 第一层 : 启动只加载元数据(name/description)→ ~100 Token/技能
  • 第二层 : 命中时才加载完整 SOP → 1000-5000 Token
  • 第三层 : 附加资源按需加载

Token 节省 90% 以上 。

三层实现架构

文件层:SKILL.md

---
name: ui-ux-pro-max
description: UI/UX设计专家,提供50+风格、21+配色、字体搭配等
---


#### UI/UX 设计指南(示例)

**可用风格:**
Glassmorphism(玻璃拟态)
Claymorphism(粘土拟态)
Minimalism(极简主义)
...

**使用方式:** 根据项目类型选择合适的风格,提供具体实现代码。

$ARGUMENTS

格式要求:

  • YAML frontmatter 定义元数据
  • Markdown 正文写 SOP
  • $ARGUMENTS 是参数占位符

加载层:SkillLoader

# 扫描 skills/ 目录
skill_loader.scan()

# 获取元数据列表(仅 name + description)
skill_loader.list_skills()  # 低成本

# 加载完整 Skill(按需)
skill_loader.get_skill("ui-ux-pro-max")  # 高成本,只在使用时触发

缓存策略基于 mtime ,文件没变就不重新扫描。

工具层:Skill Tool

{
  "name""Skill",
  "arguments": {
    "name""ui-ux-pro-max",
    "args""科技感项目介绍页,深蓝主色调"
  }
}

执行时 解 析 $ARGUMENTS ,把用户参数注入 SOP,然后塞进当前上下文。Agent 瞬间拥有了"领域专家的记忆"。

5、实战:一个长程任务的完整协作流程

说了那么多,来看个真实例子。我给 MyCodeAgent 安装了 tavily-mcp ,然后布置了一个任务:

任务:

基于对全项目的理解,输出一个“面对新人开发者的项目介绍网页”

要求:

1. 内容需包含:项目概览、核心能力、架构/流程、注意事项

2. 网页风格你自行决定(科技感/赛博科技等均可)

3. 若你有 UI/UX 相关 Skill 可用,建议调用

4. 可以使用 MCP 工具搜索同类产品的功能点,在网页上进行对比

这个任务横跨了所有扩展能力。来看 Agent 是怎么"团队协作"的:

Step 1:启动 Todo,规划路线

Agent 首先调用 TodoWrite,拆解任务:

好,路线图有了。

Step 2:探索项目 + MCP 搜索

主 Agent 首先进行:

外部搜索(MCP)

  • 调用 tavily_search ,搜索竞品
  • 获取功能对比信息

随后进行内部探索

  • 用 LS 、 Read 扫描项目结构
  • 读取 README.md 、核心模块文档
  • 建立整体认知

Step 3:外包分析(Task 子代理)

"深入分析架构细节"这个活儿,主 Agent 不想亲自干——太琐碎,而且需要遍历大量文件。

于是启动 Task:

主 Agent 拿到报告, 上下文依然干净 ——这些技术细节没有污染主会话。

Step 4:加载 Skill,获取设计指南

Agent 发现自己有 ui-ux-pro-max 这个 Skill,果断调用:

Skill 返回 设计指南:

  • 风格:Cyberpunk / Tech-Noir
  • 字体:Rajdhani(科技)+ Share Tech Mono(终端)
  • 配色:深黑底 + 霓虹绿/青/粉/蓝
  • 特效:CRT扫描线、屏幕闪烁、故障艺术、霓虹发光

Agent 瞬间从"盲人摸象"变成"有设计规范可依"。

Step 5:统筹输出

现在主 Agent 手里有:

  • ✅ 项目整体理解(自己探索)
  • ✅ 竞品对比信息(MCP 搜索)
  • ✅ 技术架构细节(Task 分析报告)
  • ✅ 设计规范(Skill 加载)

开始写 HTML:

最终产出放在 demo/mycodeagent_intro.html 。

成果展示

页面顶部的 Hero 区如同巨型霓虹灯 牌:

  • "MYCODEAGENT" 标题采用 Rajdhani 字体,被青色霓虹光晕(text-shadow 三层叠加)包裹,hover 时触发 故障艺术(Glitch) 动画——文字像老式电视信号不稳定般微微抖动
  • 副标题用等宽字体 Share Tech Mono 打出":: 可追溯 :: 可验证 :: 可扩展 ::",模仿系统启动日志的样式
  • 底部两个 CTA 按钮采用 玻璃态(Glassmorphism) 设计:半透明黑底 + 青色边框发光,hover 时边框光晕扩散

四大核心能力以 悬浮卡片 形式呈现:

  • 每张卡片顶部有一条渐变色条(绿→青→粉),默认收缩,hover 时横向展开
  • 卡片边框采用 赛博边框 设计:四角有独立的装饰角标,hover 时角标延伸成完整边框
  • 图标使用霓虹色 emoji,在深色背景上形成高对比度视觉锚点

同类产品对比( 好像有点小bug,agent搜索的是agent开发框架 ):

扩展能力的真正价值

回顾这个任务,如果没有这些扩展能力:

  • 没有 MCP : Agent 只能基于内部知识"拍脑袋"对比,信息过时且容易幻觉
  • 没有 Task : 主 Agent 亲自分析架构细节,上下文被技术琐碎塞爆,主线思路断裂
  • 没有 Todo : Agent 可能在写到一半时突然去优化某个细节,最后忘了主线目标
  • 没有 Skills : Agent 凭"感觉"设计网页,风格可能不统一、不专业

有了它们,Agent 才能:

  • MCP 提供外部信息
  • Task 实现代码分析外包(子代理只读,安全隔离)
  • Todo 保持专注与进度追踪
  • Skills 提供专业领域知识

合理的分工是:主 Agent 控制 MCP 和最终决策,Task 处理内部代码分析,Skill 提供专业指导,Todo 串联全程。

这才是"像团队一样协作"——不是让 Agent 变复杂,而是让复杂变得可控。

写在最后:一个初学者的碎碎念

回顾整个系列:V0 → 工具重构 → Function Calling → 上下文工程 → 可观测性 → 扩展能力 → Skills。

这八篇文章,记录了我从一个 Agent 开发小白,跟着 Datawhale 的 Hello-Agent 教程一步步摸索过来的全过程。

首先要特别感谢 Datawhale 的开源教程。 如果没有 Hello-Agent 这个扎实的入门项目,我可能到现在还在各种"智能体框架"的概念里打转,而不会真正动手去写一个能跑起来的 Agent。正是教程最后那个"毕业设计"的推动,让我有了这个项目,也有了这八篇学习笔记。

说实话,写完这八篇,我越发觉得自己的渺小。

Agent 开发这个领域发展太快了,我文中提到的很多方案——比如上下文压缩策略、Skills 的设计、MCP 的集成方式——可能都还有更好的做法。我只是一个初学者,在实践中踩坑、总结、记录,分享的更多是一个"小白视角"的踩坑实录,而不是什么"最佳实践"。

如果你发现文中有错误,或者有更好的实现思路,非常欢迎在评论区指出。 我很希望能和大家交流,也很期待能向各位大佬请教。

一点真心话:我们都是在给 LLM "擦屁股"

做完这个项目,我有个特别深的感触,可能听起来有点糙,但话糙理不糙:

Agent 开发的核心,不是让模型更自由,而是通过工程设计,把模型"不确定的能力"约束在"最小可控的范围"里。说白了,我们就是在给 LLM 擦屁股。

为什么这么说?

你看啊,LLM 很强,能写代码、能读文档、能推理。但它就像一个特别聪明但特别不靠谱的实习生——

  • 你让它去打印文件,它可能把全公司的打印机都调用一遍;
  • 你让它整理会议纪要,它可能把上周的会议也掺和进来;
  • 你让它写个函数,它写得贼溜,但变量命名全是 a 、 b 、 c , 还顺带改了 你没让改的文件。

它的"强"是能力上的强,但"不靠谱"是确定性上的不靠谱。

而我们做 Agent 工程,本质上就是在解决这个矛盾。

你看这八章的内容,从工具原子化到上下文工程,从可观测性到子代理—— 每一层都是在给模型"打补丁",帮它收拾烂摊子。

但这恰恰是最有意思的地方。

以前我觉得,AI 时代工程师的价值会下降。现在我觉得恰恰相反: 模型越强大,越需要工程能力来驾驭它。 就像汽车引擎越来越强,但好的底盘、刹车、悬挂系统反而更重要。

我们不是在和模型竞争,而是在和模型协作——它负责"能做什么",我们负责"怎么让它稳定地做对"。

所以,如果你问我做完这个项目最大的收获是什么?

不是学会了什么高大上的架构,而是想明白了一个朴素的道理: 优秀的 Agent 不是"让模型更自由"的产物,而是"把不确定性约束到最小"的结果。

讲真,这个认知转变,可能比所有代码都值钱。

未来的打算

MyCodeAgent 还只是一个"能跑"的玩具,离真正生产可用还有很长的路要走。接下来我想继续完善的方向:

  1. 多 Agent 协作 ——Task 只是简单的子代理,能不能做更复杂的多 Agent 编排?
  2. 视觉能力集成 ——让 Agent 能看懂 UI、能截图分析,这是我特别想做的方向
  3. 更完善的测试体系 ——现在的测试还比较简陋,需要补全协议合规性和行为测试

如果你也对 Agent 开发感兴趣,或者正在学习相关技术,欢迎一起交流。我的 GitHub 仓库地址是:https://github.com/YYHDBL(欢迎 Star 和 PR!)

Agent 开发这条路还很长,希望能和志同道合的朋友一起走下去。

   
127   次浏览       5 次
相关文章

企业架构、TOGAF与ArchiMate概览
架构师之路-如何做好业务建模?
大型网站电商网站架构案例和技术架构的示例
完整的Archimate视点指南(包括示例)
相关文档

数据中台技术架构方法论与实践
适用ArchiMate、EA 和 iSpace进行企业架构建模
Zachman企业架构框架简介
企业架构让SOA落地
相关课程

云平台与微服务架构设计
中台战略、中台建设与数字商业
亿级用户高并发、高可用系统架构
高可用分布式架构设计与实践

最新活动计划
嵌入式软件测试方法&实践 3-20[在线]
MBSE理论方法到工作实践 3-28[北京]
需求分析与管理 4-21[在线]
基于LLM的Agent应用开发 4-18[北京]
SysML和EA系统设计建模 4-23[北京]
基于本体的体系架构设计 4-24[北京]
认证课:OCSMP-MU 周末班[在线]
 
 
最新文章
架构设计-谈谈架构
实现SaaS(软件及服务)架构三大技术挑战
到底什么是数据中台?
响应式架构简介
业务架构、应用架构与云基础架构
最新课程
软件架构设计方法、案例与实践
从大型电商架构演进看互联网高可用架构设计
大型互联网高可用架构设计实践
企业架构师 (TOGAF官方认证)
嵌入式软件架构设计—高级实践
更多...   
成功案例
某新能源电力企业 软件架构设计方法、案例与实践
中航工业某研究所 嵌入式软件开发指南
某轨道交通行业 嵌入式软件高级设计实践
北京 航天科工某子公司 软件测试架构师
北京某领先数字地图 架构师(设计案例)
更多...