跳转至

美团Agent面试

Agent 项目的核心架构是怎么样的?

我们的 Agent 项目是一个面向复杂任务的多模态智能体系统,其核心架构围绕“感知-记忆-规划-执行”四层展开,并具备可扩展的工具生态和自我进化能力。

整体架构分层:

(1)感知层(Perception)

  • 以多模态大模型(如 Qwen-VL-Max、GPT-4V)为核心,负责统一理解文本、图像、音频等多模态输入。

  • 包含一个轻量级意图分类器和实体抽取模块,用于快速识别用户意图和关键槽位,加速后续处理。

  • 支持文件解析(PDF、Office、图片等),将非结构化数据转化为可理解的描述和元信息。

(2)记忆层(Memory)

  • 短期记忆:当前会话的完整对话历史、已执行的工具调用结果、中间推理步骤。以 KV Cache 和 token 序列形式存储,受上下文窗口限制。

  • 长期记忆:基于向量数据库的多模态 RAG 系统,存储用户偏好、历史任务经验、领域知识文档、常用工具使用模式等。支持跨会话的记忆检索。

  • 工作记忆:任务执行过程中的临时变量、中间文件、状态快照,类似“草稿纸”。

(3)规划与决策层(Planning & Reasoning)

  • 采用 ReAct 范式作为基础循环,结合 Plan & Execute 的宏观规划能力。

  • 核心是一个可插拔的决策引擎,支持不同的提示策略(Chain-of-Thought、Tree-of-Thoughts、Self-Consistency)。

  • 包含一个任务分解器,能将复杂用户目标拆解为子任务序列,并动态调整计划。

  • Skill 机制正是这一层的关键创新——它将可复用的流程、知识和约束封装为技能,使 Agent 能像调用工具一样调用“能力”。

(4)执行层(Execution)

  • 工具调用框架:基于 MCP(Model Context Protocol)的标准化工具接口,支持 REST API、CLI 命令、Python 代码执行、数据库查询等多种工具类型。

  • 沙箱环境:代码执行、文件操作等在隔离的沙箱中完成,保证安全。

  • 动作执行器:将模型生成的 action(如 function call)解析并分发给对应的工具或技能,收集结果后反馈给决策层。

  • Human-in-the-loop:在涉及高风险操作或模型不确定时,自动暂停并请求人工确认。

(5)基础设施层

  • 基于 LangGraph 构建状态图,管理 Agent 的复杂控制流和状态持久化。

  • 分布式任务队列、日志与监控、模型网关(统一管理多个 LLM 的调用和 fallback)。

  • 评估与反馈闭环:记录每次任务执行轨迹,用于离线评估和模型优化。

这一架构实现了多模态理解、复杂任务规划、工具使用、长期记忆和持续进化的有机统一。


为什么用 LangGraph?有什么缺点?是否过度设计?

为什么选择 LangGraph:

  • 显式状态管理:Agent 任务往往涉及多步、多分支的复杂决策流程,LangGraph 通过有向图来显式建模状态和状态转移,使得流程清晰、可调试、可审计。相比隐式的 ReAct 循环,它更容易处理条件分支、循环、并行和中断恢复。

  • 持久化与检查点:LangGraph 内置检查点机制,可将 Agent 的每一步状态持久化。这对于长时间运行的任务至关重要,允许随时暂停、恢复,并支持调试时回溯到任意历史步骤。

  • 人机协同:LangGraph 原生支持在图中插入“中断”节点,等待人工审批或输入后再继续执行,完美契合我们 Human-in-the-loop 的需求。

  • 可组合性:可以将多个子图(Subgraph)组合为复杂的 Agent 行为,例如将“检索”、“推理”、“工具执行”封装为独立的子图,再在顶层编排。

  • 生态兼容:与 LangChain 生态无缝对接,能直接利用其丰富的工具、模型 I/O 和回调机制,降低开发成本。

缺点与问题:

  • 学习曲线陡峭:图状态、节点、边、条件路由等概念需要团队深入理解,初期开发效率较低。

  • 黑盒调试困难:当图结构复杂时,执行流程可能在意料之外的分支中循环或死锁,日志虽然丰富但难以快速定位根因。

  • 性能开销:LangGraph 的状态序列化/反序列化、检查点写入在频繁交互时可能成为瓶颈,对于简单对话任务显得有些“重”。

  • 版本管理与热更新:图中定义的逻辑作为代码发布,当 Agent 需要热更新行为时,图结构的修改需要重新部署,不如纯 LLM 驱动那样灵活。

  • 过度设计风险:对于简单的单轮工具调用或固定流程,LangGraph 的图抽象确实是过度设计。我们团队曾把原本几十行 ReAct 循环能解决的事情用复杂的图实现,导致维护成本不必要地增加。

