在实际分布式 AI 应用开发中一个常见的困境是如何将多个独立的 AI 模型或智能体Agent高效地组织起来让它们既能协同工作又能灵活部署在不同的计算节点上同时开发者还能方便地编写、调试和监控整个流程。传统的做法往往需要开发者自行搭建复杂的消息队列、服务发现和远程过程调用RPC框架这不仅引入了沉重的运维负担也使得开发体验变得割裂——编写逻辑在一个环境调试和观察在另一个环境。FEDERaiDE 正是为了解决这一系列痛点而出现的工具。它本质上是一个集成了点对点P2P多智能体路由能力的终端用户界面TUI框架并且内置了一个轻量级的集成开发环境IDE。你可以把它理解为一个专为多智能体系统设计的“脚手架”和“控制台”的结合体。它允许开发者在一个统一的 TUI 界面中定义多个智能体配置它们之间的通信路由实时观察消息流并直接编写或修改智能体的逻辑代码而无需在多个终端、日志文件和代码编辑器之间来回切换。这对于构建需要多个 AI 模型协作的复杂应用如智能客服编排、自动化工作流、多模型决策系统等能显著提升开发效率和系统可观测性。本文将以一个实际的协作任务为例带你从零开始理解 FEDERaiDE 的核心概念搭建开发环境创建一个包含两个智能体一个负责文本总结一个负责情感分析的简单 P2P 网络并通过内置 IDE 进行交互和调试。你将学习到如何定义智能体、配置路由、在 TUI 中监控消息传递以及如何处理常见的连接和序列化问题。1. 理解 FEDERaiDE 的核心架构P2P 路由与 TUI-IDE 融合在深入代码之前必须厘清 FEDERaiDE 的几个核心概念这决定了你能否正确使用它。1.1 什么是 P2P 多智能体路由在多智能体系统中智能体Agent是执行特定任务如调用大语言模型 API、处理数据、决策的独立单元。P2PPeer-to-Peer路由意味着这些智能体之间可以直接通信而不必经过一个中心化的服务器进行消息中转。每个智能体既是消息的消费者也可能是消息的生产者即对等节点。FEDERaiDE 实现的 P2P 路由机制通常包含以下组件节点发现智能体启动时如何找到网络中的其他智能体。消息协议智能体间交换数据的格式常见如 JSON-RPC、自定义二进制协议等。路由逻辑决定一条消息从智能体 A 发送到智能体 B 的路径。在简单的全连接网络中可能就是直接发送在复杂拓扑中可能需要经过中间智能体转发。这种架构的优势在于去中心化避免了单点故障并且理论上可以降低通信延迟如果节点间网络状况良好。但同时也带来了挑战比如节点动态加入/离开的处理、消息的可靠送达、循环消息的预防等。1.2 TUI 与内置 IDE 如何提升开发体验TUIText-based User Interface是基于文本终端的图形界面。与传统的命令行CLI只能接受单行命令不同TUI 可以提供窗口、面板、菜单、实时更新的列表等丰富的交互元素。FEDERaiDE 的 TUI 主要扮演两个角色系统仪表盘实时展示所有已注册智能体的状态在线/离线、当前负载、消息队列长度等。交互式控制台允许开发者向特定智能体发送测试消息手动触发路由或查看经过某个节点的消息历史。而“内置 IDE”并非指像 VS Code 或 PyCharm 那样的全功能编辑器。在 FEDERaiDE 的上下文中它更可能指的是一个嵌入式代码编辑与热重载环境。具体可能包括在 TUI 中直接打开并编辑某个智能体的业务逻辑代码文件如 Python 脚本。修改代码后能够通知对应的智能体进程重新加载热重载而无需重启整个系统。提供简单的语法高亮和错误提示。这种融合将“编码”、“部署”、“调试”和“监控”集中在同一个界面中实现了开发流程的闭环尤其适合快速原型开发和逻辑调试。1.3 FEDERaiDE 的典型工作流程理解以下流程有助于把握后续实操的脉络定义智能体为每个独立的任务单元创建一个智能体类实现其初始化、消息处理等方法。配置网络指定每个智能体运行的地址主机和端口以及它们之间允许的通信规则路由表。启动 Harness运行 FEDERaiDE 的主程序它会加载所有智能体配置启动 TUI 界面。TUI 监控与交互在 TUI 中观察智能体上线使用内置工具发送测试消息查看消息流。动态编辑与调试如果某个智能体的逻辑有问题可以直接在 TUI 的内置编辑器中修改其源代码并触发热重载实时观察行为变化。2. 环境准备与项目初始化在开始构建多智能体系统之前需要准备好 Python 环境并安装 FEDERaiDE。由于 FEDERaiDE 是一个相对较新的项目以下步骤基于其常见设计模式实际安装时请以官方文档为准。2.1 基础环境要求确保你的系统满足以下条件组件要求说明操作系统Linux, macOS, 或 Windows (WSL2 推荐)TUI 组件在原生 Windows 终端下可能有渲染问题WSL2 是最佳选择。Python3.8 或更高版本这是大多数现代 AI 框架和异步库的最低要求。包管理工具pip 20.0用于安装 Python 依赖。终端支持 256 色及以上的终端如 iTerm2, Windows Terminal, GNOME Terminal 等以保证 TUI 正常显示。首先创建一个独立的虚拟环境以避免依赖冲突# 创建项目目录并进入 mkdir federade_demo cd federade_demo # 创建 Python 虚拟环境 python3 -m venv venv # 激活虚拟环境 # Linux/macOS source venv/bin/activate # Windows (CMD或PowerShell) # venv\Scripts\activate2.2 安装 FEDERaiDE 及其核心依赖假设 FEDERaiDE 可通过 pip 安装。其核心依赖通常包括异步框架如asyncio或anyio用于处理智能体间的并发通信。TUI 库如textual,rich或prompt_toolkit用于构建终端界面。网络通信库如aiohttp,websockets或zeromq的 Python 绑定用于实现 P2P 通信。消息序列化如msgpack,orjson或pydantic用于高效地序列化消息。安装命令可能如下请替换为实际包名# 示例安装命令实际包名可能为 federade 或 federade-harness pip install federade # 通常还需要安装一些常用的 AI 库作为智能体后端例如 OpenAI SDK pip install openai如果遇到安装错误通常是网络问题或依赖冲突。可以尝试使用国内镜像源并确保pip和setuptools是最新版本pip install --upgrade pip setuptools wheel pip install federade -i https://pypi.tuna.tsinghua.edu.cn/simple2.3 验证安装与创建项目骨架安装完成后可以通过命令行工具验证是否成功并初始化一个示例项目。# 查看是否安装了命令行工具 federade --version # 或 python -m federade --help # 初始化一个新的多智能体项目 federade init my_multi_agent_project cd my_multi_agent_project初始化后的项目目录结构通常如下所示my_multi_agent_project/ ├── agents/ # 智能体模块目录 │ ├── __init__.py │ ├── summarizer.py # 示例总结智能体 │ └── sentimeter.py # 示例情感分析智能体 ├── configs/ # 配置文件目录 │ └── network.yaml # P2P 网络配置定义节点和路由 ├── main.py # 应用主入口启动 FEDERaiDE harness ├── requirements.txt # 项目依赖 └── README.md3. 构建你的第一个多智能体 P2P 网络现在我们来创建两个简单的智能体SummarizerAgent总结智能体和SentimentAgent情感智能体。它们将组成一个简单的链式工作流用户输入一段文本先由SummarizerAgent生成摘要然后将摘要发送给SentimentAgent分析情感倾向。3.1 定义智能体SummarizerAgent在agents/summarizer.py中我们定义一个智能体。一个典型的 FEDERaiDE 智能体类需要继承自基础 Agent 类并实现on_message或类似的消息处理方法。# agents/summarizer.py import asyncio import logging from typing import Any, Dict # 假设 FEDERaiDE 提供了 BaseAgent 基类 from federade import BaseAgent logger logging.getLogger(__name__) class SummarizerAgent(BaseAgent): 文本总结智能体。接收文本返回摘要。 def __init__(self, agent_id: str): super().__init__(agent_id) # 可以在这里初始化模型、API客户端等资源 # 例如self.client OpenAI(api_keyyour-key) # 为简化示例我们使用一个模拟的总结函数 self.summary_model mock-summarizer-v1 async def on_start(self): 智能体启动时调用。 logger.info(fAgent {self.agent_id} started with model {self.summary_model}.) async def on_message(self, message: Dict[str, Any]) - Dict[str, Any]: 处理收到的消息。 预期消息格式: {text: 长文本内容, request_id: xxx} 返回格式: {summary: 摘要文本, original_length: 100, request_id: xxx} logger.info(fSummarizerAgent received message: {message.get(text, )[:50]}...) # 1. 提取消息内容 text_to_summarize message.get(text, ) request_id message.get(request_id, unknown) if not text_to_summarize: error_msg Message missing text field. logger.error(error_msg) return {error: error_msg, request_id: request_id} # 2. 模拟调用总结模型实际项目中替换为真实的模型调用 # 这里简单取前100个字符作为“摘要” simulated_summary text_to_summarize[:100] ... if len(text_to_summarize) 100 else text_to_summarize # 模拟处理耗时 await asyncio.sleep(0.5) # 3. 构造响应消息 response { summary: simulated_summary, original_length: len(text_to_summarize), request_id: request_id, processed_by: self.agent_id } logger.info(fSummarizerAgent sending response for request {request_id}.) return response async def on_stop(self): 智能体停止时调用用于清理资源。 logger.info(fAgent {self.agent_id} is stopping.)关键点解释agent_id每个智能体的唯一标识符用于在路由中寻址。on_message这是智能体的核心方法。所有发送给该智能体的消息都会路由到此方法。它必须是异步的async以支持高并发。消息格式我们约定了一个简单的 JSON-like 字典格式。生产环境中应使用更严谨的协议如使用pydantic定义Request和Response模型。模拟处理为了示例清晰我们使用asyncio.sleep和字符串切片模拟 AI 处理。在实际集成中这里会调用如 OpenAI API、本地 Hugging Face 模型等。3.2 定义智能体SentimentAgent同理创建agents/sentimeter.py# agents/sentimeter.py import asyncio import logging from typing import Any, Dict from federade import BaseAgent logger logging.getLogger(__name__) class SentimentAgent(BaseAgent): 情感分析智能体。接收文本返回情感极性。 def __init__(self, agent_id: str): super().__init__(agent_id) # 模拟情感分析模型 self.sentiment_model mock-sentiment-v1 async def on_start(self): logger.info(fSentimentAgent {self.agent_id} started.) async def on_message(self, message: Dict[str, Any]) - Dict[str, Any]: 处理消息。 预期输入格式: {text: 待分析文本, request_id: xxx, ...} 返回格式: {sentiment: POSITIVE/NEGATIVE/NEUTRAL, confidence: 0.95, request_id: xxx} logger.info(fSentimentAgent received message for request {message.get(request_id)}.) text_to_analyze message.get(text, ) request_id message.get(request_id, unknown) if not text_to_analyze: return {error: No text to analyze., request_id: request_id} # 模拟情感分析逻辑简单根据关键词判断 positive_words [good, great, excellent, happy, positive] negative_words [bad, terrible, awful, sad, negative] text_lower text_to_analyze.lower() positive_score sum(word in text_lower for word in positive_words) negative_score sum(word in text_lower for word in negative_words) await asyncio.sleep(0.3) # 模拟处理时间 if positive_score negative_score: sentiment POSITIVE confidence min(0.5 positive_score * 0.1, 0.99) elif negative_score positive_score: sentiment NEGATIVE confidence min(0.5 negative_score * 0.1, 0.99) else: sentiment NEUTRAL confidence 0.5 response { sentiment: sentiment, confidence: round(confidence, 2), request_id: request_id, processed_by: self.agent_id } return response async def on_stop(self): logger.info(fSentimentAgent {self.agent_id} stopped.)3.3 配置 P2P 网络与路由智能体定义好后需要告诉 FEDERaiDE 如何运行它们以及它们之间如何通信。这通常在configs/network.yaml中配置。# configs/network.yaml federade: version: 1.0 # 日志配置 logging: level: INFO format: %(asctime)s - %(name)s - %(levelname)s - %(message)s # 定义所有智能体节点 agents: summarizer: class: agents.summarizer:SummarizerAgent # 模块导入路径 id: summarizer_01 # 智能体实例ID transport: type: tcp # 传输层协议也可以是 ws (WebSocket), zmq 等 host: 127.0.0.1 port: 8001 # 该智能体监听的端口 # 可以在这里传递初始化参数 # params: # model_name: gpt-4 sentiment: class: agents.sentimeter:SentimentAgent id: sentiment_01 transport: type: tcp host: 127.0.0.1 port: 8002 # 定义消息路由规则 routing: # 规则列表按顺序匹配 rules: # 规则1所有发送到 summarizer 的消息直接路由到 summarizer 智能体 - from: * # 来源可以是任意智能体或外部客户端 to: summarizer action: direct # 直接发送 # 规则2从 summarizer 发出的且包含 summary 字段的消息自动转发给 sentiment 智能体 - from: summarizer to: sentiment condition: has_field # 条件消息包含某个字段 condition_field: summary action: forward # 规则3从 sentiment 发出的消息默认路由回一个名为 client 的虚拟端点可用于TUI测试 - from: sentiment to: client action: direct # TUI 界面配置 tui: enabled: true refresh_interval: 1.0 # 状态刷新间隔秒 # 可以配置要显示的面板agents, messages, logs, editor panels: [agents, messages, logs] # 内置 IDE/编辑器配置 ide: enabled: true # 允许热重载的文件路径列表支持通配符 watch_files: [agents/*.py, configs/*.yaml] # 热重载延迟秒 reload_delay: 2.0配置详解agents声明了系统中所有的智能体。每个智能体需要指定其 Python 类的位置、唯一 ID 以及网络传输配置监听的地址和端口。这就是 P2P 的基础——每个智能体都是一个独立的网络服务。routing这是 P2P 路由的核心。规则定义了消息的流向。from和to指定消息的源和目的地智能体 ID。*是通配符。condition可选只有满足条件的消息才会被路由。例如has_field检查消息中是否存在某个字段。actiondirect表示直接发送到目标forward表示将接收到的消息转发给下一个目标。tui和ide控制 FEDERaiDE 用户界面的行为。watch_files是关键它列出了被监视的文件当这些文件被修改并保存后FEDERaiDE 会自动重新加载对应的智能体实现热重载。3.4 编写主程序入口最后我们需要一个主程序来加载配置并启动整个 FEDERaiDE 系统。创建main.py# main.py import asyncio import logging from pathlib import Path from federade import FederadeHarness async def main(): 启动 FEDERaiDE 主程序。 # 设置日志方便查看运行过程 logging.basicConfig( levellogging.INFO, format%(asctime)s - %(name)s - %(levelname)s - %(message)s ) # 配置文件路径 config_path Path(__file__).parent / configs / network.yaml # 创建并启动 Harness harness FederadeHarness(config_pathconfig_path) try: # 启动所有智能体并运行 TUI await harness.run() except KeyboardInterrupt: # 捕获 CtrlC优雅关闭 logging.info(Shutdown signal received.) finally: await harness.cleanup() logging.info(FEDERaiDE harness stopped.) if __name__ __main__: asyncio.run(main())4. 运行、验证与 TUI 交互一切就绪现在可以启动系统并观察 P2P 多智能体网络是如何工作的。4.1 启动系统在项目根目录下运行python main.py如果一切正常你的终端会清空并显示 FEDERaiDE 的 TUI 界面。界面可能会被分成几个面板例如左侧面板显示所有智能体列表及其状态绿色为在线红色为离线。中间面板显示实时消息流可以看到summarizer_01和sentiment_01之间流转的消息。底部面板显示系统日志。顶部或侧边栏可能有菜单栏提供发送测试消息、打开编辑器等选项。4.2 通过 TUI 发送测试消息在 TUI 界面中通常可以通过快捷键如CtrlN或菜单打开一个“发送消息”的对话框。按照界面提示选择目标智能体Target Agent例如summarizer。输入消息内容。需要符合我们代码中约定的格式一个 JSON 字符串{text: The product is really good and works great. Im very happy with the purchase, although the delivery was a bit slow., request_id: test_001}发送消息。观察消息流面板你应该能看到类似以下的事件序列[INFO] Client - summarizer_01: {text: ..., request_id: test_001} [INFO] summarizer_01: Processing request test_001. [INFO] summarizer_01 - sentiment_01: {summary: The product is really good and works great. Im very happy with the purchase, althou..., request_id: test_001, ...} [INFO] sentiment_01: Processing request test_001. [INFO] sentiment_01 - client: {sentiment: POSITIVE, confidence: 0.7, request_id: test_001, ...}这直观地展示了 P2P 路由规则在起作用消息从客户端到总结器再根据规则自动转发到情感分析器最后结果返回给客户端。4.3 使用内置 IDE 进行热修改假设我们发现情感分析过于简单想修改SentimentAgent的逻辑。无需停止程序在 TUI 界面中找到“编辑器”或“文件”菜单快捷键可能是CtrlE。导航并打开agents/sentimeter.py文件。TUI 内嵌的编辑器会加载该文件。修改情感分析逻辑。例如将判断逻辑改为基于文本长度仅为示例# 修改 on_message 方法中的分析逻辑 # 原有关键词判断代码注释掉或删除 # positive_words [...] # ... (原判断逻辑) # 改为基于文本长度的简单“情感” text_length len(text_to_analyze) await asyncio.sleep(0.3) if text_length 100: sentiment POSITIVE_LONG confidence 0.8 elif text_length 50: sentiment NEUTRAL_MEDIUM confidence 0.6 else: sentiment NEGATIVE_SHORT confidence 0.7保存文件CtrlS。由于我们在配置中设置了ide.watch_files: [agents/*.py]FEDERaiDE 会检测到文件变化。等待几秒由reload_delay控制你可能会在日志面板看到[INFO] File change detected: agents/sentimeter.py [INFO] Reloading agent sentiment_01... [INFO] Agent sentiment_01 reloaded successfully.此时SentimentAgent已经加载了新的代码。再次通过 TUI 发送相同的测试消息观察输出。你会发现情感结果从POSITIVE变成了POSITIVE_LONG因为我们的示例文本长度超过100字符。这验证了热重载功能。5. 常见问题排查与调试指南在实际使用中你可能会遇到各种问题。以下是基于 FEDERaiDE 架构的典型排查路径。5.1 智能体启动失败现象TUI 中某个智能体状态一直显示为“离线”红色或日志中报错Failed to start agent X。可能原因与排查步骤端口冲突检查network.yaml中配置的端口如 8001, 8002是否已被其他程序占用。检查命令netstat -an | grep LISTEN | grep 8001(Linux/macOS) 或netstat -ano | findstr :8001(Windows)。解决修改配置文件中冲突的端口号。Python 类导入错误配置中class路径写错或智能体代码存在语法错误。检查查看启动时的完整日志。错误通常会显示ModuleNotFoundError或ImportError。解决确保class路径格式为模块路径:类名且模块在 Python 路径中。可以手动在 Python 解释器中尝试导入from agents.summarizer import SummarizerAgent。依赖缺失智能体__init__方法中引用了未安装的第三方库。检查日志中会有ModuleNotFoundError。解决在虚拟环境中安装缺失的包或修改智能体代码。5.2 消息路由失败或丢失现象消息发送后没有到达目标智能体也没有后续处理日志。排查步骤检查路由规则确认network.yaml中的routing.rules是否正确。特别是from、to和condition是否与消息的发送者和内容匹配。检查消息格式发送的消息必须是一个字典且能被序列化为 JSON。如果包含 Python 特定对象如datetime需要先转换为字符串或数字。查看 TUI 消息流面板这是最直接的调试工具。确认消息是否从“客户端”发出是否被某个智能体接收。如果消息卡在某个环节可能是该智能体的on_message方法抛出了未处理的异常。检查智能体日志在智能体的on_message方法开始处添加详细的日志记录确认方法是否被调用以及传入的参数是什么。网络连通性在 P2P 模式下确保智能体配置的host如127.0.0.1是可访问的。如果智能体分布在不同的机器需要检查防火墙和网络策略。5.3 热重载不生效现象修改了agents/下的文件并保存但智能体行为没有改变。排查步骤确认监视配置检查configs/network.yaml中的ide.watch_files模式是否覆盖了你修改的文件。检查文件权限确保 FEDERaiDE 进程有权限读取你修改的文件。查看重载日志在日志面板中搜索reload或file change关键词看是否有相关记录。如果没有说明文件变动未被检测到。手动触发重载某些 TUI 界面提供手动重载智能体的命令如快捷键CtrlR尝试使用它。重启 Harness如果热重载机制有问题最直接的方式是停止 (CtrlC) 并重新启动python main.py。5.4 TUI 界面显示异常或卡顿现象界面乱码、刷新缓慢或对键盘输入无响应。可能原因终端兼容性确保你使用的是支持现代 TUI 的终端并尝试调整终端字体或颜色设置。日志洪水如果智能体产生大量日志输出可能会拖慢 TUI 渲染。尝试将日志级别调整为WARNING。资源占用复杂的路由或高频率消息可能消耗较多 CPU。在 TUI 中观察智能体的状态看是否有某个智能体处理消息特别慢形成了瓶颈。6. 生产环境部署与最佳实践将基于 FEDERaiDE 开发的原型部署到生产环境需要考虑更多因素。6.1 配置管理外置化开发环境的配置如 API 密钥、模型路径、数据库连接不应硬编码在代码中。建议使用环境变量或外部配置文件如.env文件来管理敏感信息和环境差异。在智能体的__init__方法中通过os.getenv()或配置库来读取。示例import os from openai import OpenAI class SummarizerAgent(BaseAgent): def __init__(self, agent_id: str): super().__init__(agent_id) api_key os.getenv(OPENAI_API_KEY) if not api_key: raise ValueError(OPENAI_API_KEY environment variable is not set.) self.client OpenAI(api_keyapi_key)6.2 增强智能体的健壮性生产环境的智能体必须有完善的错误处理和资源管理。异常捕获在on_message内部用try...except包裹核心逻辑避免单个消息处理失败导致整个智能体崩溃。超时机制对于调用外部 API 或耗时操作设置超时。重试逻辑对于可重试的临时性错误如网络抖动加入指数退避的重试机制。资源清理在on_stop方法中确保关闭网络连接、释放模型资源等。async def on_message(self, message: Dict[str, Any]) - Dict[str, Any]: request_id message.get(request_id, unknown) try: # 设置处理超时 async with asyncio.timeout(30.0): result await self._call_external_api(message[text]) return {result: result, request_id: request_id} except asyncio.TimeoutError: logger.error(fRequest {request_id} timed out.) return {error: Processing timeout, request_id: request_id} except Exception as e: logger.exception(fRequest {request_id} failed with error: {e}) # 根据错误类型决定是否可重试 return {error: fInternal agent error: {str(e)}, request_id: request_id, retryable: False}6.3 监控与可观测性FEDERaiDE 的 TUI 适合开发调试生产环境需要更强大的监控。结构化日志将日志输出为 JSON 格式便于被 ELK、Loki 等日志系统收集和查询。记录关键指标如消息处理延迟、成功率。指标暴露在每个智能体中集成指标收集如使用prometheus_client暴露如messages_processed_total、processing_duration_seconds等指标。健康检查端点为每个智能体实现一个简单的 HTTP 健康检查端点如果基于 TCP/HTTP 传输供负载均衡器或编排系统如 Kubernetes探测。6.4 安全考虑身份验证与授权在 P2P 通信中确保只有经过授权的智能体才能相互通信。可以在传输层加入 TLS 加密和基于令牌的认证。输入验证与清理智能体接收的消息可能来自不可信的来源。务必验证消息格式和内容防止注入攻击。限制资源使用对单个消息的处理时间、内存使用做出限制防止恶意或错误消息耗尽资源。6.5 部署模式选择FEDERaiDE 的 P2P 架构灵活但生产部署可以根据场景选择不同模式单机多进程所有智能体运行在同一台机器上通过本地回环地址通信。适合中小型应用。容器化部署推荐将每个智能体打包成独立的 Docker 容器使用 Docker Compose 或 Kubernetes 编排。这需要将配置中的host从127.0.0.1改为容器名或服务名。混合部署将计算密集的智能体如大模型部署在 GPU 服务器上将轻量级逻辑智能体部署在普通服务器上通过内部网络进行 P2P 通信。FEDERaiDE 通过将 P2P 多智能体路由与 TUI-IDE 深度集成为分布式 AI 应用的开发、调试和监控提供了一种新颖且高效的一体化体验。它降低了构建复杂智能体工作流的门槛尤其适合快速迭代和原型验证。然而将其用于生产环境时必须围绕配置管理、错误处理、监控和安全进行加固。从本例中的简单双智能体工作流出发你可以进一步探索更复杂的路由模式如广播、条件分支、聚合、集成真实的 AI 模型服务并利用其热重载特性实现业务的快速迭代。