mcpField
B
bolna-graph-agents

bolna-graph-agents

github.com/bolna-aiAgent 工作流368 次安装今天更新
代码仓库

关于 bolna-graph-agents

bolna-graph-agents 是用于构建 Bolna 多步骤语音对话的技能,以节点和边描述流程,并结合确定性规则与 LLM 路由。适合包含阶段、分支、外部事件或合规检查点的通话流程。

主要功能

  1. 01用节点和边组织对话流程
  2. 02支持表达式、事件和 LLM 路由
  3. 03以静态节点预生成语音
  4. 04按节点配置知识库检索
  5. 05通过事件注入推进实时通话

适用场景

  1. 01搭建预约登记等分阶段通话
  2. 02按营业时间或重试次数分支
  3. 03响应支付完成等后端事件
  4. 04确保通话经过合规检查点

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/bolna-ai/skills --skill bolna-graph-agents

安装后在 Claude Code 中输入/bolna-graph-agents确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

assets/
full-example.json
references/
debugging.md
edges-and-routing.md
event-injection.md
static-and-silence.md
scripts/
create_graph_agent.py
inject_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。
节点字段
字段类型说明
idstring由边和 current_node_id 引用。
promptstring"llm" 节点必填。说明响应 LLM 在此处要执行的操作。
node_type"llm"(默认)或 "static"static = 缓存音频、约 50ms、零成本。
static_messagestringnode_type == "static" 时必填。
repeat_after_silence_secondsnumber用户静默 N 秒后自动重播。适用于两种节点类型。
edgesarray可能的转出方式。
examplesobject各语言的示例说法,例如 { "en": [...], "hi": [...] }。
function_callstring进入节点时,强制将响应 LLM 的 tool_choice 设为此工具。用于转接节点。
rag_configobject按节点配置的知识库。详见下文深入介绍。
Edge 字段
字段说明
to_node_id必填。
condition面向用户的描述;对于 LLM 边,会用作路由 LLM 的工具描述。
condition_type"llm"(默认)、"expression"、"unconditional"、"event"。
expressionexpression 边必填。参见 references/edges-and-routing.md。
event_nameevent 边必填。参见 references/event-injection.md。
parameters{name: type},用于在转换期间从用户输入中提取参数。合并到 context_data 中。
priority数值越低,越先触发。确定性边默认为 0,LLM 边默认为 100。
function_name, function_description覆盖自动生成的路由工具名称和描述。

每轮用户对话中的路由顺序

  1. 优先处理确定性边(expression、unconditional、event)——按 priority 顺序执行。第一个匹配项生效,耗时不到 1 毫秒。
  2. LLM 边——路由 LLM 根据用户回复评估 condition 字符串。选择最佳匹配项,或返回 stay_on_current_node。
  3. 没有匹配项——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_hour0-23。仅在通话中设置了 timezone 时才会设置。
recipient_data.current_weekday小写,例如 "wednesday"。
recipient_data.current_day / _month / _year / _minute数值型组成部分。
recipient_data.timezonetz 数据库名称。
recipient_data.user_numberE.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.mdREST 端点、时序、并发、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 — 节点提示词模式和多语言支持。

评论

支持 Markdown