是否过度设计?

对于我们的多步、多工具、有人工审批的复杂 Agent,使用 LangGraph 是合理的。但我们也会反思:对于明确的、流程化的任务,可以使用更轻量的编排(如 Dify 的工作流或简单的 Python 循环),避免“为图而图”。关键是按任务复杂度分而治之——简单任务直接 ReAct,复杂长任务借助图状态管理。


为什么不直接用 Claude Code 或公司内部 Agent?

为什么不直接用 Claude Code:

  • 闭源与数据安全:Claude Code(Anthropic 的编码代理)会将代码和上下文上传到云端,对于企业内部的敏感代码库和业务逻辑,这不可接受。我们需要完全私有化部署和可控的数据流。

  • 可定制性不足:Claude Code 是一个通用编码助手,其工具链、系统提示、安全策略是预定义的。而我们的 Agent 需要对接内部特有的工具、数据库、知识库和业务流程,深度定制不可避免。此外,我们需要精细控制 Agent 的行为边界,比如某些操作必须经多人审批。

  • 成本与依赖风险:长期依赖外部闭源 API,成本不可控,且随时可能因策略变更而中断服务。自研 Agent 可以与公司内的模型训练、部署基础设施深度整合,实现成本可控和自主演进。

  • 领域特殊性:我们的任务并非纯代码,而是涉及多模态理解、业务流程自动化、专业文档处理等,这些跨领域的复杂编排远超出 Claude Code 的设计范畴。

为什么不直接用公司内部 Agent:

  • 功能与架构不匹配:公司内部已有 Agent 可能面向其他产品线,其工具集、安全沙箱、记忆策略、评估标准等与我们的多模态任务需求不匹配。改造已有 Agent 的成本可能高于新建。

  • 技能机制缺失:我们创新性地设计了 Skill 机制 来封装可复用的领域能力,而内部现有 Agent 大多只支持基础的 function calling,缺乏这种高级抽象。这决定了我们必须自研核心编排层。

  • 隔离与稳定性:作为探索性项目,我们需要充分的灵活性和快速迭代能力,不受内部平台稳定性限制和发布周期约束。自研使我们能独立控制版本和实验范围。

  • 技术栈一致性:我们选用 LangGraph + MCP + 多模态 LLM 的技术组合,与内部已有 Agent 的框架可能完全不同。技术栈的统一对于团队长期维护至关重要。

综合来看,自研 Agent 是为了在数据安全、深度定制、核心创新(Skill)和长期可控性上获得最大自由度。


Skill 机制具体是做什么的?

Skill 是我们 Agent 架构中的核心抽象概念,位于工具调用之上,是一种可复用的、封装的、包含领域知识的“能力包”。它不仅告诉 Agent “可以做什么”,更重要的是告诉 Agent “如何做”、“什么时候做”以及“做完之后如何检查”。

一个 Skill 包含以下要素:

  • 元数据描述:名称、版本、触发条件、适用场景、所需的权限和资源。

  • 操作指令:自然语言描述的执行流程、步骤和注意事项,直接作为 system prompt 的一部分注入到 Agent 的规划中。这相当于给 Agent 内建了一份“专家操作规程”。

  • 工具集合:Skill 可以绑定一组专属的工具或 API,例如一个“数据可视化” Skill 会绑定图表生成工具、数据聚合工具等。

  • 输入输出规范:明确的 JSON Schema,定义 Skill 需要的输入参数和输出的结构。

  • 前置条件与校验规则:执行前必须满足的状态(如某文件必须存在),以及执行后用来验证成功的断言。

  • 示例与边界情况处理:包含了正确执行的轨迹和常见错误的恢复策略。

Skill 的工作方式:当 Agent 解析用户意图后,如果匹配到某个 Skill,该 Skill 的完整描述和指令会被动态加载到模型的上下文中,Agent 会依照 Skill 规定的流程去规划和执行,而不是每次从零开始推理。这使得 Agent 的行为在特定任务上更加稳定、可控、高效。

与普通 function calling 的区别:Function 只是“输入-输出”的黑箱工具;Skill 则是一个有状态、有流程、有校验的“智能子程序”,它能指导 Agent 如何合理组合多个工具来完成复杂目标,实现了知识的固化与传递。


你在支持 Skill 机制这方面具体做了哪些开发工作?你的 Skill 里面会放代码吗?

