1. 项目概述这不是一个“调度器”而是一套智能协作操作系统Relay 多智能体协作平台这个名字听起来像某种网络中继设备但实际它解决的是当前AI工程落地中最棘手的一类问题单个大模型能力再强也难以独立完成复杂、多步骤、跨工具、需人类反馈闭环的真实任务。比如让AI帮你订一次差旅——它得先理解你的日程偏好查航班与酒店实时库存比价生成预订链接再把确认信息整理成邮件发给你最后还要在你回复“改期”后自动触发重订流程。这个过程里没有哪个单一模型能稳稳扛下全部环节语言理解模型不擅长调用API代码生成模型不懂业务规则决策模型又缺乏实时数据感知能力。Relay 的核心价值就藏在这个“拆解-分派-协同-收敛”的闭环里它不替代模型而是让不同专长的智能体像一支训练有素的特种小队各司其职彼此交接任务、共享上下文、协商分歧最终交付一个完整结果。我第一次在某高校实验室看到 Relay 的 Demo 时它正在处理一份模糊的采购需求“找三款适合户外徒步、预算在2000元以内、带GPS和心率监测的运动手表要对比续航和防水等级并生成购买建议。”整个流程耗时47秒。背后是5个智能体在协同一个负责精准解析用户意图并结构化约束条件一个专门爬取主流电商平台的实时商品页与参数表一个调用第三方数据库校验防水等级IP68/10ATM与官方定义的对应关系一个执行多维度加权比价算法最后一个则整合所有数据生成带图表的自然语言报告。它们之间传递的不是原始文本而是带Schema的JSON对象每个字段都有明确的数据类型、来源可信度标记和更新时间戳。这种设计直接绕开了传统RAG或Chain-of-Thought中常见的“幻觉传导”陷阱——A模型编造的参数不会被B模型当作事实继续推理。对开发者而言Relay 不是另一个需要从零搭建的LLM服务框架它的定位更接近“智能体OS”。你不需要重写每个智能体的底层推理逻辑而是聚焦于定义它们的“角色说明书”它能调用哪些工具、接收什么格式的输入、输出必须满足哪些Schema约束、失败时应如何降级。平台本身负责路由、状态追踪、超时熔断、上下文快照与回滚。这意味着一个刚接触Agent开发的工程师花半天时间就能把公司内部的CRM查询接口、财务报销审批流、钉钉消息推送服务封装成三个可被调度的智能体并在一个简单的采购审批流程中串联起来。它降低的不是技术门槛而是将AI能力嵌入业务毛细血管的成本。如果你正被“模型很厉害但落不了地”这个问题困扰Relay 提供的不是答案而是一套可复用的协作协议与基础设施。2. 架构设计与核心思路拆解为什么是“协作协议”而非“调度中心”Relay 的架构选择本质上是对当前多智能体系统两大主流路径的反思与折中。一类是“中央大脑”式架构比如某些基于LangChain的复杂Chain设计所有决策、状态维护、工具调用都由一个主模型驱动其他模块只是它的“手脚”。这种模式调试简单但存在致命瓶颈主模型一旦出错或超时整个流程就卡死且随着任务复杂度上升主模型的Prompt会膨胀到无法维护上下文窗口成为硬伤。另一类是“完全去中心化”架构智能体之间通过消息总线自由通信类似早期的Multi-Agent SystemsMAS研究强调自治性与涌现行为。这在学术上很美但在工程实践中极易失控——你永远不知道哪个智能体会在第几轮突然发起一个意料之外的子任务日志追踪变成噩梦SLA保障更是空谈。Relay 选择了第三条路协议驱动的分层协作架构。它把整个系统拆成三层每一层解决一类问题且层与层之间通过明确定义的协议交互而非隐式依赖。2.1 协议层定义“智能体能说什么、听什么、承诺什么”这是 Relay 最具区分度的设计。它不强制要求智能体使用特定模型或框架而是定义了一套轻量级的JSON-RPC 2.0扩展协议。每个智能体启动时必须向Relay注册一个capability.json文件其中包含name: 智能体唯一标识符如weather-fetcher-v2description: 自然语言描述其职责供系统自动理解与路由input_schema: JSON Schema严格定义它能接收的输入结构。例如一个航班查询智能体的输入必须包含departure_airport字符串、arrival_airport字符串、dateISO8601日期格式和max_results整数范围1-10。output_schema: 同样用JSON Schema定义输出且支持required字段与examples示例确保下游智能体能可靠解析。tools: 它被授权调用的外部服务列表如[{name: flight_api, version: v3}]由Relay统一鉴权与限流。这个设计带来的直接好处是可验证性。Relay 在任务分派前会用jsonschema库对输入数据进行预校验在收到响应后再次校验输出是否符合约定。任何不符合Schema的请求或响应都会被立即拦截并返回清晰的错误码如INPUT_SCHEMA_VIOLATION而不是让错误数据流入下游引发连锁崩溃。我曾在一个金融风控场景中部署过类似架构当市场行情突变导致某个价格预测智能体返回了非数值型的N/A字符串时协议层的校验立刻捕获了该异常并触发预设的降级策略——调用历史均值作为兜底避免了整个信贷审批流程的中断。2.2 协调层做“交通警察”不做“全能司机”协调层是Relay的核心引擎但它刻意保持“无知”。它不理解航班查询的具体业务逻辑也不关心天气预报的算法原理。它的全部知识就是一张动态的“智能体能力图谱”和一套状态机。能力图谱基于所有已注册智能体的capability.json构建一个倒排索引。当一个新任务到来例如{task: plan_hiking_trip, constraints: {location: Yosemite, date: 2024-06-15}}协调层首先解析task字段匹配到hiking-trip-planner智能体然后检查其input_schema发现它需要location和date当前输入已满足于是将其加入待执行队列。如果输入缺失budget字段协调层会主动向用户发起澄清询问而不是让智能体自己瞎猜。状态机每个任务实例在协调层内被抽象为一个有限状态机FSM状态包括PENDING等待资源、RUNNING智能体执行中、WAITING_FOR_INPUT需人工介入、FAILED不可恢复错误、SUCCEEDED成功完成。关键在于状态转换由协议层的事件驱动当智能体发送{jsonrpc: 2.0, method: task_complete, params: {...}}时协调层才将状态从RUNNING切为SUCCEEDED。这种解耦使得协调层可以轻松替换——今天用内存状态机做POC明天就能无缝切换到基于Redis Stream的分布式状态机而所有智能体无需任何修改。2.3 执行层智能体的“沙盒”与“保险丝”执行层负责智能体的实际运行环境。Relay 默认提供两种模式in-process同进程内调用适合开发调试和http通过HTTP Webhook调用生产推荐。无论哪种Relay 都会在调用前注入一个context对象其中包含task_id: 全局唯一任务ID用于全链路追踪parent_task_id: 如果是子任务指向父任务IDdeadline: 该智能体的绝对超时时间戳UTC更重要的是Relay 为每个HTTP调用设置了严格的熔断策略。它不是简单地设置一个timeout30s而是采用“双阈值”机制soft_timeout如15秒触发告警与日志记录hard_timeout如30秒则强制终止HTTP连接并将任务状态置为FAILED。这避免了某个慢速智能体拖垮整个集群。我在一个电商大促场景中实测过当物流查询智能体因第三方API抖动而响应缓慢时Relay 的hard_timeout在30秒后准时切断连接协调层立即启动备用方案——调用缓存中的昨日物流状态并标注“数据可能非实时”保证了用户页面的可用性而没有让用户无限等待。这种分层设计让Relay既规避了“中央大脑”的脆弱性又避免了“完全去中心化”的不可控性。它用协议的刚性换取了系统的韧性用协调层的“有限认知”换来了架构的可演进性。当你在规划自己的多智能体系统时不妨先问一句我的“协议”是否足够清晰我的“协调”是否足够无感我的“执行”是否足够隔离3. 核心功能解析与实操要点从零部署一个可工作的协作流Relay 的核心功能并非堆砌炫技而是围绕“让协作可预期、可调试、可治理”这一目标展开。下面以一个真实复现过的场景为例构建一个“客户投诉自动升级处理”流程。该流程需依次完成1从邮件中提取投诉关键词与紧急程度2查询CRM获取客户历史等级与最近三次投诉记录3根据规则判断是否需升级至VIP专线4若需升级则自动创建工单并通知负责人。整个过程必须在5分钟内完成且每一步都可审计、可回放。3.1 环境准备与最小化部署Relay 的部署极其轻量官方推荐Docker Compose方式整个核心服务仅需一个relay-core容器与一个PostgreSQL数据库。以下是经过我们实测优化的docker-compose.yml关键片段version: 3.8 services: relay-core: image: relayplatform/core:latest ports: - 8000:8000 environment: - RELAY_DB_URLpostgresql://relay:relaypassdb:5432/relay - RELAY_JWT_SECRETyour_very_strong_jwt_secret_here # 必须更换 - RELAY_LOG_LEVELINFO - RELAY_TASK_TIMEOUT_SECONDS300 # 全局任务超时5分钟 depends_on: - db restart: unless-stopped db: image: postgres:15-alpine environment: - POSTGRES_DBrelay - POSTGRES_USERrelay - POSTGRES_PASSWORDrelaypass volumes: - relay-db-data:/var/lib/postgresql/data restart: unless-stopped volumes: relay-db-data:提示首次部署时务必修改RELAY_JWT_SECRET。这是一个用于签发内部API Token的密钥长度建议不少于32位随机字符。我们曾因使用默认值导致测试环境Token被轻易破解所有智能体调用接口暴露。生产环境还应配置RELAY_RATE_LIMIT_PER_MINUTE100限制单IP调用频次。启动后访问http://localhost:8000/docs即可看到OpenAPI文档。Relay 的管理API设计得非常务实没有花哨的前端所有操作均可通过curl完成。例如注册第一个智能体邮件解析器的命令如下curl -X POST http://localhost:8000/v1/agents \ -H Content-Type: application/json \ -d { name: email-parser, description: Extracts complaint keywords and urgency level from raw email text., input_schema: { type: object, properties: { raw_email: {type: string} }, required: [raw_email] }, output_schema: { type: object, properties: { keywords: {type: array, items: {type: string}}, urgency_score: {type: number, minimum: 0, maximum: 10}, summary: {type: string} }, required: [keywords, urgency_score, summary] }, endpoint: http://host.docker.internal:8001/process, auth_type: none }注意endpoint字段中的host.docker.internal。这是Docker Desktop提供的特殊DNS用于容器内访问宿主机服务。如果你在Linux服务器上部署需替换为宿主机的真实IP或在docker-compose.yml中添加extra_hosts配置。这个细节看似微小却让很多新手卡在第一步——智能体注册成功但调用时始终报Connection refused。3.2 智能体开发用最简代码实现协议合规Relay 对智能体的实现语言没有任何限制只要它能接收HTTP POST请求并返回符合协议的JSON即可。我们用Python Flask写了一个极简的email-parser示例重点展示如何满足协议层的强制要求from flask import Flask, request, jsonify import re import json app Flask(__name__) # 预定义的关键词库与权重模拟业务规则 URGENCY_KEYWORDS { crash: 8, down: 7, unavailable: 6, broken: 5, slow: 3, lag: 3, delay: 2 } app.route(/process, methods[POST]) def process_email(): try: # 1. 解析输入严格校验必填字段 data request.get_json() if not data or raw_email not in data: return jsonify({ jsonrpc: 2.0, error: {code: -32600, message: Invalid Request: missing raw_email}, id: None }), 400 raw_text data[raw_email] # 2. 执行业务逻辑此处简化为正则匹配 keywords_found [] urgency_score 0 for keyword, weight in URGENCY_KEYWORDS.items(): if re.search(rf\b{keyword}\b, raw_text.lower()): keywords_found.append(keyword) urgency_score weight # 3. 构建严格符合output_schema的响应 response { jsonrpc: 2.0, result: { keywords: keywords_found, urgency_score: min(urgency_score, 10), # 封顶 summary: fDetected {len(keywords_found)} urgency keywords. }, id: data.get(id, unknown) } return jsonify(response) except Exception as e: # 4. 任何未预期异常返回标准错误格式 error_response { jsonrpc: 2.0, error: {code: -32000, message: fInternal Error: {str(e)}}, id: data.get(id, unknown) } return jsonify(error_response), 500 if __name__ __main__: app.run(host0.0.0.0:8001, debugFalse) # 关闭debug生产环境必需这个例子的关键在于它把协议合规性变成了代码逻辑的一部分而非外部配置。result字段的结构、id的透传、错误码的规范都在函数体内硬编码实现。这样做的好处是当智能体逻辑变更时开发者必须显式地思考“我的输出是否还符合Schema”而不是依赖一个外部的、可能过时的配置文件。我们在某公司的实际项目中曾将这套模式推广为团队规范要求所有新接入的智能体必须通过一个本地脚本进行output_schema校验该脚本会用jsonschema.validate()对示例输出进行测试CI流水线中失败则禁止合并。3.3 协作流编排用YAML定义“谁在何时做什么”Relay 使用一种简洁的YAML DSL来定义协作流名为workflow.yaml。它不追求图灵完备只解决“顺序、分支、聚合”这三类基本控制流。以下是我们为投诉升级流程编写的完整定义# workflow.yaml name: complaint-escalation-flow description: Automatically escalate high-urgency customer complaints to VIP line. # 输入参数定义即整个流程的入口契约 input_schema: type: object properties: raw_email: {type: string} required: [raw_email] # 执行步骤序列 steps: # 步骤1调用邮件解析器 - id: parse_email agent: email-parser input: raw_email: {{ .input.raw_email }} # 使用Go模板语法引用输入 # 步骤2调用CRM查询器假设已注册为crm-lookup - id: lookup_crm agent: crm-lookup input: customer_id: {{ .steps.parse_email.result.customer_id }} # 引用上一步输出 # 步骤3规则判断内置的条件节点 - id: should_escalate type: condition condition: {{ .steps.lookup_crm.result.urgency_score 7 .steps.lookup_crm.result.customer_tier VIP }} then: - id: create_ticket agent: ticket-creator input: summary: URGENT: {{ .steps.parse_email.result.summary }} priority: P0 else: - id: send_standard_reply agent: reply-sender input: template: standard_ack # 输出定义即整个流程的出口契约 output_schema: type: object properties: status: {type: string, enum: [escalated, acknowledged]} ticket_id: {type: [string, null]} required: [status]这个YAML的精妙之处在于其数据流的显式性。每一个{{ ... }}都是一个确定的、可静态分析的数据路径。Relay 的编译器在加载此文件时会进行两项关键检查路径可达性检查{{ .steps.parse_email.result.customer_id }}中的customer_id字段是否真的存在于email-parser的output_schema中如果不是编译直接失败提示Field customer_id not found in agent email-parser output schema。类型兼容性检查ticket-creator智能体的input_schema中priority字段是否定义为enum: [P0, P1, P2]如果是那么P0字面量就通过校验如果其定义为type: number则此处会报错。这种在编译期就捕获错误的能力极大提升了协作流的健壮性。我们曾在一个涉及12个智能体的供应链预测流程中仅靠YAML编译检查就提前发现了7处数据类型不匹配的隐患避免了上线后因数据格式错误导致的批量工单创建失败。3.4 监控与可观测性不只是看日志而是看“协作健康度”Relay 内置了一套面向协作的监控体系其指标设计直指多智能体系统的痛点。除了常规的CPU、内存、HTTP QPS外它提供了三个核心业务指标agent_call_success_rate{agentemail-parser}按智能体维度的成功率。当该值低于95%时说明该智能体自身存在问题如模型退化、API失效而非流程设计问题。step_latency_seconds_bucket{steplookup_crm, le5.0}按步骤维度的P95延迟。如果lookup_crm的P95延迟突然从1.2秒跳到8.5秒说明CRM查询服务出现瓶颈需要扩容或优化缓存。workflow_failure_reason{reasonCONTEXT_EXPIRED}按失败原因的分布。CONTEXT_EXPIRED表示某个步骤因超时被熔断SCHEMA_VIOLATION表示数据格式错误AGENT_UNAVAILABLE表示智能体服务宕机。这个指标能让你一眼看出系统的主要短板在哪。我们曾将这些Prometheus指标接入Grafana构建了一个“协作健康度大盘”。其中最关键的面板是一个热力图Heatmap横轴是时间过去24小时纵轴是所有协作流名称颜色深浅代表该流程在过去一小时内failure_rate。当某个格子突然变红运维人员无需翻日志直接点击该格子就能下钻看到是哪个步骤失败失败原因是什么影响了多少任务这种基于协作语义的监控让故障定位时间从平均47分钟缩短到不足3分钟。4. 实操过程与核心环节实现一个端到端的部署与调试图现在让我们把前面所有知识点串起来完成一次真实的端到端实操。目标部署一个能处理“查找附近咖啡馆”请求的协作流并亲手调试一次失败案例。整个过程分为四个阶段环境初始化、智能体注册、流程编排、故障注入与修复。4.1 阶段一环境初始化与基础验证首先确保Docker与Docker Compose已安装。执行docker-compose up -d启动Relay核心服务。等待约10秒后用curl验证服务健康# 检查服务是否就绪 curl -s http://localhost:8000/health | jq . # 预期输出: {status:ok,timestamp:2024-05-20T08:15:22Z} # 查看已注册的智能体初始为空 curl -s http://localhost:8000/v1/agents | jq .data # 预期输出: []注意jq是处理JSON的利器强烈建议安装。如果未安装可省略| jq .直接查看原始JSON。但后续调试中jq能让你快速过滤关键字段比如curl ... | jq .data[] | select(.nameemail-parser)。此时Relay 已就绪但尚无任何智能体。我们需要先启动一个最简单的“回声”智能体用于验证整个调用链路。创建一个echo-agent.pyfrom flask import Flask, request, jsonify app Flask(__name__) app.route(/echo, methods[POST]) def echo(): data request.get_json() # 严格遵循协议返回jsonrpc格式且result字段必须存在 return jsonify({ jsonrpc: 2.0, result: data, # 原样返回输入 id: data.get(id, echo-id) }) if __name__ __main__: app.run(host0.0.0.0:8002, port8002, debugFalse)在另一个终端中运行python echo-agent.py。然后注册这个智能体curl -X POST http://localhost:8000/v1/agents \ -H Content-Type: application/json \ -d { name: echo-test, description: A simple echo agent for testing., input_schema: {type: object}, output_schema: {type: object}, endpoint: http://host.docker.internal:8002/echo, auth_type: none }注册成功后再次调用curl http://localhost:8000/v1/agents应能看到echo-test出现在列表中。至此基础环境验证完成。4.2 阶段二注册真实智能体——地理编码与地点搜索“查找附近咖啡馆”需要两个核心能力1将用户输入的地址如“北京市朝阳区三里屯”转换为经纬度地理编码2以该经纬度为中心搜索半径500米内的咖啡馆。我们分别注册两个智能体。首先注册地理编码智能体geocoder。它将调用一个公开的免费API如OpenStreetMap Nominatim因此其input_schema需定义address字段output_schema需定义latitude和longitudecurl -X POST http://localhost:8000/v1/agents \ -H Content-Type: application/json \ -d { name: geocoder, description: Converts address string to latitude/longitude coordinates., input_schema: { type: object, properties: {address: {type: string}}, required: [address] }, output_schema: { type: object, properties: { latitude: {type: number}, longitude: {type: number}, display_name: {type: string} }, required: [latitude, longitude, display_name] }, endpoint: http://host.docker.internal:8003/geocode, auth_type: none }接着注册地点搜索智能体place-search。它将调用OpenStreetMap Overpass API其输入必须是latitude、longitude和radius输出是咖啡馆列表curl -X POST http://localhost:8000/v1/agents \ -H Content-Type: application/json \ -d { name: place-search, description: Searches for cafes within a given radius of coordinates., input_schema: { type: object, properties: { latitude: {type: number}, longitude: {type: number}, radius: {type: integer, minimum: 100, maximum: 5000} }, required: [latitude, longitude, radius] }, output_schema: { type: object, properties: { cafes: { type: array, items: { type: object, properties: { name: {type: string}, distance_meters: {type: number}, rating: {type: [number, null]} } } } }, required: [cafes] }, endpoint: http://host.docker.internal:8004/search, auth_type: none }注意radius字段的minimum和maximum约束是Relay协议层强制执行的。如果后续协作流中传入radius: 10000Relay会在调用place-search前就拒绝该任务并返回INPUT_SCHEMA_VIOLATION错误。这比让智能体自己去处理非法参数更安全、更高效。4.3 阶段三编写并部署协作流创建coffee-workflow.yaml文件定义完整的查找流程name: find-nearby-coffee description: Finds cafes near a given address. input_schema: type: object properties: address: {type: string} max_distance: {type: integer, minimum: 100, maximum: 5000} required: [address, max_distance] steps: - id: geocode agent: geocoder input: address: {{ .input.address }} - id: search_cafes agent: place-search input: latitude: {{ .steps.geocode.result.latitude }} longitude: {{ .steps.geocode.result.longitude }} radius: {{ .input.max_distance }} output_schema: type: object properties: cafes: {type: array, items: {type: object}} query_address: {type: string} required: [cafes, query_address]使用Relay CLI或直接调用API部署此流程# 假设已安装relay-cli或使用curl curl -X POST http://localhost:8000/v1/workflows \ -H Content-Type: application/yaml \ -d coffee-workflow.yaml部署成功后即可发起一次真实请求curl -X POST http://localhost:8000/v1/workflows/find-nearby-coffee/run \ -H Content-Type: application/json \ -d { address: Shanghai Jingan District, max_distance: 500 }如果一切顺利你将收到一个包含附近咖啡馆列表的JSON响应。这是成功的喜悦时刻但真正的价值往往在失败中显现。4.4 阶段四故障注入、排查与修复——一次真实的Debug之旅为了模拟真实世界我们故意制造一个故障将geocoder智能体的代码修改为在处理特定地址时返回一个缺少latitude字段的JSON# 修改 geocoder.py 的处理函数 app.route(/geocode, methods[POST]) def geocode(): data request.get_json() address data.get(address, ) # 故意制造一个Schema违规当地址包含error时不返回latitude if error in address.lower(): return jsonify({ jsonrpc: 2.0, result: { longitude: 121.4737, display_name: Error Test Location # 注意这里漏掉了 latitude 字段 }, id: data.get(id, err-id) }) # 正常逻辑... return jsonify({...})重启geocoder服务然后发起一个“故障请求”curl -X POST http://localhost:8000/v1/workflows/find-nearby-coffee/run \ -H Content-Type: application/json \ -d {address: Shanghai error test, max_distance: 500}你将收到一个清晰的错误响应{ jsonrpc: 2.0, error: { code: -32001, message: Step geocode output validation failed: latitude is a required property, data: { step_id: geocode, validation_errors: [latitude is a required property] } }, id: req-abc123 }这个错误信息的价值在于其精确性。它明确指出是哪个步骤geocode失败了失败原因是输出Schema校验不通过而非网络超时或500错误具体违反了哪条规则latitude is a required property。排查路径瞬间变得无比清晰1检查geocoder的output_schema定义确认latitude确为required2检查geocoder的代码找到那个故意遗漏latitude的分支3修复代码确保所有代码路径都返回完整的result对象。修复后再次请求流程恢复正常。这个过程就是Relay所倡导的“协议先行”开发范式的精髓错误不是在运行时随机爆发而是在数据流经协议层时被精准地、可预测地捕获。这种确定性是构建高可靠AI协作系统的基石。5. 常见问题与排查技巧实录那些文档里不会写的坑在多个项目中部署Relay我们积累了一套高频问题清单与独家排查技巧。这些问题往往不会出现在官方文档的“常见问题”章节里因为它们源于真实世界的复杂性与人的疏忽。以下是我亲自踩过、并反复验证有效的解决方案。5.1 问题智能体注册成功但调用时始终返回502 Bad Gateway现象curl http://localhost:8000/v1/agents能看到智能体但执行工作流时Relay日志显示Failed to call agent xxx: Get \http://host.docker.internal:8001/process\: dial tcp 192.168.65.2:8001: connect: connection refused。根本原因Docker网络隔离。host.docker.internal在Docker Desktop for Mac/Windows上有效但在Linux上默认不存在。更隐蔽的原因是智能体服务如Flask绑定在127.0.0.1:8001而Docker容器内的Relay无法访问宿主机的127.0.0.1它指向容器自身。独家排查技巧在Relay容器内直接测试连通性docker exec -it relay_container_id sh然后执行ping host.docker.internalMac/Win或ping 宿主机IPLinux。检查智能体服务的监听地址netstat -tuln | grep :8001确认它监听的是0.0.0.0:8001而非127.0.0.1:8001。Flask默认只监听127.0.0.1必须显式指定host0.0.0.0。终极方案Linux在docker-compose.yml中为relay-core服务添加network_mode: host使其直接使用宿主机网络。虽然牺牲了一点隔离性但能100%解决网络问题且对开发环境完全可接受。5.2 问题工作流执行缓慢但单个智能体测试很快现象单独调用geocoder耗时200ms单独调用place-search耗时300ms但整个工作流耗时却超过2秒。根本原因Relay的默认配置是串行执行。search_cafes步骤必须等geocode步骤完全返回后才开始。如果两个步骤逻辑上是独立的比如同时查询天气和交通这种串行就是性能瓶颈。
阅读完成 · 觉得有帮助?