1. 项目概述:一个让AI智能体“组队干活”的SDK

如果你最近在折腾AI应用开发,尤其是想搞点“智能体”(Agent)相关的项目,大概率会遇到一个头疼的问题:单个AI模型能力再强,也总有短板。比如,让Claude写代码逻辑清晰,但让它做代码安全检查可能就不如专门的安全模型;让GPT-4做创意发散很棒,但让它整理结构化数据可能又差点意思。于是,一个很自然的想法就冒出来了——能不能让多个不同特长的AI智能体一起协作,像一支团队那样完成复杂任务?

这个想法很好,但实践起来全是坑。你得自己写一堆“胶水代码”来管理这些智能体的进程、处理它们之间的通信、协调任务流程、还要抽象不同AI服务提供商(如OpenAI、Anthropic、Google等)的API差异。每做一个新项目,这套轮子就得重造一遍,效率极低。

今天我分享的 @parallax/relay 这个开源SDK,就是来解决这个痛点的。它本质上是一个 多智能体编排框架 ,用TypeScript写成。你可以把它想象成一个“AI智能体调度中心”。它的核心设计非常简洁:你通过它“孵化”(Spawn)出多个来自不同供应商、具备不同任务的AI智能体,然后把它们“连线”(Wire)到指定的“频道”(Channel)上,接下来,你只需要向频道里扔一个任务指令,这些智能体就会自动开始对话、协作,直到完成任务。你作为开发者,几乎不用关心它们内部怎么聊的,只需要关注最终产出。

它适合谁呢?如果你是前端、全栈或Node.js开发者,正在构建需要复杂AI逻辑的应用程序,比如自动化的内容创作流水线、智能代码审查系统、多步骤数据分析工具,或者任何你觉得单个AI模型搞不定、需要“群策群力”的场景,那么这个SDK会大幅降低你的开发门槛。即使你对多智能体系统(Multi-Agent System)的理论不熟,也能快速上手,因为它把复杂的分布式协调问题,封装成了几个直观的API调用。

2. 核心设计理念与架构拆解

在深入代码之前,我们得先理解 @parallax/relay 是怎么思考这个问题的。它的设计哲学可以概括为 “进程即智能体,频道即协作空间” 。这和我们常见的、在单个应用进程内调用多个AI API的“伪多智能体”方案有本质区别。

2.1 为什么选择“进程隔离”作为智能体载体?

很多初学者的做法是,在一个Node.js服务里,创建几个不同的函数或类,每个函数负责调用一种AI API。这看起来简单,但存在严重问题:

  1. 状态污染与内存泄漏 :AI对话通常需要维护上下文(Context)。多个智能体的上下文在同一个进程里混在一起,容易相互干扰,清理不当就会内存泄漏。
  2. 阻塞与性能 :如果一个智能体的AI API调用特别慢(比如等待GPT-4的长篇生成),它会阻塞整个Node.js事件循环,导致其他智能体也“卡住”。
  3. 生命周期管理困难 :你很难优雅地单独重启、升级或替换其中的一个智能体。

@parallax/relay 的解决方案很彻底: 每个智能体都是一个独立的子进程(Child Process) 。你指定的那个 provider (如 claude , openai ),实际上对应一个命令行工具(CLI)。Relay会为你启动这个CLI进程,并通过标准输入(stdin)/输出(stdout)与它通信。这样做的好处显而易见:

  • 隔离性 :每个智能体崩溃了,不会影响其他智能体。
  • 独立性 :每个智能体可以有完全独立的环境、依赖和配置。
  • 可扩展性 :理论上,这些进程可以分布到不同的机器上,Relay的频道机制就是它们之间的通信总线。

2.2 “频道”模型:简化通信的巧妙抽象

智能体之间要协作,必须能通信。最笨的办法是让智能体A直接知道智能体B的进程ID然后发消息,这会导致复杂的网状依赖。 @parallax/relay 引入了 “频道”(Channel) 的概念。

