智能体面试准备(十二):从零写一个 ReAct Agent——不依赖框架的完整可运行实现
智能体面试准备十二从零写一个 ReAct Agent——不依赖框架的完整可运行实现系列写到第十二篇该交作业了。前面讲了 ReAct 范式B2、工具调用B3、状态机编排B9、评估体系B11今天把这些线头收拢从零写一个不依赖任何 Agent 框架、纯 Python 标准库、可以直接运行调试的 ReAct Agent。为什么面试要准备这个因为手写一个简单 Agent正在成为智能体岗位的新型手撕题。框架用得再熟create_react_agent()一行调用证明不了理解深度能白板写出提示词模板→模型输出解析→工具执行→结果回填这个闭环的人才真正把 ReAct 焊进了脑子。更实际的是自己写过一遍之后LangChain/LangGraph 里那些魔法般的抽象会突然变得透明。一、动手前想清楚:ReAct 闭环的五个组件ReAct 的本质一句话让模型交替产生推理Thought和动作Action动作的观察结果Observation回填进上下文循环往复直到模型认为可以给出最终答案Final Answer。拆成工程组件就是五块组件职责关键设计决策提示词模板告诉模型输出格式与可用工具格式约束越明确解析越稳工具注册表管理工具的名称、描述、参数、执行函数描述是写给模型看的质量决定选对率输出解析器从模型自由文本中抽出 Action/Final Answer必须容错格式漂移是最高频故障执行引擎调工具、捕获异常、把结果回填工具报错要回喂给模型而不是直接崩溃循环控制最大步数、重复动作检测、超时没有护栏的循环生产上必出事故这张表本身就是答题框架面试官问设计一个 Agent 要考虑什么按五个组件逐个展开即可。二、完整实现:150 行可运行代码下面的实现有两个刻意设计其一FakeLLM用规则模拟模型行为让整个 Agent无需 API Key 就能跑通你可以先在纯离线环境理解机制再把FakeLLM换成真实的 OpenAI 兼容接口其二所有护栏最大步数、重复动作、解析失败处理都写全了这些正是面试官区分玩具和工程的检查点。从零实现 ReAct Agent纯标准库无需 API Key 即可运行 import re, json, math # 1. 工具注册表 class ToolRegistry: def __init__(self): self._tools {} def register(self, name, description): def deco(fn): self._tools[name] {desc: description, fn: fn} return fn return deco def spec_text(self): return \n.join(f- {n}: {t[desc]} for n, t in self._tools.items()) def run(self, name, arg): if name not in self._tools: return f错误工具 {name} 不存在可用工具{list(self._tools)} try: return str(self._tools[name][fn](arg)) except Exception as e: # 工具异常回喂模型而非崩溃 return f工具执行出错{e} registry ToolRegistry() registry.register(calculator, 计算数学表达式输入如 23*4712) def calculator(expr): if not re.fullmatch(r[0-9\-*/().\s], expr): raise ValueError(仅支持数字与四则运算) return eval(expr) # 已用白名单正则约束演示可用 registry.register(knowledge_base, 查询知识库输入为关键词) def knowledge_base(query): kb {珠穆朗玛峰: 珠穆朗玛峰海拔 8848.86 米, 马里亚纳海沟: 马里亚纳海沟最深处约 10909 米} for k, v in kb.items(): if k in query: return v return 知识库中未找到相关条目 # 2. 提示词模板 PROMPT 尽你所能回答问题。你可以使用以下工具 {tools} 严格使用以下格式 Question: 需要回答的问题 Thought: 你的思考 Action: 工具名必须是 [{names}] 之一 Action Input: 工具输入 Observation: 工具返回结果 ...Thought/Action/Action Input/Observation 可重复多轮 Thought: 我已知道最终答案 Final Answer: 对原问题的最终回答 开始 Question: {question} {scratchpad} # 3. 输出解析器容错 def parse(text): if Final Answer: in text: return finish, text.split(Final Answer:)[-1].strip() m re.search(rAction:\s*(\w)\s*[\n\r]Action Input:\s*(.), text) if m: return action, (m.group(1).strip(), m.group(2).strip().strip()) return error, 无法解析输出请严格按 Action/Action Input 格式 # 4. 模拟 LLM可换成真实 API 调用 class FakeLLM: 按 scratchpad 状态决定下一步模拟真实模型的 ReAct 行为 def invoke(self, prompt): if Observation: 珠穆朗玛峰海拔 not in prompt: return (Thought: 我需要先查珠峰海拔\n Action: knowledge_base\nAction Input: 珠穆朗玛峰) if Observation: 5 not in prompt: return (Thought: 海拔 8848.86 米换算成千米需要除以 1000 再保留两位\nAction: calculator\nAction Input: 8848.86/1000) return (Thought: 我已知道最终答案\n Final Answer: 珠穆朗玛峰海拔约 8848.86 米即约 8.85 千米) # 5. 执行引擎与循环控制 class ReActAgent: def __init__(self, llm, registry, max_steps6): self.llm, self.registry, self.max_steps llm, registry, max_steps def run(self, question, verboseTrue): scratchpad, seen_actions , set() for step in range(1, self.max_steps 1): prompt PROMPT.format(toolsself.registry.spec_text(), names, .join(self.registry._tools), questionquestion, scratchpadscratchpad) output self.llm.invoke(prompt) kind, payload parse(output) if verbose: print(f--- Step {step} ---\n{output}\n) if kind finish: return {answer: payload, steps: step, status: ok} if kind error: # 解析失败回喂纠错 scratchpad f\nObservation: {payload}\n continue tool, arg payload if (tool, arg) in seen_actions: # 重复动作护栏 scratchpad \nObservation: 检测到重复动作请换一种思路\n continue seen_actions.add((tool, arg)) obs self.registry.run(tool, arg) scratchpad f\n{output}\nObservation: {obs}\n return {answer: None, steps: self.max_steps, status: max_steps} # 运行 if __name__ __main__: agent ReActAgent(FakeLLM(), registry) result agent.run(珠穆朗玛峰海拔多少米换算成千米是多少) print(最终结果:, json.dumps(result, ensure_asciiFalse))直接python react_agent.py就能看到完整的 Thought→Action→Observation 三轮循环与最终答案。接真实模型时只需把FakeLLM.invoke换成对 OpenAI 兼容接口的调用stop[Observation:]参数别忘了——防止模型自己编造 Observation这是接真实 API 时最经典的坑。三、代码讲解:六个面试检查点写完不算完要能讲。以下六个点对应面试官大概率的追问为什么 scratchpad 是字符串拼接而不是消息列表原始 ReAct 论文基于 completion 模式把历史拼进 prompt。改成 chat 模式的消息列表tool role 回填就演化成了 Function Calling 形态——两者机制等价Function Calling 由 API 层强制格式解析更稳B3 详述。解析失败为什么不重试而是回喂把错误信息作为 Observation 回喂让模型自我纠正比机械重试更有效——模型看到格式错了通常下一轮就改对。这是 self-correction 的最小实现。重复动作检测为什么用 (tool, arg) 二元组只记 tool 会误伤同一工具不同参数的合法调用记完整二元组精确命中真死循环。B9 讲过的防死循环三层护栏步数/重复/预算这里落了两层。工具异常为什么返回字符串而不抛出Agent 的鲁棒性原则环境的任何反馈都是信息。参数错误回喂给模型它往往能自己修正参数重试直接崩溃则整条任务报废。eval 的安全问题演示里用白名单正则约束了字符集生产必须换ast.literal_eval或专用表达式引擎并放进沙箱——这里主动提一句 Agent 安全B16 会展开显得有安全意识。这个 Agent 怎么测直接套上一篇B11的评估器固定任务集 参考轨迹统计成功率、轨迹匹配度、工具选择准确率。run()返回的结构化结果就是为评估预留的接口。四、从 150 行到生产还差什么差距项本文实现生产要求模型输出FakeLLM 规则模拟真实 API 流式 重试退避状态管理内存字符串持久化断点续跑、审计并发单任务同步异步并发 队列可观测printtracing 埋点langfuse 等安全白名单正则沙箱执行 权限最小化评估无评估集 CI 回归这张表也是极好的答题素材写一个 demo Agent 和上生产差在哪——按行展开即可且每一行都能挂回本系列对应篇目。收尾建议把这份代码放进自己的 GitHubREADME 里写清设计决策与护栏思路面试时直接投屏讲。能讲清为什么这么写的 150 行比能跑起来的 1500 行更有说服力。下一篇 B13 继续实战给 Agent 加上可持久化的记忆系统把 B6 的记忆理论落成完整可运行的工程实现。

