面灵AI→

CVTE AI 原生智能体面经:从 ReAct 到 Runtime

时间
2026-10
来源
牛客网

《面试题目》

  1. 自我介绍,重点讲一下那个 AI 原生智能体项目到底解决了什么问题,目标用户是谁。
  2. 你这个 AI 原生智能体和普通的大模型调用有什么区别?不用 Agent 能不能做?
  3. 为什么不用固定 Workflow 来做?单纯写规则代码加接口调用不能实现吗?
  4. 你项目里的 ReAct 循环具体是怎么实现的?每一步的输入输出格式是什么?
  5. Thought、Action、Observation 这三个环节在你代码里分别对应什么数据结构?
  6. 模型返回的 tool call 格式不合法的时候你会怎么处理?有没有做校验和重试?
  7. 工具调用参数类型错误怎么处理?最终由谁来兜底,是代码还是模型?
  8. 工具调用的超时时间设多少?为什么这么设,依据是什么?
  9. 如果工具 3 秒没返回但任务不能停,你会怎么处理?降级还是换工具?
  10. 多个工具可以并行调用吗?依赖关系怎么判断?谁来决策?
  11. 上下文超过模型窗口后你具体怎么压缩?摘要还是滑动窗口?
  12. 摘要压缩丢了关键信息怎么办?你怎么评估压缩后的信息损失?
  13. 滑动窗口和摘要压缩你选哪个?为什么?有没有结合使用?
  14. 长期记忆怎么存?存什么内容?什么时候写入?谁来触发写入?
  15. 用户说「以后都用中文回答」,你怎么判断这是长期偏好还是临时指令?
  16. 长期记忆和当前指令冲突时听谁的?优先级怎么设计?
  17. 多 Agent 之间怎么通信?共享内存还是消息传递?为什么?
  18. 子 Agent 挂了怎么办?主 Agent 怎么感知?有没有心跳机制?
  19. 多个 Agent 同时修改一个文件怎么防冲突?锁的粒度多大?
  20. 你怎么判断一个 Agent 任务已经完成?终止条件是什么?
  21. 任务循环调用同一个工具,怎么检测和打断?有没有最大轮次限制?
  22. 你怎么做 Agent 的效果评估?指标有哪些?端到端成功率多少?
  23. 失败案例怎么归因?怎么回流?有没有自动化分析?
  24. 如果让你从零设计一个通用 Agent Runtime,你会怎么分层?
  25. 怎么兼容 OpenAI、Claude、通义千问这些不同厂商的 API?怎么抽象?
  26. 工具数量超过 100 个时 Prompt 怎么放?怎么避免上下文爆炸?
  27. 工具描述重叠导致模型选错,怎么优化?有没有分层路由?
  28. 你怎么控制 Agent 的 Token 成本?有哪些具体策略?
  29. 如果线上 Agent 跑一次要 20 分钟,用户等不及了,怎么取消?
  30. 取消机制你实际写过吗?状态怎么清理?会不会有残留?
  31. 你二开的 Agent 执行框架改了什么?为什么改?改完效果怎么样?
  32. 你怎么理解 Harness?它和 LangChain 的区别是什么?
  33. 反问:团队现在最缺什么能力的人?
  34. 手撕:实现一个简化版 ReAct 循环,要求支持工具注册、工具选择、执行、Observation 回注与最大循环次数限制。

《参考解析》

ReAct 在工程上就是一个「模型出结构化动作 → 宿主执行 → 观察结果回注 → 再问模型」的循环,答的时候要落到数据结构。 一轮的消息序列是:system 里放角色约束与工具清单摘要,user 放当前任务与已有的观察,assistant 返回 thought(自然语言推理)与 tool_calls(工具名加参数),宿主执行后再以 tool 角色的消息把结果或错误码拼回上下文。所谓 Thought/Action/Observation 三个环节,落到代码里就是「模型输出里的推理字段 / 工具调用对象(名称 + 参数 JSON)/ 工具返回结构(成功标志、数据、错误码)」。循环的退出条件必须显式:模型不再产出工具调用、任务完成条件被满足、达到最大轮次、或者预算(token、耗时)耗尽。同一个工具用相同参数被连续调用,是模型卡住的典型信号,做法是对「工具名 + 参数」做规范化哈希,重复到阈值就把「这步没有产生新信息」当作 Observation 回灌并强制它换路径或终止。

tool call 的健壮性有三道闸,兜底一定在代码这一侧。 第一道是 schema 校验:用 JSON Schema 或 Pydantic 校验参数的存在性、类型与取值范围,不合法就把校验错误的原文回灌给模型让它重发,并且限制重试次数(两次足够)。第二道是格式容错:模型偶尔把 JSON 包在 markdown 代码块里或多输出一段说明,需要容错解析并对「格式修复次数」打点,这个指标持续升高说明提示词或模型该换了。第三道是语义校验:比如订单号格式对但库里不存在,就返回可读错误而不是抛异常。参数类型错误(该给整数给了字符串 "3")属于第一道闸,绝不能靠模型自觉——让模型自己兜底等于把正确性交给概率,系统层面要有默认值、拒绝执行或转人工的确定性路径。所有错误都要带可判定的错误码回给模型,否则它只会换个说法反复重试。

