Playbook 高级特性完整指南
February 14, 2026 · View on GitHub
本文档介绍 Active-Call Playbook 系统中的高级特性,包括环境变量支持、SIP Headers 提取、变量管理、HTTP 调用等。
目录
环境变量支持 (Universal)
✨ 新特性:所有配置字段支持 ${VAR_NAME} 语法
从 v0.3.37+ 开始,所有 Playbook 配置字段都支持环境变量模板语法。
语法
# 字符串字段
provider: "${MY_PROVIDER}"
api_key: "${OPENAI_API_KEY}"
# 数值字段(不需要引号)
speed: ${TTS_SPEED}
temperature: ${LLM_TEMPERATURE}
max_tokens: ${LLM_MAX_TOKENS}
# 嵌套字段
base_url: "${OPENAI_BASE_URL}"
language: "${ASR_LANGUAGE}"
示例
---
asr:
provider: "${ASR_PROVIDER}" # sensevoice, tencent, aliyun
language: "${ASR_LANGUAGE}" # zh, en, auto
tts:
provider: "${TTS_PROVIDER}" # supertonic, cosyvoice
speaker: "${TTS_SPEAKER}" # F1, M1, M2, F2
speed: ${TTS_SPEED} # 0.8, 1.0, 1.2
llm:
provider: "${LLM_PROVIDER}" # openai, azure, dashscope
model: "${LLM_MODEL}" # gpt-4o, gpt-4o-mini
apiKey: "${LLM_API_KEY}"
baseUrl: "${LLM_BASE_URL}"
temperature: ${LLM_TEMPERATURE} # 0.0 - 2.0
max_tokens: ${LLM_MAX_TOKENS} # integer
---
优势
- 安全性:API keys 不会被提交到代码仓库
- 灵活性:同一个 playbook,不同环境不同配置
- 动态性:可以在运行时切换模型、语言等参数
- 通用性:支持所有字段类型(字符串、数值、嵌套对象)
回退行为
- 如果环境变量未定义,
${VAR_NAME}会被保留(原样输出) - YAML 解析器可能会因为无效值而失败
- 建议:始终设置必需的环境变量
完整示例
参见:Environment Variables Example
⚠️ 与运行时变量 {{var}} 的区别
重要:${VAR} 和 {{var}} 是两个不同的能力,不会冲突!
| 语法 | 用途 | 时机 | 作用域 | 来源 |
|---|---|---|---|---|
${VAR} | 环境变量 | Playbook 加载时(静态) | YAML 配置 | 系统环境变量 |
{{var}} | 运行时变量 | 对话运行时(动态) | Prompt 文本 | SIP Headers、set_var |
示例:
---
# 配置中使用 ${VAR} - 从环境变量读取
llm:
apiKey: "${OPENAI_API_KEY}" # ← 加载时替换
model: "${LLM_MODEL}"
sip:
extract_headers:
- "X-Customer-Name"
---
# Prompt 中使用 {{var}} - 运行时替换
# Scene: main
你好,{{ sip["X-Customer-Name"] }}! # ← 每次通话不同
详细对比:参见 Template Syntax Comparison
SIP Headers 提取与使用
1. 配置提取规则
在 Playbook 的 YAML 配置部分指定要提取的 SIP Headers:
---
sip:
extract_headers:
- "X-CID" # 客户ID
- "X-Session-Type" # 会话类型
- "X-Agent-ID" # 坐席ID
llm:
provider: "aliyun"
model: "qwen-turbo"
---
2. 在 Playbook 中使用
提取的 Headers 会自动注入到 Playbook 的变量上下文中。由于 Header 名称通常包含连字符(如 X-Customer-ID),而 Jinja2 会将连字符解析为减法运算符,因此需要使用 字典访问语法:
你好!您的客户编号是 {{ sip["X-CID"] }}。
本次会话类型:{{ sip["X-Session-Type"] }}。
关键说明:
- ✅ 推荐:
{{ sip["X-Header-Name"] }}- 使用sip字典访问,支持包含连字符的变量名 - ❌ 错误:
{{ X-Header-Name }}- 会被解析为X 减 Header 减 Name,导致错误 - 📋 sip 字典范围:只包含 Headers(比如以
X-或x-开头的变量) - ✅ 普通变量:对于不含连字符的变量(如
customer_id),可以直接使用{{ customer_id }}
3. LLM 访问方式
LLM 可以通过系统消息获取这些变量(自动注入到上下文):
用户: 我的编号是多少?
LLM: 根据系统记录,您的客户编号是 {{ sip["X-CID"] }}。
4. 使用 set_var 更新 SIP Headers
在对话过程中,LLM 可以使用 <set_var> 动态设置或更新单个 SIP Header:
LLM: 您的工单已创建 <set_var key="X-Ticket-ID" value="TKT-12345" />
LLM: 通话评分为优秀 <set_var key="X-Call-Rating" value="excellent" />
这些设置的 Headers 会:
- 立即写入
ActiveCall.extras - 在 BYE 请求的
render_sip_headers中可用 - 可被后续的模板引用
5. BYE Headers 渲染
挂断时,可以配置 hangup_headers 模板,访问所有变量(包括 SIP headers 和普通变量):
---
sip:
extract_headers:
- "X-Customer-ID"
hangup_headers:
X-Call-Result: "{{ call_result }}" # 普通变量
X-Customer: "{{ sip["X-Customer-ID"] }}" # SIP Header
X-Agent: "{{ agent_name }}" # 普通变量
---
在对话中设置变量:
<set_var key="call_result" value="successful" />
<set_var key="agent_name" value="Alice" />
6. 完整示例
---
sip:
extract_headers:
- "X-Customer-ID"
- "X-Customer-Name"
- "X-Session-Type"
llm:
provider: "aliyun"
model: "qwen-turbo"
greeting: "{{ sip["X-Customer-Name"] }}您好!"
---
# Scene: main
您的客户编号是 {{ sip["X-Customer-ID"] }},会话类型为 {{ sip["X-Session-Type"] }}。
请问有什么可以帮您?
内置会话变量
系统会在通话建立时自动注入一组内置变量到 ActiveCall.extras 中,无需手动设置即可在 Prompt 和模板中使用。
内置变量列表
| 变量名 | 说明 | 示例值 |
|---|---|---|
session_id | 通话会话唯一标识 | "abc123-def456" |
call_type | 通话类型 | "sip" / "websocket" / "webrtc" / "b2bua" |
caller | 主叫方 SIP URI | "sip:13800138000@domain.com" |
callee | 被叫方 SIP URI | "sip:10086@domain.com" |
start_time | 通话开始时间(RFC 3339 格式) | "2025-01-15T10:30:00+08:00" |
在 Prompt 中使用
# Scene: main
当前会话ID:{{ session_id }}
通话类型:{{ call_type }}
开始时间:{{ start_time }}
主叫号码:{{ caller }}
被叫号码:{{ callee }}
注意事项
- 内置变量使用
entry().or_insert_with()模式注入,不会覆盖外部传入的同名变量 caller和callee仅在 SIP 通话中可用- 所有内置变量都可以在场景切换时通过动态渲染在 Prompt 中使用
动态场景 Prompt 渲染
问题背景
在多场景 Playbook 中,Prompt 模板在 Playbook 加载时就会被渲染({{var}} 被替换)。这意味着通过 <set_var> 在对话过程中设置的变量无法在其他场景的 Prompt 中被引用。
例如:
# Scene: collect_info
请收集用户的意向信息。
# Scene: confirm
用户的意向是:{{ intent }} ← 加载时 intent 尚不存在,渲染为空
解决方案
从 v0.3.38 开始,系统支持动态场景 Prompt 渲染:
- 解析 Playbook 时,原始模板保存在
Scene.raw_prompt中 - 每次切换场景(
<goto>)时,使用当前extras中的变量重新渲染 Prompt - 渲染失败时自动回退到已有的 Prompt,确保不影响正常对话
使用示例
---
sip:
extract_headers:
- "X-Jobid"
llm:
provider: "openai"
model: "gpt-4o"
---
# Scene: collect
你是一个意向收集助手。
请收集用户的购买意向。收集完毕后输出 <set_var key="intent" value="用户意向" /> 然后输出 <goto scene="confirm" />
# Scene: confirm
你是一个确认助手。
工单编号:{{ sip["X-Jobid"] }}
会话ID:{{ session_id }}
用户意向:{{ intent }}
请向用户确认以上信息是否正确。
执行流程:
- 通话开始,进入
collect场景 - LLM 收集信息后执行
<set_var key="intent" value="买零食" /> - LLM 输出
<goto scene="confirm" /> - 系统切换场景时,读取
confirm场景的raw_prompt,使用当前 extras(含intent="买零食"、session_id等)重新渲染 confirm场景的 Prompt 变为:"用户意向:买零食","会话ID:abc123"
支持的变量类型
动态渲染时可以使用以下所有变量:
| 类型 | 示例 | 说明 |
|---|---|---|
<set_var> 变量 | {{ intent }} | 对话中设置的变量 |
| SIP Headers | {{ sip["X-Jobid"] }} | 通过 sip 字典访问 |
| 内置变量 | {{ session_id }} | 系统自动注入 |
容错机制
- 引用不存在的变量时,渲染为空字符串(MiniJinja 默认行为)
- 模板语法错误时,自动回退到原有 Prompt,不会中断通话
- 日志记录渲染失败的详细信息,便于排查
变量管理 (<set_var>)
<set_var> 标签允许 LLM 在对话过程中动态设置或修改变量。
语法
<set_var key="变量名" value="变量值" />
使用场景
1. 记录用户信息
LLM: 请问您贵姓?
用户: 我姓张
LLM: 张先生您好 <set_var key="user_name" value="张先生" />,请问有什么可以帮您?
2. 保存业务状态
LLM: 已为您创建工单,工单号 12345 <set_var key="ticket_id" value="12345" />
3. 设置 BYE Headers(用于 SIP)
LLM: 通话结束,感谢您的来电 <set_var key="_hangup_headers" value='{"X-Hangup-Reason":"normal","X-Duration":"180"}' />
特性说明
- 实时更新: 变量立即写入
ActiveCall.extras - 持久化: 变量在整个通话会话中保持
- 可传递: 可用于后续的 BYE 请求 Headers
- 类型: 所有值都存储为字符串(JSON Value::String)
注意事项
⚠️ 变量值中的特殊字符需要转义:
<!-- 正确 -->
<set_var key="note" value='{"status":"ok"}' />
<!-- 错误 - 双引号冲突 -->
<set_var key="note" value="{"status":"ok"}" />
HTTP 外部调用 (<http>)
<http> 标签允许 LLM 在对话中调用外部 HTTP API。
语法
<http url="API地址" method="方法" body="请求体" />
- url: 必需,API 完整 URL
- method: 可选,默认 GET(支持 GET/POST/PUT)
- body: 可选,请求体内容
使用场景
1. 查询外部数据
LLM: 我来帮您查询天气 <http url="https://api.weather.com/v1/current?city=beijing" />
响应处理:HTTP 响应会自动添加到对话历史中:
系统: HTTP GET https://api.weather.com/v1/current?city=beijing returned (200): {"temp":15,"condition":"sunny"}
LLM: 北京当前温度 15 度,天气晴朗。
2. 提交数据
LLM: 正在为您创建工单 <http url="https://api.crm.com/tickets" method="POST" body='{"subject":"咨询","customer":"123"}' />
3. 多步骤交互
用户: 帮我订一张去上海的机票
LLM: 好的,我先查询一下航班信息 <http url="https://api.flight.com/search?dest=shanghai" />
[系统返回航班列表]
LLM: 有 MU5180 和 CA1234 两个航班,您选择哪个?
用户: MU5180
LLM: 正在为您预订 <http url="https://api.flight.com/book" method="POST" body='{"flight":"MU5180","passenger":"user123"}' />
特性说明
- 同步执行: HTTP 请求会阻塞流式输出,等待响应
- 自动注入: 响应内容自动添加到对话历史
- 错误处理: 失败时错误信息也会添加到历史
- 安全性: 建议只调用可信的内部 API
注意事项
⚠️ 性能考虑:
- HTTP 调用会增加响应延迟
- 建议设置合理的超时时间
- 避免在流式响应中频繁调用
SIP BYE Headers 定制
在 SIP 通话结束时,可以附加自定义 Headers 到 BYE 请求。
1. 配置模板
在 Playbook 配置中定义 BYE Headers 模板:
---
sip:
hangup_headers:
X-Hangup-Reason: "{{ hangup_reason }}"
X-Call-Duration: "{{ call_duration }}"
X-User-Rating: "{{ user_rating }}"
X-Ticket-ID: "{{ ticket_id }}"
---
2. LLM 设置变量
在对话中通过 <set_var> 设置这些变量:
LLM: 问题已解决,请对本次服务评分(1-5)
用户: 5分
LLM: 感谢您的好评 <set_var key="user_rating" value="5" /> <set_var key="hangup_reason" value="satisfied" />,再见!<hangup/>
3. 自动渲染
系统会在发送 BYE 时:
- 读取
ActiveCall.extras中的所有变量 - 使用 Jinja2 渲染
hangup_headers模板 - 将渲染结果作为 SIP Headers 发送
直接设置 Headers
也可以直接设置 _hangup_headers 变量:
<set_var key="_hangup_headers" value='{"X-Custom":"value","X-Status":"completed"}' />
注意:直接设置会覆盖模板渲染结果
完整流程示例
场景:客服呼叫中心
---
asr:
provider: "aliyun"
llm:
provider: "openai"
model: "gpt-4o"
apiKey: "${OPENAI_API_KEY}"
prompt: |
你是一个客服机器人。可以使用以下工具:
- 查询工单: <http url="https://api.crm.com/tickets/{ticket_id}" />
- 创建工单: <http url="https://api.crm.com/tickets" method="POST" body='...' />
- 记录信息: <set_var key="变量名" value="值" />
当用户要求转人工时,输出: <refer to="sip:agent@domain.com"/>
对话结束时,务必记录通话原因。
tts:
provider: "aliyun"
sip:
extract_headers:
- "X-Customer-ID"
- "X-Call-Source"
- "X-Priority"
hangup_headers:
X-Hangup-Reason: "{{ hangup_reason }}"
X-Resolved: "{{ is_resolved }}"
X-Ticket-ID: "{{ ticket_id }}"
X-Sentiment: "{{ user_sentiment }}"
---
你是客服助手小智。请注意:
1. 首先使用客户ID查询历史工单: <http url="https://api.crm.com/customers/{{ sip[\"X-Customer-ID\"] }}/tickets" />
2. 处理问题时创建工单并记录ID
3. 对话中识别用户情绪(positive/neutral/negative)
4. 结束时记录必要信息
礼貌、专业、简洁回复。
实际对话流程
[SIP INVITE with X-Customer-ID: CUST001]
机器人: 您好,我是小智客服。<http url="https://api.crm.com/customers/CUST001/tickets" />
[API返回: 该客户有1个未处理工单 #TK123]
机器人: 看到您有一个进行中的工单 #TK123,是关于这个问题吗?
客户: 不是,我有新问题,网络连不上
机器人: 好的,我帮您创建新工单 <http url="https://api.crm.com/tickets" method="POST" body='{"customer":"CUST001","subject":"网络连接故障","priority":"high"}' />
<set_var key="user_sentiment" value="neutral" />
[API返回: 创建成功,工单号 TK456]
机器人: 工单 TK456 已创建 <set_var key="ticket_id" value="TK456" />。现在帮您排查...
[技术支持对话...]
机器人: 问题解决了吗?
客户: 好了,谢谢!
机器人: 太好了 <set_var key="is_resolved" value="true" /> <set_var key="user_sentiment" value="positive" /> <set_var key="hangup_reason" value="problem_solved" />!
祝您使用愉快,再见!<hangup/>
[SIP BYE with Headers:]
X-Hangup-Reason: problem_solved
X-Resolved: true
X-Ticket-ID: TK456
X-Sentiment: positive
完整测试用例
参见 tests.rs 中的:
test_set_var_in_streamingtest_set_var_with_sip_headerstest_http_command_in_streamtest_http_command_post_with_bodytest_multiple_commands_in_sequence
最佳实践
1. 性能优化
✅ 推荐:
查询中 <http url="..." /> 处理中...
❌ 避免:
<http url="...1" /> <http url="...2" /> <http url="...3" />
2. 错误处理
在 LLM Prompt 中说明错误处理:
如果 API 调用失败,礼貌告知用户并提供备选方案。
不要暴露技术错误细节。
3. 安全建议
- ✅ 只调用内部可信 API
- ✅ 使用环境变量存储敏感信息(如 API Key)
- ✅ 在 API 网关层做访问控制
- ❌ 不要在 Playbook 中硬编码密钥
- ❌ 不要调用不可控的外部 URL
4. 变量命名规范
- 使用下划线分隔:
user_name,ticket_id - 保留前缀
_sip_用于系统变量 - 避免使用特殊字符和空格
故障排查
问题:变量未生效
检查:
<set_var>语法是否正确(注意引号)- 变量名是否在模板中拼写一致
- 查看日志确认变量已写入 extras
问题:HTTP 调用无响应
检查:
- URL 是否可访问(网络/防火墙)
- API 是否有超时限制
- 查看对话历史中的系统消息确认响应内容
问题:BYE Headers 未携带
检查:
sip.hangup_headers配置是否正确- 变量在 hangup 前是否已设置
- 确认是 SIP 类型通话(WebRTC 不支持)
API 参考
ActiveCall.extras
类型:Option<HashMap<String, serde_json::Value>>
存储通话会话中的所有变量,包括:
- 提取的 SIP Headers
<set_var>设置的变量- 系统注入的变量
Playbook 配置
sip:
extract_headers: [String] # 要提取的 Headers 列表
hangup_headers: HashMap<String, String> # BYE Headers 模板
相关文档
版本历史
- v0.3.38: 内置会话变量(session_id, call_type, caller, callee, start_time);动态场景 Prompt 渲染(set_var 变量可在场景切换时应用到 Prompt)
- v0.3.37: 通用
${VAR_NAME}环境变量支持 - v0.3.36:
<http>响应注入 - v0.3.35: SIP BYE Headers 定制
- v0.3.34:
<set_var>单/双引号支持 - v0.3.30: SIP Headers 提取