news 2026/9/7 5:03:02

Goose 的 Recipe 自动生成提示词解析:recipe.md 如何把对话沉淀为可复用配方

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Goose 的 Recipe 自动生成提示词解析:recipe.md 如何把对话沉淀为可复用配方

Goose 的 Recipe 自动生成提示词解析:recipe.md 如何把对话沉淀为可复用配方

【免费下载链接】goosean open source, extensible AI agent that goes beyond code suggestions - install, execute, edit, and test with any LLM项目地址: https://gitcode.com/GitHub_Trending/goose3/goose

在 goose 中,Recipe(配方)是把一段已验证有效的对话流程沉淀为可复用资产的核心机制。本文以 crates/goose/src/prompts/recipe.md 这份提示词模板为主体,完整拆解它对模型输出的结构化契约、运行时加载链路,以及 Agent::create_recipe 中从"对话历史"到"Recipe 对象"的全流程解析与降级策略。读完本文,你可以理解 goose 在什么时机调用这份提示词、模型必须返回什么样的 JSON、以及解析失败时系统如何兜底。

提示词原文与输出契约

recipe.md 是一份直接发给模型的"元提示词",要求模型基于"到目前为止的整个对话"生成一个配方定义。其完整原文如下:

Based on our conversation so far, could you create: 1. A concise title (5-10 words) that captures the main topic or task 2. A brief description (1-2 sentences) that summarizes what this recipe helps with 3. A concise set of instructions (1-2 paragraphs) that describe what you've been helping with. Make the instructions generic, and higher-level so that can be re-used across various similar tasks. Pay special attention if any output styles or formats are requested (and make it clear), and note any non standard tools used or required. 4. A list of 3-5 example activities (as a few words each at most) that would be relevant to this topic Format your response in _VALID_ json, with keys being `title`, `description`, `instructions` (string), and `activities` (array of strings). For example, perhaps we have been discussing fruit and you might write: { "title": "Fruit Information Assistant", "description": "A recipe for finding and sharing information about different types of fruit.", "instructions": "Using web searches we find pictures of fruit, and always check what language to reply in.", "activities": [ "Show pics of apples", "say a random fruit", "share a fruit fact" ] }

这份提示词的设计要点可以归纳为一张输出契约表:

输出字段JSON 类型提示词中的约束对应的 Recipe 字段
titlestring5-10 个词,概括主题或任务Recipe.title(必填)
descriptionstring1-2 句,说明该配方能帮到什么Recipe.description(必填)
instructionsstring1-2 段;要求"泛化、高层"以便跨相似任务复用;必须显式保留用户要求的输出风格/格式,并注明使用到的非标准工具Recipe.instructions
activitiesstring[]3-5 个,每个仅几个词,作为加载配方时的活动建议(activity pills)Recipe.activities

两个字段名"泛化"是刻意为之的:instructions不能只复述本次对话,而要写成可以在多个相似任务中重放的通用说明;文末的"水果助手"示例则用具体样例锚定了四个字段的长度、语气与 JSON 形状,降低模型跑偏的概率。

值得注意的是,契约中只要求这 4 个字段——versionsettingsauthorextensions等其余 Recipe 字段不由模型生成,而是由宿主代码在解析后注入(后文详述),这缩小了模型出错的表面积。

提示词的注册与运行时加载

模板在 crates/goose/src/prompt_template.rs 中注册,描述为 "Prompt for generating recipe files from conversations":

( "recipe.md", "Prompt for generating recipe files from conversations", ),

加载入口是 PromptManager::get_recipe_prompt:

pub async fn get_recipe_prompt(&self) -> String { let context: HashMap<&str, Value> = HashMap::new(); prompt_template::render_template("recipe.md", &context) .unwrap_or_else(|_| "The recipe prompt is busted. Tell the user.".to_string()) }

两个实现细节值得注意:

  • 空上下文渲染recipe.md不含任何模板变量(如{{...}}占位符),因此传入空的HashMap即可。这与同目录中需要注入工具列表的plan.md等模板形成对比——提示词本身与运行环境解耦,全部上下文来自调用方拼接的对话消息。
  • 显式降级:渲染失败时不抛错,而是返回一句让 Agent 告知用户的兜底文案,保证"创建配方"这条交互路径不会因模板问题而硬失败。

create_recipe:从对话历史到模型调用

核心调用链在 Agent::create_recipe,按源码顺序分五步:

  1. 收集运行环境:从session_id取出会话,读取该会话工作目录下的扩展信息(extensions_info)与模型配置(model_config),并用当前 goose_mode 构建系统提示词。
  2. 注入提示词与工具get_recipe_prompt()的文本被作为一条Message::user()消息追加到对话末尾;同时通过get_prefixed_tools拿到会话可用的工具集,并过滤掉is_tool_visible_to_model判定为不可见的工具——即模型在"写配方"时仍能看到当前会话用过的工具描述,这正是提示词要求"注明非标准工具"的底气所在。
  3. 裁剪对话历史:recipe_conversation_history 只保留agent_visible_messages()并剔除is_turn_context()消息。源码注释说明了原因:"The recipe prompt has no turn-context instructions; drop the blocks."——recipe.md 是纯抽取型提示词,不承载轮次上下文语义,多余的 turn-context 块只会干扰抽取。
  4. 消息修复:依次执行fix_conversation(问题逐条tracing::warn!记录)与merge_consecutive_messages_for_request,保证送模型的消息序列合法。
  5. 单次补全:调用provider.complete(&model_config, &system_prompt, messages, &tools)一次性拿到结果文本,不做流式。

