From 7f5513adf3cc46e380b83b0b102676d7c7c059c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Tue, 2 Jun 2026 22:19:15 +0800 Subject: [PATCH] =?UTF-8?q?docs(prompt):=20=E6=B7=BB=E5=8A=A0=E6=8F=90?= =?UTF-8?q?=E7=A4=BA=E8=AF=8D=E5=B7=A5=E7=A8=8B=E6=96=B9=E6=A1=88=E8=AE=BE?= =?UTF-8?q?=E8=AE=A1=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/4-prompt-engineering.md | 611 +++++++++++++++++++++++++++++++++++ 1 file changed, 611 insertions(+) create mode 100644 docs/4-prompt-engineering.md diff --git a/docs/4-prompt-engineering.md b/docs/4-prompt-engineering.md new file mode 100644 index 0000000..7f5ae32 --- /dev/null +++ b/docs/4-prompt-engineering.md @@ -0,0 +1,611 @@ +# Phase 1: Prompt Engineering — 方案设计 + +> 定稿日期:2026-06-02 + +## 背景与目标 + +AG Core Phase 0(Foundation)已完成 LLM 调用周期的全部基础设施。Phase 1 的目标是补齐**提示词工程**能力,提供提示词的组合、模板化与优化能力,使其能直接服务于 Phase 2(工具系统)和 Phase 4(Agent 运行时)。 + +**目标**:实现 `PromptTemplate`(模板引擎)和 `PromptComposer`(提示词组合器)两个核心组件,覆盖变量插值、条件渲染、多消息序列拼接等场景。 + +--- + +## 需求分析 + +### 功能需求 + +| 模块 | 需求 | 验收条件 | +|------|------|---------| +| `PromptTemplate` | 支持变量插值 `{{ var }}` | 渲染后正确替换所有变量 | +| `PromptTemplate` | 支持条件渲染 `{{#if var}}...{{/if}}` | 变量非空/非 false 时渲染块 | +| `PromptTemplate` | 支持列表循环 `{{#each list}}...{{/each}}` | 遍历渲染集合元素 | +| `PromptTemplate` | 支持嵌套模板引用 `{{> template_name }}` | 引用已注册的模板片段 | +| `PromptTemplate` | 支持部分转义(原样输出 `{{ literal }}`) | 提供 raw block 语法 | +| `PromptComposer` | 按角色拼接 system/user/assistant 消息序列 | 输出 `Vec` | +| `PromptComposer` | 支持插入预编译的 PromptTemplate | 组合器中混合使用静态文本和模板 | +| `PromptComposer` | 支持从已有消息列表扩展 | 接收 `Vec` 作为初始状态 | +| `PromptComposer` | 支持多模态 ContentPart 构建(图片/音频/文件) | `user_content()`/`system_content()` 等接受 `OpenaiContentPart` | +| `PromptComposer` | 支持 Developer 角色(o1 系列模型) | `developer()` / `developer_template()` / `developer_content()` | +| `PromptComposer` | 支持 Tool 角色(工具执行结果回传) | `tool()` / `tool_content()` 接受 `tool_call_id` | +| `PromptComposer` | 支持 `name` 字段设置(多角色区分) | `with_name()` 链式方法为上一消息设置名称 | +| `PromptComposer` | 支持消息序列合法性验证 | `validate_messages()` 检查顺序约束与角色交替 | +| `PromptTemplateRegistry` | 模板注册表:按名称注册、查找、文件加载 | 从字符串/文件注册模板,按名称渲染 | +| `PromptTemplateRegistry` | 支持延迟编译模式 | `register_lazy()` 存储原始字符串,首次渲染时编译 | +| `PromptTemplate` | 实现 `Display` trait | 输出原始模板字符串(用于日志和调试) | +| `TemplateContext` | 支持从 JSON 构造 | `from_json()` 递归转换 `serde_json::Value` 为 `TemplateValue` | + +### 非功能需求 + +- 所有公开 API 必须带 `///` 文档注释 +- 无新增 `unwrap()` 调用 +- **零运行时依赖**(不使用 tera、askama 等模板引擎 crate) +- 模板引擎失败时返回结构化错误(`PromptError`) +- 与现有 `OpenaiChatMessage` / `ChatRequest` 类型自然集成 + +--- + +## 方案设计 + +### 模块结构 + +``` +src/ + prompt.rs # prompt 模块根:声明子模块 + 重导出公共 API + prompt/ + template.rs # PromptTemplate — 模板引擎 + template/ + registry.rs # PromptTemplateRegistry — 模板注册表 + composer.rs # PromptComposer — 提示词组合器 + error.rs # PromptError — 错误类型 +``` + +`prompt.rs` 根模块声明: + +```rust +// prompt.rs +pub mod composer; +pub mod error; +pub mod template; + +pub use composer::PromptComposer; +pub use error::PromptError; +pub use template::{PromptTemplate, PromptTemplateRegistry}; +``` + +`lib.rs` 添加: + +```diff + pub mod llm; ++pub mod prompt; +``` + +### 1. 模板引擎选择:自建轻量 + +**决策**:不使用 `tera` / `askama` / `maud` / `minijinja` 等第三方模板 crate。 + +**理由**: +- Phase 1 模板需求极其简单(变量插值 + 条件 + 列表循环),不需要 Jinja2/Handlebars 全能力 +- 无依赖 = 编译更快、无安全面、版本冲突为 0 +- 自建 50-80 行核心逻辑即可覆盖所有需求 +- Roadmap 估算 400 行总代码,60 行模板引擎足够 + +**语法设计**(参考 Handlebars 子集): + +``` +{{ variable_name }} → 变量插值 +{{#if var}}...{{/if}} → 条件渲染(var 存在且非空) +{{#if var}}...{{else}}...{{/if}} → 条件 + 否则 +{{#each list}} {{item}} {{/each}} → 列表循环 +{{#raw}} {{literal}} {{/raw}} → 原始块(不解析内部模板语法) +{{> template_name}} → 引用已注册的嵌套模板 +``` + +### 2. PromptTemplate — 模板引擎 + +```rust +// prompt/template.rs + +use std::collections::HashMap; +use crate::prompt::error::PromptError; + +/// 渲染上下文中使用的值类型。 +pub enum TemplateValue { + String(String), + Bool(bool), + Array(Vec), + Object(HashMap), +} + +/// `TemplateValue` 自动转换(提升 `ctx.insert("name", "Alice")` 的易用性)。 +impl From for TemplateValue { ... } +impl From<&str> for TemplateValue { ... } +impl From for TemplateValue { ... } + +/// 模板变量上下文。 +pub struct TemplateContext { + vars: HashMap, +} + +impl TemplateContext { + pub fn new() -> Self; + + /// 插入变量(支持 `&str` / `String` / `bool` 自动转换)。 + pub fn insert(&mut self, key: impl Into, value: impl Into); + + pub fn get(&self, key: &str) -> Option<&TemplateValue>; + + /// 从 `serde_json::Value` 递归构造(支持嵌套 Object/Array)。 + pub fn from_json(value: &serde_json::Value) -> Result; + + /// 从 `HashMap` 构造(适用于配置加载场景)。 + pub fn from_map(map: HashMap) -> Self; +} + +/// 预编译的模板。 +pub struct PromptTemplate { + /// 原始模板字符串(用于 debug)。 + raw: String, + /// 编译后的 AST 片段。 + fragments: Vec, +} + +/// 编译后的 AST 节点(内部枚举)。 +enum Fragment { + Literal(String), + Variable { name: String }, + If { condition: String, body: Vec, else_body: Vec }, + Each { variable: String, body: Vec }, + Raw(String), + Include(String), +} + +impl PromptTemplate { + /// 从模板字符串编译。 + pub fn compile(template: &str) -> Result; + + /// 使用上下文渲染。 + pub fn render(&self, ctx: &TemplateContext) -> Result; + + /// 注册可引用的子模板。 + pub fn register_partial(&mut self, name: &str, template: PromptTemplate); +} + +/// `Display` 输出原始模板字符串,便于日志和调试。 +impl fmt::Display for PromptTemplate { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.raw) + } +} +``` + +**编译流程**: +1. 逐字符扫描模板字符串 +2. 遇到 `{{` 时解析指令类型(variable/if/each/raw/include) +3. 生成 `Fragment` AST 列表 +4. 返回 `PromptTemplate { raw, fragments }` + +**渲染流程**: +1. 遍历 `fragments` +2. `Literal` → 直接追加 +3. `Variable { name }` → `ctx.get(name)` → 追加字符串值 +4. `If { condition, body, else_body }` → 判断 `ctx.get(condition)` 是否存在且真 → 递归渲染 body/else_body +5. `Each { variable, body }` → `ctx.get(variable)` 转为数组 → 为每个元素设置 `item` 变量 → 递归渲染 body +6. `Raw(text)` → 原样追加(不解析 `{{ }}`) +7. `Include(name)` → 查找已注册的 partials → 递归渲染 + +### 3. PromptTemplateRegistry — 模板注册表 + +提供轻量的模板管理能力,按名称注册、查找、从文件加载,适用于管理多个 Agent 的提示词模板。 + +```rust +// prompt/template/registry.rs + +use std::collections::HashMap; +use std::path::Path; +use crate::prompt::error::PromptError; +use crate::prompt::template::{PromptTemplate, TemplateContext}; + +/// 内部存储的模板(支持延迟编译)。 +enum StoredTemplate { + Compiled(PromptTemplate), + Raw(String), +} + +/// 模板注册表——管理多模板实例。 +pub struct PromptTemplateRegistry { + templates: HashMap, +} + +impl PromptTemplateRegistry { + pub fn new() -> Self; + + /// 从模板字符串编译并注册(立即编译)。 + pub fn register(&mut self, name: &str, template: &str) -> Result<(), PromptError>; + + /// 延迟编译注册:只存储原始字符串,首次渲染时编译。 + /// 适合模板数量多但并非全部立即使用的场景。 + pub fn register_lazy(&mut self, name: &str, template: &str); + + /// 从文件读取并编译注册。 + pub fn register_file(&mut self, name: &str, path: &Path) -> Result<(), PromptError>; + + /// 获取已注册的模板(延迟编译的模板在此首次编译)。 + pub fn get(&mut self, name: &str) -> Result<&PromptTemplate, PromptError>; + + /// 按名称渲染(`{{> name }}` 引用时自动查找)。 + pub fn render(&mut self, name: &str, ctx: &TemplateContext) -> Result; +} +``` + +**设计约束**: +- 不设全局单例,用户自行创建和持有 +- 不引入文件系统监听、热加载、序列化等复杂能力 +- 注册表内部模板可用于解析 `{{> partial_name }}` 子模板引用 +- 用户仍可单独持有 `PromptTemplate` 实例,不强制使用注册表 + +### 4. PromptComposer — 提示词组合器 + +```rust +// prompt/composer.rs + +use crate::llm::types::message::{OpenaiChatMessage, OpenaiContentPart, ContentField}; +use crate::prompt::error::PromptError; +use crate::prompt::template::{PromptTemplate, TemplateContext}; + +/// 提示词组合器——构建多角色消息序列。 +pub struct PromptComposer { + messages: Vec, + pending_name: Option, +} + +impl PromptComposer { + /// 创建一个空的组合器。 + pub fn new() -> Self; + + /// 从已有的消息列表初始化。 + pub fn from_messages(messages: Vec) -> Self; + + // ===== 纯文本消息 ===== + + /// 添加一条纯文本 system 消息。 + pub fn system(mut self, text: impl Into) -> Self; + + /// 添加一条纯文本 user 消息。 + pub fn user(mut self, text: impl Into) -> Self; + + /// 添加一条纯文本 assistant 消息。 + pub fn assistant(mut self, text: impl Into) -> Self; + + /// 添加一条纯文本 developer 消息(o1 系列模型使用)。 + pub fn developer(mut self, text: impl Into) -> Self; + + /// 添加一条 Tool 消息(工具执行结果回传)。 + pub fn tool(mut self, tool_call_id: impl Into, content: impl Into) -> Self; + + // ===== 模板消息 ===== + + /// 使用模板和上下文渲染后添加为 user 消息。 + pub fn user_template( + mut self, + template: &PromptTemplate, + ctx: &TemplateContext, + ) -> Result; + + /// 使用模板和上下文渲染后添加为 system 消息。 + pub fn system_template( + mut self, + template: &PromptTemplate, + ctx: &TemplateContext, + ) -> Result; + + /// 使用模板和上下文渲染后添加为 assistant 消息。 + pub fn assistant_template( + mut self, + template: &PromptTemplate, + ctx: &TemplateContext, + ) -> Result; + + /// 使用模板和上下文渲染后添加为 developer 消息。 + pub fn developer_template( + mut self, + template: &PromptTemplate, + ctx: &TemplateContext, + ) -> Result; + + // ===== 多模态 ContentPart ===== + + /// 添加一条含指定 ContentPart 的 system 消息。 + pub fn system_content(mut self, part: OpenaiContentPart) -> Self; + + /// 添加一条含指定 ContentPart 的 user 消息。 + pub fn user_content(mut self, part: OpenaiContentPart) -> Self; + + /// 添加一条含指定 ContentPart 的 assistant 消息。 + pub fn assistant_content(mut self, part: OpenaiContentPart) -> Self; + + /// 添加一条含指定 ContentPart 的 developer 消息。 + pub fn developer_content(mut self, part: OpenaiContentPart) -> Self; + + /// 添加一条含指定 ContentPart 的 Tool 消息。 + pub fn tool_content(mut self, tool_call_id: impl Into, part: OpenaiContentPart) -> Self; + + /// 批量添加 ContentPart 作为 user 消息。 + pub fn user_contents(mut self, parts: Vec) -> Self; + + /// 批量添加 ContentPart 作为 system 消息。 + pub fn system_contents(mut self, parts: Vec) -> Self; + + /// 批量添加 ContentPart 作为 assistant 消息。 + pub fn assistant_contents(mut self, parts: Vec) -> Self; + + /// 批量添加 ContentPart 作为 developer 消息。 + pub fn developer_contents(mut self, parts: Vec) -> Self; + + // ===== 角色标识 ===== + + /// 为上一条添加的消息设置 `name` 字段(多 agent 系统中区分同角色实体)。 + pub fn with_name(mut self, name: impl Into) -> Self; + + // ===== 构建 ===== + + /// 构建最终的消息列表。 + pub fn build(self) -> Vec; + + /// 构建并直接创建 ChatRequest(需搭配 model 参数)。 + /// 返回的 `OpenaiChatRequest` 中 `tools`、`temperature`、`max_tokens` 等字段均为 `None`, + /// 可通过结构体更新语法补全:`OpenaiChatRequest { tools: Some(...), ..req }`。 + pub fn build_request( + self, + model: impl Into, + ) -> crate::llm::types::request::OpenaiChatRequest; +} + +/// 验证消息序列是否符合 OpenAI API 要求。 +/// 检查项:Tool 消息必须紧跟在匹配的 Assistant 消息后、角色交替规则等。 +pub fn validate_messages(messages: &[OpenaiChatMessage]) -> Result<(), PromptError>; +``` + +**Builder 模式设计**: +- `PromptComposer` 采用链式调用(builder pattern),与 Rust 生态的主流风格一致 +- 每个 `system()` / `user()` / `assistant()` / `developer()` / `tool()` 方法返回 `Self`,支持连续调用 +- `with_name()` 作用于上一条消息,内部通过 `pending_name: Option` 暂存,push 消息时消费 +- `build()` 返回 `Vec`,`build_request()` 创建完整的 `OpenaiChatRequest` +- **`ContentField` 类型约定**:所有纯文本消息(`system()` / `user()` / `assistant()` / `developer()`)统一使用 `ContentField::Array(vec![OpenaiContentPart::Text{...}])`,与 OpenAI API 非流式响应的标准格式一致 + +**`validate_messages()` 校验规则**: +1. `Tool` 角色的消息必须跟在 `Assistant` 角色且含 `tool_calls` 的消息之后 +2. 禁止连续出现两条同角色的非 Tool 消息(system 除外) +3. 消息列表不能为空 + +### 5. PromptError — 错误类型 + +```rust +// prompt/error.rs + +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum PromptError { + #[error("模板解析错误: {0}")] + Parse(String), + + #[error("渲染错误: 变量 '{0}' 未找到")] + VariableNotFound(String), + + #[error("渲染错误: 引用的子模板 '{0}' 未注册")] + PartialNotFound(String), + + #[error("渲染错误: '{0}' 不是数组,无法遍历")] + NotAnArray(String), + + #[error("渲染递归超过最大深度限制 ({0})")] + MaxDepthReached(u8), + + #[error("渲染错误: {0}")] + Render(String), + + #[error("消息序列校验失败: {0}")] + InvalidSequence(String), + + #[error("文件读取错误: {0}")] + Io(#[from] std::io::Error), +} +``` + +### 6. 使用示例 + +```rust +use agcore::prompt::{PromptComposer, PromptTemplate, TemplateContext}; + +// 编译模板 +let tpl = PromptTemplate::compile( + "你是{{role}}。请回答以下问题:\n{{#if context}}参考背景:{{context}}\n{{/if}}提问:{{question}}" +)?; + +// 构建上下文 +let mut ctx = TemplateContext::new(); +ctx.insert("role", "资深工程师"); +ctx.insert("question", "Rust 的所有权规则是什么?"); +ctx.insert("context", "用户有 Java 背景"); + +// 使用组合器构建消息序列 +let messages = PromptComposer::new() + .system("你是一个专业的 Rust 助手") + .user_template(&tpl, &ctx)? + .build(); + +// 可选:直接构建 ChatRequest +let request = PromptComposer::new() + .system("你是一个翻译助手") + .user("Hello, world!") + .build_request("gpt-4o"); +``` + +### 7. 与 LlmCycle 的集成 + +`PromptComposer::build()` 输出 `Vec`,但 **`LlmCycle.messages` 是私有字段**,无法直接赋值。因此**需要对 `LlmCycle` 进行扩展**,提供消息注入入口,使 Composer 能和 `LlmCycle` 的多轮对话循环协同工作。 + +**方案**:在 `LlmCycle` 上新增 3 个方法: + +```rust +// llm/cycle.rs + +impl LlmCycle { + /// 直接设置消息历史(覆盖已有消息),支持 Builder 链式调用。 + pub fn with_messages(mut self, messages: Vec) -> Self; + + /// 追加消息到历史尾部。 + pub fn extend_messages(&mut self, messages: Vec); + + /// 使用预构建消息提交(跳过自动 push user prompt)。 + /// 与 submit() 不同,不自动添加 user_text(prompt)。 + pub async fn submit_messages( + &mut self, + messages: Vec, + tools: Vec, + ) -> Result; +} +``` + +**`submit_messages()` 与 `submit()` 的区别**: + +| 维度 | `submit()` | `submit_messages()` | +|------|-----------|-------------------| +| 输入 | `prompt: String` + `tools` | `messages: Vec` + `tools` | +| 内部操作 | 自动 push `user_text(prompt)` | 不自动添加任何消息 | +| 适用场景 | 简单单轮对话 | 多轮/预构建消息序列 | +| system_prompt 处理 | 自动插入(如果无 System 消息) | 完全由调用方控制 | + +**`system_prompt` 冲突处理**:`submit_messages()` 关闭 `LlmCycle` 的自动 system prompt 插入逻辑,避免与 `PromptComposer` 已构建的 System/Developer 消息重复。由调用方全权控制消息序列内容。 + +**使用示例**: +```rust +let messages = PromptComposer::new() + .system("你是一个专业的 Rust 助手") + .user_template(&query_tpl, &ctx)? + .build(); + +let mut cycle = LlmCycle::new(provider, config) + .with_messages(messages); + +let resp = cycle.submit_messages(vec![], tools).await?; +``` + +`PromptComposer::build_request()` 直接创建 `OpenaiChatRequest`,可用于绕过 `LlmCycle` 直接调用 `LlmProvider` 的场景。 + +**注意**:`PromptComposer` 模块 **不** 直接依赖 `LlmCycle`(避免 `prompt → cycle` 的强耦合)。集成方法全部在 `LlmCycle` 侧实现,保持单一职责。 + +--- + +## 实现计划 + +### Step 1: 创建方案文档 + +创建 `docs/4-prompt-engineering.md`(即本文档)。 + +### Step 2: PromptError + +- 创建 `src/prompt/error.rs` +- 定义 `PromptError` 枚举(Parse / Render / VariableNotFound / PartialNotFound) + +### Step 3: PromptTemplate + +- 创建 `src/prompt.rs`(模块根) +- 创建 `src/prompt/template.rs` +- 实现编译(`compile()`):逐字符扫描 → 生成 `Vec` + - 注意处理:嵌套 `#if` 栈匹配、`{{` 字面量转义、未闭合标签检测 +- 实现渲染(`render()`):递归遍历 Fragment → 拼接字符串 + - 定义非字符串值渲染格式:`Bool`→`"true"`/`"false"`、`Array`→JSON、`Object`→JSON + - 递归加深度限制(16 层)防止循环引用 +- 支持功能:变量插值、条件渲染、列表循环、原始块、子模板引用 +- 实现 `Display for PromptTemplate`(输出原始模板字符串) +- 编写 20+ 边界测试覆盖:嵌套 if/each、未闭合标签、空变量、空数组 each、递归深度超限 +- 运行 `cargo test + cargo check` 验证 + +### Step 4: PromptTemplateRegistry + +- 推荐选项:`template` 保持单文件,`PromptTemplateRegistry` 合并同文件(~40 行不值得单独目录) +- 内部存储使用 `StoredTemplate` 枚举(支持 `Compiled` 和 `Raw` 两种状态) +- 实现:`register()` 立即编译、`register_lazy()` 延迟编译、`register_file()` 文件加载 +- 实现 `get()` / `render()`(延迟编译的模板首次渲染时编译) +- 运行 `cargo check` 验证 + +### Step 5: PromptComposer + +- 创建 `src/prompt/composer.rs` +- 实现 Builder 链式 API,内部维护 `messages: Vec` + `pending_name: Option` +- 角色方法:`system()` / `user()` / `assistant()` / `developer()` / `tool()` + - 纯文本消息统一使用 `ContentField::Array([Text])` + - `tool()` 需传入 `tool_call_id` 和 `content` +- 模板方法:`system_template()` / `user_template()` / `assistant_template()` / `developer_template()` +- 多模态方法:`*_content()`(单个 ContentPart)和 `*_contents()`(批量) +- `with_name()`:作用于上一条消息的 `name` 字段 +- `build()` / `build_request()` +- `validate_messages()`:独立的纯函数,校验 Tool→Assistant 顺序、角色交替、非空 +- 运行 `cargo check` 验证 + +### Step 6: LlmCycle 扩展 + +- `cycle.rs` 新增 3 个方法: + - `with_messages(self, messages: Vec) -> Self` — 链式设置消息历史 + - `extend_messages(&mut self, messages: Vec)` — 追加消息 + - `submit_messages(&mut self, messages: Vec, tools: Vec) -> Result<...>` — 预构建消息提交 +- `submit_messages()` 关闭自动 system_prompt 插入(避免与 Composer 的 System/Developer 消息重复) +- 运行 `cargo check` 验证 + +### Step 7: lib.rs 注册 + +- `lib.rs` 添加 `pub mod prompt;` +- 运行 `cargo check` 验证 + +### Step 8: 收尾 + +- `cargo clippy` — 无警告 +- `cargo build` — 完整构建 +- 检查所有新公开 API 有 `///` 文档注释 + +--- + +## 术语表 + +| 术语 | 说明 | +|------|------| +| `TemplateValue` | 模板渲染上下文中使用的值类型枚举 | +| `TemplateContext` | 模板变量上下文,持有所有变量 | +| `PromptTemplate` | 预编译的模板,持有 AST 片段列表 | +| `Fragment` | 编译后的 AST 节点(内部枚举) | +| `PromptComposer` | 提示词组合器,构建多角色消息序列 | +| `PromptError` | 提示词工程专属错误类型 | + +--- + +## 风险评估 + +| 风险 | 概率 | 缓解措施 | +|------|------|---------| +| 模板语法不支持复杂场景(如嵌套 each) | 低 | 当前需求不涉及;后续可引入 tera 替换 | +| 自定义模板引擎解析有 bug | 中 | 编写单元测试覆盖所有语法分支 | +| 与未来 Prompt Optimizer 冲突 | 低 | PromptOptimizer 只修改模板/上下文,不改模板引擎接口 | +| 条件语义不明确(什么是"假"值) | 低 | 明确定义:None / false / 空字符串 / 空数组 均为假 | +| 子模板循环引用导致栈溢出 | 低 | 渲染时加递归深度限制(16 层)或已注册集合去重检测 | + +--- + +## 验收标准 + +1. `cargo check` 编译通过 +2. `cargo clippy` 无警告 +3. 模块文件路径正确:`src/prompt.rs` + `src/prompt/{template,composer,error}.rs` +4. `PromptTemplate::compile()` 能解析含变量/条件/循环的模板 +5. `PromptTemplate::render()` 正确渲染所有语法 +6. `PromptTemplate` 实现 `Display` trait,输出原始模板字符串 +7. `TemplateContext` 提供 `from_json()` / `from_map()` 构造方式,支持 `From<&str>` 自动转换 +8. `PromptTemplateRegistry` 支持立即编译(`register()`)、延迟编译(`register_lazy()`)、文件加载(`register_file()`) +9. `PromptComposer` 支持链式调用,覆盖 System / User / Assistant / Developer / Tool 五种角色 +10. `PromptComposer` 支持 `user_content()` / `system_content()` / `assistant_content()` / `developer_content()` / `tool_content()` 多模态方法 +11. `PromptComposer` 支持 `with_name()` 设置消息角色标识 +12. `PromptComposer::build_request()` 能创建 `OpenaiChatRequest` +13. `validate_messages()` 能校验消息序列合法性 +14. `LlmCycle` 新增 `with_messages()` / `extend_messages()` / `submit_messages()` 支持 Composer 集成 +15. `lib.rs` 包含 `pub mod prompt;` +16. 所有新公开 API 有文档注释