提示词和上下文门面模块、片段及嵌入式指令模板
这一阶段属于幕后支撑,不是开机也不是主循环。它主要管“给模型看什么话”。几个门面文件像总入口,把用户指令、提示词库、上下文片段统一摆出来,外部代码不用到处找。协作模板文件把固定说明提前打包进程序。片段定义文件给系统塞进去的小纸条规定格式和标签,避免和用户原话混淆。扩展接口还能交来新的提示词片段,最后由核心上下文目录统一登记、拼装。
嵌入式指令源
这些 crate 根暴露内置指令和提示模板源,供系统其他部分在组装提示时使用。
codex-home/src/lib.rs源码 ↗
这个文件本身很短,但作用很关键。可以把它想成一家店的前台:真正干活的东西放在后面的 instructions 模块里,而外部使用者只需要从这里拿到 CodexHomeUserInstructionsProvider 就行。第一行声明项目里有一个叫 instructions 的内部模块;第二行把这个模块里的 CodexHomeUserInstructionsProvider 重新导出,也就是摆到库的门口给别人用。这样做的好处是,外部代码不用写很长、很内部的路径,也不会被内部文件结构绑死。以后如果 instructions 里面怎么拆、怎么改,只要这里继续提供同一个名字,外部使用者就不用跟着改。
collaboration-mode-templates/src/lib.rs源码 ↗
这个文件很小,但很关键。它把 templates 目录里的几份 Markdown 文本直接编进最终程序:计划模式、默认模式、执行模式、结对编程模式。这里的 include_str! 是 Rust 的一个编译期工具,意思是“在编译程序时,把某个文件的内容当成字符串塞进来”。这样做的好处是,程序发布后不需要额外携带这些模板文件,也不会因为运行目录不对、模板文件丢失而出错。其他代码只要引用 PLAN、DEFAULT、EXECUTE 或 PAIR_PROGRAMMING,就能拿到对应模板的完整文字。它本身不做判断、不改内容,只是一个稳定的模板出口。
prompts/src/lib.rs源码 ↗
这个文件本身不写具体提示词,也不做复杂计算。它的作用更像一本书的目录加前台接待:先声明这个库里有哪些内部模块,比如代理说明、补丁工具说明、权限说明、实时对话提示、审查请求提示等;然后再用公开导出,把外部真正需要用的内容摆到统一入口上。这样,别的代码只要从 prompts 这个库拿东西就行,不必直接去找 agents、compact、review_request 这些内部文件。这样做的好处是减少混乱:以后内部文件怎么拆、怎么改,外部使用者受到的影响会小很多。它还明确了哪些提示词和函数是“正式对外提供”的,哪些只是库内部细节。
片段抽象
此组定义核心片段模型,并将其作为公共 API 暴露,用于注册和渲染上下文提示片段。
context-fragments/src/fragment.rs源码 ↗
系统有时会自动往对话里插入一些背景信息,比如环境说明、策略提醒、剩余 token 提示等。这些内容看起来也是一段文字,但它们不是用户亲手说的,所以后面过滤、记录或重放时需要能认出来。这个文件就提供了一套通用接口。实现 ContextualUserFragment 的类型,要说明这段文字用什么角色发送、正文是什么、前后是否带特殊标记。render 会把“开头标记 + 正文 + 结尾标记”拼成最终文本;into、into_response_input_item 等方法会把它变成协议里真正能发送的消息格式。FragmentRegistrationProxy 则像一个“登记牌”,不用真的创建片段内容,也能问某段文字是不是这种片段。matches_marked_text 是底层检查工具:它只认前后都有标记的文本,并且忽略大小写和首尾空白。
FragmentRegistrationProxy::new18–22 ↗
fn new() -> Self
作用:创建一个 FragmentRegistrationProxy,也就是某类上下文片段的“登记代理”。它本身不保存真实内容,只用来代表某一种片段类型。
数据流:进去不需要任何实际数据 → 它放入一个 PhantomData 标记,意思是“我代表类型 T,但不真的占有 T” → 出来一个空壳代理对象,可用于后续匹配文本。
调用关系:这是创建代理的起点。FragmentRegistrationProxy::default 会直接借用它来生成默认值;之后这个代理可以通过 FragmentRegistrationProxy::matches_text 去询问某段文字是否属于对应片段类型。
FragmentRegistrationProxy::default26–28 ↗
fn default() -> Self
作用:提供 Rust 里的默认创建方式,让调用者可以不写 new,也能得到同样的登记代理。
数据流:进去不需要参数 → 它调用 FragmentRegistrationProxy::new 来完成真正创建 → 出来一个默认的 FragmentRegistrationProxy。
调用关系:它只是把“默认构造”转交给 FragmentRegistrationProxy::new,方便需要 Default 能力的地方统一创建代理。
调用图:外部调用 1 个(new)。
FragmentRegistrationProxy::matches_text32–34 ↗
fn matches_text(&self, text: &str) -> bool
作用:判断一段文字是不是某种上下文片段。它自己不懂具体规则,而是把问题交给类型 T 自己定义的匹配方法。
数据流:进去一段文本 text → 它调用对应片段类型的 matches_text → 出来 true 或 false,表示这段文本是否像该类型自动插入的片段。
调用关系:上下文过滤代码会拿这类代理来识别注入片段。这个方法是代理和具体片段类型之间的桥,真正判断逻辑在 ContextualUserFragment::matches_text 或该类型自己的实现里。
调用图:外部调用 1 个(matches_text)。
ContextualUserFragment::matches_text57–63 ↗
fn matches_text(text: &str) -> bool
作用:按某种片段类型声明的前后标记,判断一段已有文本是不是这种片段。它用于事后识别系统曾经插入过的上下文。
数据流:进去一段待检查文本 → 它先从 type_markers 取出这种片段的开头标记和结尾标记 → 再交给 matches_marked_text 检查文本首尾是否符合 → 出来一个布尔值。
调用关系:FragmentRegistrationProxy::matches_text 会间接使用它。它本身不做细节检查,而是把通用的首尾标记判断交给 matches_marked_text。
调用图:调用 1 个内部函数(matches_marked_text);外部调用 1 个(type_markers)。
ContextualUserFragment::render65–73 ↗
fn render(&self) -> String
作用:把一个上下文片段变成最终要放进消息里的纯文本。它会把标记和正文拼起来,或者在没有标记时只返回正文。
数据流:进去的是片段对象本身 → 它读取 markers 得到前后标签,再调用 body 得到正文 → 如果没有标签就直接输出正文,否则输出“开头标签 + 正文 + 结尾标签”。
调用关系:这是所有转换成协议消息前的关键一步。ContextualUserFragment::into、ContextualUserFragment::into_boxed_response_item 和 ContextualUserFragment::into_response_input_item 都会用它生成消息内容。
调用图:外部调用 1 个(format!)。
ContextualUserFragment::into75–88 ↗
fn into(self) -> ResponseItem
作用:把一个上下文片段包装成 ResponseItem,也就是系统协议里的一条响应消息。很多地方要把环境更新、策略提醒、图片保存记录等内容塞进对话历史时会用它。
数据流:进去一个具体片段对象 → 它读取 role 作为消息角色,调用 render 生成文本内容,并把文本放进 ContentItem::InputText 列表里 → 出来一条 ResponseItem::Message;原来的片段对象被消耗掉。
调用关系:这是片段进入响应历史的常用出口。像 build_environment_update_item、append_guardian_followup_reminder、record_execpolicy_amendment_message、record_network_policy_amendment_message 等流程会调用它,把各自生成的片段统一变成协议消息。
调用图:被 11 处调用(build_environment_update_item, append_guardian_followup_reminder, record_execpolicy_amendment_message, record_network_policy_amendment_message, handle_output_item_done_records_image_save_history_message, sample_rollout, maybe_record_token_budget_remaining_context, record_image_generation_instructions, interrupted_turn_history_marker, user_shell_command_record_item (+1 more));外部调用 1 个(vec!)。
ContextualUserFragment::into_boxed_response_item90–100 ↗
fn into_boxed_response_item(self: Box<Self>) -> ResponseItem
作用:把装在 Box 里的上下文片段转换成 ResponseItem。Box 可以理解成“放在盒子里的对象”,常用于在运行时处理不同具体类型的片段。
数据流:进去一个 Box<Self> 形式的片段 → 它从盒子里的对象读取 role,调用 render 得到文本 → 出来一条 ResponseItem::Message;这个盒子和里面的片段都会被消耗。
调用关系:它服务于需要用“统一外壳”保存片段的场景。和 ContextualUserFragment::into 做的包装几乎一样,只是输入形式从普通对象变成了 boxed 对象。
调用图:外部调用 1 个(vec!)。
ContextualUserFragment::into_response_input_item102–113 ↗
fn into_response_input_item(self) -> ResponseInputItem
作用:把上下文片段包装成 ResponseInputItem,也就是作为下一次模型输入的一条消息。它用于把自动补充的上下文直接送进模型输入流。
数据流:进去一个片段对象 → 它读取 role,调用 render 生成最终文本,并把文本放进输入消息的 content 里 → 出来一条 ResponseInputItem::Message;原片段被消耗。
调用关系:它和 ContextualUserFragment::into 很像,但目标类型不同:into 面向响应记录,into_response_input_item 面向模型输入。两者都依赖 render 生成真正的文字。
调用图:外部调用 1 个(vec!)。
matches_marked_text116–130 ↗
fn matches_marked_text(start_marker: &str, end_marker: &str, text: &str) -> bool
作用:检查一段文字是否被指定的开头标记和结尾标记包住。它是识别“这是不是系统插入片段”的底层小工具。
数据流:进去开头标记、结尾标记和待检查文本 → 如果任一标记为空,直接返回 false;否则先忽略文本开头空白检查开头,再忽略文本结尾空白检查结尾,并且比较时不区分大小写 → 出来 true 或 false。
调用关系:ContextualUserFragment::matches_text 会调用它完成实际判断。它把通用的标记匹配规则集中在一处,避免每种片段都重复写一遍。
调用图:被 1 处调用(matches_text)。
context-fragments/src/lib.rs源码 ↗
可以把这个文件想成一家店的前台。真正的东西分别放在 additional_context 和 fragment 两个内部模块里;这里不做复杂计算,只告诉 Rust:这些模块存在,并把几个常用名字公开出去。pub use 的意思是“转手公开”:外部代码可以直接从这个包拿到 AdditionalContextDeveloperFragment、AdditionalContextUserFragment、ContextualUserFragment、FragmentRegistration 和 FragmentRegistrationProxy,不用钻进内部目录找。这样做的好处是接口稳定:以后内部文件怎么整理,外部使用者不一定要跟着改。
面向贡献者的提示片段
这些文件为扩展贡献者定义提示片段载荷,并通过核心上下文命名空间公开共享片段类型。
ext/extension-api/src/contributors/prompt.rs源码 ↗
这个文件像是在给提示词准备“贴标签的小纸条”。每个 PromptFragment 都包含两样东西:一是 slot,也就是这段话该放到哪一类位置;二是 text,也就是模型最终能看到的文字。PromptSlot 枚举列出了几种位置,比如开发者规则、开发者能力说明、用户上下文、单独的开发者消息。这样做的好处是,扩展只需要说“我有一段开发者规则”或“我有一段能力说明”,不用关心最终提示词怎么排版、怎么合并。文件里还提供了几个方便的创建函数,避免调用者每次都手动指定槽位。最后的 slot() 和 text() 是读取器,用来让后续组装提示词的代码拿到位置和内容。文件顶部的注释也说明,这里可能只是临时实现,未来会被已有的 fragment 机制替换。
PromptFragment::new19–24 ↗
fn new(slot: PromptSlot, text: impl Into<String>) -> Self
作用:创建一个提示词片段,把“放在哪里”和“写什么内容”打包在一起。别人需要精确指定槽位时,会直接用它。
数据流:输入一个 PromptSlot,也就是目标位置,再输入一段可以变成字符串的 text。函数把 text 转成真正的 String,并和 slot 一起放进新的 PromptFragment。输出是这个新建好的片段,不会改动别的东西。
调用关系:这是最基础的创建入口。developer_policy、developer_capability 和 separate_developer 这些更方便的函数都会把具体槽位准备好,然后把实际创建工作交给它。它内部只额外调用 into,把传入的文字转换成统一的字符串格式。
调用图:外部调用 1 个(into)。
PromptFragment::developer_policy27–29 ↗
fn developer_policy(text: impl Into<String>) -> Self
作用:快速创建一段“开发者规则”提示词片段。调用者不用记住具体的槽位名字,只要把规则文字传进来就行。
数据流:输入一段规则文字。函数把槽位固定为 DeveloperPolicy,再调用 PromptFragment::new,把槽位和文字合成一个 PromptFragment。输出是标记为开发者规则的提示词片段。
调用关系:这是给外部代码用的便捷包装。它不自己保存数据,而是把创建细节交给 PromptFragment::new,保证所有片段的创建方式一致。
调用图:外部调用 1 个(new)。
PromptFragment::developer_capability32–34 ↗
fn developer_capability(text: impl Into<String>) -> Self
作用:快速创建一段“开发者能力说明”提示词片段。它适合用来告诉模型某个扩展或环境能做什么。
数据流:输入一段能力说明文字。函数把槽位固定为 DeveloperCapabilities,然后调用 PromptFragment::new 生成完整片段。输出是带有能力说明标签的 PromptFragment。
调用关系:它是 PromptFragment::new 的一个专用入口。上层代码在贡献能力说明时会用它,后续组装提示词的部分再通过 slot 和 text 读取这些内容。
调用图:外部调用 1 个(new)。
PromptFragment::separate_developer37–39 ↗
fn separate_developer(text: impl Into<String>) -> Self
作用:快速创建一段“单独的开发者消息”提示词片段。它用于那些不想混进其他类别、需要作为独立开发者内容出现的文字。
数据流:输入一段开发者文字。函数把槽位设置成 SeparateDeveloper,再交给 PromptFragment::new 创建片段。输出是一个被标记为独立开发者提示的 PromptFragment。
调用关系:它和其他便捷创建函数一样,负责选好类别,再把真正的构造工作交给 PromptFragment::new。这样调用者不用接触底层槽位设置。
调用图:外部调用 1 个(new)。
PromptFragment::slot42–44 ↗
fn slot(&self) -> PromptSlot
作用:读取这个提示词片段的目标位置。后续组装提示词时,需要靠它判断这段文字应该放到哪一部分。
数据流:输入是已经存在的 PromptFragment。函数从里面取出 slot 并返回,因为 PromptSlot 是很小的可复制值,所以这里直接返回一份。它不修改片段内容。
调用关系:它通常会被负责拼装最终提示词的代码调用。那些代码先用 slot 判断分类,再用 text 拿到文字内容,从而把片段放到正确位置。
PromptFragment::text47–49 ↗
fn text(&self) -> &str
作用:读取这个提示词片段里真正要给模型看的文字。它只借出文字,不把文字拿走。
数据流:输入是一个 PromptFragment 的引用。函数返回内部 String 的字符串视图,也就是 &str,让调用者能读取内容。它不复制整段文字,也不修改片段。
调用关系:它通常和 slot 搭配使用:组装提示词的代码先看 slot 知道该放哪,再调用 text 拿到具体文字。这个函数不再调用其他项目函数,只是安全地暴露内部文本。
core/src/context/permissions_instructions.rs源码 ↗
这个文件很小,只有一行:把 codex_prompts 里的 PermissionsInstructions 重新导出。可以把它理解成一个“转发窗口”:真正的东西放在别的仓库或模块里,这里只是挂一个本地招牌,方便 core 里的代码从 context 这个位置找到它。PermissionsInstructions 大意是“权限相关的说明文字或规则载体”,用于告诉系统在执行某些操作时该怎样理解权限要求。没有这个转发层,其他文件可能要直接依赖更底层的包路径,路径一变就容易到处改。这个文件让引用更稳定,也让权限说明在代码结构里看起来属于 context 这一块。
core/src/context/mod.rs源码 ↗
模型回答用户时,光有用户刚说的话通常不够,还需要补充很多背景:比如当前环境、权限规则、可用插件、用户自定义指令、图片生成提示、实时会话开始或结束说明等。这个文件不直接生成这些文字,而是把每一种“上下文片段”的模块接进来,再用 pub use 或 pub(crate) use 暴露给项目其他地方。可以把它理解成一个前台索引:真正的说明书分别放在后面的小抽屉里,这里负责告诉大家有哪些抽屉、哪些抽屉能公开使用、哪些只给内部使用。没有它,组装模型输入的代码就得到处引用零散路径,既难读也容易出错。