你可以把频道理解为一个 命名的消息总线 或者一个 群聊房间 。智能体通过订阅( channels 配置项)加入一个或多个频道。任何发送到这个频道的消息,所有订阅了该频道的智能体都会收到。这就实现了 发布-订阅(Pub/Sub)模式

这种设计的美妙之处在于 解耦

  • 发送者不需要知道接收者是谁 :你只需要 relay.broadcast('design', '我们需要一个登录页的UI草图') ,所有在 design 频道上的UI设计师智能体、产品经理智能体都会收到。
  • 智能体之间无需直接引用 :它们只关心频道里的消息,而不需要维护一个“同事列表”。这使得动态增加或减少智能体变得非常容易。

当然,它也支持点对点通信( relay.sendMessage ),用于需要直接指令的场景。

2.3 统一提供商接口:用CLI化解差异

支持多种AI提供商(OpenAI的GPT, Anthropic的Claude, Google的Gemini等)是另一个挑战。每个提供商的API签名、认证方式、返回格式都不同。

@parallax/relay 采用了一个务实且强大的策略: 它不直接对接各家的HTTP API,而是要求提供商提供一个命令行接口(CLI) 。这个CLI工具需要遵守一个简单的约定:从标准输入读取JSON格式的请求,向标准输出写入JSON格式的响应。

这样做带来了巨大的灵活性:

  1. 屏蔽底层复杂性 :SDK只和CLI通信,CLI内部可以用任何语言(Python, Go, Rust)实现,负责处理认证、重试、格式化等脏活累活。
  2. 支持任何模型 :只要你能为某个模型或API包装一个CLI,它就能被Relay集成。官方已经支持了主流厂商,而 custom 选项让你可以接入任何自定义的CLI,甚至是本地运行的Ollama模型。
  3. 便于调试 :你可以单独在终端里运行这个CLI,手动输入JSON来测试,这比调试HTTP请求直观得多。

整个架构如下图所示,非常清晰:

┌─────────────┐
│   Relay      │ ← 核心编排器,你的代码直接调用它
├──────┬───────┤
│ Agent│ Agent │ ← 独立的子进程,运行不同提供商的CLI
├──────┴───────┤
│   Channels   │ ← 虚拟的消息频道,实现智能体间通信
└──────────────┘

3. 从零开始:环境准备与第一个多智能体应用

理论讲完了,我们动手搭一个。假设我们要构建一个简单的“技术博客助手”系统:一个智能体负责起草文章,另一个负责校对和润色。

3.1 初始化项目与安装依赖

首先,创建一个新的Node.js项目(如果你还没有的话):

mkdir ai-blog-team && cd ai-blog-team
npm init -y

然后,安装 @parallax/relay 核心库:

npm install @parallax/relay

关键一步:配置AI提供商CLI Relay本身不包含AI调用能力,它依赖外部CLI。以OpenAI为例,你需要安装 parallax 官方维护的CLI工具 @parallax/ai 。它像一个统一的适配器。

npm install -g @parallax/ai

安装后,你需要设置API密钥。这里以OpenAI为例(其他如Anthropic、Google类似):

parallax config set openai.apiKey your-openai-api-key-here

注意 @parallax/ai CLI 是一个独立的工具,它帮你封装了对各大厂商API的调用,并暴露给Relay。你也可以自己编写CLI,只要遵循 { "response": "..." } 这样的输入输出格式即可。对于初学者,强烈建议使用官方CLI,省时省力。

3.2 编写第一个多智能体脚本

创建一个文件 blog-orchestrator.ts 。我们将使用TypeScript,但纯JavaScript也完全兼容。

import { Relay, Models } from '@parallax/relay';

// 初始化Relay核心实例
const relay = new Relay();
// 你可以传入配置,比如日志级别、超时时间等
// const relay = new Relay({ logLevel: 'debug' });

