普通程序员有没有必要学习 Agent 开发?从零手写 ReAct Agent 到 LangGraph 实战
一、先说结论有必要而且窗口期就在当下如果你是一个每天写 CRUD、调接口、改 Bug 的普通后端 / 前端 / 全栈程序员看到「Agent」「智能体」这些词第一反应很可能是“这不就是调一下大模型 API 吗跟我有什么关系”但如果你仔细看 2024—2026 年的招聘需求和技术演进会发现一个明显趋势大模型不再只是一个聊天机器人而是开始接管真实的工作流。能写 API 的工程师很多但能把「大模型 工具 记忆 规划」组合成一个可靠系统的工程师目前仍然稀缺。这篇文章不空谈概念。我会先讲清楚 Agent 到底是什么、和普通 LLM 调用有什么本质区别然后带你从零手写一个 ReAct Agent最后再引入LangGraph做工程化实践。全程代码都可以直接复制运行。二、Agent 到底是什么先厘清三个容易混淆的概念很多人分不清「LLM 调用」「RAG」和「Agent」这会导致学习路线跑偏。概念核心能力一句话理解普通 LLM 调用单次问答你问一句模型答一句无记忆、无外部动作RAG检索增强生成外挂知识库先从文档里检索相关内容再让模型基于资料回答Agent智能体感知—决策—行动循环模型自己决定「调用哪个工具、调用几次、什么时候停」用一个简单的流程图表示 Agent 的工作方式是否用户输入任务LLM 思考现在需要做什么需要调用工具吗调用工具查询/计算/搜索/写文件把工具结果返回给 LLM输出最终答案关键区别在于「循环」和「自主决策」。普通 LLM 调用是一次性的输入→输出。而 Agent 是一个循环模型在每一轮都重新判断「我完成了吗没有的话下一步该做什么」直到任务收敛。这就是为什么 Agent 开发有门槛——它不再是把字符串喂给 API 那么简单而是要设计控制流、工具协议、状态管理、错误兜底。三、为什么普通程序员有必要学三个现实理由3.1 它正在变成「基本功」而非「加分项」回顾几个时间节点2023 年会调 GPT API 就能吹一波2024 年RAG 成了企业知识库项目的标配2025 年之后Agent 开始渗透到客服、运维、数据分析、代码生成、办公自动化等真实场景。当一个技能从「实验室概念」走向「业务系统」对工程化的要求就会指数级上升。而工程化恰好是普通程序员的强项——你不需要成为算法研究员只需要把已有的软件工程能力迁移过来。3.2 岗位和薪资在向「复合型」倾斜目前市面上的 Agent 相关岗位大致分两类算法侧研究模型推理、规划算法、多智能体协作门槛较高工程侧负责 Agent 框架搭建、工具集成、状态管理、性能优化、生产部署门槛并不比你现在的日常工作高多少。普通程序员切入的是第二条路。一个能写出健壮 REST API、理解并发和幂等的后端工程师学习 Agent 开发的速度通常比纯算法背景的人更快因为 Agent 系统的本质就是一个带着不确定性的分布式工作流系统。3.3 它会反哺你的传统技能而不是取代学习 Agent 开发会让你重新审视很多「老问题」函数设计给模型用的工具参数描述要清晰到什么程度状态管理多轮循环中上下文什么时候该裁剪可靠性当模型可能输出格式错误时怎么兜底这些问题本质上和你做后端时思考的「接口设计、缓存策略、异常处理」是同一套逻辑。学会了 Agent你会发现自己对系统设计有了更立体的理解。一句话总结普通程序员学 Agent不是转行是升级。四、动手前需要搞懂的三个核心概念在写代码之前先把三个概念讲透否则后面代码会看不懂。4.1 ReAct 模式ReAct Reasoning推理 Acting行动。它是目前多数单 Agent 的底层范式流程是模型输出一段「思考」然后决定调用某个工具 → 工具执行 → 结果返回模型 → 模型继续思考 → 直到任务完成。4.2 工具Tool协议工具就是「模型可以调用的函数」。但模型不能直接调用函数它只能输出文本。所以通常的做法是你用 JSON Schema 描述工具的参数模型输出一个结构化的「工具调用请求」你的代码解析这个请求并真正执行函数把执行结果再喂回模型。4.3 状态State与消息列表Agent 的「记忆」本质上就是一个消息列表conversation history。每一轮都维护 system prompt、历史 user/assistant 消息、工具调用记录等。这个状态对象的设计直接决定了 Agent 的性能和可靠性。五、上手最小 Agent依赖与整体结构进入实战。我们先用最少的依赖手写一个 ReAct Agent理解底层原理。环境要求Python 3.10一个 OpenAI 兼容的 API KeyOpenAI、DeepSeek、通义千问、Kimi 等都行安装依赖pipinstallopenai我们需要三个文件或者放在一个文件里也行工具定义给模型看的 JSON Schema以及真正执行的 Python 函数Agent 核心循环调用模型 → 解析工具调用 → 执行工具 → 把结果塞回上下文运行入口初始化客户端、定义工具、发起任务。为了贴近真实场景我实现两个工具get_weather(city)查询城市天气用随机数据模拟方便无网络运行calculate(expression)安全计算数学表达式。六、从零手写 ReAct Agent完整代码先贴出完整可运行代码再逐段讲解。# react_agent.pyimportjsonfromopenaiimportOpenAI# 1. 工具的真实实现 defget_weather(city:str)-str:模拟天气查询。真实场景可以替换为调用天气 API。importrandom temps{北京:8,上海:12,深圳:22,杭州:15,成都:14}weathers[晴,多云,小雨,阴]temptemps.get(city,random.randint(0,30))weatherrandom.choice(weathers)returnf{city}今天{weather}气温{temp}°Cdefcalculate(expression:str)-str:安全地计算数学表达式。仅允许数字和基础运算符。importastimportoperator allowed_ops{ast.Add:operator.add,ast.Sub:operator.sub,ast.Mult:operator.mul,ast.Div:operator.truediv,ast.Pow:operator.pow,ast.USub:operator.neg,}def_eval(node):ifisinstance(node,ast.Expression):return_eval(node.body)ifisinstance(node,ast.Constant)andisinstance(node.value,(int,float)):returnnode.valueifisinstance(node,ast.BinOp)andtype(node.op)inallowed_ops:returnallowed_ops[type(node.op)](_eval(node.left),_eval(node.right))ifisinstance(node,ast.UnaryOp)andtype(node.op)inallowed_ops:returnallowed_ops[type(node.op)](_eval(node.operand))raiseValueError(不支持的表达式)try:treeast.parse(expression,modeeval)result_eval(tree)returnstr(result)exceptExceptionase:returnf计算失败{e}# 2. 给模型看的工具 Schema TOOLS[{type:function,function:{name:get_weather,description:查询指定城市的天气。当用户询问天气时调用。,parameters:{type:object,properties:{city:{type:string,description:城市名称如北京}},required:[city],},},},{type:function,function:{name:calculate,description:计算数学表达式。当用户需要算术计算时调用。,parameters:{type:object,properties:{expression:{type:string,description:数学表达式如(35)*2}},required:[expression],},},},]# 工具名 - Python 函数的映射TOOL_FUNCTIONS{get_weather:get_weather,calculate:calculate,}# 3. Agent 核心循环 SYSTEM_PROMPT你是一个智能助手可以调用工具来帮助用户完成任务。 规则 1. 如果需要查询天气调用 get_weather 工具。 2. 如果需要数学计算调用 calculate 工具。 3. 如果不需要调用工具直接回答用户。 4. 一次只能调用一个工具工具结果返回后你可以继续调用下一个工具或直接回答。 defrun_agent(client:OpenAI,user_input:str,model:strdeepseek-chat,max_rounds:int8):ReAct Agent 主循环。 返回最终回答文本。 messages[{role:system,content:SYSTEM_PROMPT},{role:user,content:user_input},]for_inrange(max_rounds):# 第 1 步调用模型允许模型返回工具调用请求responseclient.chat.completions.create(modelmodel,messagesmessages,toolsTOOLS,temperature0.2,)msgresponse.choices[0].message messages.append(msg)# assistant 消息入栈# 第 2 步判断模型是否要求调用工具ifnotmsg.tool_calls:# 没有工具调用说明模型认为任务完成直接返回returnmsg.contentor模型没有返回内容# 第 3 步执行所有工具调用本例一次一个但协议支持并行fortool_callinmsg.tool_calls:tool_nametool_call.function.name# 模型给的参数是 JSON 字符串需要解析argumentsjson.loads(tool_call.function.arguments)print(f[Agent] 调用工具{tool_name}参数{arguments})funcTOOL_FUNCTIONS.get(tool_name)iffuncisNone:resultf错误未知工具{tool_name}else:try:resultfunc(**arguments)exceptExceptionase:resultf工具执行出错{e}# 第 4 步把工具结果以 tool 消息的形式加回上下文messages.append({role:tool,tool_call_id:tool_call.id,content:result,})# 达到最大轮数仍未收敛return已达到最大推理轮数任务可能未完成。# 4. 运行入口 if__name____main__:# 使用 OpenAI 兼容接口换成你自己的 keyclientOpenAI(api_keysk-xxxxxxxxxxxxxxxx,# 你的 API Keybase_urlhttps://api.deepseek.com,# 可换成 openai/通义/kimi 的地址)# 单个简单任务print(*40)print(任务 1查天气)print(*40)answerrun_agent(client,北京明天天气怎么样适合出门吗)print(f最终答案{answer}\n)# 需要多次工具调用的任务print(*40)print(任务 2组合任务)print(*40)answerrun_agent(client,北京今天气温是 8 度上海是 12 度两座城市温差是多少)print(f最终答案{answer}\n)# 不需要工具的任务print(*40)print(任务 3纯问答)print(*40)answerrun_agent(client,什么是 ReAct 模式)print(f最终答案{answer})6.1 逐段解读工具的真实实现与 Schema 是分离的。上面代码里get_weather和calculate是给人执行器看的 Python 函数TOOLS列表是给模型看的描述。模型只能通过 Schema 了解「有哪些工具、参数是什么」然后输出一个 JSON 格式的调用请求。对话状态由messages列表驱动。注意第 2 步里有两处容易忽略的细节模型的assistant消息包含 tool_calls必须原样 append 回 messages工具执行结果必须以role: tool并且带上tool_call_id的形式返回。这两点缺了任何一个API 都会报错或行为异常。这也是 Agent 开发里「状态管理」的核心体现。循环有上限。max_rounds是安全阀。模型理论上可能陷入「反复调用工具、一直不给出最终答案」的状态真实项目中必须设置上限并对超限做降级处理。6.2 运行效果示例任务 1 的输出大致是这样实际结果因模型的随机性会略有差异 任务 1查天气 [Agent] 调用工具get_weather参数{city: 北京} 最终答案北京今天多云气温 8°C。8 度的气温偏低建议穿厚外套或羽绒服出门注意保暖。任务 2 展示了 Agent 的「多步推理」能力 任务 2组合任务 [Agent] 调用工具get_weather参数{city: 北京} [Agent] 调用工具get_weather参数{city: 上海} [Agent] 调用工具calculate参数{expression: 12 - 8} 最终答案北京今天气温 8°C上海今天气温 12°C两座城市的温差是 4°C。注意模型自己拆解了任务先查两地天气再计算温差。这就是 Agent 和普通 LLM 调用的本质区别——它会自主规划执行步骤。七、手写 Agent 的三个隐形坑手写代码能帮你理解原理但生产环境会遇到三个大问题这正是我们引入框架的原因。7.1 工具调用参数解析不可靠模型偶尔会返回非法 JSON、漏参数、参数类型错误。上面的代码直接json.loads遇到坏格式就会抛异常。真实场景需要 try/except 重试 格式纠正。7.2 循环没有「记忆管理」messages.append会让上下文无限增长。一轮复杂任务的 tool_calls 可能产生几千 token 的历史多轮下来成本飙升、还容易让模型「遗忘」早期关键信息上下文窗口溢出。7.3 没有中断与分支现实中的 Agent 不一定是「一直循环到结束」的单线流程。你可能需要在某个节点做人工审核、失败重试、并行分支。手写的 while 循环很快会变成难以维护的意大利面条。所以下一步我们用LangGraph把上面的逻辑「工程化」。八、进阶实战用 LangGraph 构建可控 AgentLangGraph 是 LangChain 团队出的一个有状态、可编排的 Agent 框架。它的核心思想是把 Agent 的每一步建模成图Graph里的节点状态在节点之间传递。先安装依赖pipinstalllanggraph langchain-openai langchain-core下面是功能与手写版基本一致的 LangGraph 实现# langgraph_agent.pyimportjsonfromtypingimportAnnotated,TypedDictfromlangchain_openaiimportChatOpenAIfromlangchain_core.toolsimporttoolfromlanggraph.graphimportStateGraph,START,ENDfromlanggraph.graph.messageimportadd_messages# 1. 定义状态本质是消息列表 classAgentState(TypedDict):# add_messages 会把新消息追加到列表里而不是覆盖messages:Annotated[list,add_messages]# 2. 用装饰器定义工具LangChain 会自动生成 Schematooldefget_weather(city:str)-str:查询指定城市的天气。当用户询问天气时调用。importrandom temps{北京:8,上海:12,深圳:22,杭州:15,成都:14}weathers[晴,多云,小雨,阴]temptemps.get(city,random.randint(0,30))weatherrandom.choice(weathers)returnf{city}今天{weather}气温{temp}°Ctooldefcalculate(expression:str)-str

