[{"content":"使用 LangChain 构建多智能体工作流\r人工智能（AI）正飞速发展，2025 年最大的转变是向 **智能体系统（agentic systems）**迈进 —— 这类 AI 能够进行规划、协作，并跨多步骤执行任务。团队不再依赖单一模型处理所有事情，而是构建由专业智能体组成的网络，它们可以像真实工作流一样协同运作。\nLangChain 已成为这一转型中的关键框架。其生态系统让构建与运行稳定、可预测、可直接上线的 LangChain 多智能体变得更加简单。\n在本指南中，我们将讲解 LangChain 中多智能体工作流的运行原理、重要价值，以及你如何一步步从零开始构建。\nTL;DR 极简总结\r为什么多智能体很重要：将工作流拆分为专业智能体，相比单智能体系统，能提升准确性、稳定性与可扩展性。\nLangChain 如何提供帮助：提供核心构建模块、智能体、工具、记忆、LangGraph 与编排能力，用于设计结构化多智能体流程。\n这类系统成功的关键：清晰的角色、严格的格式规范、校验机制、规范的通信与持续测试，保证多智能体工作流可靠运行。\nEma 的定位：针对生产级自动化，Ema 在 LangChain 之上提供治理、集成、可观测性与结果导向的编排能力。\n什么是多智能体？\r我们所说的**多智能体系统**，是指多个由大语言模型（LLM）驱动的独立主体，为实现更宏大的目标协同工作。每个智能体都有自己的提示词、模型、工具与逻辑，并通过信息传递完成步骤间的协作。\n从本质上看，任何多智能体架构都由两个问题定义：\n有哪些智能体？ 它们如何连接？ 图结构是最直观的呈现方式。在 LangGraph 中，每个智能体是一个节点，边定义了信息与控制流如何在它们之间传递。智能体更新共享状态，流转规则决定下一个执行的智能体。它本质上是一个状态机：每个智能体代表一个状态，边决定工作流的走向。\n在理解高层概念后，下一步就是看它在 LangChain 内部如何实现。\n在 LangChain 中，“多智能体” 究竟意味着什么\r在 LangChain 中，多智能体工作流是一个协调系统，不同智能体负责流程中的不同环节。你不再让一个模型包办一切，而是创建一组专业化组件：\n智能体（Agent）：由 LLM 驱动的单元，接收指令、推理并执行动作。\n工具（Tool）：智能体可调用的外部函数，如 API、数据库、代码执行、搜索、Python 工具等。\n状态（State）：智能体完成任务后传递的动态数据。\n调度器（可选，Supervisor）：更高层级的智能体，决定下一个执行的智能体。\n图（通过 LangGraph）：结构化布局，展示智能体如何连接、何时运行。\n简单来说，你从 “一个大智能体包办一切”，转向 “一组聚焦、协同、模拟真实业务流程的智能体集合”。\n下面我们来看为什么这种方案对真实业务如此重要。\n多智能体工作流的价值，以及为什么 LangChain 处于领先地位\r单智能体系统适用于简单、线性的任务，但真实企业工作流很少符合这种模式。它们依赖多种工具、并行流程、校验层与严格合规要求。当一个智能体试图管理所有事情时，提示词会变得臃肿、上下文窗口溢出、工具调用不稳定。\n多智能体工作流通过给每个智能体明确、聚焦的职责解决这一问题：一个负责规划、一个负责检索、一个负责校验、一个负责执行。系统开始像协作团队一样运作，这也与现实组织的运行方式一致。\n企业已经切实感受到这一点：单一 LLM 在演示中效果很好，但在真实负载、集成、长上下文、治理要求下会力不从心。而多智能体设计提供了这些环境所需的结构与可靠性。\n为什么 LangChain 脱颖而出\rLangChain 成为多智能体系统的首选框架，因为它在灵活性与结构化之间取得了最佳平衡。开发者无需从零构建，就能协调多个智能体。\n如今大多数自主智能体团队都将 LangChain 作为核心编排层。2024 到 2025 年，其 GitHub Star 数与包下载量激增，Star 数已突破 12 万。许多团队将 LangChain 与向量数据库结合，为智能体提供长期记忆与更稳定的上下文管理。\n这些趋势印证了 LangChain 在构建可靠、可上线的多智能体系统方面的口碑。\n这些趋势反映了 LangChain 能够打造出可靠、可上线的多智能体系统的缘由。有了这样的背景信息，接下来让我们来看看 LangChain 是如何在内部构建这些智能体的。\n2025 年的 LangChain 智能体架构\r到 2025 年，LangChain 智能体架构已成熟为模块化、分层系统，每个智能体负责工作流中的特定环节：规划、执行、通信或评估。这种职责分离让多智能体系统更易扩展、调试与扩展。\n核心智能体类型\r规划智能体（Planner Agent）\n作为战略大脑，理解用户目标，拆分为小任务，确定执行顺序，选择调用的智能体或工具，并根据新上下文调整计划。\n执行智能体（Executor Agents）\n负责完成规划定义的实际工作，每个专注特定功能：\nRAG 智能体：检索文档 代码生成智能体：编写 / 修复脚本 翻译智能体：格式 / 语言转换 通信智能体（Communicator Agent）\n管理步骤间的交接，为下一个智能体重构输出格式，保留上下文，确保信息不丢失。\n评估智能体（Evaluator Agent）\n作为质量检查点，校验输出的准确性、相关性与一致性。异常时可将任务退回规划器或转发给其他执行器。\n所有智能体都运行在 LangChain 编排层下，管理共享状态、协调动作、支持并行执行与故障恢复。\n开发者可使用 LangChain 内置的 MultiAgentExecutor，或集成 CrewAI、OpenAI AutoGen 等运行时进行生产部署。\n接下来，让我们来看看这些智能体能够正常运转所依赖的实际组成部分。\n你实际会用到的 LangChain 组件\rLangChain 提供一套精简的构建模块，用于设计多智能体系统：\nLangGraph：图引擎，支持节点、边、重试、分支逻辑。将工作流建模为状态图，用于调度、路由与复杂编排。当您需要实现代理自主性但又不需要完整的图时，请使用它。 AgentExecutor：运行单个智能体的运行时，处理提示词、工具与结构化输出。 Tools：智能体可调用的确定性函数或集成：API、数据库、搜索、代码执行、文件生成、自定义连接器。工具使代理能够采取行动。 Memory：短期缓冲或基于向量的长期存储，让智能体跨步骤回忆上下文。 State management：显式状态传递（通过 LangGraph），每个步骤接收、更新、返回明确定义的字段，避免上下文覆盖。 Sandboxes：隔离环境，安全运行代码或外部进程，避免主系统风险。 这些组件组合在一起，让 LangChain 成为实用、对开发者友好的多智能体工作流平台，能够实现真正的多智能体工作流程。一旦您了解了核心组件，多智能体设置所带来的好处就会变得更加清晰。\n为什么要使用 LangChain 多智能体？\r随着任务复杂度提升，单智能体架构会逐渐失效。LangChain 让多智能体方案变得实用可靠，主要优势：\n专业化带来更高准确率：每个智能体只做一件事，结果更稳定。 并行任务提升性能：独立任务可同时运行，降低延迟、提高吞吐量。 更容易调试：出问题只需检查单个智能体，而非巨型提示词。 设计可扩展：增删改智能体无需重构整个系统。 治理更清晰：每个智能体可拥有独立规则、提示词、权限与安全约束，适合企业级使用。 更贴合真实业务：与团队协作模式一致，适用于客服、文档处理、研究、风险评估等场景。 先解决了这些优势问题，接下来让我们逐步详细讲解如何实际构建一个多智能体工作流。\n使用 LangChain 构建多 AI 智能体工作流步骤\r步骤 1：从问题出发，而非从模型出发\r先明确你想要达成的业务目标—— 例如更快的问题分诊、会议纪要、发票核验，或是任何当前耗时费力的工作。\n把工作流拆解为清晰步骤，例如：采集 → 清洗 → 分析 → 校验 → 执行\n每一步都可以成为一个独立的智能体。\n这样能让范围更务实，并确保每个智能体都对应真实业务价值。\n第二步：定义智能体角色、接口与成功标准\r为每个智能体明确以下内容：\n输入结构：它接收什么 输出结构：必须返回的精确格式 工具：允许调用的 API、数据库查询或函数 成功标准：校验规则或置信度阈值 清晰的契约可以避免不可预测的行为，让流程衔接更干净。\n第三步：选择控制模式 / 架构\r选择适配你工作流的编排方式：\n主管 / 编排器：最适合线性、可预测的业务流程 LangGraph / 状态图：适合分支逻辑、重试、审计与复杂流程 路由 + 专家智能体：适合将任务分发给多个专用智能体 共享草稿区：适用于协作式推理（谨慎使用） 控制模式决定了系统的可靠性、可观测性与可维护性。\n第四步：选择合适的模型与 LangChain 组件\r根据成本、速度与能力为每个智能体选择模型，再搭配 LangChain 基础组件：\nAgentExecutor：运行单个智能体 工具：确定性函数、API、沙箱、搜索、数据库 记忆：短期缓冲或基于向量的长期记忆 LangGraph：用于结构化多智能体路由 让组件与任务精准匹配，能让系统更稳定、更经济。\n第五步：用清晰的提示词、工具与校验构建智能体\r将每个智能体设计为专注、可测试的单元：\n编写简洁的系统提示词，并附带任务专属示例 输出严格的 JSON 或结构化格式 尽可能使用确定性工具 立即校验输出，不匹配则重试或快速失败 把提示词当作 API 来设计，保证工作流可预测。\n第六步：编排与串联整个系统\r单个智能体表现稳定后，用选定的架构将它们串联：\n在节点之间校验输入与输出 增加重试、降级路径与升级规则 为高风险决策增加可选的人工审核点 先用模拟数据做集成测试，再接入真实模型。\n第七步：增加可观测性、安全与治理能力\r为工作流接入日志与追踪，监控：\n提示词与输出 智能体决策 模型版本与 Token 消耗 工具调用与状态变化 实施企业级安全措施：如个人隐私信息脱敏、加密、角色权限控制、提示词版本管理。\n这些防护措施让系统具备生产环境可用的能力。\n第八步：试点、度量与优化\r先小规模试点，并监控：\n准确率与错误模式 延迟与吞吐量 Token 使用量与成本 通过优化提示词、缓存、并行执行、更新智能体角色来提升效果。\n第九步：安全部署与扩容\r选择运行环境：\n无服务器（Serverless）：维护成本低，适合流量波动大的场景 容器 + Kubernetes：可控性、可观测性更强，扩容更稳定 使用自动扩缩容、缓存、并行与熔断机制，永远保留回滚方案。\n理解这些步骤后，再去看团队在构建真实多智能体系统时最常用的架构模式会更有帮助。\n你可以搭建的常见多智能体架构\r不同工作流需要不同结构。LangChain 和 LangGraph 支持多种在真实系统中稳定可用的模式，以下是最常见的几种：\n每种模式都各有取舍，但它们都强化了同一个核心原则：结构至关重要。如果让智能体在没有明确规则的情况下自由交互，会导致行为不可预测。当你的架构与企业内部真实业务流程完全匹配时，系统会变得更加稳定，也更容易理解与推理。\n结构搭建完成后，下一步就是在系统不断扩展的过程中，保持工作流的可靠性。\n多智能体工作流设计最佳实践\r多智能体系统功能强大，但只有在设计严谨规范时才能保持稳定可靠。以下实践在基于 LangChain 和 LangGraph 搭建的各类生产级工作流中均被反复验证有效：\n保持角色精简聚焦：让每个智能体只承担一项职责。专业化能提升准确率、减少幻觉，并大幅降低调试难度。\n谨慎控制上下文：只向智能体提供其必需的信息。精简的输入能让提示更清晰、降低成本、提升一致性。\n使用严格的结构化输出格式：为每个智能体定义 JSON 或基于 Schema 的输出。清晰的格式能让下游流程交接可预测，消除歧义。\n优先使用确定性工具：当智能体调用 API 或外部函数时，优先选择输出稳定、可复现的工具。确定性工具是工作流的稳定锚点，能减少结果波动。\n增加防护机制与故障安全措施：使用超时、重试、备用模型，以及可选的人工审核步骤 —— 尤其在高风险或关键任务中。\n避免过度碎片化：智能体数量过多会引入噪声与额外开销。少数几个定义清晰的智能体，通常优于几十个职责重叠的智能体。\n监控每一次智能体决策：追踪提示词、输出结果、工具调用与状态流转。可视性对调试、评估和企业治理至关重要。\n综合来看，这些实践指明了多智能体系统的发展方向，也解释了它们为何正成为企业自动化的核心。\n多智能体系统与企业自动化的未来\r向多智能体工作流的转变并非一时风潮。它反映了真实企业的实际运作方式：公司不会依赖单个人处理所有事务，而是依靠有着明确分工、交接流程与责任体系的团队。多智能体系统遵循同样的模式，这也是它们正成为企业自动化核心的原因。\nLangChain 非常适合探索这种思路，尤其在你试验智能体角色、规划逻辑或小型内部工作流时。但当需求扩展到以下场景时：\n可靠的编排能力\n与现有系统集成\n企业级治理能力\n可预测的执行结果\n可规模化的运营\n你就需要一个为生产环境而生的平台。\n核心总结\r多智能体系统正在重塑企业复杂工作的自动化方式。LangChain 是体验多智能体的优秀起点，但要将这类工作流投入生产，仅靠精巧提示词远远不够。你需要稳定的编排、治理能力、深度集成，以及专为真实业务场景打造的平台。\n常见问题（FAQ）\r什么是多智能体 AI 系统？\n多智能体系统由多个专业 AI 智能体协同完成任务。每个智能体专注单一角色，让整体工作流更精准、高效、易于扩展。\nLangChain 如何用于构建多智能体工作流？\nLangChain 提供智能体、工具、记忆与链状组件，支持搭建并串联多个大模型驱动的执行步骤，管理推理、工具调用与流程交接，让智能体顺畅协作。\n开发者为何要在 LangChain 中使用 LangGraph？\nLangGraph 增加了基于图的控制层，定义智能体之间的连接方式与状态流转逻辑，让复杂或分支型工作流更可预测、易调试，更适合生产环境。\n什么是 LangChain 智能体？\nLangChain 智能体是基于大语言模型的组件，可在工作流中自主推理、调用工具并执行操作。每个智能体都有专属提示、工具与适配特定任务的逻辑。\nLangChain 智能体有哪些应用案例？\n常见场景包括研究助手、代码生成智能体、文档处理工具、入驻引导机器人、多步骤客服系统等。任何需要 “推理 + 执行” 的任务都适合采用智能体架构。\n搭建多智能体系统必须使用 GPT‑4、Claude 3 等高级模型吗？\n并非必须。很多智能体在 GPT‑3.5、Mistral、Claude Sonnet 这类更快、更经济的模型上就能良好运行。可将高级模型用于高推理需求任务，常规步骤使用轻量模型，以此控制成本。\n参考来源：https://www.ema.co/additional-blogs/addition-blogs/multi-agent-workflows-langchain-langgraph\n","date":"2026-02-03T00:00:00Z","image":"/p/%E4%BD%BF%E7%94%A8-langchain-%E6%9E%84%E5%BB%BA%E5%A4%9A%E6%99%BA%E8%83%BD%E4%BD%93%E5%B7%A5%E4%BD%9C%E6%B5%81/images/252Fdbd1b23601318794f079b6a6d19a3ef13257f871.png","permalink":"/p/%E4%BD%BF%E7%94%A8-langchain-%E6%9E%84%E5%BB%BA%E5%A4%9A%E6%99%BA%E8%83%BD%E4%BD%93%E5%B7%A5%E4%BD%9C%E6%B5%81/","title":"使用 LangChain 构建多智能体工作流"},{"content":"主流开源 Rerank 模型解析与选型指南（2026 版）\r​\t在检索增强生成（RAG）场景中，Rerank（重排序）是决定检索精度的关键环节。核心作用是对向量检索、关键词检索等初步召回的候选文档进行精细排序，筛选出与查询最相关的结果，以提升检索精度。本文聚焦 6 款主流开源 Rerank 模型，从技术特性、性能表现、部署成本、适用场景等核心维度展开深度解析，结合 2025 年最新评测数据输出可落地的选型建议，助力开发者搭建高效的检索系统。\n1 核心概念与模型速览\r1.1 Rerank 核心价值\r在检索增强生成（RAG）的流程中，初步召回阶段（如向量检索）追求“全”，核心是尽可能网罗所有潜在相关文档，避免遗漏有价值的信息，这一阶段的结果精度通常不高。进入 Rerank 阶段后，核心诉求转向 “准”：通过深度建模查询与文档的语义关联，精准捕捉二者间的匹配逻辑，修正初步召回的排序偏差。一款优秀的 Rerank 模型，能在不显著增加系统延迟的前提下，将 MRR、NDCG 等核心相关性指标提升 20%-50%，是平衡检索效率与结果精度的关键核心组件。\n另外，Rerank 的核心价值还体现在 “降本增效” 上：一方面，通过精准筛选剔除无关文档，可大幅减少后续 LLM 推理的输入文本量，降低 Token 消耗与推理延迟；另一方面，能有效避免无关信息对 LLM 的干扰，帮助模型更聚焦核心需求，进而提升生成内容的准确性与相关性。\n1.2 当前主流开源 Rerank 模型速览（2025-12 更新）\r模型名称 研发机构 发布时间 参数量 上下文长度 授权协议 核心亮点 Qwen3-Reranker-4B 阿里通义 2025-06 4B 32k Apache 2.0 MMTEB-R 评分 72.74（高精度），支持32K长文本，中英文均衡适配，适合企业级高精度RAG mxbai-rerank-large-v2 MixedBread 2025-07 1.5B 8k Apache 2.0 BEIR 18项任务零样本SOTA，完美适配德/英/西/法等多语言场景，泛化性强 bge-reranker-v2-m3 BAAI 2024-03 567M 8k Apache 2.0 中文社区主流选择，量化后体积\u0026lt;200MB，中英文混合场景表现突出，部署成本低 jina-reranker-v3 Jina AI 202510 0.6B 默认8k CC-BY-NC-4.0（商用需授权） 支持100+语言，专为Agentic-RAG场景微调，listwise交叉交互机制，适配多轮检索决策 ms-marco-MiniLM-L6-v2 Microsoft 2021 22M 512 MIT 超轻量架构，CPU/边缘端部署首选，英文通用场景标杆模型，推理速度快、集成成本低 ColBERT v2 CMU/Stanford 2021-12 110M 原生512 MIT 预编码+后期交互架构，百万级文档低延迟重排，吞吐较传统模型提升10倍，适合大规模英文知识库，长文本需分块处理 2 主流Reranker模型评测\r（一）Mixedbread Reranker模型评测\r2025年，Mixedbread在官方博客中针对旗下rerank-v2系列模型开展系统性评测，测试覆盖英文、中文、多语言、工具检索、代码检索五大类基准测试场景，并与其它同类开源/闭源模型进行了性能对比：\n原文链接：https://www.mixedbread.com/blog/mxbai-rerank-v2\n这里只列出英文、中文 BEIR 基准测试的表现。\n英文 BEIR 基准测试性能\nC-Pack（中文）检索基准测试表现\n1 NDCG 是 “归一化折损累积增益”（Normalized Discounted Cumulative Gain）的缩写，是信息检索领域常用的评估指标。它的核心作用是衡量检索结果的相关性和排序质量： ​\t评测结果显示：mxbai-rerank-v2 在中文检索基准 C-Pack 中表现领先，15 亿参数版本以 84.16 分夺冠，5 亿参数版本获 83.70 分紧随其后；相较上一代，该模型得分提升超 11 分，检索准确率大幅突破，成为中文高精度检索场景的优选方案。\n（二）阿里rerank系列模型评测\r2025年6月，阿里发布Qwen3-Reranker-4B系列模型及对应的评测结果，相关信息可参考官方开源地址：\n链接：https://github.com/dengcao/Qwen3-Reranker-4B\n评测采用MTEB（英文，v2版）、MTEB（中文，v1版）、MMTEB及MTEB（代码领域）的检索任务子集，对应命名为MTEB-R、CMTEB-R、MMTEB-R与MTEB-Code。\n所有评测分数均由阿里团队独立测试计算得出，测试基于稠密嵌入模型Qwen3-Embedding-0.6B（https://huggingface.co/Qwen/Qwen3-Embedding-0.6B）检索得到的前100条候选结果。\n（三）Jina Reranker 模型评测\rJina 于2025年10月发布了jina-reranker-v3，这个新模型在 BEIR 上实现了 61.94 nDCG@10，优于 Qwen3-Reranker-4B，同时体积缩小了 6 倍。\n链接：https://jina.ai/zh-CN/news/jina-reranker-v3-0-6b-listwise-reranker-for-sota-multilingual-retrieval/\n展示了评测结果如下：\nModel Size BEIR MIRACL MKQA CoIR jina-reranker-v3 0.6B 61.94 66.83 67.92 70.64 jina-reranker-v2 0.3B 57.06 63.65 67.90 56.14 jina-reranker-m0 2.4B 58.95 66.75 68.19 63.55 bge-reranker-v2-m3 0.6B 56.51 69.32 67.88 36.28 mxbai-rerank-base-v2 0.5B 58.40 55.32 64.24 65.71 mxbai-rerank-large-v2 1.5B 61.44 57.94 67.06 70.87 Qwen3-Reranker-0.6B 0.6B 56.28 57.70 65.34 65.18 Qwen3-Reranker-4B 4.0B 61.16 67.52 67.52 73.91 jina-code-embeddings-0.5b 0.5B - - - 73.94 在 BEIR 上衡量的英语检索性能，以 nDCG@10 为指标。所有分数都是我们基于来自 jina-embeddings-v3 作为第一阶段检索器的前 100 个结果进行的运行。我们评估了 jina-reranker-v3 的三个变体：按相关性得分降序排列的文档、升序排列的得分和随机排列。评估表明，v3 在不同的输入排序中保持相对稳定的性能，这表明强大的自注意力机制可以有效地处理文档，而不管其初始排列如何。\n3 主流开源Rerank模型解析\r本节从技术特性、性能与部署、优缺点、适用场景四个核心维度，对6款模型进行解析，为选型提供数据支撑。\n3.1 Qwen3-Reranker-4B（阿里通义）\r阿里通义Qwen3大模型生态的核心重排组件，4B参数实现“性能与效率的黄金平衡”，面向高精度 RAG、长文本检索及跨语言场景，是企业级部署的优选模型。\n技术特性\n架构：采用单塔 Cross-Encoder（交叉编码器）结构，将查询与候选文档拼接输入，通过动态交互特征计算相关性得分，相比传统双塔模型更精准捕捉细粒度语义关联； 长文本处理：支持32K tokens上下文，采用双块注意力机制保障长文档（如法律合同、科研论文）的语义连贯性，特别优化了专业领域长文本排序稳定性； 多阶段训练：① 先基于Qwen3-32B大模型生成1.5亿条弱监督query-doc数据对完成预训练，夯实基础语义匹配能力；② 在MS MARCO、BEIR、中文CLIR等12个权威高质量数据集上进行微调，针对性提升特定场景性能；③ 采用球面线性插值（slerp）技术合并多阶段训练检查点，进一步增强模型鲁棒性与泛化能力。 多语言支持：覆盖 119 种自然语言 + 30 种编程语言，跨语言检索平均误差较上一代降低 30 %，中文场景 MTEB-R 得分领先同类开源模型。 性能与部署\n权威评测：MMTEB-R 72.74，MTEB-R多语言基准69.76分，代码检索任务得分突破81.0，远超同类模型； 硬件要求：FP16精度下需14GB显存（推荐A100/RTX4090），AWQ量化后显存降至8GB，精度损失\u0026lt;1%； 推理延迟：100文档排序延迟\u0026lt;100ms（A100），支持vLLM加速提升吞吐量； 授权协议：Apache 2.0，完全开源可商用，支持Hugging Face、ModelScope及GitHub获取权重。 优缺点\n优点：综合性能顶尖，长文本/跨语言能力强，开源可商用，生态完善； 缺点：参数规模较大（4B），推理速度慢于轻量模型，低显存设备（如T4）部署受限。 适用场景\n企业级RAG系统精排环节：适配金融、法律、医疗等行业的高精度问答场景，可与Qwen3-Embedding-4B组合构建“初检索+精排序”全流程RAG方案，提升企业知识库、智能客服的回答准确率，降低模型幻觉率。 专业领域长文本检索：适用于法律合同审查、科研论文检索、企业技术手册匹配等场景，能精准捕捉长文本中的关键语义信息，助力专业人员快速定位核心内容，提升工作效率。 多语言/跨境检索场景：可支撑跨国企业知识库构建、跨境电商商品搜索等需求，在西班牙语-英语、中文-俄语等跨语言匹配任务中表现优异，能显著提升小语种市场的检索准确率与用户体验。 代码检索与研发辅助：针对30种编程语言的代码片段检索优化，可应用于企业研发知识库、智能编码辅助工具（如通义灵码），帮助开发人员快速定位所需代码资源，提升研发效率。 3.2 mxbai-rerank-large-v2（MixedBread）\rMixedBread 第二代重排模型的旗舰版本，包括 mxbai-rerank-base-v2（5 亿个参数）和 mxbai-rerank-large-v2（1.5B，15 亿个参数）。它们是基于 Qwen-2.5 架构的开源（Apache 2.0 许可证）交叉编码器。1.5B参数主打多语言零样本泛化，是BEIR基准18项任务的零样本SOTA，适合全球化应用及多语言检索场景。\n技术特性\n架构：DeBERTa-v3-large 优化，Cross-Encoder + 动态注意力，语义交互建模更精准； 强化学习训练：在初始训练的基础上融入了三阶段强化学习 (RL) 方法：引导强化提示优化（GRPO）+对比学习+偏好学习。让模型输出更清晰的相关性得分，精准区分文档优先级； 多语言适配：原生支持德、英、西、法等多语言，无需额外微调即可适配全球主流语言场景； 低集成成本：可直接对接Elasticsearch/OpenSearch等关键词搜索引擎，无需修改现有基础设施，一行代码即可提升检索相关性。 性能与部署\n权威评测：BEIR平均NDCG@10 57.49，MR-TyDi多语言数据集29.79，零样本泛化能力行业领先； 硬件要求：推荐GPU部署（A10以上），单查询处理耗时0.89s（A100，NFC数据集），比同类大模型快8倍； 授权协议：Apache 2.0，开源可商用，支持Hugging Face/Transformers兼容，vLLM加速。 优缺点\n优点：多语言零样本性能顶尖，集成成本低，商用友好； 缺点：1.5B参数推理速度慢于轻量模型（如bge-rerank-v2-m3），中文场景微调数据较少。 适用场景\n全球化多语言检索：适配德/法/西/英等多语言文档库，无需针对单一语言微调即可实现多语种内容精准排序。 现有搜索引擎优化：为已部署Elasticsearch/OpenSearch的企业提供轻量升级方案，通过接入模型快速提升关键词检索的语义相关性，无需重构现有系统。 零样本跨语言迁移：适用于小语种地区电商商品检索、跨境客服知识库等场景，依托强大的零样本能力快速适配低资源语言市场，提升当地用户检索体验。。 3.3 bge-rerank-v2-m3（BAAI）\r作为北京人工智能研究院（BAAI）FlagEmbedding项目的核心成员，主打“轻量化+中英文混合适配”，是中文社区最受欢迎的重排模型之一。核心定位为资源有限场景（低配置硬件、低成本部署）提供高效重排方案，兼顾中英文检索精度与部署效率。\n技术特性\n架构：基于 BERT 类优化的 Cross-Encoder，query-passage 拼接建模语义交互；\n多语言优化：基于XLM-RoBERTa预训练，融合中英文海量语料微调，采用SentencePiece分词器适配非空格分隔语言，跨语言语义对齐能力强，尤其适配中英文混合场景；\n高效训练：通过知识蒸馏将大模型知识迁移到567M参数模型，同时采用对比学习、多任务学习提升泛化性；\n量化友好：支持FP16/INT8/INT4量化，量化后体积\u0026lt;200MB，可在低配置设备运行。\n性能与部署\n权威评测：中文C-MTEB排名前列，中英文混合场景MRR@10≈0.87，MTEB多语言基准表现优秀； 硬件要求：CPU（8核16G）可运行，GPU（T4）单卡QPS≈500（batch=32）； 推理延迟：单样本GPU≈0.5ms，CPU≈10ms； 授权协议：Apache 2.0，开源可商用，FlagEmbedding库提供一键调用接口，支持ONNX/TensorRT加速。 优缺点\n优点：中文表现突出，轻量化易部署，量化后成本极低； 缺点：英文精度不及 MixedBread，百万级候选排序效率低于ColBERT。 适用场景\n中文为主的中小企业RAG系统：适配中文企业知识库、智能客服、内部文档检索等场景，在低成本硬件（如普通服务器、云服务器低配GPU）上即可部署，提升中文问答精度；\n中英文混合办公场景：适用于有跨境业务的中小企业，可支撑中英文合同检索、双语产品手册匹配等需求，兼顾检索精度与部署成本；\n边缘设备/低成本部署场景：如智能问答终端、中小企业轻量化检索工具等，INT4量化后可在低显存GPU或高性能CPU上运行，无需高额硬件投入。\n3.4 jina-reranker-v3（Jina AI）\r专为Agentic-RAG（智能代理驱动的RAG）设计的重排模型，主打多语言适配与listwise交叉交互，适合需要智能代理自主检索的场景。\n技术特性\n架构：Cross-Encoder，推理速度较 v1 提升 6 倍，吞吐量超 bge 15 倍； Agentic优化：针对Agentic-RAG场景专用微调，支持“检索-思考-再检索”的多轮交互逻辑，提升代理自主决策的检索精度； listwise交互：区别于传统pointwise（单文档评分），采用listwise策略建模候选文档间的相对相关性，排序更符合人类认知； 多语言覆盖：支持100+语言，可直接适配跨境电商、国际客服等全球化场景。 性能与部署\n权威评测：MTEB多语言重排任务表现均衡，Agentic-RAG场景下相关性提升3%-5%； 硬件要求：轻量部署，GPU（T4）单卡QPS≈600（batch=32），CPU（8核16G）可运行； 授权协议：CC-BY-NC-4.0，非商用免费，商用需获取Jina AI官方授权； 生态支持：原生兼容LangChain、Haystack等RAG框架，支持Docker快速部署。 优缺点\n优点：Agentic-RAG场景适配性强，多语言支持全面，推理速度快； 缺点：商用需授权，单语言精度略逊于Qwen3/mxbai-rerank-large-v2。 适用场景\nAgentic-RAG智能代理系统：适配企业智能知识库代理、自主研发助手、智能决策系统等场景，支撑代理自主完成多轮检索与信息筛选，提升决策准确性；\n多语言智能客服代理：适用于跨国企业的智能客服系统，通过智能代理自主检索多语言知识库，为全球用户提供精准问答服务，提升客服响应效率与质量；\n2.5 ms-marco-MiniLM-L6-v2（Microsoft）\r微软经典轻量重排模型，22M参数实现“极致轻量化”，是边缘端、CPU部署及高QPS场景的标杆，主打英文通用检索。\n技术特性\n架构精简：基于MiniLM-L6（6层Transformer）的Cross-Encoder，去除冗余参数，在保证基础精度的前提下最大化提升推理速度； 训练聚焦：仅基于MS MARCO英文检索数据集训练，专门优化英文通用场景的相关性判断； 生态兼容：原生支持所有主流检索框架（Haystack、LangChain、Pyserini），集成成本几乎为零。 性能与部署\n权威评测：英文MTEB平均74.2，MS MARCO MRR@10≈0.885，英文通用场景性能稳定；中文无适配，MRR@10≈0.62（性能暴跌）； 硬件要求：CPU（4核8G）即可高效运行，GPU单卡QPS≈2000+（batch=32）； 推理延迟：单样本GPU≈0.2ms，CPU≈5ms，是所有模型中速度最快的； 授权协议：MIT协议，开源可商用，持续维护更新。 优缺点\n优点：超轻量、推理最快，CPU部署友好，英文通用场景生态成熟； 缺点：仅支持英文，泛化性弱，垂直领域需额外微调。 适用场景\n英文高并发检索场景：适配英文电商平台商品检索、英文资讯聚合平台、国际社交平台内容筛选等超高QPS场景，以极致低延迟保障用户检索体验；\n边缘设备/CPU部署场景：如英文智能终端、嵌入式检索系统、无GPU资源的小型服务器等，在低配置硬件上即可稳定运行，实现轻量化英文检索功能；\n2.6 ColBERT v2（CMU/Stanford）\rColBERT 采用后交互（Late-Interaction）架构，把“预编码 + 最后细粒度比对”做成两步，平衡速度、精度与规模。主打“百万级候选文档低延迟重排”，是搜索引擎、大规模知识库检索的核心选择。\n技术特性\n架构：ColBERT v2 延续了Late-Interaction 架构：离线索引阶段把文档编码成“token 级向量集合”，查询阶段同样把问句编码成向量集合，最后在线计算两集合的 MaxSim 加权和，既保留 Cross-Encoder 的细粒度精度，又具备 Bi-Encoder 的预编码效率，是工业级“百万级候选低延迟重排”的首选方案。 高效优化：引入向量压缩、动态维度选择技术，降低存储和计算成本，相比v1版本性能提升10%+； 长文本处理：原生上下文512token，长文本需分块处理，配合预编码机制可高效处理超长文档集合。 性能与部署\n权威评测：英文MTEB平均78.7，MS MARCO MRR@10（10k候选）≈0.905，百万级候选排序效率超传统Cross-Encoder 10倍； 硬件要求：推荐A100/V100，支持分布式部署；100万文档向量约占用500G存储空间； 推理延迟：10k候选排序（GPU）≈0.5s，100k候选≈5s，百万候选≈30s； 生态支持：提供ColBERT Indexer工具链，集成Pyserini/Anserini等检索框架，开源免费（非商用友好）。 优缺点\n优点：大规模候选排序效率极高，预编码降低检索延迟，精度接近高性能Cross-Encoder； 缺点：仅支持英文（中文适配难度高），部署复杂度高，存储成本高，小体量场景性价比低。 适用场景\n大规模英文搜索引擎：适配商业英文搜索引擎、学术英文文献检索平台（如 arXiv 文献检索）等场景，处理百万级以上候选文档的高效重排，平衡检索速度与精度； 大规模英文知识库：适用于大型企业的英文内部知识库、全球英文技术文档库等场景，支持海量文档的快速精准检索，提升企业知识管理效率；原版的 ColBERT v2 仅支持英文，后续由 Jina AI 推出的 Jina-ColBERT-v2 已经扩展为多语言模型。 大型搜索引擎“二段重排”：召回 10 k → ColBERT 精排 200 → Cross-Encoder 终排 10，整体延迟 \u0026lt;500 ms。 4 开源Rerank模型选型\r4.1 选型决策树（快速定位模型）\r明确语言需求：是否以中文/中英文混合为主？\n→ 是：优先从Qwen3-Reranker-4B、bge-reranker-v2-m3中选择；\n→ 否：需支持英文/多语言？优先mxbai-rerank-large-v2、jina-rerank-v3；\n平衡精度与速度：候选文档数量多少？\n→ 候选数\u0026lt;50：选base版/轻量型Cross-Encoder（如bge-reranker-v2-m3） ；\n→ 候选数50-200：选large版/高性能Cross-Encoder（如Qwen3-Reranker-4B、mxbai-rerank-large-v2）；\n→ 候选数\u0026gt;1k/百万级：选ColBERT v2（英文）；\n适配现有生态：现有技术栈适配需求？\n→ 对接BGE嵌入模型：优先bge-reranker-v2-m3（生态无缝衔接） ；\n→ 兼容sentence-transformers：可选所有Cross-Encoder模型（如mxbai-rerank-large-v2、ms-marco-MiniLM-L6-v2）；\n确认部署环境与授权：\n→CPU/边缘端：选超轻量/量化模型（如：ms-marco-MiniLM-L6-v2）；\n→GPU环境：可选中大型高精度模型（如：Qwen3-Reranker-4B、mxbai-rerank-large-v2、jina-rerank-v3）；\n→商用场景：优先Apache 2.0/MIT协议模型；\n4.2 核心选型维度决策表\r说明：“对应优选模型”已结合2025年最新模型表现排序，优先推荐商用友好、生态成熟的方案。\n选型维度 关键考量点 对应优选模型 补充说明 语言需求 中文为主/中英文混合 1. Qwen3-Reranker-4B；2. bge-reranker-v2-m3 前者精度更高，后者部署成本更低 英文为主/多语言（德/西/法等） 1. mxbai-rerank-large-v2；2. jina-rerank-v3 前者零样本泛化性强，后者适配Agentic-RAG 仅英文 ms-marco-MiniLM-L6-v2 超轻量，边缘端 / CPU 部署首选，英文通用场景标杆 候选集规模 小体量（\u0026lt;1k） 所有Cross-Encoder模型（优先轻量款：bge-reranker-v2-m3） 轻量款可平衡速度与精度，降低资源占用 大体量（\u0026gt;1k/百万级） 1. ColBERT v2（英文）、 Jina-ColBERT-v2 (多语言，包括中文)。 需配合高效召回策略（如Elasticsearch）使用 性能优先级 精度优先（企业级RAG/专业领域） 1. Qwen3-Reranker-4B；2. mxbai-rerank-large-v2 专业领域（金融/法律）建议补充少量领域数据微调 速度优先（高QPS/边缘端） 1. ms-marco-MiniLM-L6-v2；2. bge-rerank-v2-m3（量化版） 量化版可进一步提升速度，精度损失\u0026lt;5% 部署环境 CPU/边缘端（低资源） 1. ms-marco-MiniLM-L6-v2（仅英文）； 4核8G CPU可支撑QPS≈500+（单模型） GPU（高资源） 1. Qwen3-Reranker-4B；2. mxbai-rerank-large-v2；3.jina-rerank-v3 推荐16G+显存，支持批量推理提升吞吐量 商业授权 商用无限制 Qwen3-Reranker-4B、bge-rerank-v2-m3、mxbai-rerank-large-v2、ms-marco-MiniLM-L6-v2、ColBERT v2 均为Apache 2.0协议，企业商用无侵权风险 非商用/可接受授权 jina-rerank-v3 商用需联系官方获取授权 4.3 典型场景化选型方案\r以下典型场景化选型方案，覆盖金融、电商、客服、学术等主流领域，可直接参考复用。\n场景1：中文企业级高精度RAG（如金融/法律文档检索）\n首选方案：Qwen3-Reranker-4B\n核心理由：中文语义理解精度领先（MMTEB-R 72.74），支持32K长文本（完美适配法律合同、金融年报等长文档），Apache 2.0协议商用无限制，4B参数在精度与部署成本间达到最优平衡。\n备选方案：bge-rerank-v2-m3（量化版）+ 领域微调\n核心理由：部署成本极低（量化后\u0026lt;200MB），中文社区生态成熟，配套微调工具链完善，少量领域标注数据（500-1000条）即可显著提升专业场景表现。\n场景2：全球化跨境电商检索（多语言+高QPS）\n首选方案：jina-rerank-v3\n核心理由：支持100+语言（覆盖主流跨境市场），专为Agentic-RAG优化（适配电商多轮检索场景，如“先找商品→再找售后政策”）。\n备选方案：mxbai-rerank-large-v2（量化版）\n核心理由：多语言零样本性能顶尖（BEIR 18项任务SOTA），Apache 2.0协议商用友好，适合对检索精度要求更高的高客单价跨境场景（如奢侈品、工业用品）。\n场景3：边缘端英文智能客服（CPU部署+高QPS）\n唯一首选：ms-marco-MiniLM-L6-v2\n核心理由：22M超轻量体积，4核8G CPU即可稳定运行，QPS可达1000+，满足边缘端（如门店智能终端、小型服务器）部署需求；英文通用场景精度稳定，客服常见问题（FAQ）检索准确率\u0026gt;90%。\n场景4：百万级英文知识库检索（如学术论文库、技术文档库）\n首选方案：ColBERT v2\n核心理由：预编码+后交互架构，支持百万级文档低延迟重排，推理速度较传统Cross-Encoder提升10倍；精度接近高性能Cross-Encoder，学术论文相关性检索NDCG@10\u0026gt;0.85。\n配套方案：Elasticsearch召回 + ColBERT v2重排\n核心理由：通过Elasticsearch实现高效初步召回（召回率\u0026gt;95%），再由ColBERT v2进行精准重排，平衡召回全面性与排序精度，整体检索延迟\u0026lt;500ms。\n场景5：Agentic-RAG智能代理（多轮检索+决策）\n首选方案：jina-rerank-v3 核心理由：专为Agentic-RAG微调优化，listwise交叉交互机制可更好适配代理的多轮决策逻辑（如根据上一轮检索结果调整下一轮重排策略）；多语言支持可适配全球化智能代理场景。\n进阶方案： Qwen3-Embedding+jina-rerank-v3 核心理由：形成“高效召回+精准重排”黄金组合，Qwen3-Embedding提供高质量向量召回，jina-rerank-v3保障重排精度，多轮检索准确率较单一模型提升15%-20%。\n4.4 选型避坑指南\r避免跨语言硬适配：ms-marco-MiniLM-L6-v2、ColBERT v2无中文优化，强行处理中文会导致MRR（相关性评价核心指标）下降20%+；中文场景优先选原生中文优化模型。\n大规模候选别用纯Cross-Encoder：mxbai-rerank-large-v2等纯Cross-Encoder在候选数\u0026gt;1k时，推理延迟呈指数级上升；建议采用“召回+重排”分层策略，或选用ColBERT v2等高效架构模型。\n商用场景授权协议：jina-rerank-v3为CC-BY-NC-4.0协议，未经授权商用属于侵权；企业级项目优先选Apache 2.0协议的模型（如Qwen3-Reranker-4B、bge-rerank-v2-m3）。\n不盲目追求大参数：4B参数的Qwen3-Reranker-4B虽精度高，但部署成本也高；若业务为通用中文场景（如通用FAQ检索），bge-rerank-v2-m3（567M）即可满足需求，成本仅为前者的1/5。\n4.5 选型总结\r6款主流开源Rerank模型各有侧重，选型核心逻辑是“对齐业务核心需求”，而非盲目追求性能指标。结合2025年最新技术趋势，给出以下核心选型建议：\n追求「中文高精度+可商用」：首选Qwen3-Reranker-4B； 追求「多语言零样本+泛化性」：首选mxbai-rerank-large-v2； 追求「中文轻量化+低成本」：首选bge-rerank-v2-m3、Qwen3-Reranker-0.6B； 追求「Agentic-RAG+多语言」：首选jina-rerank-v3； 追求「英文边缘端+高QPS」：首选ms-marco-MiniLM-L6-v2； 追求「百万级英文候选+高效」：首选ColBERT v2。 落地建议：模型选定后，基于小批量真实业务数据开展验证测试，重点关注两大核心相关性指标 ——MRR@10（衡量首条正确结果的排序优先级）、NDCG@10（衡量前 10 条结果的整体排序质量）；若业务场景特殊（如专业领域、小众语言），可通过少量领域数据微调进一步提升性能，确保模型真正适配业务需求。\n5 开源Rerank模型使用示例代码\r5.1 下载/加载模型\r国内下载模型最主流的两个渠道 Hugging Face Hub和阿里云的 ModelScope（魔搭）。以下分别详细说明从两个平台下载 / 加载模型的完整流程，包括「自动下载」「手动下载」两种核心方式。\n一、从 Hugging Face 下载 / 加载模型\rHugging Face 是全球最大的开源模型托管平台，国内需通过镜像加速解决网络问题，核心分「自动加载（推荐）」和「手动下载」两种方式：\n方式 1：自动加载（配置镜像后自动下载）\r适合希望自动下载模型的场景，核心是通过 HF_ENDPOINT 环境变量指定国内镜像源。\n安装依赖\n1 pip install sentence-transformers transformers torch huggingface-hub -U 配置国内镜像（关键）\n国内无法直接访问huggingface 网站，可配置国内镜像或代理的方式访问，推荐国内镜像方式，只需配置镜像地址即可。\n临时生效（终端执行）：\n1 2 3 4 5 6 # Linux/Mac export HF_ENDPOINT=https://hf-mirror.com # Windows CMD set HF_ENDPOINT=https://hf-mirror.com # Windows PowerShell $env:HF_ENDPOINT=\u0026#34;https://hf-mirror.com\u0026#34; 永久生效（推荐）：\nLinux/Mac：编辑 ~/.bashrc 或 ~/.zshrc，添加 export HF_ENDPOINT=https://hf-mirror.com，执行 source ~/.bashrc 生效；\n示例：\n1 2 echo \u0026#39;export HF_ENDPOINT=https://hf-mirror.com\u0026#39; \u0026gt;\u0026gt; ~/.bashrc source ~/.bashrc Windows：系统环境变量中新增 HF_ENDPOINT=https://hf-mirror.com（此电脑→属性→高级系统设置→环境变量）。\n代码自动下载并加载\n1 2 3 4 5 6 7 8 9 10 11 12 import torch from sentence_transformers import CrossEncoder # 首次运行会从镜像源自动下载模型到本地缓存（默认路径：~/.cache/huggingface/hub/） # 后续运行直接读取缓存，无需重复下载 model = CrossEncoder( \u0026#34;BAAI/bge-reranker-v2-m3\u0026#34;, device=\u0026#34;cuda\u0026#34; if torch.cuda.is_available() else \u0026#34;cpu\u0026#34; ) # 测试使用 scores = model.predict([(\u0026#34;人工智能\u0026#34;, \u0026#34;大模型应用场景\u0026#34;), (\u0026#34;人工智能\u0026#34;, \u0026#34;建筑工程设计\u0026#34;)]) print(scores) 方式 2：手动下载（无网络限制，适合离线场景）\r若镜像自动加载失败，可手动下载模型文件到本地，再加载。\n下载模型文件\n方式 1：通过 Hugging Face 镜像站 下载文件，核心文件包括：\n核心文件 作用 pytorch_model.bin或model.safetensors 模型权重（核心） config.json 模型配置 tokenizer.json 分词器配置 tokenizer_config.json 分词器参数 vocab.txt 词表文件 方式 2：用 huggingface-hub 工具批量下载（需先安装：pip install huggingface-hub）：\n1 2 3 4 5 # 指定镜像源下载到本地目录（如 ./bge-reranker-v2-m3） huggingface-cli download \\ --resume-download BAAI/bge-reranker-v2-m3 \\ --local-dir ./bge-reranker-v2-m3 \\ --endpoint-url https://hf-mirror.com 下载完成后，本地目录会包含模型所有核心文件（pytorch_model.bin、config.json、tokenizer.json 等）。\n加载本地模型\n1 2 3 4 5 6 7 8 9 10 11 import torch from sentence_transformers import CrossEncoder # 直接指定本地模型路径，无需联网 model = CrossEncoder( \u0026#34;./bge-reranker-v2-m3\u0026#34;, # 替换为你的本地路径 device=\u0026#34;cuda\u0026#34; if torch.cuda.is_available() else \u0026#34;cpu\u0026#34; ) scores = model.predict([(\u0026#34;人工智能\u0026#34;, \u0026#34;大模型应用场景\u0026#34;), (\u0026#34;人工智能\u0026#34;, \u0026#34;建筑工程设计\u0026#34;)]) print(scores) 二、 从 ModelScope（魔搭）下载 / 加载模型\rModelScope 是阿里云推出的国内开源模型平台，访问无需外网，天然适配国内环境。\n方式 1：自动加载（一键下载）\r适合直接通过 ModelScope 接口下载并加载，无需额外配置。\n步骤 1：安装依赖\r1 pip install modelscope sentence-transformers torch -U 步骤 2：代码自动下载并加载\r1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import torch from modelscope.hub.snapshot_download import snapshot_download from sentence_transformers import CrossEncoder # 步骤1：从ModelScope下载模型到本地缓存（默认路径：~/.cache/modelscope/hub/） # 首次运行下载，后续直接读取缓存 model_dir = snapshot_download(\u0026#34;BAAI/bge-reranker-v2-m3\u0026#34;) # 步骤2：加载模型（与Hugging Face加载方式完全一致） model = CrossEncoder( model_dir, device=\u0026#34;cuda\u0026#34; if torch.cuda.is_available() else \u0026#34;cpu\u0026#34; ) # 测试使用 scores = model.predict([(\u0026#34;人工智能\u0026#34;, \u0026#34;大模型应用场景\u0026#34;), (\u0026#34;人工智能\u0026#34;, \u0026#34;建筑工程设计\u0026#34;)]) print(scores) 方式 2：手动下载\r若需将模型下载到指定目录（如服务器特定路径），可手动指定下载路径。\n步骤 1：手动下载模型文件\n方式 1：通过 魔塔平台 下载全部文件（核心文件包括：pytorch_model.bin 或 model.safetensors、config.json、tokenizer.json、tokenizer_config.json、vocab.txt）；\n方式 2：通过终端命令下载（需安装 modelscope）：\n1 modelscope download --model BAAI/bge-reranker-v2-m3 --local_dir ./modelscope_bge_reranker 步骤 2：加载本地模型\n1 2 3 4 5 6 7 8 9 10 11 12 import torch from sentence_transformers import CrossEncoder # 直接指定ModelScope下载的本地路径 model = CrossEncoder( \u0026#34;./modelscope_bge_reranker\u0026#34;, device=\u0026#34;cuda\u0026#34; if torch.cuda.is_available() else \u0026#34;cpu\u0026#34; ) # 测试使用 scores = model.predict([(\u0026#34;人工智能\u0026#34;, \u0026#34;大模型应用场景\u0026#34;), (\u0026#34;人工智能\u0026#34;, \u0026#34;建筑工程设计\u0026#34;)]) print(scores) 5.2 开源 Rerank 模型使用示例代码\r以下提供 6 款主流 Rerank 模型的示例使用代码，代码基于 Python 3.9+。\n通用前置依赖安装：\n1 2 3 4 5 6 # 核心依赖（适配所有模型） pip install torch transformers sentence-transformers # 专项依赖（按需安装） pip install colbert-ai # ColBERT v2专用 pip install accelerate # 大模型推理加速（Qwen3-Reranker-4B） 一. Qwen3-Reranker-4B示例代码（中文高精度 + 可商用）\r1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 # 安装依赖：pip install transformers\u0026gt;=4.51.0 torch modelscope import torch from typing import List, Optional, Tuple from modelscope import AutoTokenizer, AutoModelForCausalLM class QwenReranker: \u0026#34;\u0026#34;\u0026#34; 通义千问重排模型封装类 基于ModelScope的Qwen3-Reranker系列模型，实现查询(Query)与文档(Document)的相关性评分及排序， 模型通过判断文档是否满足查询需求，输出0-1之间的相关性得分（越接近1表示相关性越高）。 基础用法示例： \u0026gt;\u0026gt;\u0026gt; ranker = QwenReranker(\u0026#34;Qwen/Qwen3-Reranker-0.6B\u0026#34;) \u0026gt;\u0026gt;\u0026gt; query = \u0026#34;人工智能在医疗领域的应用\u0026#34; \u0026gt;\u0026gt;\u0026gt; docs = [\u0026#34;AI辅助诊断系统可分析医学影像...\u0026#34;, \u0026#34;人工智能在农业中的应用...\u0026#34;] \u0026gt;\u0026gt;\u0026gt; sorted_results = ranker.rerank_sorted(query, docs) \u0026gt;\u0026gt;\u0026gt; print(sorted_results) # [(doc索引, 得分), ...]（按得分降序） \u0026#34;\u0026#34;\u0026#34; DEFAULT_INSTRUCTION = \u0026#34;Given a web search query, retrieve relevant passages that answer the query\u0026#34; def __init__( self, model_name_or_path: str = \u0026#34;Qwen/Qwen3-Reranker-0.6B\u0026#34;, device: Optional[str] = None, max_length: int = 8192, ): \u0026#34;\u0026#34;\u0026#34; 初始化重排模型 Args: model_name_or_path: 模型名称或本地路径。 1.若为模型名称：如果本地缓存（默认用户路径 ~/.cache/modelscope/hub ）中没有模型文件，则 from_pretrained() 会自动 从modelscope.cn下载模型权重、配置和分词器文件。 如果本地缓存已存在该模型，代码会直接加载缓存中的文件，无需重新下载。 2.若为本地模型的存储路径：则需要手动下载模型文件到路径地址（config.json、*.safetensors 等）. device: 运行设备（\u0026#34;cuda\u0026#34;/\u0026#34;cpu\u0026#34;），默认自动检测 max_length: 默认最大文本长度 \u0026#34;\u0026#34;\u0026#34; self.tokenizer = AutoTokenizer.from_pretrained(model_name_or_path, padding_side=\u0026#34;left\u0026#34;) self.max_length = max_length # 设备自动检测 self.device = device if device else (\u0026#34;cuda\u0026#34; if torch.cuda.is_available() else \u0026#34;cpu\u0026#34;) # 加载模型 self.model = AutoModelForCausalLM.from_pretrained(model_name_or_path).eval() # We recommend enabling flash_attention_2 for better acceleration and memory saving. # model = AutoModelForCausalLM.from_pretrained(\u0026#34;Qwen/Qwen3-Reranker-0.6B\u0026#34;, torch_dtype=torch.float16, # attn_implementation=\u0026#34;flash_attention_2\u0026#34;).cuda().eval() if device and device != \u0026#34;auto\u0026#34;: # 指定单卡 self.model = self.model.to(device) # 特殊 token id self.token_true_id = self.tokenizer.convert_tokens_to_ids(\u0026#34;yes\u0026#34;) self.token_false_id = self.tokenizer.convert_tokens_to_ids(\u0026#34;no\u0026#34;) # prompt 模板 self.prefix = ( \u0026#34;\u0026lt;|im_start|\u0026gt;system\\nJudge whether the Document meets the requirements based on the Query and the \u0026#34; \u0026#34;Instruct provided. Note that the answer can only be \\\u0026#34;yes\\\u0026#34; or \u0026#34; \u0026#34;\\\u0026#34;no\\\u0026#34;.\u0026lt;|im_end|\u0026gt;\\n\u0026lt;|im_start|\u0026gt;user\\n\u0026#34;) self.suffix = \u0026#34;\u0026lt;|im_end|\u0026gt;\\n\u0026lt;|im_start|\u0026gt;assistant\\n\u0026lt;think\u0026gt;\\n\\n\u0026lt;/think\u0026gt;\\n\\n\u0026#34; self.prefix_tokens = self.tokenizer.encode(self.prefix, add_special_tokens=False) self.suffix_tokens = self.tokenizer.encode(self.suffix, add_special_tokens=False) @staticmethod def _format(instruction: Optional[str], query: str, doc: str) -\u0026gt; str: inst = instruction or QwenReranker.DEFAULT_INSTRUCTION return f\u0026#34;\u0026lt;Instruct\u0026gt;: {inst}\\n\u0026lt;Query\u0026gt;: {query}\\n\u0026lt;Document\u0026gt;: {doc}\u0026#34; def _tokenize(self, texts: List[str]): inputs = self.tokenizer( texts, padding=False, truncation=\u0026#34;longest_first\u0026#34;, return_attention_mask=False, max_length=self.max_length - len(self.prefix_tokens) - len(self.suffix_tokens), ) # 拼接 prefix + text + suffix for idx, ids in enumerate(inputs[\u0026#34;input_ids\u0026#34;]): inputs[\u0026#34;input_ids\u0026#34;][idx] = self.prefix_tokens + ids + self.suffix_tokens # pad 到 batch inputs = self.tokenizer.pad(inputs, padding=True, return_tensors=\u0026#34;pt\u0026#34;, max_length=self.max_length) for key in inputs: inputs[key] = inputs[key].to(self.model.device) return inputs @torch.no_grad() def _compute_logits(self, inputs) -\u0026gt; List[float]: logits = self.model(**inputs).logits[:, -1, :] true_score = logits[:, self.token_true_id] false_score = logits[:, self.token_false_id] batch_scores = torch.stack([false_score, true_score], dim=1) batch_scores = torch.nn.functional.log_softmax(batch_scores, dim=1) scores = batch_scores[:, 1].exp().tolist() return scores def rerank_sorted( self, query: str, docs: List[str], instruction: Optional[str] = None, batch_size: int = 8, ) -\u0026gt; List[Tuple[int, float]]: \u0026#34;\u0026#34;\u0026#34; 对单 query 多 doc 的场景，返回 [(doc_idx, score), ...] 按得分降序 \u0026#34;\u0026#34;\u0026#34; queries = [query] * len(docs) pairs = [self._format(instruction, q, d) for q, d in zip(queries, docs)] scores = [] for i in range(0, len(pairs), batch_size): batch_pairs = pairs[i: i + batch_size] inputs = self._tokenize(batch_pairs) scores.extend(self._compute_logits(inputs)) return sorted(enumerate(scores), key=lambda x: x[1], reverse=True) # ---------------- 使用示例 ---------------- if __name__ == \u0026#34;__main__\u0026#34;: # 1. 初始化模型（CPU 也能跑，GPU 更快） # model_name_or_path: 模型名称或本地路径。 # -若为模型名称：如果本地缓存（默认路径~ /.cache/modelscope/）中没有模型文件，from_pretrained() # 会自动从modelscope.cn下载模型权重、配置和分词器文件。 如果本地缓存已存在该模型，代码会直接加载缓存中的文件，无需重新下载。 # -若为本地模型的存储路径：则需要手动下载模型文件到路径地址（config.json、model.safetensors(或pytorch_model.bin)、tokenizer.json # tokenizer_config.json、special_tokens_map.json、vocab.txt）. ranker = QwenReranker( # 由于本地硬件资源有限，替换为Qwen3-Reranker-0.6B模型. # model_name_or_path=\u0026#34;Qwen/Qwen3-Reranker-4B\u0026#34;, model_name_or_path=\u0026#34;Qwen/Qwen3-Reranker-0.6B\u0026#34;, ) # 2. 测试查询+文档 query = \u0026#34;人工智能在医疗领域的应用\u0026#34; docs = [ \u0026#34;人工智能驱动的农业无人机可实现作物病虫害监测，精准率达85%以上\u0026#34;, # 无关（完全跨领域） \u0026#34;AI辅助诊断系统可快速分析CT、MRI等医学影像，使肺癌、乳腺癌等疾病的早期检出率提升30%以上\u0026#34;, # 高相关 \u0026#34;深度学习算法在金融风控中的应用案例，有效降低信贷违约率15%\u0026#34;, # 无关（金融领域） \u0026#34;卷积神经网络在皮肤癌图像分类任务上达到91%的准确率，为基层医疗机构提供了低成本诊断方案\u0026#34;, # 中高相关（技术细分，间接相关） \u0026#34;智能模拟在生物医药研发中的应用，主要用于药物分子结构模拟，不属于临床医疗应用范畴\u0026#34;, # 干扰项（语义相近但领域不符） \u0026#34;人工智能技术在医疗领域的核心应用包括AI辅助诊断、个性化治疗方案生成、医疗影像分析等\u0026#34;, # 高相关 \u0026#34;机器学习算法在医保风控系统中的应用，可识别虚假就医报销行为，属于AI在医疗管理的边缘场景\u0026#34;, # 低相关（跨领域AI，弱关联） \u0026#34;医疗大数据平台通过机器学习算法整合患者电子病历，为医院管理决策提供数据支撑\u0026#34;, # 中相关（泛医疗AI，关联性减弱） ] # 3. 自动排序 print(\u0026#34;\\n【按得分降序】\u0026#34;) sorted_res = ranker.rerank_sorted(query, docs) for rank, (idx, score) in enumerate(sorted_res, 1): print(f\u0026#34;{rank}. {score:.4f}\\t{docs[idx]}\u0026#34;) 运行结果如下：\n二. mxbai-rerank-large-v2示例代码（多语言零样本 + 泛化性）\r1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 import logging from typing import List, Tuple, Optional, Dict, Any import torch from sentence_transformers import CrossEncoder # 配置日志 logging.basicConfig( level=logging.INFO, format=\u0026#34;%(asctime)s - %(name)s - %(levelname)s - %(message)s\u0026#34; ) logger = logging.getLogger(__name__) class MxbaiReranker: \u0026#34;\u0026#34;\u0026#34; MixedBread AI Rerank 模型封装类 支持多语言（德/英/西/法/中等）的文本重排 \u0026#34;\u0026#34;\u0026#34; def __init__( self, model_name_or_path: str = \u0026#34;mixedbread-ai/mxbai-rerank-large-v2\u0026#34;, max_length: int = 8192, device: Optional[str] = None, model_kwargs: Optional[Dict[str, Any]] = None ): \u0026#34;\u0026#34;\u0026#34; 初始化重排模型 Args: model_name_or_path: 模型名称或本地路径 max_length: 文本最大长度 device: 运行设备 (e.g., \u0026#34;cpu\u0026#34;, \u0026#34;cuda\u0026#34;, \u0026#34;cuda:0\u0026#34;) model_kwargs: 传递给CrossEncoder的额外参数 \u0026#34;\u0026#34;\u0026#34; self.model_name_or_path = model_name_or_path self.max_length = max_length # 设备自动检测 self.device = device if device else (\u0026#34;cuda\u0026#34; if torch.cuda.is_available() else \u0026#34;cpu\u0026#34;) self.model_kwargs = model_kwargs or {} # 加载模型 self._load_model() def _load_model(self) -\u0026gt; None: \u0026#34;\u0026#34;\u0026#34;加载CrossEncoder模型\u0026#34;\u0026#34;\u0026#34; try: logger.info(f\u0026#34;正在加载模型: {self.model_name_or_path}\u0026#34;) self.model = CrossEncoder( model_name_or_path=self.model_name_or_path, max_length=self.max_length, device=self.device, **self.model_kwargs ) logger.info(\u0026#34;模型加载成功\u0026#34;) except Exception as e: logger.error(f\u0026#34;模型加载失败: {e}\u0026#34;) raise def rerank_sorted( self, query: str, passages: List[str], batch_size: int = 16, return_scores: bool = True ) -\u0026gt; List[Tuple[str, float]] | List[str]: \u0026#34;\u0026#34;\u0026#34; 对段落进行重排 Args: query: 多语言查询文本 passages: 待重排的段落列表 batch_size: 批量推理大小,可根据CPU/GPU调整，CPU建议设为8/16 return_scores: 是否返回得分 Returns: 排序后的段落列表（可选包含得分） \u0026#34;\u0026#34;\u0026#34; if not query: raise ValueError(\u0026#34;查询文本不能为空\u0026#34;) if not passages: raise ValueError(\u0026#34;段落列表不能为空\u0026#34;) try: # 构造输入对 inputs = [[query, passage] for passage in passages] # 批量推理 logger.debug(f\u0026#34;开始推理，批次大小: {batch_size}\u0026#34;) # 推理打分（batch_size可根据CPU/GPU调整，CPU建议设为8/16） scores = self.model.predict( inputs, batch_size=batch_size, show_progress_bar=False ) # 排序 ranked_pairs = sorted( zip(passages, scores), key=lambda x: x[1], reverse=True ) logger.info(f\u0026#34;重排完成，共处理 {len(passages)} 个段落\u0026#34;) if return_scores: return ranked_pairs else: return [pair[0] for pair in ranked_pairs] except Exception as e: logger.error(f\u0026#34;重排失败: {e}\u0026#34;) raise def release(self): \u0026#34;\u0026#34;\u0026#34;显式释放模型资源（建议手动调用）\u0026#34;\u0026#34;\u0026#34; try: if hasattr(self, \u0026#39;model\u0026#39;) and self.model is not None: del self.model if torch.cuda.is_available(): torch.cuda.empty_cache() logger.info(\u0026#34;模型资源已释放\u0026#34;) except Exception as e: logger.warning(f\u0026#34;释放模型资源时出错: {e}\u0026#34;) # 使用示例 if __name__ == \u0026#34;__main__\u0026#34;: # 初始化重排器 reranker = MxbaiReranker( # 模型名称或本地路径 # 这里使用本地模型路径，从huggingface.co或modelscope.cn中下载模型(mxbai-rerank-large-v2)文件，存储本地路径下。 # model_name_or_path=\u0026#34;mixedbread-ai/mxbai-rerank-large-v2\u0026#34;, # 模型名称,自动下载到~/.cache/huggingface/ model_name_or_path=\u0026#34;../../data/models_reranker_data/mixedbread-ai/mxbai-rerank-large-v2\u0026#34;, # 本地模型路径 max_length=8192, # device=\u0026#34;cuda\u0026#34; # 如果有GPU可以指定 ) # 测试数据 query = \u0026#34;人工智能在医疗领域的应用\u0026#34; docs = [ \u0026#34;人工智能驱动的农业无人机可实现作物病虫害监测，精准率达85%以上-E\u0026#34;, # 无关（完全跨领域） \u0026#34;AI辅助诊断系统可快速分析CT、MRI等医学影像，使肺癌、乳腺癌等疾病的早期检出率提升30%以上-A\u0026#34;, # 高相关 \u0026#34;深度学习算法在金融风控中的应用案例，有效降低信贷违约率15%-E\u0026#34;, # 无关（金融领域） \u0026#34;卷积神经网络在皮肤癌图像分类任务上达到91%的准确率，为基层医疗机构提供了低成本诊断方案-B\u0026#34;, # 中高相关（技术细分，间接相关） \u0026#34;智能模拟在生物医药研发中的应用，主要用于药物分子结构模拟，不属于临床医疗应用范畴-D\u0026#34;, # 干扰项（语义相近但领域不符） \u0026#34;人工智能技术在医疗领域的核心应用包括AI辅助诊断、个性化治疗方案生成、医疗影像分析等-A\u0026#34;, # 高相关 \u0026#34;机器学习算法在医保风控系统中的应用，可识别虚假就医报销行为，属于AI在医疗管理的边缘场景-D\u0026#34;, # 低相关（跨领域AI，弱关联） \u0026#34;医疗大数据平台通过机器学习算法整合患者电子病历，为医院管理决策提供数据支撑-C\u0026#34;, # 中相关（泛医疗AI，关联性减弱） ] # 执行重排 results = reranker.rerank_sorted(query, docs, batch_size=8) # 打印结果 print(f\u0026#34;\\nmxbai-rerank-large-v2 重排结果：\u0026#34;) print(f\u0026#34;query:{query}\u0026#34;) print(\u0026#34;-\u0026#34; * 80) for i, (passage, score) in enumerate(results, 1): print(f\u0026#34;第{i}名：得分 {score:.4f} | 内容：{passage}\u0026#34;) 执行结果如下：\n1 从结果可见 mxbai-rerank-large-v2模型对中文的重排并不好。 三. bge-reranker-v2-m3示例代码（中文轻量化 + 低成本）\r1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 import os from typing import List, Tuple, Optional import torch from FlagEmbedding import FlagReranker class RerankerBgeV2: \u0026#34;\u0026#34;\u0026#34; BGE-Reranker-V2 重排模型处理器 基于BAAI推出的bge-reranker-v2-m3模型实现文本相关性重排，用于提升检索系统中 召回阶段候选文档的排序精度，核心通过计算query-doc对的语义相似度分数实现重排。 适用于中文/多语言场景的检索后重排任务。 \u0026#34;\u0026#34;\u0026#34; def __init__( self, model_path: str = \u0026#34;../../data/models_reranker_data/BAAI/bge-reranker-v2-m3\u0026#34;, use_fp16: bool = True, device: str = \u0026#34;cpu\u0026#34; ): \u0026#34;\u0026#34;\u0026#34; 初始化重排模型 model_path: 模型本地存储路径，需包含完整的模型文件（config.json、model.safetensors等） use_fp16: 是否启用FP16精度推理，GPU环境下推荐True，CPU环境会自动强制关闭 device: 模型运行设备，支持\u0026#34;cpu\u0026#34;、\u0026#34;cuda\u0026#34;、\u0026#34;cuda:0\u0026#34;等格式，为空时自动检测可用设备 \u0026#34;\u0026#34;\u0026#34; # 转换为绝对路径，避免相对路径导致的文件查找失败 self.model_path = os.path.abspath(model_path) # 设备自动检测 self.device = device if device else (\u0026#34;cuda\u0026#34; if torch.cuda.is_available() else \u0026#34;cpu\u0026#34;) # CPU环境下FP16无性能收益且可能导致兼容性问题，强制关闭 self.use_fp16 = use_fp16 if device != \u0026#34;cpu\u0026#34; else False # 重排模型实例，延迟初始化 self.reranker: Optional[FlagReranker] = None # 初始化模型 self._init_model() def _init_model(self) -\u0026gt; None: \u0026#34;\u0026#34;\u0026#34;加载并重初始化重排模型\u0026#34;\u0026#34;\u0026#34; try: self.reranker = FlagReranker( model_name_or_path=self.model_path, use_fp16=self.use_fp16, device=self.device ) print(f\u0026#34;✅ 重排模型加载成功，设备：{self.device}，FP16：{self.use_fp16}\u0026#34;) except Exception as e: raise RuntimeError(f\u0026#34;模型初始化失败：{e}\u0026#34;) def rerank_sorted( self, query: str, candidate_docs: List[str], top_k: Optional[int] = None ) -\u0026gt; List[Tuple[str, float]]: \u0026#34;\u0026#34;\u0026#34; 对候选文档列表按与查询语句的相关性进行重排 Args: query: 用户查询语句（文本） candidate_docs: 待重排的候选文档列表 top_k: 返回前k个结果（None返回全部） Returns: 重排结果列表，元素为(文档文本, 相关性分数)，按分数降序排列 \u0026#34;\u0026#34;\u0026#34; if not candidate_docs: raise ValueError(\u0026#34;候选文档列表不能为空\u0026#34;) if self.reranker is None: raise RuntimeError(\u0026#34;模型未初始化，请检查模型路径和设备配置\u0026#34;) # 构造query-doc文本对,格式：[[query1, doc1], [query1, doc2], ...] text_pairs = [[query, doc] for doc in candidate_docs] # 批量计算相关性分数 try: scores = self.reranker.compute_score(text_pairs) except Exception as e: raise RuntimeError(f\u0026#34;分数计算失败：{e}\u0026#34;) # 按分数降序排序 ranked_results = sorted( zip(candidate_docs, scores), key=lambda x: x[1], reverse=True ) # 截取top_k结果 if top_k is not None and top_k \u0026gt; 0: ranked_results = ranked_results[:top_k] return ranked_results # 示例：BGE-Reranker-V2模型的完整使用流程 if __name__ == \u0026#34;__main__\u0026#34;: # 1. 初始化重排处理器 # 需下载模型文件到存储路径../../data/models_reranker_data/BAAI，模型下载地址： # - HuggingFace: https://huggingface.co/BAAI/bge-reranker-v2-m3 # - ModelScope: https://www.modelscope.cn/models/BAAI/bge-reranker-v2-m3 # 需下载的核心文件：config.json、model.safetensors(或pytorch_model.bin)、 tokenizer.json、 # tokenizer_config.json、special_tokens_map.json、vocab.json。 reranker = RerankerBgeV2( model_path=\u0026#34;../../data/models_reranker_data/BAAI/bge-reranker-v2-m3\u0026#34;, # device=\u0026#34;cuda:0\u0026#34;, # 如需使用GPU可显式指定 # use_fp16=True # GPU环境下建议开启FP16 ) # 2. 准备测试数据 query = \u0026#34;人工智能在医疗领域的应用\u0026#34; docs = [ \u0026#34;人工智能驱动的农业无人机可实现作物病虫害监测，精准率达85%以上\u0026#34;, # 无关（完全跨领域） \u0026#34;AI辅助诊断系统可快速分析CT、MRI等医学影像，使肺癌、乳腺癌等疾病的早期检出率提升30%以上\u0026#34;, # 高相关 \u0026#34;深度学习算法在金融风控中的应用案例，有效降低信贷违约率15%\u0026#34;, # 无关（金融领域） \u0026#34;卷积神经网络在皮肤癌图像分类任务上达到91%的准确率，为基层医疗机构提供了低成本诊断方案\u0026#34;, # 中高相关（技术细分，间接相关） \u0026#34;智能模拟在生物医药研发中的应用，主要用于药物分子结构模拟，不属于临床医疗应用范畴\u0026#34;, # 干扰项（语义相近但领域不符） \u0026#34;人工智能技术在医疗领域的核心应用包括AI辅助诊断、个性化治疗方案生成、医疗影像分析等\u0026#34;, # 高相关 \u0026#34;机器学习算法在医保风控系统中的应用，可识别虚假就医报销行为，属于AI在医疗管理的边缘场景\u0026#34;, # 低相关（跨领域AI，弱关联） \u0026#34;医疗大数据平台通过机器学习算法整合患者电子病历，为医院管理决策提供数据支撑\u0026#34;, # 中相关（泛医疗AI，关联性减弱） ] # 3. 执行重排 results = reranker.rerank_sorted( query=query, candidate_docs=docs, top_k=None # 可改为None返回全部 ) # 4. 打印结果 print(\u0026#34;\\n\u0026#34; + \u0026#34;=\u0026#34; * 50) print(f\u0026#34;bge-reranker-v2-m3 重排结果（分数越高越相关）：\u0026#34;) print(f\u0026#34;查询语句：{query}\u0026#34;) print(\u0026#34;=\u0026#34; * 50) for idx, (doc, score) in enumerate(results, 1): print(f\u0026#34;Top{idx} | 分数：{score:.4f} | 文档：{doc}\u0026#34;) print(\u0026#34;=\u0026#34; * 50 + \u0026#34;\\n\u0026#34;) 执行结果如下： 1 从结果可见 bge-reranker-v2-m3模型对中文的重排相对较好。 四. ms-marco-MiniLM-L6-v2示例代码（英文边缘端 + 高 QPS）\r1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 from typing import List, Tuple, Optional, Union import torch from sentence_transformers import CrossEncoder class CrossEncoderReranker: def __init__( self, model_name_or_path: str = \u0026#34;../data/models_data/cross-encoder/ms-marco-MiniLM-L6-v2\u0026#34;, device: Optional[str] = None, batch_size: int = 16 ): \u0026#34;\u0026#34;\u0026#34; 初始化重排器。 基于CrossEncoder的文档重排器，用于检索系统中对召回阶段的候选文档进行语义相关性重排，提升检索精度。 适配采用CrossEncoder架构系列模型, 如ms-marco-MiniLM-L-12-v2、bge-reranker-large、jina-rerank-v3、mxbai-rerank-large-v2等 Args: model_name_or_path: str，模型名称（HuggingFace Hub规范名）或本地存储路径： - 1.模型名称：本地缓存（默认~/.cache/huggingface/）无该模型时，自动从Hub下载权重/配置/分词器；缓存已存在则直接加载，无需重复下载。 - 2.本地路径：需手动下载完整模型文件（包含config.json、model.safetensors/pytorch_model.bin等）到本地路径地址。 device: 模型运行设备，None则自动检测（优先使用CUDA，无则使用CPU） batch_size: 推理批次大小，建议CPU设8/16，GPU可根据显存适当增大（默认16） \u0026#34;\u0026#34;\u0026#34; # 设备自动适配 self.device = device if device else (\u0026#34;cuda\u0026#34; if torch.cuda.is_available() else \u0026#34;cpu\u0026#34;) self.batch_size = batch_size self.model_name_or_path = model_name_or_path self.model: Optional[CrossEncoder] = None # 初始化模型 self._load_model() def _load_model(self) -\u0026gt; None: \u0026#34;\u0026#34;\u0026#34;内部方法：加载CrossEncoder模型\u0026#34;\u0026#34;\u0026#34; try: self.model = CrossEncoder(self.model_name_or_path, device=self.device) print(f\u0026#34;✅重排模型加载完成 | 设备：{self.device} | 批次大小：{self.batch_size}\u0026#34;) except Exception as e: raise RuntimeError(f\u0026#34;模型加载失败：{e}\\n请检查：1. 模型路径是否正确 2. 网络是否正常（首次下载需联网）\u0026#34;) def rerank_sorted( self, query: str, candidate_docs: List[str], return_scores: bool = True, top_k: Optional[int] = None ) -\u0026gt; Union[List[str], List[Tuple[str, float]]]: \u0026#34;\u0026#34;\u0026#34; 对候选文档进行相关性重排 Args: query: 用户查询语句 candidate_docs: 召回阶段的候选文档列表（建议top-20以内，保证重排效率） return_scores: 是否返回相关性得分（得分越高，相关性越强） top_k: 返回前k个最相关文档，None返回全部 Returns: 重排后的文档列表（或文档+得分的元组列表），按相关性降序排列 Raises: ValueError: 候选文档列表为空时抛出 RuntimeError: 模型未成功加载时抛出 \u0026#34;\u0026#34;\u0026#34; # 输入合法性校验 if not candidate_docs: raise ValueError(\u0026#34;候选文档列表不能为空\u0026#34;) if self.model is None: raise RuntimeError(\u0026#34;模型未初始化，请检查模型加载是否成功\u0026#34;) # 构造query-doc配对（CrossEncoder必需输入格式） doc_pairs = [[query, doc] for doc in candidate_docs] # 批量推理计算相关性得分 try: scores = self.model.predict(doc_pairs, batch_size=self.batch_size) except Exception as e: raise RuntimeError(f\u0026#34;重排打分失败：{e}\u0026#34;) # 按得分降序排序 sorted_indices = scores.argsort()[::-1] ranked_results = [ (candidate_docs[i], float(scores[i])) for i in sorted_indices ] # 截取top_k结果 if top_k is not None and top_k \u0026gt; 0: ranked_results = ranked_results[:top_k] # 适配返回格式 if return_scores: return ranked_results else: return [item[0] for item in ranked_results] def release(self): \u0026#34;\u0026#34;\u0026#34;显式释放模型资源（建议手动调用）\u0026#34;\u0026#34;\u0026#34; if self.model is not None: self.model = None if torch.cuda.is_available(): torch.cuda.empty_cache() print(\u0026#34;🔌 重排模型资源已释放\u0026#34;) # 示例使用 if __name__ == \u0026#34;__main__\u0026#34;: # 1. 创建重排器实例 reranker = CrossEncoderReranker( # model_name_or_path=\u0026#34;cross-encoder/ms-marco-MiniLM-L6-v2\u0026#34;, # 使用HuggingFace远程加载模型 # 手动从huggingface.co或modelscope.cn中下载模型文件，存储本地路径下 model_name_or_path=\u0026#34;../../data/models_reranker_data/cross-encoder/ms-marco-MiniLM-L6-v2\u0026#34;, # device=\u0026#34;cpu\u0026#34;, # 强制使用CPU batch_size=16 ) # 2. 准备测试数据 query = \u0026#34;人工智能在医疗领域的应用\u0026#34; docs = [ \u0026#34;人工智能驱动的农业无人机可实现作物病虫害监测，精准率达85%以上-E\u0026#34;, # 无关（完全跨领域） \u0026#34;AI辅助诊断系统可快速分析CT、MRI等医学影像，使肺癌、乳腺癌等疾病的早期检出率提升30%以上-A\u0026#34;, # 高相关 \u0026#34;深度学习算法在金融风控中的应用案例，有效降低信贷违约率15%-E\u0026#34;, # 无关（金融领域） \u0026#34;卷积神经网络在皮肤癌图像分类任务上达到91%的准确率，为基层医疗机构提供了低成本诊断方案-B\u0026#34;, # 中高相关（技术细分，间接相关） \u0026#34;智能模拟在生物医药研发中的应用，主要用于药物分子结构模拟，不属于临床医疗应用范畴-D\u0026#34;, # 干扰项（语义相近但领域不符） \u0026#34;人工智能技术在医疗领域的核心应用包括AI辅助诊断、个性化治疗方案生成、医疗影像分析等-A\u0026#34;, # 高相关 \u0026#34;机器学习算法在医保风控系统中的应用，可识别虚假就医报销行为，属于AI在医疗管理的边缘场景-D\u0026#34;, # 低相关（跨领域AI，弱关联） \u0026#34;医疗大数据平台通过机器学习算法整合患者电子病历，为医院管理决策提供数据支撑-C\u0026#34;, # 中相关（泛医疗AI，关联性减弱） ] # 3. 执行重排 # 返回带得分的结果 ranked_docs_with_scores = reranker.rerank_sorted( query=query, candidate_docs=docs, return_scores=True, top_k=None # 可指定返回前N个，如top_k=2 ) # 4. 输出结果 print(\u0026#34;\\n=== ms-marco-MiniLM-L6-v2 重排结果===\u0026#34;) print(f\u0026#34;query:{query}\u0026#34;) for idx, (doc, score) in enumerate(ranked_docs_with_scores, 1): print(f\u0026#34;TOP-{idx} | 得分：{score:.4f} 文档：{doc}\u0026#34;) 执行结果如下： 1 从执行结果可看出ms-marco-MiniLM-L6-v2 模型对中文的重排效果很差 五. jina-rerank-v3示例代码（Agentic-RAG + 多语言）\r1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 from typing import List, Tuple, Optional, Union, Dict, Any import torch from transformers import AutoModel class JinaRerankV3: \u0026#34;\u0026#34;\u0026#34; 基于 transformers.AutoModel 封装的 jina-rerank-v3 重排器 \u0026#34;\u0026#34;\u0026#34; DEFAULT_MODEL_NAME = \u0026#34;jinaai/jina-rerank-v3\u0026#34; def __init__( self, model_name_or_path: str = DEFAULT_MODEL_NAME, device: Optional[str] = None, batch_size: int = 32, # jina-rerank-v3 批量推理最优批次 trust_remote_code: bool = True, dtype: Union[str, torch.dtype] = \u0026#34;auto\u0026#34; ): \u0026#34;\u0026#34;\u0026#34; 初始化 jina-rerank-v3 重排器（基于 AutoModel 原生接口） Args: model_name_or_path: 模型本地路径或HuggingFace名称 device: 运行设备（auto/cuda/cpu） batch_size: 推理批次大小 trust_remote_code: 是否允许加载模型仓库中自定义的代码（如 modeling_*.py、等），并执行这些代码来初始化模型/配置。 dtype: 模型数据类型（auto/float16/float32） \u0026#34;\u0026#34;\u0026#34; # 设备适配 self.device = device if device else (\u0026#34;cuda\u0026#34; if torch.cuda.is_available() else \u0026#34;cpu\u0026#34;) self.batch_size = batch_size self.model_name_or_path = model_name_or_path self.trust_remote_code = trust_remote_code self.dtype = dtype if isinstance(dtype, torch.dtype) else dtype # 模型初始化 self.model: Optional[AutoModel] = None self._load_model() def _load_model(self) -\u0026gt; None: \u0026#34;\u0026#34;\u0026#34;重载模型加载方法：使用 AutoModel 加载 jina-rerank-v3\u0026#34;\u0026#34;\u0026#34; try: # 加载原生 AutoModel（jina官方推荐方式） self.model = AutoModel.from_pretrained( self.model_name_or_path, trust_remote_code=self.trust_remote_code, dtype=self.dtype, device_map=self.device # 自动分配设备 ) # 设置评估模式 self.model.eval() print( f\u0026#34;✅ Jina-Rerank-V3 模型加载完成 | 设备：{self.device} | 批次大小：{self.batch_size} | \u0026#34; f\u0026#34;数据类型：{self.dtype} | 评估模式：{self.model.training}\u0026#34; ) except Exception as e: raise RuntimeError( f\u0026#34;Jina-Rerank-V3 模型加载失败：{e}\\n\u0026#34; \u0026#34;排查建议：\\n\u0026#34; \u0026#34;1. 模型文件是否完整 \\n\u0026#34; \u0026#34;2. trust_remote_code 是否设为True \\n\u0026#34; \u0026#34;3. transformers版本是否≥4.36.0\u0026#34; ) def rerank_sorted( self, query: str, candidate_docs: List[str], return_scores: bool = True, top_k: Optional[int] = None ) -\u0026gt; Union[List[str], List[Tuple[str, float]]]: \u0026#34;\u0026#34;\u0026#34; 核心重排方法 调用 jina-rerank-v3 原生 rerank 方法，返回排序后的结果 Args: query: 查询语句（支持多语言） candidate_docs: 候选文档列表 return_scores: 是否返回相关性得分 top_k: 返回前k个结果（None返回全部） Returns: 排序后的文档列表 或 (文档, 得分) 元组列表 \u0026#34;\u0026#34;\u0026#34; # 输入合法性校验 if not candidate_docs: raise ValueError(\u0026#34;候选文档列表不能为空\u0026#34;) if self.model is None: raise RuntimeError(\u0026#34;模型未初始化，请检查模型加载是否成功\u0026#34;) # 核心推理：调用 jina 原生 rerank 方法 try: with torch.no_grad(): # 禁用梯度计算（节省显存） results = self.model.rerank( query=query, documents=candidate_docs, top_n=top_k if top_k else len(candidate_docs) ) except Exception as e: raise RuntimeError(f\u0026#34;重排推理失败：{e}\u0026#34;) # 解析结果（按得分降序排列，jina原生结果已排序） ranked_results = [ (result[\u0026#34;document\u0026#34;], float(result[\u0026#34;relevance_score\u0026#34;])) for result in results ] # 适配返回格式 if return_scores: return ranked_results else: return [item[0] for item in ranked_results] def release(self) -\u0026gt; None: \u0026#34;\u0026#34;\u0026#34;资源释放：清理模型和GPU缓存\u0026#34;\u0026#34;\u0026#34; if self.model is not None: del self.model self.model = None if torch.cuda.is_available(): torch.cuda.empty_cache() print(\u0026#34;🔌 Jina-Rerank-V3 模型资源已释放\u0026#34;) # -------------------------- 测试示例 -------------------------- if __name__ == \u0026#34;__main__\u0026#34;: # 1. 初始化重排器（本地模型路径） reranker = JinaRerankV3( model_name_or_path=\u0026#34;../../data/models_reranker_data/jinaai/jina-reranker-v3\u0026#34;, batch_size=16 ) # 2. 测试数据（多语言混合） query = \u0026#34;人工智能在医疗领域的应用\u0026#34; docs = [ \u0026#34;人工智能驱动的农业无人机可实现作物病虫害监测，精准率达85%以上-E\u0026#34;, # 无关（完全跨领域） \u0026#34;AI辅助诊断系统可快速分析CT、MRI等医学影像，使肺癌、乳腺癌等疾病的早期检出率提升30%以上-A\u0026#34;, # 高相关 \u0026#34;深度学习算法在金融风控中的应用案例，有效降低信贷违约率15%-E\u0026#34;, # 无关（金融领域） \u0026#34;卷积神经网络在皮肤癌图像分类任务上达到91%的准确率，为基层医疗机构提供了低成本诊断方案-B\u0026#34;, # 中高相关（技术细分，间接相关） \u0026#34;智能模拟在生物医药研发中的应用，主要用于药物分子结构模拟，不属于临床医疗应用范畴-D\u0026#34;, # 干扰项（语义相近但领域不符） \u0026#34;人工智能技术在医疗领域的核心应用包括AI辅助诊断、个性化治疗方案生成、医疗影像分析等-A\u0026#34;, # 高相关 \u0026#34;机器学习算法在医保风控系统中的应用，可识别虚假就医报销行为，属于AI在医疗管理的边缘场景-D\u0026#34;, # 低相关（跨领域AI，弱关联） \u0026#34;医疗大数据平台通过机器学习算法整合患者电子病历，为医院管理决策提供数据支撑-C\u0026#34;, # 中相关（泛医疗AI，关联性减弱） ] # 3. 重排结果 print(\u0026#34;===== 重排结果=====\u0026#34;) results_with_scores = reranker.rerank_sorted( query=query, candidate_docs=docs, return_scores=True ) for idx, (doc, score) in enumerate(results_with_scores, 1): print(f\u0026#34;{idx}. 得分：{score:.4f} 文档：{doc[:100]}\u0026#34;) # 4. 释放资源 reranker.release() 执行结果如下： 从执行结果可以看出jinaai/jina-rerank-v3模型对中文的reranker效果非常的好，基本上是标准答案。\n六. ColBERT v2示例代码（百万级英文候选 + 高效）\r1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 # 安装 ColBERT pip install colbert-ai # 额外依赖（适配模型加载） pip install torch transformers faiss-cpu # GPU版可装 faiss-gpu from typing import List, Tuple, Optional, Union import os import torch from colbert import Indexer, Searcher from colbert.infra import Run, RunConfig, ColBERTConfig class ColBERTReranker: \u0026#34;\u0026#34;\u0026#34; ColBERT v2 重排器（适配百万级候选集的高效检索重排） \u0026#34;\u0026#34;\u0026#34; # 默认配置（平衡精度/速度/存储） DEFAULT_CHECKPOINT = \u0026#34;colbert-ir/colbertv2.0\u0026#34; DEFAULT_TOP_K = 100 def __init__( self, checkpoint: str = DEFAULT_CHECKPOINT, index_root: str = \u0026#34;./data/colbert_index\u0026#34;, nbits: int = 2, # 2bit量化，百万级数据仅占GB级存储 doc_maxlen: int = 512, nranks: int = 1, experiment_name: str = \u0026#34;default\u0026#34;, device=None ): \u0026#34;\u0026#34;\u0026#34; 初始化 ColBERT 重排器 Args: checkpoint: ColBERT 模型权重（设置为HuggingFace模型名称/本地路径） - 1.若为模型名称：如果本地缓存（默认路径~ /.cache/huggingface/）中没有该模型，会自动从 Hugging Face Hub 下载模型权重、配置和分词器文件。 若本地已存在该模型，代码会直接加载缓存中的文件，无需重新下载。由于国内无法访问huggingface，需要设置代理或镜像源方式。 - 2.若为本地路径：则需要手动下载模型文件到路径地址. index_root: 索引存储根路径 nbits: 量化位数（1/2/4/8，位数越低存储越小、精度略降） doc_maxlen: 文档最大长度 nranks: 并行进程数（单机设1，分布式可增大） experiment_name: 实验名称（用于ColBERT日志） \u0026#34;\u0026#34;\u0026#34; # 基础配置 self.checkpoint = checkpoint self.index_root = os.path.abspath(index_root) # 索引根路径 self.experiment_name = experiment_name self.nbits = nbits self.doc_maxlen = doc_maxlen self.nranks = nranks self.device = device if device else (\u0026#34;cuda\u0026#34; if torch.cuda.is_available() else \u0026#34;cpu\u0026#34;) # 核心组件 self.indexer: Optional[Indexer] = None self.searcher: Optional[Searcher] = None self.collection: Optional[List[str]] = None # 存储原始段落集合（索引构建后缓存） # 日志提示 print(f\u0026#34;🔧 ColBERT 初始化完成 | 模型：{checkpoint} | 索引路径：{self.index_root}\u0026#34;) def _get_colbert_config(self) -\u0026gt; ColBERTConfig: \u0026#34;\u0026#34;\u0026#34;构建 ColBERT 配置对象（封装通用配置逻辑）\u0026#34;\u0026#34;\u0026#34; config = ColBERTConfig( nbits=self.nbits, root=self.index_root, experiment=self.experiment_name, # 关联实验名称 doc_maxlen=self.doc_maxlen, bsize=128, # 批量编码大小,额外优化参数（百万级场景） ) return config def build_index( self, passages: List[str], index_name: str = \u0026#34;english_kb\u0026#34;, overwrite: bool = False ) -\u0026gt; Indexer | None: \u0026#34;\u0026#34;\u0026#34; 构建 ColBERT 量化索引（支持百万级段落） Args: passages: 段落列表（支持百万级数据） index_name: 索引名称（存储在index_path下的子目录） overwrite: 是否覆盖已有索引 Returns: 初始化后的 Indexer 实例 \u0026#34;\u0026#34;\u0026#34; # 参数校验 if not passages: raise ValueError(\u0026#34;段落列表不能为空，无法构建索引\u0026#34;) # 索引路径校验，Indexer默认使用的索引路径由root（实验根路径）/experiment（实验名）/indexes/index_name（索引名）拼接 full_index_path = os.path.join(self.index_root, self.experiment_name, \u0026#34;indexes\u0026#34;, index_name) if os.path.exists(full_index_path) and not overwrite: print(f\u0026#34;索引已存在：{full_index_path} | 如需覆盖请设置 overwrite=True\u0026#34;) return # 缓存原始段落（用于检索时映射ID到文本） self.collection = passages try: print(f\u0026#34;🚀 开始构建 ColBERT 索引 | 段落数量：{len(passages):,} | 索引名称：{index_name}\u0026#34;) run_config = RunConfig( nranks=self.nranks, root=self.index_root, # 实验根路径 experiment=self.experiment_name, # 实验名 ) with Run().context(run_config): _config = self._get_colbert_config() self.indexer = Indexer(checkpoint=self.checkpoint, config=_config) # 构建索引（自动分批处理百万级数据） index_path = self.indexer.index( name=index_name, collection=passages, overwrite=overwrite ) print(f\u0026#34;✅ ColBERT 索引构建完成 | 存储路径：{index_path}\u0026#34;) return self.indexer except Exception as e: raise RuntimeError(f\u0026#34;ColBERT 索引构建失败：{str(e)}\u0026#34;) from e def load_index(self, index_name: str = \u0026#34;english_kb\u0026#34;, collection: Optional[List[str]] = None) -\u0026gt; Searcher: \u0026#34;\u0026#34;\u0026#34; 加载已构建的索引（避免重复构建，提升复用效率） Args: index_name: 索引名称 collection: 原始段落列表（若未缓存需传入，用于映射ID到文本） Returns: 初始化后的 Searcher 实例 Raises: FileNotFoundError: 索引路径不存在 \u0026#34;\u0026#34;\u0026#34; full_index_path = os.path.join(self.index_root, self.experiment_name, \u0026#34;indexes\u0026#34;, index_name) if not os.path.exists(full_index_path): raise FileNotFoundError(f\u0026#34;索引不存在：{full_index_path} | 请先调用 build_index 构建索引\u0026#34;) try: print(f\u0026#34;📥 加载 ColBERT 索引 | 路径：{full_index_path}\u0026#34;) # RunConfig 关联路径和实验名 run_config = RunConfig( nranks=self.nranks, root=self.index_root, experiment=self.experiment_name, ) with Run().context(run_config): config = self._get_colbert_config() self.searcher = Searcher( index=index_name, checkpoint=self.checkpoint, config=config ) # 关联原始集合 if collection is not None: self.searcher.collection = collection print(f\u0026#34;✅ ColBERT 索引加载完成 | 索引规模：{len(self.searcher.collection):,} 段落\u0026#34;) return self.searcher except Exception as e: raise RuntimeError(f\u0026#34;ColBERT 索引加载失败：{str(e)}\u0026#34;) from e def rerank( self, query: str, top_k: int = DEFAULT_TOP_K, index_name: str = \u0026#34;english_kb\u0026#34;, load_index_if_needed: bool = True, collection: Optional[List[str]] = None ) -\u0026gt; List[Tuple[str, float]]: \u0026#34;\u0026#34;\u0026#34; 基于 ColBERT 索引的高效重排 Args: query: 查询语句（ColBERT v2 推荐英文，中文需适配中文版模型） top_k: 返回前k条最相关段落 index_name: 索引名称 load_index_if_needed: 若未加载索引，是否自动加载 collection: 原始段落列表（若未缓存需传入，用于映射ID到文本） Returns: 排序结果列表：[(段落文本, 相关性得分), ...]（按得分降序） Raises: RuntimeError: 索引未加载且自动加载失败 \u0026#34;\u0026#34;\u0026#34; # 参数校验 if top_k \u0026lt;= 0: raise ValueError(f\u0026#34;top_k 必须大于0，当前值：{top_k}\u0026#34;) if not query.strip(): raise ValueError(\u0026#34;查询语句不能为空\u0026#34;) # 自动加载索引 if self.searcher is None and load_index_if_needed: self.load_index(index_name=index_name,collection=collection) # 校验索引是否就绪 if self.searcher is None: raise RuntimeError(\u0026#34;索引未加载 | 请先调用 build_index 或 load_index\u0026#34;) try: print(f\u0026#34;开始 ColBERT 重排 | 查询：{query[:50]}... | Top-K：{top_k}\u0026#34;) with Run().context(RunConfig(nranks=self.nranks)): # 核心检索（预编码+后期交互，百万级低延迟） results = self.searcher.search(query, k=top_k) # 解析结果：(段落ID, 得分) → (段落文本, 得分) pid_list, _, score_list = results # 拆分3个列表（忽略第二个辅助列表） ranked_results = [] for pid, score in zip(pid_list, score_list): # 按位置一一对应 try: passage_text = self.searcher.collection[pid] ranked_results.append((passage_text, float(score))) except KeyError: print(f\u0026#34;警告：pid {pid} 在collection中不存在，跳过\u0026#34;) print(f\u0026#34;ColBERT 重排完成 | 返回结果数：{len(ranked_results)}\u0026#34;) return ranked_results except Exception as e: raise RuntimeError(f\u0026#34;ColBERT 重排失败：{str(e)}\u0026#34;) from e def release(self): \u0026#34;\u0026#34;\u0026#34;释放资源\u0026#34;\u0026#34;\u0026#34; if self.indexer is not None: del self.indexer if self.searcher is not None: del self.searcher self.collection = None print(\u0026#34;✅ ColBERT 资源已释放\u0026#34;) # -------------------------- 测试示例 -------------------------- if __name__ == \u0026#34;__main__\u0026#34;: # 1. 初始化 ColBERT 重排器 colbert_reranker = ColBERTReranker( # 模型权重路径（二选一）： # 1. 填写HuggingFace模型名称，默认从HuggingFace自动加载,如：checkpoint=\u0026#34;colbert-ir/colbertv2.0\u0026#34; # 2. 填写模型本地存储路径：需提前下载模型文件到指定路径 # 模型文件获取地址：https://modelscope.cn/models/colbert-ir/colbertv2.0/ # 需下载文件：config.json、model.safetensors、special_tokens_map.json、tokenizer.json、tokenizer_config.json、vocab.txt checkpoint=\u0026#34;../../data/models_reranker_data/colbert-ir/colbertv2\u0026#34;, # 本地路径 # checkpoint=\u0026#34;./colbertv2_models\u0026#34;, # 本地路径 index_root=\u0026#34;./colbert_index\u0026#34;, # 可选扩展配置（按需启用）： # nbits=2, # 量化位数（1/2/4/8，位数越低存储越小、精度略降） # doc_maxlen=512, # 文档最大token长度（适配BERT类模型） # experiment_name=\u0026#34;colbert_v2_rerank\u0026#34;, # 实验名（用于索引路径分级） # device=\u0026#34;cuda\u0026#34;, # 运行设备（自动优先GPU，无则用CPU） # nranks=1, # 并行进程数（单机建议设1，分布式可增大） ) # 2. 模拟百万级段落（实际可替换为真实数据） test_passages = [f\u0026#34;Document {i}: RAG optimization for large-scale knowledge base {i}\u0026#34; for i in range(1000)] # 插入目标段落（高相关） test_passages[100] = \u0026#34;Document 100: ColBERT v2 achieves 10x throughput for million-scale reranking\u0026#34; test_passages[200] = \u0026#34;Document 200: ColBERT v2 is optimized for million-scale reranking with 2-bit quantization\u0026#34; test_passages[400] = \u0026#34;Document 400: Memory-efficient index for 1M+ passages using ColBERT v2 2-bit quantization\u0026#34; # 插入无关段落（测试重排精度） test_passages[150] = \u0026#34;Document 150: Weather forecast for New York on day 2 - sunny with 25°C\u0026#34; test_passages[250] = \u0026#34;Document 250: How to bake a cake - step by step guide for beginners\u0026#34; test_passages[350] = \u0026#34;Document 350: Car maintenance tips for gasoline engines 3\u0026#34; # 3. 构建索引（首次运行耗时，后续可复用） try: colbert_reranker.build_index( passages=test_passages, index_name=\u0026#34;english_kb\u0026#34;, overwrite=False # 覆盖已有索引（测试用） ) except RuntimeError as e: print(f\u0026#34;索引构建失败：{e}\u0026#34;) exit(0) # 4. 执行重排 query = \u0026#34;Which model is efficient for million-scale English reranking?\u0026#34; try: results = colbert_reranker.rerank( query=query, top_k=5, index_name=\u0026#34;english_kb\u0026#34;, collection=test_passages ) except RuntimeError as e: raise RuntimeError(f\u0026#34;重排失败：{str(e)}\u0026#34;) from e print(\u0026#34;\\n===== ColBERT v2 百万级候选重排结果（前5条）=====\u0026#34;) for i, (passage, score) in enumerate(results, 1): print(f\u0026#34;\\n{i}. 得分：{score:.4f} 文档：{passage[:80]}\u0026#34;) # 5. 释放资源 colbert_reranker.release() 执行结果： 成功快速检索到了前3条高相关文档。\n相关问题\r执行过程中，ColBERT Windows 环境 C++ 扩展编译报错问题\r问题现象\nColBERT 框架默认依赖自研的 C++ 扩展实现高性能量化 / 检索，但在 Windows 系统下运行时，易触发编译器相关报错，核心错误信息：\n1 W1230 23:40:32.872000 17580 site-packages\\torch\\utils\\cpp_extension.py:480] Error checking compiler version for cl: [WinError 2] 系统找不到指定的文件。 根因说明\n该报错本质是 Windows 系统缺少 ColBERT C++ 扩展编译所需的 MSVC 编译器环境，且 ColBERT 官方对 Windows 原生环境的兼容性支持有限。\n推荐解决方案\n最优方案：直接在 Linux 环境（如 Ubuntu 20.04+/CentOS 8+）中部署和使用 ColBERT，完全规避跨平台编译兼容问题； Windows 兼容方案：在 Windows 系统中启用 WSL2，WSL2 提供与原生 Linux 一致的运行环境，能正常编译并运行 ColBERT 的 C++ 扩展。 源码地址\r完整源码地址：\nGitHub 仓库：https://github.com/tinyseeking/tidy-agent-practice/tree/main/llm/reranker Gitee 仓库（国内）：https://gitee.com/tinyseeking/tidy-agent-practice/tree/main/llm/reranker ","date":"2026-02-01T00:00:00Z","image":"/p/%E4%B8%BB%E6%B5%81%E5%BC%80%E6%BA%90-rerank-%E6%A8%A1%E5%9E%8B%E8%A7%A3%E6%9E%90%E4%B8%8E%E9%80%89%E5%9E%8B%E6%8C%87%E5%8D%972026-%E7%89%88/images/image-1-17667340159537.png","permalink":"/p/%E4%B8%BB%E6%B5%81%E5%BC%80%E6%BA%90-rerank-%E6%A8%A1%E5%9E%8B%E8%A7%A3%E6%9E%90%E4%B8%8E%E9%80%89%E5%9E%8B%E6%8C%87%E5%8D%972026-%E7%89%88/","title":"主流开源 Rerank 模型解析与选型指南（2026 版）"},{"content":"深入解析AgentGPT：原理、实战与源码拆解\r介绍\r​ AgentGPT是一个自主AI代理平台，它使用户能够在浏览器中创建和部署可定制的自主AI代理。只需要指定一个目标，然后观看它自动开始一段激动人心的旅程，以完成指定的任务。AgentGPT通过调用大型语言模型（如GPT-4）来实现目标，旨在在无需人类干预的情况下理解目标、制定策略并交付结果。\n​ AgentGPT的基本思路是：给它一个任意目标，AgentGPT首先会对目标进行分解成一系列子任务，然后对每一项子任务进行分析思考、并按需要使用外部工具来执行任务，然后完成所有子任务。并从子任务的执行结果来学习总结，最终完成用户的目标。而且这一系列过程是自动完成的。可以把它看成是 AutoGPT与 babyAGI结合的增强版本，而且还提供了网页端，用户会话管理及历史交互的数据库存储。\n安装\r​\t本节指导您在本地安装 AgentGPT。有两种安装方式：docker安装及命令行(非docker)安装。官方建议使用 Docker 进行安装，因为Docker 简化了安装过程，所有的依赖项都已处理好，使得安装过程更加简单，出错的可能性更小。\n​\t由于我们主要是在学习，为了更好的了解项目结构、配置、及其运行过程，我们这里推荐使用命令行的方式来运行。\n前提条件\r在开始之前，需要安装了以下软件:\nGit：是一个开源的分布式版本控制系统，用于从github中下载项目代码。 Node.js：一个开源、跨平台的 JavaScript 运行时环境，运行前端时需要。 OpenAI API key ：由于国内无法直连openAI的官方接口，解决方法是可以使用第三方提供的服务，或使用azure openAl 的接口服务。推荐使用azure openAI。 Docker 方式安装\r除了上述的先决条件外，还需要安装Docker。按照以下步骤进行Docker的安装：\n下载并安装Docker Docker方式安装步骤如下：\n打开Terminal\nclone 存储库并进入目录 ，通过运行以下命令来克隆存储库并进入目录：\nMac/Linux 用户\n1 2 3 git clone https://github.com/reworkd/AgentGPT.git cd AgentGPT ./setup.sh Windows 用户\n1 2 3 git clone https://github.com/reworkd/AgentGPT.git cd AgentGPT ./setup.bat 按照运行setup.bat脚本中的设置向导，添加openAI API密钥。\n一旦所有服务都开始运行，在web浏览器上访问http://localhost:3000。\n命令行安装（非docker方式）\r一、下载源码及运行setub.bat脚本\r非Docker方式安装，同样需要下载源代码，并运行setup.sh脚本（window环境下运行setup.bat脚本）配置ENV文件，同时需要更新Prisma的配置指向本地SQLite实例。\n1 Prisma是一个开源的数据库工具和对象关系映射(ORM)框架，通过提供丰富的功能和强大的集成支持，简化了开发人员与数据库的交互过程，提高了开发效率和代码质量。Prisma与TypeScript紧密集成，确保了开发过程中的类型安全性。它支持多种数据库，包括MySQL、PostgreSQL、MongoDB等，并且支持多种编程语言，如JavaScript、TypeScript、Go等。Prisma还提供了高效、类型安全的数据访问方式，使开发者能够获得有关数据库事件的通知。 1 2 3 git clone https://github.com/reworkd/AgentGPT.git cd AgentGPT ./setup.sh 运行setub.bat脚本会引导你设置一些必要的参数，如openAI API密钥。 如果你使用的是第三方连接openAI或 azure 提供的openAI 服务时，可以先不设置，直接回车。\nAgentGPT 代码包括 platform（后端工程）和 next（前端工程）两个部分。\n脚本运行完成后，会在 platform（后端工程）目录及 next（前端工程）目录下分别创建.env文件，然后在该文件中设置OPENAI_API_KEY及数据库连接配置。使用第三方连接openAI或 azure 提供的openAI 服务时，还需要配置OPENAI_API_BASE。\n二、参数配置\r在后端工程目platform找到名为.env.template的文件，复制并将其命名为.env\n具体配置如下：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 #部署环境，默认development(开发模式)，该模式使用简化的身份验证机制，以便于测试和调试。这种方式不安全， #不应在生产环境中使用。 NODE_ENV=development NEXT_PUBLIC_VERCEL_ENV=${NODE_ENV} ...//省略部分默认配置 #使用第三方的OAuth身份验证登录时配置 (在非development模式下有效): GOOGLE_CLIENT_ID=*** GOOGLE_CLIENT_SECRET=*** GITHUB_CLIENT_ID=*** GITHUB_CLIENT_SECRET=*** DISCORD_CLIENT_SECRET=*** DISCORD_CLIENT_ID=*** # Backend: REWORKD_PLATFORM_ENVIRONMENT=${NODE_ENV} REWORKD_PLATFORM_FF_MOCK_MODE_ENABLED=false REWORKD_PLATFORM_MAX_LOOPS=${NEXT_PUBLIC_MAX_LOOPS} #重要配置OPENAI_API_KEY、REWORKD_PLATFORM_OPENAI_API_BASE #支持兼容 OpenAI 接口的模型服务(如：千问、DeepSeek、智谱等)，使用此类服务时，需对应调整 #`REWORKD_PLATFORM_OPENAI_API_KEY`、`REWORKD_PLATFORM_OPENAI_API_BASE` 参数的值。 REWORKD_PLATFORM_OPENAI_API_KEY=\u0026#34;替换为你自己的api key\u0026#34; REWORKD_PLATFORM_FRONTEND_URL=http://localhost:3000 REWORKD_PLATFORM_RELOAD=true #若使用第三方连接openAI、azure提供的openAI服务、兼容 OpenAI 接口的模型服务，配置请求的端口 REWORKD_PLATFORM_OPENAI_API_BASE=https://api.chatanywhere.tech REWORKD_PLATFORM_SERP_API_KEY=\u0026#34;\u0026#34; REWORKD_PLATFORM_REPLICATE_API_KEY=\u0026#34;\u0026#34; # 后端连接数据库配置 (Backend): REWORKD_PLATFORM_DATABASE_USER=root #连接数据库的用户名 REWORKD_PLATFORM_DATABASE_PASSWORD=修改为你自己的数据库密钥 #连接数据库的密码 REWORKD_PLATFORM_DATABASE_HOST=localhost #连接数据库的IP地址 REWORKD_PLATFORM_DATABASE_PORT=3306 #连接数据库的IP端口 REWORKD_PLATFORM_DATABASE_NAME=reworkd_platform #连接数据库名称 REWORKD_PLATFORM_DATABASE_URL=mysql://${REWORKD_PLATFORM_DATABASE_USER}:${REWORKD_PLATFORM_DATABASE_PASSWORD}@${REWORKD_PLATFORM_DATABASE_HOST}:${REWORKD_PLATFORM_DATABASE_PORT}/${REWORKD_PLATFORM_DATABASE_NAME} 修改next/.env 文件，具体配置如下：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 #部署环境，默认development(开发模式)，该模式使用简化的身份验证机制，以便于测试和调试。这种方式不安全， #不应在生产环境中使用。 NODE_ENV=development NEXT_PUBLIC_VERCEL_ENV=${NODE_ENV} # NextJS 与后端交互的URL: NEXT_PUBLIC_BACKEND_URL=http://localhost:8000 NEXT_PUBLIC_MAX_LOOPS=100 # Next Auth config: NEXTAUTH_SECRET=Ak+3iVAMOMLeLX2ahh9ZyAUIeoMs2YAnyNyHR9VKhwk= NEXTAUTH_URL=http://localhost:3000 #使用第三方的OAuth身份验证登录时配置 (在非development模式下有效): GOOGLE_CLIENT_ID=*** GOOGLE_CLIENT_SECRET=*** GITHUB_CLIENT_ID=*** GITHUB_CLIENT_SECRET=*** DISCORD_CLIENT_SECRET=*** DISCORD_CLIENT_ID=*** # 前端连接数据库配置 (Frontend): DATABASE_USER=root DATABASE_PASSWORD=修改为你自己的数据库密钥 DATABASE_HOST=localhost DATABASE_PORT=3306 DATABASE_NAME=reworkd_platform DATABASE_URL=mysql://${DATABASE_USER}:${DATABASE_PASSWORD}@${DATABASE_HOST}:${DATABASE_PORT}/${DATABASE_NAME} 三、数据库表结构初始化\rAgentGPT使用数据库来持久化数据，如保存用户的注册信息及自定义Agent的历史交互等信息。\n由于在官方下载的源文件中，找到的相关数据库初始化文件是\\db\\setup.sql ，但在该文件中，只有创建数据库的执行语句，并没有数据库表结构初始化的执行语句。所以我这里参照源代码整理了一份，在mysql数据库中，按setup.sql中的语句，创建好数据库reworkd_platform后，再执行以下语句创建表结构：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 -- ---------------------------- -- Table structure for agent -- ---------------------------- DROP TABLE IF EXISTS `agent`; CREATE TABLE `agent` ( `id` varchar(255) NOT NULL, `userId` varchar(255) DEFAULT NULL, `name` varchar(255) DEFAULT NULL, `goal` varchar(255) DEFAULT NULL, `deleteDate` datetime DEFAULT NULL, `createDate` datetime DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -- ---------------------------- -- Table structure for agent_run -- ---------------------------- DROP TABLE IF EXISTS `agent_run`; CREATE TABLE `agent_run` ( `id` varchar(255) NOT NULL, `user_id` varchar(255) DEFAULT NULL, `goal` varchar(500) DEFAULT NULL, `create_date` datetime DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -- ---------------------------- -- Table structure for agent_task -- ---------------------------- DROP TABLE IF EXISTS `agent_task`; CREATE TABLE `agent_task` ( `id` varchar(255) NOT NULL, `run_id` varchar(255) DEFAULT NULL, `type` varchar(255) DEFAULT NULL, `create_date` datetime DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -- ---------------------------- -- Table structure for agenttask -- ---------------------------- DROP TABLE IF EXISTS `agenttask`; CREATE TABLE `agenttask` ( `id` varchar(255) NOT NULL, `agentId` varchar(255) DEFAULT NULL, `type` varchar(100) DEFAULT NULL, `status` varchar(255) DEFAULT NULL, `value` text CHARACTER SET utf8mb4, `info` text CHARACTER SET utf8mb4, `sort` int(11) DEFAULT NULL, `createDate` datetime DEFAULT NULL, `deleteDate` datetime DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -- ---------------------------- -- Table structure for oauth_credentials -- ---------------------------- DROP TABLE IF EXISTS `oauth_credentials`; CREATE TABLE `oauth_credentials` ( `id` varchar(100) NOT NULL, `user_id` varchar(255) DEFAULT NULL, `organization_id` varchar(255) DEFAULT NULL, `provider` varchar(255) DEFAULT NULL, `state` varchar(255) DEFAULT NULL, `redirect_uri` varchar(255) DEFAULT NULL, `token_type` varchar(255) DEFAULT NULL, `access_token_enc` varchar(255) DEFAULT NULL, `access_token_expiration` varchar(255) DEFAULT NULL, `refresh_token_enc` varchar(255) DEFAULT NULL, `scope` varchar(255) NOT NULL, `create_date` datetime DEFAULT NULL, `update_date` datetime DEFAULT NULL, `delete_date` datetime DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -- ---------------------------- -- Table structure for organization -- ---------------------------- DROP TABLE IF EXISTS `organization`; CREATE TABLE `organization` ( `id` varchar(255) NOT NULL, `name` varchar(255) DEFAULT NULL, `created_by` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -- ---------------------------- -- Table structure for organization_user -- ---------------------------- DROP TABLE IF EXISTS `organization_user`; CREATE TABLE `organization_user` ( `id` varchar(255) NOT NULL, `user_id` varchar(255) DEFAULT NULL, `organization_id` varchar(255) DEFAULT NULL, `role` varchar(255) DEFAULT NULL, `create_date` datetime DEFAULT NULL, `update_date` datetime DEFAULT NULL, `delete_date` datetime DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -- ---------------------------- -- Table structure for session -- ---------------------------- DROP TABLE IF EXISTS `session`; CREATE TABLE `session` ( `id` varchar(255) NOT NULL, `sessionToken` varchar(255) DEFAULT NULL, `userId` varchar(255) DEFAULT NULL, `expires` datetime DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -- ---------------------------- -- Table structure for user -- ---------------------------- DROP TABLE IF EXISTS `user`; CREATE TABLE `user` ( `id` varchar(100) NOT NULL, `name` varchar(255) DEFAULT NULL, `email` varchar(255) DEFAULT NULL, `emailVerified` varchar(10) DEFAULT NULL, `image` varchar(255) DEFAULT NULL, `super_admin` tinyint(4) DEFAULT NULL, `createDate` datetime DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 四、运行项目\r配置好参数及创建了库表结构之后，运行以下命令来配置Next.js项目：\n运行前端项目，cd 到下载根目录的next文件夹下，执行以下命令：\n1 2 3 4 // Frontend cd ./next npm install npm run dev 打开另一个窗口中，运行以下命令来启动后端:\n1 2 3 4 // 确保您处于项目的根目标 cd ./platform poetry install poetry run python -m reworkd_platform 所有服务启动后，您可以在浏览器输入 http://localhost:3000 即可访问。\n运行成功，会出现如下界面：\n使用示例\r登录\r运行成功后，首先需要登录后才能使用，点击左下角的Sign in 链接，弹出对话框如下所示：\n点击Sign in 按钮，弹出登录界面，如下所示：\n由于我们之前配置的是开发模式NODE_ENV=development(.env文件中)，该模式简化了身份验证机制，只需要输入用户名即可，便于测试和调试。如果在生产环境中，需要修改为production。\n输入用户名，即可登录，登录后左下角会显示用户名，左边菜单显示之前创建的Agent列表。如下图所示：\n输入目标\ragentGPT预先配置了三个Agent（TravelGPT、CalculusGPT、HustleGPT），您可以点击其中一个进行运行，也可以在下面的目标框中输入你要实现的目标，然后点击下面的运行按钮开始运行。\n作为测试，我这里点击已配置的HustleGPT Agent，实现的目标是“Create a comprehensive report for how to scale a startup to 1000 customers “ ，翻译为中文为“编写一份详尽的报告，介绍如何将初创企业扩大到拥有1000名客户” 。接下来，我们就看着AengtGPT自动的为我们完成任务了。\n目标分解\rAgentGPT 首先会对目标进行分解，生成一系列的子任务，这里生成了5个子任务，如下所示：\n执行子任务\r接着，AgentGPT会自动的依次完成每一个子任务， 在完成任务过程中，首先会对任务进行分析决策，返回要如何处理的方案，如是否需要调用工具搜索外部数据或执行代码等等。然后根据处理方案进行执行以完成任务。\n如：这里处理“如何制定初创企业整长策略吸引更多客户”子任务时，对该任务分析决策的结果是，先进行web搜索，根据搜索的结果生成总结。如下所示：\n生成总结\r在完成所有子任务后，还可以生成总结。如下图所示：\n点击“Summarize”按钮，则AgentGPT会根据目标、所有的子任务及其执行结果来生成总结。如下图所示：\n最后，还可以导出为图片或PDF，如上图画红框所示。\n运行原理\r​\tAgentGPT项目包含前端和后端，主要的运行流程如下：\n用户输入一个目标，前端向后端发送请求/start（启动目标Agent），后端根据用户的目标构建提示词调用大模型，返回实现目标的子任务列表。\n前端依次遍历处理每一个子任务，先向后端发送请求/analyze （任务分析请求）,传入参数：目标，当前任务及可用的工具列表。后端接收请求，调用大模型对任务进行分析决策，返回如何处理该任务的行动命令（action）。返回示例如下：\n然后，前端向后端发送请求/execute（任务执行请求）,传入参数：目标、当前任务、上一步返回的任务分析结果(包含执行的行动命令)。后端执行参数中指定的行动命令，以完成该任务。返回任务完成的结果。\n1 AgentGPT中默认集成了些工具命令如：Search（谷歌搜索）、Image（用于素描、绘制或生成图像）、Code（编写和审查代码）、SID（查找私人信息）。 重复执行2、3步骤，直到所有子任务都执行完成。\n执行完成后，可以对所有子任务的执行结果进行总结，用户点击总结按钮，前端向后端发送请求/summarize（总结请求），传入参数：目标、所有子任务的执行结果。后端调用大模型生成总结信息并返回。\n另外，在第一步前端调用/start返回后会创建agent，并保存agent信息到数据库agent表中。在2、3、5步执行后也会更新agent任务的执行信息到表agent_task（任务执行事件记录）、agentTask（任务执行详细记录）中。所以AgentGPT，其实记录了用户历史的Agent交互记录。\n另外，在第一步前端调用/start返回后会创建agent，并保存agent信息到数据库中。在2、3、5步执行后也会保存agent任务的执行信息到数据库中，并更新agent任务的执行状态。所以AgentGPT，其实记录了用户历史的Agent交互记录。\n源码解析\rAgentGPT项目包含两个部分，前端及后端工程，前端采用next框架，后端采用Uvicorn +FastAPI 架构。\n后端源码在reworkd_platform目录下，其代码结构如下：\ndb：定义实体对象，与数据库表结构的映射关系，及CRUD操作。 schemas：主要定义一些请求传输对象的结构及其数据验证信息。如AgentRun、UserBase等对象的字段及数据验证，验证使用Pydantic库。 services：定义一些通用的服务类，如：AWS的S3存储服务、pinecone向量数据库的添加、查询服务、Token的计算服务、安全加解密服务等。 tests：测试相关的代码。 web：处理前端发送的请求。 /api/agent：agent处理的核心，包含定义处理agent请求的接口，及具体实现逻辑的服务类。 /api/auth：授权相关操作，定义第三方oauth的授权登录等。 /api/memory：增加记忆功能，如将任务生成嵌入到向量数据库中，后续可从向量数据库中搜索相似性任务上下文。 /api/monitoring：出错处理及健康检测接口服务。 接下来我们主要根据运行流程，来深入源码了解其实现细节。\n一、前端向后端发送请求/start（启动Agent）\r用户输入目标，点击运行后。前端向后端发送请求/start，后端根据用户的目标构建提示词调用大模型，返回实现目标的子任务列表。该请求接口的处理代码在/web/api/agent/views.py中定义，代码如下：\n1 2 3 4 5 6 7 8 9 10 11 #定义处理post请求\u0026#34;/start\u0026#34; @router.post( \u0026#34;/start\u0026#34;, ) async def start_tasks( req_body: AgentRun = Depends(agent_start_validator), agent_service: AgentService = Depends(get_agent_service(agent_start_validator)), ) -\u0026gt; NewTasksResponse: #调用agent_service服务的start_goal_agent方法，传入目标，获取新的任务列表，并返回给前端. new_tasks = await agent_service.start_goal_agent(goal=req_body.goal) return NewTasksResponse(newTasks=new_tasks, run_id=req_body.run_id) 调用agent_service服务的start_goal_agent方法，传入目标，获取新的任务列表。agent_service的实现类是/web/api/agent/agent_service/open_ai_agent_service.py，其中start_goal_agent方法的实现如下：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 #传入目标，构造提示，调用大模型生成新的任务列表，并按字符串数组格式返回. async def start_goal_agent(self, *, goal: str) -\u0026gt; List[str]: #传入提示词模板 start_goal_prompt，构造聊天模型的提示. prompt = ChatPromptTemplate.from_messages( [SystemMessagePromptTemplate(prompt=start_goal_prompt)] ) #计算并设置模型输出的最大token数。OpenAI 计算token数是合并计算 Prompt和 Completion 的总 #token数,要求总 token 数不能超过模型上限(如默认模型 token 上限为 4096). #计算公式为:max_allowed_tokens(模型许可的最大长度)-prompt_token(提示词的token长度). self.token_service.calculate_max_tokens( self.model, prompt.format_prompt( goal=goal, language=self.settings.language, ).to_string(), ) #调用大模型并进行容错处理，返回生成的新任务列表响应. completion = await call_model_with_handling( self.model, ChatPromptTemplate.from_messages( [SystemMessagePromptTemplate(prompt=start_goal_prompt)] ), {\u0026#34;goal\u0026#34;: goal, \u0026#34;language\u0026#34;: self.settings.language}, settings=self.settings, callbacks=self.callbacks, ) #解析大模型返回的响应数据，格式为json的字符串数组 task_output_parser = TaskOutputParser(completed_tasks=[]) tasks = parse_with_handling(task_output_parser, completion) return tasks 这里主要的是使用提示start_goal_prompt，该提示其实是遵循 Plan-and-Solve 框架的提示模板来生成新任务列表，提示信息如下：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 # Create initial tasks using plan and solve prompting # https://github.com/AGI-Edgerunners/Plan-and-Solve-Prompting start_goal_prompt = PromptTemplate( template=\u0026#34;\u0026#34;\u0026#34;You are a task creation AI called AgentGPT. You answer in the \u0026#34;{language}\u0026#34; language. You have the following objective \u0026#34;{goal}\u0026#34;. Return a list of search queries that would be required to answer the entirety of the objective. Limit the list to a maximum of 5 queries. Ensure the queries are as succinct as possible. For simple questions use a single query. Return the response as a JSON array of strings. Examples: query: \u0026#34;Who is considered the best NBA player in the current season?\u0026#34;, answer: [\u0026#34;current NBA MVP candidates\u0026#34;] query: \u0026#34;How does the Olympicpayroll brand currently stand in the market, and what are its prospects and strategies for expansion in NJ, NY, and PA?\u0026#34;, answer: [\u0026#34;Olympicpayroll brand comprehensive analysis 2023\u0026#34;, \u0026#34;customer reviews of Olympicpayroll.com\u0026#34;, \u0026#34;Olympicpayroll market position analysis\u0026#34;, \u0026#34;payroll industry trends forecast 2023-2025\u0026#34;, \u0026#34;payroll services expansion strategies in NJ, NY, PA\u0026#34;] query: \u0026#34;How can I create a function to add weight to edges in a digraph using {language}?\u0026#34;, answer: [\u0026#34;algorithm to add weight to digraph edge in {language}\u0026#34;] query: \u0026#34;What is the current weather in New York?\u0026#34;, answer: [\u0026#34;current weather in New York\u0026#34;] query: \u0026#34;5 + 5?\u0026#34;, answer: [\u0026#34;Sum of 5 and 5\u0026#34;] query: \u0026#34;What is a good homemade recipe for KFC-style chicken?\u0026#34;, answer: [\u0026#34;KFC style chicken recipe at home\u0026#34;] query: \u0026#34;What are the nutritional values of almond milk and soy milk?\u0026#34;, answer: [\u0026#34;nutritional information of almond milk\u0026#34;, \u0026#34;nutritional information of soy milk\u0026#34;]\u0026#34;\u0026#34;\u0026#34;, input_variables=[\u0026#34;goal\u0026#34;, \u0026#34;language\u0026#34;], ) 翻译如下：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 使用plan and solve提示创建初始任务列表 # https：//github.com/AGI-Edgerunners/Plan-and-Solve-Prompting 开始目标提示模板： 模板内容为：“你是一个名为AgentGPT的任务创建AI。 你用{language}回答问题。你的目标是{goal}。 返回一个需要回答整个目标的搜索查询列表。 将列表限制在最多5个查询。确保查询尽可能简洁。 对于简单的问题使用单个查询。 返回JSON格式的字符串数组响应。示例： 查询：“谁被认为是当前赛季NBA最好的球员？”，答案：“当前NBA MVP候选人” 查询：“奥林匹克支付品牌在市场上目前处于何种地位？它在新泽西州、纽约州和宾夕法尼亚州的扩张前景和战略是什么？”，答案：“奥林匹克支付品牌全面分析2023”、“Olympicpayroll.com客户评价”、“奥林匹克支付市场地位分析”、“薪资行业趋势预测2023-2025”、“薪资服务在新泽西州、纽约州和宾夕法尼亚州的扩张策略” 查询：“如何使用 {language} 创建一个为有向图中的边添加权重的函数？”，答案：“{语言} 中为有向图边添加权重的算法” 查询：“纽约的当前天气如何？”，答案：“纽约的当前天气” 查询：“5 + 5等于多少？”，答案：“5和5的和” 查询：“有什么好的自制肯德基鸡肉配方？”，答案：“在家制作肯德基风格鸡肉的食谱” 查询：“杏仁奶和豆浆的营养成分是什么？”，答案：“杏仁奶的营养信息”，“豆浆的营养信息” 二、前端先向后端发送请求/analyze （任务分析请求）\r前端依次遍历处理每一个子任务，先向后端发送请求/analyze （任务分析请求）,传入参数：目标，当前任务及可用的工具列表。后端接收请求，调用大模型对任务进行分析决策，返回如何处理该任务的行动命令（action）。\n该请求接口的处理代码同样在/web/api/agent/views.py中定义，代码如下：\n1 2 3 4 5 6 7 8 9 10 11 12 #定义处理post请求\u0026#34;/analyze\u0026#34; @router.post(\u0026#34;/analyze\u0026#34;) async def analyze_tasks( req_body: AgentTaskAnalyze = Depends(agent_analyze_validator), agent_service: AgentService = Depends(get_agent_service(agent_analyze_validator)), ) -\u0026gt; Analysis: #调用agent_service服务的analyze_task_agent方法，传入目标，当前任务，可用的工具列表.返回如何处理 #该任务的行动命令 return await agent_service.analyze_task_agent( goal=req_body.goal, task=req_body.task or \u0026#34;\u0026#34;, tool_names=req_body.tool_names or [], ) 调用agent_service服务的analyze_task_agent方法。该方法实际调用大模型对任务进行分析决策，返回需要执行的行动命令。实现类是/web/api/agent/agent_service/open_ai_agent_service.py，其中该方法的实现如下：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 #根据传入的目标、当前任务、可用工具列表，构造提示，调用大模型获取如何处理该任务的行动命令， async def analyze_task_agent( self, *, goal: str, task: str, tool_names: List[str] ) -\u0026gt; Analysis: #获取可用工具列表 user_tools = await get_user_tools(tool_names, self.user, self.oauth_crud) #构建Function Calling 调用工具的信息描述,让大模型来选择使用哪个工具(或Function)，大模型实际 #上并不会真的执行函数，它会根据你给与它的上下文信息来推断要执行的函数，以及函数对应的Arguments， #然后以JSON的方式输出函数名和参数信息，随后就可以根据JSON信息来真正的执行Function。 functions = list(map(get_tool_function, user_tools)) #使用analyze_task_prompt提示模板构建提示，传入目标、当前任务 prompt = analyze_task_prompt.format_prompt( goal=goal, task=task, language=self.settings.language, ) #计算并设置模型输出的最大token数，原因之前已经说明 self.token_service.calculate_max_tokens( self.model, prompt.to_string(), str(functions), ) #调用大模型并进行容错处理，返回响应结果.返回下一步要使用哪个工具(或Function)的决策。 message = await openai_error_handler( func=self.model.apredict_messages, messages=prompt.to_messages(), functions=functions, settings=self.settings, callbacks=self.callbacks, ) #获取大模型响应的下一步需要执行的函数信息及其参数 function_call = message.additional_kwargs.get(\u0026#34;function_call\u0026#34;, {}) completion = function_call.get(\u0026#34;arguments\u0026#34;, \u0026#34;\u0026#34;) try: #解析调用参数 pydantic_parser = PydanticOutputParser(pydantic_object=AnalysisArguments) analysis_arguments = parse_with_handling(pydantic_parser, completion) #构造返回数据对象Analysis,格式是：{action:str, reasoning: str,arg: str} return Analysis( action=function_call.get(\u0026#34;name\u0026#34;, get_tool_name(get_default_tool())), **analysis_arguments.dict(), ) except (OpenAIError, ValidationError): return Analysis.get_default_analysis(task) 这里关键使用的提示是analyze_task_prompt，该提示信息如下：\n1 2 3 4 5 6 7 8 9 10 11 template=\u0026#34;\u0026#34;\u0026#34; High level objective: \u0026#34;{goal}\u0026#34; Current task: \u0026#34;{task}\u0026#34; Based on this information, use the best function to make progress or accomplish the task entirely. Select the correct function by being smart and efficient. Ensure \u0026#34;reasoning\u0026#34; and only \u0026#34;reasoning\u0026#34; is in the {language} language. Note you MUST select a function. \u0026#34;\u0026#34;\u0026#34;, input_variables=[\u0026#34;goal\u0026#34;, \u0026#34;task\u0026#34;, \u0026#34;language\u0026#34;], 翻译如下:\n1 2 3 4 5 6 7 8 9 10 template= \u0026#34; \u0026#34; 高级目标:“{goal}” 当前任务:“{task}” 基于这些信息，选择最佳的函数来取得进展或完成任务。 聪明高效地选择正确的函数。确保“推理”并且只有“推理”使用在{language}中定义的语言。 注意你必须选择一个函数。 ”“”, Input_variables =[\u0026#34;goal\u0026#34;， \u0026#34;task\u0026#34;， \u0026#34;language\u0026#34;]， 三、前端再向后端发送请求/execute（任务执行请求）\r上一步对任务进行分析决策,获取完成该任务要执行的行动命令（action）。然后前端向后端发送请求/execute（任务执行请求）,传入参数：目标、当前任务、上一步返回的分析决策结果(包含行动命令）。后端执行参数中指定的行动命令，以完成该任务,返回任务完成的结果。\n该请求接口的处理代码同样在/web/api/agent/views.py中定义，代码如下：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 #定义处理post请求\u0026#34;/execute\u0026#34; @router.post(\u0026#34;/execute\u0026#34;) async def execute_tasks( req_body: AgentTaskExecute = Depends(agent_execute_validator), agent_service: AgentService = Depends( get_agent_service(validator=agent_execute_validator, streaming=True), ), ) -\u0026gt; FastAPIStreamingResponse: #调用agent_service服务的execute_task_agent方法，传入参数：目标、当前任务、上一步的分析决策。 #返回执行动作的结果. return await agent_service.execute_task_agent( goal=req_body.goal or \u0026#34;\u0026#34;, task=req_body.task or \u0026#34;\u0026#34;, analysis=req_body.analysis, ) 调用agent_service服务的execute_task_agent方法。该方法执行上一步分析决策的行动命令,并返回执行结果。实现类是/web/api/agent/agent_service/open_ai_agent_service.py，其中该方法的实现如下：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 # 根据命令名称获取对应的工具类，实例化工具类,调用其call方法进行执行，返回执行结果。 async def execute_task_agent( self, *, goal: str, task: str, analysis: Analysis, ) -\u0026gt; StreamingResponse: # TODO: 更成熟的max_tokens计算方法 if self.model.max_tokens \u0026gt; 3000: self.model.max_tokens = max(self.model.max_tokens - 1000, 3000) #根据命令名称获取对应的工具类 tool_class = get_tool_from_name(analysis.action) #实例化工具类,调用其call方法进行执行 return await tool_class(self.model, self.settings.language).call( goal, task, analysis.arg, self.user, self.oauth_crud, ) AgentGPT中默认集成了些工具，如：Search（谷歌搜索）、Image（用于素描、绘制或生成图像）、Code（编写和审查代码）、SID（查找私人信息）。\n四、前端向后端发送请求/summarize（总结请求）\r当所有子任务都执行完成后，可以对执行结果进行总结，用户点击总结按钮，前端向后端发送请求/summarize（总结请求），传入参数：目标、所有子任务的执行结果。后端调用大模型生成总结信息并返回。\n该请求接口的处理代码同样在/web/api/agent/views.py中定义，代码如下：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #定义处理post请求\u0026#34;/summarize\u0026#34; @router.post(\u0026#34;/summarize\u0026#34;) async def summarize( req_body: AgentSummarize = Depends(agent_summarize_validator), agent_service: AgentService = Depends( get_agent_service( validator=agent_summarize_validator, streaming=True, llm_model=\u0026#34;gpt-3.5-turbo-16k\u0026#34;, ), ), ) -\u0026gt; FastAPIStreamingResponse: #调用agent_service服务的summarize_task_agent方法，传入参数：目标，所有子任务的执行结果列表。返回 #生成的总结信息。 return await agent_service.summarize_task_agent( goal=req_body.goal or \u0026#34;\u0026#34;, results=req_body.results, ) 调用agent_service服务的summarize_task_agent方法。该方法根据所有子任务的执行结果列表、目标构建提示，调用大模型生成总结信息响应。实现类是/web/api/agent/agent_service/open_ai_agent_service.py，其中该方法的实现如下：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 #根据所有子任务的执行结果列表、目标构建提示，调用大模型生成总结信息响应 async def summarize_task_agent( self, *, goal: str, results: List[str], ) -\u0026gt; FastAPIStreamingResponse: self.model.model_name = \u0026#34;gpt-3.5-turbo-16k\u0026#34; self.model.max_tokens = 8000 # Total tokens = prompt tokens + completion tokens snippet_max_tokens = 7000 # Leave room for the rest of the prompt #计算模型输出的最大token数 text_tokens = self.token_service.tokenize(\u0026#34;\u0026#34;.join(results)) #截取最大token数范围内的文本 text = self.token_service.detokenize(text_tokens[0:snippet_max_tokens]) logger.info(f\u0026#34;Summarizing text: {text}\u0026#34;) #调用大模型，生成总结响应 return summarize( model=self.model, language=self.settings.language, goal=goal, text=text, ) 执行 summarize 方法，其内部其实是构建langchain 的chain，调用大模型，生成总结响应。 代码如下：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 def summarize( model: BaseChatModel, language: str, goal: str, text: str, ) -\u0026gt; FastAPIStreamingResponse: from reworkd_platform.web.api.agent.prompts import summarize_prompt #构建langchain 的chain chain = LLMChain(llm=model, prompt=summarize_prompt) #生成总结并流式响应返回 return StreamingResponse.from_chain( chain, { \u0026#34;goal\u0026#34;: goal, \u0026#34;language\u0026#34;: language, \u0026#34;text\u0026#34;: text, }, media_type=\u0026#34;text/event-stream\u0026#34;, ) 五、前端获取外部的工具列表\rAgentGPT前端在启动Agent之前，会请求接口/tools获取用户的可用工具列表。在向后端发送请求/analyze （任务分析请求）,对任务进行分析决策，获取下一步如何处理该任务的行动命令时，作为可用工具列表参数传入。\n该请求接口的处理代码在/web/api/agent/views.py中定义，代码如下：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #定义处理get请求\u0026#34;/tools\u0026#34; router.get(\u0026#34;/tools\u0026#34;) async def get_user_tools() -\u0026gt; ToolsResponse: #获取外部的工具列表，默认定义的有三个：Image（用于素描、绘制或生成图像）、Code（编写和审查代码）、 #SID（查找私人信息） tools = get_external_tools() #构建ToolModel对象数组，ToolModel对象包含名称、描述、颜色、图片URL formatted_tools = [ ToolModel( name=get_tool_name(tool), description=tool.public_description, color=\u0026#34;TODO: Change to image of tool\u0026#34;, image_url=tool.image_url, ) for tool in tools if tool.available() ] return ToolsResponse(tools=formatted_tools) 以上这些就是AgentGPT的核心接口。\n剩下的还有些非主流程的辅助功能，如：\n授权相关操作（如第三方oauth的授权登录），接口定义在reworkd_platform/web/api/auth/views.py中。 出错处理及健康检测接口服务，接口定义在reworkd_platform/web/api/monitoring/views.py中。 实现也相对比较简单，这里就不再列出来，有兴趣的朋友，可以进到相应的目录文件中查看。\n项目相关代码及文件放在网盘中，地址：\n链接：https://pan.quark.cn/s/11ca682f0ec7 提取码：KXJr\n参考\rhttps://github.com/reworkd/AgentGPT\nhttps://docs.reworkd.ai/development/setup\n","date":"2026-01-28T00:00:00Z","image":"/p/%E6%B7%B1%E5%85%A5%E8%A7%A3%E6%9E%90agentgpt%E5%8E%9F%E7%90%86%E5%AE%9E%E6%88%98%E4%B8%8E%E6%BA%90%E7%A0%81%E6%8B%86%E8%A7%A3/images/image-20240826145321939.png","permalink":"/p/%E6%B7%B1%E5%85%A5%E8%A7%A3%E6%9E%90agentgpt%E5%8E%9F%E7%90%86%E5%AE%9E%E6%88%98%E4%B8%8E%E6%BA%90%E7%A0%81%E6%8B%86%E8%A7%A3/","title":"深入解析AgentGPT：原理、实战与源码拆解"},{"content":"深入解析BabyAGI：原理、实战与源码拆解\r什么是BabyAGI？\r​ BabyAGI是AI驱动的任务管理系统的一个实例，它使用 OpenAI 和向量数据库（例如 Chroma 或 Weaviate）来创建、优先级排序和执行任务。\n​ 该系统背后的主要思想是：根据先前任务执行的结果和预定义的目标创建任务。该系统是通过使用 OpenAI 的自然语言处理（NLP）的能力根据目标创建新任务，并使用Chroma/Weaveate存储并检索任务的结果来构建提示上下文。这是原始的 Task-Driven Autonomous Agent （任务驱动的自治代理，2023年3月28日）的简化版本。\n工作原理\r该系统通过在一个无限循环中运行，该循环执行以下步骤：\n从任务列表中拉取第一个未完成的任务。首次运行时，获取的是用户配置的初始任务和目标。 将目标及任务构建提示词，发送给执行代理(execution_agent)，执行代理调用 OpenAI 的 API 根据上下文完成任务，获取任务的响应结果。 丰富响应结果，并将其存储在Chroma / Weaviate(向量数据库)中。 使用**任务创建代理(task_creation_agent)根据目标和上一个任务的结果创建新任务，并使用排序代理(prioritization_agent)**重新排列任务列表的优先级。 并继续到第一步，不断循环，直到完成所有任务。 从上面的流程图可以看到，该执行过程，主要包括三个代理动作：\n任务执行代理（execution_agent）\n执行代理是系统的核心，它利用OpenAI的API来处理任务。该代理的实现是 execution_agent() 函数，该函数采用两个参数：目标和任务。向OpenAI的API发送提示，提示包括 AI 系统任务、目标和任务本身的描述，然后返回字符串（string ）形式任务结果。\n任务创建代理（task_creation_agent）\n任务创建代理，使用OpenAI的API基于当前对象和之前任务执行的结果创建新任务。其实现是task_creation_agent（）函数，该函数有四个参数：目标、上一个任务的结果、任务描述和当前任务列表。它向OpenAI的API发送提示，并以字符串形式返回新任务列表。然后，该函数将新任务列表作为字典列表返回，其中每个字典都包含任务的名称。\n排序代理（prioritization_agent）\n排序代理，用于重新排列任务列表优先级。其实现是prioritization_agent()函数，该函数接受一个参数，即当前任务的 ID。向OpenAI的API发送提示，返回已重新排序的新任务列表(以数字编号)。\n另外，使用Chroma/Weaveate来存储和检索任务结果以获取上下文。根据table_name变量中指定的表名创建Chroma/Weaveate集合（collection）。然后使用Chroma/Weaveate将任务的结果与任务名称及其它元数据一起存储在集合中。\n如何使用\r使用BabyAGI，按照以下步骤操作：\n通过命令 git clone https://github.com/yoheinakajima/babyagi.git 克隆 repository(仓库), 然后使用cd进入克隆的存储目录.\n安装所需的软件包：pip install -r requirements.txt\n将.env.example文件复制一份，并把文件名改为 .env。也可以执行命令：cp .env.example .env。并按需设置以下（4-8步骤）的变量。\n在OpenAI_API_key中设置OpenAI API密钥、配置OpenAI_PI_MODEL变量。\n默认使用的模型是openAI的gpt-3.5-turbo。支持所有OpenAI模型，以及Llama.cpp中的Llama及其变体，要使用其他模型，请通过LLM_model设置或使用命令行来指定。\n默认使用Chroma作为向量数据库，如果要使用Weaviate，还需要设置相关的附加变量，详见此处.\n若是使用第三方代理而不是直接连接openAI，则还需要配置OPENAI_API_BASE 参数。\n同时，该项目也支持千问、DeepSeek、智谱等兼容 OpenAI 接口的第三方模型服务，使用此类服务时，需对应调整 LLM_MODEL、OPENAI_API_KEY、OPENAI_API_BASE 三个参数的值。\n如下配置所示：\n1 2 3 4 LLM_MODEL=gpt-3.5-turbo # alternatively, gpt-4, text-davinci-003, etc OPENAI_API_KEY= Replace with your key #替换为你的key OPENAI_API_BASE=https://api.xxx.com/ #使用第三方代理时需配置 在RESULTS_STORE_NAME变量中设置表的名称。任务结果存储在这里。\n（可选）在 BABY_NAME 变量中设置BabyAGI实例的名称。\n（可选）在 OBJECTIVE 变量中设置任务管理系统的目标。\n（可选）在 INITIAL_TASK 变量中设置系统的第一个任务。\n运行脚本：python babyagi.py\n上述所有可选值也可以在命令行上指定。\n在docker 容器内运行\r你也可以使用docker容器来运行，需要先安装 Docker 和 Docker Compose。Docker Desktop 是最简单的选择，到地址https://www.docker.com/products/docker-desktop/下载安装即可：\n要在 Docker 容器中运行系统，请按照上述步骤设置 .env 文件，然后运行以下命令：\n1 docker-compose up 1 注意：BabyAGI是一个任务管理系统，被设计为连续运行。这可能导致API的高使用率、内存和资金。所以，请务必谨慎使用，同时监控系统的行为以及确保应用程序的有效性非常重要。 运行示例\r我们通过一个示例来看使用的效果，首先需要设定一个目标及初始任务，官方默认的目标是Solve world hunger，初始任务是Develop a task list。我们把目标改为：培养孩子的兴趣，初始任务改为：制定任务清单。\n具体配置参数如下:\n1 2 3 4 5 6 7 8 9 10 11 12 13 #设置表的名称,任务结果存储在这里。 RESULTS_STORE_NAME=baby-agi-interest-table # BabyAGI实例的名称 INSTANCE_NAME=BabyInterestAGI COOPERATIVE_MODE=none # local # 定义目标 OBJECTIVE=培养孩子的兴趣 #OBJECTIVE=Solve world hunger # 初始任务 #INITIAL_TASK=Develop a task list INITIAL_TASK=制定任务清单 配置参数后，运行脚本python babyagi.py。\n一、从任务存储中取出第一个任务执行\r首先，打印出目标及初始任务，列出当前的任务列表，列表中只有一个任务，即初始任务：制定任务清单。取出该任务，并执行后，获取任务的结果。如下所示：\n二、根据任务的执行结果，创建新的任务列表\r接着，根据任务的执行结果及目标、任务的描述，调用任务创建代理(task_create_agent)，生成新的任务列表响应。这里生成了2个新的任务（红色框部分）。并将任添加进任务存储中（绿色框部分），如下所示：\n三、重新排序任务列表优先级\r最后，调用任务排序代理（prioritization_agent），重新排列任务列表优先级。主程序会把排序后的任务列表替换tasks_storage 中的旧任务列表。所示如下：\n到此完成一轮流程。\n继续重新开始一轮循环，列出任务存储中的任务列表，这里可以看到列表中有两条任务，即上一轮重新排序后的任务列表。取出第一条任务执行。获取结果，所示如下：\n以此，不断循环，直到任务存储中没有任务需要处理为止。但我这里经过几轮循环后，任务变得越来越细，如下所示：\n可以看到，生成了7个子任务。视乎没有看到能结束的时候。这里果断停止了运行，可以看到，运行BabyAGI需要耗费大量的token。\n源码解析\rBabyAGI 的核心代码比较简单，主要是在主程序目录下的 babyagi.py文件中，运行的主流程代码如下：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 def main(): loop = True while loop: #只要存储中存在任务，则往下执行. if not tasks_storage.is_empty(): # 打印任务列表 print(\u0026#34;\\033[95m\\033[1m\u0026#34; + \u0026#34;\\n*****TASK LIST*****\\n\u0026#34; + \u0026#34;\\033[0m\\033[0m\u0026#34;) for t in tasks_storage.get_task_names(): print(\u0026#34; • \u0026#34; + str(t)) # Step 1: 取出第一个未完成的任务 task = tasks_storage.popleft() #打印接下来要执行的任务 print(\u0026#34;\\033[92m\\033[1m\u0026#34; + \u0026#34;\\n*****NEXT TASK*****\\n\u0026#34; + \u0026#34;\\033[0m\\033[0m\u0026#34;) print(str(task[\u0026#34;task_name\u0026#34;])) # 发送给任务执行代理去完成任务，传入两个参数：目标、任务名称。该执行代理向OpenAI的API发送提\t# 示，提示包括AI系统任务、目标和任务本身的描述，返回字符串（string ）形式任务结果。 result = execution_agent(OBJECTIVE, str(task[\u0026#34;task_name\u0026#34;])) print(\u0026#34;\\033[93m\\033[1m\u0026#34; + \u0026#34;\\n*****TASK RESULT*****\\n\u0026#34; + \u0026#34;\\033[0m\\033[0m\u0026#34;) print(result) # Step 2: 丰富结果并存储任务、及其执行结果、ID 在 results_storage 中. # 如果需要，在这里丰富结果内容. enriched_result = { \u0026#34;data\u0026#34;: result } # 如果有丰富结果内容，可以从字典中提取丰富的实际结果，这里没有，已注释。 # vector = enriched_result[\u0026#34;data\u0026#34;] result_id = f\u0026#34;result_{task[\u0026#39;task_id\u0026#39;]}\u0026#34; # 任务执行后，会将任务、及其响应的结果、ID保存到results_storage（结果存储）中。 results_storage.add(task, result, result_id) # Step 3: 调用任务创建代理task_creation_agent，使用OpenAI的API基于当前对象和之前任务 # 执行的结果创建新任务，有四个参数：目标、上一个任务的结果、任务描述和当前任务列表。返回新的 # 任务列表 new_tasks = task_creation_agent( OBJECTIVE, enriched_result, task[\u0026#34;task_name\u0026#34;], tasks_storage.get_task_names(), ) #把新的任务添加到任务存储(tasks_storage)中保存下来 print(\u0026#39;Adding new tasks to task_storage\u0026#39;) for new_task in new_tasks: new_task.update({\u0026#34;task_id\u0026#34;: tasks_storage.next_task_id()}) print(str(new_task)) tasks_storage.append(new_task) # JOIN_EXISTING_OBJECTIVE(加入已存在的目标)变量控制是否在生产新任务时重新优化排序，该值\t# 默认是false,也即需要重新优化排序. if not JOIN_EXISTING_OBJECTIVE: #调用任务排序代理、重新排列任务列表优先级。向OpenAI的API发送提示，返回已重新排序的新任 #务列表。 prioritized_tasks = prioritization_agent() #将返回的新任务列表替换旧任务列表 if prioritized_tasks: tasks_storage.replace(prioritized_tasks) # 休眠一会儿 time.sleep(5) else: print(\u0026#39;Done.\u0026#39;) loop = False if __name__ == \u0026#34;__main__\u0026#34;: main() 以上的主流程已注释说明，主要包括三个代理执行器及两个存储器。\n代理执行器包括：\n任务执行代理（execution_agent） 任务创建代理（task_creation_agent） 排序代理（prioritization_agent） 存储器包括：\nresults_storage (结果存储器) tasks_storage （任务存储） 下面分别介绍。\n任务执行代理（execution_agent）\r首先，从任务存储（tasks_storage ）中拉取第一个未完成的任务。首次运行时，获取的是用户配置的初始任务和目标。调用执行代理（execution_agent）来执行任务，它利用OpenAI的API来完成任务。\n该代理的实现是 execution_agent() 函数，该函数采用两个参数：目标和任务。向OpenAI的API发送提示，提示包括 AI 系统任务、目标和任务本身的描述，然后返回字符串（string ）形式的响应结果。\n代码如下：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 def execution_agent(objective: str, task: str) -\u0026gt; str: \u0026#34;\u0026#34;\u0026#34; 根据给定的目标和先前的上下文执行任务.传入两个参数. Args: objective (str): 执行任务的目的或目标 task (str): 要执行的任务 Returns: str: AI为给定任务生成字符串格式的响应. \u0026#34;\u0026#34;\u0026#34; #获取目标的关联上下文信息，从results_storage中查询之前执行的任务及其响应结果信息。这是在执行任务完成 #后，将任务及其结果保存到results_storage。 #results_storage的默认实现是使用chroma(向量数据库)。 context = context_agent(query=objective, top_results_num=5) # print(\u0026#34;\\n****RELEVANT CONTEXT****\\n\u0026#34;) # print(context) # print(\u0026#39;\u0026#39;) #构建提示词,传入objective(目标)和context(关联上下文)、task(任务)组装提示词。 prompt = f\u0026#39;根据以下目标执行一项任务: {objective}.\\n\u0026#39; if context: prompt += \u0026#39;考虑到这些之前完成的任务:\u0026#39; + \u0026#39;\\n\u0026#39;.join(context) prompt += f\u0026#39;\\nYour task: {task}\\nResponse:\u0026#39; return openai_call(prompt, max_tokens=2000) 任务执行后，主程序会将任务、及其响应的结果保存到results_storage（结果存储）中。以便后续再执行任务时，从results_storage中检索相关的上下文信息。results_storage的默认实现是chroma(向量数据库)。\n任务创建代理（task_creation_agent）\r任务执行后，接下来，系统会根据之前任务执行的结果，调用任务创建代理生成新任务列表（task_creation_agent）。使用OpenAI的API基于当前对象和之前任务执行的结果创建新任务。\n其实现是task_creation_agent（）函数，该函数有四个参数：目标、上一个任务的结果、任务描述和当前未完成的任务列表。它向OpenAI的API发送提示，并以字符串形式返回新任务列表。然后，该函数将新任务列表作为字典列表返回，其中每个字典都包含任务的名称。\n代码如下：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 def task_creation_agent( objective: str, result: Dict, task_description: str, task_list: List[str] ): #构建提示词,使用参数\u0026#34;目标、上一个任务的结果、任务描述和当前未完成的任务列表\u0026#34;组装提示词. prompt = f\u0026#34;\u0026#34;\u0026#34; 您将使用执行代理的结果来创建具有以下目标的新任务: {objective}. 最后完成的任务结果: \\n{result[\u0026#34;data\u0026#34;]} 这个结果是基于这个任务描述: {task_description}.\\n\u0026#34;\u0026#34;\u0026#34; if task_list: prompt += f\u0026#34;未完成的任务: {\u0026#39;, \u0026#39;.join(task_list)}\\n\u0026#34; prompt += \u0026#34;根据结果，返回需要完成的任务列表以实现目标. \u0026#34; if task_list: prompt += \u0026#34;这些新任务不能与未完成的任务重叠. \u0026#34; prompt += \u0026#34;\u0026#34;\u0026#34; 在响应中每行返回一个任务。结果必须是以下格式的编号列表: #. First task #. Second task 每个条目的编号后面必须跟一个句号。如果你的清单是空的，那就写 \u0026#34;There are no tasks to add at this time.\u0026#34; 除非您的列表为空，否则不要在编号列表之前包含任何标题，或者在编号列表之后使用任何其他输出.\u0026#34;\u0026#34;\u0026#34; print(f\u0026#39;\\n*****TASK CREATION AGENT PROMPT****\\n{prompt}\\n\u0026#39;) #发送给openAI 模型 response = openai_call(prompt, max_tokens=2000) print(f\u0026#39;\\n****TASK CREATION AGENT RESPONSE****\\n{response}\\n\u0026#39;) #获取响应的新任务列表 new_tasks = response.split(\u0026#39;\\n\u0026#39;) new_tasks_list = [] for task_string in new_tasks: task_parts = task_string.strip().split(\u0026#34;.\u0026#34;, 1) if len(task_parts) == 2: task_id = \u0026#39;\u0026#39;.join(s for s in task_parts[0] if s.isnumeric()) task_name = re.sub(r\u0026#39;[^\\w\\s_]+\u0026#39;, \u0026#39;\u0026#39;, task_parts[1]).strip() if task_name.strip() and task_id.isnumeric(): new_tasks_list.append(task_name) # print(\u0026#39;New task created: \u0026#39; + task_name) out = [{\u0026#34;task_name\u0026#34;: task_name} for task_name in new_tasks_list] return out 调用任务创建代理task_creation_agent，获得返回新的任务列表，然后，主程序会把新的任务添加到任务存储(tasks_storage)中保存下来。\n任务排序代理（prioritization_agent）\r当调用任务创建代理（task_creation_agent），主程序把新的任务添加到任务存储(tasks_storage)中后，若JOIN_EXISTING_OBJECTIVE（加入已存在的目标， 默认为false）未设置成true，则调用排序代理。\n排序代理（prioritization_agent），用于重新排列任务列表优先级。其实现是prioritization_agent()函数，向OpenAI的API发送提示，返回已重新排序的新任务列表(以数字编号)。\n代码如下：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 def prioritization_agent(): //获取当前存储的未完成任务列表 task_names = tasks_storage.get_task_names() bullet_string = \u0026#39;\\n\u0026#39; prompt = f\u0026#34;\u0026#34;\u0026#34; 你需要对以下任务进行排序：: {bullet_string + bullet_string.join(task_names)} 考虑团队的最终目标: {OBJECTIVE}. 任务应该从最高优先级到最低优先级进行排序，其中高优先级的任务是那些作为先决条件或对实现目标更重要的任务。 不要删除任何任务。以下列格式返回排序任务的编号列表: #. First task #. Second task 条目必须连续编号，从1开始。每个条目的编号后面必须跟一个句号。 在排名列表之前不包含任何标题，或者在列表之后使用任何其他输出.\u0026#34;\u0026#34;\u0026#34; print(f\u0026#39;\\n****TASK PRIORITIZATION AGENT PROMPT****\\n{prompt}\\n\u0026#39;) response = openai_call(prompt, max_tokens=2000) print(f\u0026#39;\\n****TASK PRIORITIZATION AGENT RESPONSE****\\n{response}\\n\u0026#39;) if not response: print(\u0026#39;Received empty response from priotritization agent. Keeping task list unchanged.\u0026#39;) return #获取响应的任务列表. new_tasks = response.split(\u0026#34;\\n\u0026#34;) if \u0026#34;\\n\u0026#34; in response else [response] new_tasks_list = [] for task_string in new_tasks: task_parts = task_string.strip().split(\u0026#34;.\u0026#34;, 1) if len(task_parts) == 2: task_id = \u0026#39;\u0026#39;.join(s for s in task_parts[0] if s.isnumeric()) task_name = re.sub(r\u0026#39;[^\\w\\s_]+\u0026#39;, \u0026#39;\u0026#39;, task_parts[1]).strip() if task_name.strip(): new_tasks_list.append({\u0026#34;task_id\u0026#34;: task_id, \u0026#34;task_name\u0026#34;: task_name}) return new_tasks_list 在调用排序代理（prioritization_agent）对任务进行排序后，主程序会将新排序的任务列表替换任务存储（tasks_storage）中的任务列表。代码如下：\n1 2 3 4 if not JOIN_EXISTING_OBJECTIVE: prioritized_tasks = prioritization_agent() if prioritized_tasks: tasks_storage.replace(prioritized_tasks) 到此，完成一次流程，接下来，继续从任务存储中获取第一条未完成的任务，调用任务执行代理（execution_agent)执行任务，不断循环，直到完成所有任务才结束。\nresults_storage (结果存储器)\r任务执行后，主程序会将任务、及其响应的结果保存到results_storage（结果存储器）中。以便后续再执行任务时，从results_storage中检索相关的上下文信息。results_storage的默认实现是chroma(向量数据库)。其它还有weaviate 和 pinecone 的实现。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 class DefaultResultsStorage: def __init__(self): logging.getLogger(\u0026#39;chromadb\u0026#39;).setLevel(logging.ERROR) # 创建Chroma集合 chroma_persist_dir = \u0026#34;chroma\u0026#34; chroma_client = chromadb.PersistentClient( settings=chromadb.config.Settings( persist_directory=chroma_persist_dir, ) ) #配置嵌入(Embedding)模型 metric = \u0026#34;cosine\u0026#34; if LLM_MODEL.startswith(\u0026#34;llama\u0026#34;): embedding_function = LlamaEmbeddingFunction() else: embedding_function = OpenAIEmbeddingFunction(api_key=OPENAI_API_KEY) self.collection = chroma_client.get_or_create_collection( name=RESULTS_STORE_NAME, metadata={\u0026#34;hnsw:space\u0026#34;: metric}, embedding_function=embedding_function, ) #添加任务及其响应结果到 collection中. def add(self, task: Dict, result: str, result_id: str): # Break the function if LLM_MODEL starts with \u0026#34;human\u0026#34; (case-insensitive) if LLM_MODEL.startswith(\u0026#34;human\u0026#34;): return # Continue with the rest of the function embeddings = llm_embed.embed(result) if LLM_MODEL.startswith(\u0026#34;llama\u0026#34;) else None if ( len(self.collection.get(ids=[result_id], include=[])[\u0026#34;ids\u0026#34;]) \u0026gt; 0 ): # Check if the result already exists self.collection.update( ids=result_id, embeddings=embeddings, documents=result, metadatas={\u0026#34;task\u0026#34;: task[\u0026#34;task_name\u0026#34;], \u0026#34;result\u0026#34;: result}, ) else: self.collection.add( ids=result_id, embeddings=embeddings, documents=result, metadatas={\u0026#34;task\u0026#34;: task[\u0026#34;task_name\u0026#34;], \u0026#34;result\u0026#34;: result}, ) #检索相关信息 def query(self, query: str, top_results_num: int) -\u0026gt; List[dict]: count: int = self.collection.count() if count == 0: return [] results = self.collection.query( query_texts=query, n_results=min(top_results_num, count), include=[\u0026#34;metadatas\u0026#34;] ) return [item[\u0026#34;task\u0026#34;] for item in results[\u0026#34;metadatas\u0026#34;][0]] tasks_storage （任务存储）\rtasks_storage （任务存储）用于存储未完成的任务列表。\n在循环开始时，首先从任务存储中获取第一条未完成的任务，给任务执行代理（execution_agent）去执行。 然后，在调用任务创建代理（task_creation_agent）获取新创建的任务列表时，会将这些任务添加到tasks_storage 中。 最后，在调用排序代理（prioritization_agent）获取优化排序的新任务列表时，主程序会将该列表替换tasks_storage 中的旧任务列表。 实现代码如下：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 # 任务存储，只有一个实例，数据保存在一个队列中。 class SingleTaskListStorage: def __init__(self): self.tasks = deque([]) self.task_id_counter = 0 def append(self, task: Dict): self.tasks.append(task) def replace(self, tasks: List[Dict]): self.tasks = deque(tasks) def popleft(self): return self.tasks.popleft() def is_empty(self): return False if self.tasks else True def next_task_id(self): self.task_id_counter += 1 return self.task_id_counter def get_task_names(self): return [t[\u0026#34;task_name\u0026#34;] for t in self.tasks] 以上5个组件是babyAGI的主要部分。其它还包括环境变量加载与配置（如：使用dotenv库加载.env文件中的环境变量）、导入模块与扩展插件（如：extensions.argparseext和extensions.human_mode，支持命令行参数解析和人类模式输入）支持等辅助代码，这里就不再列出了。\n结语\r​\t总的来说，BabyAGI仍处于起步阶段，作为AI驱动的任务管理系统的一个实例。用户给它一个目标，它可以自动生成完成此目标所需要的任务清单。逐步完成任务清单中的每个任务，不断迭代，直到完成所有任务。如何结合外部工具如搜索引擎等，对应一些简单的目标，有可能会循环迭代完成，有比较大的帮助，或起辅助作用。但对应复杂的任务，其生成的任务清单就会越来越长，无法结束。\n项目相关代码及文件放在网盘中，地址：\n链接：https://pan.quark.cn/s/11ca682f0ec7 提取码：KXJr\n参考文档\r官方文档 https://github.com/yoheinakajima/babyagi\n","date":"2026-01-27T00:00:00Z","image":"/p/%E6%B7%B1%E5%85%A5%E8%A7%A3%E6%9E%90babyagi%E5%8E%9F%E7%90%86%E5%AE%9E%E6%88%98%E4%B8%8E%E6%BA%90%E7%A0%81%E6%8B%86%E8%A7%A3/images/235015461-543a897f-70cc-4b63-941a-2ae3c9172b11-17241256699664.png","permalink":"/p/%E6%B7%B1%E5%85%A5%E8%A7%A3%E6%9E%90babyagi%E5%8E%9F%E7%90%86%E5%AE%9E%E6%88%98%E4%B8%8E%E6%BA%90%E7%A0%81%E6%8B%86%E8%A7%A3/","title":"深入解析BabyAGI：原理、实战与源码拆解"},{"content":"深入解析AutoGPT：原理、实战与源码拆解\r介绍\r​ 自ChatGPT发布以来，涌现了许多围绕大模型(即LLM)的应用开发框架，authGPT就是其中之一，在github上截止2024年7月达到160K颗star。这篇文章我们全面深入讲解autoGTP，首先了解autoGPT是什么，它的整体架构是怎么组织的，再通过实际使用示例来探究其运作原理及流程、最后对源码进行解析，来看它是如何实现的。\n​ AutoGPT的概念是在OpenAI发布其GPT-4模型及其论文，阐述该模型的高级推理和任务解决能力时提出的。该概念其实相当简单：用户只需要提出目标，让LLM不断思考决定要做什么，需要采取什么行动，并将其行动的结果反馈到提示中，重新发送给大模型，让LLM继续思考下一步要做什么，采取什么行动\u0026hellip;，如此不断迭代，使程序自主的逐步地朝着目标前进，最终完成用户的目标任务。\n​ AutoGPT是一个开源的自主通用型AI Agent框架。它不是针对特定任务而设计的，相反，它是为了能够在许多学科中执行广泛的任务而设计的，只要这些任务可以在计算机上完成即可；同时它运用大模型推理决策的能力，根据决策结果自主行动，自动完成目标。这就是为什么叫自主型代理框架的原因。**这个框架的核心原理是利用大型语言模型（LLM）来自主决策和执行操作，观察执行的反馈结果不断迭代，从最终实现用户设定的目标。**AutoGPT扩展了基本ReAct（Reason + Act）框架，让语言模型不断进行思考和行动的循环，以自主迭代地朝着目标前进。\n​\tAutoGPT集成以下能力：\n访问互联网以进行搜索和信息收集。\n管理长短期记忆。\n调用大型模型进行推理、决策及文本生成。\n存储和总结文件。\n集成一系列的工具(如browse_website、write_to_file等)，同时可扩展，用户可以根据具体的场景需求开发工具，以满足特定的需求 。\n整体架构\r可以说Auto-GPT是一项使大模型自动化来完成任务的尝试，那它是如何实现自主行为，逐步完成用户设定的目标呢? 我们来看下Auto-GPT的整体运行架构。\nAuto-GPT的核心运作流程主要可分为6个步骤，具体如下:\n初始化Agent，Auto-GPT首先会检查是否已经存在Agent，若存在，用户可以选择其中一个Agent继续；若不存在或不使用之前的，则可以创建一个新的Agent。创建Agent，需要用户输入一个任务，尽可能提供详细描述。auth-GPT会将该任务描述结合一个返回的示例样本构建提示词，发送给大模型，由大模型生成Agent的名称、任务描述，及最佳实践和约束条件中每个分别1到5个指令，这些指令与成功完成分配的任务保持最佳一致。通过大模型生成的这些信息结合基础的配置参数创建Agent。\n构建Prompt，整合上一步大模型生成的任务描述、最佳实践和约束条件的指令与基础提示模板、参数文件(prompt_settings.yaml、ai_settings.yaml等)的信息、构建完整提示词，要求大模型根据给定的目标和到目前为止所执行的行动步骤，确定下一步要使用的命令，并使用指定的JSON格式进行响应。\n决策下一步的行动，这是关键核心的一步，大模型进行思考推理并作出决策，按JSON格式生成下一步需要执行的行动及相关参数。该步骤大模型会进行如下思考过程：\nobservations：观察最后一个动作的执行结果(如果有的话)。 Thoughts：做出思考结论，将要做什么。 reasoning：推理，为什么得出这个思考结论。 self_criticism：建设性的自我批评。 plan：计划，形成简短的项目符号列表传达计划。 speak：总结想法，说给用户(如果有文本转语音模型，可以把它播放出来)。 以上的思考过程，实际上是扩展ReAct（Reason + Act）框架，让模型观察思考、做出决策，生成下一步行动的命令。\n执行行动，根据大模型返回的下一步要执行的命令名称，调用对应的命令函数，获得执行结果。\n保存行动结果，记录本次执行的行动及结果，添加到历史行动数据集中存储。\n生成总结，根据行动及结果等信息，使用LLM（大型语言模型）生成行动的摘要，保留具体事实信息。\n重新构建Prompt，整合之前的行动步骤及其结果，重新构建Prompt，进入下一轮循环（2-6步骤），直到程序中断或大模型决策可以结束任务,输出下一步执行的命令名称是finish。\n以上是AutoGPT主要的运行流程。在“运行流程”章节，再通过运行一个实例来详细讲解每一步骤。\n安装使用\r首先确保先安装了 Python 3.9以上版本。\n安装poetry\rAutoGPT项目由于支持执行Python代码，处于安全考虑，这个系统为每个Agent都创建了一个独立的虚拟环境，这套虚拟环境的管理是通过Poetry实现的，所以需要先安装Poetry， 关于Poetry的详细介绍，可见https://python-poetry.org/。\n安装poetry\r方法1：\n1 pip install poetry 方法2：\nLinux, macOS, Windows (WSL)系统\n需求先安装curl。\nLinux, macOS, Windows (Windows 上的 Linux 子系统)使用如下指令：\n1 2 3 4 5 6 Poetry 要求Python 版本为2.7 或者3.5+ python3: curl -sSL https://install.python-poetry.org | python3 - python2: curl -sSL https://install.python-poetry.org | python2 - Windows系统\n打开 Powershell，使用如下指令\n1 (Invoke-WebRequest -Uri https://install.python-poetry.org -UseBasicParsing).Content | py - 上面都会安装在默认的路径。\n配置环境变量\r安装完成后，需要添加Poetry安装目录添加进环境变量。\nLinux, macOS, Windows (WSL)系统 Poetry的安装后的默认路径： $HOME/.local/bin, 以下是安装后添加进环境变量的方式\n1 2 3 4 5 6 7 1. 对于 Bash 终端，可以执行以下命令打开 ~/.bashrc 文件： nano ~/.bashrc 2. 在文件中添加以下行： export PATH=$HOME/.local/bin:$PATH 3. 保存并关闭文件后，使用以下命令使修改生效： source ~/.bashrc window系统（以window10为例子）\n使用安装方法1，安装成功后会提示poetry.exe 安装的目录路径，如下图画黄线的就是安装的目录:\n添加到环境变量步骤：\n右键单击 \u0026ldquo;此电脑\u0026rdquo;-\u0026gt; \u0026ldquo;属性\u0026rdquo;-\u0026gt;选择 \u0026ldquo;高级\u0026rdquo; 选项卡-\u0026gt;点击 \u0026ldquo;环境变量\u0026rdquo; 按钮。\n打开环境变量窗口，在 \u0026ldquo;用户变量\u0026rdquo; 部分，找到并选择 \u0026ldquo;path\u0026rdquo; 变量，然后点击 \u0026ldquo;编辑\u0026rdquo; 按钮。\n在编辑环境变量窗口中，点击 \u0026ldquo;新建\u0026rdquo; 按钮，在弹出的对话框中输入poetry安装目录：%APPDATA%\\Local\\Packages\\PythonSoftwareFoundation.Python.3.9_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python39\\Scripts，然后点击 \u0026ldquo;确定\u0026rdquo;。\n在 \u0026ldquo;环境变量\u0026rdquo; 窗口中，点击 \u0026ldquo;确定\u0026rdquo; 按钮应用更改。如下图所示：\n测试\r输入如下命令,返回显示poetry的版本，即安装成功：\n1 poetry -V 在window系统中，打开powershell,输入命令后，显示如下：\n其它系统类似。\n**安装Auto-GPT **\r截止当前2024年7月，最新的稳定发布版本是v0.5.1，我们使用该版本作为演示。\n下载项目\r一、git clone 方式下载\n打开CMD命令行，输入：\n1 git clone https://github.com/Significant-Gravitas/AutoGPT 完成后，可以看到当前文件夹已经下载了 Auto-GPT 的源代码。如果下载比较慢或常中断，建议直接下载源码包。\n二、直接下载源码包\n打开项目地址：https://github.com/Significant-Gravitas/AutoGPT，点击 Code，复制地址，如下图：\n新建一个文件夹 autogpt，将下载的文件解压到该文件夹中。\n如果需要下载其它稳定版本，打开项目地址：https://github.com/Significant-Gravitas/AutoGPT/releases，拉到 Assets 处，点击源码压缩包下载。这里下载版本v0.5.1。\n下载依赖\rautoGPT 在0.5 版本后采用 poetry 进行包管理及打包。在进入autogpts\\autogpt目录下，运行如下命令，安装所有必需的依赖项：\n1 poetry install 另外运行命令 ：\n1 autogpt.bat --help 也会自动检测依赖包并下载，运行该命令，依赖包下载完成后，可以看到如下的界面：\n配置\r打开 autogpt 源码文件夹，进入autogpt目录。\n找到名为.env.template的文件，并将其命名为.env。注意：在某些操作系统中，点前缀的文件默认情况下可能被隐藏。 如果在命令提示符/终端窗口中，可以使用以下命令拷贝：\n1 cp .env.template .env 在文本编辑器中打开.env文件。配置访问openAl API 的 key(必须)。具体配置查看下文。\n注意不需要用英文双引号包括：\n调用openAI api 大概有四种方式：\n直连：直接连接openAI官网 API。由于目前国内无法访问openAI官网，这种方式需要能科学上网。\n转发url：通过转发服务连接openAI官网接口，比如Cloudflare提供的AI Gateway，转发到openAI http://api.openai.com，需要到 https://www.cloudflare-cn.com注册账号，目前这个服务是免费的。由于openAI官方从7月9日起停止对我们地区的api服务。所以这种方式稳定性还不好确定。\nAzure openAI：在微软的Azure上部署OpenAI的大模型，提供和openAI官方一样的api服务，这种方式不用科学上网，企业可以走正规渠道，有想应的售后服务，推荐使用这种方式。\n第三方：使用第三方搭建的中转服务，通常第三方会提供一个key和一个中转链接。这种方式不需要科学上网，稳定性不好确定，但比较方便，用于测试使用可以，正式环境推荐使用Azure openAI方式。（这种服务在百度上搜索下，应该会有很多）。\n需要在.env 文件上找到 OPENAI_API_KEY，OPENAI_API_BASE_URL参数填入实际的值，配置如下所示：\n1 2 OPENAI_API_KEY=your-openai-api-key #替换为第三方提供的key OPENAI_API_BASE_URL=https://api.xxx.com/v1 #替换为第三方提供的中转链接 其它的组件访问KEY(可选)\n按需要配置其它组件的访问KEY，如google搜索KEY。\n运行 auto-gpt\r进入auto-gpt的程序目录下，%下载目录%\\autogpts\\autogpt， 打开powershell。\n使用./autogpt.sh \u0026ndash;help命令。会列出所有你可以使用的子命令和参数，若之前没有下载依赖，该命令检查并下载安装所需的依赖项。\n在Windows上，使用autogpt.bat代替autogpt.sh。其它所有内容(子命令、参数)应该都是一样的。\n1 2 3 4 5 6 7 8 9 $ ./autogpt.sh --help Usage: python -m autogpt [OPTIONS] COMMAND [ARGS]... Options: --help Show this message and exit. Commands: run Sets up and runs an agent, based on the task specified by the... serve Starts an Agent Protocol compliant AutoGPT server, which creates... 命令行模式\n执行以下命令，使用命令行方式启动AutoGPT（window 中./autogpt.sh 改为 autogpt.bat )：\n1 ./autogpt.sh run 执行命令后，显示如下，表明启动了autoGPT：\n开启调试功能，添加 \u0026ndash;debug参数（window 中./autogpt.sh 改为 autogpt.bat )：\n1 ./autogpt.sh run --debug Continuous 模式\n运行无需用户授权，100%自动化。不建议使用连续模式。这会有潜在的危险，可能会导致你的agent永远运行或执行你通常不会授权的操作。\n1 ./autogpt.sh --continuous 按“Ctrl+C”退出程序。\n运行流程\r下面我们通过运行一个示例，来深入了解autoGPT的运行流程。\n初始化Agent\r在autoGPT自主地完成任务之前，首先需要初始化Agent。描述这个代理叫什么名字，它应该完成的目标是什么。在程序启动后，会提示用户输入希望AutoGPT执行的任务，如下所示：\n1 2 2024-07-26 09:57:05,989 DEBUG Asking user via keyboard... Enter the task that you want AutoGPT to execute, with as much detail as possible: 输入任务\u0026quot;制作素食三明治食谱\u0026quot;，系统会创建一个自主的agent来完成任务，创建Agent需要提供代理的名称、任务描述、最佳实践及约束的指令等信息，这些信息部分是通过发送给大模型来生成的，提供给大模型的提示词如下：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 SYSTEM: Your job is to respond to a user-defined task, given in triple quotes, by invoking the `create_agent` function to generate an autonomous agent to complete the task. You should supply a role-based name for the agent (_GPT), an informative description for what the agent does, and 1 to 5 directives in each of the categories Best Practices and Constraints, that are optimally aligned with the successful completion of its assigned task. Example Input: \u0026#34;\u0026#34;\u0026#34;Help me with marketing my business\u0026#34;\u0026#34;\u0026#34; Example Call: ``` [ { \u0026#34;type\u0026#34;: \u0026#34;function\u0026#34;, \u0026#34;function\u0026#34;: { \u0026#34;name\u0026#34;: \u0026#34;create_agent\u0026#34;, \u0026#34;arguments\u0026#34;: { \u0026#34;name\u0026#34;: \u0026#34;CMOGPT\u0026#34;, \u0026#34;description\u0026#34;: \u0026#34;a professional digital marketer AI that assists Solopreneurs in growing their businesses by providing world-class expertise in solving marketing problems for SaaS, content products, agencies, and more.\u0026#34;, \u0026#34;directives\u0026#34;: { \u0026#34;best_practices\u0026#34;: [ \u0026#34;Engage in effective problem-solving, prioritization, planning, and supporting execution to address your marketing needs as your virtual Chief Marketing Officer.\u0026#34;, \u0026#34;Provide specific, actionable, and concise advice to help you make informed decisions without the use of platitudes or overly wordy explanations.\u0026#34;, \u0026#34;Identify and prioritize quick wins and cost-effective campaigns that maximize results with minimal time and budget investment.\u0026#34;, \u0026#34;Proactively take the lead in guiding you and offering suggestions when faced with unclear information or uncertainty to ensure your marketing strategy remains on track.\u0026#34; ], \u0026#34;constraints\u0026#34;: [ \u0026#34;Do not suggest illegal or unethical plans or strategies.\u0026#34;, \u0026#34;Take reasonable budgetary limits into account.\u0026#34; ] } } } } ] ``` USER: \u0026#34;\u0026#34;\u0026#34;制作素食三明治食谱\u0026#34;\u0026#34;\u0026#34; 上述提示词大意是：“你的工作是响应用户定义的任务（以三引号表示），通过调用create_agent函数来生成一个自主的代理来完成该任务。你应该为代理提供一个基于角色的名称（_GPT），一个说明代理功能的信息性描述，以及在“最佳实践”和“约束”类别中各1至5条指令，这些指令与成功完成其分配的任务高度相关“。同时提供了期望输出应该是什么样的例子（Example 部分）。给任何生成式的大型语言模型举例子都非常有效。通过描述输出应该是什么样子，它更容易生成准确的答案。\n将这个提示词发送给大模型，得到响应结果如下：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 role=\u0026lt;Role.ASSISTANT: \u0026#39;assistant\u0026#39;\u0026gt; content=None tool_calls=[AssistantToolCall(id=\u0026#39;call_EamkbLMEfUnz04fi788xwujM\u0026#39;, type=\u0026#39;function\u0026#39;, function=AssistantFunctionCall(name=\u0026#39;create_agent\u0026#39;, arguments={ \u0026#39;name\u0026#39;: \u0026#39;VeggieSandwichChef_GPT\u0026#39;, \u0026#39;description\u0026#39;: \u0026#39;an AI dedicated to creating delicious and innovative vegetarian sandwich recipes, focusing on healthy ingredients and diverse flavors.\u0026#39;, \u0026#39;directives\u0026#39;: { \u0026#39;best_practices\u0026#39;: [ \u0026#39;Always focus on a blend of nutritional balance and taste, incorporating a variety of vegetables, proteins, and grains.\u0026#39;, \u0026#39;Include clear, easy-to-follow instructions that cater to both novice and experienced cooks.\u0026#39;, \u0026#39;Ensure recipes can be adapted for vegan preferences whenever possible, providing suitable substitutions for dairy or other animal products.\u0026#39;, \u0026#39;Highlight the preparation time and serving size to help users in meal planning and preparation.\u0026#39;], \u0026#39;constraints\u0026#39;: [ \u0026#39;Avoid using ingredients that are known allergens without offering alternatives.\u0026#39;, \u0026#39;Keep the recipes simple with ingredients that are easily accessible.\u0026#39;, \u0026#39;Do not include any meat, fish, or poultry products in the recipes.\u0026#39;, \u0026#39;Respect dietary restrictions by clearly indicating if a recipe contains common allergens like nuts, soy, or gluten.\u0026#39;] } } ))] 大模型为我们生产了agent的名称VeggieSandwichChef_GPT、描述、及最佳实践和约束各若干条指令。autoGPT可以利用这些信息结合默认模板配置来构建名为VeggieSandwichChef_GPT的agent。\n构建初始Prompt\r这是实现自主任务循环的第一步，创建触发大模型进行决策生成下一步动作的提示。\n这一步提示词主要由三部分组成：\n系统提示（SYSTEM PROMPT)：为完成目标任务的完整背景信息，及应该遵循的指导。包含agent角色身份、描述、任务目标、约束条件、可利用的资源、可执行的命令列表和最佳实践指令、响应的格式。 历史步骤及总结（Retain History of Steps \u0026amp; summary）：历史已执行步骤的列表，同时会对执行步骤进行总结，以提炼重要的事实信息，减少提示信息量。 号召行动（Call to Action）：决定下一步执行的命令。 我们先来看下构建的完整提示词示例，再来了解每一项，示例如下：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 2024-07-26 09:58:04,794 DEBUG Executing prompt: ============== ChatPrompt ============== Length: 5 messages ----------------- SYSTEM ----------------- You are VeggieSandwichChef_GPT, an AI dedicated to creating delicious and innovative vegetarian sandwich recipes, focusing on healthy ingredients and diverse flavors. Your decisions must always be made independently without seeking user assistance. Play to your strengths as an LLM and pursue simple strategies with no legal complications. ## Constraints You operate within the following constraints: 1. Exclusively use the commands listed below. 2. You can only act proactively, and are unable to start background jobs or set up webhooks for yourself. Take this into account when planning your actions. 3. You are unable to interact with physical objects. If this is absolutely necessary to fulfill a task or objective or to complete a step, you must ask the user to do it for you. If the user refuses this, and there is no other way to achieve your goals, you must terminate to avoid wasting time and energy. 4. Avoid using ingredients that are known allergens without offering alternatives. 5. Keep the recipes simple with ingredients that are easily accessible. 6. Do not include any meat, fish, or poultry products in the recipes. 7. Respect dietary restrictions by clearly indicating if a recipe contains common allergens like nuts, soy, or gluten. ## Resources You can leverage access to the following resources: 1. Internet access for searches and information gathering. 2. The ability to read and write files. 3. You are a Large Language Model, trained on millions of pages of text, including a lot of factual knowledge. Make use of this factual knowledge to avoid unnecessary gathering of information. ## Commands These are the ONLY commands you can use. Any action you perform must be possible through one of these commands: 1. list_folder: List the items in a folder. Params: (folder: string) 2. open_file: Opens a file for editing or continued viewing; creates it if it does not exist yet. Note: If you only need to read or write a file once, use `write_to_file` instead.. Params: (file_path: string) 3. open_folder: Open a folder to keep track of its content. Params: (path: string) 4. read_file: Read an existing file. Params: (filename: string) 5. write_file: Write a file, creating it if necessary. If the file exists, it is overwritten.. Params: (filename: string, contents: string) 6. ask_user: If you need more details or information regarding the given goals, you can ask the user for input. Params: (question: string) 7. web_search: Searches the web. Params: (query: string) 8. read_webpage: Read a webpage, and extract specific information from it. You must specify either topics_of_interest, a question, or get_raw_content.. Params: (url: string, topics_of_interest?: Array\u0026lt;string\u0026gt;, question?: string, get_raw_content?: boolean) 9. finish: Use this to shut down once you have completed your task, or when there are insurmountable problems that make it impossible for you to finish your task.. Params: (reason: string) ## Best practices 1. Continuously review and analyze your actions to ensure you are performing to the best of your abilities. 2. Constructively self-criticize your big-picture behavior constantly. 3. Reflect on past decisions and strategies to refine your approach. 4. Every command has a cost, so be smart and efficient. Aim to complete tasks in the least number of steps. 5. Only make use of your information gathering abilities to find information that you don\u0026#39;t yet have knowledge of. 6. Always focus on a blend of nutritional balance and taste, incorporating a variety of vegetables, proteins, and grains. 7. Include clear, easy-to-follow instructions that cater to both novice and experienced cooks. 8. Ensure recipes can be adapted for vegan preferences whenever possible, providing suitable substitutions for dairy or other animal products. 9. Highlight the preparation time and serving size to help users in meal planning and preparation. ## Your Task The user will specify a task for you to execute, in triple quotes, in the next message. Your job is to complete the task while following your directives as given above, and terminate when your task is done. ------------------ USER ------------------ \u0026#34;\u0026#34;\u0026#34;制作素食三明治食谱\u0026#34;\u0026#34;\u0026#34; ----------------- SYSTEM ----------------- The current time and date is Fri Jul 26 09:58:04 2024 ----------------- SYSTEM ----------------- Respond with pure JSON. The JSON object should be compatible with the TypeScript type `Response` from the following: interface Response { thoughts: { // Relevant observations from your last action (if any) observations: string; // Thoughts text: string; reasoning: string; // Constructive self-criticism self_criticism: string; // Short markdown-style bullet list that conveys the long-term plan plan: string; // Summary of thoughts, to say to user speak: string; }; command: { name: string; args: Record\u0026lt;string, any\u0026gt;; }; } ------------------ USER ------------------ Determine exactly one command to use next based on the given goals and the progress you have made so far, and respond using the JSON schema specified previously: 系统提示\n系统提示给予大模型的上下文，应该遵循的指导信息。\n主要包含以下几项内容：\nAgent 的目标身份和描述。\n最佳实践\n约束条件\n可选择的命令列表\n列出命令列表，及每个命令的名称，描述、参数等信息。执行的任何操作都必须通过这些命令之一实现。\n任务目标\n1 2 3 4 ## Your Task The user will specify a task for you to execute, in triple quotes, in the next message. Your job is to complete the task while following your directives as given above, and terminate when your task is done. ------------------ USER ------------------ \u0026#34;\u0026#34;\u0026#34;制作素食三明治食谱\u0026#34;\u0026#34;\u0026#34; 响应格式\n指定了响应的JSON格式，这样方便结果的解析。\n可以看到要求的json格式，包含thoughts（思考）和comand （命令）两部分。\nThoughts中包含六个步骤:\nobservations（观察）：最后一个动作的执行结果(如果有的话)。 Thoughts（思考）：针对用户目标的回答，将要做什么。 reasoning（推理）：，是对回答的解释，告诉用户我为什么要这么做。 self_criticism（自我批评）：建设性的自我批评，标记出用户目标的核心点，以便在回答时能够抓住重点。 plan（计划）： 完成用户给定的目标的执行计划。 speak（发言）：总结想法，说给用户(是一个语言播放的功能，只有配置了语言播放功能才有效)。 ​\t这些步骤实际上是扩展了 Reason and ACT(ReACT)框架的提示格式，通过让模型模拟广泛的推理，观察、自我批评与直接回答问题相比，它往往会给出更准确的答案。\nComand 指定下一步需要执行的动作：\n包含待执行的命令及响应的参数，name是命令的名称，args 列出命令的参数。\n历史步骤及总结\n这里列出历史每一步执行的行动及响应结果信息。由于这是第一步，所以之前没有历史步骤信息，当再一次循环时，会添加到提示词中的“##progress“部分。\n确定下一步执行命令\n指示大模型需要根据给定的目标，确定下一步要使用的命令，并按指定的JSON格式响应。\n1 [{\u0026#34;role\u0026#34;: \u0026#34;user\u0026#34;, \u0026#34;content\u0026#34;: \u0026#34;Determine exactly one command to use next based on the given goals and the progress you have made so far, and respond using the JSON schema specified previously\u0026#34; }] 决策下一步的行动\r在前一步中定义的提示符发送给大模型，大模型进行思考推理，决策出要采取的操作，返回响应结果如下：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 2024-07-26 09:58:37,367 DEBUG LLM response content: { \u0026#34;thoughts\u0026#34;: { \u0026#34;observations\u0026#34;: \u0026#34;The task is to create a vegetarian sandwich recipe.\u0026#34;, \u0026#34;text\u0026#34;: \u0026#34;To begin recipe development, I need to define the core ingredients and structure of the sandwich. This includes selecting a bread type, the main protein source (e.g., tofu, beans), vegetables, and condiments that harmonize well.\u0026#34;, \u0026#34;reasoning\u0026#34;: \u0026#34;A systematic approach to crafting a well-balanced and appealing vegetarian sandwich involves brainstorming potential ingredients and their combinations. This forms the baseline before I can write and present a complete recipe.\u0026#34;, \u0026#34;self_criticism\u0026#34;: \u0026#34;I initially considered searching the web for existing vegetarian sandwich recipes for inspiration but realized it may lead to standard ideas. Instead, creating something unique might be more beneficial and in line with the user\u0026#39;s expectations.\u0026#34;, \u0026#34;plan\u0026#34;: \u0026#34;- List potential ingredients for a vegetarian sandwich - Select a combination that ensures nutritional balance and taste - Write the recipe with clear instructions and indicate allergens - Provide the completed recipe to the user\u0026#34;, \u0026#34;speak\u0026#34;: \u0026#34;Let\u0026#39;s start by brainstorming some exciting and nutritious ingredients for our vegetarian sandwich recipe!\u0026#34; 译“让我们开始集思广益一些令人兴奋和营养的食材，为我们的素食三明治食谱!” }, \u0026#34;command\u0026#34;: { \u0026#34;name\u0026#34;: \u0026#34;web_search\u0026#34;, \u0026#34;args\u0026#34;: { \u0026#34;query\u0026#34;: \u0026#34;popular vegetarian sandwich ingredients\u0026#34; 译“受欢迎的素食三明治配料” } } } 从响应看出，大模型按提示词要求遵循六个步骤的思考过程，更加精准的决策，生成下一步的行动计划，这里下一步执行的命令是web_search，参数是\u0026quot;popular vegetarian sandwich ingredients\u0026quot; 译“受欢迎的素食三明治配料”。\n执行行动\r接下来就需要执行命令，命令是一段逻辑处理函数。在autoGPT中默认集成些命令，如互联网搜索，默认是调用谷歌或duckduckgo_search搜索引擎获取搜索结果，并对结果进行解析处理成文本内容。您也可以根据需要进行扩展。\n这里程序下一步执行的命令是web_search， 在执行命令之前询问用户授权操作，如下所示：\n1 2 3 4 5 2024-07-26 10:01:24,205 INFO NEXT ACTION: COMMAND = web_search ARGUMENTS = {\u0026#39;query\u0026#39;: \u0026#39;popular vegetarian sandwich ingredients\u0026#39;} 2024-07-26 10:01:24,206 INFO Enter \u0026#39;y\u0026#39; to authorise command, \u0026#39;y -N\u0026#39; to run N continuous commands, \u0026#39;n\u0026#39; to exit program, or enter feedback for VeggieSandwichChef_GPT... 2024-07-26 10:01:24,206 DEBUG Asking user via keyboard... Input: y 2024-07-26 10:01:39,318 INFO -=-=-=-=-=-=-= COMMAND AUTHORISED BY USER -=-=-=-=-=-=-= 这里执行需要你的授权确认，输入\u0026quot;y\u0026quot;表示授权确认继续，输入\u0026quot;y -10“ 表示后续10个都确认，输入\u0026quot;y -N\u0026quot;表示直至结束都确认。这里输入Y回车，继续执行命令，输出执行结果如下：\n1 2 3 4 5 6 7 8 9 2024-07-26 10:01:54,733 DEBUG PROCESSING: SYSTEM: The user is going to give you a text enclosed in triple quotes. The text represents an action, the reason for its execution, and its result. Condense the action taken and its result into one line. Preserve any specific factual information gathered by the action. USER: \u0026#34;\u0026#34;\u0026#34;Executed `web_search(query=\u0026#39;popular vegetarian sandwich ingredients\u0026#39;)` - **Reasoning:** \u0026#34;Considering the lack of specific user preferences, designing a versatile vegetarian sandwich recipe will cater to potentially broad tastes and preferences. This would include elements like vegetables, protein, and suitable spreads to ensure the sandwich is nutritious and delicious.\u0026#34; - **Status:** `error` - **Reason:** https://duckduckgo.com RequestsError: Failed to perform, curl: (28) Connection timed out after 10001 milliseconds. See https://curl.se/libcurl/c/libcurl-errors.html first for more details. - **Error:** CommandExecutionError(\u0026#39;https://duckduckgo.com RequestsError: Failed to perform, curl: (28) Connection timed out after 10001 milliseconds. See https://curl.se/libcurl/c/libcurl-errors.html first for more details.\u0026#39;) \u0026#34;\u0026#34;\u0026#34; autoGPT默认的搜索操作是访问dockdockgo.com网站获取结果，由于国内无法访问该网站，所以这里执行失败。这个可以通过扩展搜索插件，使用bing或其它的搜索引擎解决。\n保存行动结果\rautoGPT会将执行的步骤及结果信息保存下来，在下一轮循环中会追加这些信息作为上下文发送给大模型，以便大模型知道已经执行的动作，从而更好的判断接下来要执行的动作，同时也避免重复执行相同的命令。\n生成总结\rautoGPT同时还会将执行动作的步骤及结果信息，发送给大模型，来生成摘要。将执行的行动及其结果信息进行总结摘要，保留行动过程中收集到的任何具体事实信息。这个摘要在后续的循环中使用，查看代码看到，在下一次循环时，会添加之前的执行步骤信息，前4个步骤使用完整的格式来描述，后面则使用摘要来描述.\n这样做的目的，一方面可以压缩提示词内容，减少存储空间及TOKEN的使用量；另一方还可以通过大模型过滤无关紧要的信息，保留跟当前目标相关的信息。\n调用大模型，生成总结summary信息如下：\n1 2 3 ---------------- SUMMARY ----------------- Attempted web search for popular vegetarian sandwich ingredients but encountered a connection timeout error from DuckDuckGo. ------------------------------------------ 至此完成一轮的交互，重新进入下一轮的循环，直到遇到\u0026quot;finish\u0026quot;命令,来结束整个对话流程。\n构建第二轮Prompt\r新一轮循环开始，这一步的提示词，跟第一步相比，多增加了前面执行的步骤信息，放在\u0026quot;## Progress\u0026quot;部分，提示词如下：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 2024-07-26 10:02:11,818 DEBUG Executing prompt: ============== ChatPrompt ============== Length: 6 messages ----------------- SYSTEM ----------------- You are VeggieSandwichChef_GPT, an AI dedicated to creating delicious and innovative vegetarian sandwich recipes, focusing on healthy ingredients and diverse flavors. Your decisions must always be made independently without seeking user assistance. Play to your strengths as an LLM and pursue simple strategies with no legal complications. ## Constraints You operate within the following constraints: 1. Exclusively use the commands listed below. 2. You can only act proactively, and are unable to start background jobs or set up webhooks for yourself. Take this into account when planning your actions. 3. You are unable to interact with physical objects. If this is absolutely necessary to fulfill a task or objective or to complete a step, you must ask the user to do it for you. If the user refuses this, and there is no other way to achieve your goals, you must terminate to avoid wasting time and energy. 4. Avoid using ingredients that are known allergens without offering alternatives. 5. Keep the recipes simple with ingredients that are easily accessible. 6. Do not include any meat, fish, or poultry products in the recipes. 7. Respect dietary restrictions by clearly indicating if a recipe contains common allergens like nuts, soy, or gluten. ## Resources You can leverage access to the following resources: 1. Internet access for searches and information gathering. 2. The ability to read and write files. 3. You are a Large Language Model, trained on millions of pages of text, including a lot of factual knowledge. Make use of this factual knowledge to avoid unnecessary gathering of information. ## Commands These are the ONLY commands you can use. Any action you perform must be possible through one of these commands: 1. list_folder: List the items in a folder. Params: (folder: string) 2. open_file: Opens a file for editing or continued viewing; creates it if it does not exist yet. Note: If you only need to read or write a file once, use `write_to_file` instead.. Params: (file_path: string) 3. open_folder: Open a folder to keep track of its content. Params: (path: string) 4. read_file: Read an existing file. Params: (filename: string) 5. write_file: Write a file, creating it if necessary. If the file exists, it is overwritten.. Params: (filename: string, contents: string) 6. ask_user: If you need more details or information regarding the given goals, you can ask the user for input. Params: (question: string) 7. web_search: Searches the web. Params: (query: string) 8. read_webpage: Read a webpage, and extract specific information from it. You must specify either topics_of_interest, a question, or get_raw_content.. Params: (url: string, topics_of_interest?: Array\u0026lt;string\u0026gt;, question?: string, get_raw_content?: boolean) 9. finish: Use this to shut down once you have completed your task, or when there are insurmountable problems that make it impossible for you to finish your task.. Params: (reason: string) ## Best practices 1. Continuously review and analyze your actions to ensure you are performing to the best of your abilities. 2. Constructively self-criticize your big-picture behavior constantly. 3. Reflect on past decisions and strategies to refine your approach. 4. Every command has a cost, so be smart and efficient. Aim to complete tasks in the least number of steps. 5. Only make use of your information gathering abilities to find information that you don\u0026#39;t yet have knowledge of. 6. Always focus on a blend of nutritional balance and taste, incorporating a variety of vegetables, proteins, and grains. 7. Include clear, easy-to-follow instructions that cater to both novice and experienced cooks. 8. Ensure recipes can be adapted for vegan preferences whenever possible, providing suitable substitutions for dairy or other animal products. 9. Highlight the preparation time and serving size to help users in meal planning and preparation. ## Your Task The user will specify a task for you to execute, in triple quotes, in the next message. Your job is to complete the task while following your directives as given above, and terminate when your task is done. ------------------ USER ------------------ \u0026#34;\u0026#34;\u0026#34;制作素食三明治食谱\u0026#34;\u0026#34;\u0026#34; ----------------- SYSTEM ----------------- ## Progress * Step 1: Executed `web_search(query=\u0026#39;popular vegetarian sandwich ingredients\u0026#39;)` - **Reasoning:** \u0026#34;Considering the lack of specific user preferences, designing a versatile vegetarian sandwich recipe will cater to potentially broad tastes and preferences. This would include elements like vegetables, protein, and suitable spreads to ensure the sandwich is nutritious and delicious.\u0026#34; - **Status:** `error` - **Reason:** https://duckduckgo.com RequestsError: Failed to perform, curl: (28) Connection timed out after 10001 milliseconds. See https://curl.se/libcurl/c/libcurl-errors.html first for more details. - **Error:** CommandExecutionError(\u0026#39;https://duckduckgo.com RequestsError: Failed to perform, curl: (28) Connection timed out after 10001 milliseconds. See https://curl.se/libcurl/c/libcurl-errors.html first for more details.\u0026#39;) ----------------- SYSTEM ----------------- The current time and date is Fri Jul 26 10:02:11 2024 ----------------- SYSTEM ----------------- Respond with pure JSON. The JSON object should be compatible with the TypeScript type `Response` from the following: interface Response { thoughts: { // Relevant observations from your last action (if any) observations: string; // Thoughts text: string; reasoning: string; // Constructive self-criticism self_criticism: string; // Short markdown-style bullet list that conveys the long-term plan plan: string; // Summary of thoughts, to say to user speak: string; }; command: { name: string; args: Record\u0026lt;string, any\u0026gt;; }; } ------------------ USER ------------------ Determine exactly one command to use next based on the given goals and the progress you have made so far, and respond using the JSON schema specified previously: ========================================== 可以看到，上面的提示词中，追加了之前执行的步骤信息，放在了“## Progress”下面。\n重新进入下一轮的循环，不断迭代，逐步朝着完成目标的方向前进，直到程序中断或模型认为已经完成了目标，返回下一步执行的命令是\u0026quot;finish\u0026quot;, 来结束整个对话流程。\n源码解析\r这里使用截止当前2024年7月最新的autoGPT版本 V0.5.1 代码进行解析。\n代码结构\r该版本AutoGPT项目由四个主要部分组成:\nThe Agent ： AutoGPT的核心，一个由llm提供支持的半自主代理，为你执行任何任务。 The Benchmark ： 基准测试，agbenchmark` 可以与任何支持代理协议的代理一起使用。进行自主、客观的性能评估，确保您的代理已准备好在现实世界中执行任务。 The Forge ：打造自己的代理， Forge提供了创建你定制代理应用程序的现成模板。 The Frontend 代理的前端交互UI。 另外为了将这些联系在一起，在项目的根目录中添加了一个CLI命令，使得非常方便的使用以上组件。如安装依赖只需要执行命令：\n1 . /run setup 这里针对AutoGPT的核心代码进行解析。该部分代码在 “%下载根目录%\\autogpts\\autogpt\\autogpt”中。\n从代码组织结构，主要包含以下几个模块：\nagents ：配置及创建Agent，以及Agent 的执行如调用大模型推理、执行行动命令、解析结果、保存历史行动步骤等的核心逻辑。 app：控制整个Auto-GPT的核心运行流程，将各个模块串联在一起完成用户的目标。 comands：集成可执行的命令工具列表。如读写文件、网页搜索等等。 config：全局环境的配置和agent任务相关的配置，全局环境配置对应的是.env文件中的配置，读取配置文件prompt_settings.yaml、ai_settings.yaml、azure.yaml、plugins_config.yaml的配置。 core：公共基础类，如基础基类定义等。调用openAI的聊天交互请求处理其实写在该模块中。位置在：/core/resource/model_providers/openai.py llm：调用大模型接口参数定义及交互的一些处理。 memory：历史交互记录的缓存处理，在该版本中只保留了JSON文件的方式来保存历史行动记录。不在支持pinecone、redis、weaviate、milvus等存储服务。 prompt：默认提示词的配置及格式处理。 代码执行流程\r命令行的执行入口在 autogpt/app/cli.py 文件中\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 def run( continuous: bool, continuous_limit: Optional[int], speak: bool, gpt3only: bool, gpt4only: bool, //...省略 ) -\u0026gt; None: \u0026#34;\u0026#34;\u0026#34; 根据用户指定的任务设置并运行agent，或者恢复执行之前存在的agent。 \u0026#34;\u0026#34;\u0026#34; # 将import放在函数内部，以避免在启动CLI时导入所有内容 from autogpt.app.main import run_auto_gpt run_auto_gpt( continuous=continuous, continuous_limit=continuous_limit, ai_settings=ai_settings, prompt_settings=prompt_settings, //...省略 ) 初始化并创建agent\r进入run_auto_gpt 方法，在autogpt/app/main.py文件下。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 async def run_auto_gpt( continuous: bool = False, continuous_limit: Optional[int] = None, //...省略 ): # 设置配置,从配置文件.env,ai_settings.yaml、azure.yaml、plugins_config.yaml # prompt_settings.yaml中读取. config = ConfigBuilder.build_config_from_env() #配置数据存储的方式，默认为本地文件保存，其它配置选项为GCS(google云存储)、S3(亚马逊 #云存储) local = config.file_storage_backend == FileStorageBackendName.LOCAL restrict_to_root = not local or config.restrict_to_workspace file_storage = get_storage( config.file_storage_backend, root_path=\u0026#34;data\u0026#34;, restrict_to_root=restrict_to_root ) file_storage.initialize() //...省略部分，检查openAI key、根据函数的传参覆盖默认配置 //实例化llm提供者，封装与LLM的接口调用。 llm_provider = _configure_openai_provider(config) //...省略部分，初始化日志处理器，配置插件等 # 列出已存在的代理列表，让用户选择要运行的代理 agent_manager = AgentManager(file_storage) existing_agents = agent_manager.list_agents() load_existing_agent = \u0026#34;\u0026#34; if existing_agents: print( \u0026#34;Existing agents\\n---------------\\n\u0026#34; + \u0026#34;\\n\u0026#34;.join(f\u0026#34;{i} - {id}\u0026#34; for i, id in enumerate(existing_agents, 1)) ) load_existing_agent = clean_input( config, \u0026#34;Enter the number or name of the agent to run,\u0026#34; \u0026#34; or hit enter to create a new one:\u0026#34;, ) //...省略 //若用户有选择之前的代理，则询问用户是否恢复该代理，默认或回复“y”则恢复代理状态,并根据该状态创建代理。 if load_existing_agent: agent_state = None while True: answer = clean_input(config, \u0026#34;Resume? [Y/n]\u0026#34;) if answer == \u0026#34;\u0026#34; or answer.lower() == \u0026#34;y\u0026#34;: agent_state = agent_manager.load_agent_state(load_existing_agent) break elif answer.lower() == \u0026#34;n\u0026#34;: break if agent_state: //根据之前的代理状态创建代理 agent = configure_agent_with_state( state=agent_state, app_config=config, file_storage=file_storage, llm_provider=llm_provider, ) //检查agent的最后执行命令如果是finish，表明之前的已经完成，则询问用户给出一个后续问题或任务。 if ( agent.event_history.current_episode and agent.event_history.current_episode.action.name == finish.__name__ and not agent.event_history.current_episode.result ): finish_reason = agent.event_history.current_episode.action.args[\u0026#34;reason\u0026#34;] print(f\u0026#34;Agent previously self-terminated; reason: \u0026#39;{finish_reason}\u0026#39;\u0026#34;) new_assignment = clean_input( config, \u0026#34;Please give a follow-up question or assignment:\u0026#34; ) //没有已存在的代理或用户没有选择已存在的代理，则要求用户输入一个任务，尽可能详细描述。 if not agent: task = \u0026#34;\u0026#34; while task.strip() == \u0026#34;\u0026#34;: task = clean_input( config, \u0026#34;Enter the task that you want AutoGPT to execute,\u0026#34; \u0026#34; with as much detail as possible:\u0026#34;, ) //加载默认的指令模板数据,从prompt_settings.yaml文件中提取 base_ai_directives = AIDirectives.from_file(config.prompt_settings_file) //同时请求大模型根据任务生成角色名称（_GPT），描述，以及在“最佳实践”和“约束”类别中各1至5 //条指令，这些指令与完成任务高度相关。 ai_profile, task_oriented_ai_directives = await generate_agent_profile_for_task( task, app_config=config, llm_provider=llm_provider, ) //整合基础模板数据和大模型生成的数据，构建提示词 ai_directives = base_ai_directives + task_oriented_ai_directives //...省略部分 //创建代理 agent = create_agent( agent_id=agent_manager.generate_id(ai_profile.ai_name), task=task, ai_profile=ai_profile, directives=ai_directives, app_config=config, file_storage=file_storage, llm_provider=llm_provider, ) ################# # 运行代理主流程 # ################# try: await run_interaction_loop(agent) except AgentTerminated: //中断或结束时，保存代理状态，可用户指定代理ID agent_id = agent.state.agent_id logger.info(f\u0026#34;Saving state of {agent_id}...\u0026#34;) # Allow user to Save As other ID save_as_id = clean_input( config, f\u0026#34;Press enter to save as \u0026#39;{agent_id}\u0026#39;,\u0026#34; \u0026#34; or enter a different ID to save to:\u0026#34;, ) await agent.save_state(save_as_id if not save_as_id.isspace() else None) 运行代理主流程\r上一步进入run_interaction_loop方法，该方法同样位于 autogpt/app/main.py 文件中，这个是控制执行的主流程，运行代理的交互循环。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 #运行代理的主交互循环,传入参数agent async def run_interaction_loop( agent: \u0026#34;Agent\u0026#34;, ) -\u0026gt; None: //...省略部分，加载代理配置参数、设置日志处理器、设置中断信号处理等代码 ######################### # 应用程序主循环 # ######################### #continuous_mode/continuous_limit配置默认的循环次数，同时询问用户授权交互中，用户可指定继续循环 #的次数（回复\u0026#39;y\u0026#39;：当前继续循环(即cycles_remaining+1)，回复‘y -n’：后面n次继续循环，回复\u0026#34;n\u0026#34;退出程 #序）,cycle_budget:为自动继续循环执行的次数，remaining：剩下循环的次数 while cycles_remaining \u0026gt; 0: logger.debug(f\u0026#34;Cycle budget: {cycle_budget}; remaining: {cycles_remaining}\u0026#34;) handle_stop_signal() ############ # 让代理(内部是调用大模型进行推理决策)决定下一步要采取的行动。返回下一步执行命令 ########### with spinner: try: ( command_name, command_args, assistant_reply_dict, ) = await agent.propose_action() except InvalidAgentResponseError as e: //记录连续的失败的次数，若超过3次，则退出 logger.warning(f\u0026#34;The agent\u0026#39;s thoughts could not be parsed: {e}\u0026#34;) consecutive_failures += 1 if consecutive_failures \u0026gt;= 3: logger.error( \u0026#34;The agent failed to output valid thoughts\u0026#34; f\u0026#34; {consecutive_failures} times in a row. Terminating...\u0026#34; ) raise AgentTerminated( \u0026#34;The agent failed to output valid thoughts\u0026#34; f\u0026#34; {consecutive_failures} times in a row.\u0026#34; ) continue #重置连续的失败的次数 consecutive_failures = 0 ############### # 打印agent的想法和下一步执行的命令 # ############### update_user( ai_profile, command_name, command_args, //... ) handle_stop_signal() //若当前继续循环次数为1时，需要征询用户的授权指令，输入\u0026#39;y\u0026#39;表示授权执行，\u0026#39;y -N\u0026#39;表示连续运行N个命 //令，\u0026#39; N\u0026#39;表示退出程序，或输入反馈建议. if cycles_remaining == 1: # Last cycle user_feedback, user_input, new_cycles_remaining = await get_user_feedback( legacy_config, ai_profile, ) //...省略部分代码 ################### # 执行命令# ################### if command_name: //代理执行命令，打印执行的结果 result = await agent.execute(command_name, command_args, user_input) if result.status == \u0026#34;success\u0026#34;: logger.info( result, extra={\u0026#34;title\u0026#34;: \u0026#34;SYSTEM:\u0026#34;, \u0026#34;title_color\u0026#34;: Fore.YELLOW} ) elif result.status == \u0026#34;error\u0026#34;: logger.warning( f\u0026#34;Command {command_name} returned an error: \u0026#34; f\u0026#34;{result.error or result.reason}\u0026#34; ) 这里是核心部分，主控制流程，不断循环迭代代理的执行，主要包括以下两个操作：\nagent.propose_action()\n代理调用大模型进行推理决策，决定下一步要采取的行动。返回下一步执行命令名称及参数。\nagent.execute(command_name, command_args, user_input)\n代理执行命令，返回命令的执行结果。\n接下来我们来看这两个方法的逻辑。\n决策下一步的行动\r进入agent.propose_action() 方法 ，位于autogpt/agents/features/watchdog.py 文件中。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 async def propose_action(self, *args, **kwargs) -\u0026gt; BaseAgent.ThoughtProcessOutput: ######## #调用大模型进行推理决策，生成下一步要采取的行动，获取响应的命令名称、参数、思考推理过程。 ######## command_name, command_args, thoughts = await super( WatchdogMixin, self ).propose_action(*args, **kwargs) #若没有禁用混合模式(即big_brain为false),则判断是否配置的fast_llm 与 smart_llm大模型不一 #致，若不一致，且上一步大模型响应结果中没有生成下一步的执行命令或执行命令与前一次调用的一样，则使 #用SMART_LLM模型重新推理。 if not self.config.big_brain and self.config.fast_llm != self.config.smart_llm: previous_command, previous_command_args = None, None if len(self.event_history) \u0026gt; 1: # Detect repetitive commands previous_cycle = self.event_history.episodes[ self.event_history.cursor - 1 ] previous_command = previous_cycle.action.name previous_command_args = previous_cycle.action.args rethink_reason = \u0026#34;\u0026#34; if not command_name: rethink_reason = \u0026#34;AI did not specify a command\u0026#34; elif ( command_name == previous_command and command_args == previous_command_args ): rethink_reason = f\u0026#34;Repititive command detected ({command_name})\u0026#34; if rethink_reason: logger.info(f\u0026#34;{rethink_reason}, re-thinking with SMART_LLM...\u0026#34;) with ExitStack() as stack: @stack.callback def restore_state() -\u0026gt; None: # Executed after exiting the ExitStack context self.config.big_brain = False # Remove partial record of current cycle self.event_history.rewind() # Switch to SMART_LLM and re-think self.big_brain = True return await self.propose_action(*args, **kwargs) return command_name, command_args, thoughts 这一步的核心是调用大模型进行推理决策，生成下一步要采取的行动，获取响应的命令名称、参数、思考推理过程。内部调用方法：super(WatchdogMixin, self ).propose_action(*args, **kwargs) ，该方法在\nautogpt/agents/base.py 文件中。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 async def propose_action(self) -\u0026gt; ThoughtProcessOutput: ####### #基于任务和当前状态，提出要执行的下一个操作.返回: 命令名称和参数(如果有的话)以及代理的想法。 ##### # 作为临时存储的提示词生成器，应用插件添加额外提示元素。(例如命令、约束、最佳实践) self._prompt_scratchpad = PromptScratchpad() #构建提示词， prompt: ChatPrompt = self.build_prompt(scratchpad=self._prompt_scratchpad) prompt = self.on_before_think(prompt, scratchpad=self._prompt_scratchpad) #调用大模型生成ReAct（Reason + Act）框架的响应 response = await self.llm_provider.create_chat_completion( prompt.messages, functions=get_openai_command_specs( self.command_registry.list_available_commands(self) ) + list(self._prompt_scratchpad.commands.values()) if self.config.use_functions_api else [], model_name=self.llm.name, completion_parser=lambda r: self.parse_and_process_response( r, prompt, scratchpad=self._prompt_scratchpad, ), ) self.config.cycle_count += 1 return self.on_response( llm_response=response, prompt=prompt, scratchpad=self._prompt_scratchpad, ) 内部通过调用self.llm_provider.create_chat_completion(\u0026hellip;) 方法，该方法内部使用openai的客户端封装包AsyncAPIClient 调用LLM官方接口，获取大模型的响应数据。这里就不在贴出代码了。\n执行命令\r将完整提示词发送给大模型，大模型生成下一步要执行的命令、参数、及思考的过程数据。响应格式如：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 { \u0026#34;thoughts\u0026#34;: { \u0026#34;observations\u0026#34;: \u0026#34;The user did not provide specific preferences...\u0026#34;, \u0026#34;text\u0026#34;: \u0026#34;To proceed effectively, I should create a well-rounded vegetarian ...\u0026#34;, \u0026#34;reasoning\u0026#34;: \u0026#34;Considering the lack of specific user preferences, designing a...\u0026#34;, \u0026#34;self_criticism\u0026#34;: \u0026#34;I could have followed up with another question to probe ...\u0026#34;, \u0026#34;plan\u0026#34;: \u0026#34;- Create a flexible vegetarian sandwich recipe...\u0026#34;, \u0026#34;speak\u0026#34;: \u0026#34;Let\u0026#39;s create a delicious and balanced vegetarian sandwich!...\u0026#34; }, \u0026#34;command\u0026#34;: { \u0026#34;name\u0026#34;: \u0026#34;web_search\u0026#34;, \u0026#34;args\u0026#34;: { \u0026#34;query\u0026#34;: \u0026#34;popular vegetarian sandwich ingredients\u0026#34; } } } 解析响应数据，获取下一步执行命令名称，参数、思考推理过程信息。调用agent的excute方法执行命令，\n1 2 3 4 5 6 7 8 9 10 11 12 if command_name: result = await agent.execute(command_name, command_args, user_input) if result.status == \u0026#34;success\u0026#34;: logger.info( result, extra={\u0026#34;title\u0026#34;: \u0026#34;SYSTEM:\u0026#34;, \u0026#34;title_color\u0026#34;: Fore.YELLOW} ) elif result.status == \u0026#34;error\u0026#34;: logger.warning( f\u0026#34;Command {command_name} returned an error: \u0026#34; f\u0026#34;{result.error or result.reason}\u0026#34; ) agent.execute(\u0026hellip;) 方法位于autogpt/agents/agent.py文件中，代码如下：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 #执行命令 async def execute( self, command_name: str, command_args: dict[str, str] = {}, user_input: str = \u0026#34;\u0026#34;, ) -\u0026gt; ActionResult: result: ActionResult //在运行代理主流程代码中看到，如果cycles_remaining为1，则会询问用户输入授权确认，输入Y 即授权 //执行，也可以输入反馈信息，如果是用户反馈，这里就返回用户的反馈信息。 if command_name == \u0026#34;human_feedback\u0026#34;: result = ActionInterruptedByHuman(feedback=user_input) self.log_cycle_handler.log_cycle( self.ai_profile.ai_name, self.created_at, self.config.cycle_count, user_input, USER_INPUT_FILE_NAME, ) else: //在执行命令前，遍历插件的前置处理 for plugin in self.config.plugins: if not plugin.can_handle_pre_command(): continue command_name, command_args = plugin.pre_command( command_name, command_args ) try: //真正执行命令 return_value = await execute_command( command_name=command_name, arguments=command_args, agent=self, ) result = ActionSuccessResult(outputs=return_value) //...省略 # 更新行动结果 self.event_history.register_result(result) #同时对该行动及结果生成总结 await self.event_history.handle_compression( self.llm_provider, self.legacy_config ) return result 这里实际上是调用agent的方法execute_command()来执行命令。代码如下：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 # 执行命令并返回结果 async def execute_command( command_name: str, #要执行的命令的名称 arguments: dict[str, str],#命令的参数 agent: Agent, #正在执行命令的代理 ) -\u0026gt; CommandOutput: # 执行具有相同名称或别名(如果存在)的本机命令 #command_registry 是包含代理可用的所有命令的注册表 if command := agent.command_registry.get_command(command_name): try: #直接执行本机命令，获得执行结果 result = command(**arguments, agent=agent) if inspect.isawaitable(result): return await result return result except AgentException: raise except Exception as e: raise CommandExecutionError(str(e)) # 处理非本机命令 (比如扩展插件中的命令) if agent._prompt_scratchpad: for name, command in agent._prompt_scratchpad.commands.items(): if ( command_name == name or command_name.lower() == command.description.lower() ): try: return command.method(**arguments) except AgentException: raise except Exception as e: raise CommandExecutionError(str(e)) raise UnknownCommandError( f\u0026#34;Cannot execute command \u0026#39;{command_name}\u0026#39;: unknown command.\u0026#34; ) 更新行动结果\r上一步执行行动后，会更新当前的行动结果。\n1 2 3 4 5 6 7 8 def register_result(self, result: ActionResult) -\u0026gt; None: if not self.current_episode: raise RuntimeError(\u0026#34;Cannot register result for cycle without action\u0026#34;) elif self.current_episode.result: raise ValueError(\u0026#34;Result for current cycle already set\u0026#34;) self.current_episode.result = result self.cursor = len(self.episodes) 行动步骤添加到行动历史中保存，即agent的event_history中，那此次的行动步骤是什么时候添加到agent的行动历史中的呢？实际上是在调用大模型生成下一步的行动，返回响应数据进行解析后时添加的。代码在autogpt/agents/agent.py中。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 def parse_and_process_response( self, llm_response: AssistantChatMessage, *args, **kwargs ) -\u0026gt; Agent.ThoughtProcessOutput: ##解析响应内容，获取名称名称、参数、思考推理过程信息(observations、text、reasoning、 ##self_criticism、plan、speak) ( command_name, arguments, assistant_reply_dict, ) = self.prompt_strategy.parse_response_content(llm_response) # 检查command_name和arguments是否已经在event_history中 if self.event_history.matches_last_command(command_name, arguments): raise DuplicateOperationError( f\u0026#34;The command {command_name} with arguments {arguments} \u0026#34; f\u0026#34;has been just executed.\u0026#34; ) if command_name: //保存行动命令、及参数、推理信息 self.event_history.register_action( Action( name=command_name, args=arguments, reasoning=assistant_reply_dict[\u0026#34;thoughts\u0026#34;][\u0026#34;reasoning\u0026#34;], ) ) return command_name, arguments, assistant_reply_dict 这里会将返回的行动命令、及参数、推理信息到agent的event_history中存储。\n生成总结\r更新当前的行动结果后，同时对该行动及结果生成总结。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #执行命令 async def execute( self, command_name: str, command_args: dict[str, str] = {}, user_input: str = \u0026#34;\u0026#34;, ) -\u0026gt; ActionResult: //...省略 # 更新行动结果 self.event_history.register_result(result) #同时对该行动及结果生成总结 await self.event_history.handle_compression( self.llm_provider, self.legacy_config ) return result 调用agent的event_history.handle_compression(\u0026hellip;)方法,对行动及结果信息调用大模型生成总结保存下来。代码如下：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 async def handle_compression( self, llm_provider: ChatModelProvider, app_config: Config ) -\u0026gt; None: ###### ##迭代动作历史中的所有不包含摘要的行动记录，并使用LLM生成摘要。 ##### compress_instruction = ( \u0026#34;The text represents an action, the reason for its execution, \u0026#34; \u0026#34;and its result. \u0026#34; \u0026#34;Condense the action taken and its result into one line. \u0026#34; \u0026#34;Preserve any specific factual information gathered by the action.\u0026#34; ) async with self._lock: # 收集所有没有摘要的行动记录 episodes_to_summarize = [ep for ep in self.episodes if ep.summary is None] #并行化调用生成总结 summarize_coroutines = [ #该方法调用大模型生成总结， summarize_text( episode.format(), instruction=compress_instruction, llm_provider=llm_provider, config=app_config, ) for episode in episodes_to_summarize ] summaries = await asyncio.gather(*summarize_coroutines) # 将摘要保存到对应的历史行动中 for episode, (summary, _) in zip(episodes_to_summarize, summaries): episode.summary = summary 构建下一轮提示词\r命令执行完成后，若程序没有中断或用户确认已完成任务退出，将进入下一轮的循环，重新构建生成下一步决策行动的提示词，这里的提示词与第一步不同的是，包含了历史的行动步骤及结果，这些信息可以帮助大模型不重复生产之前同样的操作。\n构建提示词方法build_prompt(\u0026hellip;) ，在 autogpt/agents/base.py文件中，代码如下:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 def build_prompt( self, scratchpad: PromptScratchpad, extra_commands: Optional[list[CompletionModelFunction]] = None, extra_messages: Optional[list[ChatMessage]] = None, **extras, ) -\u0026gt; ChatPrompt: ##使用\u0026#39; self.prompt_strategy \u0026#39;构造一个提示符`. prompt = self.prompt_strategy.build_prompt( task=self.state.task, ai_profile=self.ai_profile, ai_directives=ai_directives, commands=get_openai_command_specs( self.command_registry.list_available_commands(self) ) + extra_commands, //历史行动步骤记录 event_history=self.event_history, //... ), extra_messages=extra_messages, **extras, ) return prompt 至此，程序不断循环执行，直到它达到目标或用户中断执行。若大模型认为已经完成了目标，会生成下一步的行动命令是finish，表示已经完成目标任务。\n执行流程时序图\rautoGPT整体执行流程时序图如下：\n命令列表\rautoGPT集成了一系列的行动命令，在autogpt/commands目录下，用户也可以根据需要开发自己的行动命令。\n1 2 3 4 5 6 7 8 9 10 11 ## Commands These are the ONLY commands you can use. Any action you perform must be possible through one of these commands: 1. list_folder: List the items in a folder. Params: (folder: string) 2. open_file: Opens a file for editing or continued viewing; creates it if it does not exist yet. Note: If you only need to read or write a file once, use `write_to_file` instead.. Params: (file_path: string) 3. open_folder: Open a folder to keep track of its content. Params: (path: string) 4. read_file: Read an existing file. Params: (filename: string) 5. write_file: Write a file, creating it if necessary. If the file exists, it is overwritten.. Params: (filename: string, contents: string) 6. ask_user: If you need more details or information regarding the given goals, you can ask the user for input. Params: (question: string) 7. web_search: Searches the web. Params: (query: string) 8. read_webpage: Read a webpage, and extract specific information from it. You must specify either topics_of_interest, a question, or get_raw_content.. Params: (url: string, topics_of_interest?: Array\u0026lt;string\u0026gt;, question?: string, get_raw_content?: boolean) 9. finish: Use this to shut down once you have completed your task, or when there are insurmountable problems that make it impossible for you to finish your task.. Params: (reason: string) 总结\r​\tAutoGPT是一项使用大模型自动化来完成任务的尝试，是运用ReAct（Reason + Act）框架来解决问题完成目标任务的优秀实践。运用大模型的泛化推理决策能力，结合可定制的工具列表，自主迭代循环思考与行动、逐步朝着完成目标方向前进，最终完成用户的目标。\n​ autoGPT展示了AI大模型在自主完成任务方面的巨大潜力，随着大模型技术不断发展，类似autoGPT这样基于大模型的应用Agent也会不断完善发展，扩展大模型的能力边界，应用于企业或个人生活的各种场景，解决实际问题，并提供生产效率。\n​ 因水平有限，难免存在认识不足或理解错误的地方，欢迎各位批评指正，一起学习交流，非常感谢！\n项目相关代码及文件放在网盘中，地址：\n链接：https://pan.quark.cn/s/11ca682f0ec7 提取码：KXJr\n参考资料\rhttps://www.maartengrootendorst.com/blog/autogpt/\nhttps://docs.agpt.co/autogpt/\n","date":"2026-01-26T00:00:00Z","image":"/p/%E6%B7%B1%E5%85%A5%E8%A7%A3%E6%9E%90autogpt%E5%8E%9F%E7%90%86%E5%AE%9E%E6%88%98%E4%B8%8E%E6%BA%90%E7%A0%81%E6%8B%86%E8%A7%A3/images/image-20240730120705816.png","permalink":"/p/%E6%B7%B1%E5%85%A5%E8%A7%A3%E6%9E%90autogpt%E5%8E%9F%E7%90%86%E5%AE%9E%E6%88%98%E4%B8%8E%E6%BA%90%E7%A0%81%E6%8B%86%E8%A7%A3/","title":"深入解析AutoGPT：原理、实战与源码拆解"},{"content":"Plan-and-execute 原理、架构与代码实现\r1. 介绍\r​\t为挖掘大型语言模型的推理能力，以解决多步复杂推理任务。研究人员（Wei 等人，2022）提出了少样本思维链（CoT）提示法，通过少量人工构建的分步推理示例，能让大型语言模型明确生成推理步骤，提高解决推理任务的准确率。同时，为了省去提示中人工构建示例的操作，一些研究人员（ Kojima 等人，2022）提出零样本思维链（Zero-shot-CoT）：通过将目标问题陈述后附加 “让我们一步步思考” 作为输入提示给大型语言模型，出人意料地让大语言模型取得了与少样本思维链提示法相近的性能。但尽管零样本思维链取得了成功，但它仍存在以下三个缺陷：\n计算错误：中间计算过程出错，导致最终答案偏差。 步骤缺失错误：复杂任务中遗漏关键推理环节，逻辑链断裂。 语义误解错误：出现对问题的语义理解错误和推理步骤不连贯等其它错误。 ​\t为解决零样本思维链的上述缺陷，尤其是 “步骤缺失” 与 “推理不连贯” 问题，由 Lei Wang 等人于 2023 年提出Plan-and-Solve（简称 PS，计划-求解）框架，其论文《Plan-and-Solve Prompting: Improving Zero-Shot Chain-of-Thought Reasoning by Large Language Models》（论文地址：https://arxiv.org/pdf/2305.04091）于 2023 年 5 月发表。Plan-and-Solve架构作为一种改进零样本思维链（ Chain-of-Thought ，CoT）推理的方法。其核心思想是通过引导模型先制定解决问题的计划，再按照计划逐步执行，这一模式显著提高了大模型在复杂任务上的成功率和效率。\n2 论文实验\r​\tPlan-and-Solve方法本质上是先计划再执行，即先把用户的问题分解成一个个的子任务，然后再执行这些子任务，最后合并输出得到结果。在论文中的实验作法是：简单地将零样本思维链中的 “让我们一步步思考” 替换为 “让我们首先理解问题并制定解决计划，然后按照计划一步步解决问题”。如下图所示：\n​\t图：GPT-3 在（a）零样本思维链提示、（b）Plan-and-Solve提示、（c）答案提取提示的示例。\n​\t上图中，分别演示了三种提示：（a）零样本思维链提示、（b）Plan-and-Solve提示、（c）答案提取提示的示例。零样本思维链（Zero-shot-CoT）通过 “让我们一步步思考” 促使大语言模型生成多步骤推理，但在问题复杂时仍可能产生错误的推理步骤，最终结果错误。与零样本思维链不同，Plan-and-Solve提示首先要求大语言模型通过生成分步计划并执行该计划来设计解题方案，得出最终正确的结果。答案提取提示在Plan-and-Solve提示的基础上，进一步提炼最终答案的格式：因此，答案（阿拉伯数字形式）是60%。\n​\t以下是图片文字的中文翻译：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 （a）零样本思维链提示 问题（Q）：在一个有 20 名学生的舞蹈班中，20% 的学生报名参加当代舞，剩下学生中的 25% 报名参加爵士舞，其余的报名参加嘻哈舞。整个班级中报名参加嘻哈舞的学生占比是多少？ 回答（A）：逐步思考。 首先，班级中 20% 的学生报名参加当代舞，这意味着剩下 80% 的学生。 接下来，剩下 80% 学生中的 25% 报名参加爵士舞，所以这意味着原始班级中 25% 的学生报名参加爵士舞。这样就剩下原始班级的 55%。 最后，班级剩下的（55%）学生报名参加嘻哈舞。所以，整个班级中 55% 的学生报名参加嘻哈舞。（错误答案 ） （b）Plan-and-Solve提示 问题（Q）：在一个有 20 名学生的舞蹈班中，20% 的学生报名参加当代舞，剩下学生中的 25% 报名参加爵士舞，其余的报名参加嘻哈舞。整个班级中报名参加嘻哈舞的学生占比是多少？ 回答（A）：首先，理解问题并制定解决问题的计划。 然后，逐步执行计划并解决问题。 计划（Plan）： 步骤 1：计算报名参加当代舞和爵士舞的学生总数。 步骤 2：计算报名参加嘻哈舞的学生总数。 步骤 3：计算报名参加嘻哈舞的学生占比。 解答（Solution）： 步骤 1：20 名学生的 20% 是 4 名学生，剩下 16 名学生的 25% 是 4 名学生。所以，报名参加当代舞和爵士舞的学生总数是 8 名。 步骤 2：剩下的 12 名学生报名参加嘻哈舞。 步骤 3：报名参加嘻哈舞的学生占比是 12/20 = 60%。（正确答案 ） （c）Plan-and-Solve答案提取提示 问题（Q）：在一个有 20 名学生的舞蹈班中，20% 的学生报名参加当代舞，剩下学生中的 25% 报名参加爵士舞，其余的报名参加嘻哈舞。整个班级中报名参加嘻哈舞的学生占比是多少？ 回答（A）：首先，理解问题并制定解决问题的计划。 然后，逐步执行计划并解决问题。 计划（Plan）： 步骤 1：计算报名参加当代舞和爵士舞的学生总数。 步骤 2：计算报名参加嘻哈舞的学生总数。 步骤 3：计算报名参加嘻哈舞的学生占比。 解答（Solution）： 步骤 1：20 名学生的 20% 是 4 名学生，剩下 16 名学生的 25% 是 4 名学生。所以，报名参加当代舞和爵士舞的学生总数是 8 名。 步骤 2：剩下的 12 名学生报名参加嘻哈舞。 步骤 3：报名参加嘻哈舞的学生占比是 12/20 = 60%。 因此，答案（阿拉伯数字形式）是 60% 。 ​\t为解决零样本思维链的计算错误并提升生成的推理步骤质量，我们为 PS 提示法添加了更详细的指令。具体作法是：提示词中增加了 “提取相关变量及其对应数值” 和 “计算中间结果（注意计算和常识）” 的指令。这种提示变体被称为 PS + 提示策略（如下图（b）所示）。\n​\t​ 图:（a）Plan-and-Solve提示（PS 提示）和（b）带有更详细指令的Plan-and-Solve提示（PS + 提示）\n​\t上图展示了（a）Plan-and-Solve提示（PS 提示）和（b）带有更详细指令的Plan-and-Solve提示（PS + 提示）的示例， PS + 策略仅在提示中增加了 “提取相关变量及其对应数值” 和 “计算中间结果（注意计算和常识）” 的指令，尽管简单，却显著提升了生成的推理过程的质量，并获得正确答案。\n​\t论文在三个推理问题类别的十个基准数据集上评估所提出的Plan-and-Solve提示法，包含： 在六个数学推理数据集（包括 AQuA（代数应用题数据集）、GSM8K（小学数学应用题数据集）、MultiArith（需要多步推理和运算的数学应用题数据集）、AddSub（加减法算术应用题数据集）、SingleEq（单方程小学代数应用题的数据集） 和 SVAMP（ 4 年级及以下学生的单未知数算术应用题基准））；两个常识推理数据集（CommonsenseQA（多项选择题基准数据集）和 StrategyQA（需要多步推理但未给出推理步骤的基准数据集））以及两个符号推理数据集（Last Letter（单词的最后一个字母拼接起来的问题数据集） 和 Coin Flip（关于硬币经过翻转或未翻转后是否仍正面朝上的问题数据集））。\n​\t总体而言，实验评估结果表明：\n零样本 PS 提示法能生成比零样本思维链提示法质量更高的推理过程，因为 PS 提示提供了更详细的指令，引导大型语言模型进行正确推理。 零样本 PS + 提示法在所有推理问题和数据集上均大幅优于零样本思维链。此外，尽管 PS + 提示法不需要人工示例，但在算术推理上的性能与少样本思维链提示法相近，在部分数据集上优于少样本人工思维链提示法。 2. 核心思想\r2.1 原论文的Plan-and-Solve(规划-求解)提示策略\r​\t原论文中提出的Plan-and-Solve(规划-求解)提示策略（简：PS 和 PS + 提示法），是一种新的零样本思维链提示方法，核心是让大语言模型在最终答案前，引导大型语言模型制定解决问题的计划并生成中间推理过程（将整个任务分解为更小的子任务），然后按照计划逐步执行求解，以提高复杂推理任务的准确性和稳定性。其思路通俗来讲，就是让大模型在解决问题时模拟人的过程 “先想后做”，如我们在解决一道复杂的数学题时，不会一下子就写出答案，而是会先在脑海里规划出解题的步骤，比如先分析题目条件、确定使用的公式、分步骤计算等，然后再按照这个规划一步步去计算，最终得到答案。\n​\t原论文中提出的Plan-and-Solve(规划-求解)提示策略的实现比较简单，主要是通过构建提示词指令模板，该模板需满足以下两个标准：\n模板应能促使大型语言模型生成确定计划（子任务）并完成这些子任务。 模板应引导大型语言模型更关注计算和中间结果，尽可能确保其正确性。 ​\t为满足第一个标准，借鉴零样本思维链。在零样本思维链中，输入指令包括触发指令 “让我们一步步思考”。而零样本 PS 提示法包含 “制定计划” 和 “执行计划” 的指令，因此，提示为 “ 让我们首先理解问题并制定解决计划，然后按照计划一步步解决问题。”\n​\t为满足第二个标准，在基于计划的触发语句中扩展更详细的指令。具体来说，添加 “注意计算” 到触发句中，要求大型语言模型尽可能准确地进行计算。为减少因缺失必要推理步骤导致的错误，加入 “提取相关变量及其对应数值”，明确指示大型语言模型不要忽略输入问题中的相关信息。此外，在提示中添加 “计算中间结果”，以增强大型语言模型生成相关重要推理步骤的能力。如提示：\u0026ldquo;先理解问题，提取相关变量与对应数值，制定计划；再执行计划，计算中间结果（关注计算和常识），逐步解题并给出答案。\u0026rdquo;\n​\t总体来说，这种Plan-and-Solve(规划-求解)提示策略，尽管简单，却显著提升了生成的推理过程的质量。\n2.2 改进的计划与求解 （Plan-and-Solve)框架\r​\t随着构建基于大语言模型（LLM）的代理（Agent）在复杂任务中的应用不断深化，涌现了许多适配复杂场景的优质设计模式，其中比较典型的是ReAct设计模式，它通过 “思考（Reasoning）→行动（Action）→观察（Observation）” 的循环机制实现动态决策： 先对任务需求进行分析（Reason）并决策出下一步行动，然后执行行动(Action)（如工具调用或信息获取等），再基于行动结果调整策略（Observation），最终逐步逼近目标。\n​\t虽然ReAct 对一些任务有效，然而，ReAct 模式存在显著局限 —— 其决策逻辑高度依赖 “局部判断”，每一步仅聚焦单个行动选择，缺乏对任务全局的统筹规划与前瞻性布局。这使得它在复杂任务中易出现两类问题：一是 “路径冗余”，例如处理 “年度经营分析报告生成” 任务时，可能反复调用同一数据工具补充信息；二是 “目标偏移”，比如客服代理在处理用户复合诉求时，因过度关注某一细节问题，偏离了整体需求的解决方向。其在复杂场景下暴露出两个核心缺点：\nLLM 调用效率低：每次执行工具调用前，都需触发一次 LLM 调用以确定行动，导致任务流程中 LLM 调用频次过高，不仅增加时间成本，还提升了资源消耗； 全局规划缺失：LLM 仅针对当前子问题进行规划，未对整个任务的完整流程进行系统性推理，易形成次优执行轨迹，难以保障任务最终效果。 ​ 通过明确的规划步骤可以克服这两个缺点，借鉴了论文《Plan-and-Solve Prompting: Improving Zero-Shot Chain-of-Thought Reasoning by Large Language Models》中的思想，在 ReAct 的基础上添加 “全局规划” 能力，形成新的plan-and-execute框架，并涌现了类似的框架项目如Baby-AGI，其核心逻辑可概括为 “先全局规划、后分步执行、再动态调整” 的闭环逻辑：借助 LLM 对任务进行整体规划，生成完整分步计划；再按照计划有序执行，并根据执行反馈（如工具调用结果）对计划进行动态优化，形成 “规划 - 执行 - 反馈 - 调整” 的完整链路。既保留了 ReAct 的灵活性，又弥补了其全局统筹能力的不足。\n​\tPlan-and-Execute 的架构由规划器（Planner）、执行器（Executor）、重规划器（Replanner） 三部分组成：\n规划器（Planner）：接收用户需求后，调用 LLM 进行深度拆解与全局规划，生成完整分步计划。 执行器（Executor）：接收规划器输出的分步计划，按序执行每个子任务。在实现层面，执行器可为具备工具调用能力的独立 Agent— 根据子任务需求自主选择 API 接口、数据检索工具或自动化组件，完成具体操作并返回执行结果（如调用海关数据库获取目标市场关税数据）。 重规划器（Replanner）：负责根据执行器的实际执行情况和信息反馈来调整计划。 ​\tPlan-and-Execute框架是在Plan-and-Solve Prompting框架上的扩展。通过以上这三个部分，形成 “规划→执行→反馈→调整” 的完整循环链路，既保证了任务处理的全局统筹性，又赋予系统应对复杂场景的动态适应能力。\n3. 工作流程\rPlan-and-Execute（规划 - 执行）框架的工作流程图如下：\n输入任务：接收来自用户用户或系统提交待处理的任务。 Planner（规划器）：接收任务后，调用模型（LLM）对任务进行深入分析，生成清晰的分步执行计划（即 Task list ）。存储生成的计划列表，作为后续执行的 “行动指南”，让复杂任务变 “可落地步骤”。 Executor（执行器）：按任务列表逐个子任务执行，每次取出任务列表中的第一个任务进行处理，并将返回执行的结果传给Replanner。 Replanner（重规划器）：接收执行结果后，结合任务目标进行评估执行偏差，判断是否已经完成任务，还是需要调整计划。若以完成任务，则生成最终响应；若未完成，则生成调整计划列表。 更新计划清单：更新Replanner生成的调整计划列表。进入2-3循环。 输出结果：任务完成后，输出最终成果（如完整报告、问题解决方案 ），结束流程。 4. 与其它模式的对比\rPlan-and-Execute 架构与类似框架ReAct、ReWOO的核心对比如下：\nReAct：通过 “思考→行动→观察” 的循环来实现动态决策。智能体先对任务需求进行思考分析，做出下一步行动决策并执行，再观察行动的结果进行下一轮的思考分析，逐步逼近目标 。但本质是 “局部决策驱动”，每步仅聚焦单个行动选择，缺乏对任务全局的统筹规划，复杂场景下易出现路径冗余或目标偏移，且每次工具调用前均需触发 LLM 调用，Token 消耗与效率成本较高。 ReWOO ：ReWOO 是对 ReAct 框架的优化，主要是引入规划器，将核心组件拆分为三个独立模块：规划器、工作器和求解器。先使用规划器分解任务并制定相互依赖的计划（子任务）列表；再交给工作器依次执行；执行完成后由求解器整合结果。解决了 ReAct 中 \u0026ldquo;每次执行工具调用前，都需触发一次 LLM 调用以确定行动，导致 LLM 调用频次过高\u0026quot;的问题，从而显著降低 Token 消耗。但生成计划后，计划执行较为刚性，缺乏动态调整机制。若某子任务执行结果与预期偏差（如检索到的信息失效、工具调用失败），无法对原有计划进行调整，易导致 “一步错、步步错”的问题。 Plan-and-Execute 框架：采用 “规划 - 执行 - 反馈 - 调整” 的闭环逻辑。与 ReWOO的关键区别，是新增了 Replan（重规划）机制，弥补了 ReWOO “计划生成后缺乏动态调整机制” 的局限。流程上，规划器先借助 LLM 完成任务全局拆解，生成完整分步计划 ；执行器按计划调用工具执行任务；重规划器根据执行反馈判断是否调整计划，若需调整则重新规划，确保任务朝着目标推进。由于可根据实时反馈优化计划，更适配复杂多变的任务场景。 6. 代码实现\r6.1 架构设计\rPlan-and-Solve 的架构由以下几个核心组件构成：\n规划器 (Planner)：负责将复杂任务拆解为可落地的多步骤执行计划，为后续行动明确路径。通常由具备强推理能力的大语言模型（LLM）承担。 执行器 (Executor)：负责执行规划器生成的具体计划任务。执行器可以是另一个大语言模型，也可以是一个专门设计的 Agent，如 ReAct 模式的Agent，通过 “思考 - 行动 - 观察” 的循环，自主思考、决策下一步的行动、调用工具（如调用API，查询数据库、代码执行器）来完成计划任务。 重新规划器（replanner）：接收执行器反馈的子任务结果，通过 LLM 评估 “实际结果是否符合计划预期”，若出现偏差（如数据缺失、工具调用失败、新信息出现），则触发重新规划，并更新计划列表。 状态管理(State)：扮演 “记忆中枢”，负责跟踪任务的当前状态，包括当前计划、已执行的步骤及其结果、当前的响应等。状态管理器确保 Agent 在执行过程中能够保持上下文感知能力。 6.2 编写代码\r6.2.1 前置准备\r环境要求：\nPython 3.11+ 安装依赖库：pip install openai requests python-dotenv langchain_community langgraph langchain_openai langchain-tavily 基础配置：\n设置模型调用API Key\n创建.env文件，配置模型 API访问密钥，这里使用千问模型，需配置QWEN_API_KEY及QWEN_BASE_URL。\n1 2 3 4 5 # 千问模型接口访问key # 如何获取API Key：https://help.aliyun.com/zh/model-studio/developer-reference/get-api-key QWEN_API_KEY=\u0026#34;sk-*******\u0026#34; # 千问模型接口访问地址 QWEN_BASE_URL=\u0026#34;https://dashscope.aliyuncs.com/compatible-mode/v1\u0026#34; 搜索引擎 API 密钥（这里使用tavily）\n.env文件中配置搜索引擎tavily API 密钥，Tavily国内需科学上网才能访问：\n1 2 3 # tavily 搜索API KEY # 访问 https://tavily.com 注册并获取API Key,用户每月有1000次免费调用额度. TAVILY_API_KEY=\u0026#34;tvly-ZuSNSW4CehsNizV****\u0026#34; 6.2.2 引入依赖包\r引入相关依赖，读取.env文件中配置的参数转化为环境变量。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 import os import operator from typing import Annotated, List, Tuple, Union # 导入LangChain相关组件：输出解析器、图结构、类型定义等 from langchain_core.output_parsers import PydanticOutputParser from langgraph.graph.graph import CompiledGraph from langgraph.graph.state import CompiledStateGraph from typing_extensions import TypedDict # 环境变量加载、语言模型接口、提示模板等 from dotenv import load_dotenv from langchain_core.language_models import BaseChatModel from langchain_core.prompts import ChatPromptTemplate from langchain_openai import ChatOpenAI from langgraph.constants import END from langgraph.graph import StateGraph, START from langgraph.prebuilt import create_react_agent from pydantic import BaseModel, Field # 导入搜索工具 from tools.web_search.langchain_tavily_search import TavilySearch load_dotenv() 6.2.3 配置与常量（Config 类）\r该类集中管理所有静态配置，便于维护。\n1 2 3 4 5 6 class Config: DEFAULT_MODEL = \u0026#34;qwen-plus-2025-04-28\u0026#34; # 推荐使用千问Plus模型（效果优于Turbo） QWEN_BASE_URL = \u0026#34;https://dashscope.aliyuncs.com/compatible-mode/v1\u0026#34; # 千问API兼容地址 DEFAULT_TEMPERATURE = 0 # 温度=0,工具调用/规划场景需确定性输出，故设为0;创意场景可设 0.7-1.0 TAVILY_MAX_RESULTS = 2 # 搜索工具返回的最大结果数（减少冗余信息） RECURSION_LIMIT = 10 # 递归限制（防止工作流无限循环，如计划永远执行不完） 6.2.4 定义状态\rLangGraph 工作流的核心是状态（State）在节点间的流转，而 state 则定义了节点间传递的数据结构，主要包含三部分：\n当前计划列表：用字符串列表记录待执行的步骤； 已执行步骤记录：用元组列表存储（包含步骤内容 + 执行结果）； 基础信息：包括最终响应和原始输入数据。 1 2 3 4 5 6 class PlanExecute(TypedDict): input: str # 用户原始查询（如“2024澳网男单冠军家乡”） plan: List[str] # 待执行的步骤列表（如[\u0026#34;查冠军是谁\u0026#34;, \u0026#34;查冠军家乡\u0026#34;]） # 已执行步骤记录：用Annotated+operator.add实现“累加”（而非覆盖） past_steps: Annotated[List[Tuple], operator.add] response: str # 最终返回给用户的答案 6.2.5 LLM 与工具初始化（核心能力封装）\r该部分负责定义智能体的 “大脑”（LLM）和 “手脚”（工具），是智能体具备 “思考” 和 “行动” 能力的基础。\n（1）initialize_llm：初始化语言模型\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 def initialize_llm(model: str = Config.DEFAULT_MODEL, temperature: float = Config.DEFAULT_TEMPERATURE) -\u0026gt; BaseChatModel: api_key = os.getenv(\u0026#34;QWEN_API_KEY\u0026#34;) # 从.env文件获取千问API密钥 if not api_key: raise ValueError(\u0026#34;缺少环境变量QWEN_API_KEY\u0026#34;) # 异常处理：防止密钥缺失 base_url = os.getenv(\u0026#34;QWEN_BASE_URL\u0026#34;) or Config.QWEN_BASE_URL # 优先用环境变量的API地址 # 用ChatOpenAI调用千问（千问兼容OpenAI格式） return ChatOpenAI( model=model, api_key=api_key, openai_api_base=base_url, temperature=temperature ) 环境变量管理：通过os.getenv从.env文件读取密钥，避免硬编码（安全最佳实践）。 这里使用 LangChain 的ChatOpenAI类来访问千问模型，可以替换为兼容 OpenAI 接口格式的其它模型。替换为其它模型，需要修改这里的api_key（模型API访问密钥）、base_url（模型API地址）、model（模型名称）参数。 （2）initialize_tools：初始化工具\n1 2 3 4 def initialize_tools() -\u0026gt; List: # 初始化Tavily搜索工具（设置最大结果数） search = TavilySearch(max_results=Config.TAVILY_MAX_RESULTS) return [search] 这里默认只添加了网络搜索工具Tavily，可根据业务场景添加其它的工具。\n6.2.6 定义规划器节点\r规划器节点根据用户输入，生成完成任务的计划步骤列表。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 class Plan(BaseModel): \u0026#34;\u0026#34;\u0026#34;未来要遵循的计划步骤\u0026#34;\u0026#34;\u0026#34; steps: List[str] = Field(description=\u0026#34;要遵循的不同步骤，应按顺序排列\u0026#34;) @staticmethod async def plan_step(state: PlanExecute): \u0026#34;\u0026#34;\u0026#34;规划步骤节点：生成初始计划 根据用户输入生成完成任务所需的步骤计划 参数: state: 当前工作流状态，包含用户输入等信息 返回: 包含生成的计划步骤的字典 \u0026#34;\u0026#34;\u0026#34; # 创建解析器生成格式说明 parser = PydanticOutputParser(pydantic_object=Plan) # 生成初始计划的提示模板，指导模型为目标生成完整的分步计划，确保计划能够直接导向正确答案。 planner_prompt = ChatPromptTemplate.from_messages([ ( \u0026#34;system\u0026#34;, \u0026#34;\u0026#34;\u0026#34;该计划应包含单个任务，若执行正确，将得出正确答案。不要添加任何多余步骤。\\ 最后一步的结果应为最终答案。确保每个步骤都包含所需的所有信息——不要跳过步骤。\\n 必须严格按照以下格式要求返回数据，不要添加任何额外内容：\\n {format_instructions}\\n\\n 请确保输出完全符合上述格式，字段类型和约束要严格遵守。 \u0026#34;\u0026#34;\u0026#34;, # 添加了JSON格式要求 ), (\u0026#34;placeholder\u0026#34;, \u0026#34;{messages}\u0026#34;), ]) # 创建规划器：提示模板 + LLM（带结构化输出） planner = planner_prompt | initialize_llm().with_structured_output(Plan) # 调用规划器生成计划 result = await planner.ainvoke({ \u0026#34;messages\u0026#34;: [(\u0026#34;user\u0026#34;, state[\u0026#34;input\u0026#34;])], \u0026#34;format_instructions\u0026#34;: parser.get_format_instructions() # 解析器生成格式说明 }) # 返回包含计划步骤，将被合并到工作流状态中 return {\u0026#34;plan\u0026#34;: result.steps} PydanticOutputParser.get_format_instructions()：生成 LLM 可理解的格式说明，指导LLM输出相应的响应格式（如 “输出应为 JSON，包含 steps 字段，类型为列表”），确保 LLM 输出合规。 with_structured_output(Plan)：LangChain 的便捷方法，替代手动解析 LLM 输出（无需自己写 JSON 解析逻辑）。 6.2.7 定义执行器节点\r执行器节点从当前计划列表中，取出第一个任务进行执行，并记录执行结果。这里使用ReAct模式的智能体作为执行器。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 # 创建执行器，用来执行计划。 def _create_agent_executor() -\u0026gt; CompiledGraph: \u0026#34;\u0026#34;\u0026#34;创建执行器,使用React模式创建一个能够使用工具执行具体步骤的智能体。 \u0026#34;\u0026#34;\u0026#34; llm = initialize_llm() tools = initialize_tools() prompt = \u0026#34;你是一个专业的助手。\u0026#34; # 创建并返回React模式的智能体 return create_react_agent(llm, tools, prompt=prompt) @staticmethod async def execute_step(state: PlanExecute): \u0026#34;\u0026#34;\u0026#34;执行步骤节点：执行计划中的第一步 调用智能体执行器处理计划中的第一个步骤，并记录执行结果 参数: state: 当前工作流状态，包含计划步骤等信息 返回: 包含已执行步骤记录的字典 \u0026#34;\u0026#34;\u0026#34; # 1. 获取React智能体执行器 executor = _create_agent_executor() # 2. 处理计划：格式化步骤列表，提取第一步任务 plan = state[\u0026#34;plan\u0026#34;] plan_str = \u0026#34;\\n\u0026#34;.join(f\u0026#34;{i + 1}. {step}\u0026#34; for i, step in enumerate(plan)) task = plan[0] # 执行计划的第一个步骤（避免多步骤并行，简化逻辑） # 3. 格式化任务描述 task_formatted = f\u0026#34;\u0026#34;\u0026#34;对于以下计划： {plan_str} 你的任务是执行第1步：{task}。\u0026#34;\u0026#34;\u0026#34; # 4. 调用执行器执行任务（debug=True：输出调试信息，正式环境需关闭） agent_response = executor.invoke( {\u0026#34;messages\u0026#34;: [(\u0026#34;user\u0026#34;, task_formatted)]}, debug=True ) # 5. 记录已执行步骤（(任务内容, 执行结果)），返回给LangGraph合并状态 return { \u0026#34;past_steps\u0026#34;: [(task, agent_response[\u0026#34;messages\u0026#34;][-1].content)], } agent_response[\u0026quot;messages\u0026quot;][-1].content：React 智能体的输出存储在messages列表中，最后一条消息是最终执行结果。 单步执行：每次只执行计划的第一步，后续通过 “重新规划” 决定下一步，避免步骤混乱。 6.2.8 定义重新规划节点\r重新规划节点根据 “已执行步骤结果”，通过 LLM 评估下一步的操作：\n直接返回答案：如果已经做完的步骤，已足够信息能得出用户要的结果，则直接生成响应答案给用户。 继续执行新计划：如果还未完成，则重新生成计划，并更新state中的计划列表。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 class Response(BaseModel): \u0026#34;\u0026#34;\u0026#34;用户响应数据模型\u0026#34;\u0026#34;\u0026#34; response: str class Act(BaseModel): \u0026#34;\u0026#34;\u0026#34;要执行的动作,用于在重新规划阶段决定下一步动作：是直接响应还是继续执行计划\u0026#34;\u0026#34;\u0026#34; action: Union[Response, Plan] = Field( description=\u0026#34;要执行的动作。若要响应用户，使用Response。若需要进一步使用工具，使用Plan。\u0026#34; ) @staticmethod async def replan_step(state: PlanExecute): # 1. 定义重新规划的提示模板 replanner_prompt = ChatPromptTemplate.from_template(\u0026#34;\u0026#34;\u0026#34;针对给定目标，制定一个简单的分步计划。 该计划应包含单个任务，若执行正确，将得出正确答案。不要添加任何多余步骤。 最后一步的结果应为最终答案。确保每个步骤都包含所需的所有信息——不要跳过步骤。 你的目标是： {input} 你最初的计划是： {plan} 你目前已完成以下步骤： {past_steps} 相应地更新你的计划。如果无需更多步骤且可以回复用户，则进行回复。否则，填写计划。仅在计划中添加仍需完成的步骤。不要将已完成的步骤作为计划的一部分返回。 必须严格按照以下格式要求返回数据，不要添加任何额外内容：\\n {format_instructions}\\n\\n 请确保输出完全符合上述格式，字段类型和约束要严格遵守。 \u0026#34;\u0026#34;\u0026#34;) # 添加了JSON格式要求 # 2. 创建Act模型的解析器（判断动作类型：Response/Plan） parser = PydanticOutputParser(pydantic_object=Act) # 3. 创建重新规划器：提示模板 + LLM（带结构化输出） replanner = replanner_prompt | initialize_llm().with_structured_output(Act) # 4. 调用重新规划器生成新的动作指令 output = await replanner.ainvoke({ \u0026#34;input\u0026#34;: state[\u0026#34;input\u0026#34;], \u0026#34;plan\u0026#34;: state[\u0026#34;plan\u0026#34;], \u0026#34;past_steps\u0026#34;: state[\u0026#34;past_steps\u0026#34;], \u0026#34;format_instructions\u0026#34;: parser.get_format_instructions() }) # 5. 根据输出的动作类型返回不同的结果 if isinstance(output.action, Response): return {\u0026#34;response\u0026#34;: output.action.response} # 有答案了，返回最终响应 else: return {\u0026#34;plan\u0026#34;: output.action.steps} # 还需继续，返回新计划 6.2.9 定义路由节点\r判断工作流是否结束，是 “继续执行” 还是 “终止”。\n1 2 3 4 @staticmethod def should_end(state: PlanExecute) -\u0026gt; str: # 若状态中有response（最终答案），则返回END（结束）；否则返回\u0026#34;execute\u0026#34;（继续执行） return END if \u0026#34;response\u0026#34; in state and state[\u0026#34;response\u0026#34;] else \u0026#34;execute\u0026#34; 6.2.10 定义图（LangGraph 核心）\r图定义工作流的 “节点连接关系”，即 “从哪里来，到哪里去”。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 def build_plan_and_execute_workflow() -\u0026gt; CompiledStateGraph: # 1. 初始化状态图（指定状态类型为PlanExecute） workflow = StateGraph(PlanExecute) # 2. 添加节点（将Nodes类的静态方法与节点名绑定） workflow.add_node(\u0026#34;planner\u0026#34;, Nodes.plan_step) # 规划节点 workflow.add_node(\u0026#34;execute\u0026#34;, Nodes.execute_step) # 执行节点 workflow.add_node(\u0026#34;replan\u0026#34;, Nodes.replan_step) # 重新规划节点 # 3. 定义“无条件边”（固定流向） workflow.add_edge(START, \u0026#34;planner\u0026#34;) # 起始点 → 规划节点（第一步先规划） workflow.add_edge(\u0026#34;planner\u0026#34;, \u0026#34;execute\u0026#34;) # 规划后 → 执行节点（执行第一步） workflow.add_edge(\u0026#34;execute\u0026#34;, \u0026#34;replan\u0026#34;) # 执行后 → 重新规划节点（判断下一步） # 4. 定义“条件边”（根据重新规划结果决定流向） workflow.add_conditional_edges( \u0026#34;replan\u0026#34;, # 源节点：从“重新规划”节点出发 Nodes.should_end, # 路由函数：判断流向 [\u0026#34;execute\u0026#34;, END] # 可能的目标节点：继续执行 或 结束 ) # 5. 编译工作流（生成可执行的状态图） return workflow.compile() 6.2.11 主程序（运行入口）\rmain函数是代码的执行入口，负责初始化环境、构建工作流。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 async def main(): # 1. 加载环境变量（.env文件） load_environment() # 2. 构建工作流 app = build_plan_and_execute_workflow() # 3. （可选）保存工作流图片（需科学上网） # save_grath_image(app) # 4. 测试查询（用户输入） inputs = {\u0026#34;input\u0026#34;: \u0026#34;2024年澳大利亚网球公开赛男子单打冠军的家乡是哪里？\u0026#34;} # 5. 设置递归限制（防止无限循环） config = {\u0026#34;recursion_limit\u0026#34;: Config.RECURSION_LIMIT} # 6. 流式运行工作流（实时输出每一步结果） async for event in app.astream(inputs, config=config): for k, v in event.items(): if k != \u0026#34;__end__\u0026#34;: # 过滤掉“结束”标记，只输出有效结果 print(v) # 运行主程序（异步函数需用asyncio.run启动） if __name__ == \u0026#34;__main__\u0026#34;: import asyncio asyncio.run(main()) 6.2.12 完整代码\r完整代码位于项目根目录下：cognitive_pattern/plan_and_solve/plan_and_execute.py\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 import os import operator from typing import Annotated, List, Tuple, Union # 导入LangChain相关组件：输出解析器、图结构、类型定义等 from langchain_core.output_parsers import PydanticOutputParser from langgraph.graph.graph import CompiledGraph from langgraph.graph.state import CompiledStateGraph from typing_extensions import TypedDict # 环境变量加载、语言模型接口、提示模板等 from dotenv import load_dotenv from langchain_core.language_models import BaseChatModel from langchain_core.prompts import ChatPromptTemplate from langchain_openai import ChatOpenAI from langgraph.constants import END from langgraph.graph import StateGraph, START from langgraph.prebuilt import create_react_agent from pydantic import BaseModel, Field # 导入搜索工具 from tools.web_search.langchain_tavily_search import TavilySearch load_dotenv() # ---------------------------- # 配置与常量 # ---------------------------- class Config: \u0026#34;\u0026#34;\u0026#34;应用配置常量\u0026#34;\u0026#34;\u0026#34; DEFAULT_MODEL = \u0026#34;qwen-plus-2025-04-28\u0026#34; # 推荐\u0026#34;qwen-plus\u0026#34; 使用qwen-turbo系列效果差 DEFAULT_TEMPERATURE = 0 # 默认温度参数（0表示确定性输出） TAVILY_MAX_RESULTS = 2 # 搜索工具返回的最大结果数 QWEN_BASE_URL = \u0026#34;https://dashscope.aliyuncs.com/compatible-mode/v1\u0026#34; # 千问API基础地址 RECURSION_LIMIT = 10 # 递归限制，防止无限循环 # ---------------------------- # 状态与数据模型 # ---------------------------- class PlanExecute(TypedDict): \u0026#34;\u0026#34;\u0026#34;智能体运行状态数据结构,用于在工作流的不同节点之间传递状态信息\u0026#34;\u0026#34;\u0026#34; input: str # 用户输入的原始查询 plan: List[str] # 待执行的计划步骤列表 # 已执行步骤记录，使用Annotated和operator.add实现状态累加 past_steps: Annotated[List[Tuple], operator.add] response: str # 最终要返回给用户的响应 class Plan(BaseModel): \u0026#34;\u0026#34;\u0026#34;未来要遵循的计划步骤\u0026#34;\u0026#34;\u0026#34; steps: List[str] = Field(description=\u0026#34;要遵循的不同步骤，应按顺序排列\u0026#34;) class Response(BaseModel): \u0026#34;\u0026#34;\u0026#34;用户响应数据模型\u0026#34;\u0026#34;\u0026#34; response: str class Act(BaseModel): \u0026#34;\u0026#34;\u0026#34;要执行的动作,用于在重新规划阶段决定下一步动作：是直接响应还是继续执行计划\u0026#34;\u0026#34;\u0026#34; action: Union[Response, Plan] = Field( description=\u0026#34;要执行的动作。若要响应用户，使用Response。若需要进一步使用工具，使用Plan。\u0026#34; ) # ---------------------------- # LLM初始化 # ---------------------------- def load_environment(): \u0026#34;\u0026#34;\u0026#34;加载环境变量,从.env文件中加载API密钥等敏感信息，避免硬编码\u0026#34;\u0026#34;\u0026#34; load_dotenv() def initialize_llm( model: str = Config.DEFAULT_MODEL, temperature: float = Config.DEFAULT_TEMPERATURE ) -\u0026gt; BaseChatModel: \u0026#34;\u0026#34;\u0026#34;初始化语言模型(LLM) 根据配置参数创建并返回一个LLM实例，优先使用环境变量中的配置 参数: model: 模型名称 temperature: 控制输出随机性的参数，0表示最确定，1表示最随机 返回: 初始化好的BaseChatModel实例 异常: ValueError: 当缺少必要的API密钥时抛出 \u0026#34;\u0026#34;\u0026#34; api_key = os.getenv(\u0026#34;QWEN_API_KEY\u0026#34;) if not api_key: raise ValueError(\u0026#34;缺少环境变量QWEN_API_KEY\u0026#34;) base_url = os.getenv(\u0026#34;QWEN_BASE_URL\u0026#34;) or Config.QWEN_BASE_URL return ChatOpenAI( model=model, api_key=api_key, openai_api_base=base_url, temperature=temperature ) def initialize_tools() -\u0026gt; List: \u0026#34;\u0026#34;\u0026#34;初始化工具列表 创建并返回智能体可以使用的工具集合，目前包含Tavily搜索工具 \u0026#34;\u0026#34;\u0026#34; # 初始化搜索工具，设置最大结果数 # 请求网络时，如果是同步则使用requests库发起网络请求，异步使用aiohttp发起网络请求。 # --同步调用的 requests 库会自动检测系统代理（如环境变量 HTTP_PROXY/HTTPS_PROXY、系统网络设置）， # --而异步 aiohttp 默认不会。需要自行设置系统代理，实现异步网络请求。 # 所以这里对langchain默认的TavilySearch做了修改添加自动获取系统代理并配置. search = TavilySearch(max_results=Config.TAVILY_MAX_RESULTS) return [search] # 创建执行器，用来执行计划。 def _create_agent_executor() -\u0026gt; CompiledGraph: \u0026#34;\u0026#34;\u0026#34;创建执行器,使用React模式创建一个能够使用工具执行具体步骤的智能体执行器 \u0026#34;\u0026#34;\u0026#34; llm = initialize_llm() tools = initialize_tools() prompt = \u0026#34;你是一个专业的助手。\u0026#34; # 创建并返回React模式的智能体 return create_react_agent(llm, tools, prompt=prompt) # ---------------------------- # 节点函数 # ---------------------------- class Nodes: \u0026#34;\u0026#34;\u0026#34;智能体图节点集合类 封装工作流中所有节点的处理函数，使代码结构更清晰 \u0026#34;\u0026#34;\u0026#34; @staticmethod async def plan_step(state: PlanExecute): \u0026#34;\u0026#34;\u0026#34;规划步骤节点：生成初始计划 根据用户输入生成完成任务所需的步骤计划 参数: state: 当前工作流状态，包含用户输入等信息 返回: 包含生成的计划步骤的字典 \u0026#34;\u0026#34;\u0026#34; # 创建解析器生成格式说明 parser = PydanticOutputParser(pydantic_object=Plan) # 生成初始计划的提示模板，指导模型为目标生成完整的分步计划，确保计划能够直接导向正确答案。 planner_prompt = ChatPromptTemplate.from_messages([ ( \u0026#34;system\u0026#34;, \u0026#34;\u0026#34;\u0026#34;该计划应包含单个任务，若执行正确，将得出正确答案。不要添加任何多余步骤。\\ 最后一步的结果应为最终答案。确保每个步骤都包含所需的所有信息——不要跳过步骤。\\n 必须严格按照以下格式要求返回数据，不要添加任何额外内容：\\n {format_instructions}\\n\\n 请确保输出完全符合上述格式，字段类型和约束要严格遵守。 \u0026#34;\u0026#34;\u0026#34;, # 添加了JSON格式要求 ), (\u0026#34;placeholder\u0026#34;, \u0026#34;{messages}\u0026#34;), ]) # 创建规划器：提示模板 + LLM（带结构化输出） planner = planner_prompt | initialize_llm().with_structured_output(Plan) # 调用规划器生成计划 result = await planner.ainvoke({ \u0026#34;messages\u0026#34;: [(\u0026#34;user\u0026#34;, state[\u0026#34;input\u0026#34;])], \u0026#34;format_instructions\u0026#34;: parser.get_format_instructions() # 解析器生成格式说明 }) # 返回包含计划步骤，将被合并到工作流状态中 return {\u0026#34;plan\u0026#34;: result.steps} @staticmethod async def execute_step(state: PlanExecute): \u0026#34;\u0026#34;\u0026#34;执行步骤节点：执行计划中的第一步 调用智能体执行器处理计划中的第一个步骤，并记录执行结果 参数: state: 当前工作流状态，包含计划步骤等信息 返回: 包含已执行步骤记录的字典 \u0026#34;\u0026#34;\u0026#34; # 1.获取React智能体执行器 executor = _create_agent_executor() # 2.处理计划：格式化步骤列表，提取第一步任务 plan = state[\u0026#34;plan\u0026#34;] plan_str = \u0026#34;\\n\u0026#34;.join(f\u0026#34;{i + 1}. {step}\u0026#34; for i, step in enumerate(plan)) task = plan[0] # 取出计划中的第一个步骤 # 3.格式化任务描述，明确告知执行器要完成的工作 task_formatted = f\u0026#34;\u0026#34;\u0026#34;对于以下计划： {plan_str} 你的任务是执行第1步：{task}。\u0026#34;\u0026#34;\u0026#34; # 4.调用执行器执行任务 agent_response = executor.invoke( {\u0026#34;messages\u0026#34;: [(\u0026#34;user\u0026#34;, task_formatted)]} , debug=True # 开启调试，正式应用需关闭 ) # 5.记录已执行步骤（(任务内容, 执行结果)），返回给LangGraph合并状态 return { \u0026#34;past_steps\u0026#34;: [(task, agent_response[\u0026#34;messages\u0026#34;][-1].content)], } @staticmethod async def replan_step(state: PlanExecute): \u0026#34;\u0026#34;\u0026#34;重新规划步骤节点：根据执行结果更新计划 分析已执行步骤的结果，决定是继续执行新的步骤还是直接响应用户 参数: state: 当前工作流状态，包含原始计划和已执行步骤 返回: 包含新计划或最终响应的字典 \u0026#34;\u0026#34;\u0026#34; # 1. 定义重新规划的提示模板 replanner_prompt = ChatPromptTemplate.from_template(\u0026#34;\u0026#34;\u0026#34;针对给定目标，制定一个简单的分步计划。 该计划应包含单个任务，若执行正确，将得出正确答案。不要添加任何多余步骤。 最后一步的结果应为最终答案。确保每个步骤都包含所需的所有信息——不要跳过步骤。 你的目标是： {input} 你最初的计划是： {plan} 你目前已完成以下步骤： {past_steps} 相应地更新你的计划。如果无需更多步骤且可以回复用户，则进行回复。否则，填写计划。仅在计划中添加仍需完成的步骤。不要将已完成的步骤作为计划的一部分返回。 必须严格按照以下格式要求返回数据，不要添加任何额外内容：\\n {format_instructions}\\n\\n 请确保输出完全符合上述格式，字段类型和约束要严格遵守。 \u0026#34;\u0026#34;\u0026#34;) # 添加了JSON格式要求 # 2. 创建解析器,用于生成响应格式说明 parser = PydanticOutputParser(pydantic_object=Act) # 3. 创建重新规划器：提示模板 + LLM（带结构化输出） replanner = replanner_prompt | initialize_llm().with_structured_output(Act) # 4. 调用重新规划器生成新的动作指令 output = await replanner.ainvoke({ \u0026#34;input\u0026#34;: state.get(\u0026#34;input\u0026#34;), \u0026#34;plan\u0026#34;: state.get(\u0026#34;plan\u0026#34;), \u0026#34;past_steps\u0026#34;: state.get(\u0026#34;past_steps\u0026#34;), \u0026#34;format_instructions\u0026#34;: parser.get_format_instructions() # 解析器生成格式说明 }) # 5. 根据输出的动作类型返回不同的结果 if isinstance(output.action, Response): # 如果是响应动作，返回最终回答 return {\u0026#34;response\u0026#34;: output.action.response} else: # 如果是计划动作，返回更新后的计划 return {\u0026#34;plan\u0026#34;: output.action.steps} @staticmethod def should_end(state: PlanExecute) -\u0026gt; str: \u0026#34;\u0026#34;\u0026#34;路由节点：决定是否结束流程 根据当前状态判断工作流应继续执行还是结束 参数: state: 当前工作流状态 返回: 下一步节点名称（\u0026#34;execute\u0026#34;表示继续执行，END表示结束） \u0026#34;\u0026#34;\u0026#34; # 若状态中有response（最终答案），则返回END（结束）；否则返回\u0026#34;execute\u0026#34;（继续执行） return END if \u0026#34;response\u0026#34; in state and state[\u0026#34;response\u0026#34;] else \u0026#34;execute\u0026#34; # ---------------------------- # 图构建（工作流定义） # ---------------------------- def build_plan_and_execute_workflow() -\u0026gt; CompiledStateGraph: \u0026#34;\u0026#34;\u0026#34;构建plan-execute工作流图 定义工作流中的节点和节点之间的连接关系，构建完整的智能体工作流程 返回: 编译好的状态图(CompiledStateGraph) \u0026#34;\u0026#34;\u0026#34; workflow = StateGraph(PlanExecute) # 添加节点 workflow.add_node(\u0026#34;planner\u0026#34;, Nodes.plan_step) # 规划节点 workflow.add_node(\u0026#34;execute\u0026#34;, Nodes.execute_step) # 执行节点 workflow.add_node(\u0026#34;replan\u0026#34;, Nodes.replan_step) # 重新规划节点 # 定义节点间的连接关系 workflow.add_edge(START, \u0026#34;planner\u0026#34;) # 起始点 -\u0026gt; 规划节点 workflow.add_edge(\u0026#34;planner\u0026#34;, \u0026#34;execute\u0026#34;) # 规划节点 -\u0026gt; 执行节点 workflow.add_edge(\u0026#34;execute\u0026#34;, \u0026#34;replan\u0026#34;) # 执行节点 -\u0026gt; 重新规划节点 # 添加条件边：根据重新规划的结果决定下一步 workflow.add_conditional_edges( \u0026#34;replan\u0026#34;, # 源节点 Nodes.should_end, # 路由判断函数 [\u0026#34;execute\u0026#34;, END], # 可能的目标节点 ) # 编译并返回工作流图 return workflow.compile() def save_grath_image(app: CompiledStateGraph): \u0026#34;\u0026#34;\u0026#34;保存工作流图为图片 生成并保存工作流的可视化图表，默认使用mermaid api 生成图片，需要科学上网。 参数: app: 编译好的状态图实例 \u0026#34;\u0026#34;\u0026#34; from PIL import Image import io # 获取图像二进制数据（使用mermaid API生成） image_bytes = app.get_graph(xray=True).draw_mermaid_png() image_stream = io.BytesIO(image_bytes) # 保存为本地文件 with Image.open(image_stream) as img: img.save(\u0026#34;plan_and_execute.png\u0026#34;) # 保存为PNG格式 # ---------------------------- # 主程序 # ---------------------------- async def main(): \u0026#34;\u0026#34;\u0026#34;运行计划-执行智能体示例\u0026#34;\u0026#34;\u0026#34; # 1. 初始化环境变量 load_environment() # 2. 构建工作流 app = build_plan_and_execute_workflow() # 3. （可选）保存工作流图片（需科学上网） # 默认使用mermaid api 生成图片，需要科学上网,才能执行成功 # save_grath_image(app) # 4. 测试查询（用户输入） inputs = {\u0026#34;input\u0026#34;: \u0026#34;2024年澳大利亚网球公开赛男子单打冠军的家乡是哪里？\u0026#34;} # 5. 设置递归限制（防止无限循环） config = {\u0026#34;recursion_limit\u0026#34;: Config.RECURSION_LIMIT} # 6. 流式运行工作流（实时输出每一步结果） async for event in app.astream(inputs, config=config): for k, v in event.items(): if k != \u0026#34;__end__\u0026#34;: # 过滤掉结束标记 print(v) if __name__ == \u0026#34;__main__\u0026#34;: import asyncio # 运行主程序 asyncio.run(main()) 6.3 运行测试\r执行以下命令，由于使用tavily搜索引擎，需要配置科学上网哦：\n1 python plan_and_execute.py 或者在IDE如pycharm中右键运行。\n执行结果如下：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 D:\\program\\anaconda3\\envs\\langchain_311_env\\python.exe D:\\dev\\guwei\\tiny-agent-lab\\agent-practice\\cognitive_architecture\\plan_and_execute\\plan_and_execute.py {\u0026#39;plan\u0026#39;: [\u0026#39;确定2024年澳大利亚网球公开赛男子单打冠军是谁。\u0026#39;, \u0026#39;查找该冠军选手的家乡所在地。\u0026#39;, \u0026#39;返回该地点信息作为最终答案。\u0026#39;]} [-1:checkpoint] State at the end of step -1: {\u0026#39;messages\u0026#39;: []} [0:tasks] Starting 1 task for step 0: - __start__ -\u0026gt; {\u0026#39;messages\u0026#39;: [(\u0026#39;user\u0026#39;, \u0026#39;对于以下计划：\\n\u0026#39; \u0026#39; 1. 确定2024年澳大利亚网球公开赛男子单打冠军是谁。\\n\u0026#39; \u0026#39;2. 查找该冠军选手的家乡所在地。\\n\u0026#39; \u0026#39;3. 返回该地点信息作为最终答案。\\n\u0026#39; \u0026#39;\\n\u0026#39; \u0026#39;你的任务是执行第1步：确定2024年澳大利亚网球公开赛男子单打冠军是谁。。\u0026#39;)]} [0:writes] Finished step 0 with writes to 1 channel: - messages -\u0026gt; [(\u0026#39;user\u0026#39;, \u0026#39;对于以下计划：\\n\u0026#39; \u0026#39; 1. 确定2024年澳大利亚网球公开赛男子单打冠军是谁。\\n\u0026#39; \u0026#39;2. 查找该冠军选手的家乡所在地。\\n\u0026#39; \u0026#39;3. 返回该地点信息作为最终答案。\\n\u0026#39; \u0026#39;\\n\u0026#39; \u0026#39;你的任务是执行第1步：确定2024年澳大利亚网球公开赛男子单打冠军是谁。。\u0026#39;)] [0:checkpoint] State at the end of step 0: {\u0026#39;messages\u0026#39;: [HumanMessage(content=\u0026#39;对于以下计划：\\n 1. 确定2024年澳大利亚网球公开赛男子单打冠军是谁。\\n2. 查找该冠军选手的家乡所在地。\\n3. 返回该地点信息作为最终答案。\\n\\n你的任务是执行第1步：确定2024年澳大利亚网球公开赛男子单打冠军是谁。。\u0026#39;, additional_kwargs={}, response_metadata={}, id=\u0026#39;961a5990-e723-490e-8a29-45e1ddb66451\u0026#39;)]} [1:tasks] Starting 1 task for step 1: - agent -\u0026gt; {\u0026#39;is_last_step\u0026#39;: False, \u0026#39;messages\u0026#39;: [HumanMessage(content=\u0026#39;对于以下计划：\\n 1. 确定2024年澳大利亚网球公开赛男子单打冠军是谁。\\n2. 查找该冠军选手的家乡所在地。\\n3. 返回该地点信息作为最终答案。\\n\\n你的任务是执行第1步：确定2024年澳大利亚网球公开赛男子单打冠军是谁。。\u0026#39;, additional_kwargs={}, response_metadata={}, id=\u0026#39;961a5990-e723-490e-8a29-45e1ddb66451\u0026#39;)], \u0026#39;remaining_steps\u0026#39;: 9} ......省略 [3:checkpoint] State at the end of step 3: {\u0026#39;messages\u0026#39;: [HumanMessage(content=\u0026#39;对于以下计划：\\n 1. 查找扬尼克·辛纳（Jannik Sinner）的家乡所在地。\\n2. 返回该地点信息作为最终答案。\\n\\n你的任务是执行第1步：查找扬尼克·辛纳（Jannik Sinner）的家乡所在地。。\u0026#39;, additional_kwargs={}, response_metadata={}, id=\u0026#39;a80a2c97-c82c-4852-8767-487e18107b77\u0026#39;), AIMessage(content=\u0026#39;\u0026#39;, additional_kwargs={\u0026#39;tool_calls\u0026#39;: [{\u0026#39;id\u0026#39;: \u0026#39;call_3aff83ab43c94e5d828682\u0026#39;, \u0026#39;function\u0026#39;: {\u0026#39;arguments\u0026#39;: \u0026#39;{\u0026#34;query\u0026#34;: \u0026#34;Jannik Sinner hometown\u0026#34;}\u0026#39;, \u0026#39;name\u0026#39;: \u0026#39;tavily_search\u0026#39;}, \u0026#39;type\u0026#39;: \u0026#39;function\u0026#39;, \u0026#39;index\u0026#39;: 0}], \u0026#39;refusal\u0026#39;: None}, response_metadata={\u0026#39;token_usage\u0026#39;: {\u0026#39;completion_tokens\u0026#39;: 26, \u0026#39;prompt_tokens\u0026#39;: 1175, \u0026#39;total_tokens\u0026#39;: 1201, \u0026#39;completion_tokens_details\u0026#39;: None, \u0026#39;prompt_tokens_details\u0026#39;: None}, \u0026#39;model_name\u0026#39;: \u0026#39;qwen-plus-2025-04-28\u0026#39;, \u0026#39;system_fingerprint\u0026#39;: None, \u0026#39;id\u0026#39;: \u0026#39;chatcmpl-a398dbdc-edbf-9357-9281-6f69e4fe7b25\u0026#39;, \u0026#39;service_tier\u0026#39;: None, \u0026#39;finish_reason\u0026#39;: \u0026#39;tool_calls\u0026#39;, \u0026#39;logprobs\u0026#39;: None}, id=\u0026#39;run--85564be0-28ac-4d7f-a700-ee6050307128-0\u0026#39;, tool_calls=[{\u0026#39;name\u0026#39;: \u0026#39;tavily_search\u0026#39;, \u0026#39;args\u0026#39;: {\u0026#39;query\u0026#39;: \u0026#39;Jannik Sinner hometown\u0026#39;}, \u0026#39;id\u0026#39;: \u0026#39;call_3aff83ab43c94e5d828682\u0026#39;, \u0026#39;type\u0026#39;: \u0026#39;tool_call\u0026#39;}], usage_metadata={\u0026#39;input_tokens\u0026#39;: 1175, \u0026#39;output_tokens\u0026#39;: 26, \u0026#39;total_tokens\u0026#39;: 1201, \u0026#39;input_token_details\u0026#39;: {}, \u0026#39;output_token_details\u0026#39;: {}}), ToolMessage(content=\u0026#39;{\u0026#34;query\u0026#34;: \u0026#34;Jannik Sinner hometown\u0026#34;, \u0026#34;follow_up_questions\u0026#34;: null, \u0026#34;answer\u0026#34;: null, \u0026#34;images\u0026#34;: [], \u0026#34;results\u0026#34;: [{\u0026#34;url\u0026#34;: \u0026#34;https://en.wikipedia.org/wiki/Jannik_Sinner\u0026#34;, \u0026#34;title\u0026#34;: \u0026#34;Jannik Sinner\u0026#34;, \u0026#34;content\u0026#34;: \u0026#34;Jannik Sinner was born 16 August 2001 to Hanspeter and Siglinde Sinner in ... He grew up in the town of Sexten in the Dolomites, the family hometown, where ...See more\u0026#34;, \u0026#34;score\u0026#34;: 0.98563, \u0026#34;raw_content\u0026#34;: null}, {\u0026#34;url\u0026#34;: \u0026#34;https://www.britannica.com/biography/Jannik-Sinner\u0026#34;, \u0026#34;title\u0026#34;: \u0026#34;Jannik Sinner | Height, Age, Wimbledon, Carlos Alcaraz, \u0026amp; ...\u0026#34;, \u0026#34;content\u0026#34;: \u0026#34;Jannik Sinner has won four Grand Slam titles: the Australian Open twice (2024, 2025), Wimbledon once (2025), and the U.S. Open once (2024). **Jannik Sinner** (born August 16, 2001, San Candido, Italy) is an Italian tennis player who is a leading star in the generation of players rising in the wake of the indomitable Big Three (Novak Djokovic, Roger Federer, and Rafael Nadal). In 2024 Sinner won his first majors, at the Australian Open and the U.S. Open, and attained the number one ranking on the men’s ATP (Association of Tennis Professionals) tour. Sinner won four ATP titles in 2021 and advanced to the round of 16 in both the French Open and the U.S. Open.\u0026#34;, \u0026#34;score\u0026#34;: 0.9835, \u0026#34;raw_content\u0026#34;: null}], \u0026#34;response_time\u0026#34;: 1.01, \u0026#34;request_id\u0026#34;: \u0026#34;1ded3904-8b59-43c8-b290-0f5393490872\u0026#34;}\u0026#39;, name=\u0026#39;tavily_search\u0026#39;, id=\u0026#39;3022a878-ae9c-4637-a84a-3fcbb984db66\u0026#39;, tool_call_id=\u0026#39;call_3aff83ab43c94e5d828682\u0026#39;), AIMessage(content=\u0026#39;扬尼克·辛纳（Jannik Sinner）的家乡是意大利的桑坎迪多（San Candido）。他于2001年8月16日出生在那里。此外，他成长于多洛米蒂山脉的塞斯顿（Sexten）镇，这是他家庭的故乡。\u0026#39;, additional_kwargs={\u0026#39;refusal\u0026#39;: None}, response_metadata={\u0026#39;token_usage\u0026#39;: {\u0026#39;completion_tokens\u0026#39;: 67, \u0026#39;prompt_tokens\u0026#39;: 1652, \u0026#39;total_tokens\u0026#39;: 1719, \u0026#39;completion_tokens_details\u0026#39;: None, \u0026#39;prompt_tokens_details\u0026#39;: None}, \u0026#39;model_name\u0026#39;: \u0026#39;qwen-plus-2025-04-28\u0026#39;, \u0026#39;system_fingerprint\u0026#39;: None, \u0026#39;id\u0026#39;: \u0026#39;chatcmpl-f6f9b966-f2db-9624-9c6d-3306ae217dc2\u0026#39;, \u0026#39;service_tier\u0026#39;: None, \u0026#39;finish_reason\u0026#39;: \u0026#39;stop\u0026#39;, \u0026#39;logprobs\u0026#39;: None}, id=\u0026#39;run--d7f867fe-7fb2-4c76-b727-65897708a403-0\u0026#39;, usage_metadata={\u0026#39;input_tokens\u0026#39;: 1652, \u0026#39;output_tokens\u0026#39;: 67, \u0026#39;total_tokens\u0026#39;: 1719, \u0026#39;input_token_details\u0026#39;: {}, \u0026#39;output_token_details\u0026#39;: {}})]} {\u0026#39;past_steps\u0026#39;: [(\u0026#39;查找扬尼克·辛纳（Jannik Sinner）的家乡所在地。\u0026#39;, \u0026#39;扬尼克·辛纳（Jannik Sinner）的家乡是意大利的桑坎迪多（San Candido）。他于2001年8月16日出生在那里。此外，他成长于多洛米蒂山脉的塞斯顿（Sexten）镇，这是他家庭的故乡。\u0026#39;)]} {\u0026#39;response\u0026#39;: \u0026#39;2024年澳大利亚网球公开赛男子单打冠军扬尼克·辛纳的家乡是意大利的桑坎迪多（San Candido）。\u0026#39;} Process finished with exit code 0 最后响应结果为：{'response': '2024年澳大利亚网球公开赛男子单打冠军扬尼克·辛纳的家乡是意大利的桑坎迪多（San Candido）。'}\n6.4 结论\r从设计角度看，Plan-and-Execute是对ReAct和ReWOO模式的进一步优化：\n相比ReAct，它的优势在于推理阶段就形成明确的长期规划，能指导执行路径，解决了ReAct缺乏对任务全局的统筹规划、复杂场景下易出现路径冗余或目标偏移的问题； 相比ReWOO，它新增eplanner（重规划器），实现了计划的动态调整机制，弥补了ReWOO一旦生成计划就无法灵活调整的缺陷 —— 避免了因某子任务执行结果偏离预期却无法修正，最终导致 “一步错、步步错” 的问题。 不过Plan-and-Execute存在一个明显局限：任务需按顺序执行，下一个任务必须等待上一个完成才能启动，总执行时间等同于所有任务耗时之和，效率较低。改进的方式是：可将任务的 “线性列表” 式结构，转化为有向无环图（DAG） 结构 —— 通过 DAG 明确任务间的依赖关系，让无依赖的任务实现并行执行，可大幅缩短总耗时。这种改进思路类似于LLM Compiler`设计模式的逻辑。\n完整源码地址：\nGitHub 仓库：https://github.com/tinyseeking/tidy-agent-practice/tree/main/cognitive_pattern/plan_and_solve Gitee 仓库（国内）：https://gitee.com/tinyseeking/tidy-agent-practice/tree/main/cognitive_pattern/plan_and_solve ","date":"2026-01-25T00:00:00Z","image":"/p/%E5%BC%80%E6%BA%90%E6%99%BA%E8%83%BD%E4%BD%93%E6%A1%86%E6%9E%B6plan-and-execute-%E5%8E%9F%E7%90%86%E6%9E%B6%E6%9E%84%E4%B8%8E%E4%BB%A3%E7%A0%81%E5%AE%9E%E7%8E%B0/images/image-20250826174630927.png","permalink":"/p/%E5%BC%80%E6%BA%90%E6%99%BA%E8%83%BD%E4%BD%93%E6%A1%86%E6%9E%B6plan-and-execute-%E5%8E%9F%E7%90%86%E6%9E%B6%E6%9E%84%E4%B8%8E%E4%BB%A3%E7%A0%81%E5%AE%9E%E7%8E%B0/","title":"开源智能体框架——Plan-and-execute 原理、架构与代码实现"},{"content":"Self-Ask 原理、架构与代码实现\r1 介绍\r​\t在自然语言处理领域，大语言模型（LLMs）已展现出强大能力。但当面对复杂的推理问题，特别是在处理需要将多个子问题的答案进行组合以得出最终解答的任务时，模型存在显著缺陷。即模型能正确回答了各个子问题，却无法整合这些信息以生成正确的答案。\n​\t例如，模型能够准确回答 “贾斯汀・比伯是哪年出生的？” 以及 “大师赛冠军通常在哪些年份产生？”，但当被问及 “谁是贾斯汀・比伯出生那一年的大师赛冠军？”这样的2跳组合问题时，却可能给出错误答案。\n​\t这一局限暴露了大模型的核心问题：擅长记忆事实，但不擅长基于事实进行推理。传统提示方法（如直接提问）无法引导模型 “主动思考”，导致复杂任务的解决依赖于训练数据中是否存在现成答案，而非真正的推理能力。\n​\t2023 年 10 月，来自华盛顿大学、OpenAI 等机构的研究人员 Ofir Press、Muru Zhang 等在 arXiv 上发表了名为《Measuring and Narrowing the Compositionality Gap in Language Models》的论文，论文中引入了组合性差距这一术语，来描述在所有 “由多个子问题组成、且模型能把每个子问题都答对” 的组合问题里，模型把整个组合问题回答错误的比例。并且提出了一种创新的方法 —**Self-Ask（自我询问）框架：**让语言模型将复杂问题分解为更简单的子问题，先回答子问题再回答主问题方式，显著的缩小了组合性差距，并且可以在回答子问题时，轻松接入搜索引擎的搜索结果，进一步提高准确性。\n​\t该框架是在思维链基础上进行改进，核心机制为：引导大语言模型在回答初始复杂问题前，显式地自我提问是否可以把问题改写/拆解成一个简单的子问题，并进行回答，回答时可以调用搜索工具来获得答案，然后根据工具返回结果，继续进行自我提问，直到模型认为无需继续提出子问题后，再整合之前的子问题及其答案，生成最终的答复。这是一种递归式的问题拆解与解答过程，能让模型更系统地剖析复杂问题，更深入地探索问题的各个方面，从而提高答案的质量和准确性 。这就如同人类面对复杂问题时（如做复杂数学题目），会将大问题拆分成多个小问题，逐个思考解决后再汇总答案。\n2 实验示例\r​\tSelf-Ask建立在思维链(CoT)的基础上，但不同于输出连续的、未划分的思维链，而是让模型在回答问题之前显示地提出它想要问的下一个后续问题。\n​\t具体步骤是：通过在提示词中加入 1-shot 或 few-shot 提示演示如何回答问题，接着附加推理时的问题。在提示的末尾插入短语 “Are follow up questions needed here：”，然后模型输出响应。在大多数情况下，模型首先输出 “是”，表示需要后续问题，并输出后续问题（Follow up）；接着模型回答它，并添加Intermediate answer：用于放置子问题的中间答案，继续重复提问和回答后续问题，直到模型认为自己有足够的信息；此时，输出 “所以最终答案是：”；这使得最终答案易于解析，即最后一行中 “：” 后面的内容。在极少数情况下，语言模型会认为不需要提出后续问题，可以立即回答问题，具体提出多少个后续问题是由模型来思考决策的。\n​\t图：直接提示与思维链和self-ask方法对比。白色背景的文本是提示，绿色背景的文本是语言模型的输出，带下划线的文本是推理时的问题。\n上图示例中，展示了三种方法：直接提示、思维链、self-ask在回答问题超导现象被发现时，美国的总统是谁？的对比。\n直接提示（模型直接输出答案）：\n1 2 3 4 5 问题：西奥多・海克尔和哈里・沃恩・沃特金斯谁活得更久？ 答案：哈里・沃恩・沃特金斯。 问题：超导现象被发现时，美国的总统是谁？ 答案：富兰克林・D・罗斯福 （错误） 思维链（模型先输出推理过程再给答案）：\n1 2 3 4 5 问题：西奥多・海克尔和哈里・沃恩・沃特金斯谁活得更久？ 答案：西奥多・海克尔去世时 65 岁。哈里・沃恩・沃特金斯去世时 69 岁。所以最终答案（人名）是：哈里・沃恩・沃特金斯。 问题：超导现象被发现时，美国的总统是谁？ 答案：超导现象于 1911 年由海克・卡末林・昂内斯发现。伍德罗・威尔逊在 1913 年至 1921 年期间担任美国总统。所以最终答案（总统名）是：伍德罗・威尔逊。（正确） self-ask（模型显式拆解问题为子问题，逐一解答后整合答案，含 “Follow up:”“Intermediate answer:” 等结构化支架）：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 问题：西奥多・海克尔和哈里・沃恩・沃特金斯谁活得更久？ 这里需要后续问题吗：是。 Follow up：西奥多・海克尔去世时多大年纪？ Intermediate answer：西奥多・海克尔去世时 65 岁。 Follow up：哈里・沃恩・沃特金斯去世时多大年纪？ Intermediate answer：哈里・沃恩・沃特金斯去世时 69 岁。 所以最终答案是：哈里・沃恩・沃特金斯 问题：超导现象被发现时，美国的总统是谁？(真正的用户问题) 这里需要后续问题吗：是。 Follow up：超导现象是什么时候被发现的？ Intermediate answer：超导现象于 1911 年被发现。 Follow up：1911 年美国的总统是谁？ Intermediate answer：威廉・霍华德・塔夫脱。 所以最终答案是：威廉・霍华德・塔夫脱。（正确） Self-ask 优于思维链的优势在于，它将完整问题的分解（通过提出子问题）与这些子问题的实际答案分离开来，这使得子问题的回答可以借助外部的工具，如搜索引擎。此外，Self-ask 提供的严格框架使模型更容易以简洁、可解析的方式陈述最终答案。在某些情况下，思维链不会输出简短形式的最终答案，而是倾向于使用完整句子。\n用搜索引擎工具改进\n​\t另外，self-ask 在回答子问题时，可以利用外部搜索工具，即在Follow-up后的子问题，用搜索引擎来代替LLM来回答子问题。该子问题被输入搜索引擎；获取搜索引擎的查询结果，再反馈给语言模型，语言模型生成另一个子问题，依此类推，直到输出最终答案。如下图所示：\n上图展示了 Self - Ask 结合搜索引擎解决问题的流程示例，逐步推理得出最终答案，具体步骤如下：\n初始问题与子问题拆解\n首先使用一个少样本提示词，并将初始问题附加到该提示词后，发送给模型（这里是使用的模型是GPT-3）。初始问题是 “In what year was the current tallest wooden lattice tower completed?”（目前最高的木制 lattice 塔是哪一年建成的？ ）。\n模型判断需要后续问题（“Are follow up questions needed here: Yes” ），并提出第一个子问题 “What is the current tallest wooden lattice tower?”（目前最高的木制 lattice 塔是什么？ ）\n结合搜索引擎获取子问题答案\n将子问题 “What is the current tallest wooden lattice tower?” 作为查询（“Query” ）发给搜索引擎，得到响应（“Response” ）“Radio Tower Gliwice” ，即确定最高木制 lattice 塔是 Gliwice 无线电塔。\n基于子问题答案继续推理\n记录中间答案（“Intermediate answer: Radio Tower Gliwice” ），并提出新子问题 “When was Gliwice Radio Tower completed?”（Gliwice 无线电塔是哪一年建成的？ ）\n再次结合搜索引擎与模型得出最终答案\n把新子问题作为查询发给搜索引擎，得到响应 “1935” 。记录中间答案（“Intermediate answer: 1935” ），整合信息后输出最终答案 “So the final answer is: 1935” ，即确定目标塔的建成年份是 1935 年。\n​\tSelf-Ask框架通过“拆解复杂问题生成子问题→借助外部工具（搜索引擎）获取子问题答案→模型整合信息推理” 的分步处理迭代循环，让模型更系统地解决多跳推理问题，也展示了大语言模型与外部工具协同工作的过程。\n同类技术对比\n维度 直接提示 思维链（CoT） ReAct self-ask 核心逻辑 直接输出答案，无中间步骤。 引导模型输出连续推理步骤（如 “一步步思考”），但步骤无结构化格式。 结合推理和行动，可调用外部工具获取信息。 显式拆分成子问题，并记录中间结果。 适用场景 单跳事实问答（如 “XXX 的生日是哪天？”） 简单推理任务（如数学题） 适用于需要外部知识或实时信息支持的任务，如查询天气、获取最新新闻等 多跳推理、需要结合外部工具的任务 工具整合 无 弱（步骤无明确边界，难对接工具） 强（可调用外部工具获取信息） 强（子问题可直接作为工具查询输入） 性能瓶颈- 依赖模型自身知识、超出自身知识的问题易出现幻觉。 在处理需要外部知识的问题时，由于自身知识局限，准确性可能不足。 对外部工具的依赖性较强，若工具出现问题，会影响整体结果。 对于不需要中间问题的简单问题，可能会增加推理步骤，降低效率。 3 核心思想\r​\tself-ask 的核心思想是模拟人类分步推理的认知过程：面对复杂问题时，先将其拆解为一系列可解决的子问题，逐一解答子问题后，再整合结果得到最终答案。\n具体来说，通过以下设计方法实现：\n显式子问题分解：用 “Follow up: [子问题]” 的提示词格式，引导模型先生成需要解决的小问题。 结构化中间输出：再通过模型生成小问题的答案，用 “Intermediate answer: [子问题答案]” 记录每步结果，确保推理过程可追踪。 动态终止机制：让模型自己决定是否需要更多子问题，依次迭代，直到信息足够，再用 “Final answer: [结果]” 输出答案。 深层来看，self-ask 其实是降低了推理的 “难度”。复杂问题的难点在于 “一次性记住所有步骤并组合”，而拆分成子问题后，模型每次只需解决一个简单问题，最后再汇总，自然不容易出错。\n4 工作流程\rself-ask 实现工作流程如下图所示：\n步骤说明如下：\n输入问题：用户输入需要解决的复杂问题（多跳推理问题）。比如 “弗里达・卡罗出生地的电话区号是什么？”。 模型判断是否需要子问题：构建提示词（ “Are follow up questions needed here:”）引导模型判断是否需分解问题，若模型认为当前信息已经足够回复用户问题，则直接生成答案，否则生成子问题。 解析子问题：解析模型生成的子问题：Follow up: 后面的信息（如 “Follow up: 子问题内容”），子问题需为可独立解答的简单问题。 回答子问题：模型自主回答子问题，或调用外部工具（如搜索引擎）获取答案，避免模型知识过时或不足。 记录中间结果：用 “Intermediate answer:” 存储子问题答案，作为后续推理的依据。 重复迭代：返回步骤 2，判断是否需要更多子问题，直至模型认为信息足够。 整合与输出：模型基于所有中间答案，推理得到最终结果，用 “Final answer:” 输出。 5. 应用场景\rself-ask 的适用场景如下：\n复杂事实性问答：这类场景的核心需求是准确回答涉及多步推理、需要多个事实信息支撑的问题。self-ask 框架通过拆解问题为中间问题，逐步获取事实信息，能够很好地满足这一需求，其逐步推理的方式与复杂事实性问答对信息准确性和完整性的要求相匹配。\n知识图谱查询辅助：在知识图谱查询中，有时需要明确多个实体之间的关系，核心需求是理清实体关联路径。self-ask 可以通过提出中间问题，逐步明确实体间的关系。\n多步骤问题解决：如解决数学应用题、逻辑推理题等，核心需求是清晰的步骤和准确的中间结果。self-ask 的中间问题拆解和逐步解答模式，能够满足这类场景对步骤和中间结果的要求。\n6. 代码实现\r6.1 前置准备\r环境要求：\nPython 3.11+ 安装依赖库：pip install openai requests python-dotenv TavilyClient 基础配置：\n设置API Key\n创建.env文件，配置模型 API访问密钥，这里使用千问模型，需配置QWEN_API_KEY及QWEN_BASE_URL。\n1 2 3 4 5 # 千问模型接口访问key # 如何获取API Key：https://help.aliyun.com/zh/model-studio/developer-reference/get-api-key QWEN_API_KEY=\u0026#34;sk-*******\u0026#34; # 千问模型接口访问地址 QWEN_BASE_URL=\u0026#34;https://dashscope.aliyuncs.com/compatible-mode/v1\u0026#34; 搜索引擎 API 密钥（这里使用tavily）\n.env文件中配置搜索引擎tavily API 密钥：\n1 2 3 # tavily 搜索API KEY # 访问 https://tavily.com 注册并获取API Key,用户每月有1000次免费调用额度. TAVILY_API_KEY=\u0026#34;tvly-ZuSNSW4CehsNizV****\u0026#34; 6.2 编写代码\r定义用于判断是否需要生成子问题的prompt（参考论文示例）：\n1 2 3 4 5 6 7 def _get_followup_prompt(self, query: str, history: str) -\u0026gt; str: \u0026#34;\u0026#34;\u0026#34;用于判断是否需要生成子问题的prompt\u0026#34;\u0026#34;\u0026#34; return f\u0026#34;\u0026#34;\u0026#34; 问题：{query} 已有的推理过程：{history} 接下来还需要问什么问题吗？请仅回答\u0026#34;Yes\u0026#34;或\u0026#34;No\u0026#34;。如果回答Yes，请同时给出具体的追问（格式：Follow up:[你的问题]）。 \u0026#34;\u0026#34;\u0026#34; 定义基于综合中间答案得到最终结果的prompt\n1 2 3 4 5 6 7 8 def _get_final_answer_prompt(self, query: str, history: str) -\u0026gt; str: \u0026#34;\u0026#34;\u0026#34;基于综合中间答案得到最终结果的prompt\u0026#34;\u0026#34;\u0026#34; return f\u0026#34;\u0026#34;\u0026#34; 请根据以下问题和推理过程，给出最终答案： 问题：{query} 推理过程：{history} 请直接给出答案，无需额外解释。 \u0026#34;\u0026#34;\u0026#34; 调用模型生成子问题或最终答案：\n1 2 3 4 5 6 7 8 9 10 11 12 def _call_llm(self, prompt: str) -\u0026gt; str: \u0026#34;\u0026#34;\u0026#34;调用LLM生成回答\u0026#34;\u0026#34;\u0026#34; try: response = self.client.chat.completions.create( model=self.llm_model, messages=[{\u0026#34;role\u0026#34;: \u0026#34;user\u0026#34;, \u0026#34;content\u0026#34;: prompt}], temperature=0 # 降低随机性，保证输出稳定 ) return response.choices[0].message.content except Exception as e: print(f\u0026#34;LLM调用失败: {e}\u0026#34;) return \u0026#34;\u0026#34; 对接搜索引擎工具获取子问题的答案：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 def _search(self, query: str) -\u0026gt; Optional[str]: \u0026#34;\u0026#34;\u0026#34;使用搜索答案\u0026#34;\u0026#34;\u0026#34; if not tavily_api_Key: print(\u0026#34;未配置tavily_api_Key，跳过搜索\u0026#34;) return None client = TavilyClient(api_key=tavily_api_Key) try: # 调用Tavily API执行搜索 response = client.search(query, max_results=2) # 提取搜索结果项 items = response.get(\u0026#34;results\u0026#34;, []) # 提取所有摘要并合并为字符串（用换行分隔） combined_summary = \u0026#34;\\n\u0026#34;.join([item[\u0026#34;content\u0026#34;] for item in items]) print(f\u0026#34;搜索结果：\\n{combined_summary}\u0026#34;) return combined_summary except Exception as e: print(f\u0026#34;搜索过程中发生错误: {e}\u0026#34;) return None 核心流程：run\n该方法实现 self-ask 的完整逻辑：拆解问题→生成子问题→求解子问题→整合答案，步骤如下：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 def run(self, query: str) -\u0026gt; str: \u0026#34;\u0026#34;\u0026#34;运行Self-Ask流程\u0026#34;\u0026#34;\u0026#34; self.history = [] current_history = \u0026#34;\u0026#34; step = 0 while step \u0026lt; self.max_steps: step += 1 # 1. 判断是否需要继续提问 followup_prompt = self._get_followup_prompt(query, current_history) followup_response = self._call_llm(followup_prompt) # 2. 解析是否需要子问题 if \u0026#34;No\u0026#34; in followup_response: # 无需更多子问题，生成最终答案 final_prompt = self._get_final_answer_prompt(query, current_history) final_answer = self._call_llm(final_prompt) return final_answer elif \u0026#34;Yes\u0026#34; in followup_response and \u0026#34;Follow up:\u0026#34; in followup_response: # 提取子问题 followup_question = followup_response.split(\u0026#34;Follow up:\u0026#34;)[-1].strip() self.history.append({\u0026#34;followup\u0026#34;: followup_question, \u0026#34;answer\u0026#34;: \u0026#34;\u0026#34;}) print(f\u0026#34;子问题 {step}: {followup_question}\u0026#34;) # 3. 搜索子问题答案（失败则用LLM回答） search_answer = self._search(followup_question) if search_answer: intermediate_answer = search_answer else: print(f\u0026#34;搜索无结果，使用LLM回答子问题：{followup_question}\u0026#34;) intermediate_answer = self._call_llm(followup_question) # 4. 记录中间答案，更新历史 self.history[-1][\u0026#34;answer\u0026#34;] = intermediate_answer current_history += f\u0026#34;\\nFollow up:{followup_question}\\nIntermediate answer: {intermediate_answer}\u0026#34; print(f\u0026#34;中间答案 {step}: {intermediate_answer}\u0026#34;) else: # LLM输出格式异常，直接用LLM回答原问题 print(\u0026#34;LLM输出格式异常，直接回答原问题\u0026#34;) return self._call_llm(query) # 超过最大步骤，强制生成最终答案 final_prompt = self._get_final_answer_prompt(query, current_history) return self._call_llm(final_prompt) 6.3 完整实现代码\r完整代码位于项目根目录下：cognitive_pattern/self_ask/self_ask.py\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 import os from typing import Optional, List, Dict from dotenv import load_dotenv from openai import OpenAI, Client from tavily import TavilyClient # 加载环境变量 load_dotenv() # tavily 搜索API KEY tavily_api_Key = os.getenv(\u0026#34;TAVILY_API_KEY\u0026#34;) def initialize_model_client() -\u0026gt; Client: \u0026#34;\u0026#34;\u0026#34; 获取模型调用客户端,使用openai SDK，支持所有兼容openai接口的模型服务，默认使用千问模型 Returns: OpenAI客户端实例 \u0026#34;\u0026#34;\u0026#34; # 获取千问API密钥 # 千问模型接口访问key # 如何获取API Key：https://help.aliyun.com/zh/model-studio/developer-reference/get-api-key qwen_api_key = os.getenv(\u0026#34;QWEN_API_KEY\u0026#34;) # 从环境变量获取 if not qwen_api_key: raise ValueError(f\u0026#34;缺少环境变量QWEN_API_KEY\u0026#34;) # 获取千问请求端口URL qwen_base_url = os.getenv(\u0026#34;QWEN_BASE_URL\u0026#34;) if not qwen_base_url: raise ValueError(f\u0026#34;缺少环境变量QWEN_BASE_URL\u0026#34;) client = OpenAI( api_key=qwen_api_key, base_url=qwen_base_url, ) return client class SelfAskAgent: def __init__(self, client: Client, llm_model: str = \u0026#34;qwen-plus-latest\u0026#34;, max_steps: int = 5): \u0026#34;\u0026#34;\u0026#34; 初始化Self-Ask代理 :param client: 模型调用客户端 :param llm_model: 用于生成子问题和答案的LLM模型,默认使用千问模式:qwen-plus-latest :param max_steps: 最大推理步骤（防止无限循环） \u0026#34;\u0026#34;\u0026#34; self.client = client self.llm_model = llm_model self.max_steps = max_steps self.history: List[Dict] = [] # 记录推理历史：子问题、中间答案 def _call_llm(self, prompt: str) -\u0026gt; str: \u0026#34;\u0026#34;\u0026#34;调用LLM生成回答\u0026#34;\u0026#34;\u0026#34; try: response = self.client.chat.completions.create( model=self.llm_model, messages=[{\u0026#34;role\u0026#34;: \u0026#34;user\u0026#34;, \u0026#34;content\u0026#34;: prompt}], temperature=0 # 降低随机性，保证输出稳定 ) return response.choices[0].message.content except Exception as e: print(f\u0026#34;LLM调用失败: {e}\u0026#34;) return \u0026#34;\u0026#34; def _search(self, query: str) -\u0026gt; Optional[str]: \u0026#34;\u0026#34;\u0026#34;使用搜索答案\u0026#34;\u0026#34;\u0026#34; if not tavily_api_Key: print(\u0026#34;未配置tavily_api_Key，跳过搜索\u0026#34;) return None client = TavilyClient(api_key=tavily_api_Key) try: # 调用Tavily API执行搜索 response = client.search(query, max_results=2) # 提取搜索结果项 items = response.get(\u0026#34;results\u0026#34;, []) # 提取所有摘要并合并为字符串（用换行分隔） combined_summary = \u0026#34;\\n\u0026#34;.join([item[\u0026#34;content\u0026#34;] for item in items]) print(f\u0026#34;搜索结果：\\n{combined_summary}\u0026#34;) return combined_summary except Exception as e: print(f\u0026#34;搜索过程中发生错误: {e}\u0026#34;) return None def _get_followup_prompt(self, query: str, history: str) -\u0026gt; str: \u0026#34;\u0026#34;\u0026#34;生成用于判断是否需要子问题的prompt\u0026#34;\u0026#34;\u0026#34; return f\u0026#34;\u0026#34;\u0026#34; 问题：{query} 已有的推理过程：{history} 接下来还需要问什么问题吗？请仅回答\u0026#34;Yes\u0026#34;或\u0026#34;No\u0026#34;。如果回答Yes，请同时给出具体的追问（格式：Follow up:[你的问题]）。 \u0026#34;\u0026#34;\u0026#34; def _get_final_answer_prompt(self, query: str, history: str) -\u0026gt; str: \u0026#34;\u0026#34;\u0026#34;生成用于综合中间答案得到最终结果的prompt\u0026#34;\u0026#34;\u0026#34; return f\u0026#34;\u0026#34;\u0026#34; 请根据以下问题和推理过程，给出最终答案： 问题：{query} 推理过程：{history} 请直接给出答案，无需额外解释。 \u0026#34;\u0026#34;\u0026#34; def run(self, query: str) -\u0026gt; str: \u0026#34;\u0026#34;\u0026#34;运行Self-Ask流程\u0026#34;\u0026#34;\u0026#34; self.history = [] current_history = \u0026#34;\u0026#34; step = 0 while step \u0026lt; self.max_steps: step += 1 # 1. 判断是否需要继续提问 followup_prompt = self._get_followup_prompt(query, current_history) followup_response = self._call_llm(followup_prompt) # 2. 解析是否需要子问题 if \u0026#34;No\u0026#34; in followup_response: # 无需更多子问题，生成最终答案 final_prompt = self._get_final_answer_prompt(query, current_history) final_answer = self._call_llm(final_prompt) return final_answer elif \u0026#34;Yes\u0026#34; in followup_response and \u0026#34;Follow up:\u0026#34; in followup_response: # 提取子问题 followup_question = followup_response.split(\u0026#34;Follow up:\u0026#34;)[-1].strip() self.history.append({\u0026#34;followup\u0026#34;: followup_question, \u0026#34;answer\u0026#34;: \u0026#34;\u0026#34;}) print(f\u0026#34;子问题 {step}: {followup_question}\u0026#34;) # 3. 搜索子问题答案（失败则用LLM回答） search_answer = self._search(followup_question) if search_answer: intermediate_answer = search_answer else: print(f\u0026#34;搜索无结果，使用LLM回答子问题：{followup_question}\u0026#34;) intermediate_answer = self._call_llm(followup_question) # 4. 记录中间答案，更新历史 self.history[-1][\u0026#34;answer\u0026#34;] = intermediate_answer current_history += f\u0026#34;\\nFollow up:{followup_question}\\nIntermediate answer: {intermediate_answer}\u0026#34; print(f\u0026#34;中间答案 {step}: {intermediate_answer}\u0026#34;) else: # LLM输出格式异常，直接用LLM回答原问题 print(\u0026#34;LLM输出格式异常，直接回答原问题\u0026#34;) return self._call_llm(query) # 超过最大步骤，强制生成最终答案 final_prompt = self._get_final_answer_prompt(query, current_history) return self._call_llm(final_prompt) # 示例运行 if __name__ == \u0026#34;__main__\u0026#34;: # 初始话模型调用客户端 client = initialize_model_client() # 初始化代理 agent = SelfAskAgent(client=client, max_steps=5) # 测试2跳问题 question = \u0026#34;超导被发现时，谁是美国总统？\u0026#34; print(f\u0026#34;问题：{question}\u0026#34;) answer = agent.run(question) print(f\u0026#34;最终答案：{answer}\u0026#34;) # 测试4跳问题 question = \u0026#34;贾斯汀·比伯出生那年谁赢得了大师赛？,这个赢得了大师赛的人出生的那年谁是美国总统\u0026#34; print(f\u0026#34;\\n问题：{question}\u0026#34;) answer = agent.run(question) print(f\u0026#34;最终答案：{answer}\u0026#34;) 6.4 运行测试\r测试\n​\t执行命令：python self_ask.py\n输出结果\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 D:\\program\\anaconda3\\envs\\langchain_311_env\\python.exe D:\\dev\\guwei\\tiny-agent-lab\\agent-practice\\cognitive_architecture\\self_ask\\self_ask.py 问题：超导被发现时，谁是美国总统？ 子问题 1: 超导是在哪一年被发现的？ 搜索结果： 超导体的发现可以追溯到1911年，荷兰物理学家海克·卡末林·昂内斯（Heike Kamerlingh Onnes）在实验中发现，汞的电阻在冷却到液氦温度（约-269°C）时突然降至零。 这一发现标志着 在1911年，昂内斯测量了纯金属（汞，后来是锡和铅）在极低温度下的电导率，从而发现了石破天惊的超导现象。有意思的是，当时的一些科学家，如威廉·汤姆森（Lord 中间答案 1: 超导体的发现可以追溯到1911年，荷兰物理学家海克·卡末林·昂内斯（Heike Kamerlingh Onnes）在实验中发现，汞的电阻在冷却到液氦温度（约-269°C）时突然降至零。 这一发现标志着 在1911年，昂内斯测量了纯金属（汞，后来是锡和铅）在极低温度下的电导率，从而发现了石破天惊的超导现象。有意思的是，当时的一些科学家，如威廉·汤姆森（Lord 最终答案：威廉·霍华德·塔夫脱 -------------------------------------------------- 问题：北京奥运会那年谁赢得了大师赛？,这个赢得了大师赛的人出生的那年谁是美国总统 子问题 1: 北京奥运会是哪一年举办的？ 搜索结果： 第29届夏季奥林匹克运动会（Beijing 2008；Games of the XXIX Olympiad），又称 2008年 北京奥运会。 2008年8月8日晚上8时整在中国北京开幕，8月24日闭幕。 主办城市是北京，上海、天津、沈阳、秦皇岛、青岛为协办城市。 2008年北京奥运会的成功举办，是中国第一次举办世界性的体育赛事，这次奥运会既是中国走向世界的重要一步，也是向世界全面展示中国改革开放成就的重要舞台。 中间答案 1: 第29届夏季奥林匹克运动会（Beijing 2008；Games of the XXIX Olympiad），又称 2008年 北京奥运会。 2008年8月8日晚上8时整在中国北京开幕，8月24日闭幕。 主办城市是北京，上海、天津、沈阳、秦皇岛、青岛为协办城市。 2008年北京奥运会的成功举办，是中国第一次举办世界性的体育赛事，这次奥运会既是中国走向世界的重要一步，也是向世界全面展示中国改革开放成就的重要舞台。 子问题 2: 2008年大师赛（高尔夫）的冠军是谁？ 搜索结果： 历届冠军 ; 2008 · 特雷弗·伊梅尔曼 · 南非, −8, 3 ; 2007 · 扎克·詹森 · 美国, +1, 2 美国大师赛The Masters ; 2008, Trevor Immelman, South Africa ; 2007, Zach Johnson, United States ; 2006, Phil Mickelson (2), United States ; 2005 中间答案 2: 历届冠军 ; 2008 · 特雷弗·伊梅尔曼 · 南非, −8, 3 ; 2007 · 扎克·詹森 · 美国, +1, 2 美国大师赛The Masters ; 2008, Trevor Immelman, South Africa ; 2007, Zach Johnson, United States ; 2006, Phil Mickelson (2), United States ; 2005 子问题 3: 特雷弗·伊梅尔曼（Trevor Immelman）是哪一年出生的？ 搜索结果： 特雷弗·伊梅尔曼（Trevor Immelman，1979年12月16日—）是一位南非职业高尔夫球手。 出生在南非的开普敦。 于1999年转为职业球员。 他在2008年战胜了世界排名第一的老虎伍兹， 特雷弗·伊梅尔曼（Trevor Immelman，1979年12月16日—），南非职业高尔夫运动员，出生于开普敦高尔夫世家。5岁开始打球，1998年赢得US业余公共林克斯锦标赛冠军，1999年转为 中间答案 3: 特雷弗·伊梅尔曼（Trevor Immelman，1979年12月16日—）是一位南非职业高尔夫球手。 出生在南非的开普敦。 于1999年转为职业球员。 他在2008年战胜了世界排名第一的老虎伍兹， 特雷弗·伊梅尔曼（Trevor Immelman，1979年12月16日—），南非职业高尔夫运动员，出生于开普敦高尔夫世家。5岁开始打球，1998年赢得US业余公共林克斯锦标赛冠军，1999年转为 最终答案：1979年美国总统是吉米·卡特。 7 总结\r综上，self-ask 是一种为提升语言模型组合性推理能力设计的结构化提示框架，核心思想是通过显式拆解复杂问题为子问题、分步求解并整合答案，以缩小组合性差距（模型能正确回答子问题却无法生成整体答案的比例），是增强大模型复杂任务处理能力的重要思路。\n其对子问题的解答上，可灵活集成外部工具进行处理，可以做如下优化：\n多工具集成：除搜索引擎外，整合计算器、数据库查询、API 调用等，适配不同类型问题。 工具选择机制：让模型自主判断子问题需要哪种工具，而非固定优先搜索。 8 实验测试及结果（附录）\r论文中的使用多跳问答来量化语言模型的推理能力，通过设计数据集、对比提示方法及结合外部工具，系统探究了组合性差距的特性及缩小策略，具体如下：\n测试数据集\r实验使用 4 类数据集，涵盖自动生成、手动构建及已有公开数据集，确保测试的全面性：\nCompositional Celebrities (CC)：自动生成的 8.6k 个 2 跳 问题，基于 17 类模板（如 “名人出生地的国家代码”“名人出生年份的诺贝尔奖得主”），特点是子事实（如名人出生地、年份相关事实）在训练数据中常见，但组合形式罕见（几乎未在训练数据或互联网中出现），可有效区分模型的 “记忆” 与 “推理” 能力；\n2WikiMultiHopQA和Musique：已有的公开 2-hop 问题数据集，基于维基百科事实，用于跨数据集验证方法的通用性与稳定性。\nBamboogle：手动构建的 125 个 2-hop 问题，源自维基百科关键条目，问题高度多样化（无固定模板），且经筛选确保搜索引擎的 “精选摘要” 输出错误（证明问题未在网络中广泛存在），用于测试模型对非结构化、复杂问题的推理能力。\n1 论文中获取Bamboogle数据的方式：通过查询文章主题的两个不相关事实来生成每篇文章的 2 跳问题。例如，在阅读 “旅行者 2 号” 的文章时，我们了解到它是第一个接近天王星的探测器，并且它是用泰坦 IIIE 火箭发射的，由此产生了问题：“第一个接近天王星的航天器是用什么火箭发射的？” 然后，我们将我们的问题输入互联网搜索引擎，只有当查询引出的 “精选摘要” 答案不正确时，才将其添加到最终数据集；我们使用搜索引擎来筛选我们的数据集，因为我们假设搜索引擎无法回答这些问题，表明这些问题不在网络上。 ​\t这种问题形式有很多优点：几乎所有问题都有唯一正确答案，易于分解为子问题（让我们能验证语言模型是否了解背景事实），而且大多数问题的答案范围很广（不同于是非题或选择题）。因此，随机猜对正确答案的概率很低。\n关键测试结果\r组合性差距的特性：\nGPT-3 系列中，模型规模从 0.35B 增至 175B 时，单跳问题准确率提升显著快于多跳问题，组合性差距稳定在 40% 左右（Instruct 与非 Instruct 模型一致），表明更大模型更擅长记忆事实而非组合推理； 模型对事实的信心（perplexity，即对正确子问题答案的困惑度）与组合能力正相关：当子问题答案的最大困惑度极低（1.000-1.002）时，组合问题准确率达 81.1%，显著高于高困惑度场景（42.6%）。 对比同类方法的效果：\n以下表格呈现了论文中不同方法在各测试数据集上的准确率结果。\n方法 Bamboogle 2WikiMultiHopQA Musique 直接提示 17.6 25.4 5.6 思维链 46.4 29.8 12.6 搜索引擎 0.0 2.2 1.5 搜索引擎 + 后处理 - 26.3 6.5 self-ask 57.6 30.0 13.8 self-ask+ 搜索 60.0 40.1 15.2 结合数据可得出以下关键结论：\n诱导性提示显著优于直接提示：self-ask 使组合问题准确率大幅提升，甚至能缩小组合性差距（部分模型中接近闭合）；\nself-ask 优于思维链：在 Bamboogle（高多样性问题）上，self-ask 准确率（57.6%）比思维链（46.4%）高 11%，因结构化拆解更适应非模板化问题；在 2WikiMultiHopQA 和 Musique 上，self-ask 准确率（30.0%、13.8%）略高于思维链（29.8%、12.6%）；\n思维链存在格式一致性问题：在 Bamboogle 中，40% 的思维链输出为完整句子（非短形式答案），而 self-ask 因结构化支架，该比例仅 17%（结合搜索引擎后降至 3%）。\nself-ask + 搜索引擎进一步提升性能：在 2WikiMultiHopQA 上准确率达 40.1%（高于 self-ask 的 30.0%），在 Musique（15.2% vs 13.8%）和 Bamboogle（60.0% vs 57.6%）上提升 5%-10%，因搜索引擎补充了模型缺失的实时或罕见事实；\n兼容性优势：无需修改模型或提示，仅通过 API 接口即可联动，实现简单（数行代码）。\n与同类分解式方法（如 Least-to-most）相比，self-ask 在相近准确率下生成的 tokens 更少（如 2WikiMultiHopQA 中，self-ask 平均生成 569 tokens，Least-to-most 为 844 tokens），速度快 30% 以上，更高效。\n完整源码地址：\nGitHub 仓库：https://github.com/tinyseeking/tidy-agent-practice/tree/main/cognitive_pattern/self_ask Gitee 仓库（国内）：https://gitee.com/tinyseeking/tidy-agent-practice/tree/main/cognitive_pattern/self_ask ","date":"2026-01-19T00:00:00Z","image":"/p/%E5%BC%80%E6%BA%90%E6%99%BA%E8%83%BD%E4%BD%93%E6%A1%86%E6%9E%B6self-ask-%E5%8E%9F%E7%90%86%E6%9E%B6%E6%9E%84%E4%B8%8E%E4%BB%A3%E7%A0%81%E5%AE%9E%E7%8E%B0/images/image-20250815105357593.png","permalink":"/p/%E5%BC%80%E6%BA%90%E6%99%BA%E8%83%BD%E4%BD%93%E6%A1%86%E6%9E%B6self-ask-%E5%8E%9F%E7%90%86%E6%9E%B6%E6%9E%84%E4%B8%8E%E4%BB%A3%E7%A0%81%E5%AE%9E%E7%8E%B0/","title":"开源智能体框架——Self-Ask 原理、架构与代码实现"},{"content":"ReAct 原理、架构与代码实现\r1 介绍\r​\t传统大语言模型（LLM）在处理复杂任务时，还面临着诸多局限性：比如只能进行静态推理（训练完成后内部知识固定），无法根据新情况动态调整；它们常常编造不存在的事实（称为幻觉），如描述一款从未发布的手机型号；在需要多步骤推理的任务中，容易在中间环节出错；更重要的是缺乏与外部环境的交互能力，它们像被关在信息孤岛上，无法与计算器、数据库等外部工具交互，在需要实时信息或工具辅助的复杂任务中表现不佳。而 ReAct 模式的出现，正是为了解决这些问题。\n​\tReAct 是一种结合 “推理（Reasoning）” 与 “行动（Acting）” 的设计模式，源自 2022 年10月 Google 团队的论文《ReAct: Synergizing Reasoning and Acting in Language Models》（论文地址：https://arxiv.org/pdf/2210.03629），一种通过语言模型将推理与行动相结合进行循环迭代，逐步推进问题解决的范式，旨在解决传统 LLM 在复杂任务中存在的静态推理、易幻觉、缺乏外部交互等局限。这种模式在智能体中占有重要地位。\n2 核心思想\r​\tReAct的核心思想源于人类解决问题的过程，本质上是一场 \u0026ldquo;思考 - 行动 - 反馈\u0026rdquo; 的认知闭环：人类在解决问题时，通常会进行“内心独白”（reasoning）并结合与环境的互动（acting）来逐步逼近目标 。就如周末计划出游时，你会先琢磨 “得看看天气怎么样”（想办法），然后点开天气软件查一查（动手做），发现 “周六要下雨”（看到结果），接着就改主意去看电影了（又开始新一轮琢磨）。\n​\tReAct的核心思路是模拟人类 “思考 - 行动 - 反馈” 的认知闭环，成功实现了将语言模型推理能力与外部工具行动能力的有机结合，显著提升了在复杂任务中的问题解决效率与实际落地能力。具体来说，ReAct 模式通过 “思考（Thought）- 行动（Action）- 观察（Observation）” 的迭代循环机制运作：接收用户任务后，大语言模型生成思考过程，规划下一步行动；接着执行行动，调用工具接口与外部环境交互；然后获取观察结果，即工具返回的信息；最后更新上下文，将任务、思考、行动、观察等完整轨迹存储起来，作为下一轮循环的输入，不断循环迭代，直到完成任务或达到最大迭代次数。\n​\t其中核心是推理和行动之间产生强的协同效应：推理轨迹使模型能够进行动态推导、跟踪和调整行动计划，而行动则使模型能够与外部环境资源交互以收集额外信息，进而影响并优化推理决策，通过与环境的持续互动迭代来逐步推进任务的完成。其优势显著，不仅能提升复杂任务的解决能力，通过外部工具补充实时信息解决知识过时问题；还能增强可解释性，显式的 “思考轨迹” 让决策过程透明化，增强可信度；同时具有通用性，适用于知识问答、交互决策、工具调用等多种场景，在智能体开发中占据核心地位。\n3 与传统模式的对比\r​\t在ReAct 没有出现之前，研究者大都是将推理（Reasoning） 和 行动（Act） 分开来研究，如仅推理（eg：Chain-of-Thought、Tree-of-Thought）、仅行动（eg：WebGPT）,它们的对比如下：\n**思维链（Chain-of-Thought、COT）：**是一种让模型生成中间推理步骤的技术，但它是一个静态的黑箱过程。模型仅使用内部表示生成思考，不与外部世界交互，这限制了其在反应性推理或知识更新方面的能力，容易导致事实幻觉和错误传播。 **思维树（Tree-of-Thoughts、TOT）：**允许模型在决策过程中探索多个可能的路径，类似于树状搜索。与 ReAct 相比，ToT 更注重探索不同的解决方案路径，而 ReAct 更强调思考与行动的交互循环。 纯行动模式：只执行动作而不生成显式的思考步骤，就像没有大脑的执行者，只能机械地按预设指令操作（如 WebGPT 与互联网交互回答问题的方式），无法根据新情况调整策略。 ReAct 模式：ReAct 则结合了思考和行动的优势：思考步骤帮助模型归纳、跟踪和更新行动计划，处理异常情况；而行动允许模型与外部资源（如知识库或环境）交互，收集额外信息。这种协同作用使 ReAct 在多个基准测试中表现出色。 ​ 为了更好的了解它们之间的区别，我们以 “查询并预订周五晚上的餐厅” 为例，按不同模式的交互来说明，示例提示问题是 “想找一家周五晚上适合 2 人聚餐的西餐厅，人均消费在 100-200 元之间，帮我查一下并预订。”\n1 2 3 4 5 提示： 解决这个问题，按人类的思路是： 1. 先筛选符合“西餐厅、人均100-200元、周五晚可预订2人位”条件的餐厅； 2. 查看餐厅评分、口味评价等信息，缩小选择范围； 3. 确定目标餐厅后，完成预订并确认细节（时间、座位偏好等）。 仅用思维链（Chain-of-Thought、COT）提示。得到结果： 1 2 3 想法：首先得找人均100-200元的西餐厅，然后看周五晚上有没有2人的位置。应该选评分高一点的，口味好的。可能需要查一下附近的店。 答案：可以选附近的几家西餐厅，预订周五晚上的位置。 答案流于形式，虽有推理逻辑，但未结合实际信息（如具体餐厅、空位情况），无法落地执行，相当于“只说不做”。 仅用思维树（Tree-of-Thoughts、TOT）提示。得到结果： 1 2 3 想法：分几种情况考虑：方案一，若A餐厅有空位，就订A（评分4.8，人均180元）；方案二，若A满了，选B餐厅（评分4.6，人均160元）；方案三，若前两家都满了，换C餐厅（评分4.5，人均150元）。但不确定这几家周五是否有空位。 答案：可能订A、B或C餐厅，需看实际空位情况。 答案停留在假设层面，虽探索了多路径，但因未验证“餐厅是否有空位”这一关键信息，无法给出确定结论，属于“空想式规划”。 仅用动作。得到结果： 1 2 3 4 5 6 7 8 动作1：搜索“人均100-200元的西餐厅” 结果1：返回A、B、C三家餐厅信息 动作2：查询这三家周五晚的空位 结果2：A和B满座，C有2人位 动作3：完成【预订C餐厅】 答案虽有结果，但缺乏推理过程。未解释“为何选C（如评分、口味）”，也未确认预订细节（如到店时间、座位偏好），属于“盲目执行”。 ReAct 方法，即推理 + 动作。得到结果： 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 思考1：我需要先找到符合“人均100-200元、西餐厅”条件的餐厅，再查它们周五晚的空位。 动作1：搜索“人均100-200元的西餐厅” 结果1：返回A（评分4.8，人均180）、B（4.6，160）、C（4.5，150）三家。 思考2：接下来得确认这三家周五晚是否有2人位，优先选评分高的。 动作2：查询三家周五晚的空位 结果2：A和B满座，C有2人位（靠窗位置可选）。 思考3：C餐厅有空位，且评分4.5不算低，人均150元符合预算。需要确认它的口味（比如是否有招牌牛排）和预订细节。 动作3：查看C餐厅的口味评价和预订要求 结果3：招牌牛排评价高，支持在线预订，需备注到店时间（建议18:30-20:00）。 思考4：信息齐全，可以预订了。 动作4：完成【预订C餐厅周五晚19:00的2人靠窗位】 答案准确且可执行，通过“推理指导动作（如先筛选再查空位）、动作反馈优化推理（如根据空位和评价确定C餐厅）”的闭环，既解决了问题，又体现了决策逻辑。 ​\t通过这个示例可见，ReAct 模式的核心价值在于：用模型推理明确思考过程 “为什么这么做”，用行动验证 “能不能做”，用反馈优化 “怎么做”，完美复刻了人类 “思考 - 行动 - 反馈” 的解决问题模式，既避免了 “空想不落地”，又杜绝了 “盲目乱执行”。\nReAct 模式相比传统方法具有多方面的优势：\n减少幻觉问题：通过与外部环境交互，ReAct 能够验证假设并获取实时信息，显著降低模型 \u0026ldquo;幻觉\u0026rdquo;（生成不准确信息）的风险。 提高问题解决能力：在需要多步骤决策和信息检索的任务中，ReAct 表现出色，例如问答、事实核查和交互式决策等领域。 增强可解释性：ReAct 生成的推理轨迹提供了透明的决策过程，使得模型的行为更易于理解和信任。 适应性强：能够在不同领域和任务间灵活应用，只需调整可用工具集和提示策略。 效率提升：最新研究显示，通过优化策略，可实现高达 34% 的运行时间减少，同时保持或提高准确性。 4 工作流程\r​\tReAct 的工作机制可以概括为一个循环过程 “思考（Thought）→ 行动（Action）→ 观察（Observation）”，简称 TAO 循环，如下图所示：\n主要包含以下几个关键步骤：\n接收输入：代理接收用户的初始查询或任务描述。代理根据预设好的 ReAct 提示词模板(格式为Quesion-\u0026gt;Thought-\u0026gt;Action-\u0026gt;Observation)和用户的问题合并，构建上下文提示词发送给大模型。 生成思考：LLM 进行思考，分析当前上下文（包含任务和历史交互记录），生成解决问题所需的推理步骤和规划下一步的行动。思考应该明确下一步行动的理由。LLM 生成的行动是标准化的工具调用指令，格式必须严格规范，以便系统解析，通常行动指令包含工具名称和参数，采用：Action: 工具名称(参数键=参数值, ...)。例如调用计算器时：Action: calculator(expression=3.14*5^2)。若模型思考已足够生成最终答案，则输出行动为Finish，表示已完成任务，并输出最终答案。 执行行动：代理解析大模型生成的响应结果，判断输出的Action 是不是 Finish，如果是Finish则表示已经完成任务，直接返回最终答案响应给用户；否则解析出需要执行的工具名称及其参数信息，并调用相应的工具执行，如执行代码、查询数据库、调用 API或执行计算等。 获取观察：代理接收行动的结果，即观察值。这一步是检验我们的行动是否有效，是否接近了问题的答案。 更新上下文：代理将新的观察结果存入到上下文中（提示词中），形成新的状态，作为下一轮思考的依据。 重复循环：代理根据新的状态继续生成思考和执行行动，直到达到终止条件（即任务完成或达到最大迭代次数）。 ​\t这种循环结构使得代理能够在 \u0026ldquo;思考 - 行动 - 观察\u0026rdquo; 的闭环中不断调整策略，逐步逼近最优解。\n5 应用场景\rReAct 模式在多个领域展现出巨大潜力：\n信息检索与问答系统：如 HotpotQA 等复杂问答任务，ReAct 能够通过多步搜索获取准确答案。 交互式决策系统：如在 ALFWorld 和 WebShop 等环境中，ReAct 能够完成复杂的任务序列，成功率显著高于传统方法。 智能客服与对话系统：通过结合推理和工具调用，ReAct 可以提供更准确、更个性化的客户支持。 专业领域应用：如医疗诊断、法律分析和金融决策等需要专业知识和外部数据支持的领域。 6 代码实现\r6.1 架构设计\r​\t要将 ReAct 的设计思想落地，实现 “思考 - 行动 - 观察” 的循环，关键是构建一套分工明确且协同工作的架构。其核心组成如下：\n大语言模型（LLM）：智能体的 “大脑”，负责生成思考过程和行动指令，推理能力直接决定智能体表现。**注意：**引导模型生成思考过程和行动指令是由预设好的 ReAct 提示词模板设定的。\n工具接口（Tools）：智能体的 “手脚”，是与外部环境交互的桥梁，如执行代码、查询数据库、调用 API或执行计算等。工具接口需要统一的调用规范，通常要求行动指令包含工具名称和参数，例如：Action: search(\u0026quot;2025人工智能法案\u0026quot;, \u0026quot;en\u0026quot;)\n上下文管理器（Context）：“记忆中枢”，存储完整交互轨迹，包括原始任务、思考记录、行动及观察结果，作为 prompt 输入 LLM，确保智能体能回顾进程、把握当前状态。\n决策控制器：负责判断何时终止循环（如已经得到最终答案），何时需要继续迭代；同时处理异常情况，比如工具调用失败时指令 LLM 重试，或在超时情况下强制结束并返回当前结果。\n6.2 执行流程\r各组件是如何交互协作来共同处理任务的呢？如下时序图展示了详细的步骤。\n任务接收与初始化 接收用户输入的问题或任务指令，格式化 ReAct 提示词模板，构建上下文提示词发送给大模型。列如：用户问题 \u0026ldquo;广州天气如何\u0026rdquo;。 思考生成（Thought） LLM 进行思考，分析当前上下文（包含任务和历史交互记录），生成解决问题所需的思考步骤和下一步的行动。 示例：AI输出 \u0026ldquo;我需要查询广州的天气，应该使用 get_weather 工具\u0026rdquo;，工具调用的 JSON：{\u0026quot;action\u0026quot;: \u0026quot;get_weather\u0026quot;, \u0026quot;action_input\u0026quot;: {\u0026quot;location\u0026quot;: \u0026quot;广州\u0026quot;}} 行动决策与执行（Action） 解析模型输出，提取出工具名称和参数，调用对应工具获取结果。 示例：调用get_weather(\u0026ldquo;广州\u0026rdquo;)，获取天气数据 观察结果整合（Observation） 接收工具返回的结果，更新到上下文，作为下一轮思考的依据。 循环与终止 重复思考 - 行动 - 观察过程，直到模型判断已获取足够信息，生成最终答案。 生成最终答案，格式通常为：我已经获取了广州的天气信息，现在可以整理成自然语言回答。Final Answer: 地球直径约为月球的3.67倍 6.3 ReAct模式实现方案\r​\t目前，许多大语言模型（LLM）支持 Function Calling 机制，可实现高效的工具调用及与外部 API 的交互。像 GPT-4、通义千问 Plus 等支持该机制的模型，能自动判断何时需要调用函数，并以 JSON 格式输出包含函数名和所需参数的结构化数据。\n​\t不过，由于 ReAct 模式的论文发表于 2022 年 10 月，而 Function Calling 机制最早由 OpenAI 在 2023 年 6 月提出，因此早期的 ReAct 代码实现需通过编写较复杂的提示词，要求模型返回特定格式的响应。但并非所有 LLM 都支持 Function Calling，对于不支持该机制的模型，仍需依靠提示词来实现 ReAct 模式。\n​\t此外，LangGrath作为强大的大模型应用开发框架，也有许多的开发者使用。鉴于此，这里列出三种 ReAct 模式的代码实现方案：\n实现方案一：基于提示词的ReAct模式代码实现。\n实现方案二：基于Function Calling的ReAct模式实现。\n实现方案三：基于LangGrath的ReAct模式实现。\n6.4 实现方案一：基于提示词的ReAct模式实现\r1、准备工作\rPython 3.11+ 安装必要的库：pip install openai requests python-dotenv 一个可用的大语言模型接口（这里使用的是 \u0026ldquo;qwen\u0026rdquo;，也可替换为其它模型） 配置虚拟环境（使用如conda、venv等，推荐但可选） 2、设置API Key\r创建.env，配置模型的API访问密钥，这里使用千问模型，需配置QWEN_API_KEY、QWEN_BASE_URL。\n1 2 3 4 5 # 千问模型接口访问key # 如何获取API Key：https://help.aliyun.com/zh/model-studio/developer-reference/get-api-key QWEN_API_KEY=\u0026#34;sk-*******\u0026#34; # 千问模型接口访问地址 QWEN_BASE_URL=\u0026#34;https://dashscope.aliyuncs.com/compatible-mode/v1\u0026#34; 3、创建文件，导入依赖\r创建react_original.py文件，导入相关依赖：\n1 2 3 4 5 6 7 8 9 10 11 12 13 import json import re from typing import Optional, Dict import requests import urllib.parse # 加载环境变量 from dotenv import load_dotenv from call_llm.call_llm import initialize_model_client #基于openai SDK的模型调用客户端 load_dotenv() 4、准备Prompt模板\r​\t实现ReAct模式的关键是设计一个清晰的Prompt模板，该提示模板是给模型的 \u0026ldquo;操作手册\u0026rdquo;，规定了思考和调用工具的规则。示例如下：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 # react 提示词模板 REACT_PROMPT_FORMAT = \u0026#34;\u0026#34;\u0026#34; Answer the following questions as best you can. You have access to the following tools: {tools} The way you use the tools is by specifying a json blob. Specifically, this json should have a `action` key (with the name of the tool to use) and a `action_input` key (with the input to the tool going here). The only values that should be in the \u0026#34;action\u0026#34; field are: {tool_names} The $JSON_BLOB should only contain a SINGLE action, do NOT return a list of multiple actions. Here is an example of a valid $JSON_BLOB: ``` {{{{ \u0026#34;action\u0026#34;: $TOOL_NAME, \u0026#34;action_input\u0026#34;: $INPUT }}}} ``` ALWAYS use the following format: Question: the input question you must answer Thought: you should always think about what to do Action: ``` $JSON_BLOB ``` Observation: the result of the action ... (this Thought/Action/Observation can repeat N times) Thought: I now know the final answer Final Answer: the final answer to the original input question Begin! Reminder to always use the exact characters `Final Answer` when responding. \u0026#34;\u0026#34;\u0026#34; 提示词模板包含以下几个元素：\n告诉模型有哪些工具可用（{tools}会被替换为实际工具列表） 如何调用这些工具，严格规定工具调用格式（用特定的 JSON 格式） 强制要求遵循 \u0026ldquo;思考→行动→观察\u0026rdquo; 的流程 明确最终答案的格式（必须以 \u0026ldquo;Final Answer:\u0026rdquo; 开头） 5、定义工具（Tools）\r​\t定义工具（Tools）的schema是为了让模型知道有哪些工具可用，使模型能准确判断何时及如何使用工具，生成准确的工具调用指令。包含函数名称、功能描述，清晰的适用场景及参数要求。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 # 工具定义schema tool_definition = [ { \u0026#34;type\u0026#34;: \u0026#34;function\u0026#34;, \u0026#34;function\u0026#34;: { \u0026#34;name\u0026#34;: \u0026#34;get_weather\u0026#34;, \u0026#34;description\u0026#34;: \u0026#34;获取指定地点的天气信息。参数为地点名称，例如：北京、上海\u0026#34;, \u0026#34;parameters\u0026#34;: { \u0026#34;type\u0026#34;: \u0026#34;object\u0026#34;, \u0026#34;properties\u0026#34;: { \u0026#34;location\u0026#34;: {\u0026#34;type\u0026#34;: \u0026#34;string\u0026#34;, \u0026#34;description\u0026#34;: \u0026#34;location\u0026#34;} }, \u0026#34;required\u0026#34;: [\u0026#34;location\u0026#34;], }, }, }, { \u0026#34;type\u0026#34;: \u0026#34;function\u0026#34;, \u0026#34;function\u0026#34;: { \u0026#34;name\u0026#34;: \u0026#34;calculate\u0026#34;, \u0026#34;description\u0026#34;: \u0026#34;用于数学计算，输入应为数学表达式，例如\u0026#39;3+5*2\u0026#39;或\u0026#39;(4+6)/2\u0026#39;\u0026#34;, \u0026#34;parameters\u0026#34;: { \u0026#34;type\u0026#34;: \u0026#34;object\u0026#34;, \u0026#34;properties\u0026#34;: { \u0026#34;expression\u0026#34;: {\u0026#34;type\u0026#34;: \u0026#34;string\u0026#34;, \u0026#34;description\u0026#34;: \u0026#34;数学表达式\u0026#34;} }, \u0026#34;required\u0026#34;: [\u0026#34;expression\u0026#34;], }, }, } ] 工具的具体实现\n实现工具的具体功能，这里定义了两个函数：\nget_weather函数：通过访问天气 API 获取指定地点的天气 calculate函数：计算数学表达式的结果 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 # 实现获取天气 def get_weather(location: str) -\u0026gt; str: \u0026#34;\u0026#34;\u0026#34;获取指定地点的天气信息。参数为地点名称，例如：北京、上海\u0026#34;\u0026#34;\u0026#34; url = \u0026#34;http://weather.cma.cn/api/autocomplete?q=\u0026#34; + urllib.parse.quote(location) response = requests.get(url) data = response.json() if data[\u0026#34;code\u0026#34;] != 0: return \u0026#34;没找到该位置的信息\u0026#34; location_code = \u0026#34;\u0026#34; for item in data[\u0026#34;data\u0026#34;]: str_array = item.split(\u0026#34;|\u0026#34;) if ( str_array[1] == location or str_array[1] + \u0026#34;市\u0026#34; == location or str_array[2] == location ): location_code = str_array[0] break if location_code == \u0026#34;\u0026#34;: return \u0026#34;没找到该位置的信息\u0026#34; url = f\u0026#34;http://weather.cma.cn/api/now/{location_code}\u0026#34; return requests.get(url).text def calculate(expression: str) -\u0026gt; str: \u0026#34;\u0026#34;\u0026#34;计算数学表达式\u0026#34;\u0026#34;\u0026#34; try: print(f\u0026#34;[执行计算] {expression}\u0026#34;) # 实际应用中应使用更安全的计算库 result = eval(expression) return f\u0026#34;计算结果: {expression} = {result}\u0026#34; except Exception as e: return f\u0026#34;计算错误: {str(e)}\u0026#34; 初始化工具及统一工具调用\n1 2 3 4 5 6 7 8 9 10 11 12 # 初始化工具 tools = { \u0026#34;get_weather\u0026#34;: get_weather, \u0026#34;calculate\u0026#34;: calculate } # 实现统一工具调用，根据名称找到并调用对应的工具 def invoke_tool(toolName: str, toolParameters) -\u0026gt; str: \u0026#34;\u0026#34;\u0026#34;工具调用\u0026#34;\u0026#34;\u0026#34; if toolName not in tools: return f\u0026#34;函数{toolName}未定义\u0026#34; return tools[toolName](**toolParameters) 6、生成系统提示词\r根据之前准备的 ReAct Prompt模板，填充可用工具定义，生成完整的系统提示词。\n1 2 3 4 def get_system_prompt(): tool_strings = \u0026#34;\\n\u0026#34;.join([json.dumps(tool[\u0026#34;function\u0026#34;], ensure_ascii=False) for tool in tool_definition]) tool_names = \u0026#34;, \u0026#34;.join([tool[\u0026#34;function\u0026#34;][\u0026#34;name\u0026#34;] for tool in tool_definition]) return REACT_PROMPT_FORMAT.format(tools=tool_strings, tool_names=tool_names) 7、实现ReAct 主流程逻辑\rReAct 模式的核心逻辑，主导 \u0026ldquo;思考→行动→观察\u0026rdquo; 的闭环循环，直至达成任务目标或达到最大迭代次数。代码如下：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 # 系统信息 system_msg = {\u0026#34;role\u0026#34;: \u0026#34;system\u0026#34;, \u0026#34;content\u0026#34;: get_system_prompt()} maxIter = 5 # 最大迭代次数 agent_scratchpad = \u0026#34;\u0026#34; # 历史思考、行动、观察记录 # 获取千问模型客户端 client = initialize_model_client() for iter_seq in range(1, maxIter + 1): print(f\u0026#34;\\n\\n\\n\u0026gt;\u0026gt;\u0026gt; 迭代次数: {iter_seq}\u0026#34;) # 构建消息列表 messages = [ system_msg, {\u0026#34;role\u0026#34;: \u0026#34;user\u0026#34;, \u0026#34;content\u0026#34;: f\u0026#34;Question: {query}\\n\\nhistory：\\n{agent_scratchpad}\u0026#34;} ] # 向LLM发起请求 chat_completion = client.chat.completions.create( ...... ) print(f\u0026#34;\u0026gt;\u0026gt;\u0026gt; LLM响应:\\n{content}\u0026#34;) # 检查是否有最终答案，有则直接返回答案 final_answer_match = re.search(r\u0026#34;Final Answer:\\s*(.*)\u0026#34;, content, re.DOTALL) if final_answer_match: final_answer = final_answer_match.group(1) print(f\u0026#34;\\n\u0026gt;\u0026gt;\u0026gt; 最终答案: {final_answer}\u0026#34;) return # 解析提取工具调用信息 tool_call = parse_action(content) if not tool_call: result = \u0026#34;错误: 无法从响应中提取工具调用信息\u0026#34; print(f\u0026#34;\u0026gt;\u0026gt;\u0026gt; {result}\u0026#34;) # 更新思考过程 agent_scratchpad += f\u0026#34;{content}\\nObservation: {result}\\n\u0026#34; continue try: tool_name = tool_call[\u0026#34;tool_name\u0026#34;] tool_parameters = tool_call[\u0026#34;input\u0026#34;] # 调用工具并获取结果 result = invoke_tool(tool_name, tool_parameters) print(f\u0026#34;\u0026gt;\u0026gt;\u0026gt; 工具返回结果: {result}\u0026#34;) # 更新思考过程 agent_scratchpad += f\u0026#34;{content}\\nObservation: {result}\\n\u0026#34; except Exception as e: print(f\u0026#34;\u0026gt;\u0026gt;\u0026gt; 错误: 工具调用失败 - {str(e)}\u0026#34;) # 更新思考过程 agent_scratchpad += f\u0026#34;{content}\\nObservation: 错误: 工具调用失败 - {str(e)}\\n\u0026#34; print(\u0026#34;\\n\u0026gt;\u0026gt;\u0026gt; 迭代次数达到上限或发生错误，无法生成最终答案\u0026#34;) 实现基于 ReAct 模式的 Agent 执行流程，核心逻辑如下：\n初始化系统信息（系统提示、最大迭代次数、历史记录存储）和模型客户端。 在最大迭代次数内循环执行： 构建包含系统提示、用户问题和历史记录的消息。 调用大语言模型（千问或 GPT）获取思考结果。 检查是否生成最终答案（响应以Final Answer:开头），若有则返回，若没有说明还有行动（工具调用）要完成。 否则解析模型响应中的工具调用指令。 执行相应工具并获取结果（即观察结果），更新历史记录。 进入下一轮的循环。 若达到最大迭代次数仍无结果，则提示无法完成。 ​ 整个流程通过循环实现 \u0026ldquo;思考→行动→观察\u0026rdquo; 的闭环，历史记录 (agent_scratchpad) 不断累积以提供上下文，确保 Agent 能基于之前的交互继续推理，直至达成任务目标或达到最大迭代次数。\n8、完整代码\r完整代码位于项目根目录下：cognitive_pattern/react/react_original.py\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 import json import os import re from typing import Optional, Dict import requests import urllib.parse # 加载环境变量 from dotenv import load_dotenv from openai import OpenAI def initialize_model_client() -\u0026gt; OpenAI: \u0026#34;\u0026#34;\u0026#34; 获取模型调用客户端,使用openai SDK，支持所有兼容openai接口的模型服务，默认使用千问模型 Returns: OpenAI客户端实例 \u0026#34;\u0026#34;\u0026#34; # 获取千问API密钥 qwen_api_key = os.getenv(\u0026#34;QWEN_API_KEY\u0026#34;) if not qwen_api_key: raise ValueError(f\u0026#34;缺少{qwen_api_key}环境变量\u0026#34;) # 获取千问请求端口URL qwen_base_url = os.getenv(\u0026#34;QWEN_BASE_URL\u0026#34;) client = OpenAI( # 配置请求密钥:api_key，这里使用千问模型，请用百炼API Key将下行替换。 # 如何获取API Key：https://help.aliyun.com/zh/model-studio/developer-reference/get-api-key api_key=qwen_api_key, base_url=qwen_base_url, ) return client load_dotenv() # react 提示词模板 REACT_PROMPT_FORMAT = \u0026#34;\u0026#34;\u0026#34; Answer the following questions as best you can. You have access to the following tools: {tools} The way you use the tools is by specifying a json blob. Specifically, this json should have a `action` key (with the name of the tool to use) and a `action_input` key (with the input to the tool going here). The only values that should be in the \u0026#34;action\u0026#34; field are: {tool_names} The $JSON_BLOB should only contain a SINGLE action, do NOT return a list of multiple actions. Here is an example of a valid $JSON_BLOB: ``` {{{{ \u0026#34;action\u0026#34;: $TOOL_NAME, \u0026#34;action_input\u0026#34;: $INPUT }}}} ``` ALWAYS use the following format: Question: the input question you must answer Thought: you should always think about what to do Action: ``` $JSON_BLOB ``` Observation: the result of the action ... (this Thought/Action/Observation can repeat N times) Thought: I now know the final answer Final Answer: the final answer to the original input question Begin! Reminder to always use the exact characters `Final Answer` when responding. \u0026#34;\u0026#34;\u0026#34; # 工具定义 tool_definition = [ { \u0026#34;type\u0026#34;: \u0026#34;function\u0026#34;, \u0026#34;function\u0026#34;: { \u0026#34;name\u0026#34;: \u0026#34;get_weather\u0026#34;, \u0026#34;description\u0026#34;: \u0026#34;获取指定地点的天气信息。参数为地点名称，例如：北京、上海\u0026#34;, \u0026#34;parameters\u0026#34;: { \u0026#34;type\u0026#34;: \u0026#34;object\u0026#34;, \u0026#34;properties\u0026#34;: { \u0026#34;location\u0026#34;: {\u0026#34;type\u0026#34;: \u0026#34;string\u0026#34;, \u0026#34;description\u0026#34;: \u0026#34;location\u0026#34;} }, \u0026#34;required\u0026#34;: [\u0026#34;location\u0026#34;], }, }, }, { \u0026#34;type\u0026#34;: \u0026#34;function\u0026#34;, \u0026#34;function\u0026#34;: { \u0026#34;name\u0026#34;: \u0026#34;calculate\u0026#34;, \u0026#34;description\u0026#34;: \u0026#34;用于数学计算，输入应为数学表达式，例如\u0026#39;3+5*2\u0026#39;或\u0026#39;(4+6)/2\u0026#39;\u0026#34;, \u0026#34;parameters\u0026#34;: { \u0026#34;type\u0026#34;: \u0026#34;object\u0026#34;, \u0026#34;properties\u0026#34;: { \u0026#34;expression\u0026#34;: {\u0026#34;type\u0026#34;: \u0026#34;string\u0026#34;, \u0026#34;description\u0026#34;: \u0026#34;数学表达式\u0026#34;} }, \u0026#34;required\u0026#34;: [\u0026#34;expression\u0026#34;], }, }, } ] # 实现获取天气 def get_weather(location: str) -\u0026gt; str: \u0026#34;\u0026#34;\u0026#34;获取指定地点的天气信息。参数为地点名称，例如：北京、上海\u0026#34;\u0026#34;\u0026#34; url = \u0026#34;http://weather.cma.cn/api/autocomplete?q=\u0026#34; + urllib.parse.quote(location) response = requests.get(url) data = response.json() if data[\u0026#34;code\u0026#34;] != 0: return \u0026#34;没找到该位置的信息\u0026#34; location_code = \u0026#34;\u0026#34; for item in data[\u0026#34;data\u0026#34;]: str_array = item.split(\u0026#34;|\u0026#34;) if ( str_array[1] == location or str_array[1] + \u0026#34;市\u0026#34; == location or str_array[2] == location ): location_code = str_array[0] break if location_code == \u0026#34;\u0026#34;: return \u0026#34;没找到该位置的信息\u0026#34; url = f\u0026#34;http://weather.cma.cn/api/now/{location_code}\u0026#34; return requests.get(url).text def calculate(expression: str) -\u0026gt; str: \u0026#34;\u0026#34;\u0026#34;计算数学表达式\u0026#34;\u0026#34;\u0026#34; try: print(f\u0026#34;[执行计算] {expression}\u0026#34;) # 实际应用中应使用更安全的计算库 result = eval(expression) return f\u0026#34;计算结果: {expression} = {result}\u0026#34; except Exception as e: return f\u0026#34;计算错误: {str(e)}\u0026#34; # 初始化工具 tools = { \u0026#34;get_weather\u0026#34;: get_weather, \u0026#34;calculate\u0026#34;: calculate } # 工具调用 def invoke_tool(toolName: str, toolParameters) -\u0026gt; str: \u0026#34;\u0026#34;\u0026#34;工具调用\u0026#34;\u0026#34;\u0026#34; if toolName not in tools: return f\u0026#34;函数{toolName}未定义\u0026#34; return tools[toolName](**toolParameters) # 系统提示词 def get_system_prompt(): tool_strings = \u0026#34;\\n\u0026#34;.join([json.dumps(tool[\u0026#34;function\u0026#34;], ensure_ascii=False) for tool in tool_definition]) tool_names = \u0026#34;, \u0026#34;.join([tool[\u0026#34;function\u0026#34;][\u0026#34;name\u0026#34;] for tool in tool_definition]) return REACT_PROMPT_FORMAT.format(tools=tool_strings, tool_names=tool_names) # 解析提取调用工具及其参数 def parse_action(text: str) -\u0026gt; Optional[Dict[str, str]]: \u0026#34;\u0026#34;\u0026#34;解析模型输出，提取工具调用信息\u0026#34;\u0026#34;\u0026#34; try: # 查找格式为: # Action: # ``` # {\u0026#34;action\u0026#34;: \u0026#34;...\u0026#34;, \u0026#34;action_input\u0026#34;: {...}} # ``` action_pattern = re.compile(r\u0026#34;\\nAction:\\n`{3}(?:json)?\\n(.*?)`{3}.*?$\u0026#34;, re.DOTALL) action_match = action_pattern.search(text) if action_match: tool_call = json.loads(action_match.group(1)) return { \u0026#34;tool_name\u0026#34;: tool_call.get(\u0026#34;action\u0026#34;), \u0026#34;input\u0026#34;: tool_call.get(\u0026#34;action_input\u0026#34;, {}) } return None except json.JSONDecodeError as e: print(f\u0026#34;错误: 解析工具调用信息失败 - {str(e)}\u0026#34;) return None def run(query: str): # 系统信息 system_msg = {\u0026#34;role\u0026#34;: \u0026#34;system\u0026#34;, \u0026#34;content\u0026#34;: get_system_prompt()} print(f\u0026#34;\\n【系统】:\\n{system_msg[\u0026#39;content\u0026#39;]}\u0026#34;) print(f\u0026#34;\\n【用户】:\\n{query}\u0026#34;) maxIter = 5 # 最大迭代次数 agent_scratchpad = \u0026#34;\u0026#34; # 历史思考、行动、观察记录 # 获取千问模型客户端 client = initialize_model_client() for iter_seq in range(1, maxIter + 1): print(f\u0026#34;\\n\\n\\n\u0026gt;\u0026gt;\u0026gt; 迭代次数: {iter_seq}\u0026#34;) # 构建消息列表 messages = [ system_msg, {\u0026#34;role\u0026#34;: \u0026#34;user\u0026#34;, \u0026#34;content\u0026#34;: f\u0026#34;Question: {query}\\n\\nhistory：\\n{agent_scratchpad}\u0026#34;} ] # 向LLM发起请求 chat_completion = client.chat.completions.create( messages=messages, model=\u0026#34;qwen-plus-latest\u0026#34;, # model=\u0026#34;gpt-3.5-turbo\u0026#34;, # 使用早期的openai模型测试 stop=\u0026#34;Observation:\u0026#34;, timeout=30 ) content = chat_completion.choices[0].message.content if not content: print(\u0026#34;\u0026gt;\u0026gt;\u0026gt; 错误: LLM返回空内容\u0026#34;) break print(f\u0026#34;\u0026gt;\u0026gt;\u0026gt; LLM响应:\\n{content}\u0026#34;) # 检查是否有最终答案 final_answer_match = re.search(r\u0026#34;Final Answer:\\s*(.*)\u0026#34;, content, re.DOTALL) if final_answer_match: final_answer = final_answer_match.group(1) print(f\u0026#34;\\n\u0026gt;\u0026gt;\u0026gt; 最终答案: {final_answer}\u0026#34;) return # 解析提取工具调用信息 tool_call = parse_action(content) if not tool_call: result = \u0026#34;错误: 无法从响应中提取工具调用信息\u0026#34; print(f\u0026#34;\u0026gt;\u0026gt;\u0026gt; {result}\u0026#34;) # 更新思考过程 agent_scratchpad += f\u0026#34;{content}\\nObservation: {result}\\n\u0026#34; continue # 执行工具 try: tool_name = tool_call[\u0026#34;tool_name\u0026#34;] tool_parameters = tool_call[\u0026#34;input\u0026#34;] print(f\u0026#34;\u0026gt;\u0026gt;\u0026gt; 调用工具: {tool_name}\u0026#34;) print(f\u0026#34;\u0026gt;\u0026gt;\u0026gt; 工具参数: {tool_parameters}\u0026#34;) # 调用工具并获取结果 result = invoke_tool(tool_name, tool_parameters) print(f\u0026#34;\u0026gt;\u0026gt;\u0026gt; 工具返回结果: {result}\u0026#34;) # 更新思考过程 agent_scratchpad += f\u0026#34;{content}\\nObservation: {result}\\n\u0026#34; except Exception as e: print(f\u0026#34;\u0026gt;\u0026gt;\u0026gt; 错误: 工具调用失败 - {str(e)}\u0026#34;) # 更新思考过程 agent_scratchpad += f\u0026#34;{content}\\nObservation: 错误: 工具调用失败 - {str(e)}\\n\u0026#34; print(\u0026#34;\\n\u0026gt;\u0026gt;\u0026gt; 迭代次数达到上限或发生错误，无法生成最终答案\u0026#34;) if __name__ == \u0026#34;__main__\u0026#34;: query = \u0026#34;广州天气如何\u0026#34; # query = \u0026#34;计算223344557799*5599\u0026#34; run(query) 9、运行测试\r打开命令行窗口，进入代码文件的目录下（位于项目的cognitive_architecture\\react目录）。\n执行代码：\n1 python .\\react_original.py 运行结果如下：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 【用户】: 广州天气如何 \u0026gt;\u0026gt;\u0026gt; 迭代次数: 1 \u0026gt;\u0026gt;\u0026gt; LLM响应: Thought: 我需要获取广州的天气信息 Action: ``` {\u0026#34;action\u0026#34;: \u0026#34;get_weather\u0026#34;, \u0026#34;action_input\u0026#34;: {\u0026#34;location\u0026#34;: \u0026#34;广州\u0026#34;}} ``` \u0026gt;\u0026gt;\u0026gt; 调用工具: get_weather \u0026gt;\u0026gt;\u0026gt; 工具参数: {\u0026#39;location\u0026#39;: \u0026#39;广州\u0026#39;} \u0026gt;\u0026gt;\u0026gt; 工具返回结果: {\u0026#34;msg\u0026#34;:\u0026#34;success\u0026#34;,\u0026#34;code\u0026#34;:0,\u0026#34;data\u0026#34;:{\u0026#34;location\u0026#34;:{\u0026#34;id\u0026#34;:\u0026#34;59287\u0026#34;,\u0026#34;name\u0026#34;:\u0026#34;广州\u0026#34;,\u0026#34;path\u0026#34;:\u0026#34;中国, 广东, 广州\u0026#34;},\u0026#34;now\u0026#34;:{\u0026#34;precipitation\u0026#34;:0.0,\u0026#34;temperature\u0026#34;:26.4,\u0026#34;pressure\u0026#34;:995.0,\u0026#34;humidity\u0026#34;:95.0,\u0026#34;windDirection\u0026#34;:\u0026#34;东北风\u0026#34;,\u0026#34;windDirectionDegree\u0026#34;:80.0,\u0026#34;windSpeed\u0026#34;:1.1,\u0026#34;windScale\u0026#34;:\u0026#34;微风\u0026#34;,\u0026#34;feelst\u0026#34;:31.4},\u0026#34;alarm\u0026#34;:[],\u0026#34;jieQi\u0026#34;:\u0026#34;\u0026#34;,\u0026#34;lastUpdate\u0026#34;:\u0026#34;2025/08/02 20:45\u0026#34;}} \u0026gt;\u0026gt;\u0026gt; 迭代次数: 2 \u0026gt;\u0026gt;\u0026gt; LLM响应: Thought: 我现在知道了广州的天气情况。 Final Answer: 广州当前天气温度为26.4°C，体感温度31.4°C，湿度95%，气压995.0 hPa，风向为东北风，风速1.1 m/s（微风），无降水。天气较闷热，建议注意防暑降温。 \u0026gt;\u0026gt;\u0026gt; 最终答案: 广州当前天气温度为26.4°C，体感温度31.4°C，湿度95%，气压995.0 hPa，风向为东北风，风速1.1 m/s（微风），无降水。天气较闷热，建议注意防暑降温。 Process finished with exit code 0 ​\t可以看到代理经过思考并调用工具get_weather，结合工具响应结果，最终回复了用户问题广州天气如何的准确答案。\n6.5 实现方案一：基于Function Calling的ReAct模式实现\r​\t基于Function Calling的ReAct模式实现与基于提示词的实现基本类似，主要有以下两点差异：\n基于提示词实现模式\r**提示词编写：**需要在系统提示词中详细定义工具调用的格式，依赖模型理解并遵守格式要求。\n**响应工具解析：**通过自然语言提示词约束模型输出格式，要求模型在回答中用特定格式（如 ``` 包裹的 JSON）描述要调用的工具和参数。 例如，模型必须输出类似：\n1 2 3 4 { \u0026#34;action\u0026#34;: \u0026#34;get_weather\u0026#34;, \u0026#34;action_input\u0026#34;: {\u0026#34;location\u0026#34;: \u0026#34;广州\u0026#34;} } 程序需要通过正则表达式手动解析这段文本，提取工具名称和参数。\n基于Function Calling 实现模式\n**提示词编写：**无需在提示词中详细描述格式，直接在请求中指定工具定义（tool_definitions），通过tools参数传递给模型 API。模型会自动生成结构化的工具调用信息（包含在响应的tool_calls字段中），无需手动解析自然语言。 **响应工具解析：**模型的响应会包含专门的tool_calls字段，直接返回结构化的工具调用数据（如function.name和function.arguments），程序可直接使用，无需正则匹配。 基于Function Calling的ReAct模式实现的完整代码如下：\n完整代码位于项目根目录下：cognitive_pattern/react/react_functioncalling.py\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 import json import os import re from typing import Dict, List import requests import urllib.parse # 加载环境变量 from dotenv import load_dotenv from openai import OpenAI load_dotenv() def initialize_model_client() -\u0026gt; OpenAI: \u0026#34;\u0026#34;\u0026#34; 获取模型调用客户端,使用openai SDK，支持所有兼容openai接口的模型服务，默认使用千问模型 Returns: OpenAI客户端实例 \u0026#34;\u0026#34;\u0026#34; # 获取千问API密钥 qwen_api_key = os.getenv(\u0026#34;QWEN_API_KEY\u0026#34;) if not qwen_api_key: raise ValueError(f\u0026#34;缺少{qwen_api_key}环境变量\u0026#34;) # 获取千问请求端口URL qwen_base_url = os.getenv(\u0026#34;QWEN_BASE_URL\u0026#34;) client = OpenAI( # 配置请求密钥:api_key，这里使用千问模型，请用百炼API Key将下行替换。 # 如何获取API Key：https://help.aliyun.com/zh/model-studio/developer-reference/get-api-key api_key=qwen_api_key, base_url=qwen_base_url, ) return client # 工具定义 - 符合function calling格式 tool_definitions = [ { \u0026#34;type\u0026#34;: \u0026#34;function\u0026#34;, \u0026#34;function\u0026#34;: { \u0026#34;name\u0026#34;: \u0026#34;get_weather\u0026#34;, \u0026#34;description\u0026#34;: \u0026#34;获取指定地点的天气信息。参数为地点名称，例如：北京、上海\u0026#34;, \u0026#34;parameters\u0026#34;: { \u0026#34;type\u0026#34;: \u0026#34;object\u0026#34;, \u0026#34;properties\u0026#34;: { \u0026#34;location\u0026#34;: {\u0026#34;type\u0026#34;: \u0026#34;string\u0026#34;, \u0026#34;description\u0026#34;: \u0026#34;地点名称，例如：北京、上海\u0026#34;} }, \u0026#34;required\u0026#34;: [\u0026#34;location\u0026#34;], }, }, }, { \u0026#34;type\u0026#34;: \u0026#34;function\u0026#34;, \u0026#34;function\u0026#34;: { \u0026#34;name\u0026#34;: \u0026#34;calculate\u0026#34;, \u0026#34;description\u0026#34;: \u0026#34;用于数学计算，输入应为数学表达式，例如\u0026#39;3+5*2\u0026#39;或\u0026#39;(4+6)/2\u0026#39;\u0026#34;, \u0026#34;parameters\u0026#34;: { \u0026#34;type\u0026#34;: \u0026#34;object\u0026#34;, \u0026#34;properties\u0026#34;: { \u0026#34;expression\u0026#34;: {\u0026#34;type\u0026#34;: \u0026#34;string\u0026#34;, \u0026#34;description\u0026#34;: \u0026#34;数学表达式，例如：1+2*3、(5+8)/2\u0026#34;} }, \u0026#34;required\u0026#34;: [\u0026#34;expression\u0026#34;], }, }, } ] # 实现获取天气 def get_weather(location: str) -\u0026gt; str: \u0026#34;\u0026#34;\u0026#34;获取指定地点的天气信息。参数为地点名称，例如：北京、上海\u0026#34;\u0026#34;\u0026#34; url = \u0026#34;http://weather.cma.cn/api/autocomplete?q=\u0026#34; + urllib.parse.quote(location) response = requests.get(url) data = response.json() if data[\u0026#34;code\u0026#34;] != 0: return \u0026#34;没找到该位置的信息\u0026#34; location_code = \u0026#34;\u0026#34; for item in data[\u0026#34;data\u0026#34;]: str_array = item.split(\u0026#34;|\u0026#34;) if ( str_array[1] == location or str_array[1] + \u0026#34;市\u0026#34; == location or str_array[2] == location ): location_code = str_array[0] break if location_code == \u0026#34;\u0026#34;: return \u0026#34;没找到该位置的信息\u0026#34; url = f\u0026#34;http://weather.cma.cn/api/now/{location_code}\u0026#34; return requests.get(url).text def calculate(expression: str) -\u0026gt; str: \u0026#34;\u0026#34;\u0026#34;计算数学表达式\u0026#34;\u0026#34;\u0026#34; try: print(f\u0026#34;[执行计算] {expression}\u0026#34;) # 实际应用中应使用更安全的计算库 result = eval(expression) return f\u0026#34;计算结果: {expression} = {result}\u0026#34; except Exception as e: return f\u0026#34;计算错误: {str(e)}\u0026#34; # 初始化工具映射 tools = { \u0026#34;get_weather\u0026#34;: get_weather, \u0026#34;calculate\u0026#34;: calculate } # 工具调用 def invoke_tool(tool_name: str, tool_parameters) -\u0026gt; str: \u0026#34;\u0026#34;\u0026#34;工具调用\u0026#34;\u0026#34;\u0026#34; if tool_name not in tools: return f\u0026#34;函数{tool_name}未定义\u0026#34; return tools[tool_name](**tool_parameters) # React模式系统提示词 def get_system_prompt(): return \u0026#34;你需要通过思考-行动-观察的循环来回答用户问题。\u0026#34; \\ \u0026#34;可以使用提供的工具获取信息。\\n\u0026#34; \\ \u0026#34;思考过程(Thought)：分析问题，决定是否需要调用工具\\n\u0026#34; \\ \u0026#34;如果需要调用工具，使用指定的函数调用格式(Action)\\n\u0026#34; \\ \u0026#34;获取工具返回结果后(Observation)，继续思考下一步\\n\u0026#34; \\ \u0026#34;当获得足够信息后，用\u0026#39;Final Answer: \u0026#39;开头给出最终答案。\u0026#34; def run(query: str): # 系统信息 system_msg = {\u0026#34;role\u0026#34;: \u0026#34;system\u0026#34;, \u0026#34;content\u0026#34;: get_system_prompt()} print(f\u0026#34;\\n【系统】:\\n{system_msg[\u0026#39;content\u0026#39;]}\u0026#34;) print(f\u0026#34;\\n【用户】:\\n{query}\u0026#34;) max_iter = 5 # 最大迭代次数 messages: List[Dict] = [system_msg, {\u0026#34;role\u0026#34;: \u0026#34;user\u0026#34;, \u0026#34;content\u0026#34;: query}] # 记录React流程的思考过程 react_history = \u0026#34;\u0026#34; # 获取模型客户端 client = initialize_model_client() for iter_seq in range(1, max_iter + 1): print(f\u0026#34;\\n\\n\\n\u0026gt;\u0026gt;\u0026gt; 迭代次数: {iter_seq}\u0026#34;) print(f\u0026#34;\u0026gt;\u0026gt;\u0026gt; 当前React历史:\\n{react_history}\u0026#34;) # 向LLM发起请求，指定可以调用的工具 chat_completion = client.chat.completions.create( messages=messages, model=\u0026#34;qwen-plus-latest\u0026#34;, tools=tool_definitions, tool_choice=\u0026#34;auto\u0026#34;, # 让模型自动决定是否调用工具 timeout=60 ) response = chat_completion.choices[0].message content = response.content #模型响应的调用工具信息 tool_calls = response.tool_calls print(f\u0026#34;\u0026gt;\u0026gt;\u0026gt; LLM响应内容:\\n{content}\u0026#34;) # 记录思考过程 react_history += f\u0026#34;Thought: {content}\\n\u0026#34; # 检查是否有最终答案 if content and re.search(r\u0026#34;Final Answer:\\s*\u0026#34;, content, re.DOTALL): final_answer = re.sub(r\u0026#34;Final Answer:\\s*\u0026#34;, \u0026#34;\u0026#34;, content, re.DOTALL) print(f\u0026#34;\\n\u0026gt;\u0026gt;\u0026gt; 最终答案: {final_answer}\u0026#34;) return # 将模型的响应添加到消息历史中 messages.append({ \u0026#34;role\u0026#34;: \u0026#34;assistant\u0026#34;, \u0026#34;content\u0026#34;: content, \u0026#34;tool_calls\u0026#34;: tool_calls }) # 如果没有工具调用，说明模型无法回答或需要更多信息 if not tool_calls: print(\u0026#34;\u0026gt;\u0026gt;\u0026gt; 模型没有调用任何工具，也没有给出最终答案\u0026#34;) react_history += \u0026#34;Observation: 无法确定需要调用的工具\\n\u0026#34; continue # 处理工具调用 for tool_call in tool_calls: function_name = tool_call.function.name function_args = json.loads(tool_call.function.arguments) print(f\u0026#34;\u0026gt;\u0026gt;\u0026gt; 调用工具: {function_name}\u0026#34;) print(f\u0026#34;\u0026gt;\u0026gt;\u0026gt; 工具参数: {function_args}\u0026#34;) # 记录工具调用 react_history += f\u0026#34;Action: 调用工具 {function_name}，参数 {json.dumps(function_args)}\\n\u0026#34; # 调用工具并获取结果 try: result = invoke_tool(function_name, function_args) print(f\u0026#34;\u0026gt;\u0026gt;\u0026gt; 工具返回结果: {result}\u0026#34;) react_history += f\u0026#34;Observation: {result}\\n\u0026#34; # 将工具调用结果添加到消息历史中 messages.append({ \u0026#34;role\u0026#34;: \u0026#34;tool\u0026#34;, \u0026#34;tool_call_id\u0026#34;: tool_call.id, \u0026#34;name\u0026#34;: function_name, \u0026#34;content\u0026#34;: result }) except Exception as e: error_msg = f\u0026#34;工具调用失败: {str(e)}\u0026#34; print(f\u0026#34;\u0026gt;\u0026gt;\u0026gt; {error_msg}\u0026#34;) react_history += f\u0026#34;Observation: {error_msg}\\n\u0026#34; messages.append({ \u0026#34;role\u0026#34;: \u0026#34;tool\u0026#34;, \u0026#34;tool_call_id\u0026#34;: tool_call.id, \u0026#34;name\u0026#34;: function_name, \u0026#34;content\u0026#34;: error_msg }) print(\u0026#34;\\n\u0026gt;\u0026gt;\u0026gt; 迭代次数达到上限或发生错误，无法生成最终答案\u0026#34;) print(f\u0026#34;\u0026gt;\u0026gt;\u0026gt; 完整React历史:\\n{react_history}\u0026#34;) if __name__ == \u0026#34;__main__\u0026#34;: query = \u0026#34;广州天气如何\u0026#34; # query = \u0026#34;计算223344557799*5599\u0026#34; run(query) 6.6 实现方案一：基于LangGrath的ReAct模式实现\r​\tLangGraph 是 LangChain 生态系统的重要组成部分，专注于构建基于 LLM（大型语言模型）的复杂工作流与 Agent 系统。它以有向图结构为核心来定义工作流，让开发者能够轻松创建动态可控、可灵活扩展的 AI 应用。\n​\t简单来说，LangGraph 是一个框架：它底层用图（Graph）结构来规划工作流 —— 把整体任务拆分成一个个「节点（Actors）」，再用「有向边（Edges）」将这些节点串联起来。其中，节点代表具体的执行步骤，既可以封装 LLM 调用、工具函数，也能植入自定义逻辑；而边则负责定义节点之间的流转规则，比如支持根据条件判断下一步走向。通过这种设计，开发者能灵活控制流程的分支与循环，从而实现复杂的多步骤工作流。\n​\tLangGraph 关键技术组件：\n**MessagesState 状态管理机制：**作为 LangGraph 的核心状态模式，采用包含消息列表的 TypedDict 结构，负责统一存储用户输入、模型响应及工具输出。该状态在所有节点间共享，为整个工作流提供了连续且一致的 “记忆” 基础。\n**节点系统：**是任务执行的基本单元。将每个可执行单元（如 LLM 调用、工具函数或自定义逻辑）抽象为图中的独立节点。节点通过返回更新后的状态（例如新生成的消息），推动状态向前传递。\n**边连接机制：**定义节点间的转换规则，支持循环构建（如工具调用与智能体决策的往返交互）、设置条件分支（根据不同结果选择下一步路径）以及停止条件（判断流程何时终止），是实现复杂流程控制的关键。\n支持三大核心能力：构建循环流程（如工具调用与智能体决策的往返交互）、设置条件分支（根据不同结果选择下一步路径）、设定停止条件（判断流程何时终止），是实现复杂流程控制的关键。\n**ToolExecutor 工具执行器：**作为连接外部能力的桥梁，它允许在 LangGraph 中直接调用 LangChain 生态的工具，便捷地将 API 接口、计算器等外部函数集成到推理流程中，扩展了系统的实际操作能力。\n**记忆循环系统：**依托 MessagesState 携带完整对话历史，使智能体能够基于过往交互进行多跳推理（如从历史信息中推导新结论）。这种循环记忆机制是实现复杂决策和上下文连贯的技术基础。\n基于LangGrath的ReAct模式的代码实现如下：\n1、准备工作\r环境配置：\nPython 3.11+ 安装必要的库：pip install openai requests python-dotenv langchain langgraph pydantic 一个可用的大语言模型接口（这里使用的是 \u0026ldquo;qwen\u0026rdquo;，也可替换为其它模型） 配置虚拟环境（使用如conda、venv等，推荐但可选） 设置API Key：\n创建.env，配置模型的API访问密钥，这里使用千问模型，需配置QWEN_API_KEY、QWEN_BASE_URL。\n1 2 3 4 5 # 千问模型接口访问key # 如何获取API Key：https://help.aliyun.com/zh/model-studio/developer-reference/get-api-key QWEN_API_KEY=\u0026#34;sk-*******\u0026#34; # 千问模型接口访问地址 QWEN_BASE_URL=\u0026#34;https://dashscope.aliyuncs.com/compatible-mode/v1\u0026#34; 2、创建文件，导入依赖\r创建文件react_langgrath.py，导入相关依赖：\n1 2 3 4 5 6 7 8 9 10 11 12 13 import requests import urllib.parse from typing import Annotated, Literal, Optional, Dict, List, Any from langgraph.graph import StateGraph, END from langgraph.graph.message import add_messages from langgraph.prebuilt import ToolNode from openai import OpenAI from pydantic import BaseModel, Field from langchain.tools import tool from dotenv import load_dotenv # 加载环境变量 load_dotenv() 3、工具函数定义\r工具是智能体与外部交互的接口，通过@tool装饰器定义，这里包含天气查询和数学计算两个工具。\n（1）天气查询工具 get_weather\r1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 @tool(\u0026#34;get_weather\u0026#34;, return_direct=False) def get_weather(location: str) -\u0026gt; str: \u0026#34;\u0026#34;\u0026#34;获取指定地点的天气信息。参数为地点名称，例如：北京、上海\u0026#34;\u0026#34;\u0026#34; try: # 1. 调用自动补全API获取地点编码（通过气象局API） url = \u0026#34;http://weather.cma.cn/api/autocomplete?q=\u0026#34; + urllib.parse.quote(location) response = requests.get(url) data = response.json() # 2. 检查API返回状态 if data[\u0026#34;code\u0026#34;] != 0: return \u0026#34;没找到该位置的信息\u0026#34; # 3. 提取地点编码（匹配地点名称） location_code = \u0026#34;\u0026#34; for item in data[\u0026#34;data\u0026#34;]: str_array = item.split(\u0026#34;|\u0026#34;) # 匹配规则：精确匹配地点名，或地点名+市，或下级区域名 if (str_array[1] == location or str_array[1] + \u0026#34;市\u0026#34; == location or str_array[2] == location): location_code = str_array[0] break # 4. 若未找到编码，返回错误 if location_code == \u0026#34;\u0026#34;: return \u0026#34;没找到该位置的信息\u0026#34; # 5. 用地点编码调用实时天气API url = f\u0026#34;http://weather.cma.cn/api/now/{location_code}\u0026#34; return requests.get(url).text except Exception as e: return f\u0026#34;获取天气信息失败：{str(e)}\u0026#34; 功能：通过中国天气网的公开 API 获取指定地点的实时天气。\n（2）数学计算工具 calculate\r1 2 3 4 5 6 7 8 9 @tool(\u0026#34;calculate\u0026#34;, return_direct=False) def calculate(expression: str) -\u0026gt; str: \u0026#34;\u0026#34;\u0026#34;计算数学表达式的结果。参数为数学表达式，例如：1+2*3、(5+8)/2\u0026#34;\u0026#34;\u0026#34; try: # 实际应用中应使用更安全的计算库，如sympy result = eval(expression) return f\u0026#34;计算结果: {expression} = {result}\u0026#34; except Exception as e: return f\u0026#34;计算错误: {str(e)}\u0026#34; 功能：计算数学表达式（如3.14*(5^2)）。\n注意：使用eval直接执行字符串表达式，存在安全风险（可能执行恶意代码），注释中建议实际应用用sympy等安全库。\n4、状态模型定义\r状态是智能体在流转过程中保存的数据，用 Pydantic 模型定义：\n1 2 3 4 5 class State(BaseModel): # 消息列表：用add_messages注解确保消息可追加（类似对话历史） messages: Annotated[List[Any], add_messages] = Field(default_factory=list) # 流程状态标志：是否已完成处理 is_finished: bool = False messages：存储对话历史（用户提问、智能体回复、工具调用结果等），add_messages确保新消息能正确追加到列表。 is_finished：控制流程是否结束（True表示可以返回最终答案） 5、智能体节点（Agent Node）\r智能体节点是决策核心，负责判断是否需要调用工具，或直接生成答案。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 def agent_node(state: State): # 初始化LLM（注意：initialize_qwen_llm函数未在代码中定义，需用户实现） llm = initialize_qwen_llm( model=\u0026#34;qwen-plus-latest\u0026#34;, # 模型名称 temperature=0, # 温度参数（0表示确定性输出） ) # 将工具绑定到LLM，使LLM具备调用工具的能力 llm_with_tools = llm.bind_tools(tools) # 系统提示：定义智能体的行为准则 system_message = { \u0026#34;role\u0026#34;: \u0026#34;system\u0026#34;, \u0026#34;content\u0026#34;: \u0026#34;你拥有调用工具的能力，可以使用工具来回答用户的问题。\u0026#34; \u0026#34;如果需要调用工具，请使用指定的函数调用格式。\u0026#34; \u0026#34;如果已经获取了足够的信息，可以直接给出最终答案。\u0026#34; \u0026#34;请用中文回答用户的问题。\u0026#34; } # 构建完整消息列表：系统提示 + 历史对话 messages = [system_message] + state.messages # 调用LLM，获取响应 response = llm_with_tools.invoke(messages) # 判断是否需要继续：若LLM未调用工具，则直接返回结果（结束流程） if not response.tool_calls: return { \u0026#34;messages\u0026#34;: [response], # 更新消息列表 \u0026#34;is_finished\u0026#34;: True # 标记流程结束 } # 若有工具调用，继续流程 return { \u0026#34;messages\u0026#34;: [response], \u0026#34;is_finished\u0026#34;: False } 核心逻辑：LLM 根据对话历史和系统提示，决定是否调用工具（生成tool_calls）或直接回答。 llm.bind_tools(tools)：让 LLM 知晓可用工具的名称、参数和功能描述（从工具函数的文档字符串中提取）。 返回值：更新后的状态字典（messages和is_finished） 6、条件判断函数\r控制流程走向的核心，决定下一步是调用工具还是结束。\n1 2 3 4 5 6 7 8 def should_continue(state: State) -\u0026gt; Literal[\u0026#34;tools\u0026#34;, END]: if state.is_finished: return END # 若已完成，返回结束节点 # 检查最后一条消息是否有工具调用 last_message = state.messages[-1] if state.messages else None if last_message and last_message.tool_calls: return \u0026#34;tools\u0026#34; # 有工具调用，进入工具节点 return END # 无工具调用，结束流程 7、状态图构建\r1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 def build_graph(visualize: bool = False): # 创建状态图（以State为数据模型） graph = StateGraph(State) # 初始化工具节点（包装工具函数） tool_node = ToolNode(tools) # 向图中添加节点：agent（智能体节点）和tools（工具节点） graph.add_node(\u0026#34;agent\u0026#34;, agent_node) graph.add_node(\u0026#34;tools\u0026#34;, tool_node) # 设置入口节点为agent（流程从智能体开始） graph.set_entry_point(\u0026#34;agent\u0026#34;) # 添加条件边：从agent节点根据should_continue的返回值决定下一步 graph.add_conditional_edges( \u0026#34;agent\u0026#34;, should_continue, ) # 添加单向边：工具节点执行完成后，回到agent节点继续处理 graph.add_edge(\u0026#34;tools\u0026#34;, \u0026#34;agent\u0026#34;) # 编译图（生成可执行的工作流） compiled_graph = graph.compile() return compiled_graph 通过 LangGraph 的状态图 API 构建 \u0026ldquo;智能体 - 工具\u0026rdquo; 的循环工作流，支持多轮工具调用。\n核心逻辑：\n定义状态图的节点（agent 和 tools） 定义节点间的连接关系：agent→条件判断→tools/END，tools→agent 实现循环。 编译图为可执行对象。 8、运行示例\r1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 def main(): # 构建图（开启可视化） app = build_graph(visualize=True) # 测试1：天气查询 query = \u0026#34;广州天气如何\u0026#34; print(f\u0026#34;【用户查询】: {query}\u0026#34;) # 调用图，传入用户消息 final_state = app.invoke({\u0026#34;messages\u0026#34;: [{\u0026#34;role\u0026#34;: \u0026#34;user\u0026#34;, \u0026#34;content\u0026#34;: query}]}) # 输出最终结果（最后一条消息） print(f\u0026#34;\\n【最终答案】:\u0026#34;) print(final_state[\u0026#39;messages\u0026#39;][-1].content) if __name__ == \u0026#34;__main__\u0026#34;: main() 完整代码位于项目根目录下：cognitive_pattern/react/react_langgrath.py\n打开命令行窗口，进入代码文件的目录下（项目的cognitive_pattern/react目录）。\n执行代码：\n1 python .\\react_langgrath.py 运行结果如下：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 【用户查询】: 广州天气如何 【最终答案】: 广州当前天气如下： - **温度**：26.3℃ - **体感温度**：31.1℃ - **降水量**：6.8mm（有降雨，建议携带雨具） - **气压**：994.0hPa - **湿度**：95%（湿度较高，体感闷热） - **风向**：东南风 - **风速**：1.3m/s（微风） 天气较为潮湿闷热，且有降雨，外出请注意防雨和防暑。 9、流式输出\r通过流式输出机制，可观察各节点的处理结果，以上代码部分修改为：\n1 2 3 4 5 6 7 # 流式输出 for output in app.stream({\u0026#34;messages\u0026#34;: [{\u0026#34;role\u0026#34;: \u0026#34;user\u0026#34;, \u0026#34;content\u0026#34;: query}]}): for key, value in output.items(): print(f\u0026#34;输出节点： {key}\u0026#34;) print(\u0026#34;_______\u0026#34;) print(value) print(\u0026#34;\\n\u0026#34;) 输出结果如下：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 【用户查询】: 广州天气如何 节点： agent _______ {\u0026#39;messages\u0026#39;: [AIMessage(content=\u0026#39;\u0026#39;, additional_kwargs={\u0026#39;tool_calls\u0026#39;: [{\u0026#39;id\u0026#39;: \u0026#39;call_0fa38c3ba4db4be8a2767f\u0026#39;, \u0026#39;function\u0026#39;: {\u0026#39;arguments\u0026#39;: \u0026#39;{\u0026#34;location\u0026#34;: \u0026#34;广州\u0026#34;}\u0026#39;, \u0026#39;name\u0026#39;: \u0026#39;get_weather\u0026#39;}, \u0026#39;type\u0026#39;: \u0026#39;function\u0026#39;, \u0026#39;index\u0026#39;: 0}], \u0026#39;refusal\u0026#39;: None}, response_metadata={\u0026#39;token_usage\u0026#39;: {\u0026#39;completion_tokens\u0026#39;: 19, \u0026#39;prompt_tokens\u0026#39;: 283, \u0026#39;total_tokens\u0026#39;: 302, \u0026#39;completion_tokens_details\u0026#39;: None, \u0026#39;prompt_tokens_details\u0026#39;: None}, \u0026#39;model_name\u0026#39;: \u0026#39;qwen-plus-latest\u0026#39;, \u0026#39;system_fingerprint\u0026#39;: None, \u0026#39;id\u0026#39;: \u0026#39;chatcmpl-b2400c67-2ba7-93b9-a1de-47ee7927f940\u0026#39;, \u0026#39;service_tier\u0026#39;: None, \u0026#39;finish_reason\u0026#39;: \u0026#39;tool_calls\u0026#39;, \u0026#39;logprobs\u0026#39;: None}, id=\u0026#39;run--1242d7b1-f588-4e00-9f77-61d385d9f8f7-0\u0026#39;, tool_calls=[{\u0026#39;name\u0026#39;: \u0026#39;get_weather\u0026#39;, \u0026#39;args\u0026#39;: {\u0026#39;location\u0026#39;: \u0026#39;广州\u0026#39;}, \u0026#39;id\u0026#39;: \u0026#39;call_0fa38c3ba4db4be8a2767f\u0026#39;, \u0026#39;type\u0026#39;: \u0026#39;tool_call\u0026#39;}], usage_metadata={\u0026#39;input_tokens\u0026#39;: 283, \u0026#39;output_tokens\u0026#39;: 19, \u0026#39;total_tokens\u0026#39;: 302, \u0026#39;input_token_details\u0026#39;: {}, \u0026#39;output_token_details\u0026#39;: {}})], \u0026#39;is_finished\u0026#39;: False} 节点： tools _______ {\u0026#39;messages\u0026#39;: [ToolMessage(content=\u0026#39;{\u0026#34;msg\u0026#34;:\u0026#34;success\u0026#34;,\u0026#34;code\u0026#34;:0,\u0026#34;data\u0026#34;:{\u0026#34;location\u0026#34;:{\u0026#34;id\u0026#34;:\u0026#34;59287\u0026#34;,\u0026#34;name\u0026#34;:\u0026#34;广州\u0026#34;,\u0026#34;path\u0026#34;:\u0026#34;中国, 广东, 广州\u0026#34;},\u0026#34;now\u0026#34;:{\u0026#34;precipitation\u0026#34;:6.8,\u0026#34;temperature\u0026#34;:26.3,\u0026#34;pressure\u0026#34;:994.0,\u0026#34;humidity\u0026#34;:95.0,\u0026#34;windDirection\u0026#34;:\u0026#34;东北风\u0026#34;,\u0026#34;windDirectionDegree\u0026#34;:83.0,\u0026#34;windSpeed\u0026#34;:1.5,\u0026#34;windScale\u0026#34;:\u0026#34;微风\u0026#34;,\u0026#34;feelst\u0026#34;:30.9},\u0026#34;alarm\u0026#34;:[],\u0026#34;jieQi\u0026#34;:\u0026#34;\u0026#34;,\u0026#34;lastUpdate\u0026#34;:\u0026#34;2025/08/04 16:55\u0026#34;}}\u0026#39;, name=\u0026#39;get_weather\u0026#39;, id=\u0026#39;eff31ebb-fdb3-4fd4-8fe0-95935a07e983\u0026#39;, tool_call_id=\u0026#39;call_0fa38c3ba4db4be8a2767f\u0026#39;)]} 节点： agent _______ {\u0026#39;messages\u0026#39;: [AIMessage(content=\u0026#39;广州当前天气情况如下：\\n\\n- **温度**：26.3°C\\n- **体感温度**：30.9°C\\n- **降水量**：6.8 mm\\n- **气压**：994.0 hPa\\n- **湿度**：95%\\n- **风向**：东北风（风向角度83.0°）\\n- **风速**：1.5 m/s，风力等级为微风\\n\\n天气较为潮湿，体感较热，建议注意防潮和防暑。最近一次更新时间为2025年8月4日16:55。\u0026#39;, additional_kwargs={\u0026#39;refusal\u0026#39;: None}, response_metadata={\u0026#39;token_usage\u0026#39;: {\u0026#39;completion_tokens\u0026#39;: 136, \u0026#39;prompt_tokens\u0026#39;: 453, \u0026#39;total_tokens\u0026#39;: 589, \u0026#39;completion_tokens_details\u0026#39;: None, \u0026#39;prompt_tokens_details\u0026#39;: None}, \u0026#39;model_name\u0026#39;: \u0026#39;qwen-plus-latest\u0026#39;, \u0026#39;system_fingerprint\u0026#39;: None, \u0026#39;id\u0026#39;: \u0026#39;chatcmpl-c2f2d34a-e3e8-9ce2-a0d3-cbdbe283a1ea\u0026#39;, \u0026#39;service_tier\u0026#39;: None, \u0026#39;finish_reason\u0026#39;: \u0026#39;stop\u0026#39;, \u0026#39;logprobs\u0026#39;: None}, id=\u0026#39;run--5ab698bd-684a-43a2-939a-b0965de9bfb1-0\u0026#39;, usage_metadata={\u0026#39;input_tokens\u0026#39;: 453, \u0026#39;output_tokens\u0026#39;: 136, \u0026#39;total_tokens\u0026#39;: 589, \u0026#39;input_token_details\u0026#39;: {}, \u0026#39;output_token_details\u0026#39;: {}})], \u0026#39;is_finished\u0026#39;: True} Process finished with exit code 0 完整源码地址：\nGitHub 仓库：https://github.com/tinyseeking/tidy-agent-practice/tree/main/cognitive_pattern/react Gitee 仓库（国内）：https://gitee.com/tinyseeking/tidy-agent-practice/tree/main/cognitive_pattern/react ","date":"2026-01-15T00:00:00Z","image":"/p/%E5%BC%80%E6%BA%90%E6%99%BA%E8%83%BD%E4%BD%93%E6%A1%86%E6%9E%B6react-%E5%8E%9F%E7%90%86%E6%9E%B6%E6%9E%84%E4%B8%8E%E4%BB%A3%E7%A0%81%E5%AE%9E%E7%8E%B0/images/image-20250730154511612.png","permalink":"/p/%E5%BC%80%E6%BA%90%E6%99%BA%E8%83%BD%E4%BD%93%E6%A1%86%E6%9E%B6react-%E5%8E%9F%E7%90%86%E6%9E%B6%E6%9E%84%E4%B8%8E%E4%BB%A3%E7%A0%81%E5%AE%9E%E7%8E%B0/","title":"开源智能体框架——ReAct 原理、架构与代码实现"},{"content":"4 手把手搭建第一个 RAG 实战：实现本地文档智能问答\r4.1 项目概述\r​\t本文将带领你从零开始搭建基于 RAG（检索增强生成）的知识问答系统，实现文档上传、内容解析与并基于检索文档内容进行智能问答。系统支持 PDF、DOCX、TXT、MD 等多格式文档，通过向量数据库存储文档向量，结合大语言模型（LLM）生成准确回答，并具有流式输出功能提升用户体验，有效解决 LLM 的静态知识局限与 “幻觉” 问题。\n核心功能如下：\n支持上传 PDF/DOCX/TXT/MD 格式文档。 自动解析文档内容、进行向量化并存储在向量数据库中。 基于用户问题检索相关文档片段，结合检索的文档片段、上下文生成准确回答。 支持多轮对话、理解上下文语境和流式输出。 提供简洁的 Web 交互界面。 4.2 工作流程\r上图展示了基于RAG的知识库问答系统的工作流程，整体可分为文档处理（知识入库）和用户查询（知识检索与回答） 两大阶段，以下分步骤解析：\n一、文档处理阶段（知识入库）\n1. 解析提取信息：对原始文档（如 PDF、Markdown、Word、txt 格式），通过工具（如pypdf库、文档解析工具）提取文本内容，转化为字符串形式。 2. 文本分割：将提取的长字符串切割为若干 “文本块”（Chunk），确保每个文本块语义相对完整（比如按段落、固定字数分割），便于后续向量化和检索。 3. 向量化：利用Embedding 模型（一种将文本转化为数值向量的模型），把每个文本块转化为高维向量。向量的数值能够表征文本的语义信息，语义越相似的文本，向量距离越近。 4. 存储：将这些文本向量存入向量数据库（Vector Database），支持后续快速的相似度查询。 二、用户查询阶段（知识检索与回答）\n5. 用户查询向量化：用户输入查询（如 “介绍下公司的发展历程”）后，同样通过上述Embedding 模型，将查询文本转化为向量。 6. 相似度查询：查询向量数据库中，计算 “用户查询向量” 与 “已存储的文本块向量” 的相似度，筛选出TOP K 个最相关的文本块（即与用户问题语义最匹配的知识片段）。 7. 结果重排（Rerank）：对 TOP K相关的结果进一步做重排，确保最相关的内容优先被选用。 8. 组合提示词：将 “用户输入、重排后的知识片段 、上下文补充（可选）、系统提示（如 “基于提供的信息生成简洁回答”）” 整合为结构化提示词（Prompt）。 9. 提交 LLM 生成响应：将组合好的提示词输入大语言模型（LLM），LLM 基于这些信息理解用户需求，生成最终的自然语言回答（Response）。 4.3 架构设计\r系统采用模块化设计，主要包含以下组件：\n前端层：基于 Streamlit 构建，负责与用户直接交互，包括提供交互界面、支持文件上传、实现聊天交互、支持连续多轮对话、流式输出。\n服务层：核心业务逻辑，封装了 RAG 技术的核心流程：\n文档处理：对用户上传的文档进行解析、分块（将长文档拆分为短文本片段，便于后续处理）。 向量化存储：调用嵌入模型，将分块后的文本片段转换为语义向量，再批量写入向量数据库，完成知识入库。 检索增强：用户发起查询时，先通过向量数据库检索出Top K 候选文本片段，再调用重排模型对候选结果做精细化排序，筛选出最相关的内容，为 LLM 提供精准知识支撑。 上下文记忆：记录对话的上下文信息，使大语言模型能理解多轮对话的逻辑（如连续追问时，模型能基于历史对话回答）。 LLM 调用：封装对 “大语言模型（LLM）” 的调用逻辑，将 “系统提示词 + 用户当前问题 + 检索到的相关文本 + 对话上下文” 整合为提示词，调用大语言模型生成准确回答，并将结果回传至前端。 基础服务层：\n为上层业务提供核心技术能力支撑，包含三大核心组件：\n嵌入模型（Embedding model）：将自然语言文本转换为高维语义向量，是实现文本相似度检索的基础。 向量数据库（Vector Database‌）：专用于存储和快速检索向量数据的数据库，支持高效的相似性检索（如余弦相似度计算），是 RAG 流程的核心存储载体。 重排模型（Reranker Model）：对向量检索得到的 Top K 候选结果做二次精准排序，提升 “相关文本片段” 的匹配精度； 大语言模型层（LLM）：即大语言模型（如 GPT5、Qwen、 DeepSeek等），负责基于用户问题和检索到的文本，生成自然语言回答。 4.4 技术选型\r前端框架：Streamlit（快速构建交互式 Web 应用）。 文档处理：LangChain（文档加载、文本分块、记忆上下文管理）。 嵌入模型：支持 Qwen、OpenAI等第三方提供的嵌入模型，以及支持本地部署模型bge-small-zh-v1.5。 向量数据库：Chroma（轻量级嵌入式向量数据库）。 大语言模型：支持 Qwen、OpenAI、DeepSeek 等模型。 文档处理：pypdf（PDF 解析）、docx2txt（DOCX 解析） 环境管理：python-dotenv（环境变量管理）。 开发语言：Python 3.8+。 4.5 代码实现\r4.5.1 项目结构搭建\r项目完成后，目录结构如下：\n1 2 3 4 5 6 7 8 9 10 11 12 simple_rag_assistant/ ├── .env # 环境变量配置 ├── requirements.txt # 依赖列表 ├── main.py # 主程序入口（Streamlit界面） ├── models/ │ ├── models_data/ # 本地嵌入模型文件 │ ├── custom_dashscope_embedding.py # 自定义千问嵌入模型适配（解决批量处理大小限制问题） │ ├── langchain_embedding.py # 嵌入模型调用 │ └── langchain_llm.py # 大语言模型调用 │ └── reranker_model.py # 重排模型调用 └── services/ └── rag_service_stream.py # RAG核心服务 4.5.2 环境准备\r一、配置Conda 虚拟环境（安装 Conda，若未安装）\r下载地址：https://docs.conda.io/en/latest/miniconda.html（conda 轻量化，推荐） 安装流程： Windows：双击安装包，勾选 \u0026ldquo;Add Miniconda3 to PATH\u0026rdquo;（方便命令行调用） Mac/Linux：执行安装脚本，按提示完成（默认会添加环境变量） 验证安装：打开终端 / 命令提示符，输入conda --version，显示版本号则成功 二、创建并激活 Conda 虚拟环境\r打开终端 / 命令提示符，执行以下命令创建虚拟环境（Python 版本指定 3.11）：\n1 2 # 创建名为rag-env的虚拟环境(名称可随意)，指定Python 3.11 conda create -n rag-env python=3.11 过程中会提示安装依赖，输入y确认。\n激活虚拟环境：\nWindows（命令提示符）：\n1 conda activate rag-env # 若执行不了，尝试：conda.bat activate rag-env Mac/Linux：\n1 conda activate rag-env 激活成功后，终端前缀会显示(rag-env)\n（可选）配置 Conda 镜像源（加速依赖安装，国内用户推荐）：\n1 2 3 4 # 添加清华镜像源 conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free/ conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main/ config --set show_channel_urls yes 三、安装项目依赖\r创建项目文件夹并进入：\n1 2 # 创建文件夹 mkdir simple_rag_assistant \u0026amp;\u0026amp; cd simple_rag_assistant 创建requirements.txt文件，添加依赖包如下：\n1 2 3 4 5 6 7 8 9 10 11 12 streamlit==1.46.0 langchain==0.3.26 langchain-chroma==0.2.4 langchain-community==0.3.27 langchain-core==0.3.66 langchain-deepseek==0.1.3 langchain-openai==0.3.19 python-dotenv==1.1.0 pypdf==5.6.1 dashscope==1.23.5 tenacity==9.1.2 sentence-transformers==5.1.2 # HuggingFace嵌入模型依赖 用 pip 安装依赖（Conda 环境中已自带 pip，无需额外配置）：\n1 pip install -r requirements.txt 若安装缓慢，也可临时使用中科大镜像或清华 pip 镜像安装（要检查下，不确定是否可用）：\n1 2 3 4 5 6 # 1. 阿里云（稳定，国内首选） pip install -r requirements.txt -i https://mirrors.aliyun.com/pypi/simple/ # 2. 清华大学（学术镜像，包全更新快） # pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple/ # 3. 腾讯云（企业级镜像，速度稳定） # pip install -r requirements.txt -i https://mirrors.cloud.tencent.com/pypi/simple/ 四、配置环境变量\r在项目根目录创建.env文件（注意文件名前有小数点）：\n1 2 3 4 5 6 7 8 9 10 11 # Qwen（千问）模型配置（必填，默认使用） QWEN_API_KEY=你的千问API密钥 QWEN_BASE_URL=你的千问API基础地址（如https://dashscope.aliyuncs.com/compatible-mode/v1） # OpenAI模型配置（可选，如需切换使用） OPENAI_API_KEY=你的OpenAI API密钥 OPENAI_BASE_URL=你的OpenAI API基础地址 # DeepSeek模型配置（可选，如需切换使用） DEEPSEEK_API_KEY=你的DeepSeek API密钥 DEEPSEEK_BASE_URL=你的DeepSeek API基础地址 说明：\n默认使用通义千问的大语言模型qwen-plus以及向量模型text-embedding-v4。千问 API 密钥获取：登录阿里云百炼大模型平台（https://dashscope.console.aliyun.com/）申请，新用户默认有100W token的免费额度。 其它 API 密钥根据实际使用需求配置，无需使用则可留空。 4.5.3 核心模块实现\r1 自定义嵌入模型适配（models/custom_dashscope_embedding.py）\r该类实现千问嵌入模型与 LangChain 的适配，负责将文本转换为向量。\n由于在使用官方提供的类from langchain_community.embeddings import DashScopeEmbeddings进行向量化时，报错:batch size is invalid,it should not be larger than 10.: input.contents。该错误原因是langchain_community 包中的的DashScopeEmbeddings类在处理文档时，默认的批量大小超过了 DashScope API 的限制。故重写了该类，调整了默认BATCH_SIZE的大小，以解决批量请求超限问题。\n在项目目录下创建models 文件夹，并在该文件夹下创建custom_dashscope_embedding.py文件，代码如下：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 from __future__ import annotations import logging from typing import ( Any, Callable, Dict, List, Optional, ) from langchain_core.embeddings import Embeddings from langchain_core.utils import get_from_dict_or_env from pydantic import BaseModel, ConfigDict, model_validator from requests.exceptions import HTTPError from tenacity import ( before_sleep_log, retry, retry_if_exception_type, stop_after_attempt, wait_exponential, ) logger = logging.getLogger(__name__) # 不同模型的批量处理大小 BATCH_SIZE = {\u0026#34;text-embedding-v1\u0026#34;: 25, \u0026#34;text-embedding-v2\u0026#34;: 25, \u0026#34;text-embedding-v3\u0026#34;: 6, \u0026#34;text-embedding-v4\u0026#34;: 6} def _create_retry_decorator(embeddings: DashScopeEmbeddings) -\u0026gt; Callable[[Any], Any]: \u0026#34;\u0026#34;\u0026#34;创建重试装饰器，处理API调用失败的情况\u0026#34;\u0026#34;\u0026#34; multiplier = 1 min_seconds = 1 # 初始重试间隔1秒 max_seconds = 4 # 最大重试间隔4秒 # Wait 2^x * 1 second between each retry starting with # 1 seconds, then up to 4 seconds, then 4 seconds afterwards return retry( reraise=True, stop=stop_after_attempt(embeddings.max_retries), wait=wait_exponential(multiplier, min=min_seconds, max=max_seconds), retry=(retry_if_exception_type(HTTPError)), before_sleep=before_sleep_log(logger, logging.WARNING), ) def embed_with_retry(embeddings: DashScopeEmbeddings, **kwargs: Any) -\u0026gt; Any: \u0026#34;\u0026#34;\u0026#34;带重试机制的嵌入生成函数，支持批量处理\u0026#34;\u0026#34;\u0026#34; retry_decorator = _create_retry_decorator(embeddings) @retry_decorator def _embed_with_retry(**kwargs: Any) -\u0026gt; Any: result = [] i = 0 input_data = kwargs[\u0026#34;input\u0026#34;] input_len = len(input_data) if isinstance(input_data, list) else 1 batch_size = BATCH_SIZE.get(kwargs[\u0026#34;model\u0026#34;], 6) # 按模型获取批量大小 # 批量处理输入，避免单次请求超出API限制 while i \u0026lt; input_len: kwargs[\u0026#34;input\u0026#34;] = ( input_data[i: i + batch_size] if isinstance(input_data, list) else input_data ) resp = embeddings.client.call(**kwargs) # 调用嵌入API if resp.status_code == 200: result += resp.output[\u0026#34;embeddings\u0026#34;] # 提取嵌入结果 elif resp.status_code in [400, 401]: raise ValueError( f\u0026#34;status_code: {resp.status_code} \\n \u0026#34; f\u0026#34;code: {resp.code} \\n message: {resp.message}\u0026#34; ) else: raise HTTPError( f\u0026#34;HTTP error occurred: status_code: {resp.status_code} \\n \u0026#34; f\u0026#34;code: {resp.code} \\n message: {resp.message}\u0026#34;, response=resp, ) i += batch_size return result return _embed_with_retry(**kwargs) class DashScopeEmbeddings(BaseModel, Embeddings): \u0026#34;\u0026#34;\u0026#34; DashScope嵌入模型封装类，适配LangChain接口。使用该模型前，您需要先安装 dashscope Python 包，并且： 1.将您的 API 密钥配置到环境变量 DASHSCOPE_API_KEY 中； 2.或者，在构造函数中以命名参数的形式传入 API 密钥。 \u0026#34;\u0026#34;\u0026#34; client: Any = None # 千问嵌入API客户端 \u0026#34;\u0026#34;\u0026#34;The DashScope client.\u0026#34;\u0026#34;\u0026#34; model: str = \u0026#34;text-embedding-v1\u0026#34; # 默认使用的千问嵌入模型 dashscope_api_key: Optional[str] = None max_retries: int = 5 # API调用最大重试次数 \u0026#34;\u0026#34;\u0026#34;Maximum number of retries to make when generating.\u0026#34;\u0026#34;\u0026#34; model_config = ConfigDict( extra=\u0026#34;forbid\u0026#34;, ) @model_validator(mode=\u0026#34;before\u0026#34;) @classmethod def validate_environment(cls, values: Dict) -\u0026gt; Any: \u0026#34;\u0026#34;\u0026#34;验证环境配置，初始化千问客户端\u0026#34;\u0026#34;\u0026#34; import dashscope # 从环境变量或参数中获取API密钥 values[\u0026#34;dashscope_api_key\u0026#34;] = get_from_dict_or_env( values, \u0026#34;dashscope_api_key\u0026#34;, \u0026#34;DASHSCOPE_API_KEY\u0026#34; ) dashscope.api_key = values[\u0026#34;dashscope_api_key\u0026#34;] try: import dashscope # 初始化千问文本嵌入客户端 values[\u0026#34;client\u0026#34;] = dashscope.TextEmbedding except ImportError: raise ImportError( \u0026#34;Could not import dashscope python package. \u0026#34; \u0026#34;Please install it with `pip install dashscope`.\u0026#34; ) return values def embed_documents(self, texts: List[str]) -\u0026gt; List[List[float]]: \u0026#34;\u0026#34;\u0026#34; 为文档文本生成嵌入向量（批量处理） Args: texts: The list of texts to embed. Returns: List of embeddings, one for each text. \u0026#34;\u0026#34;\u0026#34; embeddings = embed_with_retry( self, input=texts, text_type=\u0026#34;document\u0026#34;, model=self.model ) embedding_list = [item[\u0026#34;embedding\u0026#34;] for item in embeddings] return embedding_list def embed_query(self, text: str) -\u0026gt; List[float]: \u0026#34;\u0026#34;\u0026#34;为查询文本生成嵌入向量（单个文本）. Args: text: The text to embed. Returns: Embedding for the text. \u0026#34;\u0026#34;\u0026#34; embedding = embed_with_retry( self, input=text, text_type=\u0026#34;query\u0026#34;, model=self.model )[0][\u0026#34;embedding\u0026#34;] return embedding 2 嵌入模型调用（models/langchain_embedding.py）\r该文件统一初始化不同来源的文本嵌入模型。支持千问（Qwen）、OpenAI 和本地 BGE 模型，方便开发者根据需求切换，无需修改核心逻辑。\n在models文件夹下创建文件langchain_embedding.py，代码如下：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 # 导入必要的Python库 import os # 用于处理操作系统相关的功能 from dotenv import load_dotenv from langchain_openai import OpenAIEmbeddings from langchain_huggingface import HuggingFaceEmbeddings from models.custom_dashscope_embedding import DashScopeEmbeddings # 加载环境变量 load_dotenv() def initialize_embedding_model(provider: str = \u0026#34;qwen\u0026#34;): \u0026#34;\u0026#34;\u0026#34; 初始化并返回指定提供商的嵌入模型 参数: provider (str): 嵌入模型提供商,支持\u0026#34;openai\u0026#34;、\u0026#34;qwen\u0026#34;、\u0026#34;local_bge_small“,默认为qwen 返回: embeddings: 初始化后的嵌入模型实例 \u0026#34;\u0026#34;\u0026#34; # 加载环境变量 if provider.lower() == \u0026#34;openai\u0026#34;: # 使用OpenAI的嵌入模型 api_key = os.getenv(\u0026#34;OPENAI_API_KEY\u0026#34;) if not api_key: raise ValueError(\u0026#34;缺少OPENAI_API_KEY环境变量\u0026#34;) base_url = os.getenv(\u0026#34;OPENAI_BASE_URL\u0026#34;) if base_url: return OpenAIEmbeddings( openai_api_key=api_key, base_url=base_url, model=\u0026#34;text-embedding-ada-002\u0026#34; ) else: return OpenAIEmbeddings( openai_api_key=api_key, model=\u0026#34;text-embedding-ada-002\u0026#34; ) elif provider.lower() == \u0026#34;qwen\u0026#34;: # 使用千问的嵌入模型 api_key = os.getenv(\u0026#34;QWEN_API_KEY\u0026#34;) if not api_key: raise ValueError(\u0026#34;缺少QWEN_API_KEY环境变量\u0026#34;) base_url = os.getenv(\u0026#34;QWEN_BASE_URL\u0026#34;) if not base_url: raise ValueError(\u0026#34;缺少QWEN_BASE_URL环境变量\u0026#34;) return DashScopeEmbeddings( dashscope_api_key=api_key, model=\u0026#34;text-embedding-v4\u0026#34;, # 千问嵌入模型名称，根据实际情况调整 ) elif provider.lower() == \u0026#34;local_bge_small\u0026#34;: # BGE-small-zh-v1.5是北京智源研究院（BAAI）开发的轻量级中文文本嵌入模型，支持将文本转换为高维向量，适用于检索、分类、聚类等任务，且对资源受限场景友好。 # 手动下载向量模型，指定本地文件夹路径，若 SDK 自动下载，直接用模型名。 model_path = \u0026#34;./models_data/bge-small-zh-v1.5\u0026#34; # 手动下载的本地路径 # 或 model_path = \u0026#34;BAAI/bge-small-zh-v1.5\u0026#34;（直接用模型名，首次使用时，SDK 自动下载模型文件） return HuggingFaceEmbeddings( model_name=model_path, model_kwargs={\u0026#39;device\u0026#39;: \u0026#39;cpu\u0026#39;}, # 可指定 \u0026#39;cuda\u0026#39; 启用 GPU 加速 encode_kwargs={\u0026#39;normalize_embeddings\u0026#39;: True} # 是否对输出向量归一化（推荐用于相似度计算） ) else: raise ValueError(f\u0026#34;不支持的嵌入模型提供商: {provider}。请选择\u0026#39;openai\u0026#39;、\u0026#39;huggingface\u0026#39;或\u0026#39;qwen\u0026#39;\u0026#34;) 提供了 3 种嵌入模型，只需选择其中一直即可，每种模型的初始化逻辑如下：\n**qwen（千问）：**默认，这里使用的是千问向量模型text-embedding-v4。必须配置QWEN_API_KEY和QWEN_BASE_URL两个环境变量。\n**openai：**需在环境变量中配置OPENAI_API_KEY，若使用第三方代理还需配置OPENAI_BASE_URL（自定义接口地址）。\n**local_bge_small：**本地加载向量模型，不访问第三方提供的模型服务。这里使用的是北京智源研究院（BAAI）开发的轻量级中文文本嵌入模型bge-small-zh-v1.5，对资源要求不高，适合资源受限场景。\n若使用本地加载向量模型，执行以下步骤：\n首先安装必要的 Python 库。\n1 pip install transformers sentence-transformers torch --upgrade 下载向量模型文件，两种方式下载：\n手动下载：可从HuggingFace 或 魔塔地址下载模型文件，放到当前目录的./models_data/bge-small-zh-v1.5路径下。下载文件：config.json（模型配置）、model.safetensors（模型权重）、tokenizer.json、tokenizer_config.json、vocab.txt（分词器文件）。设置model_path 为下载的本地路径地址。\n自动下载：设置model_path为\u0026quot;BAAI/bge-small-zh-v1.5\u0026quot;，首次运行时 SDK 会自动下载（需联网）。下载默认存储路径如下：\nWindows：``C:\\Users\\用户名.cache\\huggingface\\transformers）\nLinux/Mac：~/.cache/huggingface/transformers\n下载完成后，后续运行代码会直接从缓存加载，无需重复下载。\n加载模型：通过 LangChain 库中HuggingFaceEmbeddings加载。\n配置为 CPU 运行（可改cuda用 GPU）；\n对输出向量归一化（方便后续相似度计算）。\n测试嵌入模型\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 def test_embedding_model(provider: str = \u0026#34;openai\u0026#34;): \u0026#34;\u0026#34;\u0026#34;测试嵌入模型的基本功能\u0026#34;\u0026#34;\u0026#34; try: # 初始化嵌入模型 embeddings = initialize_embedding_model(provider) # 测试文本 test_text = \u0026#34;这是一个测试文本，用于验证嵌入模型的功能。\u0026#34; # 生成嵌入向量 vector = embeddings.embed_query(test_text) # 打印基本信息 print(f\u0026#34;使用 {provider.upper()} 嵌入模型生成向量成功\u0026#34;) print(f\u0026#34;向量维度: {len(vector)}\u0026#34;) print(f\u0026#34;向量前10个元素: {vector[:10]}\u0026#34;) return vector except Exception as e: print(f\u0026#34;测试失败: {str(e)}\u0026#34;) return None if __name__ == \u0026#34;__main__\u0026#34;: # 测试千问模型 print(\u0026#34;\\n=== 测试千问嵌入模型 ===\u0026#34;) test_embedding_model(\u0026#34;qwen\u0026#34;) # 测试OpenAI模型 print(\u0026#34;=== 测试bge-small-zh-v1.5嵌入模型 ===\u0026#34;) test_embedding_model(\u0026#34;local_bge_small\u0026#34;) 运行结果如下：\n4 LLM模型调用（langchain_llm.py）\r​\t基于 LangChain 框架，封装LLM的调用，实现从环境变量读取配置、校验参数，返回模型聊天实例ChatOpenAI。可适配兼容openAI 接口规范的模型服务，如千问(Qwen)、DeepSeek、OpenAI、智谱(Zhipu)等，可配置MODEL_CONFIG_MAP灵活扩展，默认使用千问模型。\n在models文件夹下创建文件langchain_llm.py，代码如下：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 # 导入必要的Python库 import os # 用于处理操作系统相关的功能 from typing import Dict, Optional from dotenv import load_dotenv from langchain.chat_models import init_chat_model from langchain_core.language_models import BaseChatModel from langchain_openai import ChatOpenAI # 加载环境变量 load_dotenv() # 模型配置常量（集中管理，便于维护） MODEL_CONFIG_MAP: Dict[str, Dict[str, str]] = { \u0026#34;qwen\u0026#34;: { \u0026#34;api_key_env\u0026#34;: \u0026#34;QWEN_API_KEY\u0026#34;, \u0026#34;base_url_env\u0026#34;: \u0026#34;QWEN_BASE_URL\u0026#34;, \u0026#34;default_model\u0026#34;: \u0026#34;qwen-plus\u0026#34; }, \u0026#34;deepseek\u0026#34;: { \u0026#34;api_key_env\u0026#34;: \u0026#34;DEEPSEEK_API_KEY\u0026#34;, \u0026#34;base_url_env\u0026#34;: \u0026#34;DEEPSEEK_BASE_URL\u0026#34;, \u0026#34;default_model\u0026#34;: \u0026#34;deepseek-chat\u0026#34; }, \u0026#34;openai\u0026#34;: { \u0026#34;api_key_env\u0026#34;: \u0026#34;OPENAI_API_KEY\u0026#34;, \u0026#34;base_url_env\u0026#34;: \u0026#34;OPENAI_BASE_URL\u0026#34;, \u0026#34;default_model\u0026#34;: \u0026#34;gpt-4-turbo\u0026#34; }, \u0026#34;zhipu\u0026#34;: { \u0026#34;api_key_env\u0026#34;: \u0026#34;ZHIPU_API_KEY\u0026#34;, \u0026#34;base_url_env\u0026#34;: \u0026#34;ZHIPU_BASE_URL\u0026#34;, \u0026#34;default_model\u0026#34;: \u0026#34;glm-4\u0026#34; } } def _get_env_var(env_name: str, model_type: str) -\u0026gt; str: \u0026#34;\u0026#34;\u0026#34; 安全获取并校验环境变量，提供错误提示。 Args: env_name: 待读取的环境变量名称（如QWEN_API_KEY） model_type: 模型类型标识（如qwen/deepseek），用于错误提示上下文 Returns: str: 非空的环境变量值 Raises: ValueError: 当环境变量未设置或值为空时抛出，包含明确的配置指引 \u0026#34;\u0026#34;\u0026#34; value = os.getenv(env_name) if not value: raise ValueError( f\u0026#34;[{model_type.upper()}] 缺少必要的环境变量：{env_name}\\n\u0026#34; f\u0026#34;请在.env文件中配置 {env_name}=你的API密钥/基础地址\u0026#34; ) return value def langchain_llm( model_type: str = \u0026#34;qwen\u0026#34;, model: Optional[str] = None, temperature: float = 0.0, **kwargs ) -\u0026gt; BaseChatModel: \u0026#34;\u0026#34;\u0026#34; 统一的LLM模型初始化入口函数，适配所有兼容OpenAI接口规范的模型。 已支持模型：qwen（通义千问）/deepseek(深度求索)/openai(OpenAI官方)/zhipu(智谱GLM模型)，可配置MODEL_CONFIG_MAP扩展。 Args: model_type: 模型类型标识，支持：qwen/deepseek/openai/zhipu model: 具模型名称，不传则使用默认值 temperature: 生成温度系数，控制输出随机性（0=完全确定，1=高度随机），默认0.0 **kwargs: 透传参数，会传递给底层的init_chat_model/ChatOpenAI初始化方法 支持的参数示例：max_tokens(生成最大长度)、timeout(超时时间)、top_p(采样阈值)等 Returns: BaseChatModel: 初始化完成的LangChain聊天模型实例，可直接用于对话生成 \u0026#34;\u0026#34;\u0026#34; # 校验模型类型是否支持 if model_type not in MODEL_CONFIG_MAP: raise ValueError( f\u0026#34;不支持的模型类型：{model_type}\\n\u0026#34; f\u0026#34;当前支持的类型：{list(MODEL_CONFIG_MAP.keys())}\u0026#34; ) # 获取模型配置 config = MODEL_CONFIG_MAP[model_type] model = model or config[\u0026#34;default_model\u0026#34;] # 获取环境变量 api_key = _get_env_var(config[\u0026#34;api_key_env\u0026#34;], model_type) base_url = _get_env_var(config[\u0026#34;base_url_env\u0026#34;], model_type) # 根据模型类型初始化 if model_type == \u0026#34;deepseek\u0026#34;: # DeepSeek使用init_chat_model初始化 llm = init_chat_model( model=model, api_key=api_key, api_base=base_url, temperature=temperature, model_provider=\u0026#34;deepseek\u0026#34;, **kwargs ) else: # 其他模型使用ChatOpenAI（兼容OpenAI接口） llm = ChatOpenAI( model=model, api_key=api_key, openai_api_base=base_url, temperature=temperature, **kwargs ) return llm def langchain_qwen_llm(model: str = \u0026#34;qwen-plus\u0026#34;, temperature: float = 0.0) -\u0026gt; BaseChatModel: \u0026#34;\u0026#34;\u0026#34;初始化千问聊天模型\u0026#34;\u0026#34;\u0026#34; return langchain_llm(\u0026#34;qwen\u0026#34;, model=model, temperature=temperature) def langchain_deepseek_llm(model: str = \u0026#34;deepseek-chat\u0026#34;, temperature: float = 0.0) -\u0026gt; BaseChatModel: \u0026#34;\u0026#34;\u0026#34;初始化DeepSeek聊天模型\u0026#34;\u0026#34;\u0026#34; return langchain_llm(\u0026#34;deepseek\u0026#34;, model=model, temperature=temperature) def langchain_openai_llm(model: str = \u0026#34;gpt-4-turbo\u0026#34;, temperature: float = 0.0) -\u0026gt; BaseChatModel: \u0026#34;\u0026#34;\u0026#34;初始化OpenAI聊天模型\u0026#34;\u0026#34;\u0026#34; return langchain_llm(\u0026#34;openai\u0026#34;, model=model, temperature=temperature) # 新增智谱初始化函数（扩展支持） def langchain_zhipu_llm(model: str = \u0026#34;glm-4\u0026#34;, temperature: float = 0.0) -\u0026gt; BaseChatModel: \u0026#34;\u0026#34;\u0026#34;初始化智谱聊天模型\u0026#34;\u0026#34;\u0026#34; return langchain_llm(\u0026#34;zhipu\u0026#34;, model=model, temperature=temperature) 测试LLM模型\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 if __name__ == \u0026#34;__main__\u0026#34;: print(\u0026#34;=\u0026#34; * 50) print(\u0026#34;开始测试模型初始化与调用\u0026#34;) print(\u0026#34;=\u0026#34; * 50 + \u0026#34;\\n\u0026#34;) user_query = \u0026#34;请用3句话介绍下自己\u0026#34; print(f\u0026#34;用户提问：{user_query}\u0026#34;) # 1. 测试千问模型 print(\u0026#34;【测试千问模型...】\u0026#34;) qwen_llm = initialize_qwen_llm(model=\u0026#34;qwen-plus\u0026#34;, temperature=0) response = qwen_llm.invoke(user_query) print(f\u0026#34;千问响应：\\n{response.content}\\n\u0026#34;) # 2. 测试DeepSeek模型 print(\u0026#34;【测试 DeepSeek 模型...】\u0026#34;) deepseek_llm = initialize_deepseek_llm(\u0026#34;deepseek-chat\u0026#34;, temperature=0) response = deepseek_llm.invoke(user_query) # 美化输出响应 print(f\u0026#34;DeepSeek响应：\\n{response.content}\\n\u0026#34;) # 3. 测试OpenAI模型（保留注释，需启用时取消注释即可） # print(\u0026#34;【测试 OpenAI 模型...】\u0026#34;) # 取消下面2行注释即可启用OpenAI测试 # openai_llm = initialize_openai_llm(model=\u0026#34;gpt-5\u0026#34;, temperature=0.3) # print(\u0026#34;ℹ️ 若需测试OpenAI，取消代码中OpenAI相关的注释即可\\n\u0026#34;) 运行结果如下：\n5 重排模型调用（reranker_model.py）\r初始化重排模型，用于对检索阶段召回的候选文档进行语义相关性重排，提升检索精度。\n在models文件夹下创建文件reranker_model.py，代码如下：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 import logging from typing import List, Optional import torch from langchain_core.documents import Document from sentence_transformers import CrossEncoder # 配置日志 logging.basicConfig( level=logging.INFO, format=\u0026#34;%(asctime)s - %(name)s - %(levelname)s - %(message)s\u0026#34; ) logger = logging.getLogger(__name__) class RerankerCrossModel: def __init__( self, model_name_or_path: str = \u0026#34;BAAI/bge-reranker-large\u0026#34;, device: Optional[str] = None, batch_size: int = 16 ): \u0026#34;\u0026#34;\u0026#34; 初始化重排器。用于对检索阶段召回的候选文档进行语义相关性重排，提升检索精度。 适配遵循 sentence-transformers 的 CrossEncoder 规范的模型, 如ms-marco-MiniLM-L-12-v2、bge-reranker-base/large等 Args: model_name_or_path: str，模型名称（HuggingFace Hub规范名）或本地存储路径： - 1.模型名称：本地缓存（默认~/.cache/huggingface/）无该模型时，自动从Hub下载权重/配置/分词器；缓存已存在则直接加载，无需重复下载。 - 2.本地路径：需手动下载完整模型文件（包含config.json、model.safetensors/pytorch_model.bin等）到本地路径地址。 device: 模型运行设备，None则自动检测（优先使用CUDA，无则使用CPU） batch_size: 推理批次大小，建议CPU设8/16，GPU可根据显存适当增大（默认16） \u0026#34;\u0026#34;\u0026#34; # 设备自动适配 self.device = device if device else (\u0026#34;cuda\u0026#34; if torch.cuda.is_available() else \u0026#34;cpu\u0026#34;) self.batch_size = batch_size self.model_name_or_path = model_name_or_path self.reranker_model: Optional[CrossEncoder] = None # 初始化模型 self._load_model() def _load_model(self) -\u0026gt; None: \u0026#34;\u0026#34;\u0026#34;加载并重初始化CrossEncoder重排模型\u0026#34;\u0026#34;\u0026#34; try: self.reranker_model = CrossEncoder(self.model_name_or_path, device=self.device) logger.info(f\u0026#34;✅重排模型加载完成 | 设备：{self.device} | 批次大小：{self.batch_size}\u0026#34;) except Exception as e: raise RuntimeError(f\u0026#34;模型加载失败：{e}\\n请检查：1. 模型路径是否正确 2. 网络是否正常（首次下载需联网）\u0026#34;) def rerank_documents( self, query: str, documents: List[Document], top_n: int = 3, score_threshold: float = 0.0 ) -\u0026gt; List[Document]: \u0026#34;\u0026#34;\u0026#34; 对检索到的文档进行重排序 Args: query: 用户查询问题 documents: 向量检索得到的原始文档列表 top_n: 重排后保留的文档数量 score_threshold: 分数阈值，低于该值的文档会被过滤 Returns: List[Document]: 重排序后的文档列表（按相关性从高到低） \u0026#34;\u0026#34;\u0026#34; if not documents: return [] # 构造模型输入：(query, doc_text) 对 pairs = [(query, doc.page_content) for doc in documents] # 计算相关性分数 scores = self.reranker_model.predict(pairs) # 将文档与分数配对并排序 doc_score_pairs = list(zip(documents, scores)) # 按分数降序排序 doc_score_pairs.sort(key=lambda x: x[1], reverse=True) # 过滤分数阈值并截取top_n filtered_docs = [] for doc, score in doc_score_pairs: if score \u0026gt;= score_threshold and len(filtered_docs) \u0026lt; top_n: # 将分数添加到文档元数据中 doc.metadata[\u0026#34;rerank_score\u0026#34;] = float(score) filtered_docs.append(doc) elif len(filtered_docs) \u0026gt;= top_n: break logger.info(f\u0026#34;重排完成：原始{len(documents)}个文档 → 筛选后{len(filtered_docs)}个文档\u0026#34;) return filtered_docs 测试重排模型\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 if __name__ == \u0026#34;__main__\u0026#34;: # 测试数据 sample_documents = [ Document(page_content=\u0026#34;文档1内容：人工智能入门\u0026#34;, metadata={\u0026#34;id\u0026#34;: 1}), Document(page_content=\u0026#34;文档2内容：大语言模型原理\u0026#34;, metadata={\u0026#34;id\u0026#34;: 2}), Document(page_content=\u0026#34;文档3内容：Python 基础教程\u0026#34;, metadata={\u0026#34;id\u0026#34;: 3}), Document(page_content=\u0026#34;文档4内容：语义检索算法\u0026#34;, metadata={\u0026#34;id\u0026#34;: 4}), Document(page_content=\u0026#34;文档5内容：机器学习实战\u0026#34;, metadata={\u0026#34;id\u0026#34;: 5}), ] # 使用BAAI/bge-reranker-large模型 reranker = RerankerCrossModel( # 这里加载本地路径模型（需手动下载模型文件到指定路径） # a.模型文件获取地址：https://modelscope.cn/models/BAAI/bge-reranker-v2-m3 # b.需下载文件：config.json、model.safetensors、special_tokens_map.json、tokenizer.json、tokenizer_config.json # model_name_or_path=\u0026#34;BAAI/bge-reranker-base\u0026#34;, # 模型名称 model_name_or_path=\u0026#34;../../../data/models_reranker_data/BAAI/bge-reranker-v2-m3\u0026#34;, # 模型名称 device=\u0026#34;cuda\u0026#34; if torch.cuda.is_available() else \u0026#34;cpu\u0026#34;, batch_size=8 ) query = \u0026#34;大语言模型的语义检索方法\u0026#34; result_docs = reranker.rerank_documents( query=query, documents=sample_documents, top_n=3 ) # 打印最终结果（新增：输出重排后的详细信息） print(\u0026#34;\\n========== 重排结果详情 ==========\u0026#34;) for i, doc in enumerate(result_docs, 1): print(f\u0026#34;\\n第{i}名文档：\u0026#34;) print(f\u0026#34;文档ID：{doc.metadata[\u0026#39;id\u0026#39;]} 重排分数：{doc.metadata[\u0026#39;rerank_score\u0026#39;]:.4f} 文档内容：{doc.page_content}\u0026#34;) 执行结果如下：\n6 RAG 核心服务（rag_service_stream.py）\r​\t该类实现完整检索增强生成（RAG）的核心逻辑：文档处理、向量存储、检索和回答生成。基于 LangChain 构建，核心目标是让大语言模型（LLM）结合上传的文档知识进行问答，解决纯 LLM 可能存在的事实性错误、知识时效性等问题。\n​\t在项目目录下创建services文件夹，在该文件夹下创建文件rag_service_stream.py。\n一、引入依赖\r1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import logging import os import tempfile from typing import List, Dict, Optional, Any, Generator from langchain.text_splitter import RecursiveCharacterTextSplitter from langchain_community.document_loaders import TextLoader, PyPDFLoader, Docx2txtLoader from langchain_chroma import Chroma from langchain.memory import ConversationBufferWindowMemory from langchain.schema import HumanMessage from sentence_transformers import CrossEncoder from models.langchain_embedding import initialize_embedding_model from models.langchain_llm import langchain_qwen_llm from models.reranker_model import RerankerCrossModel 二、创建RAGService类并初始化\r1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 class RAGService: \u0026#34;\u0026#34;\u0026#34; RAG（检索增强生成）服务类，实现文档解析、向量化存储及基于检索的知识进行问答，辅助 LLM 生成更准确、有依据的回答。 核心流程：文档上传→解析分块→向量化存储→检索相关片段→LLM生成答案。 支持流式输出。 \u0026#34;\u0026#34;\u0026#34; def __init__(self, persist_directory: str = \u0026#34;chroma_db\u0026#34;, retrieve_k: int = 8, # 检索 top-k 个相关文本块 enable_reranker: bool = True, # 是否开启重排 model_name_or_path: str = \u0026#34;BAAI/bge-reranker-large\u0026#34;, # 重排模型名称（HuggingFace Hub规范名）或本地存储路径 rerank_top_n: int = 4, # 重排后保留数量,必须小于retrieve_k rerank_score_threshold: float = 0.1 # 重排分数阈值，大于该阈值才被选取 ): \u0026#34;\u0026#34;\u0026#34; 初始化RAG服务，加载嵌入模型、LLM模型及已存在的向量数据库。 Args: persist_directory: 向量数据库持久化存储路径，默认值为\u0026#34;chroma_db\u0026#34; model_name_or_path: 可选，重排模型名称（HuggingFace Hub规范名）或本地存储路径，默认使用BAAI的bge-reranker-v2-m3（中文性价比较高） - 1.模型名称：本地缓存（默认~/.cache/huggingface/）无该模型时，自动从Hub下载权重/配置/分词器；缓存已存在则直接加载，无需重复下载。 - 2.本地路径：需手动下载完整模型文件（包含config.json、model.safetensors/pytorch_model.bin等）到本地路径地址。 retrieve_k: 可选，向量检索阶段从数据库中召回的候选文本块数量，默认值10。 rerank_top_n: 可选，从召回的候选文本块中重排筛选后，最终保留的高相关文本块数量，默认值3。约束：必须满足 rerank_top_n \u0026lt; retrieve_k。 rerank_score_threshold:可选，重排结果的分数筛选阈值，默认值0.1。仅得分超过该阈值的文本块会被保留。 \u0026#34;\u0026#34;\u0026#34; # 向量数据库持久化目录 self.persist_directory = persist_directory # 初始化嵌入模型（用于将文本转换为向量） self.embeddings = initialize_embedding_model(\u0026#34;qwen\u0026#34;) # 检索 top-k 个相关文本块 self.retrieve_k = retrieve_k, # 初始化向量数据库（若存在） self.vectordb = self._load_vector_db() # 初始化大语言模型（用于生成答案） self.llm = langchain_qwen_llm() # 是否开启重排 self.enable_reranker = enable_reranker # 初始化重排模型 self.reranker_model = self._init_rerank_model(model_name_or_path) # 重排后保留数量,必须小于k self.rerank_top_n = rerank_top_n # 重排分数阈值，大于该阈值才被选取 self.rerank_score_threshold = rerank_score_threshold # 保存当前流式回答，用于完整存储 self.current_stream_answer = \u0026#34;\u0026#34; # 初始化内存，设置窗口大小 k=50（只保留最近100轮对话） # ConversationBufferWindowMemory 是 ConversationBufferMemory 的扩展版本，专门用于解决长对话场景下的 # 上下文管理问题。它通过只保留最近的 N 轮对话（滑动窗口机制），在维持对话连贯性的同时，避免历史记录过长导致的 Token 超限问题。 self.memory = ConversationBufferWindowMemory( k=50, # 窗口大小：仅保留最近50轮对话（1轮=1次用户+1次助手交互） return_messages=True, # 返回LangChain标准Message对象（而非纯字符串，便于格式统一） memory_key=\u0026#34;chat_history\u0026#34;, # 记忆数据的存储键（后续提取历史时使用） output_key=\u0026#34;answer\u0026#34;, # 与LLM输出结果的键对齐（适配链式调用规范） input_key=\u0026#34;input\u0026#34; # 与LLM输出结果的键对齐（适配链式调用规范） ) def _load_vector_db(self) -\u0026gt; Optional[Chroma]: \u0026#34;\u0026#34;\u0026#34; 私有方法：加载已持久化的向量数据库（若目录存在且非空）。 向量数据库用于存储文档片段的向量表示，支持高效的相似性检索。 Returns: 加载成功的Chroma向量数据库实例；若不存在或加载失败，返回None Raises: RuntimeError: 数据库加载过程中发生错误时抛出异常 \u0026#34;\u0026#34;\u0026#34; # 路径不存在时自动创建（支持多级目录） if not os.path.exists(self.persist_directory): try: os.makedirs(self.persist_directory, exist_ok=True) except Exception as e: error_msg = f\u0026#34;创建Chroma数据库路径失败：{self.persist_directory}，错误：{str(e)}\u0026#34; raise RuntimeError(error_msg) from e # 检查持久化目录是否存在且非空 try: return Chroma( embedding_function=self.embeddings, persist_directory=self.persist_directory, ) except Exception as e: raise RuntimeError(f\u0026#34;向量数据库加载失败（路径：{self.persist_directory}）：{str(e)}\u0026#34;) # ===================== 重排模型初始化 ===================== @staticmethod def _init_rerank_model(model_name_or_path: str = \u0026#34;BAAI/bge-reranker-large\u0026#34;) -\u0026gt; RerankerCrossModel | None: \u0026#34;\u0026#34;\u0026#34; 初始化重排模型，用于对检索结果进行语义重排序.默认使用BAAI的bge-reranker-large。 Args: model_name_or_path: 重排模型名称（HuggingFace Hub规范名）或本地存储路径， Returns: CrossEncoder: 初始化后的重排模型实例 \u0026#34;\u0026#34;\u0026#34; try: # 加载重排模型 rerank_model = RerankerCrossModel(model_name_or_path) logger.info(f\u0026#34;成功加载重排模型: {model_name_or_path}\u0026#34;) return rerank_model except Exception as e: logger.info(f\u0026#34;加载重排模型失败: {str(e)}\u0026#34;) return None 三、文档处理：process_document(file)\r处理用户上传的文档，解析、分块、向量化、并存储到向量数据库。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 def process_document(self, file: Any) -\u0026gt; Dict[str, bool | str]: \u0026#34;\u0026#34;\u0026#34; 处理用户上传的文档（解析、分块、向量化、存储到向量数据库）。 支持的格式：PDF、DOCX、TXT、MD（可通过扩展loader支持更多格式）。 \u0026#34;\u0026#34;\u0026#34; # -------------------------- （1）文件有效性校验与临时文件创建 ------------------------ # 验证文件对象有效性 if not file or not hasattr(file, \u0026#39;name\u0026#39;) or not hasattr(file, \u0026#39;getvalue\u0026#39;): return {\u0026#34;success\u0026#34;: False, \u0026#34;message\u0026#34;: \u0026#34;无效的文件对象\u0026#34;} # 提取并标准化文件后缀（转为小写，便于格式判断） file_name = file.name file_suffix = file_name.split(\u0026#39;.\u0026#39;)[-1].lower() if \u0026#39;.\u0026#39; in file_name else \u0026#39;\u0026#39; tmp_file_path = None # 临时文件路径（用于后续清理） try: # 创建临时文件存储上传的文件内容（避免直接操作内存中的二进制数据） with tempfile.NamedTemporaryFile( delete=False, # 关闭自动删除，确保加载器能读取 suffix=f\u0026#34;.{file_suffix}\u0026#34;, # 保留文件后缀，避免加载器解析错误 mode=\u0026#39;wb\u0026#39; # 二进制写入模式 ) as tmp_file: tmp_file.write(file.getvalue()) # 写入文件内容 tmp_file_path = tmp_file.name # 记录临时文件路径 # -------------------------- （2）文档加载（按格式适配） ------------------------- # 根据文件后缀选择对应的文档加载器 if file_suffix == \u0026#39;pdf\u0026#39;: loader = PyPDFLoader(tmp_file_path) # PDF加载器 elif file_suffix == \u0026#39;docx\u0026#39;: loader = Docx2txtLoader(tmp_file_path) # DOCX加载器 elif file_suffix in [\u0026#39;txt\u0026#39;, \u0026#39;md\u0026#39;]: loader = TextLoader(tmp_file_path, encoding=\u0026#39;utf-8\u0026#39;) # 文本文件加载器（支持UTF-8编码） else: return { \u0026#34;success\u0026#34;: False, \u0026#34;message\u0026#34;: f\u0026#34;不支持的文件类型：{file_suffix}，当前支持：pdf/docx/txt/md\u0026#34; } # 加载文档内容（返回Document对象列表，每个对象含page_content和metadata） documents = loader.load() if not documents: # 处理空文档情况 return {\u0026#34;success\u0026#34;: False, \u0026#34;message\u0026#34;: \u0026#34;文档加载失败：内容为空或无法解析\u0026#34;} # -------------------------- （3）文本分块（解决长文本问题） --------------------- # 初始化文本分块器（解决长文本超出模型上下文窗口的问题） text_splitter = RecursiveCharacterTextSplitter( chunk_size=1000, # 每个片段的字符数（根据模型上下文调整） chunk_overlap=200, # 片段间重叠字符数（保持上下文连贯性） separators=[\u0026#34;\\n\\n\u0026#34;, \u0026#34;\\n\u0026#34;, \u0026#34;。\u0026#34;, \u0026#34; \u0026#34;, \u0026#34;\u0026#34;] # 优先按中文标点分割，提升分块合理性 ) # 将文档分割为片段（每个片段作为独立单元存入向量库） splits = text_splitter.split_documents(documents) # -------------------------- （4）向量存储 -------------------------- # 将片段添加到向量数据库 if self.vectordb: # 若数据库已存在，直接添加新片段 self.vectordb.add_documents(splits) else: # 若数据库不存在，创建新库并添加片段 self.vectordb = Chroma.from_documents( documents=splits, embedding=self.embeddings, # 使用初始化的嵌入模型 persist_directory=self.persist_directory # 指定存储路径 ) return { \u0026#34;success\u0026#34;: True, \u0026#34;message\u0026#34;: f\u0026#34;文档处理成功！共添加 {len(splits)} 个文本片段（文件：{file_name}）\u0026#34; } except Exception as e: # 捕获所有异常，返回具体错误信息 return {\u0026#34;success\u0026#34;: False, \u0026#34;message\u0026#34;: f\u0026#34;文档处理失败（{file_name}）：{str(e)}\u0026#34;} finally: # -------------------------- （5）临时文件清理 -------------------------- # 确保临时文件被清理（无论处理成功/失败） if tmp_file_path and os.path.exists(tmp_file_path): try: os.remove(tmp_file_path) except Exception as e: print(f\u0026#34;警告：临时文件清理失败（路径：{tmp_file_path}）：{str(e)}\u0026#34;) 负责将用户上传的文档转换为向量并存储，流程如下：\n文件有效性校验与临时文件创建：\n校验文件对象是否包含 name（文件名）和 getvalue()（获取二进制内容）方法；\n通过 tempfile.NamedTemporaryFile 创建临时文件，写入上传文件的二进制内容（避免直接操作内存数据）。\n文档加载（按格式适配）：\n根据文件后缀选择对应的 LangChain 加载器，支持 4 种格式：\n文件格式 加载器 核心作用 PDF PyPDFLoader 解析 PDF 每页内容，生成 Document 对象 DOCX Docx2txtLoader 提取 DOCX 文本内容（忽略格式） TXT/MD TextLoader 读取纯文本，指定 UTF-8 编码 文本分块（解决长文本问题）：\n使用 RecursiveCharacterTextSplitter 进行智能分块，核心配置：\nchunk_size=1000：每个文本片段最多 1000 字符（适配 LLM 上下文窗口）； chunk_overlap=200：片段间重叠 200 字符（避免上下文断裂，比如一个事件描述跨片段）； separators=[\u0026quot;\\n\\n\u0026quot;, \u0026quot;\\n\u0026quot;, \u0026quot;。\u0026quot;, \u0026quot; \u0026quot;, \u0026quot;\u0026quot;]：优先按大分隔符（如 \\n\\n 段落）分割，分割失败再用小分隔符（如 。 中文句末），最大程度保证语义完整性。 向量化存储：\n负责将文本（问题、文档片段）转换为高维向量，是「检索」的核心基础，并存入向量数据库中。\n若向量数据库已存在（self.vectordb 非空），直接添加新分块； 若不存在，通过 Chroma.from_documents 初始化数据库并写入分块向量，同时指定持久化路径。 临时文件清理：\n通过 finally 块确保无论处理成功 / 失败，临时文件都会被删除，避免磁盘占用。\n四、问答生成：get_answer_stream(question, chat_history)\r​\t该方法是 RAG（检索增强生成）的核心执行入口，实现检索相关文档→重排→结合历史对话→调用LLM流式生成输出。核心目标是：让 LLM 基于「用户问题 + 历史对话 + 相关文档片段」生成精准、有依据的答案，同时支持上下文连贯对话，流式输出。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 def get_answer_stream(self, question: str) -\u0026gt; Generator[str, None, None]: \u0026#34;\u0026#34;\u0026#34; 基于RAG技术生成问题答案，实现流式输出，逐块产生回答内容。 核心流程：检索相关文档片段 →结合对话历史 → 拼接提示词 → 调用LLM生成答案。 Args: question: 用户当前的问题（字符串类型，非空） Returns: 生成的答案字符串；若发生错误，返回错误提示；若未上传文档，返回引导提示 \u0026#34;\u0026#34;\u0026#34; # 重置当前流式回答 self.current_stream_answer = \u0026#34;\u0026#34; # -------------------------- 1. 检查向量数据库是否初始化（是否已上传文档） ------------- if not self.vectordb: yield \u0026#34;请先上传并处理文档，才能进行问答哦~\u0026#34; return if not question or not isinstance(question, str) or question.strip() == \u0026#34;\u0026#34;: yield \u0026#34;请输入有效的问题内容~\u0026#34; return # -------------------------- 2. 对话历史记忆加载（适配长对话） ----------------------- # 上下文管理 combine_contexts = [] # 加载对话历史记忆，设置窗口大小 k=50（保留最近100轮对话,滑动窗口机制），在维持对话连贯性的同时，避免历史记录过长导致的 Token 超限问题。 for msg in self.memory.load_memory_variables({})[\u0026#34;chat_history\u0026#34;]: combine_contexts.append(msg) # ------------------------ 3. 文档检索：获取问题相关的事实依据 ------------------------ # 创建向量数据库检索器，根据问题检索相关文档片段 retriever = self.vectordb.as_retriever(search_kwargs={\u0026#34;k\u0026#34;: 5}) relevant_docs = retriever.invoke(question) # ------------------------ 4. 对检索的相关文档进行重排 ------------------------ if self.reranker_model and self.enable_reranker: filters_docs = self.reranker_model.rerank_documents( query=question, documents=relevant_docs, top_n=self.rerank_top_n, score_threshold=self.rerank_score_threshold ) if filters_docs: # 若未重排未筛选到文档,就自动选取前rerank_top_n个检索的相关文档 relevant_docs = filters_docs logger.info(f\u0026#34;重排后提取 {len(relevant_docs)} 个相关文本块\u0026#34;) else: relevant_docs = relevant_docs[:self.rerank_top_n] logger.info(f\u0026#34;重排后未筛选到文档，提取检索的 {len(relevant_docs)} 个相关文本块\u0026#34;) # -------------------------- 5. 提示词构建：拼接完整提示词 -------------------------- # 提取片段内容，格式化为字符串（便于拼接提示） context_text = \u0026#34;\\n\\n\u0026#34;.join([doc.page_content for doc in relevant_docs]) # 系统提示词模板：明确LLM角色、回答规则（避免幻觉）和输入结构（Context/History/Question） system_prompt = \u0026#34;\u0026#34;\u0026#34; 你是基于文档的问答助手，仅使用以下提供的文档片段（Context）回答问题。 如果文档中没有相关信息，直接说“根据提供的文档，无法回答该问题”，不要编造内容。 回答需简洁、准确，结合历史对话（History）理解上下文，每一次回答要重新审视当前提供的内容，不要只是简单重复历史回答。 Context: {context_text} Current Question: {question} Answer: \u0026#34;\u0026#34;\u0026#34; # 使用检索的相关文档片段和用户输入问题格式化提示模板 final_prompt = system_prompt.format( context_text=context_text, question=question ) # 添加最终提示到上下文中 combine_contexts.append(HumanMessage(content=final_prompt)) logger.info(f\u0026#34;combine_contexts:{combine_contexts}\u0026#34;) # -------------------------- 6. 流式调用LLM：逐块生成并返回答案 ---------------------- try: # 流式调用LLM：llm.stream()返回生成器，逐块获取LLM输出（而非等待完整答案） for chunk in self.llm.stream(combine_contexts): # 实时返回输出内容 if chunk.content: yield chunk.content self.current_stream_answer += chunk.content # 完整答案生成后，更新对话记忆：将本次问答（问题+完整答案）存入记忆，供下一轮对话复用 self.memory.save_context( inputs={\u0026#34;input\u0026#34;: question}, outputs={\u0026#34;answer\u0026#34;: self.current_stream_answer} ) logger.info(f\u0026#34;self.memory.save_context:{self.memory.model_dump_json()}\u0026#34;) except Exception as e: logger.error(f\u0026#34;错误：答案生成失败：{str(e)}\u0026#34;) yield \u0026#34;抱歉，处理问题时发生错误，请稍后再试~\u0026#34; 流程如下：\n前置校验\n检查向量数据库是否初始化（即是否已上传文档） 。\n检查用户问题是否有效（非空字符串）。\n对话历史加载\n通过 ConversationBufferWindowMemory（滑动窗口记忆） 管理对话历史，配置 k=50，仅保留最近 50 轮对话（1 轮 = 1 次用户提问 + 1 次助手回答），既保证对话连贯性，又避免长对话导致的 Token 超限问题。\n检索相关文档（获取回答的事实依据）\n基于向量检索技术，从已上传文档中提取与用户问题语义相似的文本片段：\n将向量数据库转为检索器（self.vectordb.as_retriever）；\n配置 search_kwargs={\u0026quot;k\u0026quot;: 5}：检索与用户问题最相关的 5 个文本片段（k 值可调整，平衡相关性与上下文长度）；\n提取检索结果的文本内容，拼接为 context_text（供 LLM 参考）。\n检索文档重排（提升文档相关性精度）\n对原始检索结果进行精细化筛选，进一步提升文档与问题的匹配度：\n若配置了重排模型（self.reranker_model），则调用模型对原始检索的文本片段进行重排； 重排规则：按「相关性分数」筛选 Top-N 个片段（top_n 可配置），并过滤分数低于阈值（score_threshold）的片段； 降级处理：若重排后无符合条件的片段，则默认选取原始检索结果的前 N 个片段。 组合提示词\n初始化上下文对话列表：combine_contexts = []。 添加历史对话记忆：从记忆中加载最近对话列表（Message对象）到上下文对话中，让 LLM 清晰识别历史交互逻辑。 定义提示词模板（system_prompt）：定义 LLM 的角色、回答规则，如：仅使用提供的文档片段回答，无相关信息时明确告知，不编造内容。 格式化提示模板：将context_text（检索到的文档）、question（当前问题） 格式化提示模板，生成结构化的 final_prompt，并作为用户输入添加到上下文对话中。 流式调用LLM输出结果及记忆更新\n实现流式输出回答，并将本次交互存入记忆以支撑后续对话：\n流式生成回答：调用 self.llm.stream(combine_contexts)，将完整上下文提交给 LLM，逐块读取生成器中的响应片段并实时返回给用户，同时将片段拼接为 self.current_stream_answer（完整答案）； 对话记忆更新：当完整答案生成后，将本次问答对（用户问题 question + 完整答案 self.current_stream_answer）存入记忆 ConversationBufferWindowMemory，供下一轮对话。 流式输出提升用户体验，记忆更新保障下一轮对话可复用本次交互信息，维持上下文连贯。\n五、清空数据库clear_database()\r支持重新上传文档、清空历史知识。\n1 2 3 4 5 6 7 8 9 10 11 12 def clear_database(self) -\u0026gt; bool: \u0026#34;\u0026#34;\u0026#34;清空向量数据库\u0026#34;\u0026#34;\u0026#34; try: if self.vectordb: self.vectordb.reset_collection() # 清除记忆 self.memory.clear() return True except Exception as e: print(f\u0026#34;错误：数据库清空失败：{str(e)}\u0026#34;) return False 清空 Chroma 向量数据库的集合内数据（reset_collection）。\n7 前端界面（main.py）\r通过 Streamlit 框架搭建 Web 交互界面，通过 “上传文档→提问” 的操作，获得基于文档的精准回答，同时支持流式输出（边生成边展示）和聊天连续性。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 import os import sys import streamlit as st from dotenv import load_dotenv # 将当前脚本所在目录加入Python搜索路径（确保能找到services目录下的RAGService） current_dir = os.path.dirname(os.path.abspath(__file__)) sys.path.append(current_dir) from services.rag_service_stream import RAGService # 加载环境变量 load_dotenv() # 配置页面标题、图标、布局 st.set_page_config( page_title=\u0026#34;RAG知识问答助手\u0026#34;, page_icon=\u0026#34;:robot:\u0026#34;, layout=\u0026#34;wide\u0026#34; ) # 初始化Streamlit会话状态（跨刷新保存数据，避免页面刷新后数据丢失） def initialize_app(): # 初始化会话状态 if \u0026#34;history\u0026#34; not in st.session_state: st.session_state.history = [] # 用于重置文件上传框状态的会话变量 if \u0026#34;upload_key\u0026#34; not in st.session_state: st.session_state.upload_key = 0 # 初始化RAG配置参数 if \u0026#34;retrieve_k\u0026#34; not in st.session_state: st.session_state.retrieve_k = 6 # 默认检索文档数量 if \u0026#34;enable_reranker\u0026#34; not in st.session_state: st.session_state.enable_reranker = False # 默认开启重排 if \u0026#34;rerank_top_n\u0026#34; not in st.session_state: st.session_state.rerank_top_n = 5 # 重排后保留文档数量 # 初始化RAG核心服务（封装了文档处理、向量存储、流式问答的核心逻辑） if \u0026#34;rag_service\u0026#34; not in st.session_state: st.session_state.rag_service = RAGService( retrieve_k=st.session_state.retrieve_k, enable_reranker=st.session_state.enable_reranker, rerank_top_n=st.session_state.rerank_top_n, # 重排模型配置：用于对检索结果进行语义重排序.默认使用BAAI的bge-reranker-v2-m3（中文效果性价比较高）。 # 这里加载本地路径模型（需手动下载模型文件到指定路径） # a.模型文件获取地址：https://modelscope.cn/models/BAAI/bge-reranker-v2-m3 # b.需下载文件：config.json、model.safetensors、special_tokens_map.json、tokenizer.json、tokenizer_config.json model_name_or_path=\u0026#34;../../data/models_reranker_data/BAAI/bge-reranker-v2-m3\u0026#34; # 指定重排模型本地存储路径 ) initialize_app() # 定义侧边栏区域 with st.sidebar: st.subheader(\u0026#34;RAG知识问答助手\u0026#34;) # RAG检索配置区域 # 1. 检索数量控制 retrieve_k = st.slider( \u0026#34;初始检索文档数量 (retrieve_k)\u0026#34;, min_value=1, max_value=10, value=st.session_state.retrieve_k, step=1, help=\u0026#34;从向量库中初始检索的文档数量，数量越多覆盖范围越广，但可能引入噪音\u0026#34; ) # 2. 重排功能开关 enable_reranker = st.toggle( \u0026#34;开启检索结果重排\u0026#34;, value=st.session_state.enable_reranker, help=\u0026#34;开启后会对检索到的文档进行语义重排序，提升回答质量，但会增加响应时间\u0026#34; ) # 3. 重排后保留数量（仅在开启重排时可配置） rerank_top_n = st.slider( \u0026#34;重排后保留文档数量 (rerank_top_n)\u0026#34;, min_value=1, max_value=8, value=st.session_state.rerank_top_n, step=1, help=\u0026#34;重排后最终保留的文档数量，需小于等于初始检索数量\u0026#34;, disabled=not enable_reranker # 关闭重排时禁用该参数 ) # 限制rerank_top_n不超过retrieve_k if rerank_top_n \u0026gt; retrieve_k: rerank_top_n = retrieve_k st.warning(f\u0026#34;重排保留数量自动调整为 {retrieve_k}（不超过初始检索数量）\u0026#34;) # 4. 应用配置按钮 if st.button(\u0026#34;应用配置\u0026#34;, use_container_width=True, type=\u0026#34;primary\u0026#34;): # 更新会话状态 st.session_state.enable_reranker = enable_reranker st.session_state.retrieve_k = retrieve_k st.session_state.rerank_top_n = rerank_top_n # 更新RAGService的配置 st.session_state.rag_service.enable_reranker = enable_reranker st.session_state.rag_service.retrieve_k = retrieve_k st.session_state.rag_service.rerank_top_n = rerank_top_n st.success(\u0026#34;配置已更新生效！\u0026#34;) # 文档管理区域 st.divider() # 添加分隔线 # 1. 多文件上传（支持PDF/DOCX/TXT/MD，与RAGService支持的格式一致） uploaded_files = st.file_uploader( \u0026#34;上传文档 (PDF/DOCX/txt/md)\u0026#34;, accept_multiple_files=True, key=f\u0026#34;file_uploader_{st.session_state.upload_key}\u0026#34; # 动态生成key ) # 处理上传的文件：调用RAGService的process_document方法，完成“解析→分块→向量化→入库” if uploaded_files: with st.spinner(\u0026#34;正在处理文档...\u0026#34;): for file in uploaded_files: st.session_state.rag_service.process_document(file) st.success(f\u0026#34;已成功处理 {len(uploaded_files)} 个文档\u0026#34;) # 处理完成后重置上传框：通过改变key值实现 st.session_state.upload_key += 1 # 2. 清空知识库（删除向量库数据+清空聊天历史，重置整个问答环境） if st.button(\u0026#34;清空知识库\u0026#34;, type=\u0026#34;secondary\u0026#34;, use_container_width=True): with st.spinner(\u0026#34;正在清空知识库...\u0026#34;): # 清空向量存储 st.session_state.rag_service.clear_database() # 清空聊天历史 st.session_state.history = [] st.success(\u0026#34;知识库已成功清空\u0026#34;) # 主界面 - 聊天区域 st.header(\u0026#34;从0开始：用 Streamlit + LangChain搭建一个简单基于RAG问答聊天助手\u0026#34;) # 1. 展示聊天历史（遍历session_state.history，按角色显示消息） for message in st.session_state.history: with st.chat_message(message[\u0026#34;role\u0026#34;]): st.markdown(message[\u0026#34;content\u0026#34;]) # 2. 处理用户输入 user_input = st.chat_input(\u0026#34;请问有什么可以帮助您？\u0026#34;) if user_input: # 步骤1：将用户消息添加到会话历史 st.session_state.history.append({\u0026#34;role\u0026#34;: \u0026#34;user\u0026#34;, \u0026#34;content\u0026#34;: user_input}) with st.chat_message(\u0026#34;user\u0026#34;): st.markdown(user_input) # 步骤2：调用RAG服务生成流式回答，并显示 with st.chat_message(\u0026#34;assistant\u0026#34;): with st.spinner(\u0026#34;思考中...\u0026#34;): # RAG回答，非流式：大模型完整输出后才展示出来 # full_answer = rag_service.get_answer(user_input) # st.markdown(full_answer) # RAG回答，流式输出 full_answer = \u0026#34;\u0026#34; # 用于存储完整的回复内容 # 调用RAGService的get_answer_stream（流式方法），用st.write_stream实现边生成边显示 for chunk in st.write_stream(st.session_state.rag_service.get_answer_stream(user_input)): full_answer += chunk # 步骤3：将完整的助手回答添加到会话历史，供下次刷新时展示 st.session_state.history.append({\u0026#34;role\u0026#34;: \u0026#34;assistant\u0026#34;, \u0026#34;content\u0026#34;: full_answer}) st.rerun() 交互流程如下：\n准备阶段：用户打开网页，看到侧边栏的 “文档上传” 和主界面的聊天区域； 上传文档：用户在侧边栏选择 1 个或多个文档（PDF/DOCX 等），点击上传，系统显示 “正在处理文档\u0026hellip;”，完成后提示 “处理成功”； 提问交互：用户在底部输入框提问（如 “公司的发展历程？”），点击发送； 流式回答：系统显示 “思考中”，并开始逐字 / 逐句显示回答（流式输出），同时将用户问题和助手回答保存到聊天历史； 重置操作：若用户想切换文档，可点击侧边栏 “清空知识库”，系统删除所有文档和聊天历史，恢复初始状态。 4.6 运行测试\r确保.env文件已正确配置 API 密钥\n在项目根目录下，打开终端执行命令：\n1 2 cd simple_rag_assistant streamlit run main.py 系统将启动 Web 服务，默认地址为 http://localhost:8501\n浏览器会自动打开界面，使用流程：\n在侧边栏上传文档（支持多文件）。 等待文档处理完成（会显示处理成功提示）。 在底部输入框提问，助手会基于上传的文档内容回答。 完整代码位于项目根目录下：practice_cases/simple_rag_assistant\n完整源码地址：\nGitHub 仓库：https://github.com/tinyseeking/tidy-agent-practice/tree/main/practice_cases/simple_rag_assistant Gitee 仓库（国内）：https://gitee.com/tinyseeking/tidy-agent-practice/tree/main/practice_cases/simple_rag_assistant 4.7 总结\r​\t本项目构建了一个功能完整的基础 RAG 问答系统，采用模块化设计保证了代码的可维护性和可扩展性。你可以在此基础上，进一步拓展核心能力与使用体验，如：\n增加更多文档格式支持（如 PPT、Excel）和多模态识别。 实现文档分段预览和定位。 添加用户认证和权限管理。 优化检索策略，实现更精准的内容匹配与高效召回。 增加模型选择功能，允许用户切换不同的 LLM。 通过该项目，你将掌握 RAG 技术的核心原理与工程化实现方法，为后续搭建更复杂的智能检索增强生成（RAG）应用奠定技术基础。\n","date":"2026-01-11T00:00:00Z","image":"/p/%E6%89%8B%E6%8A%8A%E6%89%8B%E6%90%AD%E5%BB%BA%E7%AC%AC%E4%B8%80%E4%B8%AArag%E5%AE%9E%E6%88%98%E5%AE%9E%E7%8E%B0%E6%9C%AC%E5%9C%B0%E6%96%87%E6%A1%A3%E6%99%BA%E8%83%BD%E9%97%AE%E7%AD%94/images/image-20260102230727272.png","permalink":"/p/%E6%89%8B%E6%8A%8A%E6%89%8B%E6%90%AD%E5%BB%BA%E7%AC%AC%E4%B8%80%E4%B8%AArag%E5%AE%9E%E6%88%98%E5%AE%9E%E7%8E%B0%E6%9C%AC%E5%9C%B0%E6%96%87%E6%A1%A3%E6%99%BA%E8%83%BD%E9%97%AE%E7%AD%94/","title":"手把手搭建第一个RAG实战：实现本地文档智能问答"},{"content":"第3章 构建 AI Agent 的技术架构\r​\t我们了解了AI Agent的概念框架后，我们就想构建智能体，但如何构建智能体呢？有没有一些设计模式或最佳实践可遵循，这些设计模式或最佳实践就形成技术架构。这节我们就深入的探索构建 AI Agent 的技术架构。\n​\t近年来，由于大语言模型（LLMs）展现出来的智能潜力，引发了基于大语言模型的智能体的研究热潮，产生了许多基于大语言模型的智能体项目，如AutoGPT、BabyAGI等。有些机构和专家学者对基于大语言模型的智能体项目进行了系统的总结，并提出了统一的技术框架。其中比较全面、热门的有两篇：\n一篇是2023年8月中国人民大学发表的论文“A Survey on Large Language Model based Autonomous Agents“（基于大语言模型的智能体研究综述）。 另一篇是2023年6月OpenAI 的应用研究主管 Lilian Weng 撰写了一篇博客“LLM Powered Autonomous Agents”。 ​\t本章首先分别介绍这两篇文章中所提出的技术框架，然后梳理出构建智能体的整体技术框架，最后详细的解析构成框架的每个组件，使我们对如何构建智能体有个整体且细致的了解。\n3.1 技术架构一\r​\t23年8月份中国人民大学发表的论文“A Survey on Large Language Model based Autonomous Agents“（基于大语言模型的智能体研究综述），提出了一个构建基于大语言模型的统一框架，这个框架的整体结构如下图所示：\n​\t图 2 基于大语言模型（LLM）的自主智能体架构设计的统一框架。\n该框架有四个模块组成：配置文件模块、记忆模块、规划模块和行动模块。\n配置文件模块：旨在明确智能体的角色特征。智能体在执行任务时通常会预设一个特定的角色，如程序员、教师和领域专家，这些角色特征通常写入提示中，用以影响大语言模型的行为。例如：在处理编程相关问题（如代码漏洞修复等）任务时，将智能体的角色预设为 “你是一个专业的资深程序员” 时，大语言模型会以程序员的思维方式和专业术语来进行回答。\n​\t智能体的配置信息包括基本信息(年龄、性别和职业等)、个性的心理信息，以及描述Agent之间社交关系的信息等。配置哪些信息取决于具体的应用场景，例如，如果应用旨在研究人类认知过程，那么心理信息就变得至关重要。\n记忆模块：记忆模块存储从环境中感知到的信息，并利用记录的记忆来辅助未来的行动。记忆模块可以帮助智能体积累经验、自我进化，使其行为更加一致、合理和有效。人类记忆通常从感知输入的感觉记忆开始，到短暂存储信息的短期记忆，再到长期巩固信息的长期记忆。智能体的记忆机制仿照人类记忆，可以分为短期记忆（短暂保持信息）和长期记忆（在较长时间内巩固信息）：\n短期记忆：这种结构仅模拟人类的短期记忆，通常通过上下文学习实现，记忆信息直接写入提示中。例如，在聊天智能体应用中，维护一个内部对话历史记录。在每一轮对话中，这些历史记录追加到当前输入提示中，充当智能体的短期记忆，发送给大语言模型。短期记忆受 Transformer 架构上下文窗口输入信息的限制。 长期记忆：长期记忆则是在较长时间内巩固的信息，这类信息一般被编码为嵌入向量并存储在向量数据库中，智能体可根据需要，随时从向量数据库中快速查询和检索相关的信息。 规划模块：面对复杂任务时，人类倾向于将其分解为更简单的子任务并分别解决它们。规划模块的目标是赋予Agent这种能力，使Agent的行为更加合理、强大和可靠，并具有可解释性。\n​ 列如：思维链（Chain of Thought，CoT）就是一种常见的规划策略，它将解决复杂问题的推理步骤示例输入到提示中，来启发大语言模型逐步规划和行动。在这种方法中，计划是基于提示中的示例启发而创建的。零样本思维链（Zero-shot-CoT）通过用 “一步一步思考” 这样的触发语句提示大语言模型，使其能够生成任务推理过程。与 CoT 不同，这种方法在提示中不包含推理步骤作为示例。其它的策略如自洽思维链（Self-consistent CoT，CoT-SC）、思维树（Tree of Thoughts，ToT）等等，后续会详细的介绍。\n行动模块：负责将智能体的决策转化为具体结果。该模块位于最下游位置，直接与环境交互，受配置文件模块、记忆模块和规划模块的影响。\n从四个角度来看：\n行动目标：涉及Agent行动的预期结果是什么。 行动产生：描述了行动是如何从Agent的决策过程中产生出来的？两种常用的行动生成策略：（1）通过记忆回忆生成行动。在这种策略中，行动是根据当前任务从智能体记忆中提取信息生成的。（2）通过遵循计划生成行动**。**在这种策略中，智能体按照预生成的计划采取行动。 行动空间：指的是智能体能够执行的所有可能行动的集合。一般来说，可以将行动分为两类：（1）外部工具，如调用外部 API、数据库、外部模型等等；（2）大语言模型的内部知识。 行动影响：指的是智能体行动所产生的后果。主要有三个方面：1、改变环境，智能体通过行动改变环境状态，如移动位置、收集物品、建造建筑等；2、改变内部状态，包括更新记忆、制定新计划、获取新知识等；3、触发新行动，在任务完成过程中，一个行动往往会引发后续行动。 ​\t在该架构中，配置文件模块用于确定智能体的角色，记忆模块和规划模块将智能体置于动态环境中，使其能够回忆过去的行为并规划未来的行动，行动模块负责将智能体的决策转化为具体输出。在这些模块中，配置文件模块影响记忆模块和规划模块，而这三个模块共同影响行动模块。\n3.2 技术架构二\r​\t另一个由大语言模型驱动的自主Agent系统架构，是2023年6月由OpenAI公司的安全系统主管Lilian Weng在其撰写了一篇博客“LLM Powered Autonomous Agents”中提出的，该架构包含规划（Planning）、记忆（Memory）、工具（Tools）、执行（Action）四大模块。如下图所示：\n​\t图. 由大语言模型驱动的自主智能体系统概览。\n在由大语言模型驱动的自主智能体系统中，大语言模型（LLM）充当智能体的大脑，并由几个关键组件组成：\n规划（Planning）：Agent 具备思考、规划、决策能力，能有效地执行复杂任务。涉及子目标的分解（Subgoal decomposition）、思维链（Chain of thoughts）、自我批评（Self-critics），以及对过去行动的反思（Reflection）。该组件作为Agent的大脑，由大语言模型（LLM）来担任，具体包括以下两个方面： 子目标与分解：智能体将大型任务分解为更小、更易于管理的子目标，从而能够高效处理复杂任务。赋予大模型分析、推理、规划、决策能力。 反思与优化：智能体可以对过去的行为进行自我批评和自我反思，从错误中吸取教训，并针对未来的步骤进行优化，从而提高最终结果的质量。这赋予大模型自主学习迭代改进的能力。 记忆（Memory）：记忆可以定义为用于获取、存储、保留和稍后检索信息的过程，包含短期记忆和长期记忆两种： 短期记忆：将所有的上下文学习（比如Prompt Engineering、In-Context Learning）都是利用模型的短期记忆来进行学习。 长期记忆：这使智能体能够在较长时间内保留和检索（无限的）信息的能力，通常是通过利用外部向量存储和快速检索来实现。 工具（Tool）：工具扩展了Agent的行动能力，使其能与外部环境进行交互。由于大模型一旦完成预训练，其内部能力和知识边界基本固定下来，而且难以拓展，智能体可以利用工具（如：调用外部API、数据库查询）来获取模型权重中确少的额外信息，提升输出结果质量。其它工具如日历、计算器、代码执行能力、搜索、访问专有信息源等。 行动（Action）：执行由规划模块决策的具体行动，包括输出文本内容、调用工具来完成任务等。 ​\t在这个架构中，Lilian Weng提出了 Agent=LLM + 规划 + 记忆 + 行动(工具使用)的基础架构。其中 LLM 扮演了 Agent 的 “大脑”，强调LLM作为系统的核心控制器，提供推理、规划、决策的能力；记忆组件提供记忆能力，使其能够存储和检索外部的相关信息，来优化决策；行动组件执行决策产生的行动，也可利用工具与外部环境进行交互； 而 Agent位于中心位置，它通过协同各种组件来处理复杂的流程，最终自主完成目标任务。\n3.3 构建AI Agent的整体技术架构\r​\t通过之前对AI Agent概念框架的了解，再来看构建Al Agent的技术架构，对其所需要的核心组件就会有更清晰的认知。我们梳理了当前许多研究人员及机构对构建智能体技术架构的研究成果，结合实践经验，提炼出构建AI Agent的整体技术架构，包括八个模块：Profile（配置）、Percept（感知）、Planning \u0026amp; Reasoning（规划\u0026amp;推理）、Action（行动）、Memory（记忆）、Learning（学习）、Context Management（上下文管理） 和 Orchestration（编排）。如下图所示：\n构建AI Agent的整体技术架构具体模块说明如下：\nPercept（感知）：负责接收外部环境的输入信息，包括 Text（文本 ）、Image（图像 ）、Sound（音频）、Video（视频）等多种模态数据，是智能体获取外界信息的窗口，为后续处理提供原始素材。\nProfile（配置）：定义智能体角色、职责及能力边界，聚焦个性化与适配特定场景。智能体通常扮演特定角色来执行任务，不同角色决定智能体具备的能力及可执行操作范围。如： coder（程序员）角色具备代码编写、调试等能力；teacher（教师）角色擅长知识讲解、教学规划 。该模块并不是必须的，根据具体的应用场景而配置。\nPlanning \u0026amp; Reasoning（规划与推理）：智能体的 “思考决策中枢”，主要有大语言模型来承担，负责推理、规划并决策下一步行动。有多种推理、规划和反思的模式，主要包含两类模式：\n任务分解：将复杂任务拆分为更小、更易于管理的子目标，从而能够高效处理复杂任务。这方面的模式如 CoT（思维链，Chain of Thought ）能让智能体将复杂问题分解为一系列中间步骤进行推理；ToT（树状思维，Tree of Thought ）可用于探索不同解题路径，寻找最佳的解决路径方案；GoT（图状思维，Graph of Thought）基于节点关联实现更灵活的任务拆解。 反思优化：对过去的行为进行自我批评和自我反思，来优化未来的决策步骤。典型模式包括：ReAct 融合推理和行动；Reflection（反思 ）能让智能体基于过往经验优化未来的策略；Self-Consistency（自一致性校验）生成多个不同路径的推理结果，通过结果比对筛选出高可靠性结论。这些推理、规划、反思的设计模式也叫做认知策略，后面章节我们会详细解析主流的认知策略模式及其技术实现。 Action（行动）：负责执行由规划与推理模块输出的具体行动，行动类型分为两类：输出内容（如生成文本、图像等多模态内容）；工具调用（通过调用外部工具实现复杂功能，如查询数据库、调用 API、控制硬件设备等）。其中，工具扩展了Agent的行动能力，充当 agent 的抽象能力和现实世界之间的桥梁。\n主流工具调用模式包括：\n指令解析调用：基于自然语言指令匹配适配工具，适配简单场景。 函数调用（Function Calling）：通过标准化函数定义实现工具的精准调用，支持复杂参数配置。 MCP 协议（Model Context Protocol）：统一大模型与外部数据源和工具之间的通信协议，提升多工具协同效率。 Memory（记忆）：负责信息的存储、更新与高效检索，为智能体的决策提供历史经验与领域知识支撑。分为短期记忆和长期记忆：\n短期记忆：存储实时交互的临时信息（如对话上下文、当前任务状态、中间推理结果），核心实现方式是利用大语言模型的上下文窗口，保障任务执行的连贯性。 长期记忆：存储需长期复用的信息（如历史任务经验、领域知识库、用户偏好），核心实现架构为\u0026quot;外部向量数据库 + RAG（Retrieval-Augmented Generation，检索增强生成）\u0026quot;，支持从海量数据中快速检索相关信息辅助决策。 Learning（学习）：支撑智能体的持续迭代与自我优化，核心是构建 “交互 - 评估 - 反思 - 学习” 的闭环学习机制：首先智能体通过与环境 / 用户的交互获取反馈；其次通过评价模块对反馈数据进行量化评估（如准确率、效率、满意度指标等）；再通过反思环节深挖问题根源；最后从反思结论中学习提取关键经验与洞见，并更新至长期记忆并优化决策策略，形成 “交互 - 评估 - 反思- 洞见 - 优化” 的全链路闭环，实现持续的自我改进迭代。\nContext Management（上下文管理）：负责上下文信息的全生命周期管理，包括构建、检索、更新、修剪与压缩上下文信息，核心目标是 “填充恰好适配下一步任务的上下文信息”—— 既保障信息与当前任务的强相关性，也避免信息冗余导致的推理效率下降。\nOrchestration（编排）：是智能体的 “指挥中枢”，负责协调各模块的交互逻辑与执行流程，确保组件有序配合，实现自主、高效的任务完成。例如：规划好何时以及调用哪个组件、处理数据格式转换、循环控制、异常处理（（如工具调用失败重试、推理超时中断）、必要时人类参与（复杂决策时请求人类介入）、终止条件判断（达成目标或触发最大迭代次数时停止）。主流工作模式分为两类：\nAgentic 模式（自主代理模式）：支持根据任务进展自主动态调整模块调用，适配复杂多变场景。 Workflow 模式（工作流模式）：基于固定流程模板调度模块，适配标准化、重复性任务。 具体而言，AI Agent的运行流程图如下：\n流程说明如下：\n信息感知接入：通过Percept（感知）模块接收外部环境或用户的输入信息，包括 Text（文本 ）、Image（图像 ）、Sound（声音 ）、Video（视频）等多种模态数据，经过基础预处理后传递至记忆模块。 记忆动态更新：记忆模块将实时输入信息存入短期记忆，利用模型上下文窗口维护对话连贯性；同时根据任务需求，将历史交互记录存入长期记忆，为后续通过 RAG 技术从长期记忆（如向量数据库）中检索相关历史经验或领域知识，为决策提供精准知识支撑。 上下文构建：上下文管理模块联合记忆模块，整合系统指令、短期记忆（对话上下文、当前任务状态）、长期记忆检索结果（历史交互记录、用户偏好）、Profile 角色配置、通过RAG技术从知识库中检索的关联信息（如公司季度数据、历史报告模板、行业规范）、可用工具（Available Tools）等多源信息。对这些信息进行融合优化与冗余修剪，生成\u0026quot;精准上下文信息\u0026quot;，传递至规划与推理模块。 决策策略生成：规划与推理模块基于精准上下文进行思考、规划、推理过程，如采用 CoT、ToT 、ReAct等认知策略进行推理分析，将复杂任务拆解为子目标序列；生成行动策略清单，并决策出下一步的行动。推理过程中可实时与上下文管理模块交互，若发现信息缺失，动态补充缺失信息（如请求记忆模块检索最新行业数据）。 行动执行与反馈：行动模块执行由规划与推理模块输出的具体行动，行动可以是输出文本、图片等多模态内容，也可以是调用工具与外部环境交互（如：通过Function Calling模式调用指定工具（如调用公司数据库API提取销售数据）、通过MCP协议同步调用工具返回结果）。行动结果会对环境产生新的影响并更新内部状态，同时决策和行动的操作轨迹及其结果也保存为记忆信息。 学习迭代优化：学习模块接收行动反馈数据，启动“交互 - 评估 - 反思 - 学习”闭环：通过评价指标量化任务完成质量，反思深挖问题根源，提炼结构化经验及洞见，并更新至长期记忆。使智能体能够根据过去的经验优化未来的决策，实现自主改进。 循环与终止：编排模块评估任务完成情况，若未达成目标则启动下一轮“感知-\u0026gt;记忆-\u0026gt;上下文-\u0026gt;决策-\u0026gt;行动-\u0026gt;学习”循环；若达成目标或触发终止条件（如最大迭代次数），则停止运行并输出最终结果。 整个流程中，Orchestration（编排）模块始终承担“中枢调度”角色，协调各个组件间的交互流程，并评估其进展情况，进行异常错误恢复（如设置检查点或在遇到障碍时暂停，并请求人类反馈等）、循环控制直达到目标或触发停止条件（如最大迭代次数）时终止，使整个系统高效运行，确保智能体自主完成目标任务。\n接下来，我们针对各模块的技术实现细节展开深入解析。\n","date":"2026-01-09T00:00:00Z","image":"/p/%E6%9E%84%E5%BB%BA-ai-agent-%E7%9A%84%E6%8A%80%E6%9C%AF%E6%9E%B6%E6%9E%84/images/image-20251212112412089.jpg","permalink":"/p/%E6%9E%84%E5%BB%BA-ai-agent-%E7%9A%84%E6%8A%80%E6%9C%AF%E6%9E%B6%E6%9E%84/","title":"构建 AI Agent 的技术架构"},{"content":"第2章 AI Agent 核心概念\r​\t在了解了AI Agent的起源、发展历程及其定义后，我们再来看看AI Agent的核心概念，并基于核心概念梳理出智能体的核心流程。\n2.1 核心概念\r​\t由米哈游联合复旦 NLP 组于2023年9月发布的论文《The Rise and Potential of Large Language Model Based Agents: A Survey》中，对智能体的现有研究成果进行了系统概括，提出了一个比较全面的通用概念框架，见如下图所示 ：\n​\t图1 基于大语言模型的智能体的概念框架，包含三个组成部分：大脑、感知和行动。\n基于大语言模型的智能体的概念框架，由三个关键部分组成：大脑、感知和行动：\n**大脑模块：**大脑模块作为控制中心，是智能体的核心，主要由LLM（大语言模型）构成。大脑模块不仅存储知识和记忆，还承担着信息处理和决策功能。通过分析、推理、规划过程生成下一步的行动方案，同时还具有泛化能力、能够很好地应对未见过的任务，展现出智能体的智能。 **感知模块：**使智能体可以感知外部环境的变化。需要将智能体的感知空间从纯文本领域扩展到包括文本、听觉和视觉模态等多模态领域，这种扩展使智能体能够更有效地理解和利用来自周围环境的信息。如通过文字输入、传感器、摄像头、麦克风等，建立起对外部世界或环境的感知。 **行动模块：**旨在扩展智能体行动空间。具体来说，赋予智能体使用工具技能及具身行动能力，能与外部环境或世界进行交互，使其能适应环境变化、提供反馈，甚至影响和塑造环境。 ​\t该框架可以根据不同的应用场景进行定制。一般来说，智能体按以下工作流程运行：首先，感知模块对应于人类的感官系统，如眼睛和耳朵，感知外部环境的变化，然后将多模态信息转换为智能体可理解的表示形式。随后，大脑模块作为控制中心，进行思考、决策等信息处理活动，存储经验知识和记忆，从记忆中提取有效信息以优化决策。最后，行动模块对应于人类的四肢，在工具的辅助下执行操作，并对周围环境产生影响。通过重复上述过程，智能体可以不断获得反馈并与环境进行交互。\n​\t上图中的例子来说明其工作流程：当一个人询问是否会下雨时，感知模块会将该指令转换为大语言模型能够理解的表征形式。然后，大脑模块开始根据当前的天气状况以及互联网上的天气预报（利用工具获取天气预报信息）进行推理。最后，行动模块做出回应，并将雨伞递给这个人。通过重复上述过程，智能体能够持续获得反馈并与环境进行交互。\n2.1.1 大脑模块\r​\t大脑模块的运行机制：在接收到感知模块处理的信息后，大脑模块首先转向存储，检索知识，并从记忆中回忆。这些结果有助于智能体制定计划、进行推理并做出明智的决策。此外，大脑模块可能会以摘要、向量或其它数据结构的形式，保存智能体过去的观察、想法和行动。同时，它还可以更新常识和领域知识等，以供将来使用。基于大语言模型的智能体还可以凭借其固有的泛化和迁移能力，适应不熟悉的场景。\n​\t智能体的大脑模块主要以大语言模型来构建，核心包括以下几个部分：\n**自然语言交互：**为了确保有效沟通，进行自然语言交互的能力至关重要，语言包含着丰富的信息。除了直观表达的内容之外，话语背后可能还隐藏着说话者的信念、愿望和意图。由于大语言模型（LLMs）所固有的强大自然语言理解和生成能力，智能体不仅能够熟练地用多种语言进行基本的交互式对话 ，还能展现出深入的理解能力，让人类能够轻松地与智能体进行理解和交互。\n**知识：**在大规模数据集上训练的语言模型能够将广泛的知识编码到其参数中，可对各种类型的查询做出正确的响应。这些知识可以帮助基于大语言模型的智能体做出明智的决策。但存在些问题：\n模型在训练过程中获取的知识可能会过时，甚至从一开始就是错误的。一个简单的解决方法是重新训练。但这需要先进的数据、大量的时间和计算资源。更糟糕的是，这可能会导致灾难性遗忘 。 大语言模型可能会生成与源信息或事实信息相冲突的内容 ，这种现象通常被称为 “幻觉”。这是大语言模型无法广泛应用于事实性要求严格的任务的关键原因之一。 记忆：“记忆” 存储着智能体过去的观察、想法和行动序列。当面对复杂问题时，记忆机制有助于智能体有效地回顾和应用先前的策略。并使智能体能够借鉴过去的经验来适应不熟悉的环境。\n推理与规划：\n推理是人类智力活动的基础，是解决问题、决策以及批判性分析的基石。演绎、归纳和溯因推理是人们普遍认可的主要推理形式。对于智能体而言，推理能力如同人类一样，是解决复杂任务的关键。一些研究实验已被证明，通过引导大语言模型在输出答案前先生成推理依据，能够激发出其推理能力，如具有代表性的思维链（Chain-of-Thought，CoT），通过对LLM提示词中要求「think step by step」，LLM会把问题分解成多个步骤，一步一步思考，使得输出的结果更加准确。还有一些策略，如自一致性（self-consistency）、自优化（self-polish）、自精炼（self-refine）和选择推理（selection-inference）等，也被提出用于提升大语言模型的性能，后续章节会进行详细解析。\n规划是人类应对复杂挑战时的关键策略。对人类而言，规划有助于组织思路、设定目标，并制定达成这些目标的步骤。同样，规划能力对于智能体也至关重要，推理能力是规划模块的核心。通过推理，智能体将复杂任务分解为更易处理的子任务，并为子任务制定合适的行动序列计划。同时，随着任务的推进，智能体能够通过内省来调整计划，确保其与现实情况更好地契合，实现自适应且成功的达成目标。\n规划包含两个阶段：\n计划制定：在计划制定过程中，智能体通常会将总体任务分解为多个子任务。 计划反思：制定好计划后，对其优点进行反思和评估至关重要。智能体利用内部反馈机制，从模型中获取见解，以优化其策略和规划方法。 迁移和泛化能力：\t智能不应局限于特定领域或任务，而应涵盖广泛的认知技能和能力，适应不同的领域和任务。该能力包括以下三个方面：\n未见任务泛化：研究表明，经过大规模语料库训练的语言模型无需进行特定任务的微调，就能展现出零样本泛化能力。具体而言，大语言模型能够根据自身理解，遵循指令完成在训练阶段未遇到的新任务。 上下文学习：众多研究表明，大语言模型能够通过上下文学习（ICL）执行各种复杂任务。上下文学习是指模型从上下文中的少量示例中学习的能力。少样本上下文学习通过将原始输入与几个完整示例作为提示来丰富上下文，从而提高语言模型的预测性能。 持续学习：大语言模型的规划能力在促进智能体持续学习方面的潜力。持续学习涉及技能的持续获取和更新，其核心挑战之一是灾难性遗忘，即模型在学习新任务时，往往会遗忘先前任务的知识。利用大语言模型的规划能力来结合现有技能，可以有效应对灾难性遗忘问题。 2.1.2 感知模块\r​\t人类和动物依靠眼睛、耳朵等感觉器官从周围环境收集信息。这些感知输入被转换为神经信号并发送到大脑进行处理，使得能够感知世界并与之互动。同样，对于基于智能体而言，通过感知模块将这些信息传递给大脑模块进行处理。接收各种来源和模态的信息至关重要，使智能体更好地理解环境、做出明智决策。\n​\t智能体的多模态感知能力，主要包括：\n**文本输入：**基于LLM的智能体已经具备强大的语言生成和理解能力，具备通过自然语言与人类及其它智能体进行交流的基本能力。在文本输入中，除了明确表达的内容外，背后还隐藏着信念、欲望和意图。理解隐含意义对于智能体把握人类用户的潜在和内在意图至关重要，从而提高其与用户的沟通效率和质量。\n**图像输入：**LLMs 本身并不具备视觉的感知能力，只能理解离散的文本内容；而图像输入通常包含有关世界的大量信息，包括物体的属性、空间关系、场景布局等智能体周围的信息。\n针对LLMs无法理解图像中的信息，常见的解决方法有：\n图像字幕：为图像输入生成相应的文本描述，即图像字幕，从而被LLM理解。这种方式并且无需为字幕生成进行额外的训练，从而可以节省大量的计算资源，但字幕生成是一种低带宽方法，在转换过程中可能会丢失大量潜在信息。\n图像编码器和大语言模型结合：研究尝试将图像编码器和大语言模型直接结合，使用 Transformer 对视觉信息进行编码，通过对齐视觉编码器和大语言模型，将图像编码转换为大语言模型能够理解的嵌入。以端到端的方式训练整个模型。这使智能体实现卓越的视觉感知能力，但需要大量的计算资源成本。\n**视频输入：**视频输入由一系列连续的图像帧组成。因此，智能体用于感知图像的方法可能也适用于视频领域，与图像信息相比，视频信息增加了时间维度。因此，智能体需要对视频中不同帧之间时间关系进行理解。\n**听觉输入：**当智能体具备听觉能力时，它可以提高对交互内容、周围环境的感知。有两种方法实现听觉能力：\n利用智能体的工具使用能力，智能体可以使用LLMs作为控制中心，调用现有的工具集或模型库来感知音频信息。例如，AudioGPT充分调用了FastSpeech（文本到语音）、GenerSpeech（风格转换）、Whisper（语音识别）等模型的功能。\n将感知方法从视觉领域迁移到音频领域，对于一段时间内的一段音频数据，可以将其抽象为有限长度的音频频谱图。采用类似于 ViT 的 Transformer 架构来处理音频频谱图图像。通过将音频频谱图划分为补丁，实现对音频信息的有效编码。将音频编码与其它模态的数据编码对齐，来训练整个模型。使得模型实现感知能力。\n**其他输入：**引入现实世界的更多的输入，配备更丰富的感知模块。例如：触觉和嗅觉；感知周围环境的温度、湿度和亮度；眼球追踪、身体动作捕捉，3D 地图、甚至是脑机交互中的脑波信号。此外，还可以引入对更广阔的整体环境的感知：采用激光雷达、GPS、惯性测量单元等成熟的感知模块。 2.1.3 行动模块\r​\t人类感知环境后，大脑会对感知到的信息进行整合、分析和推理，然后做出决策。随后，人类利用神经系统控制身体，从而对环境做出适应性或创造性的反应，例如进行对话、躲避障碍或生火。在智能体的构建中，行动模块接收大脑模块发送的行动序列，并执行行动与环境进行交互。\n智能体的行动能力包括以下几个方面：\n**文本输出：**基于LLM的智能体固有的语言生成能力。它可以生成流畅性、相关性、多样性等方面表现出色的文本。 **工具使用：**工具扩展智能体的行动空间。智能体利用工具与外部环境交互，例如通过搜索工具查询网页信息，来获取即时资讯、调用SQL执行器来查询数据库获取专业知识、 利用Python 解释器来执行复杂数学计算任务等等。 **具身行动：**在追求通用人工智能（AGI）的过程中，具身智能体被视为一个关键范式，它致力于将模型智能与物理世界相结合。智能体的行为不再局限于纯文本输出或调用特定工具来执行特定领域任务。相反，它们应该能够主动感知、理解并与物理环境进行交互，基于语言模型丰富的内部知识做出决策并产生特定行为来改变环境。我们将这些统称为具身行动，它使智能体能够以与物理世界进行交互和理解。 2.2 核心流程\r​\t在对AI Agent的核心概念有了整体了解后，我们可以梳理出AI Agent大致的工作流程。如下图所示：\n​\tAI Agent的工作流程是：AI Agent通过感知模块观察环境收集环境信息（如文本、图片、视觉、3D 地图等多模块信息）；将这些信息传递给大脑模块进行处理，大脑模块基于内部的世界知识、环境的状态及记忆进行思考、规划并决策，生成下一步的行动计划；最后执行具体行动，如调用工具与环境进行交互，行动结果会对环境产生新的影响。这个过程会迭代进行，直到完成目标。\nAI Agent 的工作流程核心包括以下三个模块：\n**感知（Perception）：**指 AI Agent 观察周围环境，并从环境中收集信息，提取有用的数据。Agent 所收集的信息包括文本、听觉、视觉等多模态领域。为了使智能体更好地理解环境，能够接收多模态的信息是至关重要的。 **大脑（Planning/decision）：**大脑模块核心是进行规划和决策，它根据感知模块收集的环境状态信息，结合内部的世界知识和记忆进行规划并做出决策，为达成目标任务生成下一步的行动计划。规划和决策通常由LLM（大语言模型）来承担，除此之外，还包括： World Knowledge：具备丰富的世界知识，由大语言模型在训练中得到并固化在模型参数中。 State：当前环境的状态信息。 Memory：记忆保持了历史的思考决策过程及过去的行动计划。 Reflection：反思是根据当前的环境状态信息和历史行动结果进行总结，生成更高级别抽象的见解，作为经验信息，并存储在记忆中，以优化未来的决策，该功能使得AI Agent具有自我迭代学习能力，如：在错误中总结教训并在后续步骤中完善。 **行动（Action）：**指 AI Agent 执行策略做出的具体行动，比如输出文本，使用搜索工具查询网页信息、调用API访问外部服务等等。行动会产生多样的结果，并影响环境状态。行动结果和环境状态的变化进一步被感知模块观察到，作为下一轮的输入。这个过程不断迭代，直到完成目标。 如果用一个公式来表达 AI Agent，可以表示为：\nAI Agent = Perception（Observation）+ Brain（LLM+State+Memory+Reflection）+ Action（Tool）\n","date":"2026-01-05T00:00:00Z","image":"/p/ai-agent-%E6%A0%B8%E5%BF%83%E6%A6%82%E5%BF%B5/images/image-20250411160708330.png","permalink":"/p/ai-agent-%E6%A0%B8%E5%BF%83%E6%A6%82%E5%BF%B5/","title":"AI Agent 核心概念"},{"content":"第1章 什么是智能体（AI Agent）\r1.1 AI Agent 的起源\r​\tAgent（智能体）是一个历史悠久的概念，在许多领域都有探索。其概念最早在哲学领域探讨，可以追溯到公元前350年左右的亚里士多德（Aristotle）和休谟等思想家，他们在哲学作品中描述一些拥有欲望、信念、意图和具有行动能力的实体，可以称之为智能体。从哲学意义上讲，“智能体”是指具有行动能力的实体。需要注意的是，这些智能体不仅包括个体人类，还包括物理和虚拟世界中的其他实体。重要的是，智能体的概念涉及个体自主性，赋予它们行使意志、做出选择和采取行动的能力，而不是被动地对外界刺激做出反应。\n​\t早在 1950 年代，阿兰·图灵发表了一篇划时代的论文《计算机器与智能》，文中预言了创造出具有真正智能机器的可能性。将“智能”的概念扩展到了人工实体。由于注意到“智能”这一概念难以确切定义，他提出了著名的图灵测试：\u0026ldquo;如果一台机器能够与人类展开对话（通过电传设备）而不能被辨别出其机器身份，那么称这台机器具有智能\u0026rdquo;。这一简化使得图灵能够令人信服地说明“思考的机器”是可能的。阿兰·图灵虽未直接定义Agent，但为衡量机器智能奠定基础，间接推动Agent概念发展‌。\n图灵测试(The Turing Test)起源于计算机科学和密码学的先驱艾伦·麦席森·图灵发表于1950年的一篇论文《计算机器与智能》。该测试的流程是:一名测试者写下自己的问题，随后将问题以纯文本的形式（如计算机屏幕和键盘）发送给另一个房间中的一个人与一台机器。测试者根据他们的回答来判断哪一个是真人，哪一个是机器。所有参与测试的人或机器都会被分开。如果机器能回答由人类测试者提出的一系列问题，且其超过30%的回答让测试者误认为是人类所答，则该机器通过测试。这个测试旨在探究机器能否模拟出与人类相似或无法区分的智能。 尽管图灵测试在学术界引起了广泛的讨论和争议，但它仍然被视为是评估人工智能智能程度的经典方法。 2023年7月25日，Nature刊登新闻指出ChatGPT已经能突破图灵测试，并建议寻求新的人工智能评估方法。 2024年6月，来自加利福尼亚大学圣迭戈分校认知科学家本杰明·伯根（Benjamin Bergen）和卡梅隆·琼斯（Cameron Jones）的最新研究结果表明，越来越多的人难以在图灵测试中区分GPT-4和人类。在上述科学家所做的一项实验中，500名人类与四种AI语言模型进行了5分钟的对话，其中GPT-4在54%的时间里被误认为是人类，这个比例超过了此前版本GPT-3.5的相应比例（50%）。这一结果表明，GPT-4已通过图灵测试。\n​\t在1956年达特矛斯会议上确定了AI的名称和任务，并接受“人工智能”一词作为本领域的名称。同时出现了最初的成就和最早的一批研究者，因此这一事件被广泛承认为AI诞生的标志。\n​\t之后便开启了第一波人工智能的黄金发展时期（1956 - 1974）。这一阶段开发出的程序堪称神奇：计算机可以解决代数应用题，证明几何定理，学习和使用英语。 DARPA（国防高等研究计划署）等政府机构向这一新兴领域投入了大笔资金。\n​\t然而令人惊讶的是，接着经历了两次AI的低谷时期（1974 - 1980）和（1987—1993）。直到20 世纪 80 年代中后期，主流人工智能领域的研究人员对围绕智能体的问题关注甚少。但从那以后,人们对这个主题的兴趣开始急剧增加，主流计算机科学领域的研究人员，以及从事数据通信、并发系统研究、机器人学和用户界面设计的人员，都广泛地讨论智能体。\n​\t1986年，人工智能先驱‌马文·明斯基（Marvin Minsky）在《心智社会》（Society of Mind）中首次将Agent定义为“具有自适应与自治能力的实体”，并将其作为人工智能研究的核心概念‌。这一理论奠定了Agent在AI领域的地位，强调其自主感知、决策与执行能力‌。\n​\t1995 年，英国学者Wooldridge 和 Jennings 人著作和研究成果对推动智能体概念在人工智能领域的发展起到重要作用，在其论文中\u0026laquo;智能体：理论与实践\u0026raquo;中这样定义人工智能：”它是计算机科学的一个子领域，旨在设计和构建表现出智能行为的基于计算机的Agent（智能体）。” 这一观点强调了\u0026quot;智能体\u0026quot;在人工智能的核心地位。当智能体的概念被引入到人工智能领域时，其含义发生了一些变化。在哲学领域，智能体可以是人类、动物，甚至是具有自主性的概念或实体。然而，在人工智能领域，智能体是一个计算实体。由于意识和欲望等概念对于计算实体来说似乎具有形而上学的性质，并且鉴于我们只能观察机器的行为，许多人工智能研究人员（包括艾伦・图灵）建议暂时搁置智能体是否 “真正” 思考或是否 “字面意义上” 拥有 “思想” 的问题。相反，研究人员采用其它属性来帮助描述智能体，如自主性、反应性、主动性和社交能力等属性。也有研究人员认为，智能是 “旁观者眼中的”，它不是一种天生的、孤立的属性。从本质上讲，AI Agent（人工智能体）不等同于哲学意义上的智能体，而是哲学智能体概念在人工智能背景下的具体化。\n​\t其中，Wooldridge 和 Jennings 提出的Agent的定义被广为接受，将 AI Agent 定义为一种计算机系统：该系统处于特定环境之中，能够在这个环境中自主行动，以实现其设计目标。他们还提出，AI Agent 应具备四大关键属性：\n自主性：智能体在没有人类或其他主体直接干预的情况下运行，并且对自己的行动和内部状态有一定的控制权； 反应性：智能体能够感知其周围的环境（环境可能是物理世界、虚拟环境、互联网，或者可能是这些的组合），并及时对环境中发生的变化做出反应； 社交能力：智能体通过特定的通信语言与其他智能体（也可能是人类）进行展开交互； 主动性：智能体不仅仅是对环境做出反应，能够通过主动采取行动来展现目标导向的行为。 ​\t这一定义为后续 AI Agent 的研究与发展奠定了重要基础，使得相关领域的探索有了清晰的概念框架和方向指引。之后，我们通常将人工智能体视为能够使用传感器感知周围环境、做出决策，然后使用执行器采取相应行动的人造实体。\n1.2 AI Agent 的技术趋势\r人工智能体的发展经历了几个阶段，在这里我们从技术趋势的角度简要回顾其发展。\n符号智能体(1950年代-1980年代)：在人工智能研究的早期阶段，这一阶段的 AI Agent 主要基于符号主义理论，其特点是依赖符号逻辑。这种方法使用逻辑规则和符号表示来封装知识并促进推理过程。它们主要关注两个问题：转换问题和表示**/**推理问题。这些智能体旨在模仿人类的思维模式，具有明确且可解释的推理框架，并且由于其符号性质，它们表现出高度的表达能力。这种方法的经典例子是基于知识的专家系统，主要由知识库、推理引擎和解释器组成。然而，符号智能体决策逻辑通常太过死板，依赖人为定义规则和符号，在处理不确定性和大规模现实世界问题时面临局限性。此外，由于符号推理算法的复杂性，很难找到一种高效的算法，能够在有限的时间内产生有意义的结果。\n技术特点‌：\n基于符号逻辑和规则推理（如专家系统）。 核心组件：知识库、推理引擎、解释器。 优点：逻辑透明、推理可解释。 局限：僵化的规则难以处理不确定性，复杂推理效率低。 ‌里程碑‌：\n1956年达特茅斯会议标志符号AI诞生。 1970年代专家系统（如MYCIN）兴起，1980年代商业应用达到高峰。 反应式智能体（20世纪80年代-90 年代）：与符号智能体不同，反应式智能体不使用复杂的符号推理。相反，它们主要关注智能体与环境之间的交互，强调快速和实时响应。这些智能体主要基于感知-行动循环，有效地感知环境并做出反应。这种智能体的设计优先考虑直接的输入-输出映射，而不是复杂的推理和符号操作。相对于符号Agent，反应型Agent所使用的策略更为简单，举个例子，符号Agent类似于编译器，决策引擎中有大量逻辑推演规则，而反应型Agent则就是一堆if else，通过读取环境数据快速进行判断。然而，反应式智能体也有局限性。它们通常需要较少的计算资源，能够实现更快的响应，但可能缺乏复杂的高级决策和规划能力。\n技术特点：\n依赖感知-行动循环，强调实时响应（如机器人避障）。 设计模式：基于条件规则（if-else）或行为分层（Brooks的包容架构）。 优点：低计算开销、快速响应。 局限：缺乏长期规划能力。 里程碑‌：\n1986年Rodney Brooks提出“包容架构”，推动反应式机器人发展。 1990年代应用于工业自动化和简单环境交互任务。 基于强化学习的智能体（1990年代至2010年代）：该领域的主要关注点是如何让智能体通过与环境的交互进行学习，使其能够在特定任务中获得最大的累积奖励 。最初，强化学习（RL）智能体主要基于策略搜索和价值函数优化等基础技术，例如 Q-learning 和序列决策算法（SARSA）为代表。随着深度学习的兴起，深度神经网络与强化学习的结合，即深度强化学习（DRL）。这使得智能体能够从高维输入中学习复杂的策略，从而取得了许多重大成果，比如著名的阿尔法围棋（AlphaGo）和 DQN。这种方法的**优势在于它能够让智能体在未知环境中自主学习，无需人类的明确干预。**这使得它在从游戏到机器人控制等一系列领域中都有广泛的应用。尽管如此，强化学习仍面临着一些挑战，包括训练时间长、样本效率低以及稳定性问题，尤其是在复杂的现实世界环境中应用时。\n技术特点‌：\n通过环境交互最大化累积奖励。 传统方法：Q-learning（1992）、SARSA（1994）。 DRL突破：深度神经网络+强化学习（RL），处理高维输入（如视觉）。 优点：自主学习复杂策略。 局限：样本效率低、训练不稳定。 ‌里程碑‌：\n2013年DQN（Deep Q-Network）在Atari游戏中超越人类。 2016年AlphaGo击败李世石，标志DRL进入主流。 具备迁移学习和元学习能力的智能体（2010年代末至2020年代初）：传统上，训练一个强化学习智能体需要大量的样本数据和较长的训练时间，并且缺乏泛化能力 。因此，研究人员引入了迁移学习来加快智能体对新任务的学习速度，减轻了新任务的训练负担，并有助于知识在不同任务之间的共享和迁移，从而提高了学习效率、性能和泛化能力。此外，元学习也被引入到人工智能智能体中。元学习侧重于学习如何学习，使智能体能够从少量样本中快速推断出针对新任务的最优策略 。当这样的智能体面对新任务时，它可以通过利用已获得的通用知识和策略来快速调整其学习方法，从而减少对大量样本的依赖。然而，当源任务和目标任务之间存在显著差异时，迁移学习的效果可能达不到预期，并且可能会出现负迁移 。此外，元学习所需的大量预训练和大量样本数据使得难以建立通用的学习策略。\n技术特点：\n‌迁移学习‌：跨任务知识复用（如预训练模型微调）。 ‌元学习‌：学习如何学习（如MAML，2017）。 优点：少样本学习、快速适应新任务。 局限：任务差异导致负迁移，元学习预训练成本高。 里程碑‌：\n2015年后迁移学习成为深度学习标准范式（如ImageNet预训练）。 2017年MAML提出，推动元学习在机器人控制等领域的应用。 基于大语言模型的智能体（2020年代初至今）：随着大语言模型展现出令人瞩目的涌现能力（如文本理解和生成、推理能力、泛化能力等），研究人员已开始利用这些大语言模型来构建AI Agent。具体来说，他们将大语言模型作为智能体的大脑或控制器，并通过多模态感知和工具使用等策略来拓展其感知和行动空间。这些基于大语言模型的智能体能够通过思维链（CoT）和问题分解等技术，展现出可与符号智能体相媲美的推理和规划能力。它们还能通过从反馈中学习并执行新动作，获得与环境交互的能力，这与反应式智能体类似。同样，大语言模型在大规模语料库上进行预训练，并表现出少样本和零样本泛化能力，能够在无需更新参数的情况下在不同任务之间实现无缝迁移。基于大语言模型的智能体已被应用于各种现实场景中，比如软件开发 和科学研究。由于它们具备自然语言理解和生成能力，这些智能体能够彼此无缝交互，从而引发多个智能体之间的协作与竞争。此外，研究表明，允许多个智能体共存可能会导致社会现象的出现 。\n技术特点‌：\n以LLM为核心控制器（如GPT-4、PaLM等）。 能力扩展：多模态感知、工具调用（如ChatGPT插件）、多智能体协作。 优点：零样本推理、自然语言交互、社会行为模拟。 局限：幻觉问题、实时行动能力受限。 里程碑：\n2020年GPT-3发布，展示少样本泛化能力。 2023年AutoGPT、BabyAGI、MetaGPT等智能体项目实现LLM驱动的自主任务分解与执行。 多智能体社会模拟研究（如斯坦福《生成式代理》实验，2023）。 1.3 AI Agent 的发展\r​\t1993 年至今，人工智能领域不断发展，出现了许多令人瞩目的 Agent 项目，这些项目在不同时间节点涌现，推动着 AI Agent 技术持续进步：\n1997年：深蓝（由IBM开发）在一场广为人知的比赛中击败了世界国际象棋冠军加里·卡斯帕罗夫，成为第一个击败人类国际象棋冠军的程序。由 IBM 开发，在当年一场备受瞩目的比赛中击败世界国际象棋冠军加里・卡斯帕罗夫，成为首个战胜人类国际象棋冠军的程序，展示了 AI Agent 在棋类游戏领域的强大实力，引起广泛关注 1997年：Windows发布了语音识别软件（由Dragon Systems开发）。 2000年：Cynthia Breazeal 教授开发的第一款可模拟人类面部情感的机器人，拥有眼睛、眉毛、耳朵和嘴巴等面部特征，被称为Kismet。为 AI Agent 在情感交互方面的研究提供了实践案例。 2003年：美国宇航局将两辆火星车（勇气号和机遇号）降落在火星上，它们在没有人类干预的情况下在火星表面航行。 2006年：Twitter，Facebook和Netflix等公司开始利用AI作为其广告和用户体验（UX）算法的一部分。 2010年：Microsoft 推出的Xbox 360 Kinect，这是第一款能跟踪身体运动并转化为游戏指令的游戏硬件。体现了 AI Agent 在游戏交互领域的创新应用，改变了游戏的操作体验。 2011年：IBM 创建的名为Watson的自然语言处理（NLP）计算机被编程来回答问题，在电视智力竞赛节目《Jeopardy》中战胜两位前冠军，展现了 AI Agent 在知识问答和自然语言处理方面的卓越能力 2011年：苹果发布了首款流行虚拟助手Siri，开启了 Agent 的商业化进程，让 AI Agent 走进大众生活，可实现查询天气、设置闹钟等功能，改变了人们与智能设备的交互方式。 2012年：在ImageNet计算机视觉挑战赛中，AlexNet卷积神经网络的深度学习模型取得了第一名，这一成就标志着深度学习在图像分类领域的重大突破。 2016年：AlphaGO（谷歌专门从事围棋游戏的AI Agent）将击败欧洲冠军（范慧）和世界冠军（李世石），并很快被自己的兄弟（AlphaGo Zero）打败。 2017年，Google的研究者在其论文《Attention is All You Need》中提出Transformer，主要用于处理序列数据。 2018年，谷歌发布基于Transformer模型的BERT，拉开了大语言模型序幕。 2019年，谷歌AlphaStar在视频游戏《星际争霸2》上达到了Grandmaster，表现优于除0.2%以外的所有人类玩家。 2019年，OpenAI发布GPT-2的自然语言处理模型，并分别在2020年和2022年发布了GPT-3、DALL·E 2及GPT-3.5大语言模型，随后于2022年11月推出的一个人工智能聊天机器人程序ChatGPT，ChatGPT的火爆为AI Agent在大语言模型时代的发展与应用提供了新的契机。 2023 年3月，OpenAI 推出的语言模型GPT-4，在语言理解、逻辑推理和创造力方面取得质的飞跃，为后续诸多基于大语言模型的 Agent 项目提供了核心智能支持，推动了 AI Agent 的发展。 如 Microsoft 365 Copilot、 Microsoft Security Copilot、GitHub Copilot、 Adobe Firefly 等，让 AI 成为了办公、代码、设计等场景的“智能副驾驶”。 2023年3月，微软正式宣布推出Microsoft 365 Copilot让AI成为了办公、数据可视化等场景的“智能副驾驶”。 2023 年3月底：开源项目 AutoGPT（由 Toran Bruce Richards 在 GitHub 上发布），这是一个实验性的开源应用程序，基于 GPT-4 和GPT-3.5 语言模型，具备自主思考和任务规划能力，实现用户设定的目标。用户只需为AutoGPT提供一个目标，它能自动将目标分解为多个子任务，自主调用各种工具和资源，如搜索引擎、读写文件、浏览网页、代码编辑器等，不断迭代完成复杂任务（诸如撰写商业计划书、开发软件等）。作为完全自主运行的最早示例之一，开启了AI Agent 自主执行复杂任务的新模式。 2023 年 4 月， 斯坦福大学的研究者们发表了名为《 Generative Agents: Interactive Simulacra of Human Behavior》 的论文，展示了一个由生成式代理（ Generative Agents）组成的虚拟西部小镇，开启了生成智能体之路。 在小镇上，生活着 25 个模拟人类行为的生成式 AI Agent。它们会在公园里散步，在咖啡馆喝咖啡，和同事分享当天的新闻。 甚至举办情人节排队，自动传播派对邀请的消息，结识新朋友，互相约对方一起去参加派对。 这种 Agent 具有类似人的特质、独立决策和长期记忆等功能。在这种合作模式下， Agent 不仅仅是为人类服务的工具，它们也能够在数字世界中与其它 Agent 建立社交关系，涌现了社会现象。 之后，基于大语言模型（LLM）的 AI Agent呈雨后春笋般涌现，出现了如GPT - Engineer、BabyAGI、autoGen、MetaGPT 等诸多项目。这些项目的爆发，不仅推动 LLM 的发展与应用跃入全新阶段，还促使 LLM 在创业实践与实际落地层面朝着 AI Agent方向大步迈进 。 2025年3月，由中国初创公司Monica.im开发的首款通用型AI Agent产品Manus,引发了广泛关注和热议，该款产品能独立思考、规划并执行复杂任务，它不仅提供建议或答案，还能直接交付完整的任务成果。Manus采用了Multiple Agent（多智能体）架构，能够在虚拟环境中调用各类工具，执行编写及运行代码、浏览网页、操作应用等任务。在 GAIA 基准测试中取得 SOTA 成绩，性能超越 OpenAI 同层次大模型。 ​\t至此AI Agent就进入了大众的视野，正式迈入快速普及和落地的阶段，无论是日常工作、学习、生活，还是娱乐休闲等方面，AI Agent 都展现出了巨大的潜力和价值，正在快速融入并改变着人们的生活方式和社会的运行模式 。\n1.4 AI Agent 的定义\r​\tAI Agent 目前没有完全统一、明确的定义，但不同时期的研究人员或机构从不同的角度给出了智能体的定义，较为常见和有代表性的如下：\n1995年，著名智能体研究学者Wooldridge 和 Jennings 在其论文中\u0026laquo;智能体：理论与实践\u0026raquo; 中提出“弱定义”和“强定义”二种定义： 弱定义智能体是指一种基于硬件（但更常见的是基于软件）的计算机系统，该系统具备以下特性：自主性（在没有人类或其他主体直接干预的情况下运行）、反应性（能够感知其周围的环境，并及时对环境中发生的变化做出反应）、社交能力（通过通信语言与其它智能体进行交互）、和主动性（主动采取行动来展现目标导向的行为）。 强定义智能体是指不仅具有弱定义中的基本特性，在概念化或实现时还会使用一些通常更多应用于人类的概念。如知识、信念、意图和义务等心理概念来描述智能体。有时还会涉及到具有移动性、善意性、真实性、理性等其它特性。 1997年，Franklin 和 Graesser则把智能体描述为：“智能体是一个位于环境中并作为环境一部分的系统，它能够感知环境，并随着时间的推移，为实现自身目标而对环境采取行动，进而影响其所感知到的事物。” 2003年，由Stuart J. Russell和Peter Norvig合著的经典教材《人工智能:一种现代方法》中对Agent的定义是：Agent是指任何可以被视为通过传感器感知环境，并通过执行器作用于该环境的实体。这个定义强调了Agent与环境交互的能力，包括感知环境、采取行动以及通过执行器影响环境。 2024 年， Google 团队发布的一份 《Agents 白皮书》中描述：智能体可以被定义为一个试图通过观察世界并利用其工具来达成目标的应用程序。与传统的生成式AI模型不同，智能体具有自主性，能够在没有人类干预的情况下独立行动。它们不仅能够根据明确的指令执行任务，还能在没有明确指令的情况下，通过推理来决定下一步行动。 2024年12月，anthropic公司发布的“Building effective agents”论文中将两种类型都归纳为智能体系统：1.将智能体定义为完全自主的系统，这些系统能够在较长时间内独立运行，使用各种工具来完成复杂任务；2.将智能体用来描述遵循预定义工作流的实施方案。但在架构上，区分了工作流(workflows)与智能体(agents)之间的重要区别： 工作流(workflows)：是通过预定义代码路径来协调LLM和工具的系统。\n智能体(agents)：是LLM根据自身需求指导自身处理过程和工具使用的系统，保持对任务完成方式的控制。\n​\t以上对AI Agent的定义都有几个共同点：能够感知环境；自主性（能够在没有人类干预的情况下独立行动）；规划决策能力（通过推理来决定下一步行动）；行动能力（利用其工具来达成目标）；社交能力（通过通信语言与其它智能体进行交互）。\n​\t总体上来说，可以对AI Agent定义为：‌**AI Agent（人工智能代理）是一种能够感知环境、自主进行决策并利用工具采取行动，以达成目标的智能实体。**它具备自主性、反应性、推理决策、主动性和学习能力等关键特性。拥有明确的目标或任务，能够在没有人类直接干预的情况下运作，对周围环境和接收到的信息作出及时响应，通过经验不断学习以改进自身的性能和策略‌，自主的完成目标任务。\n​\t通常，当AI工具具备以下特征时，则可被视为AI Agent：\n**自主性（Autonomy）：**能够独立执行任务，而无需人工干预。 **感知（Perception）：**通过各种传感器（如摄像头或麦克风）感知和解释它们所处的环境。 反应（Reactivity）：指对环境中的即时变化和刺激快速做出反应的能力。这意味着智能体能够感知周围环境的变化并立即采取适当行动。 推理和决策（Reasoning and decision-making）：可以分析数据、推理、规划并做出决策，决定下一步行动方案，以完成目标。 主动性（Pro-activeness）：围绕特定目标主动采取行动，能够在与环境的交互过程中不断学习、调整行动策略，以更好地实现目标。 学习力（Learning）：随着时间的推移和数据的积累，AI Agent 能够不断学习，持续优化自己的行为和决策能力。 社交能力（Social ability）：指智能体通过某种通信语言与其它智能体或人类进行交互的能力，例如理解和响应自然语言、识别语音以及通过文本交换消息。 1.5 AI Agent 与大语言模型(LLM)的区别\r​\t与人类作对比，大语言模型(LLM)如人类的大脑可以进行思考分析、推理决策，但没有感官（视觉、听觉、味觉等）、四肢（手和脚）及工具，无法观察环境并与世界互动。AI Agent相当于给大语言模型按上了感官和四肢，可以感知周围环境、并使用工具与环境交互、影响环境。扩展了大语言模型的感知空间和行动空间，并且还可有记忆能力，能对过去的行为进行反思、累积经验，不断迭代学习以优化未来的规划和决策。\n​\t举个例子，假设你想要规划一次旅行，分别向 AI Agent 和大语言模型寻求帮助：\n大语言模型：你问它 “我想去北京旅行，有什么建议？” 它会给你列出北京的一些著名景点，如故宫、天安门、长城等，还可能会告诉你一些当地的特色美食，以及推荐一些酒店。但如果你接着问它 “怎么从酒店到景点？”“景点门票怎么买？” 它就会分别针对这些问题逐一回答，不会主动将这些信息整合起来，也不会帮你进一步规划具体的行程安排，更不会帮你直接预订酒店和门票。因为普通 AI 通常只是被动地根据你的问题提供相应的信息，不会自主采取行动或进行深度的任务规划。 AI Agent：你告诉它 “我想去北京旅行，帮我规划一下行程”。它不仅会像普通 AI 一样提供景点、美食、酒店等信息，还会主动根据你的时间、预算等因素，规划出详细的每日行程安排，比如第一天上午去故宫，下午去天安门，晚上去吃北京烤鸭，并且帮你查询好从酒店到各个景点的交通路线。它还会调用航班搜索 API 帮你查询往返机票，调用酒店预订系统帮你预订合适的酒店，调用景点票务系统帮你购买门票等。此外，AI Agent 还会记住你可能提到的一些偏好，比如你喜欢安静的酒店，下次你再咨询旅行相关问题时，它会优先考虑安静的酒店进行推荐。这是因为 AI Agent 具有自主性、能使用工具、有记忆能力，可根据目标主动思考并执行一系列复杂的任务，为你提供更全面、个性化的服务。 为了更清晰地理解AI Agent与大语言模型的区别，从以下几个方面进行对比：\n大语言模型（LLM） AI Agent 知识范围 仅限于训练数据中包含的内容。 不仅包含训练数据中的内容，可通过工具接入外部系统获取扩展知识。 记忆能力 仅保留当前会话输入的上下文文本（即短期记忆），受限于模型内部的上下文机制。 不仅包括短期记忆，还包括长期记忆（即长时间保留的信息，一般是指外部知识库，通常用向量数据库来存储和检索）。 会话管理 仅能进行单次查询响应，除非特别设计，否则无法维护会话历史和上下文连续性。 能够维护完整会话历史，支持用户连贯的多轮对话。 工具使用 不具备内置工具调用能力。 直接支持工具集成，可以运用工具与外部环境交互（如调用API、搜索引擎、数据库查询工具等） 规划能力 无内置逻辑处理层，需要用户通过简单问询或利用CoT、ReAct等推理框架构建提示词来引导。 具备完整的认知架构，能够集成CoT、ReAct等框架，自主分解任务并动态调整策略‌。 注：在智能体语境中，一轮对话指的是系统接收一个查询并生成一个响应的完整交互过程。\n","date":"2025-12-25T00:00:00Z","image":"/p/%E4%BB%80%E4%B9%88%E6%98%AF%E6%99%BA%E8%83%BD%E4%BD%93ai-agent/b78c6838a16f5c957a661e5093a39366.jpeg","permalink":"/p/%E4%BB%80%E4%B9%88%E6%98%AF%E6%99%BA%E8%83%BD%E4%BD%93ai-agent/","title":"什么是智能体(AI Agent)"}]