async function main() {
  console.log('🚀 启动博客创作团队...');

  // 1. 孵化“作家”智能体
  // 使用OpenAI的GPT-4o模型,让它专注于创意写作
  const { agent: writerAgent } = await relay.spawn({
    name: 'writer', // 智能体名称,用于日志和直接消息
    provider: 'openai', // 指定提供商,对应已配置的CLI
    model: Models.OpenAI.GPT4_O, // 使用预定义的模型枚举,清晰且防错
    channels: ['drafting-room'], // 让作家加入“起草室”频道
    task: `你是一位资深技术博客作家。你的风格清晰、生动,擅长用类比解释复杂概念。请根据收到的主题,创作结构完整、引人入胜的博客文章草稿。`, // 初始系统指令,定义角色和任务
  });

  // 2. 孵化“编辑”智能体
  // 使用Claude 3 Haiku模型,它速度快、擅长理解和精简文本
  const { agent: editorAgent } = await relay.spawn({
    name: 'editor',
    provider: 'claude',
    model: Models.Claude.HAIKU,
    channels: ['drafting-room'], // 编辑也加入同一个频道,才能收到作家的草稿
    task: `你是一位严格的科技编辑。你的目标是让文本更简洁、逻辑更通顺、语法无误。你会对草稿提出具体的修改建议,并直接输出修改后的版本。`,
  });

  console.log('⏳ 等待智能体初始化并准备就绪...');
  // 3. 等待所有智能体准备完成
  // 这是一个关键步骤,确保所有子进程CLI都已启动并加载好模型
  await Relay.waitForAll([writerAgent, editorAgent], 45000); // 设置45秒超时
  console.log('✅ 所有智能体已就位!');

  // 4. 向频道广播启动指令
  // 这个消息会同时发送给“起草室”频道里的作家和编辑
  await relay.broadcast('drafting-room', '开始协作:请创作一篇关于“Node.js中Event Loop工作原理”的博客文章。作家请先输出草稿。');

  // 5. 协作流程(模拟)
  // 在实际中,你可以设置更复杂的逻辑,比如监听频道消息、判断任务是否完成。
  // 这里我们简单等待一段时间,让它们交流。
  console.log('💬 智能体们正在频道内讨论和协作...');
  await new Promise(resolve => setTimeout(resolve, 30000)); // 等待30秒

  // 6. 任务结束后,关闭所有智能体,释放资源
  console.log('🛑 任务结束,关闭智能体进程...');
  await relay.shutdown();
  console.log('👋 所有进程已安全退出。');
}

main().catch(console.error);

3.3 运行与观察

使用ts-node或编译后运行:

npx tsx blog-orchestrator.ts
# 或者先编译:tsc blog-orchestrator.ts --target es2020 --module commonjs && node blog-orchestrator.js

运行后,你会在控制台看到类似这样的日志(取决于你的配置):

🚀 启动博客创作团队...
[Relay] Spawning agent 'writer' with provider 'openai'...
[Relay] Spawning agent 'editor' with provider 'claude'...
⏳ 等待智能体初始化并准备就绪...
[Agent writer] Ready.
[Agent editor] Ready.
✅ 所有智能体已就位!
💬 智能体们正在频道内讨论和协作...
[Channel drafting-room] [writer -> all]: 这是关于Node.js Event Loop的博客草稿...
[Channel drafting-room] [editor -> all]: 收到草稿。建议如下:1. 开头可以更抓人... 这是修改后的版本...
[Channel drafting-room] [writer -> all]: 感谢编辑,根据建议已修订...
🛑 任务结束,关闭智能体进程...
[Relay] Shutting down all agents...
👋 所有进程已安全退出。

发生了什么?

  1. Relay启动了 openai claude 两个CLI子进程。
  2. 两个智能体都加入了 drafting-room 频道。
  3. 你广播的消息同时送达两者。
  4. “作家”率先生成草稿并发布到频道。
  5. “编辑”在频道里看到草稿,给出修改建议或直接发布修改版。
  6. 它们可能会在频道里进行多轮交互,而你无需干预。
  7. 最后, relay.shutdown() 会向所有智能体进程发送终止信号,并等待它们退出。

