把现有 Agent 集成到 Muxiva
现实中的起点通常不是“先写一个 Muxiva Node”,而是团队已经拥有 Agent:它有自己的 模型、会话、提示词、Tool、知识库和发布流程。Muxiva 要解决的是把这个 Agent 放进 实时多模态链路,而不是要求团队重写 Agent。
本章给出从独立 Agent 仓库到可运行 Muxiva Graph 的完整 SOP。
先理解四层边界
| 层 | 谁维护 | 应该包含什么 | 不应该包含什么 |
|---|---|---|---|
| Agent 仓库 | 应用团队 | 模型 Harness、会话、能力目录、路由策略、Tool、业务测试 | Graph 调度、RTC、ASR、TTS |
| Agent Node 适配器 | Agent 项目 | Port 映射、配置 Schema、Driver 装配 | 大量业务代码、厂商 SDK |
@muxiva/agent Binding |
Muxiva | 通用请求执行、能力契约与路由校验 | Turn 准入、新闻/天气/设备意图规则或具体 Tool |
| Muxiva Core/Runtime | Muxiva | Frame、Graph、Edge 队列、Signal、调度、Host、可观测性 | 业务 Turn 语义、Qwen、Pi 或业务 Tool |
因此,更换 Pi、LangGraph 或自研 Agent 时,RTC、ASR、TTS 和 Graph 不需要重写;更换 Agora 或 Qwen Node 时,Agent 仓库也不需要感知。
稳定 AgentDriver 接口
TypeScript Agent 通过 @muxiva/agent 的稳定 Driver 形状接入:
interface AgentDriver {
capabilities?(): readonly AgentCapability[]
route?(prompt): AgentRouteDecision
run(
prompt: { text: string; sequence: number },
sink: {
text(delta: string): void
event(type: string, payload?: Record<string, unknown>): void
},
signal: AbortSignal,
): Promise<void>
cancel?(reason: unknown): void
snapshot?(): unknown
close?(): void | Promise<void>
}
run接收一条已经由上游准入的用户问题;sink.text输出流式回答,Muxiva 会转为 Text Frame;sink.event输出 Agent 和 Tool 生命周期 Event;AbortSignal是首选的显式取消通道;cancel用于同步通知 Agent Harness;capabilities声明本 Driver 可以授予的模型、Tool 或资源能力;route为当前 Prompt 同步选择能力子集,Muxiva 会拒绝任何未声明能力;snapshot在 Driver 熔断重建时传递同实现的私有会话快照,Muxiva 不解析它;close在 Runtime 结束时释放会话、连接和订阅。
这不是 HTTP 协议,也不要求 Agent 与 Runtime 在同一个实现仓库。它是应用 Agent 与 Muxiva Node 之间最小、可测试的进程内契约。
AgentNodeAdapter 在哪一层
AgentNodeAdapter 是 @muxiva/agent 导出的框架组件,也是 defineAgentNode 背后的
默认实现。它不是新的 Graph Node,也不属于 Rust Runtime Core。应用仍然只在 Graph 中
看到一个 Agent Node。
它只处理按输入顺序执行、输出队列、显式取消、首字与整次请求超时、取消后晚到结果抑制、
Driver 熔断重建和终止事件。它不读取 Turn ID,不比较 Signal/Prompt 序列,也不会把新 Prompt
解释成对旧请求的覆盖。语音 Graph 中只有 builtin.voice_turn_controller 负责 Turn 准入与
覆盖,并通过明确的 Signal 通知 Agent。Rust Core 仍只处理 Frame、Edge、Signal 和 Node
生命周期。
能力路由同样分两部分:Muxiva 定义并校验 AgentCapability、AgentRouteDecision,应用
Agent 决定“什么输入需要什么能力”。例如“最近新闻需要联网”属于 Pi Agent 的产品策略,
绝不会写入 Muxiva Core。每次有效决定都会产生 muxiva.agent.route.selected Event。
路由里的 capabilities 表示本次请求“最多允许使用”的权限;requiredCapabilities 是其中
“回答前必须真正满足”的子集。框架会校验必需能力没有越权,Driver 负责执行并在无法满足
时显式失败。这样新闻或天气问题不会因为 Tool 只是可用但没有调用,就把模型猜测提交给
UI/TTS。
Graph Port 契约
推荐所有文本 Agent 使用同一组 Port:
| Port | Frame | 语义 |
|---|---|---|
prompt_in |
Text 输入 | ASR Final、聊天输入或上游规划结果 |
signal_in |
Signal 输入 | 显式请求取消;语音 Graph 只接 Voice Turn Controller 的标准 Signal |
text_out |
Text 输出 | 可被 TTS、UI 或下游 Agent 消费的流式片段 |
event_out |
Event 输出 | response、tool、route、failure 生命周期 |
defineAgentNode 负责有界输出队列、每次请求的 Sink、显式取消后的晚到结果抑制、内部唤醒和
关闭。Agent 不需要在 Graph 中添加 Clock Node,也不需要把 WebSocket 或 RTC 逻辑放进
自己的代码。
SOP 1:整理独立 Agent 仓库
Agent 仓库至少应做到:
my-company-agent/
├── package.json
├── package-lock.json
├── src/
│ ├── index.ts # 导出 createMyAgentDriver
│ ├── tools/ # 文件、搜索、业务 API 等 Tool
│ └── permissions.ts # 权限与资源上限
└── test/
导出一个工厂函数:
export function createMyAgentDriver({ config }) {
return {
async run(prompt, sink, signal) {
const result = await myAgent.run(prompt.text, { signal })
for await (const delta of result.textStream) sink.text(delta)
},
cancel() { myAgent.cancel() },
async close() { await myAgent.close() },
}
}
先在该仓库独立完成类型检查、Tool 单元测试、取消测试和权限测试,再发布 Tag。不要让
Muxiva 的 setup.sh 永远跟随外部仓库 main;Demo 使用 v0.2.1,真实项目也应该锁定
经过审查的 Tag 或 Commit。
SOP 2:建立薄 Node 适配器
在 Agent 项目而不是 Agent 源码仓库中创建:
node.ts 只做装配:
import { defineAgentNode } from '@muxiva/agent'
import { createMyAgentDriver } from '@my-company/my-agent'
export const MyAgentNode = defineAgentNode({
createDriver: createMyAgentDriver,
})
如果这里出现模型请求、文件操作或几十个业务 Tool,说明分层又混在了一起;这些代码应 回到独立 Agent 仓库。
Manifest 声明 node_type、精确 factory_version、四个 Port 和配置 JSON Schema。
Studio 使用 Manifest 展示 Node,Runtime 使用它做 Graph 校验和 Factory 解析。
SOP 3:锁定并安装 Agent
参考 Demo 的安装方式:
git clone --depth 1 --branch v1.2.3 \
https://github.com/my-company/my-agent.git \
.muxiva/agents/my-agent
npm ci --ignore-scripts
npm run check:typescript
package.json 使用本地锁定仓库:
{
"dependencies": {
"@muxiva/agent": "file:../../bindings/agent",
"@my-company/my-agent": "file:.muxiva/agents/my-agent"
}
}
旗舰 Demo 已把这一步自动化:
使用自己的 Agent 仓库验证相同集成路径:
MUXIVA_PI_AGENT_REPOSITORY=https://github.com/my-company/my-agent.git \
MUXIVA_PI_AGENT_REF=v1.2.3 \
./examples/voice-agent/setup.sh
脚本遇到已有的非 Git 目录、不同 Remote 或未提交修改时会停止,不会覆盖开发者代码。
SOP 4:授予文件与编码权限
“Agent 能使用文件”必须同时回答三个问题:能访问哪里、能做什么、资源上限是多少。
Pi 参考 Agent 默认配置:
{
"workspace_root": ".muxiva/workspaces/pi-agent",
"max_file_bytes": 262144,
"max_search_files": 500
}
Muxiva TypeScript Host 会把 Graph 所在目录设置为进程工作目录,并注入绝对的
MUXIVA_PROJECT_ROOT。Agent 只接受相对 workspace_root,并拒绝:
..路径穿越和绝对路径;- 指向工作区外部的符号链接;
.env、.env.*、.git和.ssh;- 超过配置上限的文件和搜索;
- 未显式
overwrite=true的已有文件覆盖。
默认 Tool 是:目录浏览、文件读取、文本搜索、创建/写入、精确文本替换。默认没有 Shell、进程执行、任意删除和项目外权限。
若确实希望 Agent 处理整个项目,可以把 workspace_root 配为 .,但应该先审查 Agent
代码和模型策略。更稳妥的方式是把需要修改的源码放进独立 workspace,验收 Diff 后再由
人或 CI 合并。
SOP 5:增加可观察的联网搜索 Tool
联网搜索属于 Agent 的工具能力,不属于 Muxiva Runtime。参考 Agent 在独立仓库中声明
web_search;外层 Pi 判断是否需要实时信息,工具再通过百炼 DashScope 原生接口执行
forced_search。普通文件任务和闲聊不会偷偷产生搜索调用。
用户问题 → Pi Agent 判断需要联网 → web_search Tool
→ 百炼 turbo search
→ answer + sources + latency
→ Pi 组织带来源回答
工具输入契约:
| 字段 | 必填 | 说明 |
|---|---|---|
query |
是 | 聚焦的检索问题,最多 2000 字符 |
freshness_days |
否 | 只关注最近 1–365 天 |
domains |
否 | 最多 10 个域名,仅搜索指定站点 |
工具输出包含 answer、sources[]、model、duration_ms、search_strategy 和 Token
用量。sources[] 保留搜索结果原始序号、标题、站点和 URL,供 Agent 在回答中引用。
Muxiva 不解析百炼协议:它只通过通用 tool.started/completed Event 和 Semantic Trace
观察这次工具调用。
参考配置:
{
"web_search_enabled": true,
"web_search_model": "qwen-flash",
"web_search_strategy": "turbo",
"web_search_max_sources": 10,
"web_search_timeout_ms": 20000
}
搜索复用 DASHSCOPE_API_KEY 和 DASHSCOPE_WORKSPACE_ID,不增加第三份凭据。turbo
用于实时语音的低延迟路径;需要更深检索时可以改成 max,但延迟和费用会增加。百炼
搜索需要账号已开通对应服务,并会单独计费。实现依据百炼联网搜索官方文档。
SOP 6:连接 Graph 并验证打断
典型级联链路:
ASR.transcript_out ──Text──> VoiceTurnController.transcript_in
VAD.speech_out ─────Event─> VoiceTurnController.activity_in
VoiceTurnController.prompt_out ──Text──> Agent.prompt_in
VoiceTurnController.signal_out ─Signal─> Agent/TTS/音频出口取消输入
Agent.text_out ─────────────Text───────> TTS.text_in
Agent.event_out ────────────Event──────> 应用 Event Encoder
一次插话被准入后,Voice Turn Controller 先向 Agent、TTS 和音频出口发送同一个标准 Signal,再转发新 Prompt;Runtime 保证同次回调的 Signal 先于 Frame 到达下游队列。 Agent 适配器只取消当前 Pi 请求并关闭该请求的 Sink,丢弃取消后晚到的 Text/Event。 新 Turn 只由 Voice Turn Controller 创建,Agent 适配器不推断。
验证清单:
muxiva doctor --voice显示外部 Agent 源码、锁定依赖和 workspace 均 Ready;- Studio Validate 通过,Palette 中能看到 Agent Node;
- 询问“列一下工作区文件”,Observe 中出现
tool.started/completed; - 要求“创建一个 index.html”,文件真实出现在 workspace;
- 询问“搜索今天的 Qwen 更新并给出来源”,Trace 出现
web_search且回答包含 URL; - Agent 回答过程中插话,旧回答与旧 Tool 结果不再进入 TTS;
- Observe 的 Semantic Trace 能按 Turn 查看 Text、Event 和 Signal;
runtime.log中没有 Host 协议破坏、路径拒绝之外的异常。
参考实现
PiyotaHu/muxiva-pi-agent 是一份独立、可 Fork
的 Pi 编码 Agent。Muxiva Demo 只保存薄适配器,并在 setup.sh 中拉取其锁定版本。这正是
推荐给最终用户的路径:先拥有并测试自己的 Agent,再把一个被审查的版本部署到 Muxiva。
具体 Tool、配置和 Demo 体验见 Pi 编码 Agent。