开发工作:

  • Skill 注册中心:设计并实现了一个基于 YAML/JSON 的 Skill 定义规范和解析器。开发者可以通过声明式的方式定义 Skill 的元数据、指令、参数 schema、依赖工具等,系统自动完成注册和版本管理。

  • 动态 Prompt 组装器:实现了一个模块,根据当前任务上下文,将匹配到的 Skill 的完整描述(指令、参数、示例)动态拼接到大模型的 system prompt 中。并采用分层注入策略——核心 Skill 信息置于系统提示前端,详细示例置于后端,避免占用过多的上下文窗口。

  • Skill 生命周期管理器:编写了 Skill 的激活、挂起、恢复和终止逻辑。一个 Skill 在执行过程中可能被更高优先级的中断,或者自身触发子 Skill,需要状态保存和恢复。

  • 执行沙箱集成:为 Skill 的执行提供了一个隔离的 Python 沙箱环境,Skill 可以安全地调用内部 API、执行轻量级脚本、进行数据转换,而不会影响宿主系统。

  • 校验与反馈机制:为每个 Skill 添加了自动化的结果校验步骤(如 JSON Schema 验证、业务规则检查),并将校验结果反馈给 Agent,使其能够自我纠正或请求人工干预。

  • Skill 市场与共享:搭建了内部 Skill 仓库,允许团队成员发布、发现和复用 Skill,并集成了权限控制和使用统计。

Skill 里面会放代码吗?

会,但仅限于声明式配置和轻量级逻辑。 我们的 Skill 定义文件中可以包含小的代码片段(如 Python 函数),用于:

  • 前置条件检查:例如检查输入文件是否存在于指定路径。

  • 数据转换:将工具返回的原始 JSON 转换成 Skill 需要的标准格式。

  • 简单的业务规则:例如“金额必须大于零且不超过限额”。

更重的、带状态的和有外部依赖的代码则封装为独立的工具或微服务,通过 MCP 或 API 由 Skill 调用。Skill 本身更像是一个“指挥者”,它主要包含指令和流程描述,复杂逻辑留在外部工具中实现,保持 Skill 的轻量与可维护性。


MCP 和 Skill 的区别是什么?和 CLI 的区别呢?

MCP(Model Context Protocol) 是由 Anthropic 提出的一个开放协议,用于标准化模型与外部工具、数据源之间的交互。它定义了一套客户端-服务器架构,使得 Agent 能够以统一的方式发现和调用各种工具。

MCP 与 Skill 的区别:

查看内嵌表格

简单说:MCP 解决“怎么连”,Skill 解决“怎么做”。我们的 Agent 同时使用两者:工具通过 MCP 接入和调用,Skill 则告诉 Agent 如何聪明地使用这些工具来达成目标。

与 CLI 的区别:

CLI(命令行接口)是操作系统中用户与程序交互的一种方式,通过输入文本命令执行特定程序。

  • 层面不同:CLI 是人机交互的界面形式,而 Skill 是 Agent 内部的能力抽象。

  • 执行主体不同:CLI 由人直接输入指令,Skill 是由 Agent 自主调用和遵循。

  • 可组合性:CLI 命令通常需要人工按顺序执行;Skill 内部可以组合多个工具(包括 CLI 工具),由 Agent 自动编排执行步骤。

  • 知识携带:CLI 不带领域知识,用户必须知道命令参数和用法;Skill 内置了领域知识、最佳实践和校验逻辑。

我们可以将 CLI 工具封装为 MCP 服务,再由 Skill 编排调用,实现从“人敲命令”到“Agent 按 Skill 自动执行”的升级。


工具链能否 Skill 化?

可以,而且这正是 Skill 机制的重要价值之一。 工具链 Skill 化就是将一系列相互关联的工具、执行脚本和检查规则封装为一个可被 Agent 直接理解和使用的 Skill。

具体方式:

  • 对于已有的工具链(如 CI/CD 流水线、数据处理管道),我们将其拆分为独立的原子步骤,每个步骤可能是一个 API 调用、一个脚本或一个人工审批节点。

  • 编写一个 Skill 定义文件,描述:

  • 整体目标:例如“部署一个微服务到测试环境”。
  • 前置条件:代码已合并到测试分支、单测通过。
  • 执行步骤:1) 拉取代码 → 2) 构建镜像 → 3) 推送镜像 → 4) 更新 K8s 配置 → 5) 验证健康检查。
  • 每步对应的工具/命令:通过 MCP 封装的 kubectl 工具、CI 触发 API 等。
  • 回滚与异常处理:如果某步失败,如何回滚,何时通知人工介入。

  • Skill 一旦注册,Agent 只需理解用户意图(如“帮我把这个服务更新到测试环境”),就能加载该 Skill,严格按流程执行,大大降低了对用户专业知识的依赖。