实操心得一:理解“任务”参数的作用 spawn 配置里的 task 参数极其重要,它就是发给AI模型的“系统提示词”(System Prompt)。这个提示词定义了该智能体的 角色、行为准则和核心职责 。写得好,智能体协作就顺畅;写得模糊,它们可能会“跑偏”或做无用功。建议为每个智能体设计清晰、专一的职责描述,避免角色重叠。例如,编辑的 task 里明确写了“直接输出修改后的版本”,这能减少不必要的“我改好了,你呢?”这类低效对话。

4. 深入核心API与高级协作模式

基础跑通后,我们来深入研究 @parallax/relay 提供的其他核心能力,并设计更复杂的协作流程。

4.1 精细化的智能体控制与通信

除了广播,你还可以进行精准控制。

向特定智能体发送私密指令: 有时你不想让某个指令被频道里所有智能体看到。比如,你想悄悄告诉编辑“这次的重点是检查技术准确性,文风暂时放次要”。

// 在广播之后,单独给编辑发一条指令
await relay.sendMessage('editor', '(内部指令)本次审核请重点关注代码示例的准确性,语言风格问题可适度放宽。');

sendMessage 是点对点的,只有名为 editor 的智能体会收到这条消息。

等待策略的选择:

  • Relay.waitForAll : 等待列表中的所有智能体就绪。适用于必须全员到齐才能开始的场景(如上面的例子)。
  • Relay.waitForAny : 只要列表中任意一个智能体就绪就返回。适用于“谁先准备好谁先干活”的竞争或负载均衡场景。
  • agent.waitForReady : 等待单个特定的智能体就绪。
// 场景:一个快速模型(Haiku)和一个慢速模型(Opus)同时启动。
// 我们希望快速模型先开始处理一些预处理任务。
const fastAgent = await relay.spawn({ provider: 'claude', model: Models.Claude.HAIKU, /* ... */ });
const slowAgent = await relay.spawn({ provider: 'claude', model: Models.Claude.OPUS, /* ... */ });

const firstReadyAgent = await Relay.waitForAny([fastAgent.agent, slowAgent.agent]);
console.log(`${firstReadyAgent.name} 首先准备好了!`);
// 可以先给 fastAgent 分配一些即时任务
await relay.sendMessage(firstReadyAgent.name, '开始整理背景资料...');

4.2 构建一个三智能体代码审查流水线

让我们看一个更贴近实际开发的例子:自动化代码审查。我们将组建一个由三个智能体构成的小队:

  1. 代码审查员(Reviewer) :检查代码风格、逻辑和最佳实践。
  2. 安全审计员(Auditor) :专门查找潜在的安全漏洞(如SQL注入、命令注入)。
  3. 协调员(Coordinator) :负责汇总前两者的报告,生成最终的人类可读总结,并决定是否通过。
import { Relay, Models } from '@parallax/relay';

const relay = new Relay({ logLevel: 'info' }); // 开启info日志,看得更清楚

