关于 bolna-graph-agents
bolna-graph-agents 是用于构建 Bolna 多步骤语音对话的技能,以节点和边描述流程,并结合确定性规则与 LLM 路由。适合包含阶段、分支、外部事件或合规检查点的通话流程。
主要功能
- 01用节点和边组织对话流程
- 02支持表达式、事件和 LLM 路由
- 03以静态节点预生成语音
- 04按节点配置知识库检索
- 05通过事件注入推进实时通话
适用场景
- 01搭建预约登记等分阶段通话
- 02按营业时间或重试次数分支
- 03响应支付完成等后端事件
- 04确保通话经过合规检查点
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ bolna-ai/ skills --skill bolna-graph-agents安装后在 Claude Code 中输入/bolna-graph-agents确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
assets/full-example.jsonreferences/debugging.mdedges-and-routing.mdevent-injection.mdstatic-and-silence.mdscripts/create_graph_agent.pyinject_event.py详细介绍
Bolna Graph Agents
通过由节点和边组成的有向图,构建结构化的多步骤语音对话。每个节点只负责一项任务,并定义一组转移条件。你可以在需要确定性时使用表达式边和事件进行路由,也可以在需要灵活性时由 LLM 驱动路由。
何时使用图 Agent
| 使用图 Agent | 使用 simple_llm_agent |
|---|---|
| 具有不同目标的明确阶段(问候 → 资格审核 → 信息收集 → 确认 → 结束) | 单一目标,例如回答常见问题 |
| 确定性分支(工作时间、重试次数、语言) | 可能涉及任何话题的自由对话 |
| 外部事件驱动对话(付款已确认、链接已点击) | 纯语音驱动流程 |
| 必须执行的合规检查点 | 仅有语气上的个性差异 |
| 静态节点能降低延迟和 LLM 成本的高流量流程 | 低流量原型 |
如果你能在白板上把通话描述成流程图,那么图 Agent 就适用。如果这是“一个角色,许多可能的话题”,就继续使用提示词 Agent。
一分钟了解核心概念
- 节点 — 一个对话步骤。拥有自己的提示词,可以是
"llm"或"static"。 - 边 — 一种转移方式。可以由 LLM 路由、基于表达式、无条件触发或由事件驱动。
- 路由 LLM — 第二个 LLM,用于决定每轮用户对话应选择哪条 LLM 边。默认值为
gpt-4.1-mini。 - 确定性优先 — 表达式边、无条件边和事件边会在路由 LLM 运行之前检查。只要有一条匹配,就不会调用路由 LLM(零延迟、零成本)。
- 静态节点 — 保存 Agent 时会预先渲染消息音频,并从缓存提供(约 50ms,相比 LLM 往返的约 800ms,且零成本)。
- 静默重播 —
repeat_after_silence_seconds会自动重播节点并递增_silence_repeats,因此表达式边可以逐步升级处理。 - 事件注入 —
POST /v1/call/{run_id}/events会将命名事件推送到正在进行的通话中。匹配的事件边会触发图转移,并启动主动语音。 - 按节点配置 RAG — 节点上的
rag_config仅在对话处于该节点时从向量存储中检索内容。
配置所在位置
agent_config
└── tasks[]
└── tools_config
└── llm_agent ← all graph fields go here
├── agent_type: "graph_agent"
├── agent_information
├── routing_instructions
├── current_node_id
└── nodes[]
顶层 llm_agent 字段
| 字段 | 说明 |
|---|---|
agent_type | 必须为 "graph_agent"。 |
agent_information | 全局系统提示词,会添加到每个节点的提示词前。用于角色设定、语言规则和严格防护规则。请保持简洁——每次调用 LLM 时都会发送。 |
routing_instructions | 路由 LLM 的提示词。支持从 context_data 替换 {variable}(缺失的键会显示为 NULL)。 |
current_node_id | 起始节点。 |
nodes | 节点对象数组。 |
model | 响应 LLM。默认值为 gpt-4.1-mini。 |
routing_model | 路由 LLM。默认值为 gpt-4.1-mini。 |
routing_max_tokens | 路由 token 数上限。默认值为 250(非 GPT-5)或 150(GPT-5)。 |
routing_reasoning_effort | 仅适用于 GPT-5:minimal / low / medium / high。 |
节点字段
| 字段 | 类型 | 说明 |
|---|---|---|
id | string | 由边和 current_node_id 引用。 |
prompt | string | "llm" 节点必填。说明响应 LLM 在此处要执行的操作。 |
node_type | "llm"(默认)或 "static" | static = 缓存音频、约 50ms、零成本。 |
static_message | string | node_type == "static" 时必填。 |
repeat_after_silence_seconds | number | 用户静默 N 秒后自动重播。适用于两种节点类型。 |
edges | array | 可能的转出方式。 |
examples | object | 各语言的示例说法,例如 { "en": [...], "hi": [...] }。 |
function_call | string | 进入节点时,强制将响应 LLM 的 tool_choice 设为此工具。用于转接节点。 |
rag_config | object | 按节点配置的知识库。详见下文深入介绍。 |
Edge 字段
| 字段 | 说明 |
|---|---|
to_node_id | 必填。 |
condition | 面向用户的描述;对于 LLM 边,会用作路由 LLM 的工具描述。 |
condition_type | "llm"(默认)、"expression"、"unconditional"、"event"。 |
expression | expression 边必填。参见 references/edges-and-routing.md。 |
event_name | event 边必填。参见 references/event-injection.md。 |
parameters | {name: type},用于在转换期间从用户输入中提取参数。合并到 context_data 中。 |
priority | 数值越低,越先触发。确定性边默认为 0,LLM 边默认为 100。 |
function_name, function_description | 覆盖自动生成的路由工具名称和描述。 |
每轮用户对话中的路由顺序
- 优先处理确定性边(expression、unconditional、event)——按
priority顺序执行。第一个匹配项生效,耗时不到 1 毫秒。 - LLM 边——路由 LLM 根据用户回复评估
condition字符串。选择最佳匹配项,或返回stay_on_current_node。 - 没有匹配项——Agent 留在当前节点并自然地再次询问。
如果确定性规则匹配,路由 LLM 绝不会触发。对于任何由数据驱动的情况(工作时间、重试次数、语言检测),请积极使用 expression 边——它们免费且即时。
示例:预约流程(5 个节点)
{
"agent_type": "graph_agent",
"agent_information": "You are Tara, an appointment booking assistant for Acme Dental. Speak warmly and concisely. Never quote prices.",
"routing_instructions": "Pick the edge whose condition best matches the customer's reply. If none match, stay on the current node.",
"current_node_id": "welcome",
"model": "gpt-4.1-mini",
"routing_model": "gpt-4.1-mini",
"nodes": [
{
"id": "welcome",
"node_type": "static",
"static_message": "Hi! This is Tara from Acme Dental. Are you calling to book a new appointment or reschedule an existing one?",
"repeat_after_silence_seconds": 8,
"edges": [
{ "to_node_id": "collect_slot", "condition": "Customer wants to book a new appointment" },
{ "to_node_id": "lookup_existing", "condition": "Customer wants to reschedule" },
{
"to_node_id": "goodbye",
"condition": "User silent 3 times",
"condition_type": "expression",
"expression": { "conditions": [{ "variable": "_silence_repeats", "operator": "gte", "value": 3 }] }
}
]
},
{
"id": "collect_slot",
"prompt": "Ask for the customer's preferred day and time. Confirm timezone is {recipient_data.timezone}. Once you have day + time, transition.",
"repeat_after_silence_seconds": 10,
"edges": [
{
"to_node_id": "confirm",
"condition": "Customer provided day and time",
"parameters": { "appointment_day": "string", "appointment_time": "string" }
},
{
"to_node_id": "after_hours",
"condition_type": "expression",
"condition": "Outside booking window",
"priority": 0,
"expression": {
"logic": "or",
"conditions": [
{ "variable": "recipient_data.current_hour", "operator": "lt", "value": 9 },
{ "variable": "recipient_data.current_hour", "operator": "gte", "value": 19 }
]
}
}
]
},
{
"id": "confirm",
"prompt": "Confirm '{appointment_day} at {appointment_time}' with the customer. If they agree, transition to booked. If they want to change, transition back to collect_slot.",
"edges": [
{ "to_node_id": "booked", "condition": "Customer confirms the slot" },
{ "to_node_id": "collect_slot", "condition": "Customer wants to change the slot" }
]
},
{
"id": "booked",
"node_type": "static",
"static_message": "All set. You'll get a confirmation SMS shortly. Thanks for choosing Acme Dental!",
"edges": [{ "to_node_id": "goodbye", "condition_type": "unconditional" }]
},
{
"id": "after_hours",
"node_type": "static",
"static_message": "Our booking team is offline right now. I'll have them call you back during business hours. Have a great day!",
"edges": []
},
{
"id": "goodbye",
"node_type": "static",
"static_message": "Thanks for calling Acme Dental. Goodbye!",
"edges": []
}
]
}
这样设计的原因:
welcome是静态节点——首句在 50 毫秒内播放。collect_slot通过parameters只需一次 LLM 调用即可提取两个字段。after_hours是 expression 边——即使用户确实说了一个有效时间,也会在路由 LLM 之前触发。_silence_repeats在静默 3 轮后转到告别节点,无需支付 LLM 费用。booked和goodbye是静态节点——结束阶段不使用 LLM。
更高级的示例(包含支付事件和呼叫转接),请参见 assets/full-example.json。
30 秒了解事件驱动流程
{
"id": "awaiting_payment",
"prompt": "Reassure the user while payment processes. Amount: {currency} {amount}.",
"edges": [
{ "to_node_id": "confirmation", "condition_type": "event", "event_name": "payment_completed" },
{ "to_node_id": "payment_failed", "condition_type": "event", "event_name": "payment_failed" }
]
}
支付网关响应时,从后端触发:
curl -X POST https://api.bolna.ai/v1/call/$RUN_ID/events \
-H "Authorization: Bearer $BOLNA_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "event": "payment_completed", "properties": { "ref": "TXN-98765" } }'
事件的 properties 会合并到 context_data 中——可在任何下游节点的提示词中作为 {ref} 替换项使用,也可作为 expression 边中的变量。run_id 来自 POST /call 的响应或入站 Webhook。
关于完整的时序语义(语句中途、语音中途、重复事件),请参见 references/event-injection.md。
内置变量
这些变量会自动填充。凡支持 {variable} 替换的地方都可以引用它们,包括 expression 边:
| 变量 | 说明 |
|---|---|
recipient_data.current_hour | 0-23。仅在通话中设置了 timezone 时才会设置。 |
recipient_data.current_weekday | 小写,例如 "wednesday"。 |
recipient_data.current_day / _month / _year / _minute | 数值型组成部分。 |
recipient_data.timezone | tz 数据库名称。 |
recipient_data.user_number | E.164 格式的电话号码。 |
detected_language | 顶层变量。"hindi"、"en"。 |
_node_turns | 当前节点上的用户消息数。转换时重置。 |
_total_turns | 整个通话中的用户消息数。 |
_silence_repeats | 当前节点上的静默重播次数。转换时重置。 |
如果基于时间的表达式始终不触发,说明你忘了在通话中设置
timezone。 这是图 Agent 最常见的坑。
调试
每个路由决策都会记录一行日志:
Routing decision (LLM): transition_to_offer_pitch | confidence: 0.95 |
reasoning: Customer confirmed identity by saying 'yeah'. (latency: 210ms)
Routing decision (deterministic): -> after_hours |
deterministic:expression:Outside working hours (latency: 0.6ms)
reasoning 字段是最有用的调试信号——当 Agent 转换错误时,它几乎总会解释清楚为什么做出这个选择。症状 → 修复对照表请参见 references/debugging.md。
上线前质量检查清单
- 每个节点至少有一条出边,或是明确的终止节点。
- 每种工具 / API 故障都有口头备用路径。
- 静态节点不包含动态事实,除非使用了安全的模板。
- 路由无法跳过合规提示(披露信息、录音通知)。
- 基于时间的表达式边仅在通话设置了
timezone时才会生效。 - 事件边有非事件备用方案(通过
_node_turns或_silence_repeats实现超时)。 - 将结果映射到处置方式或
context_data字段,以供分析使用。
深入了解
| 文件 | 内容 |
|---|---|
references/edges-and-routing.md | 表达式运算符、内联提取、优先级语义。 |
references/static-and-silence.md | 预渲染音频、静音升级模式。 |
references/event-injection.md | REST 端点、时序、并发、properties 合并。 |
references/debugging.md | 路由日志、常见异常行为、根本原因。 |
assets/full-example.json | 涵盖所有功能的注释配置示例。 |
scripts/create_graph_agent.py | 通过 POST /v2/agent 创建上述预约 Agent。 |
scripts/inject_event.py | 向正在进行的通话发送事件。 |
另请参阅
create-agent— 顶层 Agent 结构、tools_config、电话服务 / STT / TTS。setup-tools— 定义transfer_call、自定义 HTTP 工具。create-knowledgebase— 生成rag_config使用的vector_id。create-disposition— 从 graph-agent 通话中提取结构化结果。https://raw.githubusercontent.com/bolna-ai/skills/main/references/prompting-tips.md— 节点提示词模式和多语言支持。