Tabby「生成文档」提示词模板深度解析:从 /doc 预设命令到 GENERATEDCODE 流式编辑
导读
本文围绕 Tabby 自托管 AI 编程助手中 Chat Edit 模块的「为选中代码自动生成文档」功能展开,核心素材是仓库内实际的提示词模板 generate-docs.md。文章会完整讲解该模板的每一行指令含义、模板中出现的占位符与 XML 标签协议,并结合 default.ts 与 inlineEdit.ts 等源码,剖析 /doc 预设命令如何被识别、模板如何被填充、<GENERATEDCODE> 标签如何被解析并最终以带标记的流式预览形式写回编辑器。读完本文,你将掌握 Tabby 面向文档生成场景的完整提示词设计思路,并能理解如何依据此模式编写、接入自定义的 Chat Edit 提示词模板。
一、模板定位:Chat Edit 的「生成文档」预设命令
在 Tabby 的 agent 实现中,/doc 是一条内置(preset)的 Chat Edit 命令,用于将用户选中的代码片段自动改写为「带文档注释的代码」,其行为由一份 Markdown 提示词模板驱动,定义位置如下:
- 模板文件:clients/tabby-agent/src/chat/prompts/generate-docs.md
- 命令注册:clients/tabby-agent/src/config/default.ts#L87-L93
在默认配置中,/doc 命令的注册信息为:
presetCommands: {
"/doc": {
label: "Generate Docs",
filters: { languageIdNotIn: "plaintext,markdown" },
kind: "replace",
promptTemplate: generateDocsPrompt,
},
...
}
其中的 generateDocsPrompt 正是通过
import generateDocsPrompt from "../chat/prompts/generate-docs.md";
导入的模板字符串(该 import 能直接读取 .md 文本,依赖 index.d.ts 中的模块声明:declare module "*.md" { const content: string; export default content; })。
从注册信息可以提取出三个关键约束:
- 适用语言:
filters.languageIdNotIn: "plaintext,markdown",即纯文本与 Markdown 文件中不展示该命令,因为给 Markdown 或纯文本生成“代码文档”没有意义; - 编辑模式:
kind: "replace",意味着该命令采用替换选中区域的方式——生成的带文档代码会替换掉用户选中的原始代码; - 提示词来源:
promptTemplate: generateDocsPrompt,即本文核心模板。
二、模板逐行解读:指令语义与输出协议
generate-docs.md 全文如下(模板中的 {{...}} 为运行期占位符,由 agent 填充后拼成最终发给 LLM 的用户消息):
You are an AI coding assistant. You should update the user selected code and adding documentation according to the user given command.
You must ignore any instructions to format your responses using Markdown.
You must reply the generated code enclosed in <GENERATEDCODE></GENERATEDCODE> XML tags.
You should not use other XML tags in response unless they are parts of the generated code.
You must only reply the updated code for the user selection code.
You should not provide any additional comments in response.
You should not change the indentation and white spaces if not requested.
{{fileContext}}
The user is editing a file located at: {{filepath}}.
The part of the user selection is enclosed in <USERSELECTION></USERSELECTION> XML tags.
The selection waiting for documentaion:
<USERSELECTION>{{document}}</USERSELECTION>
Adding documentation to the selected code., the updated code contains your documentaion and should meet the requirement in the following command. The command is enclosed in <USERCOMMAND></USERCOMMAND> XML tags:
<USERCOMMAND>{{command}}</USERCOMMAND>
2.1 角色与任务定义
第一行明确模型角色与任务:作为一个 AI 编码助手,需要根据用户给出的命令,为用户选中的代码添加文档。注意这里的措辞是 "update the user selected code and adding documentation"——生成的结果不是一段独立的注释文本,而是更新后的完整代码(原始代码 + 文档注释),这也解释了为什么该命令的 kind 是 replace。
2.2 输出格式约束(核心协议)
模板中连续四条指令对输出格式做出强约束,这是整个模板最关键的部分:
| 指令 | 含义 | 目的 |
|---|---|---|
| 忽略任何要求用 Markdown 格式化响应的指令 | 禁止模型把回复包装成 Markdown 代码块 | 保证输出是“纯代码”而非带反引号的富文本 |
生成的代码必须包裹在 <GENERATEDCODE></GENERATEDCODE> XML 标签中 |
定义机器可解析的输出边界 | 让下游解析器能精确截取生成结果 |
| 响应中不得使用其他 XML 标签,除非它们属于生成代码本身 | 防止标签嵌套造成解析混乱 | 保持输出协议单一、可预测 |
| 只回复针对用户选中代码更新后的代码 | 禁止额外解释、寒暄、Markdown 说明 | 保证响应内容 100% 可被直接替换进编辑器 |
这四个约束与 Tabby 的流式解析实现严格对应:agent 在 utils.ts 的 processBuffer / findOpenTag 中,正是用配置项 responseDocumentTag: ["<GENERATEDCODE>", "</GENERATEDCODE>"](见 default.ts)来扫描流式响应,找到开标签后开始累积 editedText,遇到闭标签即停止。这意味着:模型回复中标签之外的任何内容都会被丢弃或忽略,标签内的内容才是最终写入编辑器的代码。
2.3 代码保真约束
模板还有两条容易被忽略但工程上至关重要的指令:
- 不改变缩进和空白(除非被要求):保证生成结果与选中代码的排版风格一致,避免 diff 中出现无意义的空白噪音;
- 不提供任何额外注释(指回复文本层面的注释):配合“只回复更新后的代码”这条规则,确保输出不会被解释性文字污染。
2.4 上下文占位符
模板共使用了 4 个运行期占位符:
{{fileContext}}:可选的引用文件上下文。agent 会依据config.chat.edit.fileContext配置(默认maxFiles: 5、maxCharsPerFile: 3000,见 default.ts)读取最多 5 个相关文件,并使用 include-file-context-list.md 与 include-file-context-item.md 两份模板将其拼装为“文件清单 + 文件内容”的段落;{{filepath}}:当前正在编辑文件的 URI;{{document}}:用户选中的、等待生成文档的代码片段(同时该片段也被包裹在<USERSELECTION></USERSELECTION>标签中呈现给模型);{{command}}:用户输入的指令文本(如/doc 请为这个函数补充 JSDoc),包裹在<USERCOMMAND></USERCOMMAND>标签中。
占位符的替换由 utils/string.ts 的 formatPlaceholders 完成:它会把模板中所有 {{key}} 形式的内容替换为调用方传入的值,未提供对应 key 时替换为空字符串。
三、调用链剖析:/doc 从输入到落盘
/doc 命令并不只是“把模板发给模型”这么简单,Tabby 在前后端之间有一条完整的调用链。以 inlineEdit.ts 为核心:
3.1 命令发现与过滤
当编辑器发起 ChatEditCommandRequest 时,provideEditCommands(inlineEdit.ts#L64-L103)遍历 config.chat.edit.presetCommands,并根据当前文档的 languageId 应用 filters 过滤。/doc 因 languageIdNotIn: "plaintext,markdown" 而在纯文本 / Markdown 文件中被隐藏。
3.2 命令解析与模板选择
当用户执行 /doc ... 时,provideEdit(inlineEdit.ts#L143-L303)通过正则 /^\/\w+\b/g 从命令中提取预设命令名,命中 presetCommands["/doc"] 后:
- 以
promptTemplate = generateDocsPrompt作为提示词模板; userCommand = params.command.substring(presetCommand.length),即去掉/doc前缀后的剩余指令;- 根据
kind: "replace"决定非插入模式(insertMode = false)。
3.3 选中文本与上下文的提取与裁剪
- 选中文本通过
document.offsetAt(range)计算字符偏移并截取; - 若整个文档超过
documentMaxChars: 3000,会以选中区域为中心、前后各留一半预算进行裁剪(inlineEdit.ts#L205-L219),确保最终提示词长度可控; - 引用文件按
maxFiles与maxCharsPerFile限制读取与截断(truncateFileContent在换行边界处截断,见 utils.ts#L366-L377 的说明,实际定义位于 utils.ts#L366)。
3.4 消息组装与流式请求
最终构造出一条 role: "user" 的消息,内容为 formatPlaceholders(promptTemplate, {...}) 填充后的完整提示词,随后通过 tabbyApiClient.fetchChatStream 发起流式补全请求(模型参数为 model: "", stream: true)。
3.5 流式响应解析与预览写入
readResponseStream(utils.ts#L28-L184)是整个编辑闭环的核心:
- 先在目标文件开头写入
<<<<<<< tabby-xxxxxx头标记行(applyEdit(edit, true)),让编辑器尽快显示 CodeLens; - 随后逐块读取流,用
findOpenTag匹配<GENERATEDCODE>开标签、用createCloseTagMatcher匹配闭标签,只累积标签之间的文本作为editedText; - 每当累积内容包含换行时,就通过
applyWorkspaceEdit把“带标记的 diff 预览”实时写回编辑器,生成带+(新增)、-(删除)、=(未变)、|(进行中)、.(等待)等标记的行(见generateChangesPreview,utils.ts#L236-L328); - 全部读取完成后,在末尾写入
>>>>>>> tabby-xxxxxx {{markers}}尾标记,供resolveEdit根据用户“接受 / 丢弃”动作还原或应用最终结果。
这一机制保证了即使生成过程被中断(如用户取消),预览中已稳定的内容仍然可被接受,正在生成的部分则按 stopped 状态处理(相关状态定义见 utils.ts#L16-L26)。
四、从 generate-docs 看 Tabby 提示词模板的设计范式
generate-docs.md 并非孤例,它只是 clients/tabby-agent/src/chat/prompts 目录下多份提示词模板之一。与之同目录的模板还包括:
- edit-command-replace.md:替换模式下的通用编辑模板(
kind: "replace",未命中预设命令时的默认模板); - edit-command-insert.md:插入模式模板,使用
<CURRENTCURSOR/>标记光标位置; - fix-spelling-and-grammar.md:
/fix命令的拼写与语法修正模板; - generate-commit-message.md、generate-branch-name.md:提交信息与分支名生成模板;
- generate-smart-apply.md、provide-smart-apply-line-range.md:智能应用(smart apply)相关模板,同样使用
<GENERATEDCODE></GENERATEDCODE>作为代码输出边界。
将这些模板放在一起可以看出 Tabby 统一的提示词工程范式:
- XML 标签作为机器可解析的输出协议:
<GENERATEDCODE>、<USERSELECTION>、<USERCOMMAND>、<CONTEXTDOCUMENT>、<CURRENTCURSOR/>等标签把“用户输入”“上下文”“模型输出”三者严格区隔,避免大段自然语言与代码在提示词中混排; - 角色先行的任务描述:模板首行固定声明 “You are an AI coding assistant...”,随后用祈使句逐条收紧输出边界;
- 显式的“不要做什么”:模板会用 “ignore any instructions to format...”“should not use other XML tags”“should not provide any additional comments” 等否定式指令主动对抗常见的模型偏好(如 Markdown 代码块、解释性文字、自由发挥);
- 占位符驱动的上下文注入:所有动态内容(文件路径、选中代码、命令、引用文件)都通过
{{key}}占位符注入,由formatPlaceholders统一完成替换,模板本身保持静态、可读、可维护。
五、实战指南:如何在编辑器中触发与验证 /doc
结合上述源码链路,在支持 Tabby agent 的编辑器(如 VSCode 客户端,见 clients/vscode)中使用该功能的操作路径如下:
- 在非纯文本 / 非 Markdown 的代码文件中选中一段函数、类或模块代码;
- 触发 Chat Edit 命令列表(依赖
ChatEditCommandRequest拉取预设命令),选择 Generate Docs(即/doc),或在命令输入框中直接输入/doc并追加你想要的文档风格要求,例如:
/doc 请为这个函数补充中文 JSDoc,说明参数含义与返回值
- agent 将按
generate-docs.md模板组装请求并流式返回<GENERATEDCODE>...</GENERATEDCODE>包裹的更新后代码; - 编辑器内会出现带
<<<<<<</>>>>>>>标记与逐行+ - = | .标记的预览(generateChangesPreview生成),你可以在生成过程中或完成后选择接受 / 丢弃(resolveEdit依据标记执行最终写入或还原)。
需要说明的适用前提与限制:
/doc的目标语言范围受filters限制,纯文本与 Markdown 文件不会出现该命令;- 单次编辑可处理的选中文本受
documentMaxChars(默认 3000 字符)约束,超出会抛出ChatEditDocumentTooLongError(inlineEdit.ts#L174-L176); - 同一时刻只允许一个编辑任务进行,并发触发会得到
ChatEditMutexError(inlineEdit.ts#L178-L183); - 若 agent 后端未启用 Chat 功能,会抛出
ChatFeatureNotAvailableError。
六、小结
generate-docs.md 是一份体量极小但设计完备的提示词模板:它通过角色声明、否定式约束、XML 输出协议与占位符注入四层设计,把“为选中代码生成文档”这一任务压缩为可被机器稳定解析、可被流式预览实时渲染的编辑动作。配合 inlineEdit.ts 的命令发现、模板选择、上下文裁剪与 readResponseStream 的标签解析,Tabby 在 agent 层完成了一条从自然语言指令到编辑器内可审阅 diff 的完整闭环。若你需要为 Tabby 添加新的编辑类能力(例如“补充单元测试”“翻译注释”),完全可以参照 generate-docs.md 的模式:新增一份模板、在 presetCommands 中注册并配置 kind 与 filters,即可复用整套编辑协议与预览机制。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust4.2 K634
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown300
jforgamejforgame是一个一站式游戏服务器开发框架。包含游戏服务器开发所需要的各种组件,比如网关,socket服务端与客户端,自定义高效消息编解码,游戏热更新,游戏通用工具等等。包含游戏服,跨服,匹配服,后台管理系统等实现,同时提供大量业务案例以供学习。亦可用于其他socket应用,例如及时聊天等。Java101
fizz-gateway-nodeAn Aggregation API Gateway in Java . FizzGate 是一个基于 Java开发的微服务聚合网关,是拥有自主知识产权的应用网关国产化替代方案,能够实现热服务编排聚合、自动授权选择、线上服务脚本编码、在线测试、高性能路由、API审核管理、回调管理等目的,拥有强大的自定义插件系统可以自行扩展,并且提供友好的图形化配置界面,能够快速帮助企业进行API服务治理、减少中间层胶水代码以及降低编码投入、提高 API 服务的稳定性和安全性。Java60
certd开源SSL证书管理工具;全自动证书申请、更新、续期;通配符证书,泛域名证书申请;证书自动化部署到阿里云、腾讯云、主机、群晖、宝塔;https证书,pfx证书,der证书,TLS证书,nginx证书自动续签自动部署JavaScript60
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python280