async function codeReview(prDiff: string) {
  // 孵化三个智能体,加入同一个频道
  const { agent: reviewer } = await relay.spawn({
    name: 'reviewer',
    provider: 'claude',
    model: Models.Claude.SONNET,
    channels: ['code-review-team'],
    task: `你是一个经验丰富的软件工程师,负责代码审查。请仔细分析提供的代码差异(Git Diff),指出不符合编码规范、逻辑错误、性能问题或可读性差的地方。每条意见请注明行号和建议的修改方式。输出格式请保持清晰。`,
  });

  const { agent: auditor } = await relay.spawn({
    name: 'auditor',
    provider: 'gemini', // 使用Gemini,它在安全分析上表现不错
    model: Models.Gemini.GEMINI_2_0_FLASH,
    channels: ['code-review-team'],
    task: `你是一个安全专家。你的唯一任务是审查代码差异,寻找所有可能的安全漏洞,包括但不限于:注入攻击、敏感信息泄露、不安全的反序列化、错误的权限检查等。对每个潜在漏洞,评估其风险等级(高/中/低)并提供修复方案。`,
  });

  const { agent: coordinator } = await relay.spawn({
    name: 'coordinator',
    provider: 'openai',
    model: Models.OpenAI.GPT4_O_MINI,
    channels: ['code-review-team'],
    task: `你是审查团队的协调员。你将收到审查员和安全审计员的报告。你的任务是:
    1. 整合两份报告,去除重复项。
    2. 将问题归类(代码质量、安全性、性能等)。
    3. 生成一份给开发者的最终摘要,语言友好、重点突出。
    4. 如果发现任何高风险安全漏洞,在摘要开头用【⚠️ 安全警报】标出。
    请直接输出最终摘要报告。`,
  });

  await Relay.waitForAll([reviewer, auditor, coordinator]);

  // 将代码Diff广播给整个团队。审查员和审计员会并行工作。
  await relay.broadcast('code-review-team', `以下是需要审查的代码差异:\n\`\`\`diff\n${prDiff}\n\``\``);

  // 关键:给协调员一个明确的指令,告诉它如何触发汇总工作。
  // 这里我们设计一个简单的协议:让前两者在评论开头加上 [REPORT] 标识。
  await relay.sendMessage('coordinator', '请等待审查员和审计员在频道内发布他们的报告。当他们发布完毕后,请开始整合并生成最终摘要。');

  // 在实际项目中,这里会更复杂。你可能需要监听频道消息,判断何时“报告完毕”。
  // 一个简单的策略是等待固定时间,或者让协调员在收到特定指令后开始工作。
  // 例如,可以让reviewer和auditor在报告结尾加上 `[END_OF_REPORT]`。
  console.log('等待审查流程进行...');
  await new Promise(resolve => setTimeout(resolve, 60000)); // 等待1分钟

  // 我们可以直接从协调员那里“窃听”最终报告(假设协调员把报告发到了频道)
  // 更优雅的做法是通过事件监听,但简单起见,我们这里只是演示。
  // 实际上,你应该设计消息协议,让协调员将最终报告发送到一个特定的输出频道或回调函数。

  await relay.shutdown();
}

// 模拟一段简单的代码Diff
const sampleDiff = `
+ function getUserInput() {
+   const input = document.getElementById('user-input').value;
+   return input;
+ }
+
+ function queryDatabase(userId) {
+   const sql = \`SELECT * FROM users WHERE id = '\${userId}'\`;
+   // ... 执行查询
+ }
`;
await codeReview(sampleDiff);

这个例子展示了如何通过 角色定义 频道通信 来设计一个工作流。三个智能体各司其职,在同一个频道里“看到”彼此的产出,并由协调员完成最后的信息整合。你可以在此基础上扩展,例如增加一个“测试生成员”智能体,或者让协调员在发现问题严重时,自动向你的项目管理工具发送通知。

4.3 处理智能体输出与状态管理

在实际应用中,你肯定需要获取智能体的输出结果,而不仅仅是看日志。 @parallax/relay 的智能体进程通过CLI与SDK通信,SDK提供了监听消息的机制。不过,在当前的API设计中,最直接获取频道消息的方式是通过日志(如果日志级别足够详细),或者你需要在自己的应用中实现一个消息中继。

一个常见的模式是 创建一个专用的“记录员”或“输出收集器”智能体 。它的任务就是订阅所有相关频道,将其它智能体的对话和最终输出整理好,并发送到一个你能轻易获取的地方(比如,通过 sendMessage 发回给主程序,或者写入文件/数据库)。

// 创建一个输出收集器智能体
const { agent: logger } = await relay.spawn({
  name: 'logger',
  provider: 'openai', // 甚至可以用一个非常轻量的模型,或者自定义的只输出输入的CLI
  model: Models.OpenAI.GPT4_O_MINI,
  channels: ['code-review-team', 'drafting-room'], // 订阅所有你关心的频道
  task: `你的任务仅仅是记录。你会收到这个频道里的所有消息。请将消息按时间顺序、以纯文本格式记录下来,并在每条消息前标注发送者。当我问你“输出日志”时,请返回迄今为止的所有记录。`,
});

// ... 其他业务流程

// 当你想获取协作记录时
await relay.sendMessage('logger', '输出日志');
// 注意:你需要通过某种方式捕获logger的回复。这可能需要更底层的事件监听。