相关新闻

深入理解 Claude Code:CLAUDE.md、Hooks、Skills、Subagents 全解析

深入理解 Claude Code:CLAUDE.md、Hooks、Skills、Subagents 全解析

1. 引言 Claude Code 是 Anthropic 推出的命令行 AI 编程助手,它不仅能理解自然语言指令,还能通过一系列机制实现高度可定制、可扩展的自动化编码工作流。对于想要真正驾驭 Claude Code 的开发者来说,仅仅会提问远远不够,理解其四…

2026/10/15 0:45:39 阅读更多 →
个人微信二次开发教程:如何通过接口实现微信账号状态监控?

个人微信二次开发教程:如何通过接口实现微信账号状态监控?

单账号掉线自己重连是一回事,多账号运营时把每个账号的状态管起来是另一回事。账号多了,哪个在线哪个掉线、哪个消息发送成功率在下降,靠人一个个看是看不过来的。要把状态监控做成一套指标加告警加看板的体系。一、监控指标——在线率之外还…

2026/10/15 0:44:39 阅读更多 →
个人微信API接口调用实践:如何实现微信消息记录管理?

个人微信API接口调用实践:如何实现微信消息记录管理?

消息记录管理不是"把消息存数据库"就完事。消息量涨起来之后,查询越来越慢、存储成本越来越高、老数据不敢删又占空间。要解决的是存储分层、检索设计、归档清理三个问题。一、存储分层——热数据冷数据分开最近的消息查询频繁,要快&#xff1…

