ai-robot-core/docs/prompt-template-analysis.md

16 KiB
Raw Blame History

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 7PromptBuilder执行

  2. 加载逻辑

    # 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已归档

发布流程

# template_service.py:248-287
async def publish_version(tenant_id, template_id, version):
    1. 查询模板是否存在租户隔离
    2. 将当前 published 版本改为 archived
    3. 将目标版本改为 published
    4. 清除缓存并预热新版本
    5. 记录日志

回滚流程

# 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 租户隔离机制

所有操作都强制进行租户隔离:

# 查询时必须带 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

动态变量示例

# 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 字段中定义

{
  "variables": [
    {
      "name": "company_name",
      "default": "XX科技有限公司",
      "description": "公司名称"
    },
    {
      "name": "service_hours",
      "default": "9:00-18:00",
      "description": "服务时间"
    }
  ]
}

使用示例

欢迎咨询 {{company_name}},我们的服务时间是 {{service_hours}}。

3.4 变量解析流程

# variable_resolver.py:45-75
def resolve(template, variables, extra_context):
    1. 构建上下文内置变量 + 自定义变量 + 额外上下文
    2. 正则匹配找到所有 {{variable}} 占位符
    3. 替换逻辑
       - 如果变量存在  替换为值函数则调用
       - 如果变量不存在  保留原占位符 + 记录警告
    4. 返回解析后的字符串

正则表达式

VARIABLE_PATTERN = re.compile(r"\{\{(\w+)\}\}")

3.5 变量优先级

变量解析的优先级(从高到低):

  1. extra_context(运行时传入的额外上下文)
  2. 自定义变量(模板定义的 variables
  3. 实例化时的上下文VariableResolver 构造函数传入)
  4. 内置变量BUILTIN_VARIABLES
# 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

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
}

响应

{
  "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 发布模板

POST /admin/prompt-templates/{tpl_id}/publish
X-Tenant-Id: szmp@ash@2026
X-API-Key: your_api_key

{
  "version": 1
}

响应

{
  "success": true,
  "message": "Version 1 published successfully"
}

生效时间:立即生效(缓存已清除)

4.3 模板生效后的实际效果

用户请求

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

    # 从缓存或数据库加载已发布的模板
    template_version = await template_service.get_published_template(
        tenant_id="szmp@ash@2026",
        scene="default"
    )
    # 返回system_instruction + variables
    
  2. 解析变量

    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. 注入行为规则(如果有):

    # 从数据库加载行为规则
    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

    messages = [
        {"role": "system", "content": system_prompt},
        {"role": "user", "content": "你好"}
    ]
    
    response = await llm_client.generate(messages)
    

4.4 更新模板

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=2status=draft
  • 旧版本version=1仍然是 published 状态
  • 模板不会立即生效,需要发布 version 2

4.5 回滚模板

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 变量验证

# variable_resolver.py:115-144
def validate_variables(template, defined_variables):
    """
    验证模板中的所有变量是否已定义

    返回:
    {
        "valid": True/False,
        "missing": ["未定义的变量列表"],
        "used_variables": ["模板中使用的所有变量"]
    }
    """

使用场景

  • 前端编辑模板时实时验证
  • 发布前检查是否有未定义的变量

5.2 变量提取

# variable_resolver.py:103-113
def extract_variables(template):
    """
    从模板中提取所有变量名

    返回:["persona_name", "current_time", "company_name"]
    """

使用场景

  • 前端显示模板使用的变量列表
  • 自动生成变量定义表单

5.3 缓存策略

TemplateCache 实现:

# 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. 为所有自定义变量提供默认值

    {
      "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. 监控缓存命中率

    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. 检查数据库:
    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 维护状态 活跃维护