实操心得二:设计消息协议 让多个AI智能体自由对话,很容易变得混乱。 为你的多智能体系统设计一个简单的通信协议至关重要 。例如:

  • 任务开始/结束标识 [TASK_START: blog_draft] , [TASK_END: blog_draft]
  • 报告标识 [REPORT_BEGIN] ... [REPORT_END]
  • 指令响应 [ACK] 表示收到指令, [RESULT] 后面跟结果。 在你的智能体 task 提示词中,就明确要求它们遵守这些协议。这样,作为“总控”的主程序,或者像上面 coordinator 这样的智能体,就能更容易地解析频道消息,判断工作流的状态,从而做出决策(例如,所有报告都收到了,开始汇总)。

5. 实战进阶:自定义提供商与复杂工作流编排

当你需要接入官方不支持的模型,或者想对AI调用有更精细的控制时,自定义提供商(Custom Provider)就派上用场了。

5.1 集成本地Ollama模型

Ollama让你能在本地运行Llama、Mistral等开源模型。 @parallax/relay 原生支持Ollama。

首先,确保你本地安装了Ollama并拉取了模型,例如 llama3.2:1b

ollama pull llama3.2:1b

然后,在你的Relay配置中,可以直接使用 ollama 作为provider,并指定模型名。

const { agent } = await relay.spawn({
  name: 'local-expert',
  provider: 'ollama',
  model: 'llama3.2:1b', // 直接使用Ollama的模型标签
  channels: ['local-chat'],
  task: '你是一个运行在本地的助手。',
});

这行代码会调用 ollama run llama3.2:1b 命令并与之通信。这对于需要数据隐私或离线运行的场景非常有用。

5.2 创建完全自定义的CLI提供商

假设你有一个用Python写的、调用某个企业内部AI服务的脚本 my_ai_helper.py 。你想把它集成到Relay中。

第一步:创建符合规范的CLI脚本 你的脚本需要从标准输入(stdin)读取JSON,处理后将结果以JSON格式打印到标准输出(stdout)。

my_ai_helper.py 示例:

#!/usr/bin/env python3
import sys
import json
import your_custom_ai_client # 假设这是你的内部AI客户端库

def main():
    # 从stdin读取整个输入
    input_data = sys.stdin.read()
    try:
        request = json.loads(input_data)
        # Relay发送的消息在 `text` 字段中
        user_message = request.get("text", "")
        
        # 调用你的自定义AI服务
        # 这里简化处理,实际调用你的API
        response_text = your_custom_ai_client.chat(user_message)
        
        # 输出必须是一个包含 `response` 字段的JSON对象
        result = {"response": response_text}
        print(json.dumps(result))
        sys.stdout.flush() # 确保立即输出
    except Exception as e:
        # 错误信息也会被Relay捕获
        error_result = {"response": f"Error: {str(e)}"}
        print(json.dumps(error_result))
        sys.stdout.flush()

if __name__ == "__main__":
    main()

确保脚本有可执行权限: chmod +x my_ai_helper.py

第二步:在Relay中配置自定义Provider 你需要告诉Relay如何调用你的脚本。这通常在创建 Relay 实例时通过配置完成。

import { Relay } from '@parallax/relay';

const relay = new Relay({
  // 在providers配置中定义自定义提供商
  providers: {
    mycompany: { // 你自定义的provider名称
      command: 'python3', // 解释器
      args: ['/path/to/your/my_ai_helper.py'], // 脚本路径
      // 还可以配置环境变量等
      env: {
        'MY_API_KEY': 'your-secret-key'
      }
    }
  }
});

// 现在可以像使用官方provider一样使用它
const { agent } = await relay.spawn({
  name: 'internal-ai',
  provider: 'mycompany', // 使用自定义名称
  model: 'default', // 对于自定义provider,model字段可能被忽略或用作CLI参数
  channels: ['internal'],
  task: '你是一个内部助手。',
});

5.3 编排复杂、有条件的工作流

到目前为止,我们的工作流大多是“发射后不管”或简单等待。真正的业务逻辑往往需要基于智能体的输出做判断,从而动态决定下一步。

