# Prompt 模板管理生效机制与占位符使用分析 ## 1. 核心架构 Prompt 模板管理系统由以下核心组件构成: ### 1.1 数据模型 **PromptTemplate(模板实体)** - `id`: UUID,模板唯一标识 - `tenant_id`: 租户 ID,实现多租户隔离 - `name`: 模板名称 - `scene`: 场景标识(如 "default"、"customer_service") - `description`: 模板描述 - `is_default`: 是否为默认模板 **PromptTemplateVersion(模板版本)** - `template_id`: 关联的模板 ID - `version`: 版本号(整数,自增) - `status`: 版本状态(draft/published/archived) - `system_instruction`: 系统指令内容(包含占位符) - `variables`: 自定义变量定义列表 ### 1.2 核心服务 **PromptTemplateService** - 模板管理服务 - 位置:`ai-service/app/services/prompt/template_service.py` - 功能:模板 CRUD、版本管理、发布/回滚、缓存 **VariableResolver** - 变量解析器 - 位置:`ai-service/app/services/prompt/variable_resolver.py` - 功能:占位符替换、变量验证 **OrchestratorService** - 编排服务 - 位置:`ai-service/app/services/orchestrator.py` - 功能:在对话生成流程中加载和应用模板 --- ## 2. 生效机制详解 ### 2.1 模板加载流程(12 步 Pipeline 中的第 7 步) ``` 用户请求 → Orchestrator._build_system_prompt() → 加载模板 → 解析变量 → 注入行为规则 → 传递给 LLM ``` **详细步骤**: 1. **触发时机**:每次对话请求到达时,在 Step 7(PromptBuilder)执行 2. **加载逻辑**: ```python # orchestrator.py:632-638 template_service = PromptTemplateService(session) template_version = await template_service.get_published_template( tenant_id=ctx.tenant_id, scene="default", # 场景可配置 ) ``` 3. **缓存机制**: - 首次加载:从数据库查询 `status=published` 的版本 - 后续请求:从内存缓存读取(TTL 300 秒) - 缓存失效:发布/回滚操作会自动清除缓存 4. **降级策略**: - 如果没有已发布的模板 → 使用硬编码的 `SYSTEM_PROMPT` - 如果数据库查询失败 → 使用硬编码的 `SYSTEM_PROMPT` ### 2.2 版本管理机制 **版本状态流转**: ``` draft(草稿)→ published(已发布)→ archived(已归档) ``` **发布流程**: ```python # template_service.py:248-287 async def publish_version(tenant_id, template_id, version): 1. 查询模板是否存在(租户隔离) 2. 将当前 published 版本改为 archived 3. 将目标版本改为 published 4. 清除缓存并预热新版本 5. 记录日志 ``` **回滚流程**: ```python # template_service.py:289-298 async def rollback_version(tenant_id, template_id, version): # 实际上就是调用 publish_version # 将历史版本重新标记为 published ``` **热更新保证**: - 发布/回滚后立即清除缓存:`self._cache.invalidate(tenant_id, scene)` - 下次请求会从数据库加载最新版本 - 无需重启服务 ### 2.3 租户隔离机制 所有操作都强制进行租户隔离: ```python # 查询时必须带 tenant_id stmt = select(PromptTemplate).where( PromptTemplate.tenant_id == tenant_id, PromptTemplate.scene == scene, ) ``` 不同租户的模板完全独立,互不影响。 --- ## 3. 占位符使用详解 ### 3.1 占位符语法 **格式**:`{{variable_name}}` **示例**: ``` 你是 {{persona_name}},当前时间是 {{current_time}}。 你正在为 {{tenant_name}} 提供 {{channel_type}} 渠道的客服服务。 ``` ### 3.2 内置变量 **VariableResolver** 提供以下内置变量: | 变量名 | 类型 | 默认值 | 说明 | |--------|------|--------|------| | `persona_name` | string | "小N" | AI 人设名称 | | `current_time` | function | 动态生成 | 当前时间(格式:YYYY-MM-DD HH:MM) | | `channel_type` | string | "default" | 渠道类型(wechat/douyin/jd) | | `tenant_name` | string | "平台" | 租户名称 | | `session_id` | string | "" | 会话 ID | **动态变量示例**: ```python # variable_resolver.py:15-21 BUILTIN_VARIABLES = { "persona_name": "小N", "current_time": lambda: datetime.now().strftime("%Y-%m-%d %H:%M"), "channel_type": "default", "tenant_name": "平台", "session_id": "", } ``` ### 3.3 自定义变量 **定义方式**:在模板的 `variables` 字段中定义 ```json { "variables": [ { "name": "company_name", "default": "XX科技有限公司", "description": "公司名称" }, { "name": "service_hours", "default": "9:00-18:00", "description": "服务时间" } ] } ``` **使用示例**: ``` 欢迎咨询 {{company_name}},我们的服务时间是 {{service_hours}}。 ``` ### 3.4 变量解析流程 ```python # variable_resolver.py:45-75 def resolve(template, variables, extra_context): 1. 构建上下文:内置变量 + 自定义变量 + 额外上下文 2. 正则匹配:找到所有 {{variable}} 占位符 3. 替换逻辑: - 如果变量存在 → 替换为值(函数则调用) - 如果变量不存在 → 保留原占位符 + 记录警告 4. 返回解析后的字符串 ``` **正则表达式**: ```python VARIABLE_PATTERN = re.compile(r"\{\{(\w+)\}\}") ``` ### 3.5 变量优先级 变量解析的优先级(从高到低): 1. **extra_context**(运行时传入的额外上下文) 2. **自定义变量**(模板定义的 variables) 3. **实例化时的上下文**(VariableResolver 构造函数传入) 4. **内置变量**(BUILTIN_VARIABLES) ```python # variable_resolver.py:77-101 def _build_context(variables, extra_context): context = {} # 1. 加载内置变量 for key, value in BUILTIN_VARIABLES.items(): if key in self._context: context[key] = self._context[key] # 实例化时的上下文 else: context[key] = value # 内置默认值 # 2. 加载自定义变量 if variables: for var in variables: context[var["name"]] = var.get("default", "") # 3. 加载额外上下文(优先级最高) if extra_context: context.update(extra_context) return context ``` --- ## 4. 实际使用示例 ### 4.1 创建模板(通过 API) ```bash POST /admin/prompt-templates X-Tenant-Id: szmp@ash@2026 X-API-Key: your_api_key { "name": "客服模板 v1", "scene": "default", "description": "标准客服对话模板", "system_instruction": "你是 {{persona_name}},一位专业的客服助手。\n当前时间:{{current_time}}\n渠道:{{channel_type}}\n\n你需要遵循以下原则:\n- 礼貌、专业、耐心\n- 优先使用知识库内容回答\n- 无法回答时建议转人工\n\n公司信息:{{company_name}}\n服务时间:{{service_hours}}", "variables": [ { "name": "company_name", "default": "XX科技", "description": "公司名称" }, { "name": "service_hours", "default": "9:00-21:00", "description": "服务时间" } ], "is_default": true } ``` **响应**: ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "客服模板 v1", "scene": "default", "description": "标准客服对话模板", "is_default": true, "created_at": "2026-02-27T12:00:00Z", "updated_at": "2026-02-27T12:00:00Z" } ``` 此时模板已创建,但版本状态为 `draft`,尚未生效。 ### 4.2 发布模板 ```bash POST /admin/prompt-templates/{tpl_id}/publish X-Tenant-Id: szmp@ash@2026 X-API-Key: your_api_key { "version": 1 } ``` **响应**: ```json { "success": true, "message": "Version 1 published successfully" } ``` **生效时间**:立即生效(缓存已清除) ### 4.3 模板生效后的实际效果 **用户请求**: ```json POST /ai/chat X-Tenant-Id: szmp@ash@2026 X-API-Key: your_api_key { "sessionId": "kf_001_wx123_1708765432000", "currentMessage": "你好", "channelType": "wechat" } ``` **Orchestrator 内部处理**: 1. **加载模板**(Step 7): ```python # 从缓存或数据库加载已发布的模板 template_version = await template_service.get_published_template( tenant_id="szmp@ash@2026", scene="default" ) # 返回:system_instruction + variables ``` 2. **解析变量**: ```python resolver = VariableResolver( channel_type="wechat", tenant_name="深圳某项目", session_id="kf_001_wx123_1708765432000" ) system_prompt = resolver.resolve( template=template_version.system_instruction, variables=template_version.variables, extra_context={"persona_name": "AI助手"} ) ``` 3. **解析结果**: ``` 你是 AI助手,一位专业的客服助手。 当前时间:2026-02-27 20:18 渠道:wechat 你需要遵循以下原则: - 礼貌、专业、耐心 - 优先使用知识库内容回答 - 无法回答时建议转人工 公司信息:XX科技 服务时间:9:00-21:00 ``` 4. **注入行为规则**(如果有): ```python # 从数据库加载行为规则 rules = await behavior_service.get_enabled_rules(tenant_id) # 拼接到 system_prompt behavior_text = "\n".join([f"- {rule}" for rule in rules]) system_prompt += f"\n\n行为约束:\n{behavior_text}" ``` 5. **传递给 LLM**: ```python messages = [ {"role": "system", "content": system_prompt}, {"role": "user", "content": "你好"} ] response = await llm_client.generate(messages) ``` ### 4.4 更新模板 ```bash PUT /admin/prompt-templates/{tpl_id} X-Tenant-Id: szmp@ash@2026 X-API-Key: your_api_key { "system_instruction": "你是 {{persona_name}},一位专业且友好的客服助手。\n...", "variables": [ { "name": "company_name", "default": "XX科技有限公司", # 修改了默认值 "description": "公司全称" } ] } ``` **效果**: - 创建新版本(version=2,status=draft) - 旧版本(version=1)仍然是 published 状态 - **模板不会立即生效**,需要发布 version 2 ### 4.5 回滚模板 ```bash POST /admin/prompt-templates/{tpl_id}/rollback X-Tenant-Id: szmp@ash@2026 X-API-Key: your_api_key { "version": 1 } ``` **效果**: - version 2 变为 archived - version 1 重新变为 published - 缓存清除,立即生效 --- ## 5. 高级特性 ### 5.1 变量验证 ```python # variable_resolver.py:115-144 def validate_variables(template, defined_variables): """ 验证模板中的所有变量是否已定义 返回: { "valid": True/False, "missing": ["未定义的变量列表"], "used_variables": ["模板中使用的所有变量"] } """ ``` **使用场景**: - 前端编辑模板时实时验证 - 发布前检查是否有未定义的变量 ### 5.2 变量提取 ```python # variable_resolver.py:103-113 def extract_variables(template): """ 从模板中提取所有变量名 返回:["persona_name", "current_time", "company_name"] """ ``` **使用场景**: - 前端显示模板使用的变量列表 - 自动生成变量定义表单 ### 5.3 缓存策略 **TemplateCache** 实现: ```python # template_service.py:32-72 class TemplateCache: def __init__(self, ttl_seconds=300): self._cache = {} # key: (tenant_id, scene) self._ttl = 300 # 5 分钟 def get(self, tenant_id, scene): # 检查是否过期 if time.time() - cached_at < self._ttl: return version else: del self._cache[key] # 自动清理过期缓存 def invalidate(self, tenant_id, scene=None): # 发布/回滚时清除缓存 ``` **缓存失效时机**: - 发布新版本 - 回滚到旧版本 - 更新模板(创建新版本时不清除,因为新版本是 draft) - TTL 过期(5 分钟) --- ## 6. 最佳实践 ### 6.1 模板设计建议 1. **使用语义化的变量名**: ``` ✅ {{company_name}}、{{service_hours}} ❌ {{var1}}、{{x}} ``` 2. **为所有自定义变量提供默认值**: ```json { "name": "company_name", "default": "XX公司", // 必须提供 "description": "公司名称" } ``` 3. **避免在模板中硬编码业务数据**: ``` ❌ 你是小明,为 XX 公司提供服务 ✅ 你是 {{persona_name}},为 {{company_name}} 提供服务 ``` 4. **合理使用内置变量**: - `current_time`:适用于时间敏感的场景 - `channel_type`:适用于多渠道差异化话术 - `session_id`:适用于调试和追踪 ### 6.2 版本管理建议 1. **小步迭代**: - 每次修改创建新版本 - 在测试环境验证后再发布 - 保留历史版本以便回滚 2. **版本命名规范**(在 description 中): ``` v1.0 - 初始版本 v1.1 - 优化语气,增加公司信息变量 v1.2 - 修复变量引用错误 ``` 3. **灰度发布**(未来扩展): - 可以为不同租户发布不同版本 - 可以按百分比逐步切换版本 ### 6.3 性能优化建议 1. **利用缓存**: - 模板内容很少变化,缓存命中率高 - 5 分钟 TTL 平衡了实时性和性能 2. **避免频繁发布**: - 发布操作会清除缓存 - 建议批量修改后统一发布 3. **监控缓存命中率**: ```python logger.debug(f"Cache hit for template: tenant={tenant_id}, scene={scene}") ``` --- ## 7. 故障排查 ### 7.1 模板未生效 **症状**:发布后仍使用旧模板或硬编码 SYSTEM_PROMPT **排查步骤**: 1. 检查版本状态:`GET /admin/prompt-templates/{tpl_id}` - 确认目标版本的 status 是否为 `published` 2. 检查缓存:等待 5 分钟或重启服务 3. 检查日志: ``` [AC-AISVC-51] Cache hit for template: tenant=xxx, scene=default [AC-AISVC-51] Loaded published template from DB: tenant=xxx, scene=default [AC-AISVC-51] No published template found, using fallback ``` ### 7.2 变量未替换 **症状**:生成的 system_prompt 中仍有 `{{variable}}` 占位符 **排查步骤**: 1. 检查变量定义:确认变量在 `variables` 中定义 2. 检查变量名拼写:必须完全匹配(区分大小写) 3. 检查日志: ``` WARNING: Unknown variable in template: xxx ``` ### 7.3 租户隔离问题 **症状**:租户 A 看到了租户 B 的模板 **排查步骤**: 1. 检查请求头:确认 `X-Tenant-Id` 正确 2. 检查数据库: ```sql SELECT * FROM prompt_templates WHERE tenant_id = 'xxx'; ``` 3. 检查代码:所有查询必须带 `tenant_id` 过滤 --- ## 8. 总结 ### 8.1 核心流程 ``` 创建模板 → 编辑内容 → 发布版本 → 缓存加载 → 变量解析 → 传递给 LLM ↓ ↓ ↓ ↓ ↓ ↓ draft draft published 内存缓存 占位符替换 生成回复 ``` ### 8.2 关键特性 - ✅ **版本管理**:支持多版本、发布/回滚、历史追溯 - ✅ **热更新**:发布后立即生效,无需重启 - ✅ **租户隔离**:多租户数据完全隔离 - ✅ **缓存优化**:5 分钟 TTL,减少数据库查询 - ✅ **变量系统**:内置变量 + 自定义变量,支持动态值 - ✅ **降级策略**:模板不可用时自动回退到硬编码 ### 8.3 扩展方向 - 🔄 **场景路由**:根据 intent 或 channel 自动选择不同 scene - 🔄 **A/B 测试**:同一场景支持多个模板并行测试 - 🔄 **模板继承**:子模板继承父模板并覆盖部分内容 - 🔄 **变量类型**:支持 string/number/boolean/array 等类型 - 🔄 **条件渲染**:支持 `{{#if}}...{{/if}}` 等逻辑控制 --- **文档生成时间**:2026-02-27 20:18 **相关代码版本**:v0.6.0 **维护状态**:✅ 活跃维护