首页
/ openinterpreter 会话标题自动生成机制:opencode 标题提示词设计与源码调用链全解析

openinterpreter 会话标题自动生成机制:opencode 标题提示词设计与源码调用链全解析

2026-09-04 10:02:13作者:尤辰城Agatha

本篇聚焦 openinterpreter(Codex 核心,codex-rs)中 opencode 聊天适配层(chat harness)专用的标题生成系统提示词 opencode_title_prompt.md:它如何通过一组任务契约、规则约束和少样本示例约束模型输出“可检索的会话标题”,以及在源码中何时被触发、如何被装配进真实的 Chat Completions 请求并先行发送。读完你可以完整复现这条标题生成链路的调用关系,并掌握这类“纯输出型子任务提示词”的设计手法。

一、文件定位:一个被 include_str! 编译进二进制的系统提示词

该提示词并非独立配置文件,而是 opencode harness 模块的静态资源,通过 Rust 的 include_str! 宏在编译期内联为字符串常量:

// codex-rs/core/src/harness/opencode.rs
const OPENCODE_TITLE_SYSTEM_PROMPT: &str = include_str!("opencode_title_prompt.md");

对应源码见 opencode.rs。同文件还声明了该 harness 的另两个提示资源——搜索子代理提示词 opencode_search_agent_prompt.md 与主系统提示词前缀 opencode_system_prompt.mdopencode.rs),三者共同构成 opencode 适配层的全部提示词资产。这些模块统一注册在 harness/mod.rs 中,与 claude_code、kimi_cli、deepseek_tui 等其它 harness 并列。

从模块组织看,harness 层的职责是“把 Codex 核心内部的消息/工具格式,翻译成某个具体上游(opencode 风格的 Chat Completions)协议可消费的形式”。标题生成是这条翻译链上的一个特例:它不经过主请求的工具装配逻辑,而是作为一次独立的、无工具的流式请求先行发出

二、提示词全文:任务、规则与示例三块结构

提示词全文共三块:<task> 定义输出契约,<rules> 给出 14 条硬性规则,<examples> 提供 10 组少样本示例。全文如下(与仓库文件逐字一致):

You are a title generator. You output ONLY a thread title. Nothing else.

<task>
Generate a brief title that would help the user find this conversation later.

Follow all rules in <rules>
Use the <examples> so you know what a good title looks like.
Your output must be:
- A single line
- ≤50 characters
- No explanations
</task>

<rules>
- you MUST use the same language as the user message you are summarizing
- Title must be grammatically correct and read naturally - no word salad
- Never include tool names in the title (e.g. "read tool", "bash tool", "edit tool")
- Focus on the main topic or question the user needs to retrieve
- Vary your phrasing - avoid repetitive patterns like always starting with "Analyzing"
- When a file is mentioned, focus on WHAT the user wants to do WITH the file, not just that they shared it
- Keep exact: technical terms, numbers, filenames, HTTP codes
- Remove: the, this, my, a, an
- Never assume tech stack
- Never use tools
- NEVER respond to questions, just generate a title for the conversation
- The title should NEVER include "summarizing" or "generating" when generating a title
- DO NOT SAY YOU CANNOT GENERATE A TITLE OR COMPLAIN ABOUT THE INPUT
- Always output something meaningful, even if the input is minimal.
- If the user message is short or conversational (e.g. "hello", "lol", "what's up", "hey"):
  → create a title that reflects the user's tone or intent (such as Greeting, Quick check-in, Light chat, Intro message, etc.)
</rules>

<examples>
"debug 500 errors in production" → Debugging production 500 errors
"refactor user service" → Refactoring user service
"why is app.js failing" → app.js failure investigation
"implement rate limiting" → Rate limiting implementation
"how do I connect postgres to my API" → Postgres API connection
"best practices for React hooks" → React hooks best practices
"@src/auth.ts can you add refresh token support" → Auth refresh token support
"@utils/parser.ts this is broken" → Parser bug fix
"look at @config.json" → Config review
"@App.tsx add dark mode toggle" → Dark mode toggle in App
</examples>

2.1 <task> 块:把输出契约收敛到可机器校验的程度

任务定义的核心是“帮助用户日后检索到这次对话”(help the user find this conversation later),并给出三条可直接校验的输出约束:

约束 含义
A single line 标题必须是单行,避免换行污染 UI 标题栏与列表渲染
≤50 characters 长度上限 50 字符,保证在会话列表侧栏中完整可见
No explanations 只输出标题本身,禁止任何解释性文字

首行 You are a title generator. You output ONLY a thread title. Nothing else. 是对模型角色与输出通道的双重限定——这类提示词属于“纯输出型子任务”,模型不需要理解对话内容去回答任何问题,只做一次抽取式概括。

