三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

从ChatModel到智能体:Octo项目架构演进与工程实践全解析

从ChatModel到智能体:Octo项目架构演进与工程实践全解析

1. 项目概述:从单体模型到智能体的必然之路

如果你最近在折腾AI应用开发,尤其是想把手头的ChatModel变成一个能自主思考、执行任务的智能体(Agent),那么“从ChatModel到Agent”这个话题你一定不陌生。我最近主导的Octo项目,就完整地走过了这条演进之路。最初,它只是一个基于大语言模型的对话接口,功能单一;而现在,它已经成长为一个能够理解复杂意图、调用工具、并具备一定记忆和规划能力的智能体系统。这个转变不是一蹴而就的,背后涉及架构思想、技术选型和工程实践的全面升级。今天,我就以Octo项目的第三阶段架构演进为例,拆解其中的核心设计、踩过的坑以及我们为什么最终选择了当前的方案。无论你是刚开始接触Agent概念的新手,还是正在为现有系统增加智能体能力而头疼的开发者,相信这些从实战中总结的经验都能给你带来直接的启发。

简单来说,ChatModel是一个优秀的“语言理解与生成专家”,你问它答,但它缺乏“手”和“脚”,也无法记住之前的对话上下文去执行一个多步骤的计划。而Agent的目标,就是赋予这个大模型“行动力”和“思考力”。在Octo项目中,我们最初使用的是类似Eino ADK这样的开发套件快速搭建了基础对话能力,但随着业务场景复杂化——比如需要它自动处理工单、分析数据并生成报告——单纯的对话模型就显得力不从心了。这就是我们启动架构演进的核心动因:让AI从“聊天机器人”进化为“数字员工”。

2. 架构演进的核心驱动力与设计原则

2.1 为何要演进?识别ChatModel的局限性

在Octo项目初期,我们基于一个强大的ChatModel(例如通过API调用或本地部署的模型)构建了客服问答系统。它表现不错,能准确回答产品信息、处理简单的标准话术。但很快,我们遇到了天花板:

  1. 无法执行外部动作:用户问“帮我查一下订单12345的物流状态”,模型能完美地复述这句话,甚至生成一段“正在为您查询…”的文本,但它无法真正连接公司的订单数据库,执行一次查询操作。它缺少与外界系统交互的“手”。
  2. 缺乏持续性规划能力:对于“分析上周销售数据,找出销量下降最多的产品,并起草一份改进建议邮件”这样的复杂请求,ChatModel倾向于生成一个笼统的步骤列表。它无法自主地、递归地分解任务,先调用数据查询工具,再调用分析工具,最后调用邮件撰写工具,并确保上一步的输出是下一步的输入。
  3. 上下文管理薄弱:在长对话中,虽然可以通过技术手段传入很长的历史记录,但模型本身并不擅长主动提炼和维持对话的“目标”或“状态”。它更像是每次都在重新理解整个上下文,而不是像一个真正的Agent那样,有一个内在的“任务状态机”。
  4. 无记忆与学习能力:每次对话都是独立的。它无法记住“用户张三偏好用邮件接收报告”这样的个性化信息,也无法从历史交互中学习如何更高效地处理类似请求。

这些局限性迫使我们必须引入Agent架构。Agent不是一个具体的库或框架,而是一种设计范式:一个具备感知(Perception)、思考(Reasoning)、行动(Action)和记忆(Memory)能力的自治系统。

2.2 设计原则:构建稳健Agent系统的四大支柱

在规划Octo的新架构时,我们确立了几个核心原则,这些原则直接影响了后续的技术选型和实现细节:

  1. 模块化与松耦合:将大脑(LLM)、工具(Tools)、记忆(Memory)、规划器(Planner)等核心组件设计成独立的模块。这样,我们可以单独升级模型(比如从GPT-3.5切换到GPT-4或本地模型)、增删工具,而不影响其他部分。这也是应对市场上各种Agent框架(如LangChain、LlamaIndex、AutoGen)快速迭代的最佳策略。
  2. 可靠性优先:Agent要操作真实系统,其行动必须可靠、可预测、可回滚。这意味着需要对工具调用进行严格的权限控制、输入验证、异常处理和状态监控。一个胡乱调用删除接口的Agent是灾难。
  3. 透明与可解释性:Agent的决策过程不能是一个黑盒。我们需要记录它的“思考链”(Chain-of-Thought),包括它为什么选择某个工具、如何解析用户指令、每一步得到了什么结果。这对于调试和用户信任至关重要。
  4. 成本与性能平衡:每一次Agent的“思考”都可能涉及多次LLM调用,成本高昂。架构需要支持流式响应、思维过程缓存、以及针对简单任务的“短路”优化(不启动完整的Agent循环)。

