从Function Calling到LangGraph:AI Agent开发实战路线与原理 📅 发布时间:2026/9/12 4:40:42 👁 浏览次数: 1. 从一周前的困惑说起为什么突然要学Agent先说说我这周的状态。Week 9本来是计划好的“大模型应用进阶”结果翻了一圈资料发现所有路线都指向同一个词AI Agent。热搜里天天挂着“AI Agent开发”“Agent面试题”GitHub上LangGraph的star涨得吓人连公司的后端同事都在问我会不会搭Agent。说实话一开始我是有点懵的——LLM能对话、能生成、能总结这不就够了吗Agent到底解决了什么问题带着这个疑问硬啃了一周现在我敢说Agent不是锦上添花而是LLM从“聊天玩具”走向“生产力工具”的关键一跃。这一周我从最底层的Function Calling开始一步步摸到LangGraph中间踩了不少坑也把很多零散的概念串成了一条线。这篇文章就把这周的学习路线、核心原理和实操笔记完整梳理一遍希望对同样在走AI全栈路线的朋友有帮助。适合谁来读如果你已经会调用大模型API会写Prompt但面对“Agent”这个概念觉得云里雾里或者想搞清楚Function Calling和LangGraph到底是什么关系这篇文章就是给你写的。我会尽量少用玄学词汇多用“人话实操”讲清楚。2. Function CallingAgent的真正起点2.1 大模型天生不会“做事”先说一个最容易被忽略的事实大模型本质上是一个“文本预测器”。你给它一段文字它预测下一个最可能出现的token然后循环往复生成完整回复。它可以写出逻辑严密的代码可以给你解释量子力学但它没办法自己打开浏览器查天气、调用支付接口转账、往数据库里写入一条记录。为什么因为它没有“手”。它活在参数空间里所有知识都压缩在权重中对外部世界一无所知。你可以通过Prompt告诉它“请查询明天的天气”但它唯一的回应方式是——根据训练数据里的模式编一段看起来像天气查询结果的话。这在很多场景下是要命的。为了验证这个说法你可以做个实验让GPT-4o或者Claude回答“北京时间现在几点”。它大概率会给你一个“基于我的知识截止日期我无法获取实时信息”之类的回答或者干脆编一个时间。这不是模型不够聪明而是架构决定的——它没有感知时间的能力。2.2 Function Calling到底做了什么为了解决“没有手”的问题OpenAI在2023年年中率先推出了Function Calling能力。它的思路非常朴素却极其有效**第一步你提前告诉模型我这里有几个工具可以用每个工具有什么名字、接收什么参数、大概干什么用。**这些描述以JSON Schema的形式放在API请求里。**第二步模型看完用户的话决定“这活需要调工具”。**它不直接调而是输出一段结构化的JSON说“我建议调用某个工具参数是XXX”。第三步你的代码收到这个JSON自己去执行真正的函数调用拿到结果后再把结果交回给模型。第四步模型拿到工具返回的真实数据结合用户的原始问题生成最终的自然语言回答。这个流程看起来简单但它打破了一个关键限制大模型第一次能够接触外部世界的真实数据并且基于真实数据做推理。这不是什么魔法而是一种“合同约定”——你用格式化的方式告诉模型有什么工具模型用格式化的方式告诉你它想用什么工具二者通过JSON对话各司其职。2.3 一个最简Function Calling落地示例说一千道一万不如跑通一个demo。我用PythonOpenAI SDK写了一个最简版本场景是“查天气”。我没有真的去对接天气API而是用一个假函数模拟核心是跑通整个调用链路。先定义工具。按OpenAI的要求工具描述要放在tools参数里from openai import OpenAI import json client OpenAI() tools [ { type: function, function: { name: get_weather, description: 查询指定城市的实时天气信息, parameters: { type: object, properties: { city: { type: string, description: 城市名称例如北京、上海 } }, required: [city] } } } ] def get_weather(city: str): 模拟天气查询函数 fake_data { 北京: {温度: 22℃, 天气: 晴}, 上海: {温度: 25℃, 天气: 多云}, 广州: {温度: 28℃, 天气: 小雨} } return json.dumps(fake_data.get(city, {温度: 未知, 天气: 未知}))然后走标准的两轮调用流程messages [{role: user, content: 北京今天天气怎么样}] # 第一轮把用户问题和工具描述一起发给模型 resp client.chat.completions.create( modelgpt-4o-mini, messagesmessages, toolstools, tool_choiceauto ) # 检查模型是否需要调用工具 msg resp.choices[0].message print(f模型返回{msg}) if msg.tool_calls: # 模型决定调用get_weather参数是{city: 北京} tool_call msg.tool_calls[0] args json.loads(tool_call.function.arguments) # 执行真正的函数 result get_weather(cityargs[city]) # 把结果作为tool消息追加回去 messages.append(msg) messages.append({ role: tool, tool_call_id: tool_call.id, content: result }) # 第二轮模型基于真实结果生成回答 resp2 client.chat.completions.create( modelgpt-4o-mini, messagesmessages, toolstools ) print(f最终回答{resp2.choices[0].message.content})跑完之后输出是这样的简化过模型返回ChatCompletionMessage(contentNone, tool_calls[ChatCompletionMessageToolCall(idcall_xxx, functionFunction(arguments{city:北京}, nameget_weather))]) 最终回答北京今天天气晴气温22℃。早晚温差较大建议带一件薄外套。看到没有第一轮模型返回的content是空的取而代之的是一段结构化指令。我们这边收到指令后真正执行了函数把结果塞回去第二轮模型才给出了“像模像样”的回答。这个“第一轮出JSON第二轮出人话”的闭环就是整个Agent世界的基石。2.4 Function Calling的三个易错细节实际跑通之后你会发现文档没写清楚的地方才是坑最多的地方。我整理了三个最值得注意的细节**细节一tools描述的质量直接决定调用准确率。**同一个函数如果你只写“查询天气”模型可能会在用户问“穿什么衣服”时也去调它如果你写“查询指定城市的实时天气信息适合在用户询问天气、温度、出行建议时调用”模型就聪明得多。工具描述本质上是另一份Prompt值得花时间打磨。**细节二tool_choice参数的作用被很多人忽略。**默认值是auto表示让模型自己判断要不要调工具。但你可以强制它none永远不调或者required必须调。在某些业务场景里比如你希望模型每个问题都先查一下知识库就可以设成required。实测下来非常有用。**细节三注意多轮对话中的消息顺序。**如果你要做多轮对话不仅要传历史对话消息还要把assistant那条带tool_calls的消息、以及每条对应的tool消息按顺序追加进messages列表否则会报错或者模型“失忆”。这块我在刚开始做的时候经常漏顺手就踩坑。3. Agent从“单次调用”到“自主循环”3.1 Agent的本质是“循环”如果只是“用户问一句工具调一次”那还叫不上Agent充其量叫“带工具的Chatbot”。Agent和工具调用的核心区别在于Agent有目标、有计划、能多步决策并且能根据中间结果调整下一步动作。你想想人类是怎么完成“帮我订一张明天去上海的高铁票顺便订个离车站近的酒店”这个任务的。你需要先查询明天的车次选定一个合适的班次再根据到达时间查酒店比较位置最后下单支付。每一步的结果都会影响下一步的决策。如果大模型只做一次Function Call它根本没法完成这种长链路任务。Agent就是把这个过程循环化**思考Thought→ 调用工具Action→ 观察结果Observation→ 再次思考 → …… 直到任务完成。**这就是业界常说的ReAct模式Reasoning Acting最早的论文《ReAct: Synergizing Reasoning and Acting in Language Models》把这条路给趟平了。你可以把它理解成一个while循环条件就是“模型自己觉得任务完成了”。3.2 用最简单的代码理解Agent循环在还没上LangGraph之前我自己手写了一个微型Agent循环帮助理解整个机制。核心逻辑只有十几行def run_agent(user_query, max_steps5): messages [ {role: system, content: 你是一个能调用工具的智能助手。}, {role: user, content: user_query} ] for step in range(max_steps): resp client.chat.completions.create( modelgpt-4o-mini, messagesmessages, toolstools, tool_choiceauto ) msg resp.choices[0].message # 如果没有要调用的工具说明任务完成了 if not msg.tool_calls: print(fStep {step1}任务完成) return msg.content print(fStep {step1}需要调用 {[tc.function.name for tc in msg.tool_calls]}) messages.append(msg) # 执行所有tool calls for tc in msg.tool_calls: fn_name tc.function.name fn_args json.loads(tc.function.arguments) result tools_map[fn_name](**fn_args) messages.append({ role: tool, tool_call_id: tc.id, content: result }) return 达到最大步数任务结束这个循环虽然简单但它已经具备了Agent的基本要素有目标用户的原始问题就是目标。能规划模型根据当前上下文决定下一步动作。能观察工具返回的结果被放回上下文供下一轮参考。会终止模型不再发出工具调用指令时循环结束。我拿这个简易版跑了几个场景比如“帮我查北京的天气和上海的温度然后比较哪个更适合晨跑”它真的会先连续调两次get_weather再对返回的数据做对比分析。那一刻我是真的感受到——**Agent的门槛并没有想象中高最难的是如何管理复杂状态、如何处理出错、如何编排多个Agent协作。**而这些东西正是LangGraph要解决的。3.3 Agent调用的工具从哪来聊到Agent绕不开一个问题那些工具到底怎么来的总不能每次都现写一个Python函数吧。企业里常见的做法是把现有系统的API封装成“工具描述”比如查订单系统把内部订单查询接口封装成query_order(order_id, user_id)写数据库把SQL执行封装成execute_sql(sql)搜文档把企业内部知识库检索封装成search_docs(keyword)每个工具就是一段JSON Schema一个真实可执行的函数。你可以手工写也可以写个脚本根据API文档自动生成Schema。我之前接触过一个团队用了Spring AI框架他们把后端的Service层方法全部暴露成Tool然后让Agent做前端的智能对话客服。技术栈虽然有差异但底层的思路完全一致——Agent的能力边界就是你能提供给它的工具集合的边界。4. LangGraph把Agent从“脚本”变成“工程”4.1 为什么还要LangGraph手写循环不香吗你可能会问我上面手写的那个循环不是挺好的吗能跑、能调工具、能多步推理为什么要学一个框架这个问题我一开始也想不通直到我尝试在这个手写循环里加入以下需求瞬间就崩了条件分支如果用户问的是天气走天气工具链如果问的是机票走机票工具链还要跟天气工具链交叉。人机交互调工具之前需要用户确认比如“你确定要下单吗”这需要暂停Agent等用户回复后再继续。并行的Agent比如一个Agent查天气另一个Agent查路线两个结果合并后给主Agent做最终决策。可观测性每个节点干了什么、耗时多少、调了哪个模型、哪个工具需要有日志。状态持久化对话历史、中间状态需要存到数据库下次还能接着跑。用我那个纯Python while循环做这些事情代码很快就会变成一坨意大利面。LangGraph的价值就是把Agent的“流程”可视化、结构化、工程化。通俗地理解LangGraph把Agent定义成一个“图”。图的节点是“动作”调用模型、调用工具、做判断图的边是“状态转移”下一步去哪。你用定义节点和边的方式编排AgentLangGraph替你管理循环、分支、状态、断点甚至并行执行。这就好比用TensorFlow写神经网络——你可以手写反向传播但框架让你把注意力放在网络结构上而不是底层的梯度计算。4.2 LangGraph vs LangChain被问了八百遍的问题网上关于LangChain和LangGraph区别的讨论一搜一大把我在学习过程中也困惑了好一阵。用我自己的话总结最核心的区别是LangChain是“组件库”——它提供了各种封装好的组件比如DocumentLoader、TextSplitter、VectorStore、LLM封装、Prompt模板等。你拿来拼装一个RAG应用非常顺手。但它的编排模型是“链Chain”本质是预定义的线式流程。一旦出现复杂的循环和分支Chain就有点力不从心了。LangGraph是“编排引擎”——它的核心抽象是“状态图StateGraph”专门为循环、分支、并行的Agent流程设计。它不替代LangChain的功能而是提供了一个更灵活的“调度层”。LangChain的组件依然能在LangGraph的节点里使用比如你在节点里照样用LangChain的PromptTemplate和ChatOpenAI。学习路线建议不用纠结“学LangChain还是LangGraph”如果你要做的是复杂Agent直接上手LangGraph需要什么组件再从LangChain里借。因为LangGraph概念更贴近Agent的本质一开始就建立“状态图”的心智模型后面写复杂应用不会跑偏。4.3 LangGraph核心概念一次讲透LangGraph里面的概念不少但核心其实就四个State、Node、Edge、Conditional Edge。State状态所有节点共享的数据结构。你可以定义成TypedDict或者Pydantic BaseModel。比如我们做个“客服Agent”State里至少要放messages对话历史和company_info从知识库检索到的企业信息。每个节点执行后都有可能更新State。from typing import TypedDict, List class AgentState(TypedDict): messages: List[dict] # 对话历史 company_info: str # 从知识库检索到的信息 next_agent: str # 下一步要执行的agent名字Node节点一个普通的Python函数接收State返回State的更新字典。你可以把“调用LLM”写成一个节点把“调用工具”写成另一个节点。def call_llm_node(state: AgentState): messages state[messages] resp llm.invoke(messages) return {messages: messages [resp]}Edge边两个节点之间的连接表示执行完A之后执行B。Conditional Edge条件边LangGraph最核心的亮点。它允许你在一个节点执行完后根据当前State的内容动态决定下一步走哪个边。比如LLM节点返回的结果中如果有tool_calls就走到工具的节点否则直接走向结束。def decide_next(state: AgentState): last_message state[messages][-1] if last_message.tool_calls: return call_tools # 需要调工具去工具节点 else: return finish # 任务结束把这些拼起来就是ReAct Agent的标准结构。官方文档里的create_react_agent其实就是在语言层面帮你生成这个图但理解底层节点和边对调试太重要了。4.4 一个LangGraph实战客服问答Agent这是我本周做的练手项目场景是“企业客服”。用户问题进到AgentAgent先用关键词判断问题类型需要查资料时从一个简化知识库里检索最终给出回答。这个例子不复杂但覆盖了LangGraph的基本用法。from langgraph.graph import StateGraph, END from langchain_openai import ChatOpenAI from typing import TypedDict, List class AgentState(TypedDict): messages: List[dict] company_info: str llm ChatOpenAI(modelgpt-4o-mini, temperature0) KNOWLEDGE_BASE { 退货: 支持7天无理由退货运费由买家承担。, 发票: 默认开具电子普通发票下单后可在个人中心下载。, 物流: 默认发顺丰快递预计3天内送达。, 质保: 产品享受一年质保人为损坏不在范围内。 } def llm_node(state: AgentState): messages state[messages] resp llm.invoke(messages) return {messages: messages [resp]} def search_kb_node(state: AgentState): messages state[messages] last_user_msg messages[-1][content] if isinstance(messages[-1], dict) else messages[-1].content result 未找到相关内容 for key, val in KNOWLEDGE_BASE.items(): if key in last_user_msg: result val break # 把检索结果注入system消息让llm基于此回答 new_messages [ {role: system, content: f以下是企业知识库检索结果{result}。请据此回答用户问题。} ] messages resp llm.invoke(new_messages) return {messages: messages [resp], company_info: result} def decide_route(state: AgentState): last_user_msg state[messages][-1][content] kb_keys [退货, 发票, 物流, 质保] if any(k in last_user_msg for k in kb_keys): return search_kb return llm_direct # 构建图 graph StateGraph(AgentState) graph.add_node(llm_direct, llm_node) graph.add_node(search_kb, search_kb_node) graph.set_entry_point(llm_direct) # 初始先进llm_direct判断 graph.add_conditional_edges(llm_direct, decide_route, { llm_direct: llm_direct, search_kb: search_kb }) graph.add_edge(search_kb, END) graph.add_edge(llm_direct, END) app graph.compile() result app.invoke({ messages: [{role: user, content: 你们退货政策是怎样的}] }) print(result[messages][-1].content)跑出来的结果大概是这样我们的退货政策是支持7天无理由退货运费由买家承担。请问还有什么可以帮您这个例子虽然简单但已经把核心链路都串起来了用户输入进入图 → LLM判断走哪条路 → 命中知识库关键词就走检索节点 → 检索结果拼入上下文 → 最终生成回答。当然这只是一个最小可运行版本离生产级还有距离。生产级Agent通常还需要做多轮对话历史持久化、评估用户意图的准确性、工具执行异常重试、观察数据与日志追踪等。这些能力LangGraph都有对应的抽象checkpointer可以保存状态、interrupt可以人机交互、send支持地图式并行。一周时间我只算入了门但确实看到了这套工具在生产场景中的潜力。5. 这一周踩过的坑和总结的经验5.1 五个典型问题速查表学习过程中踩过的坑比看十篇教程都长记性。整理成表格给你避雷问题现象根本原因解决方案模型总是拒绝调用工具直接给出模糊回答工具描述不清晰或者模型认为没必要调用优化工具的description字段把触发场景写详细甚至可以加上使用示例工具调用报错Function args is not valid JSON模型生成的参数Schema和定义的不匹配确保parameters里的required字段准确程序里做好参数的默认值兜底调用工具后模型“失忆”不记得之前对话内容工具结果没有正确拼接到对话上下文严格按assistant带tool_calls消息→tool消息的顺序追加Agent陷入死循环反复调用同一个工具缺少步数上限或工具返回结果未能使状态前进设置recursion_limit或max_steps在工具节点中设计终止条件LangGraph图结构编译时报错Invalid updated state节点返回的State更新内容与定义不一致节点函数返回的字典键必须在State定义中声明5.2 再说说“思维”层面的三个收获除了技术细节这周最大的收获其实在认知层面。**第一Agent不是某个模型或框架的专属能力而是一种设计模式。**不管是用OpenAI的Assistant API、用LangGraph还是用Spring AI的multi-agent模块本质都是LLM做决策外部函数做执行循环迭代直到完成。理解了这一点换框架只是换语法的问题。**第二Function Calling的质量决定了Agent的上限。**我在手写循环时感受特别深模型调工具的准确率很大程度上取决于工具定义得好不好。工具多了以后模型会混淆相似功能这时候可以给工具分组、加命名空间或者分多个Agent管理不同工具集。网上说的“让Agent学会调用工具Function Calling入门”就是这个意思——工具调用本身就是一门可以深入优化的学问。**第三可观测性在Agent里不是可选项而是必选项。**手写while循环时一旦Agent调用链变长出错了根本不知道是哪一步的问题。LangGraph在这方面做得比较好可以对每个节点的输入输出打日志甚至可视化整张图的执行轨迹。生产环境里这几乎是必须的能力。6. 下一步的扩展思路这周的内容虽然已经落地了一个Agent demo但距离“生产可用”还有明显差距。我给自己列了几个下周要继续深挖的方向也分享给看到这篇文章的你引入反馈循环Agent执行用户任务后加一个自动评估节点判断回答质量是否达标不达标则触发重写。这相当于给Agent装了“质检员”。Multi-Agent协作探究多个Agent如何分工比如一个“任务规划Agent”拆解复杂请求分发给“售后Agent”“物流Agent”“产品推荐Agent”最后汇总。LangGraph的send函数就是为这种Map-Reduce模式设计的。接入外部真实API把微信机器人、飞书机器人、企业微信API接进来让Agent真正可以“行动”而不只是“说话”。引入向量检索把企业文档切块向量化存入向量数据库Agent查知识库时走语义检索而不是我demo里的关键词硬匹配。如果你也在走AI全栈路线我的建议是第一周先手写一遍ReAct循环第二周再上LangGraph。亲手写过循环才会真正理解框架替你解决了什么。别急着追新框架把Function Calling吃透Agent对你来说就不会再是一个玄学词汇了。