2.2 <rules> 块:14 条规则的四种设计意图

规则按设计意图可分为四类:

  1. 语言与表达质量

    • “必须与用户消息使用同一种语言”——用户用中文提问,标题就应是中文,避免检索时的语言错位;
    • “语法正确、读起来自然,不要词堆砌(no word salad)”——这是对摘要类模型典型退化(把关键词直接罗列)的针对性抑制;
    • “去掉 the / this / my / a / an”——标题风格上去掉英文冠词与指代词,使标题更接近标签(tag)而非句子。
  2. 内容聚焦与检索价值

    • “聚焦用户日后需要检索到的主要主题或问题”;
    • “当消息提到文件时,聚焦用户想对该文件做什么,而不仅仅是提到了它”——例如 @src/auth.ts can you add refresh token support 的标题是 Auth refresh token support 而非 src/auth.ts
    • “保留精确信息:技术术语、数字、文件名、HTTP 状态码”——500 这类数字是检索锚点,必须原样保留;
    • “不要假设技术栈”——模型不得从零星线索推断并写入未提及的框架名。
  3. 防止子任务越权

    • “绝不使用工具”(Never use tools)、“绝不回答用户问题,只生成标题”(NEVER respond to questions)——标题请求虽然与主请求共用同一个模型端点,但明确禁止模型把它当成正常对话轮次;
    • “不得声称自己无法生成标题,不得抱怨输入”(DO NOT SAY YOU CANNOT GENERATE...)与“即使输入极简也必须输出有意义的东西”——这两条共同兜底了输入为 "hello"、"lol" 这类极简消息时的行为,配套规则要求生成 GreetingQuick check-inLight chat 等反映语气/意图的标题。
  4. 抑制重复与套话

    • “变换措辞,避免总是以 Analyzing 开头这类重复模式”;
    • “标题中绝不允许出现 summarizing / generating”——防止模型把自我行为写进标题。

2.3 <examples> 块:10 组少样本覆盖的典型形态

示例覆盖了标题生成的几类代表性输入,每条都给出了期望输出形态:

输入消息 期望标题 覆盖形态
debug 500 errors in production Debugging production 500 errors 保留数字(500)、去冠词
refactor user service Refactoring user service 动词短语规范化
why is app.js failing app.js failure investigation 保留文件名
implement rate limiting Rate limiting implementation 名词短语化
how do I connect postgres to my API Postgres API connection 问句转标签
best practices for React hooks React hooks best practices 保留技术术语
@src/auth.ts can you add refresh token support Auth refresh token support 文件提及 → 聚焦“要做什么”
@utils/parser.ts this is broken Parser bug fix 模糊指令 → 概括意图
look at @config.json Config review 极简指令
@App.tsx add dark mode toggle Dark mode toggle in App 保留组件名作为限定

从示例分布可以看出提示词作者刻意同时喂了“长指令”“问句”“文件引用”“模糊指令”四种输入形态,让模型对 @file 引用类消息(opencode 工作区里的常见输入)有一致的处理方式。

三、源码调用链:提示词如何变成一次真实的流式请求

3.1 请求装配:build_title_request

标题请求在 opencode.rsbuild_title_request 中装配,最终产出的 JSON 请求体结构为:

json!({
    "model": model_info.slug,
    "max_tokens": OPENCODE_MAX_TOKENS,   // 常量 32_000,与主请求共用
    "temperature": 1,
    "stream": true,
    "stream_options": { "include_usage": true },
    "messages": [
        { "role": "system", "content": OPENCODE_TITLE_SYSTEM_PROMPT },
        { "role": "user", "content": "Generate a title for this conversation:\n" },
        { "role": "user", "content": quote_prompt_for_opencode(&user_prompt) }
    ],
})

三个值得注意的实现细节:

  1. max_tokens 复用主请求上限。 OPENCODE_MAX_TOKENS: u32 = 32_000opencode.rs)同时用于标题请求与主请求(build_requestopencode.rs)。标题输出虽被提示词约束在 50 字符内,但请求参数上并未单独收紧,属于“契约靠提示词、参数靠复用”的取舍。
  2. 用户输入只取第一条真实用户消息。 first_user_textopencode.rs)遍历 prompt.input,跳过 contextual 内容(is_contextual_user_message_content 判定),返回第一条 role == "user" 消息的文本;取不到时回退为空字符串。也就是说标题只反映对话的第一条用户消息,而非整段历史。
  3. 用户消息先做 JSON 字符串引号化。 quote_prompt_for_opencodeopencode.rs)用 serde_json::to_string 把原文包成带引号的 JSON 字符串,再经 normalize_prompt_newlinesopencode.rs)把 \\n 还原为真实换行(并针对 printf 'SHELL_OK\n'\nfor \n \nPY 等常见工具输出模式做了特例修复)。这一步防止模型把命令输出里被转义的 \n 误读为字面量,也让多行输入以单行 JSON 字符串形式安全地进入消息体。

