openinterpreter 会话标题自动生成机制:opencode 标题提示词设计与源码调用链全解析
【免费下载链接】openinterpreterA coding agent for open models like Kimi K3项目地址: https://gitcode.com/GitHub_Trending/op/openinterpreter
本篇聚焦 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.md(opencode.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 条规则的四种设计意图
规则按设计意图可分为四类:
语言与表达质量
- “必须与用户消息使用同一种语言”——用户用中文提问,标题就应是中文,避免检索时的语言错位;
- “语法正确、读起来自然,不要词堆砌(no word salad)”——这是对摘要类模型典型退化(把关键词直接罗列)的针对性抑制;
- “去掉 the / this / my / a / an”——标题风格上去掉英文冠词与指代词,使标题更接近标签(tag)而非句子。
内容聚焦与检索价值
- “聚焦用户日后需要检索到的主要主题或问题”;
- “当消息提到文件时,聚焦用户想对该文件做什么,而不仅仅是提到了它”——例如
@src/auth.ts can you add refresh token support的标题是Auth refresh token support而非src/auth.ts; - “保留精确信息:技术术语、数字、文件名、HTTP 状态码”——
500这类数字是检索锚点,必须原样保留; - “不要假设技术栈”——模型不得从零星线索推断并写入未提及的框架名。
防止子任务越权
- “绝不使用工具”(Never use tools)、“绝不回答用户问题,只生成标题”(NEVER respond to questions)——标题请求虽然与主请求共用同一个模型端点,但明确禁止模型把它当成正常对话轮次;
- “不得声称自己无法生成标题,不得抱怨输入”(DO NOT SAY YOU CANNOT GENERATE...)与“即使输入极简也必须输出有意义的东西”——这两条共同兜底了输入为 "hello"、"lol" 这类极简消息时的行为,配套规则要求生成
Greeting、Quick check-in、Light chat等反映语气/意图的标题。
抑制重复与套话
- “变换措辞,避免总是以 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.rs 的build_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) } ], })三个值得注意的实现细节:
max_tokens复用主请求上限。OPENCODE_MAX_TOKENS: u32 = 32_000(opencode.rs)同时用于标题请求与主请求(build_request,opencode.rs)。标题输出虽被提示词约束在 50 字符内,但请求参数上并未单独收紧,属于“契约靠提示词、参数靠复用”的取舍。- 用户输入只取第一条真实用户消息。
first_user_text(opencode.rs)遍历prompt.input,跳过 contextual 内容(is_contextual_user_message_content判定),返回第一条role == "user"消息的文本;取不到时回退为空字符串。也就是说标题只反映对话的第一条用户消息,而非整段历史。 - 用户消息先做 JSON 字符串引号化。
quote_prompt_for_opencode(opencode.rs)用serde_json::to_string把原文包成带引号的 JSON 字符串,再经normalize_prompt_newlines(opencode.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: AtomicBool(opencode.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后:
- 若
title_request为Some,先用ChatCompletionsCompatClient::stream_chat_request_value发起标题流式请求(工具集为空ToolKinds::new()),并完整消费标题流(循环title_stream.next()仅做错误映射); - 标题请求若返回 HTTP 401,走与主请求一致的
PendingUnauthorizedRetry认证恢复流程(handle_unauthorized后continue重试); - 标题请求结束后才发送真正的
request_body主请求,并对返回流应用 harness 后处理(opencode 分支为None)。
从这条链路可以推断出整体时序:opencode 会话的首轮中,模型端点会先收到一次纯文本、无工具的“生成标题”流式请求,随后才是携带完整系统提示词与工具列表的主请求;标题结果通过流式事件进入会话层,供会话列表展示与日后检索。
四、设计要点小结
结合提示词本体与调用链,这套标题生成机制有三个可直接借鉴的工程点:
- 提示词即契约,代码即执行器。输出约束(单行、≤50 字符、无解释)全部写在提示词里,而“何时触发、取哪段输入、如何编码、如何重试”全部由
should_generate_title/first_user_text/quote_prompt_for_opencode/ 401 重试逻辑承担,提示词不掺入任何运行时状态。 - 输入面最小化。只喂第一条真实用户消息并做 JSON 引号化,既降低误读转义的风险,也把标题语义锚定在“用户最初想做什么”上——这与
<task>中“帮助日后检索”的目标一致。 - 一次性 + 首轮的触发门。
AtomicBool与“输入全为 user/developer”的组合判据,使标题成本被限制为每进程一次,且只发生在新会话首轮,对多轮长对话的 token 开销几乎为零。
相关源码入口汇总:提示词本体 opencode_title_prompt.md、请求构造与触发判定 opencode.rs、harness 集成路由 request.rs、客户端先行发送逻辑 client.rs。修改提示词内容后,由于是编译期include_str!内联,需重新构建codex-rs才能生效。
【免费下载链接】openinterpreterA coding agent for open models like Kimi K3项目地址: https://gitcode.com/GitHub_Trending/op/openinterpreter
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考