上下文压缩没有银弹,工程答案是组合拳加度量。 滑动窗口便宜、实现简单,但会硬切掉早期信息;摘要压缩省 token,但摘要模型可能丢掉精确事实(订单号、金额、地址)。常见组合是:system 与工具说明永远保留,最近若干轮保留原文,更早的内容压成摘要,同时把关键实体和已确认的约束抽成结构化字段单独留存(不依赖摘要的措辞),必要时用检索把相关历史片段召回。压缩效果的评估要提前建一套长会话回放集,比较压缩前后在任务成功率、关键槽位召回率、约束违反率上的差异,量化不出来就不要上线压缩策略。长期记忆的写入触发有三类:用户显式指令、跨会话重复出现的偏好、任务结束后的结构化总结;写入的内容要带来源与时间,方便用户纠正和过期失效。

「是长期偏好还是临时指令」这个问题,靠句式判断不可靠,要靠影响面和确认机制。 稳妥做法是把候选记忆写下来并让用户确认一次,或者按影响面分流:只影响本次任务的当临时指令处理,明确说「以后」「每次都」的当长期偏好,但长期偏好的优先级低于当前指令——当前指令永远赢,长期偏好只提供默认值,并且被覆盖时不删记忆、只标记这一次的例外。多 Agent 通信上,共享内存快但耦合强、难水平扩展,消息传递解耦但要自己处理顺序与重投;生产系统常见的是两者结合:状态(任务、步骤、产物)落库,Agent 之间只传事件消息。子 Agent 失败靠心跳或租约加超时感知,处理顺序是重派(要求幂等)、降级(跳过该子任务或换工具)、升级人工,绝不能静默当成功。多个 Agent 同时写一个文件,优先切分工作单元让写集不重叠,不可避免时用乐观并发(版本号 CAS,冲突就重取上下文重做)优于长事务式的大锁;要锁就用带 TTL 的租约锁,粒度到文件或资源而不是整个仓库。

终止条件与死循环拦截必须由宿主控制,不能交给模型自觉。 完成判定分层:能被验证的目标(工具返回的成功状态、断言通过、产物存在)算硬条件;纯语义目标用「模型自评 + 规则校验」双判,且自评只作为信号之一。硬性预算三件套——最大轮次、最大 token、最大墙钟时间——都放在调度器里数,到点就中断并把当前状态落库。无进展检测除了重复工具调用,还可以比较连续两轮的产出差异,差异低于阈值就判定卡死。取消链路也要设计:取消信号要能传播到正在执行的工具(协程取消上下文),未完成的写操作靠幂等键对账,状态清理包括释放锁与租约、回收临时资源、把任务状态置为已取消并落库;真正的残留风险是「已经产生的对外副作用」,只能靠幂等与事后对账解决,而不是靠清理函数。

评估与可观测决定这套系统能不能长期迭代。 端到端成功率必须分层看:单步工具调用(选对工具、参数合法)、多步编排(顺序与依赖正确)、长流程(跨会话、需恢复)三类的难度完全不同,混成一个数会掩盖问题。伴随指标还有工具调用正确率、参数合法率、平均轮次、平均 token、P95 时延、降级率与人工介入率。失败归因按「模型错 / 工具错 / 编排错 / 数据错」分类,并且要能自动回流成回归样本,否则修好的问题会再犯。Trace 需要记录的字段包括请求 ID、每一轮的模型与提示词版本、token 数、工具名与参数摘要、耗时、错误码和最终状态——只有这样,线上排查时才能区分「模型不行」和「工具挂了」。区分方法很朴素:把同一输入换个模型跑,能复现就是编排或数据问题,不能复现就偏模型问题。

工具规模上了百、厂商多了以后,架构上的两个关键词是「分层」和「收敛」。 工具不能全量塞进提示词:一级只给「能力域」和一个检索工具,模型表明需要某个域时再拉取该域的工具 schema,配上向量检索召回 Top-K 与 schema 缓存;工具描述之间的语义重叠是选错的根因,写描述时要让职责互斥,重叠的合并成一个带模式参数的工具,并靠分层路由(先选域再选工具)降低混淆概率。多厂商兼容的通用做法是把差异收敛到适配层:统一内部消息模型,适配器各自负责消息结构、工具 schema 格式、流式事件、推理字段与错误码的映射,上层业务不允许出现按厂商分支的 if-else。成本控制分两条线——省 token(前缀缓存、工具结果截断与结构化、上下文按需检索)和省次数(模型分层路由、并行工具调用、可复用子结果缓存),并统计单任务 token 分布来定位异常长尾。

从零设计 Agent Runtime,分层的依据是「变化频率」而不是「功能名字」。 一个能落地的划分是:接入与会话层(鉴权、限流、会话与多轮状态)、编排层(规划、状态机或图执行、循环控制与预算)、能力层(工具注册表、执行器、沙箱、鉴权与权限)、上下文与记忆层(上下文装配、压缩、长期记忆读写)、模型接入层(多厂商适配、路由与降级)、观测与评测层(Trace、指标、回放与离线评测)、配置与灰度层(提示词版本、模型配置、开关)。这样切的理由是同层内部变化、层间契约稳定:换模型只动接入层,换工具只动能力层,调提示词不影响编排。和 LangChain 这类框架的关系要说清定位差异——框架提供的是积木与抽象(链、工具、记忆的通用实现),而自建 Runtime 关心的是生产属性:可观测、可评测、可取消、可回放、成本可控,很多团队的实际选择是在框架之上包一层自己的运行时,或者只用框架里的局部组件。