# 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
**维护状态**：✅ 活跃维护