@parallax/relay 本身不内置复杂的工作流引擎(如状态机),但它提供了构建块。实现复杂工作流的核心在于 让你的主控程序监听频道消息,并根据内容做出决策

假设一个“需求分析 -> 原型设计 -> 代码生成”的流水线:

  1. 分析师 analysis 频道输出需求文档。
  2. 设计师 看到需求后,在 design 频道输出UI原型描述。
  3. 程序员 需要同时看到需求和原型,才能在 code 频道生成代码。

这里有个依赖关系:程序员需要前两者都完成。我们可以这样设计:

import { EventEmitter } from 'events'; // Node.js内置事件模块

async function complexWorkflow() {
  const relay = new Relay();
  const workflowState = { analysisDone: false, designDone: false };

  const analyst = /* ... spawn ... */; // 订阅 analysis 频道
  const designer = /* ... spawn ... */; // 订阅 analysis, design 频道
  const programmer = /* ... spawn ... */; // 订阅 analysis, design, code 频道

  // 创建一个简单的事件监听器来模拟消息路由
  // 注意:这是一个简化示例。真实场景可能需要更复杂的消息总线。
  const messageBus = new EventEmitter();

  // 假设我们有一个函数能“窃听”某个频道的消息(这需要扩展Relay或使用其内部事件)
  // 伪代码:function listenToChannel(channelName, callback) { ... }
  // 当监听到 analysis 频道有消息结尾是 [FINAL_SPEC] 时,触发事件
  // messageBus.on('analysis_complete', () => { workflowState.analysisDone = true; checkAndStartDesign(); });

  // 在主控逻辑中轮询或等待
  console.log('启动需求分析...');
  await relay.broadcast('analysis', '分析一下如何做一个个人记账App的需求。');
  
  // 这里进入一个循环或使用Promise.race/all来等待多个条件
  // 一种简单模式:让每个智能体完成任务后,向一个专用的“协调频道”发送完成信号。
  const { agent: coordinator } = await relay.spawn({
    name: 'workflow-coord',
    provider: 'claude',
    model: Models.Claude.HAIKU,
    channels: ['coordination'],
    task: `你是一个工作流协调器。你会收到两种消息:
    1. “分析师完成” -> 记录需求就绪。
    2. “设计师完成” -> 记录设计就绪。
    当两者都就绪后,在频道里发送“开始编码”消息。`,
  });

  await relay.sendMessage('analyst', '完成后请在 coordination 频道说“分析师完成”。');
  await relay.sendMessage('designer', '完成后请在 coordination 频道说“设计师完成”。');
  await relay.sendMessage('programmer', '请等待 coordination 频道的“开始编码”指令。');

  // 然后主程序可以监听 coordination 频道,当收到“开始编码”时,就知道可以继续了。
  // 这再次说明了设计一个清晰的智能体间协议是多么重要。
}

对于极其复杂的工作流,你可能需要结合 @parallax/relay 与专门的工作流引擎(如 Temporal、Camunda)或状态机库。Relay负责的是“智能体进程管理”和“消息路由”这两个底层且复杂的部分,而上层的业务逻辑编排,则可以由更擅长此道的工具来完成。

6. 常见问题、故障排查与性能优化

在实际使用中,你肯定会遇到各种问题。下面是我踩过坑后总结的一些常见情况和解决思路。

6.1 智能体启动失败或超时

问题现象 Relay.waitForAll agent.waitForReady 超时,控制台报错。

  • 可能原因1:CLI未安装或配置错误
    • 排查 :在终端手动运行 parallax --version ollama --version ,检查命令是否存在。运行 parallax config list 检查API密钥是否已正确配置。
    • 解决 :确保CLI全局安装,并正确配置认证信息。
  • 可能原因2:API密钥无效或额度不足
    • 排查 :手动使用CLI测试一个简单请求,例如 parallax chat -p openai -m gpt-4o "hello" ,看是否返回错误。
    • 解决 :更新有效的API密钥。
  • 可能原因3:网络问题或模型名称错误
    • 排查 :检查模型枚举 Models.XXX.YYY 是否与你CLI支持的模型匹配。对于自定义提供商,检查命令路径和参数。
    • 解决 :修正模型名,确保网络可以访问对应的AI服务。