2026/10/15 0:44:39 阅读更多 →

最新新闻

工业机器人坐标系全解析:从原理到标定实操

工业机器人坐标系全解析:从原理到标定实操

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

2026/10/15 1:36:03 阅读更多 →
Bugzilla部署报错couldn‘t create child process: 720002: index.cgi排查指南

Bugzilla部署报错couldn‘t create child process: 720002: index.cgi排查指南

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

2026/10/15 1:36:03 阅读更多 →
Matplotlib图例完全指南:legend()参数、位置与样式全解析

Matplotlib图例完全指南:legend()参数、位置与样式全解析

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

2026/10/15 1:36:03 阅读更多 →
单片机C运行时libspace:启动流程、多任务栈与中断安全实战

单片机C运行时libspace:启动流程、多任务栈与中断安全实战

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

2026/10/15 1:36:03 阅读更多 →
Spring BeanDefinitionRegistryPostProcessor底层注册机制详解

Spring BeanDefinitionRegistryPostProcessor底层注册机制详解

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

2026/10/15 1:36:03 阅读更多 →
CS5211单芯片eDP转LVDS方案:工业显示兼容性落地实践

CS5211单芯片eDP转LVDS方案:工业显示兼容性落地实践

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

2026/10/15 1:35:03 阅读更多 →