基于这些原则,我们开始了从单体ChatModel向Agent系统的重构。

3. 核心架构解析:Octo Agent的组成与协作

3.1 整体架构蓝图

Octo Agent系统的核心架构可以概括为“一个循环,四个核心组件”。这个循环就是经典的“感知-思考-行动”循环(ReAct Loop),而四个组件分别是:

  • Orchestrator(协调器):系统的大脑和总指挥。
  • Tools Registry(工具注册中心):Agent可用的所有“技能”仓库。
  • Memory Bank(记忆库):负责短期、长期和外部知识的存储与检索。
  • Execution Engine(执行引擎):安全、可靠地运行工具调用。
[用户输入] -> [Orchestrator] -> [思考:决定使用工具/直接回答] -> [调用 Execution Engine] ^ | | v +-- [更新 Memory Bank] <-- [获取工具执行结果] <-- [执行 Tools]

3.2 组件深度拆解之一:Orchestrator(协调器)

这是Agent的“思考”中心,其核心职责是理解用户意图,并制定行动计划。我们并没有直接采用某个现成框架的Agent类,而是基于更底层的原理构建了一个轻量级协调器。

核心工作流程:

  1. 意图解析与任务分解:接收到用户输入后,协调器首先调用LLM,判断这是一个简单问答(如“你好”),还是一个需要调用工具的任务(如“查天气”)。对于复杂任务,它会要求LLM输出一个初步的任务分解列表(Task Decomposition)。这里我们使用了思维链(CoT)程序辅助语言模型(PAL)的混合提示工程技术,引导模型输出结构化的步骤,例如:
    { "goal": "为用户查询北京明天的天气并建议是否带伞", "steps": [ {"action": "call_tool", "tool_name": "get_weather", "args": {"city": "北京", "date": "tomorrow"}}, {"action": "analyze", "goal": "根据天气情况判断是否需要带伞"}, {"action": "respond", "goal": "生成友好且包含建议的回复"} ] }
  2. 工具匹配与规划:根据分解出的任务步骤,协调器查询Tools Registry,为每个需要工具的步骤匹配合适的工具,并生成具体的调用参数。这里的一个关键点是工具描述的准确性。我们为每个工具编写了详细、格式化的描述,包括功能、输入参数格式、输出示例,这大大提高了LLM选择工具的准确率。
  3. 状态管理:协调器维护一个当前会话的“任务状态”,跟踪哪些步骤已完成、当前步骤是什么、中间结果是什么。这个状态保存在Memory Bank的短期记忆中。

实操心得:提示工程是关键协调器的智能程度几乎完全取决于你设计的提示词(Prompt)。我们花了大量时间迭代提示词模板,核心技巧包括:

  • 提供大量示例(Few-Shot Learning):在提示词中给出3-5个从用户输入到任务分解再到工具调用的完整示例。
  • 强制结构化输出:要求LLM必须按照指定的JSON格式输出,否则进行重试。我们使用了Pydantic模型来定义输出结构,并在调用后自动进行验证和解析,无效则触发重试或降级处理。
  • 设定角色与边界:明确告诉LLM“你是一个任务规划协调器,只能使用提供的工具,不能编造工具信息”,有效减少了幻觉(Hallucination)。

3.3 组件深度拆解之二:Tools Registry(工具注册中心)

工具是Agent的手和脚。一个设计良好的工具系统是Agent可用性的基石。

工具设计规范:我们为每个工具定义了一个标准的接口:

class BaseTool: name: str # 工具唯一名称,如 “get_weather” description: str # 自然语言描述,用于给LLM理解 parameters_schema: dict # JSON Schema格式的参数定义 func: Callable # 实际执行的函数 async def execute(self, **kwargs) -> str: # 1. 参数校验 (基于parameters_schema) # 2. 执行核心逻辑 # 3. 格式化返回结果(通常为字符串,以便LLM理解) pass

工具类型:我们将工具分为几类:

  • 查询类:如数据库查询、API数据获取(天气、股票)。要求快速、准确,返回结构清晰。
  • 操作类:如发送邮件、创建工单、操作文件。要求有严格的权限控制和操作确认机制。
  • 计算/处理类:如数据统计分析、图像处理、文本总结。这类工具往往内部逻辑复杂。
  • 控制类:如终止任务、切换到人工客服。这是Agent的“安全阀”。

动态注册与发现:Tools Registry是一个中心化的注册表,支持热插拔。新的微服务上线时,可以通过一个管理API自动注册其提供的工具。协调器会定期从注册表拉取最新的工具列表和描述。这种设计使得系统能力可以灵活扩展。

踩坑记录:工具执行的“悬崖”早期我们让LLM直接生成工具调用代码(如Python片段),这存在巨大的安全风险。现在我们的模式是:LLM只输出工具名和参数值,由Execution Engine根据注册信息找到对应的安全函数来执行。绝对禁止动态代码执行。

3.4 组件深度拆解之三:Memory Bank(记忆库)

记忆让Agent有了连续性和个性。我们设计了三级记忆系统:

  1. 短期对话记忆(Conversation Memory):存储当前对话轮次中的原始消息和上下文。通常使用有容量限制的队列实现,如只保留最近10轮对话。这直接提供给LLM作为上下文。
  2. 长期实体记忆(Entity Memory):存储关于用户、产品或会话的结构化事实。例如“用户张三的邮箱是zhangsan@example.com”、“用户偏好接收PDF报告”。这些信息通过对话提取,存储到向量数据库(如Chroma、Weaviate)或传统数据库中,需要时通过语义搜索检索。
  3. 外部知识记忆(Knowledge Memory):这是Agent的“知识库”,包括产品文档、公司规章、操作手册等。我们使用RAG(检索增强生成)技术。将文档切片、嵌入后存入向量库。当用户问题涉及外部知识时,协调器会先触发一个检索动作,将相关文档片段作为上下文注入给LLM。

记忆的读写策略:

  • 写操作:在对话结束后,由一个后台进程分析对话内容,提取关键实体和事实,存入长期记忆。避免在Agent的主循环中同步进行耗时的写入操作。
  • 读操作:在协调器进行意图解析和任务规划时,会并行地查询长期记忆和知识记忆,将检索到的相关信息作为补充上下文。这里的一个优化点是记忆检索的相关性打分,只注入高分片段,避免信息过载。

3.5 组件深度拆解之四:Execution Engine(执行引擎)

这是系统的“安全执行官”。它接收协调器发来的工具调用请求(包含工具名和参数),并负责以安全、可控的方式执行。

核心职责:

  1. 验证与授权:检查当前会话是否有权限调用该工具。我们集成了一套简单的RBAC(基于角色的访问控制)模型。
  2. 参数安全校验:利用工具定义中的parameters_schema,对输入参数进行严格的类型和范围校验,防止SQL注入、路径遍历等攻击。
  3. 执行与超时控制:在独立的异步任务或进程中执行工具函数,并设置超时限制。防止某个工具调用卡住整个Agent。
  4. 结果处理与格式化:捕获工具执行的结果或异常。将成功的结果格式化为LLM易于理解的文本;将异常转换为友好的错误信息,并决定是重试、切换工具还是请求人工帮助。
  5. 审计日志:详细记录每一次工具调用的时间、参数、结果和执行者(Agent会话ID),用于后续分析和审计。

4. 关键实现细节与性能优化

4.1 基于“Octo Loop”的任务流控制

我们内部将Agent的核心循环称为“Octo Loop”,它是对经典ReAct模式的细化实现,特别强调了错误处理和状态持久化。