相关新闻

JAVA练习374- 找出字符串中第一个匹配项的下标

JAVA练习374- 找出字符串中第一个匹配项的下标

题目概览 给你两个字符串 haystack 和 needle ,请你在 haystack 字符串中找出 needle 字符串的第一个匹配项的下标(下标从 0 开始)。如果 needle 不是 haystack 的一部分,则返回 -1 。 示例 1: 输入:hays…

2026/10/25 6:27:51 阅读更多 →
从1家店到100家店:SD-WAN如何解决连锁企业多门店组网难题

从1家店到100家店:SD-WAN如何解决连锁企业多门店组网难题

近几年 , 连锁零售正在加速向跨区域经营发展。便利店、药店、餐饮、服饰门店往往从一个城市扩展到多个省份 , 门店数量从几十家增加到数百家。与此同时 , 收银系统、会员系统、库存同步、外卖平台、视频监控和电子价签都离不开稳定网络。网络一旦卡顿 , 影响的不只是上网体验 ,…

2026/10/24 16:05:47 阅读更多 →
Xiaomi Miloco实战指南:从零构建智能家居AI管家

Xiaomi Miloco实战指南:从零构建智能家居AI管家

Xiaomi Miloco实战指南:从零构建智能家居AI管家 【免费下载链接】xiaomi-miloco Xiaomi Miloco 项目地址: https://gitcode.com/gh_mirrors/xi/xiaomi-miloco 你是否想过拥有一个能够真正理解家庭环境、识别家庭成员、主动提供贴心服务的AI管家?X…