避坑技巧 :在开发阶段,将 Relay logLevel 设为 'debug' 。这会输出非常详细的进程启动、消息收发日志,是定位启动问题的利器。

const relay = new Relay({ logLevel: 'debug' });

6.2 智能体无响应或消息丢失

问题现象 :广播了消息,但智能体没有反应,或者频道里看不到对话。

  • 可能原因1:智能体未成功订阅频道
    • 排查 :检查 spawn 配置中的 channels 数组,确保拼写一致。频道名是大小写敏感的。
    • 解决 :统一频道命名,建议使用常量定义。
  • 可能原因2:智能体的 task 指令过于模糊,导致AI不知道如何响应频道消息
    • 排查 :阅读该智能体的输出日志(如果有),看它是否收到了消息但回复了无关内容。
    • 解决 :在 task 中明确指令。例如:“你订阅了 ‘design’ 频道。当该频道有新消息时,将其视为设计需求并给出你的反馈。你的每次回复都直接发送到 ‘design’ 频道。”
  • 可能原因3:进程僵死或CLI有bug
    • 排查 :查看操作系统进程列表,看对应的CLI子进程是否还在运行。
    • 解决 :调用 await relay.shutdown() 后重新运行。考虑为智能体增加“心跳”机制,主程序定期发送 ping 消息,期待 pong 回复。

6.3 性能开销与资源管理

每个智能体都是一个常驻的进程,会消耗内存和CPU。

  • 控制智能体数量 :不要无节制地创建智能体。根据任务需要动态创建和销毁。对于完成单次任务就结束的智能体,确保在任务结束后调用 relay.shutdown()
  • 使用轻量级模型 :对于不需要顶尖性能的环节(如日志记录、简单分类),使用 Haiku GPT-4o Mini Gemini Flash 这类快速、廉价的模型。
  • 超时设置 :为 waitForReady , waitForAll 等操作设置合理的超时时间,避免程序因某个智能体卡住而无限等待。
  • 错误处理 :用 try...catch 包裹 spawn 和消息发送操作,做好错误恢复。一个智能体崩溃不应导致整个系统瘫痪。

6.4 调试与日志收集

调试多智能体系统比调试单线程代码复杂。

  • 为每个智能体启用独立日志 :如果自定义CLI,确保其可以将详细日志写入文件。你可以在 spawn 配置中通过 env args 传递日志文件路径。
  • 结构化消息 :让智能体在频道内发送结构化消息(如JSON字符串),便于主程序解析和判断状态。
  • 可视化工具(进阶) :可以考虑开发一个简单的Web面板,实时显示所有频道的最新消息和智能体状态,这对调试复杂工作流有巨大帮助。

6.5 成本控制

多个智能体同时调用付费API,成本可能快速增长。

  • 预算监控 :在AI服务商后台设置用量告警。
  • 本地模型优先 :对于不涉及核心机密且对效果要求不极致的任务,优先使用本地Ollama模型。
  • 任务设计 :避免让智能体进行开放式的长篇大论。通过 task 指令限制其输出格式和长度。例如,“请用不超过3个要点的列表形式回复”。

@parallax/relay 将一个复杂的多智能体系统工程问题,简化成了一个清晰的编程模型。它可能不是所有场景下的银弹,但对于需要快速组合多个AI能力、构建自动化协作流程的开发者来说,它是一个极具生产力的工具。从简单的“写作-编辑”二人组,到复杂的“分析-设计-实现-测试”流水线,你都可以基于它快速搭建原型并迭代。

我个人在实际使用中发现,最大的挑战不在于工具本身,而在于如何清晰地定义每个智能体的边界和它们之间的协作协议。把这部分设计好,代码写起来就非常顺畅。开始不妨从两个智能体的简单场景入手,熟悉频道通信和生命周期管理,然后再逐步增加复杂度。

更多推荐