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