价值:工具链 Skill 化将复杂的运维、数据、测试等流程从“专家手动操作”转变为“Agent 半自动/全自动执行”,实现了运维知识的结构化和可复用,是 AI 赋能 DevOps 的关键路径。


Agent 的循环流程是怎样的?(ReAct 循环)

我们的 Agent 采用 ReAct(Reasoning + Acting) 作为基本交互循环,并在此基础上增加了规划、记忆和校验环节。

标准 ReAct 循环流程:

  1. 输入:Agent 收到用户消息(可能包含文本、图片、文件)。

  2. 思考(Thought):大模型分析当前状态,包括用户意图、对话历史、可用工具和 Skill。它生成一个内部的“思考”,决定下一步该做什么:是直接回答、请求澄清,还是调用工具。

  3. 行动(Action):根据思考的结果,模型输出一个 Action。在实现中,通常是一个带有工具名称和参数的 JSON 结构,例如:

{
  "action": "search_knowledge_base",
  "params": {"query": "Q3财务报告"}
}

也可能是“Final Answer”表示任务完成。

  1. 观察(Observation):系统执行该 Action(调用工具、Skill 或查询数据库),并将返回结果包装为 Observation,追加到对话上下文中。Observation 可能是文本、数据表、错误信息等。

  2. 循环:模型基于新的 Observation 再次进行思考,决定下一步行动,直到输出“Final Answer”或达到最大步数限制。

我们增强的循环:

  • 规划先行:在进入 ReAct 循环前,模型先生成一个粗略的执行计划(Plan),列出子任务及其依赖。ReAct 循环在执行每个子任务时以该计划为指导。

  • Skill 注入:在思考阶段,如果当前子任务匹配某个 Skill,Agent 会加载该 Skill 的指令,使思考和行为更加结构化和可靠。

  • 记忆更新:每轮循环后,重要信息会被提取并写入工作记忆和长期记忆,供后续步骤或跨会话使用。

  • 安全中断:如果某个 Action 涉及高风险操作或模型不确定,Agent 会在 Action 中标记需要人工审批,系统暂停循环直到审批通过。

这一流程使得 Agent 能够透明地展示其推理过程,并在动态环境中灵活适应。


Plan & Execute vs ReAct 两种范式有什么区别?适用场景?

Plan & Execute(规划与执行):

  • 核心思想:Agent 在行动之前,首先生成一个完整的、结构化的计划(Plan),列出所有步骤及其依赖关系。然后按照计划逐步执行,执行过程中较少动态调整计划。

  • 优点:全局最优性强,步骤清晰可预测,适合步骤固定的长流程任务。执行效率高,因为减少了反复思考的开销。

  • 缺点:对变化和不确定性的适应能力弱。如果中间某步失败或环境发生变化,计划可能完全失效,需要重新规划。

  • 适用场景:流程固定、环境稳定、步骤明确的任务,如部署流水线、数据处理管道、标准化的多步推理(如先检索再总结)。

ReAct(推理与行动):

  • 核心思想:Agent 在每一步都进行“思考-行动-观察”的循环。它根据最新的观察动态决定下一步,具有很强的适应性和探索性。

  • 优点:灵活性极高,能处理未知和动态变化的任务。可以从错误中恢复,可以尝试多种策略。

  • 缺点:可能陷入局部最优,反复尝试却无法达成目标。每一步都需要调用大模型,成本高、延迟大。缺乏全局规划可能导致效率低下。

  • 适用场景:开放域问答、探索性任务、交互式对话、需要实时调整策略的复杂问题。

我们如何结合:我们采用“宏观 Plan & Execute,微观 ReAct”的混合模式。对于整个用户目标,Agent 先制定一个高层次的计划(例如:“1. 检索相关文档;2. 分析数据;3. 生成报告”)。对于每个子任务(如“分析数据”),则进入 ReAct 循环,灵活处理可能的数据格式问题或分析工具调用失败。这兼顾了全局效率和局部灵活性。


Agent 死循环问题怎么解决?

Agent 陷入死循环是常见问题,表现为反复调用同一工具无进展、在两个无效动作间来回跳转、或永远不输出 Final Answer。我们采用多层防御策略:

(1)硬限制(安全阀):

  • 最大步数:设置单次任务的最大 ReAct 循环步数(如 30 步)。超过即强制终止,并返回超时错误或部分结果。

  • 总执行时间限制:单任务整体超时(如 5 分钟),防止因外部 API 缓慢导致的长时间挂起。

