尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

多Agent协同架构实战:从通信协议到编排引擎

多Agent协同架构实战:从通信协议到编排引擎 1. 架构研究的起点为什么需要“代理代为交互”先说个我观察到的现象现在很多团队做AI应用最常用的形态还是“单用户单对话框单模型”。你问一句模型答一句偶尔接个工具调用完事。但一旦场景升级成“多个人同时在用AI干活而且每个AI代理之间还要彼此协作”事情就完全不一样了。我见过不少团队在这个阶段卡壳核心原因不是模型能力不够而是架构上根本没有设计“代理与代理之间如何打交道”这层机制。这个课题要解决的说白了就是三件事一是让AI代理具备代替用户去完成某类任务的能力二是让多个AI代理之间能自主分工、协商和配合三是把这套“多人多AI”的局面从代码层面组织成一个不混乱的系统。这里的AI代理不是单纯指某个大模型API而是具备“感知—决策—执行—反馈”闭环能力的智能体它有记忆、能调用工具、能拆解目标、能观察环境反馈。而“代为交互”强调的是代理站在用户侧替用户去对接其他系统、其他代理而不是用户自己挨个去切换。这个选题适合谁看两类人。一类是后端架构师或技术负责人正在规划企业内部的多Agent应用平台需要一套参考蓝图另一类是有一定编程基础、想从“单Agent开发”迈向“多Agent协同”的工程师。我会从架构设计、核心模块、实操实现、踩坑记录这几个维度展开全程带入我自己的经验和取舍逻辑不带一句废话。为什么现在讨论这个问题正当时因为单Agent的工具调用、RAG、记忆管理这些技术已经相对成熟了而多Agent协同恰恰是下一步要啃的硬骨头。协同不是把多个Agent丢在一起开会——它们会互相刷屏、任务重复、上下文漂移甚至互相等死锁。真正可行的路径是像微服务架构治理微服务一样把每个AI代理当作一个独立的服务单元再引入通信协议、注册发现、编排调度、状态同步、冲突仲裁这些基础设施让AI代理在受控的轨道上协作而不是放任自由发挥。2. 整体架构设计的核心思路与选型逻辑2.1 分层架构把“代理”当作有独立生命周期的服务单元我在设计这套架构时第一步就把系统划分成了四个基础层次接入层、代理交互层、协同编排层、模型适配层。这不是为了套分层模板而是每层解决一个独立的痛点拆开之后各自演进不会互相拖垮。接入层面向真实用户负责会话管理、身份认证、权限校验以及把用户意图转换成标准化的任务描述。这里的关键问题是用户不是直接面对某个模型而是面对一个“代理工作台”。用户发出的指令会被接入层按意图拆解分发给合适的代理去执行。用户不需要关心背后是哪个模型、哪套工具链只关心结果。代理交互层是这套架构的中枢神经。每个AI代理在这里被封装成独立的运行单元具备自己的上下文窗口、工具集、记忆库和执行策略。代理与代理之间不直接建立点对点连接而是通过一个“代理通信总线”来交换消息。为什么不能直接连因为一旦代理数量超过三个点对点连接的复杂度会指数爆炸而且你很难对消息流做监控、过滤和优先级控制。参考微服务里面用消息队列解耦服务间调用代理交互层做的就是同一件事。协同编排层负责回答“谁来做、做什么、按什么顺序做”。这块我单独说因为它是整个架构里最容易被低估的部分。很多人觉得多个Agent只要共享一个提示词模板就能协作实际上远远不够——你必须设计任务拆解规则、执行状态机、异常重试策略、结果聚合逻辑以及代理之间对同一目标的共识机制。模型适配层相对纯粹就是把不同厂商的模型统一封装成标准接口。无论是本地部署的开源模型还是云端的商业API在这一层都表现为相同的数据进出格式。这样上层代理就不需要感知模型差异也方便后续替换、降级、路由。2.2 事件驱动与请求-响应的取舍协同场景需要的是“异步总线”而非“同步调用”选型时我反复权衡过一个问题多代理之间的交互应该走传统的同步请求-响应还是事件驱动的异步消息先给结论核心链路必须走异步事件驱动。原因是协同场景中Agent任务的执行时长极不稳定。比如一个“进行竞品调研并产出报告”的任务内部可能包含检索网页、调用数据库、推理总结、排版生成多个子步骤短则几十秒长则几分钟。如果代理之间用同步阻塞调用任何一个环节变慢都会拖垮整条链路而且很难实现“多个代理并行推进各自子任务”的效果。事件驱动架构有个明显的好处消息发送方不关心接收方当前是否在线、是否忙碌。发出事件之后代理可以继续处理自己的事务或者等待下一个触发条件。系统层面通过消息总线保证事件不丢失、可回溯正好契合代理“感知—决策—执行”这个循环本身的需要——代理的本质就是在持续地感知事件、做出决策。但完全抛弃同步请求-响应也不现实。比如用户登录鉴权、获取代理目录列表、查询某个任务的执行结果这类场景数据实时性要求高、语义上天然是“我问你答”。所以我的做法是混合架构管控面走HTTP同步接口数据面走事件驱动消息总线。这就像一座大楼里既有电梯直达的快速通道也有四通八达的地下管道各走各的互不干扰。2.3 开源项目参考从Clawdbot与ROS的集成熟路中借鉴经验做架构不能闭门造车。我重点参考了热词里提到的openclawros这类Agent与机器人系统集成的项目思路。这个方向的项目用了一个很聪明的做法把AI代理的能力边界限制在“决策规划层”而把具体的物理操作交给ROS这类成熟框架去执行。代理负责理解意图、拆解任务、判断下一步动作ROS负责底层的运动控制、传感器数据采集、环境交互。AI代理不直接操作硬件而是通过标准消息接口向ROS发布指令、订阅状态。这套理念放在多AI协同场景里同样成立。我们不需要让一个Agent直接去调用另一个Agent的内部方法而是让Agent之间交换“标准化的意图消息”和“结构化的事件”具体的执行细节由接收方自己决定。这样既解耦了代理之间的实现依赖又保留了对存量系统的兼容能力。这套交互模式我后面在实操部分会给出消息格式的具体设计。3. 核心模块拆解与关键机制详解3.1 代理通信协议消息即契约多代理协同的第一步是定义一套谁都能理解、谁都能解析的消息格式。我把代理之间的通信内容规约成一个统一的“协作消息”结构核心字段包括message_id全局唯一消息ID用于追踪和幂等sender与receiver发送方与接收方标识。这里支持单播与广播两种模式后者在任务通知场景下很有用task_id任务ID标识这条消息属于哪个任务上下文message_type消息类型包括task_request任务请求、task_response任务结果、status_update状态更新、negotiation协商消息、error异常信息payload具体的业务数据通常是一个JSON对象按不同消息类型包含不同字段timestamp时间戳用于排序和延迟分析这个设计参考了传统企业服务总线中的消息契约思想但做了大幅简化。代理不是人不需要冗余的客套话它只需要足够的信息来理解“谁在什么时候、基于什么任务、向我提了什么要求”。同时task_id字段极其重要——它是串起整个协同流程的线索没有它消息之间就是孤立的碎片你很难追踪一条任务从拆解到完成的全链路。3.2 协同编排引擎从“散兵游勇”到“调度有序”编排层是我认为整个系统里最有技术含量的部分。它要做的事情是接收代理注册上来的能力声明在任务来临时快速判断哪些代理能胜任再把任务拆成可并行的子任务分配给不同代理执行最后回收结果、汇总输出。任务拆解的策略我倾向用“目标—计划—执行”三级模型。一个高层目标比如“整理一份关于可穿戴设备市场趋势的分析报告”编排引擎先拆解为“市场数据收集”“竞品分析”“趋势洞察”“报告撰写”四个子任务。每个子任务再匹配相应的代理能力。比如“市场数据收集”分配给有搜索工具和数据库访问权限的数据代理“报告撰写”分配给擅长长文生成的写作代理。这里的关键设计是能力注册表。每个代理在启动时主动向编排中心注册自己的能力描述用结构化的JSON格式声明自己“能做什么、依赖什么工具、有哪些限制”。编排引擎在任务分配时依据注册表做匹配而不是硬编码地写死某个任务必须由某个代理执行。这样当新代理接入时不需要改动编排逻辑只需注册能力系统自动获得新的调度选项。异常处理也是编排引擎的核心职责。代理可能卡死、超时、返回异常结果。我的经验是给每个子任务设置合理的超时阈值超时后自动触发降级或重试。如果任务本身允许部分失败则优先返回已有部分结果而不是无限等待所有子任务完成后再输出这对用户体验影响巨大。3.3 状态同步与共享记忆让所有代理“看同一块黑板”多Agent协同最隐蔽的坑是上下文不一致。代理A和代理B各自维护一套私有记忆结果A认为某个结论已经达成B却毫不知情就会产生重复劳动甚至互相矛盾的结果。解决这个问题的思路叫“黑板架构”。系统维护一个共享记忆层存放任务级的全局状态、中间结论、共识结果。每个代理在执行过程中既从黑板读取必要信息也在关键节点向黑板写入自己的输出。这个共享层我用的是内存态存储加持久化缓冲的组合快节奏的状态直接用Redis这类高速缓存需要长期追溯的决策记录则落到文档数据库。消息顺序问题是另一个隐患。不同代理向黑板写入信息的时间不同后来的写入可能覆盖先前的有效信息。我的处理方式是增加“版本号决策记录链”机制每条全局信息都带版本信息后写者检查版本冲突如果发现基于旧版本的写入就触发冲突仲裁流程而不是直接覆盖。这一点与分布式系统中的乐观锁思想如出一辙。3.4 冲突检测与共识仲裁多人多AI一定会遇到意见分歧多Agent协同中有一个无法回避的问题两个代理对同一件事有不同结论怎么办比如一个代理认为应该优先压缩成本另一个代理认为应该优先保证交付质量。如果系统不处理冲突最终结果就会自相矛盾。我的方案是把冲突分为两大类。第一类是事实冲突即两个代理基于同一组数据得出了矛盾结论这通常说明至少一方存在上下文缺失或推理错误解决方式是引入“裁判代理”或让双方向共享记忆层重新同步信息后再次执行。第二类是偏好冲突即双方看的都是真实信息但目标优先级不同这时需要共识仲裁机制。仲裁机制我设计得比较轻量。系统维护一个“可仲裁事项注册表”当冲突产生时注册一个仲裁任务仲裁代理通常是一个具备综合判断能力的独立代理或者直接调用主模型根据用户预设的目标权重和当前任务上下文输出裁定结果。这个机制的灵感来自区块链里的共识思路但做了大幅简化——我们需要的不是拜占庭容错级别的可靠性而是“多数情况下能快速收敛”的实用效果。4. 实操实现从架构图到可运行的核心代码4.1 技术栈选型与模块划分我实际搭建这套原型用的是以下技术组合原则是“用最熟悉的主流组件搭出最小可用闭环”消息总线RabbitMQ理由是社区成熟、部署简单、支持多种消息模型且有现成的延迟队列插件。单机吞吐对Agent场景完全够用。如果预期量级很大可以替换为Kafka但Kafka在复杂路由和优先级上不如RabbitMQ灵活。代理运行时Python FastAPI。每个代理是一个独立的服务进程暴露出健康检查接口和元信息接口。FastAPI的异步特性比较适合代理场景中大量的I/O等待。共享记忆层Redis。存储全局状态、锁、短期记忆。长期记忆落到PostgreSQL或向量数据库我用的是后者方便代理做语义检索。编排中心单独一个Python服务消费消息总线上的任务事件执行拆解与调度逻辑。代理SDK一个轻量Python库封装消息发送接收、状态上报、工具调用的公共逻辑避免每个代理重复造轮子。4.2 代理通信协议的代码实现样例先给出协作消息的JSON结构这个是所有代理交互的地基。我建议直接把这个结构作为SDK中的基础类# agent_message.py from typing import Any, Optional import uuid import json from datetime import datetime, timezone class AgentMessage: def __init__( self, sender: str, receiver: Optional[str], task_id: str, message_type: str, payload: dict[str, Any], ): self.message_id str(uuid.uuid4()) self.sender sender self.receiver receiver # None 表示广播 self.task_id task_id self.message_type message_type self.payload payload self.timestamp datetime.now(timezone.utc).isoformat() def to_json(self) - str: return json.dumps({ message_id: self.message_id, sender: self.sender, receiver: self.receiver, task_id: self.task_id, message_type: self.message_type, payload: self.payload, timestamp: self.timestamp }) classmethod def from_json(cls, raw: str) - AgentMessage: data json.loads(raw) return cls( senderdata[sender], receiverdata.get(receiver), task_iddata[task_id], message_typedata[message_type], payloaddata[payload] )这段代码本身很简单但有两个设计细节值得说明。第一message_type字段我用的是有限枚举而非自由文本。因为代理需要根据消息类型走不同的处理分支自由文本会造成解析不稳定这也是我在实际项目中踩过的坑——早期让代理自己描述消息类型结果有的写“ask”有的写“request”有的写“请帮我一下”解析逻辑越写越恶心。第二from_json这里重新生成了message_id吗为了保证追踪一致性我在类方法里直接复用传入数据里的message_id和timestamp更合理大家在实际写的时候记得保留原始值。4.3 Agent注册与发现的实现代理服务启动后的第一件事就是向编排中心注册能力。这段逻辑是通用的所以一般放在SDK的启动器里# registry_client.py import httpx class AgentRegistry: def __init__(self, registry_url: str): self.registry_url registry_url async def register( self, agent_id: str, name: str, capabilities: list[dict], endpoint: str, ): payload { agent_id: agent_id, name: name, capabilities: capabilities, # [{name: web_search, params: {...}}] endpoint: endpoint, status: online } async with httpx.AsyncClient(timeout10) as client: resp await client.post( f{self.registry_url}/agents/register, jsonpayload ) resp.raise_for_status()这里要注意capabilities必须写清楚输入参数和输出格式。比如web_search能力要注明接收query和max_results参数返回[{title, url, snippet}]结构。因为编排中心在任务分配时不仅要看代理“能不能干这活”还要决定“以什么参数调用”这个契约不清晰整个链路就跑不通。编排中心收到注册请求后会做三件事把代理加入到可用代理列表给它建一个专属的任务队列并向所有其他代理广播一条“新成员上线”的事件。广播这个动作很多人会忽略但它实际上很重要——代理们需要知道团队里有谁才能正确地发出协作请求。4.4 任务分发与协同执行的伪代码演示下面这段是编排中心的核心逻辑我把关键部分用清晰注释说明# orchestrator.py import asyncio from typing import Optional import json from agent_message import AgentMessage class Orchestrator: def __init__(self, bus, registry, memory): self.bus bus self.registry registry self.memory memory # Redis / 向量库 async def process_task(self, task: dict): # 1. 解析目标 goal task[goal] task_id task[task_id] # 2. 拆解子任务实际生产中这一步会交给一个具备规划能力的代理 # 并且辅以外部的任务规划提示词模板 subtasks self.decompose(goal, task.get(context, {})) # 3. 为每个子任务匹配可用代理 assignments {} for st in subtasks: agent_id self.match_agent(st[required_capability]) if not agent_id: raise RuntimeError(fNo capable agent for {st}) assignments[st[id]] agent_id # 4. 向代理发送任务消息 msg AgentMessage( senderorchestrator, receiveragent_id, task_idtask_id, message_typetask_request, payload{ subtask_id: st[id], description: st[description], inputs: st[inputs], context_key: task_id # 代理可以从共享记忆读取全局上下文 } ) self.bus.publish(agent.tasks, msg.to_json()) # 5. 等待结果聚合超时控制 results await self.collect_results(task_id, len(subtasks)) # 6. 聚合输出 return self.aggregate(task_id, results) async def collect_results(self, task_id, expected_count): # 实际项目中用 Redis stream 保存每个子任务的状态 # 这里简化成等待事件集。 results {} timeout 120 # 秒 elapsed 0 while len(results) expected_count and elapsed timeout: pending await self.get_pending_results(task_id) for r in pending: results[r[subtask_id]] r await asyncio.sleep(1) elapsed 1 return results有一处细节要强调collect_results里的超时时间不是拍脑袋定的。它会参考该任务历史上所有子任务的平均执行时长再乘以一个冗余系数动态计算。比如历史平均值是30秒那么超时设45秒如果有子任务曾达到过90秒就把基础值上调到90秒再乘以1.2。动态超时比固定超时在真实场景里表现好得多因为不同类型的子任务耗时差异可能一个天上一个地下。还有一个我踩过的坑不要只在编排中心做超时控制代理侧同样要维护自己的执行超时。因为很多模型调用如果不加超时会无限期挂起。我用的是信号量加异步任务的组合方式给每个内部调用挂一个独立超时任务到点强制回收并上报一个error类型消息给编排中心。4.5 冲突检测的轻量实现冲突检测模块我只做一个很简化的版本对写入共享记忆的每个信息算一个语义哈希如果某个代理基于旧版本信息提交了新的写入而读库中存在更新版本的信息则触发冲突仲裁。具体代码如下# conflict_detector.py class ConflictDetector: def __init__(self, memory): self.memory memory async def check_before_write(self, task_id: str, writer: str, new_version: int): current await self.memory.get(fmemory:{task_id}:version) if new_version current: return { conflict: True, reason: version_stale, current: current, attempted: new_version } return {conflict: False} async def arbitrate(self, task_id: str, conflict_info: dict): # 实际实现会触发仲裁代理来做决策 # 这里返回一个默认策略以当前版本为基准让写入方重新同步 return { action: resync, expected_version: conflict_info[current] }这里用的版本号机制类比到实际生活中就像团队项目管理里“文档版本”的概念——你改文档之前必须先看看是不是最新版不然提交的修改会被驳回。在多个AI代理同时操作共享数据时这个机制有效避免了“我没看到你的修改就覆盖了”的灾难场景。更严肃的项目可以升级到向量空间中的偏向调和但版本号已经能满足绝大多数场景。5. 常见问题与排查技巧实录5.1 消息风暴代理之间对话刷屏怎么办现象一次任务下发后代理之间互相扩散了数十条协商消息有的话题根本和主任务无关消息总量暴增系统吞吐被打满。原因一般有两个。一是任务拆分的语义粒度不合理一个本可以一步完成的子任务被拆得太细导致每个节点都要发一轮消息确认消息量成倍增长。二是缺少消息的“主题隔离”机制——协商消息、状态消息、任务消息全部混在同一个代理消息队列里消息之间互相放大关注度。排查方法在消息总线旁路加一个采样监控组件按task_id聚合统计消息数量和时间线分布看哪个环节消息量陡增。解决方案为不同类型消息配置独立的主题或路由键同时在编排引擎中引入“协商收敛阈值”——当一个子任务连续协商超过N次仍未达成一致就自动升级到仲裁流程而不是让代理们无限讨论下去。5.2 上下文漂移代理忘了最初的目标越执行越偏现象任务启动时目标明确执行到第三步时某个代理的输出开始偏离原方向甚至去做了别的任务。这个问题的根源在于长链路执行中传递的信息损耗。每个代理在接收任务时都带着提示词和上下文但经过多个代理的传递最初的约束条件被稀释了。我的解决措施有两层。第一在编排中心为每个任务生成一个“任务契约”里面写明目标、约束条件、可接受结果的标准这条契约不仅在任务开始时下发一次还会在每个子任务完成后由编排中心校验是否仍与全局目标一致不一致就暂停该分支并触发修正。第二在共享记忆层维护“全局目标快照”在每个代理的关键动作前做一次相似度比对。这一步我用的是嵌入向量的余弦距离阈值设为0.8低于这个值就认为上下文漂移了。5.3 死锁两个代理都在等对方先行动多Agent协同系统的死锁问题比传统并发系统更难发现因为阻塞不是发生在锁上而是发生在“语义层面”。代理A在等代理B提供的数据代理B在等代理A批准下一步行动双方谁都不愿意先让步整个任务卡死。传统的超时机制能检测到卡死但没法解决根本问题。我的方案是引入“中央心跳任务看门狗”编排中心每10秒检查一次所有活跃任务的推进状态如果一个任务在某个子节点上停留超过预设阈值编排中心主动介入而不是等代理自己解决。介入方式可以是强制降级——跳过该子任务把缺失项标记为“待补充”也可以是指派第三方代理用于打破僵局。无论哪种都不会让整个任务悬挂。5.4 模型成本失控多Agent系统的另一个现实问题是大模型调用费用增长很快。因为代理之间的每一次“思考”都会调用模型而且为了可靠涉及到重试、仲裁时调用次数更多。我的实践是给代理调用加一层“成本预算”开关。每个任务在创建时设定模型调用预算上限。代理在执行子任务前预估本次调用的token消耗如果剩余预算不足以支撑就自动选择更小的模型或退回缓存结果。这个机制虽然会影响一丝精度但换来了成本的可控性。说实话在真实项目里成本失控往往会比功能问题更快地杀死一个项目。5.5 问题排查速查表症状可能原因排查手段常用解决方案代理间消息过多任务拆得过细、消息未分流按task_id统计消息量收紧拆分粒度、按类型分流消息、协商收敛阈值结果偏离原目标上下文漂移检查全局目标快照的相似度任务契约校验、触发修正机制任务长时间无输出代理间语义死锁查看任务时间线中央心跳看门狗强制介入重复执行相同操作状态同步缺失检查共享记忆中的版本记录引入版本号程序化幂等模型费用飙升重试和仲裁调用过多增加成本度量日志设置预算上限、自动降级模型新代理上线不生效注册信息未广播查看代理目录的更新时间强制广播“新成员上线”事件6. 从原型到落地的几个进阶扩展方向当这套最小闭环跑通之后我发现有几个方向是很有意思的扩展点。一个是代理市场与动态能力路由。现阶段代理的注册和发现还是编排中心集中管理的如果团队规模起来代理数量多了集中式编排中心本身可能成为瓶颈。下一步可以把编排中心改造成“代理网关”模式代理服务的注册、发现、路由策略全部下沉到网关层编排中心只负责任务语义解析不关心具体分发细节。这样编排中心从“交通警察”变成“调度大脑”压力会小很多。另一个方向是记忆分层策略。现在我把记忆全部放在Redis里但真实的协同任务中不同阶段需要不同的记忆粒度。任务刚开始时需要快速检索核心目标与约束执行中途需要关键的中间结论结束后需要沉淀复盘信息。我设想的方案是短期记忆引擎负责当前任务快照中期记忆做结果缓存长期记忆落到向量库供语义检索。三级记忆之间通过异步事件保持最终一致。还有一个不得不提的方向是可观测性。多Agent系统比单模型调用复杂得多光靠日志很难定位问题。我在项目中给每条协作消息都加了链路ID用类似OpenTelemetry的思路串联跨代理的调用链。代理内部的动作比如“调用了什么工具”“读写了哪块记忆”“模型输入输出长度”都打点上报。这套可观测体系跑起来之后排查效率直线提升强烈建议大家不管项目多小都预留这块能力。7. 一些真实的踩坑心得写到最后说几个我在实际项目中反复被教育出来的教训。第一不要对AI代理的“自主性”抱有幻想。很多做过单Agent的人觉得只要把多个Agent放在一起给一句“你们协作完成目标”它们就会自行组织得像一个团队。实测下来不是这样。如果不加约束代理之间要么过度客气导致效率极低要么各执己见导致迟迟无法推进。架构师的职责不是“放任自由”而是划定轨道、定义边界、兜住底线让代理在可控范围内发挥创造力。第二通信协议定义得越早越好而且要有版本兜底。我第一版协议没留版本号后面想加字段时发现线上已跑的老代理不能解析新消息只能停机升级。后来学乖了消息头里固定加protocol_version字段并兼容上一版本。第三编排中心要设计成无状态服务。第一版我把编排状态直接放在进程内结果编排中心一重启所有进行中的任务全乱了。改成把状态全部放到Redis之后随便重启都没问题。这一点和传统微服务的要求一模一样——但AI项目的状态更复杂因为包含的不只是任务进度还有每个代理当前的“认知状态”这些都要一并持久化。第四测试要分层做。单元测试测的是代理内部逻辑集成测试测的是两个代理能否正确交换消息端到端测试测的是完整任务链路。很多人跨过了前两层直接做端到端出了问题根本定位不到是哪一环的沟通体问题。我踩过最大的一个坑是写了一个看似完美的多Agent演示结果崩溃在一次JSON解析错误上——那个错误其实通过一个简单的集成测试就能在十分钟内发现。但当时的代码整了半天才用链路追踪定位到消息序列化问题。实践这些思路时建议先用一个需求相对窄、任务链条短的场景做试点比如“自动收集项目进展并生成周报”跑通后再扩展到更复杂的协同场景。架构这东西纸上谈兵总觉得很完备真到跑起来才会发现各种隐藏的边界情况。这是一条值得持续投入的方向。
返回列表