循环步骤:

  1. 观察(Observe):获取用户输入和当前所有记忆上下文。
  2. 思考(Think):协调器调用LLM,分析现状,决定下一步行动(调用工具A、调用工具B、或直接回答)。LLM的输出必须包含actionthought字段。
  3. 行动(Act):如果决定行动,执行引擎安全地调用工具。
  4. 观察结果(Observe Result):获取工具执行结果。
  5. 评估与循环(Evaluate & Loop):协调器评估结果是否解决了子任务。如果已解决,更新任务状态,并判断总任务是否完成;如果未解决或出错,则重新“思考”,可能选择其他工具或寻求帮助。这里的关键是循环退出条件,我们设置了最大循环次数(如10次),防止陷入死循环。

状态持久化:每一次循环后,当前的任务状态(包括步骤列表、完成情况、中间结果)都会序列化后保存到数据库。这样即使会话中断或服务重启,Agent也能从上次中断的地方继续。这为实现长耗时、可中断的复杂任务奠定了基础。

4.2 多模型路由与降级策略

我们不能依赖单一LLM供应商或模型。Octo架构中,协调器面前有一个模型路由层

  • 路由策略:根据任务类型、复杂度、成本预算和当前负载,动态选择最合适的模型。例如:
    • 简单的意图分类和工具选择,使用小型/快速的本地模型(如通过Ollama部署的Mistral)。
    • 复杂的任务分解和规划,使用GPT-4等高级模型。
    • 流式文本生成,使用Claude或DeepSeek等擅长长文本的模型。
  • 降级策略:当首选模型API调用失败或超时时,自动无缝切换到备用模型。所有模型的调用接口被抽象成统一的generategenerate_stream方法,方便切换。

4.3 流式响应与用户体验

用户不希望等待Agent完成所有内部步骤才看到回复。我们实现了全链路的流式响应:

  1. 思考过程流式输出:将协调器中LLM的“思考”内容(thought字段)实时推送给前端,显示为“正在思考...”、“准备查询天气...”。这极大地提升了交互感和信任度。
  2. 最终答案流式生成:当Agent决定最终回复时,调用LLM的流式生成接口,逐词输出答案。 技术上,我们使用Server-Sent Events (SSE) 将后端的事件流推送到Web前端。

5. 实战中遇到的典型问题与解决方案

在开发和上线Octo Agent的过程中,我们遇到了无数挑战。以下是几个最具代表性的问题及其解决方案。

5.1 问题一:工具选择错误或参数解析不准

这是最常见的问题。LLM可能会误解工具描述,或者将用户指令错误地映射到参数上。

解决方案:

  • 精细化工具描述:不只是说“查询天气”,而是描述为“根据城市名称和日期,查询该地点的天气预报信息。日期可以是‘today’, ‘tomorrow’, 或 ‘YYYY-MM-DD’格式。”
  • 提供输出示例:在工具描述中直接包含1-2个输入输出示例,这对LLM的指导作用非常强。
  • 参数结构化引导:在提示词中要求LLM以特定键值对形式输出参数。例如,必须输出{"city": "北京", "date": "tomorrow"},而不是“城市是北京,日期是明天”。
  • 后置验证与重试:执行引擎在调用工具前,用JSON Schema验证参数。如果验证失败,将错误信息反馈给协调器,让它重新“思考”并调整参数。我们通常允许最多2次重试。

5.2 问题二:Agent陷入循环或执行无关步骤

有时Agent会卡在一个步骤里不断重复,或者开始执行一些与最终目标无关的“想象出来的”步骤。

解决方案:

  • 设定明确的循环上限:如前述,硬性限制最大循环次数(如10次)。
  • 强化目标意识:在每一次循环的提示词中都重申最终的用户目标(“你的最终目标是:XXX”),防止思维漂移。
  • 引入“超时”与“人工接管”工具:当循环次数过多或耗时过长时,设计一个内部机制触发告警,或者提供一个“请求人工帮助”的工具,让Agent可以主动“举手”求助。
  • 监控与干预面板:我们开发了一个管理员面板,可以实时查看所有活跃Agent的思考过程、工具调用历史和当前状态,必要时能手动终止或引导会话。

5.3 问题三:处理模糊或信息不足的用户请求

用户可能说“把它发给我”,但没有指明“它”是什么,或者“发”到哪里。

