如何用 pipecat Flows 实现把通话转接给人工坐席并同步上下文(warm transfer)?
2026/9/15 12:45:58 网站建设 项目流程

如何用 pipecat Flows 实现把通话转接给人工坐席并同步上下文(warm transfer)?

【免费下载链接】pipecatOpen Source framework for voice agents, multimodal apps, and realtime AI. Maintained by Daily and the community.项目地址: https://gitcode.com/GitHub_Trending/pi/pipecat

假设你正在开发一个语音客服机器人:大部分问题机器人自己能答,但遇到它处理不了的情况(示例里是"下单失败")时,需要把通话转接给人工坐席,并且坐席接进来之前先搞清楚客户刚才在干什么、卡在了哪里。pipecat(Daily 维护的开源语音 Agent 框架)仓库里提供了一个完整可运行的参考实现 warm_transfer.py,它用 Pipecat Flows 做会话状态机,用 Daily 作为传输层完成"静音客户 → 放 hold 音乐 → 给坐席简报 → 接通客户与坐席 → 机器人退出会议"的全过程。

这篇文档的目标是:按仓库里的步骤把这个示例跑起来,并理解每个节点、动作和上下文同步机制分别做了什么。前提是本机已具备 pipecat 开发环境(Python 3.11 最低、建议 3.12+)以及 Daily、LLM、Deepgram、Cartesia 的 API Key。

准备条件

先克隆仓库并按 README.md 的 "Developing Pipecat" 一节初始化开发环境(以下命令在仓库根目录执行):

git clone https://github.com/pipecat-ai/pipecat.git cd pipecat uv sync --group dev --all-extras \ --no-extra gstreamer \ --no-extra local

然后按 examples/flows/README.md 的 Setup 步骤准备环境变量:

cp env.example .env # 编辑 .env,填入以下 API Key

这个示例运行必需的配置项(对照 env.example):

环境变量用途
DAILY_API_KEYDaily API Key,用于创建房间和会议 token;若不设置DAILY_ROOM_URL,configure() 会自动创建一个临时房间
DAILY_ROOM_URL(可选)已有 Daily 房间 URL;设置后复用该房间,否则自动建临时房间
OPENAI_API_KEY默认 LLM 提供商openai_responses所需;也可按LLM_PROVIDER换成其他提供商的 Key
DEEPGRAM_API_KEY示例固定的 STT 服务(Deepgram)
CARTESIA_API_KEY示例固定的 TTS 服务(Cartesia)

LLM 提供商通过LLM_PROVIDER选择,支持openai_responses(默认)、openaianthropicgoogleaws,对应关系和所需 Key 见 utils.py 中create_llm()的文档说明。注意这是可选分支:不设LLM_PROVIDER时走默认的openai_responses

运行示例

在仓库根目录执行:

uv run python examples/flows/python/warm_transfer.py

启动成功后,日志会打印一条客户入会链接,格式为JOIN AS CUSTOMER:加带 token 的房间 URL。这个链接就是你扮演客户时用来加入 Daily 会议的入口。坐席的入会链接此时不会打印,它保存在flow_manager.state["human_agent_join_url"]中,等进入转接节点时才会输出(见下文)。

转接流程:Flow 节点与事件驱动

整个转接过程由 5 个 Flow 节点加 3 个 Daily 传输层事件处理器驱动,代码里 warm_transfer.py 开头的注释给出了完整的节点图:

  1. initial_customer_interaction:初始节点。机器人问候客户,提供两个选项——查门店地址/营业时间(check_store_location_and_hours_of_operation,总是成功)或开始下单(start_order总是返回status="error",这就是触发转接的开关)。
  2. continued_customer_interaction:上一个任务成功后的继续对话节点,功能集相同。
  3. transferring_to_human_agent:转接节点,见下节。
  4. human_agent_interaction:坐席简报节点,用摘要同步上下文,见下节。
  5. end_customer_conversation/end_human_agent_conversation:两条结束路径,分别对应"没有人工介入"和"已把客户 patch 给坐席"。

节点之间通过函数返回值里的NodeConfig转移。任务函数根据结果决定去向,核心是next_node_after_customer_task()

def next_node_after_customer_task(result: Mapping[str, Any]) -> NodeConfig: """Transition to either the "continued_customer_interaction" node or "transferring_to_human_agent" node, depending on the outcome of the previous customer task""" if result.get("status") == "success": return create_continued_customer_interaction_node() else: return create_transferring_to_human_agent_node()

也就是说:start_order返回{"status": "error"}后,Flow 就转移到转接节点——转接不是靠固定话术触发的,而是由工具执行结果驱动的。

转接节点做了什么:pre_actions 与 post_actions

transferring_to_human_agent节点的提示词让机器人向客户致歉并告知正在转接、请对方稍候。真正执行"转接动作"的是节点上的 action 配置:

NodeConfig( name="transferring_to_human_agent", task_messages=[...], # 道歉并告知正在转接 pre_actions=[ ActionConfig(type="function", handler=mute_customer), ], post_actions=[ ActionConfig(type="function", handler=start_hold_music), ActionConfig(type="function", handler=make_customer_hear_only_hold_music), ActionConfig(type="function", handler=print_human_agent_join_url), ], )
  • mute_customer(pre_action,在机器人开口前执行):通过transport.update_remote_participants()撤销客户的canSend权限,把客户静音,且客户无法自行取消静音。
  • start_hold_music(post_action):用asyncio.create_subprocess_exec启动一个独立子进程运行 hold_music.py,该脚本以hold-music身份的 token 加入同一个 Daily 房间,把hold_music.wav的音频循环发送进会议,客户等待时听到的就是它。
  • make_customer_hear_only_hold_music:把客户的canReceive限制为只接收hold-music这个 userId 的音频,防止客户听到机器人与坐席的谈话。
  • print_human_agent_join_url:把坐席入会 URL 打印到日志(JOIN AS AGENT:),这是你手动接线的入口。

hold 音乐子进程由atexit注册的cleanup_hold_music_process在退出时terminate(),无需手动清理。

上下文同步:坐席接入触发简报

坐席进入房间这件事是通过 Daily 传输层事件感知并驱动 Flow 的,而不是靠对话本身:

@transport.event_handler("on_participant_joined") async def on_participant_joined(transport: DailyTransport, participant: dict[str, Any]): user_id = participant.get("info", {}).get("userId") if user_id == "agent" and flow_manager.current_node == "transferring_to_human_agent": await start_human_agent_interaction(flow_manager=flow_manager)

只有当加入者userIdagent且当前节点是transferring_to_human_agent时,才切换到human_agent_interaction节点(示例假设坐席只会在转接等待期间加入)。

上下文同步就发生在这个节点上:它给 LLM 配置了ContextStrategy.RESET_WITH_SUMMARY和一段摘要提示词:

context_strategy=ContextStrategyConfig( strategy=ContextStrategy.RESET_WITH_SUMMARY, summary_prompt=( "Summarize the conversation with the customer, including what they were trying to accomplish and what, if anything, went wrong while trying to fulfill their requests. Include specific error details." ), ),

切换节点时,Flows 按该提示词把与客户此前的对话总结成一段上下文,机器人随即向刚加入的坐席说明客户想做什么、哪里出了问题,并询问坐席是否准备好接通。坐席确认后,机器人调用该节点唯一的功能connect_human_agent_and_customer,转移到结束节点:先调用unmute_customer_and_make_humans_hear_each_other恢复客户麦克风、放开客户与坐席互听,再执行end_conversation动作——机器人退出会议,客户和坐席留在房间里直接通话。这就是"warm transfer"(转接时先完成交接)区别于冷转接的部分。

另外两个事件处理器负责启动和收尾:on_first_participant_joined假设第一个入会者是客户,为其开启转写并flow_manager.initialize(...)启动 Flow;on_participant_left在客户和坐席都离开后runner.cancel()停止机器人。

验证转接是否按预期完成

按以下顺序操作并对照日志/通话现象判断(角色身份由 token 的user_id区分:机器人是owner,客户user_id: customer,坐席user_id: agent,hold 音乐user_id: hold-music;各 token 的初始canReceive权限在get_bot_token/get_customer_token/get_human_agent_token中定义,机器人只听得到客户和坐席、听不到 hold 音乐):

  1. 启动后在浏览器打开日志打印的JOIN AS CUSTOMER:链接,进入房间。机器人应主动问候并给出两个选项。
  2. 让机器人"开始下单"(对应start_order)。由于该函数固定返回错误,机器人应转入转接节点:向你道歉并说正在转接。此时你会听到 hold 音乐——静音和canReceive限制已生效,你听不到机器人后续与坐席的对话。
  3. 从日志复制JOIN AS AGENT:链接,用新标签页打开并加入。机器人应开始向"坐席"做简报:说明你之前想下单以及失败原因(即上下文摘要),并询问是否可以接通。
  4. 对坐席说"可以接通"。机器人应说正在 patch 你过去,随后退出会议;此时你(客户)与坐席互相可听、可直接对话。
  5. 双方都离开房间后,机器人进程随之停止(on_participant_left处理逻辑)。

边界与限制

  • 仅支持 Daily 传输。flows 示例 README 明确标注 warm_transfer 是 "DailyTransport only":静音/放开收听、hold 音乐、按userId控制谁听谁,都依赖 Daily 的update_remote_participants权限机制,其他传输层没有对等实现。
  • 示例是演示性质start_order永远失败,只用于演示转接路径;check_store_location_and_hours_of_operation返回写死的门店信息。接入真实业务时需要把这两个函数换成真实工具调用。
  • 坐席入会时机有假设on_participant_joined只在当前节点为transferring_to_human_agent时处理agent入会,其他节点期间的坐席加入被忽略;代码注释也指出了一个可改进点——客户在等待期间挂断时未通知坐席。
  • 房间创建:不设DAILY_ROOM_URL时由 pipecat.runner.daily.configure 创建带过期时间的临时房间(默认 2 小时,eject_at_room_exp=True),开发测试够用;正式部署时通常自建房间并配置DAILY_ROOM_URL

如果你要在自己的项目里复用这套模式,核心是三件事:用 Flow 节点区分"机器人服务客户"和"机器人与坐席交接"两个阶段;用 pre/post action 完成静音、hold 音乐、权限切换等传输层操作;用RESET_WITH_SUMMARY上下文策略在节点切换时把对话摘要带给下一阶段的对话对象。更完整的节点、功能和上下文策略说明可参考 examples/flows/README.md 末尾指向的 Pipecat Flows 指南,以及同目录下 multi_worker_handoff.py(worker 之间经 bus 交接的另一种形态)。

【免费下载链接】pipecatOpen Source framework for voice agents, multimodal apps, and realtime AI. Maintained by Daily and the community.项目地址: https://gitcode.com/GitHub_Trending/pi/pipecat

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询