乐于分享
好东西不私藏

从 Codex 源码看 Agent 设计:上下文、工具调用与 Skill 机制解析

从 Codex 源码看 Agent 设计:上下文、工具调用与 Skill 机制解析

自己在设计Agent时,难免遇到不少问题,比如,知识库如何构建,上下文怎么设计,窗口超限时怎么压缩。

与其闷头苦想,不如直接看看当前最优秀的Agent框架之一的codex[1]。

本文将借助 codex 来基于 codex 的源码,回答以下问题:

  • 发送一轮请求,会附带什么内容?
  • 如何去查找相关文件?
  • 上下文超限时,如何压缩?
  • skill是如何触发的?

发送一轮请求,会附带什么内容?

1.底层行为提示词

类似于系统提示词”的部分。它告诉模型自己是 Codex、怎么做代码任务、怎么用工具、怎么回答等。

具体可参见codex/codex-rs/protocol/src/prompts/base_instructions/default.md

它最终作为 Responses API 的 instructions 字段发送,区别于普通聊天历史的一条 message。

2.会话上下文消息

Codex 自己追加的上下文。首次请求时会比较完整,后续请求一般只追加变化项,主要包括:

  • 当前权限:能不能写文件、能不能联网、需不需要审批
  • 当前环境:工作目录、shell、日期、时区
  • 项目说明:AGENTS.md 里的规则
  • 可用能力:skills、plugins、apps/connectors
  • 特殊模式:collaboration mode、personality、realtime 等

3.对话历史

之前发生过的内容,包括:

  • 用户之前说过的话
  • 模型之前的回答
  • 模型请求过哪些工具
  • 工具返回过什么结果

4.当前用户发送的这条消息

用户刚刚输入的内容,它会追加到历史里,然后和前面的所有信息一起发给模型。

后续请求时,这些内容不会每次都重新塞一遍完整初始上下文,基础提示词仍然会作为 instructions 存在,如果环境、权限、模型、realtime、personality 等变化了,会追加一个“变化说明”,如果没变化,就不重复发完整说明。

如何去查找相关文件?

Codex 并不会将工程里的所有文件一次性读入,否则代码量大的仓库将直接把模型上下文塞爆了。

它是让模型自己通过工具来探索哪些文件是需要被读取的。

常见工具大概分几类:

  • 命令执行:exec_command / write_stdin,或者 shell / shell_command / local_shell,用于跑 rg、sed、cat、git grep、cargo test 等命令。
  • 文件修改:apply_patch,用于编辑文件。
  • 目录浏览:list_dir,用于列目录。
  • MCP 工具:list_mcp_resources、read_mcp_resource,其它 mcp 工具。
  • 工具发现:tool_search,用于搜索“有哪些工具可用”,不是搜索代码文件。
  • 辅助工具:update_plan、view_image、web_search、js_repl、multi-agent 相关工具等。

举个例子,比如当前项目里有很多md文件,用户提问:“xxx是怎么做的?”

xxx会被模型自动提炼成关键词,然后它开始执行一个命令:

rg -n -C 5 "关键词" /path/to/docs -g '*.md'

-n 表示 显示行号,-C 5 表示 显示命中行上下各 5 行,-g '*.md' 表示 只搜 Markdown 文件。

整行命令的意思就是:在 /path/to/docs 目录下,递归查找所有 .md 文件中包含“关键词”的内容,并显示匹配行号,以及匹配位置前后各 5 行内容。

如果匹配的内容很多,比如模型一不小心 cat 了很大的文件,返回的内容直接超过模型上下文的上限怎么办。

Codex 考虑到了这种情况,它有一个截断机制,当调用工具的返回值超过 10000 token 时,会进行截断,截断的策略是:

保留开头 + 省略中间 + 保留结尾

继续引申,如果模型搜出了一段和关键词不相关的内容,是否会被当做“辣鸡”抛弃掉,以免污染上下文呢?

答案是不会,它会一直驻留在模型的上下文中,直到上下文超限后,触发压缩机制。

所以,当你和 AI 聊着聊着发现话题跑偏时,不要试图纠偏,直接重开更快更便捷。

上下文超限时,如何压缩?

模型的上下文窗口是有限制的,而且当上下文长度越长时,模型的输出质量会愈发下降,这个现象也被叫做上下文腐烂(Context Rot)。

因此,不能等上下文窗口满了才去压缩,否则最后几轮回答已经“烂完了”。

Codex 的做法是设定了一个自动压缩阈值,默认是 90%,即当前 token 使用量超过 90% 时,触发上下文压缩。

上下文压缩也是通过模型进行的,提示词如下:

翻译一下,提示词的意思是:

你正在做 context checkpoint compaction。请给另一个将接手任务的 LLM 写交接摘要。包括当前进展、关键决策、约束、用户偏好、剩余步骤、关键数据/引用。保持简洁、结构化、聚焦于让下一个模型继续工作。

压缩之后,它会保留最近用户发送的消息和一条压缩摘要。

最近用户发送的信息也有一个阈值,默认是 20000 tokens,相当于最多保留最近用户输入的  20000 tokens内容。

这样设计也很合理,保留用户最近的输入,可以让用户感觉模型仍有最近的“记忆”,体验更顺滑,而历史的远古设定,则可能会被模型经压缩后,抛在脑后。

skill是如何触发的?

codex 安装完之后,会自动安装一些skills,放在这个文件路径:/Users/%user%/.codex/skills,基础主要包括以下五个skill:

  • imagegen:用于生成或编辑图片
  • openai-docs:用于询问 OpenAI API、模型选择、模型迁移、官方文档
  • plugin-creator:用于创建Codex plugin、生成.codex-plugin/plugin.json
  • skill-creator:用于创建或更新 Codex skill
  • skill-installer:用于安装 skill、列出可安装 skill、从 GitHub 安装 skill

每个skill的触发方式主要有三种:

  • 用户显式点名:比如你说“使用 openai-docs skill”或 OpenAI Docs。这会被识别为明确使用某个 skill

  • 任务语义匹配:比如你说“帮我查 OpenAI 最新模型怎么选”,模型看到 openai-docs 的描述后,会判断这个任务匹配该 skill,然后去读它的 SKILL.md

  • 路径引用:UI 或消息里可以带 skill path,例如指向某个 SKILL.md。代码会按路径精确匹配,避免同名歧义

总结

分析完 codex 设计后,不免发现,它给了模型很多自主性,比如工具的调用、查询命令的编写、内容的压缩均依赖模型自己完成。

如果模型能力不足,再好的 harness 也无济于事,核心竞争力是模型本身,这也就是为什么 openai 直接把 codex 开源了。

如果你想设计一个类似的agent,没必要借助 langchain 之类的繁琐工具,不如直接告诉AI:

请参考“我有一计”的这篇文章(文章链接),设计一个类似的agent。

兴许它就能直接搞定了。

参考

[1] https://github.com/openai/codex