23 KiB
意图驱动话术流程 - 设计文档
1. 架构概览
1.1 系统架构图
┌─────────────────────────────────────────────────────────────┐
│ 前端配置界面 │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ 模式选择器 │ │ 意图配置表单 │ │ 约束管理器 │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────────┘
│ HTTP API
▼
┌─────────────────────────────────────────────────────────────┐
│ 后端 API 层 │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ ScriptFlowService (CRUD) │ │
│ └──────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ FlowEngine (执行引擎) │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ start() / advance() │ │
│ │ ↓ │ │
│ │ _generate_step_content() ← 核心扩展点 │ │
│ │ ├─ fixed: 返回 content │ │
│ │ ├─ flexible: 调用 ScriptGenerator │ │
│ │ └─ template: 调用 TemplateEngine │ │
│ └──────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
│
┌───────────────┼───────────────┐
▼ ▼ ▼
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ScriptGenerator│ │TemplateEngine│ │ Orchestrator │
│ (新增) │ │ (新增) │ │ (LLM调用) │
└──────────────┘ └──────────────┘ └──────────────┘
1.2 数据流图
用户配置流程
│
├─ 选择 script_mode
│ ├─ fixed: 配置 content
│ ├─ flexible: 配置 intent + constraints
│ └─ template: 配置 content (模板)
│
▼
保存到数据库 (ScriptFlow.steps JSON)
│
▼
执行时加载流程
│
├─ FlowEngine.start() / advance()
│ │
│ ├─ 获取当前步骤配置
│ │
│ ├─ 调用 _generate_step_content()
│ │ │
│ │ ├─ fixed: 直接返回 content
│ │ │
│ │ ├─ flexible:
│ │ │ ├─ 构建 Prompt (intent + constraints + history)
│ │ │ ├─ 调用 LLM 生成话术
│ │ │ └─ 失败时返回 fallback (content)
│ │ │
│ │ └─ template:
│ │ ├─ 解析模板变量
│ │ ├─ 调用 LLM 生成变量值
│ │ └─ 替换模板占位符
│ │
│ └─ 返回生成的话术
│
▼
返回给用户
2. 核心模块设计
2.1 后端:话术生成引擎
2.1.1 FlowEngine 扩展
文件位置: ai-service/app/services/flow/engine.py
新增方法:
async def _generate_step_content(
self,
step: dict,
context: dict,
history: list[dict]
) -> str:
"""
[AC-IDS-03] 根据步骤配置生成话术内容
Args:
step: 步骤配置 (包含 script_mode, intent, constraints 等)
context: 会话上下文 (从 FlowInstance.context 获取)
history: 对话历史 (最近 N 轮)
Returns:
生成的话术文本
"""
script_mode = step.get("script_mode", "fixed")
if script_mode == "fixed":
return step.get("content", "")
elif script_mode == "flexible":
return await self._generate_flexible_script(step, context, history)
elif script_mode == "template":
return await self._generate_template_script(step, context, history)
else:
logger.warning(f"Unknown script_mode: {script_mode}, fallback to fixed")
return step.get("content", "")
修改方法:
async def start(
self,
tenant_id: str,
session_id: str,
flow_id: uuid.UUID,
) -> tuple[FlowInstance | None, str | None]:
"""
[AC-IDS-05] 修改:启动流程时生成首步话术
"""
# ... 现有逻辑 ...
first_step = flow.steps[0]
# 修改:调用话术生成引擎
history = await self._get_conversation_history(tenant_id, session_id, limit=3)
first_content = await self._generate_step_content(
first_step,
instance.context,
history
)
return instance, first_content
2.1.2 ScriptGenerator (新增模块)
文件位置: ai-service/app/services/flow/script_generator.py
职责: 灵活模式的话术生成逻辑
class ScriptGenerator:
"""
[AC-IDS-04] 灵活模式话术生成器
"""
def __init__(self, orchestrator):
self._orchestrator = orchestrator
async def generate(
self,
intent: str,
intent_description: str | None,
constraints: list[str],
context: dict,
history: list[dict],
fallback: str
) -> str:
"""
生成灵活话术
Args:
intent: 步骤意图
intent_description: 意图详细说明
constraints: 话术约束条件
context: 会话上下文
history: 对话历史
fallback: 失败时的 fallback 话术
Returns:
生成的话术文本
"""
try:
prompt = self._build_prompt(
intent, intent_description, constraints, context, history
)
# 调用 LLM,设置 2 秒超时
response = await asyncio.wait_for(
self._orchestrator.generate(prompt),
timeout=2.0
)
return response.strip()
except asyncio.TimeoutError:
logger.warning(f"[AC-IDS-05] Script generation timeout, use fallback")
return fallback
except Exception as e:
logger.error(f"[AC-IDS-05] Script generation failed: {e}, use fallback")
return fallback
def _build_prompt(
self,
intent: str,
intent_description: str | None,
constraints: list[str],
context: dict,
history: list[dict]
) -> str:
"""
[AC-IDS-04] 构建 LLM Prompt
"""
prompt_parts = [
"你是一个客服对话系统,当前需要执行以下步骤:",
"",
f"【步骤目标】{intent}"
]
if intent_description:
prompt_parts.append(f"【详细说明】{intent_description}")
if constraints:
prompt_parts.append("【约束条件】")
for c in constraints:
prompt_parts.append(f"- {c}")
if history:
prompt_parts.append("")
prompt_parts.append("【对话历史】")
for msg in history[-3:]: # 最近 3 轮
role = "用户" if msg["role"] == "user" else "客服"
prompt_parts.append(f"{role}: {msg['content']}")
if context.get("inputs"):
prompt_parts.append("")
prompt_parts.append("【已收集信息】")
for inp in context["inputs"]:
prompt_parts.append(f"- {inp}")
prompt_parts.extend([
"",
"请生成一句符合目标和约束的话术(不超过50字)。",
"只返回话术内容,不要解释。"
])
return "\n".join(prompt_parts)
2.1.3 TemplateEngine (新增模块)
文件位置: ai-service/app/services/flow/template_engine.py
职责: 模板模式的变量填充逻辑
import re
class TemplateEngine:
"""
[AC-IDS-06] 模板话术引擎
"""
VARIABLE_PATTERN = re.compile(r'\{(\w+)\}')
def __init__(self, orchestrator):
self._orchestrator = orchestrator
async def fill_template(
self,
template: str,
context: dict,
history: list[dict]
) -> str:
"""
填充模板变量
Args:
template: 话术模板(包含 {变量名} 占位符)
context: 会话上下文
history: 对话历史
Returns:
填充后的话术
"""
# 提取模板中的变量
variables = self.VARIABLE_PATTERN.findall(template)
if not variables:
return template
# 为每个变量生成值
variable_values = {}
for var in variables:
value = await self._generate_variable_value(var, context, history)
variable_values[var] = value
# 替换模板中的占位符
result = template
for var, value in variable_values.items():
result = result.replace(f"{{{var}}}", value)
return result
async def _generate_variable_value(
self,
variable_name: str,
context: dict,
history: list[dict]
) -> str:
"""
为单个变量生成值
"""
# 先尝试从上下文中获取
if variable_name in context:
return str(context[variable_name])
# 否则调用 LLM 生成
prompt = f"""
根据对话历史,为变量 "{variable_name}" 生成合适的值。
对话历史:
{self._format_history(history[-3:])}
只返回变量值,不要解释。
"""
try:
response = await asyncio.wait_for(
self._orchestrator.generate(prompt),
timeout=1.0
)
return response.strip()
except:
return f"[{variable_name}]" # fallback
2.2 前端:配置界面设计
2.2.1 类型定义扩展
文件位置: ai-service-admin/src/types/script-flow.ts
export type ScriptMode = 'fixed' | 'flexible' | 'template'
export interface FlowStep {
step_id: string
step_no: number
// 原有字段
content: string
wait_input: boolean
timeout_seconds?: number
timeout_action?: 'repeat' | 'skip' | 'transfer'
next_conditions?: NextCondition[]
// 新增字段
script_mode?: ScriptMode
intent?: string
intent_description?: string
script_constraints?: string[]
expected_variables?: string[]
}
export const SCRIPT_MODE_OPTIONS = [
{ value: 'fixed', label: '固定话术', description: '话术内容固定不变' },
{ value: 'flexible', label: '灵活话术', description: 'AI根据意图和上下文生成' },
{ value: 'template', label: '模板话术', description: 'AI填充模板中的变量' }
]
2.2.2 配置表单组件
文件位置: ai-service-admin/src/views/admin/script-flow/index.vue
UI 结构:
<template>
<el-form-item label="话术模式">
<el-radio-group v-model="currentStep.script_mode">
<el-radio-button
v-for="option in SCRIPT_MODE_OPTIONS"
:key="option.value"
:label="option.value"
>
{{ option.label }}
<el-tooltip :content="option.description">
<el-icon><QuestionFilled /></el-icon>
</el-tooltip>
</el-radio-button>
</el-radio-group>
</el-form-item>
<!-- 固定模式 -->
<template v-if="currentStep.script_mode === 'fixed'">
<el-form-item label="话术内容" required>
<el-input
v-model="currentStep.content"
type="textarea"
:rows="3"
placeholder="输入固定话术内容"
/>
</el-form-item>
</template>
<!-- 灵活模式 -->
<template v-if="currentStep.script_mode === 'flexible'">
<el-form-item label="步骤意图" required>
<el-input
v-model="currentStep.intent"
placeholder="例如:获取用户姓名"
/>
</el-form-item>
<el-form-item label="意图说明">
<el-input
v-model="currentStep.intent_description"
type="textarea"
:rows="2"
placeholder="详细描述这一步的目的和期望效果"
/>
</el-form-item>
<el-form-item label="话术约束">
<ConstraintManager v-model="currentStep.script_constraints" />
</el-form-item>
<el-form-item label="Fallback话术" required>
<el-input
v-model="currentStep.content"
type="textarea"
:rows="2"
placeholder="AI生成失败时使用的备用话术"
/>
</el-form-item>
</template>
<!-- 模板模式 -->
<template v-if="currentStep.script_mode === 'template'">
<el-form-item label="话术模板" required>
<el-input
v-model="currentStep.content"
type="textarea"
:rows="3"
placeholder="使用 {变量名} 标记可变部分,例如:您好{user_name},请问您{inquiry_style}?"
/>
<div class="template-hint">
提示:使用 {变量名} 标记需要AI填充的部分
</div>
</el-form-item>
</template>
</template>
2.2.3 约束管理组件
文件位置: ai-service-admin/src/views/admin/script-flow/components/ConstraintManager.vue
<template>
<div class="constraint-manager">
<div class="constraint-tags">
<el-tag
v-for="(constraint, index) in modelValue"
:key="index"
closable
@close="removeConstraint(index)"
>
{{ constraint }}
</el-tag>
</div>
<el-input
v-model="newConstraint"
placeholder="输入约束条件后按回车添加"
@keyup.enter="addConstraint"
class="constraint-input"
>
<template #append>
<el-button @click="addConstraint">添加</el-button>
</template>
</el-input>
<div class="constraint-presets">
<span class="preset-label">常用约束:</span>
<el-button
v-for="preset in PRESET_CONSTRAINTS"
:key="preset"
size="small"
@click="addPreset(preset)"
>
{{ preset }}
</el-button>
</div>
</div>
</template>
<script setup lang="ts">
const PRESET_CONSTRAINTS = [
'必须礼貌',
'语气自然',
'简洁明了',
'不要生硬',
'不要重复'
]
const addConstraint = () => {
if (newConstraint.value.trim()) {
emit('update:modelValue', [...modelValue.value, newConstraint.value.trim()])
newConstraint.value = ''
}
}
</script>
3. 数据模型设计
3.1 数据库 Schema
无需修改表结构,因为 script_flows.steps 已经是 JSON 类型。
现有结构:
CREATE TABLE script_flows (
id UUID PRIMARY KEY,
tenant_id VARCHAR NOT NULL,
name VARCHAR NOT NULL,
description TEXT,
steps JSONB NOT NULL, -- 直接扩展此字段
is_enabled BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
扩展后的 steps JSON 示例:
[
{
"step_no": 1,
"script_mode": "flexible",
"intent": "获取用户姓名",
"intent_description": "礼貌询问用户姓名",
"script_constraints": ["必须礼貌", "语气自然"],
"content": "请问怎么称呼您?",
"wait_input": true,
"timeout_seconds": 60
}
]
3.2 向后兼容策略
读取时:
def _normalize_step(step: dict) -> dict:
"""确保步骤配置包含所有必需字段"""
return {
"script_mode": step.get("script_mode", "fixed"),
"intent": step.get("intent"),
"intent_description": step.get("intent_description"),
"script_constraints": step.get("script_constraints", []),
"expected_variables": step.get("expected_variables", []),
**step # 保留其他字段
}
写入时:
- 前端默认
script_mode = 'fixed' - 后端不做强制校验,允许字段缺失
4. 技术决策
4.1 为什么选择 JSON 扩展而不是新表?
决策: 在现有的 steps JSON 字段中扩展,而不是创建新表
理由:
- 简化数据模型: 步骤配置是流程的一部分,不需要独立管理
- 避免数据迁移: 无需修改表结构,现有数据自动兼容
- 灵活性: JSON 字段易于扩展,未来可以继续添加新字段
- 性能: 步骤数量通常不多(<20),JSON 查询性能足够
权衡: 无法对意图字段建立索引,但实际场景中不需要按意图查询流程
4.2 为什么设置 2 秒超时?
决策: LLM 调用超时设置为 2 秒
理由:
- 用户体验: 对话系统需要快速响应,2 秒是可接受的上限
- Fallback 保障: 超时后立即返回 fallback 话术,不影响流程执行
- 成本控制: 避免长时间等待消耗资源
权衡: 可能导致部分复杂话术生成失败,但有 fallback 保障
4.3 为什么对话历史只取最近 3 轮?
决策: 传递给 LLM 的对话历史限制为最近 3 轮
理由:
- Token 成本: 减少 Prompt 长度,降低成本
- 相关性: 最近 3 轮对话最相关,更早的对话影响较小
- 性能: 减少数据库查询和网络传输
权衡: 可能丢失更早的上下文信息,但实际影响有限
4.4 为什么不缓存生成的话术?
决策: 不对生成的话术进行缓存
理由:
- 灵活性优先: 每次生成都考虑最新的上下文,更符合"灵活话术"的定位
- 缓存复杂度: 需要考虑缓存失效策略(上下文变化、配置变化)
- 实际收益有限: 同一步骤在同一会话中通常只执行一次
未来优化: 如果性能成为瓶颈,可以考虑基于上下文哈希的缓存
5. 错误处理与降级策略
5.1 话术生成失败
场景: LLM 调用超时或返回错误
处理:
- 记录错误日志(包含 tenant_id, session_id, flow_id, step_no)
- 返回
step.content作为 fallback - 在 ChatMessage 中标记
is_error=False(因为有 fallback,不算错误)
5.2 配置错误
场景: flexible 模式但 intent 为空
处理:
- 前端校验:提交时检查必填字段
- 后端容错:如果 intent 为空,降级为 fixed 模式
5.3 模板解析错误
场景: 模板语法错误(如 {unclosed)
处理:
- 捕获正则匹配异常
- 返回原始模板(不做替换)
- 记录警告日志
6. 性能考虑
6.1 预期性能指标
| 指标 | 目标值 | 说明 |
|---|---|---|
| 话术生成延迟 (P95) | < 2s | LLM 调用时间 |
| API 响应时间增加 | < 10% | 相比固定模式 |
| 数据库查询增加 | +1 次 | 获取对话历史 |
6.2 优化策略
- 并行查询: 获取对话历史和流程配置可以并行
- 限制历史长度: 只查询最近 3 轮对话
- 超时控制: 严格的 2 秒超时,避免长时间等待
7. 测试策略
7.1 单元测试
测试文件: ai-service/tests/services/flow/test_script_generator.py
测试用例:
- 固定模式:直接返回 content
- 灵活模式:正常生成、超时 fallback、异常 fallback
- 模板模式:变量替换、变量缺失、模板语法错误
7.2 集成测试
测试文件: ai-service/tests/api/test_script_flow_intent_driven.py
测试场景:
- 创建灵活模式流程
- 启动流程,验证首步话术生成
- 推进流程,验证后续步骤话术生成
- 验证对话历史正确传递
7.3 端到端测试
测试场景:
- 前端配置灵活模式流程
- 保存并启用流程
- 通过 Provider API 触发流程
- 验证生成的话术符合意图和约束
8. 部署与发布
8.1 发布顺序
-
Phase 1: 后端数据模型和 API 扩展
- 部署后端代码
- 验证 API 向后兼容性
-
Phase 2: 后端话术生成引擎
- 部署话术生成逻辑
- 验证 fallback 机制
-
Phase 3: 前端配置界面
- 部署前端代码
- 验证配置保存和加载
-
Phase 4: 灰度发布
- 选择部分租户启用灵活模式
- 监控性能和错误率
- 全量发布
8.2 回滚策略
如果出现问题:
- 前端回滚:恢复旧版本,用户无法配置灵活模式
- 后端回滚:恢复旧版本,灵活模式降级为固定模式
- 数据无需回滚:JSON 字段扩展,旧版本可以忽略新字段
9. 监控与告警
9.1 关键指标
| 指标 | 说明 | 告警阈值 |
|---|---|---|
| script_generation_latency | 话术生成延迟 | P95 > 2.5s |
| script_generation_timeout_rate | 超时率 | > 5% |
| script_generation_error_rate | 错误率 | > 1% |
| fallback_usage_rate | Fallback 使用率 | > 10% |
9.2 日志记录
关键日志:
logger.info(
f"[AC-IDS-03] Generated script: tenant={tenant_id}, "
f"session={session_id}, flow={flow_id}, step={step_no}, "
f"mode={script_mode}, latency={latency_ms}ms"
)
logger.warning(
f"[AC-IDS-05] Script generation timeout, use fallback: "
f"tenant={tenant_id}, session={session_id}, step={step_no}"
)
10. 未来扩展
10.1 短期优化(v1.1)
- 话术生成缓存(基于上下文哈希)
- 更丰富的约束条件预设
- 话术效果评估(用户满意度)
10.2 长期规划(v2.0)
- 多轮对话规划(提前生成后续步骤话术)
- 话术 A/B 测试
- 基于历史数据的话术优化建议