模型输出的解析:JSON 优先,字符串解析兜底

模型返回的文本在 agent.rs 中经过两级解析。

第一级:剥离代码围栏并解析 JSON

提示词虽要求"VALID json",但模型仍可能把结果包进 Markdown 代码块,因此先做围栏剥离:

// the response may be contained in ```json ```, strip that before parsing json let re = Regex::new(r"(?s)```[^\n]*\n(.*?)\n```").unwrap(); let clean_content = re .captures(&content) .and_then(|caps| caps.get(1).map(|m| m.as_str())) .unwrap_or(&content) .trim() .to_string();

随后对clean_contentserde_json::from_str::<Value>,并按契约严格校验:

  • instructions必须存在且为字符串,否则报Missing 'instructions' in json response/instructions' is not a string
  • activities必须存在且为数组,且每个元素必须是字符串,逐个map校验。

titledescription的提取相对宽松:解析成功但字段缺失或类型不符时,分别回落到默认值"Custom recipe from chat""a custom recipe instance from this chat session"(agent.rs#L4174-L4194)。

第二级:纯文本降级

若 JSON 解析整体失败,源码进入降级分支(agent.rs#L4111-L4143):

  • split_once("instructions:")截取 instructions 之后的内容;
  • split_once("activities:")切出活动列表;
  • 对活动列表逐行用正则^[•\-*\d]+\.?\s*去掉项目符号(圆点、星号、短横线或数字编号),过滤空行。

这条降级路径说明 goose 对该提示词的健壮性假设是"模型大概率给 JSON,但给不了也不能让功能不可用"。

宿主侧字段注入:从 4 字段 JSON 到完整 Recipe

JSON 解析出的 4 个字段并不是最终形态。agent.rs#L4145-L4211 继续注入宿主持有的字段:

  • extensionsget_enabled_extensions()取当前启用的扩展配置写入extensions
  • author:从环境变量USER(或USERNAME)读取作为contact
  • settings:由全局配置回填goose_providerconfig.get_goose_provider(),未配置时直接报No provider configured. Run 'goose configure' first)、goose_model(当前会话模型名)、temperaturemodel_config.temperature缺省为0.0)。

最终经 Recipe::builder() 构建:

let recipe = Recipe::builder() .title(title) .description(description) .instructions(instructions) .activities(activities) .extensions(extension_configs) .settings(settings) .author(author) .build()

RecipeBuilder::build()的校验规则(mod.rs#L409-L415)与提示词契约呼应:titledescription必填,且promptinstructions至少其一非空——本流程中instructions恒由提示词产出,故契约天然满足。

生成结果的去向:Recipe 如何被消费

自动生成的 Recipe 与普通手写配方走完全相同的加载与模板渲染管线:

  • Recipe::from_content 支持 YAML/JSON 双格式,并兼容顶层嵌套recipe:键的写法;解析后还会自动注入依赖扩展(如含sub_recipes时自动补summon扩展)。
  • 在 CLI 路径中,extract_recipe_info_from_cli 展示了字段语义的最终落点:recipe.prompt成为会话的首条用户输入(InputConfig.contents),recipe.instructions成为附加系统提示(additional_system_prompt),参数经 build_recipe_from_template 渲染模板中的{{param}}占位符。
  • 而提示词契约中的activities正是加载配方时界面上"活动建议"(activity pills)的数据来源——这也解释了为何提示词特别强调 activities 要"每个仅几个词":它们是要直接展示给用户点击的短标签。

小结

recipe.md 看似只有一页,却是 goose"对话资产化"闭环的起点:它用一份带完整示例的输出契约(4 字段、限定长度、泛化要求、工具声明要求)把开放式对话压缩为结构化元数据;get_recipe_prompt 保证模板可安全加载;create_recipe 则用"代码围栏剥离 + JSON 校验 + 字符串降级"的三级解析把模型输出变成可信的 Recipe 对象,再由宿主注入 provider、model、extensions 等运行字段,使自动生成的配方与手写配方等价可运行。对阅读 goose 源码的开发者而言,这条链路是理解"Agent 自我沉淀工作流"实现方式的代表性切片。

【免费下载链接】goosean open source, extensible AI agent that goes beyond code suggestions - install, execute, edit, and test with any LLM项目地址: https://gitcode.com/GitHub_Trending/goose3/goose

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/7 5:02:50

从零构建中文短文本情绪与意图分析服务:规则词典与FastAPI实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/7 5:01:17

高通平台新增QMI接口实战:从内核配置到用户态验证全流程

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/7 5:01:11

C#上位机集成虹软ArcFace SDK实现离线人脸识别实战

简介&#xff1a;一套基于虹软免费SDK的C#人脸识别Demo完整版&#xff0c;面向需要在.NET环境中快速实现人脸检测、人脸对比与人脸检索的开发者&#xff0c;适合作为WinForms桌面项目的参考工程。压缩包内共包含二十五份文件&#xff0c;核心是十七份C#源代码文件&#xff0c;完…

作者头像 李华
网站建设 2026/9/7 5:00:12

Verilog计数器必知:阻塞与非阻塞赋值底层原理解析与实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/7 4:59:01

PTN业务配置核心流程与故障排查实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华