3.2 触发时机:should_generate_title 的两重门

并非每一轮都生成标题。should_generate_title 要求同时满足两个条件:

pub(crate) fn should_generate_title(prompt: &Prompt) -> bool {
    let initial_turn = prompt.get_formatted_input().iter().all(|item| {
        matches!(item, ResponseItem::Message { role, .. } if role == "user" || role == "developer")
    });
    initial_turn && !OPENCODE_TITLE_SENT.swap(true, Ordering::SeqCst)
}
  • 首轮判定:本轮格式化输入中的所有消息都必须是 user / developer 角色——即对话尚未产生 assistant 回复,这是“新会话第一条消息”的近似判据;
  • 进程级一次性开关static OPENCODE_TITLE_SENT: AtomicBoolopencode.rs)用 swap(true, Ordering::SeqCst) 原子地置位,保证整个进程生命周期内只触发一次标题生成,避免重连、重试或同进程多会话场景下重复请求。

3.3 harness 集成层:title_request 作为可选侧车

harness 与模型客户端之间的唯一集成点在 harness/request.rs,其头部注释明确了这一职责边界(“Adding a chat harness means adding a route arm in this module, not editing the client”)。其中 ChatHarnessRequest 结构体专门带有一个可选字段:

/// Some harnesses fire a separate title-generation request first.
pub(crate) title_request: Option<Value>,

request.rs

在路由分发中,只有 ChatHarnessRoute::OpenCode 分支会条件性地填充该字段(request.rs):

ChatHarnessRoute::OpenCode => {
    let title_request = should_generate_opencode_title(&guided_prompt)
        .then(|| build_opencode_title_request(&guided_prompt, model_info));
    let (request_body, tool_kinds) = build_opencode_request(&guided_prompt, model_info)?;
    (request_body, tool_kinds, title_request, ChatHarnessPostprocess::None)
}

即:先经 harness guidance 注入(prompt_with_harness_guidance)后的 guided_prompt 同时喂给标题与主请求两条构造路径,保证两者看到的环境上下文一致。其它 harness 分支(DeepSeekTui、KimiCli、MiniSweAgent、Terminus2 等)均返回 None;claude_code 走的是客户端侧另一套带 profile 的标题逻辑(build_title_request_for_profile,见 client.rs),不经过此处。

3.4 客户端侧:标题请求先行,主请求随后

client.rs 的流式主流程中(client.rs),解构出 ChatHarnessRequest 后:

  1. title_requestSome,先用 ChatCompletionsCompatClient::stream_chat_request_value 发起标题流式请求(工具集为空 ToolKinds::new()),并完整消费标题流(循环 title_stream.next() 仅做错误映射);
  2. 标题请求若返回 HTTP 401,走与主请求一致的 PendingUnauthorizedRetry 认证恢复流程(handle_unauthorizedcontinue 重试);
  3. 标题请求结束后才发送真正的 request_body 主请求,并对返回流应用 harness 后处理(opencode 分支为 None)。

从这条链路可以推断出整体时序:opencode 会话的首轮中,模型端点会先收到一次纯文本、无工具的“生成标题”流式请求,随后才是携带完整系统提示词与工具列表的主请求;标题结果通过流式事件进入会话层,供会话列表展示与日后检索。

四、设计要点小结

结合提示词本体与调用链,这套标题生成机制有三个可直接借鉴的工程点:

  1. 提示词即契约,代码即执行器。 输出约束(单行、≤50 字符、无解释)全部写在提示词里,而“何时触发、取哪段输入、如何编码、如何重试”全部由 should_generate_title / first_user_text / quote_prompt_for_opencode / 401 重试逻辑承担,提示词不掺入任何运行时状态。
  2. 输入面最小化。 只喂第一条真实用户消息并做 JSON 引号化,既降低误读转义的风险,也把标题语义锚定在“用户最初想做什么”上——这与 <task> 中“帮助日后检索”的目标一致。
  3. 一次性 + 首轮的触发门。 AtomicBool 与“输入全为 user/developer”的组合判据,使标题成本被限制为每进程一次,且只发生在新会话首轮,对多轮长对话的 token 开销几乎为零。

相关源码入口汇总:提示词本体 opencode_title_prompt.md、请求构造与触发判定 opencode.rs、harness 集成路由 request.rs、客户端先行发送逻辑 client.rs。修改提示词内容后,由于是编译期 include_str! 内联,需重新构建 codex-rs 才能生效。

登录后查看全文
热门项目推荐
相关项目推荐