2026/10/24 23:28:27 阅读更多 →

最新新闻

Electron桌面应用开发实战:从环境搭建到打包发布全流程

Electron桌面应用开发实战:从环境搭建到打包发布全流程

做前端这些年,我大部分时间都在跟浏览器标签页打交道,直到第一次把桌面应用跑起来,才发现原来自己一直被限制在地址栏和刷新按钮围起来的框框里。Electron这个词,最开始是在某段时间频繁出现在同事的讨论里,一个用HTML…

2026/10/26 12:00:21 阅读更多 →
滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

简介:亚太天能T109滑盖智能锁产品说明书是一份PDF格式的官方技术文档,主要面向智能锁安装人员、终端用户及售后维护者,帮助快速掌握产品安装、配置与常见故障处理。全包仅1个PDF文件,大小4.37MB,内容结构清晰&#xff…

2026/10/25 22:36:10 阅读更多 →
PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

上个月我在调试一台高速AGV的驱动系统,整车要求空载跑到8m/s,电机转速已经拉到额定点,驱动器的输出电压也顶到了母线电压极限附近。再往上踩速度指令,转速纹丝不动,三相电流反而开始震荡。那会儿我的第一反应就是&…

2026/10/26 4:52:12 阅读更多 →
C++核心知识点:从内存管理到STL的实战指南

C++核心知识点:从内存管理到STL的实战指南

C 这门语言我前前后后折腾了十几年,用它写过嵌入式驱动、做过游戏服务端,也搭过基础组件库。每次看到新人一头扎进"C 核心知识点"这个搜索词,我其实挺感慨的——这门语言的难点从来不是语法多,而是知识点之间的耦合太深…

2026/10/26 12:00:21 阅读更多 →
T109滑盖智能锁怎么装?安装调试与故障排查全攻略

T109滑盖智能锁怎么装?安装调试与故障排查全攻略

简介:亚太天能T109滑盖智能锁产品说明书以PDF格式发布,适合智能锁用户、安装人员与售后维修人员阅读。内容围绕T109系列指纹密码刷卡锁展开,先介绍前面板、后面板、锁体、机械钥匙、电池盒盖等部件名称与功能,再给出完整安装步骤&…

2026/10/26 5:00:37 阅读更多 →
LLM知识降熵:从信息垄断到理解权下放

LLM知识降熵:从信息垄断到理解权下放

你有没有过这种经历:翻开源码注释、论文摘要或者一份专业报告,每个字都认识,连在一起却像天书。想找人聊聊,身边要么没人懂,要么懂的懒得跟你解释。后来我试着把同一段内容丢给一个大语言模型,几秒钟之后&a…

2026/10/26 3:16:46 阅读更多 →

日新闻

周新闻

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/26 12:00:19 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/25 13:21:54 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/26 8:34:37 阅读更多 →