解决方案:

  • 主动澄清策略:在协调器中设定规则,如果LLM在思考后认为关键信息缺失,则不再继续工具调用,而是直接生成一个澄清性问题反问用户。例如:“您指的是把刚才提到的销售报告发给您吗?请问您的邮箱是?”
  • 利用记忆库:从长期记忆和对话上下文中尽力推断缺失信息。例如,如果用户历史中留过邮箱,那么“发给我”就可以默认使用该邮箱。
  • 设计缺省值和安全假设:对于一些非关键参数,在工具层面设计合理的缺省值。例如,如果没指定日期,天气查询工具默认查询今天。

5.4 问题四:成本控制与响应延迟

复杂的Agent任务可能涉及多次LLM调用和工具调用,成本和延迟会显著增加。

解决方案:

  • 轻量级模型处理简单任务:如前所述,用模型路由将意图识别、简单分类等任务交给小模型处理。
  • 思维缓存:对于常见的、结果不变的用户查询(如“你们公司地址是什么”),将LLM的完整思考过程和最终回答缓存起来。下次遇到相同问题时,直接返回缓存结果,跳过LLM调用。
  • 异步执行与并行化:分析任务步骤间的依赖关系。对于可以并行执行的独立步骤(如同时查询A产品的库存和B产品的价格),使用异步并发来执行,缩短整体耗时。
  • 监控与预算:为每个用户或会话设置Token消耗预算和超时阈值,超出后自动降级为简化模式或终止任务。

6. 技术栈选型与团队技能发展

6.1 Octo项目当前的技术栈

  • 核心语言:Python。生态丰富,在AI和Web开发领域有绝对优势。
  • LLM接口与框架:初期使用LangChain快速原型,后期为了追求更高性能和定制化,转向了更底层的OpenAI SDK、Anthropic SDK的自定义封装,并结合了LlamaIndex来处理RAG部分。对于本地模型,Ollama是一个极佳的容器化部署和管理工具。
  • 工具执行与后端:FastAPI。异步特性好,适合构建需要处理大量IO(LLM API调用、数据库查询)的Agent服务。
  • 记忆存储
    • 短期记忆:Redis。
    • 长期记忆与向量检索:PostgreSQL(pgvector扩展)搭配LangChain的向量存储抽象层。也评估过Pinecone、Weaviate等专业向量库。
  • 消息流与事件驱动:使用Redis Pub/Sub或更专业的消息队列(如NATS)来处理Agent内部组件间的异步事件,以及推动流式响应。
  • 监控与可观测性:OpenTelemetry收集链路追踪和指标,日志集中到ELK栈,关键Agent决策点记录到数据库供分析。

6.2 团队技能树建设

从ChatModel开发转向Agent系统开发,对团队技能提出了新要求:

  • 提示工程:从简单的对话生成,进阶到需要设计复杂的、结构化的、多步的思维链提示。
  • 软件架构设计:需要深刻理解事件驱动、状态机、模块化设计等传统软件工程概念,并将其应用于AI系统。
  • 安全工程:前所未有的重要。必须考虑模型幻觉、工具滥用、数据泄露、提示词注入等新型安全威胁。
  • 测试与评估:如何测试一个非确定性的AI系统?我们建立了基于场景的端到端测试集,并定义了成功率、步骤效率、成本等评估指标。

从ChatModel到Agent的演进,对Octo项目而言,是一次从“功能实现”到“系统构建”的升维。它不再仅仅关注模型输出文本的质量,更要关注整个系统的可靠性、安全性、可扩展性和用户体验。这个过程充满挑战,但当你看到自己构建的Agent能够像一个真正的助手一样,自主完成一连串任务时,那种成就感是无与伦比的。目前,Octo Agent已经稳定处理了数万次复杂任务请求,而我们的架构仍在持续迭代中,例如探索多Agent协作、更复杂的长期记忆机制等。这条路很长,但每一步都走得非常扎实。如果你也正在这条路上探索,我的建议是:从小处着手,定义一个边界清晰的简单任务,构建一个最小可用的Agent循环,然后在此基础上逐步叠加复杂性和可靠性。

← 返回列表