(2)循环检测与中断:

  • 重复动作检测:记录最近 N 次(如 3 次)的 Action 和 Observation。如果连续执行完全相同的 Action 且 Observation 无实质性变化,判定为循环,强制 Agent 重新规划或请求用户帮助。

  • 状态指纹:将当前上下文(历史的关键信息)哈希化,如果相同状态出现多次,则告警。

  • 无进展检测:如果 Agent 连续几步没有获取到新的有价值信息(通过信息增量评估),或一直在调用工具但最终结果无变化,触发反思或终止。

(3)提示工程与自我纠错:

  • 反思提示:在系统 prompt 中明确告知模型:“如果你连续两次尝试同一方法仍未解决问题,请反思你的策略,尝试不同的方法或向用户询问更多信息。”

  • 注入循环终止指令:当 Agent 遇到困难时,在上下文中追加额外提示,如“你已经尝试多次了,如果无法解决,请告知用户并解释原因。”

  • 使用结构化输出:强制 Agent 输出一段“自我评价”,分析当前进度和下一步的合理性,由解析器发现潜在循环倾向并提前干预。

(4)架构层面:

  • LangGraph 的状态图允许在图中定义超时和循环检测的边,当检测到循环或超时状态时,自动路由到“异常处理”节点,执行恢复或终止流程。

  • 将复杂任务委托给 Skill,Skill 内部有明确的步骤和终止条件,减少了 Agent 自由发挥导致循环的风险。

(5)模型能力保障:使用推理能力更强、指令遵循更好的模型(如 GPT-4、Claude 3.5 Sonnet),从根本上降低陷入无意义循环的概率。

通过“限制-检测-引导-架构”的组合拳,我们将死循环率控制在了极低水平。


Agent 大模型请求的上下文具体是怎么分层组装的?

上下文组装是 Agent 性能的关键。我们的上下文管理遵循分层拼接、动态压缩、优先级控制的原则,确保在有限的上下文窗口内放入最有价值的信息。

分层结构(从高到低优先级):

  • 第0层:不可变的系统级指令(System Prompt)
  • Agent 的角色定义、核心行为准则、安全边界。
  • 输出格式要求(如 JSON 规范、引用格式)。
  • 始终位于上下文最前端,通常固定不变,消耗约 500-1000 tokens。

  • 第1层:动态注入的 Skill 与工具描述

  • 根据当前任务匹配到的 Skill 的完整操作指令、参数 schema、示例。
  • 相关工具的 MCP 描述(函数名、参数、功能简介)。
  • 这部分在每次任务启动或上下文刷新时动态生成,保证了 Agent 具备执行当前任务所需的知识。

  • 第2层:长期记忆检索结果

  • 从向量数据库检索到的与当前用户和任务相关的历史信息、领域文档片段。
  • 会附上来源和相关性分数,让模型知道信息的可靠程度。
  • 长度根据重要性动态截断,通常保留 Top-3 结果,约 1500 tokens。

  • 第3层:任务规划与状态摘要

  • 当前任务的宏观 Plan、已完成的子任务、当前的进度状态。
  • 一个不断更新的“工作记忆摘要”,由模型或专门的摘要模型在每完成一个子任务后更新。
  • 它使得模型无需从头阅读历史即可快速了解“我正在做什么、做到了哪里”。

  • 第4层:近期对话与执行轨迹(滑动窗口)

  • 保留了最近 K 轮完整的 ReAct 循环(Thought, Action, Observation)。
  • 对于较早的轨迹,使用摘要模型将其压缩为单条“摘要记忆”,只保留关键决策和结果。
  • 使用滑动窗口,超出窗口的原始记录被移除,但其信息已融入摘要。

  • 第5层:当前用户输入与待处理动作

  • 当前轮次的用户消息、上传的文件/图片的多模态 token。
  • 如果处于 ReAct 循环中,还包括上一步的 Observation。

组装流程:

每次调用大模型前,上下文组装器按照“L0 → L1 → L2 → L3 → L4 → L5”的顺序拼接所有 token,并实时统计总长度。若超过模型上下文窗口限制(如 128K),则从低优先级的层(L4 的早期原始轨迹、L2 的低相关文档)开始逐条删除或压缩,直到符合长度要求。同时确保高优先级的系统指令、当前任务 Skill 和最新用户输入不被裁剪。

通过这种分层架构,我们在有限上下文内做到了“记住该记的,忘掉该忘的”,显著提升了 Agent 在长任务中的一致性和效率。