日新闻

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

1. 为什么选择容器化方式部署 LoRa 网络服务LoRa 物联网项目落地时,网络服务器是整个链路的核心枢纽。它负责接收网关上报的数据包、完成去重与解密、再把有效载荷分发给上层应用。早期大家习惯把网络服务器直接装在物理机上,依赖一堆系统库和特定版本的…

2026/10/15 0:00:46 阅读更多 →
共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

1. 共享推理服务里,扩缩容到底难在哪做过大模型推理服务部署的人,大概都经历过这样的场景:一个团队共用一套推理集群,A 业务跑的是 7B 的小模型,B 业务跑的是 72B 的大模型,C 业务白天流量高、晚上几乎归零…

2026/10/15 0:00:46 阅读更多 →
Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

1. 引子:一次内核恐慌引发的追查先从一个真实场景说起。某次我给一台老旧的嵌入式设备折腾系统裁减,为了把启动时间压进两秒以内,我手贱地把一串自认为“没用”的 systemd 服务给 disable 了,其中就包括systemd-remount-fs.servic…

2026/10/15 0:00:46 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/14 3:30:28 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/14 16:30:29 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/14 3:29:58 阅读更多 →

月新闻

我发现了一个新思路:用 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/15 0:14:36 阅读更多 →
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/14 22:45:47 阅读更多 →
黑夜航拍船只数据集训练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/14 7:53:34 阅读更多 →