☰
别急着学LangChain:从零手写Agent,搞懂Function Calling与MCP
2026/10/27 12:10:39 网站建设 项目流程

1. 别急着啃框架,先把地基打牢

1.1 为什么我不建议新手一上来就学LangChain

我带过不少想转行做AI应用的朋友,十个里有八个第一句话就是:“我想学LangChain,该从哪开始?”每次听到这个,我都会先泼一盆冷水——不是LangChain不好,而是它不该是你迈出的第一步。

你想想,一个连Python函数返回值都写不利索的人,直接去啃LangChain的Chain、AgentExecutor、Tool这些抽象概念,结果是什么?大概率是照着教程复制粘贴跑通了一个Demo,然后换个需求就完全不知道从哪下手。这不是学习,这是抄作业。

LangChain本质上是一个编排层,它帮你把大模型的调用、工具的使用、记忆的管理串起来。但前提是,你得先知道“被编排的东西”长什么样。就像你不会让一个刚学做菜的人直接去研究中央厨房的流水线设计,他得先学会切菜、控火、调味。

我自己的学习路径是这样的:先用原生Python调用大模型API,手动实现一个最简单的对话循环,然后逐步加上工具调用、上下文管理、错误重试。等你把这些都手写一遍之后,再去看LangChain的源码,你会发现它做的事情你全都理解,只是它帮你封装得更优雅了。这时候学LangChain,才是真正的“入门”。

1.2 转行者和新手分别该补什么课

这里要分两种情况来说,因为“新手”和“转行者”的起点不一样。

纯新手(没写过代码或者只写过一点点):你的第一优先级不是AI,是Python基础。具体来说,你需要能熟练做到这几件事——用函数封装逻辑、用类组织代码、用列表和字典处理数据、用try/except处理异常、用pip安装第三方库、用requests发HTTP请求。这些听起来很基础,但我见过太多人跳过这一步,后面每一步都走得磕磕绊绊。

转行者(有其他语言基础,比如Java、Go、JavaScript):你的重点不是学Python语法,而是学Python的“思维方式”和AI应用开发的“套路”。比如Java程序员习惯了强类型和面向对象设计模式,转到Python做AI应用时,需要适应动态类型的灵活性,同时理解AI应用特有的“不确定性”——大模型的输出不是确定的,你需要用各种手段去约束和引导它。

不管你是哪种,有一件事是共通的:先学会用原生方式调用大模型API。不管你后面用LangChain还是Spring AI还是别的什么框架,底层都是HTTP请求加JSON解析。把这个搞明白了,框架对你来说就只是语法糖。

1.3 一个最小可用的学习路线图

我给自己带的人规划路线时,通常是这样的顺序:

  1. Python基础补齐(1-2周):重点掌握函数、类、异常处理、文件读写、HTTP请求
  2. 原生API调用(3-5天):手动实现对话、流式输出、多轮上下文
  3. 工具调用原理(1周):理解Function Calling的机制,手写一个简单的工具调度
  4. MCP协议入门(3-5天):理解Model Context Protocol的设计理念,知道它解决什么问题
  5. 再选一个框架深入(2-3周):LangChain或Spring AI,看你的技术栈偏好

这个路线看起来慢,但实际上是最快的。因为每一步你都在建立“可迁移的理解”,而不是记住某个框架的API。框架会变,LangChain从0.1到0.3改了多少东西?但底层的原理不会变。

我经常说一句话:学框架不如学协议,学协议不如学原理。协议比框架活得久,原理比协议活得久。

2. 核心概念拆解:Agent到底是什么

2.1 从“聊天机器人”到“Agent”的关键跨越

很多人对AI Agent的理解还停留在“能聊天的AI”,这其实差得很远。普通的聊天机器人是你问一句它答一句,它没有主动性,没有工具,没有记忆,没有目标感。而Agent的核心区别在于四个字:自主行动。

我给你一个具体的对比。你问聊天机器人“今天北京天气怎么样”,它可能会说“抱歉,我无法获取实时天气信息”。但如果你问一个Agent同样的问题,它会自己去调用天气API,拿到数据,然后告诉你“北京今天晴,气温25到32度,适合穿短袖”。

这个差异背后涉及几个关键技术点:工具调用(Agent能使用外部工具)、任务规划(Agent能拆解复杂任务)、记忆管理(Agent能记住上下文和历史)、循环执行(Agent能根据结果决定下一步做什么)。

用更技术化的话说,Agent就是一个“大模型驱动的循环决策系统”。它的基本运行逻辑是:观察当前状态→思考下一步行动→执行行动→观察结果→继续循环,直到任务完成或达到终止条件。

2.2 Function Calling:Agent的手和脚

Function Calling是整个Agent体系里最核心的机制之一,但很多新手对它一知半解。我用大白话解释一下:大模型本身只能输出文本,它没有手没有脚,不能帮你查数据库、发邮件、操作文件。Function Calling就是给大模型装上了“手和脚”。

具体怎么工作的?你在调用大模型API的时候,除了发消息,还可以附带一份“工具清单”,告诉它“你有这些工具可以用,每个工具需要什么参数”。大模型在理解用户需求后,如果判断需要用某个工具,它不会直接执行,而是返回一个结构化的JSON,说“我要调用get_weather这个工具,参数是city=北京”。然后你的代码去实际执行这个调用,把结果再传回给大模型,大模型再基于结果生成最终回复。

这里有个关键点新手容易搞混:大模型不执行任何工具,它只是决定调用哪个工具、传什么参数。实际的执行逻辑是你自己写的代码。这个理解非常重要,因为它意味着你对Agent的行为有完全的控制权。

我手写过一个最简单的Function Calling示例,大概长这样:

import json import requests # 定义工具清单 tools = [ { "type": "function", "function": { "name": "get_weather", "description": "获取指定城市的天气信息", "parameters": { "type": "object", "properties": { "city": { "type": "string", "description": "城市名称,如北京、上海" } }, "required": ["city"] } } } ] # 调用大模型 response = requests.post( "https://api.example.com/v1/chat/completions", headers={"Authorization": "Bearer YOUR_KEY"}, json={ "model": "gpt-4", "messages": [{"role": "user", "content": "北京今天天气怎么样"}], "tools": tools } ) # 解析大模型的返回 result = response.json() tool_call = result["choices"][0]["message"].get("tool_calls") if tool_call: # 大模型决定调用工具,我们手动执行 func_name = tool_call[0]["function"]["name"] args = json.loads(tool_call[0]["function"]["arguments"]) # 这里执行实际的天气查询逻辑 weather_data = actually_get_weather(args["city"]) # 把结果传回给大模型 # ... 第二轮调用

这段代码虽然简陋,但它揭示了Agent的本质:大模型做决策,你的代码做执行。理解了这一点,后面学任何框架都是顺理成章的事。

2.3 MCP协议:为什么它值得你花时间了解

MCP(Model Context Protocol)是最近半年AI应用开发领域最热的话题之一。很多新手看到这个词一脸懵,不知道它跟自己有什么关系。我用一个类比来解释。

在没有MCP之前,每个AI应用要接入外部工具,都得自己写一套对接代码。你想让Agent查数据库,写一套;想让它操作文件系统,再写一套;想让它调用某个SaaS服务的API,又写一套。这些代码互不通用,换个框架就得重写。

MCP做的事情,就是定义了一套标准化的工具接入协议。你可以把它理解成“AI工具界的USB接口”。只要你的工具按照MCP协议实现,任何支持MCP的AI应用都能直接使用它,不需要额外适配。

这对开发者意味着什么?意味着你写的工具可以跨框架复用。今天你用LangChain,明天换成Spring AI,你之前写的MCP工具照样能用。这也是为什么我建议新手在学完Function Calling原理之后,花几天时间了解一下MCP——它代表的是未来的方向。

MCP的核心概念包括:Server(提供工具的一方)、Client(使用工具的一方)、Tool(具体的工具定义)、Resource(可访问的资源)。你不需要一上来就自己写MCP Server,但至少要会用现成的MCP工具,知道怎么把它们接入你的Agent。

2.4 主流框架的定位差异:LangChain vs Spring AI

既然标题说了“别一上来就啃框架”,那什么时候可以啃?当你把上面这些原理都搞明白之后。这时候你需要选一个框架来提升开发效率,而选择哪个框架,取决于你的技术背景和项目需求。

LangChain是Python生态里最成熟的Agent开发框架,它的优势是生态丰富、社区活跃、文档多。但它的缺点也很明显:抽象层次多,版本迭代快,有时候你遇到问题去搜解决方案,搜到的可能是三个版本之前的写法。LangChain更适合快速原型开发和对Python生态熟悉的开发者。

Spring AI是Java生态里对标LangChain的框架,它的设计理念更偏向企业级应用——强类型、依赖注入、与Spring Boot无缝集成。如果你本身是Java开发者,或者你的项目需要部署在企业级Java环境中,Spring AI是更自然的选择。它的学习曲线对Java程序员来说更平缓,但Python社区的资源相对少一些。

我的建议是:先精通一个,再了解另一个。不要同时学两个框架,那样只会让你混淆概念。选一个跟你当前技术栈最匹配的,深入用一段时间,等你有感觉了,再看另一个框架的文档,你会发现很多概念是相通的。

3. 动手实操:从零搭一个最小Agent

3.1 环境准备与依赖安装

在开始写代码之前,我们需要把环境准备好。这里我假设你已经装好了Python(建议3.10以上版本),如果还没装,去Python官网下载安装包,安装时记得勾选“Add Python to PATH”。

接下来安装必要的依赖。我建议用虚拟环境来管理项目依赖,避免污染全局环境:

# 创建虚拟环境 python -m venv agent-demo # 激活虚拟环境(Windows) agent-demo\Scripts\activate # 激活虚拟环境(Mac/Linux) source agent-demo/bin/activate # 安装核心依赖 pip install openai requests python-dotenv

这里解释一下每个依赖的作用:openai库用来调用大模型API(很多国产大模型也兼容OpenAI的接口格式),requests用来发HTTP请求(调用外部工具时会用到),python-dotenv用来管理API密钥等敏感配置。

注意:不要把API密钥硬编码在代码里,也不要把包含密钥的文件提交到代码仓库。用.env文件管理,并在.gitignore里排除它。

3.2 手写一个带工具调用的对话循环

环境准备好之后,我们来写一个最简版的Agent。这个Agent能做什么?你问它天气,它会调用天气工具;你问它时间,它会调用时间工具;你随便聊天,它就直接回复。

先定义工具函数:

import datetime import json def get_current_time(): """获取当前时间""" now = datetime.datetime.now() return now.strftime("%Y-%m-%d %H:%M:%S") def get_weather(city: str): """模拟获取天气信息(实际项目中应调用真实天气API)""" # 这里用模拟数据演示,真实场景替换为API调用 weather_data = { "北京": "晴,25-32度,微风", "上海": "多云,23-29度,东南风3级", "广州": "雷阵雨,26-33度,湿度较高" } return weather_data.get(city, f"暂时没有{city}的天气数据")

然后定义工具清单,告诉大模型有哪些工具可用:

tools = [ { "type": "function", "function": { "name": "get_current_time", "description": "获取当前的日期和时间", "parameters": {"type": "object", "properties": {}} } }, { "type": "function", "function": { "name": "get_weather", "description": "获取指定城市的天气信息", "parameters": { "type": "object", "properties": { "city": {"type": "string", "description": "城市名称"} }, "required": ["city"] } } } ]

接下来是核心的对话循环。这个循环的逻辑是:把用户消息发给大模型→大模型可能返回工具调用请求→我们执行工具→把结果传回大模型→大模型生成最终回复。

from openai import OpenAI import os from dotenv import load_dotenv load_dotenv() client = OpenAI( api_key=os.getenv("API_KEY"), base_url=os.getenv("BASE_URL") # 如果用国产大模型,替换为对应的base_url ) # 工具名到函数的映射 tool_map = { "get_current_time": get_current_time, "get_weather": get_weather } def run_agent(user_input: str, max_turns: int = 5): messages = [ {"role": "system", "content": "你是一个有用的助手,可以调用工具来帮助用户。"}, {"role": "user", "content": user_input} ] for turn in range(max_turns): response = client.chat.completions.create( model="gpt-4", messages=messages, tools=tools ) msg = response.choices[0].message # 如果大模型没有调用工具,直接返回回复 if not msg.tool_calls: return msg.content # 把大模型的工具调用请求加入消息历史 messages.append(msg) # 执行每个工具调用 for tool_call in msg.tool_calls: func_name = tool_call.function.name args = json.loads(tool_call.function.arguments) # 执行工具 if func_name in tool_map: result = tool_map[func_name](**args) else: result = f"未知工具:{func_name}" # 把工具执行结果加入消息历史 messages.append({ "role": "tool", "tool_call_id": tool_call.id, "content": str(result) }) return "达到最大轮次限制,任务未完成"

这段代码虽然只有几十行,但它已经是一个完整的Agent了。你运行run_agent("北京天气怎么样"),它会自动调用天气工具并返回结果。你运行run_agent("现在几点了"),它会调用时间工具。

3.3 加上记忆和上下文管理

上面的Agent有一个明显的问题:它没有记忆。每次调用run_agent都是全新的对话,之前聊过什么它完全不记得。要解决这个问题,我们需要引入“会话历史”的概念。

最简单的做法是用一个列表来保存对话历史,每次调用时把历史一起传给大模型:

class SimpleAgent: def __init__(self, system_prompt="你是一个有用的助手。"): self.messages = [{"role": "system", "content": system_prompt}] def chat(self, user_input: str): self.messages.append({"role": "user", "content": user_input}) for turn in range(5): response = client.chat.completions.create( model="gpt-4", messages=self.messages, tools=tools ) msg = response.choices[0].message if not msg.tool_calls: self.messages.append({"role": "assistant", "content": msg.content}) return msg.content self.messages.append(msg) for tool_call in msg.tool_calls: func_name = tool_call.function.name args = json.loads(tool_call.function.arguments) result = tool_map[func_name](**args) if func_name in tool_map else "未知工具" self.messages.append({ "role": "tool", "tool_call_id": tool_call.id, "content": str(result) }) return "任务未完成"

这样你就能连续对话了:“北京天气怎么样”→“那上海呢”→“帮我对比一下这两个城市”。Agent会记住之前的上下文,理解“这两个城市”指的是北京和上海。

但这里有个坑:对话历史会越来越长,最终超出大模型的上下文窗口限制。解决方案有两种:一是设置一个最大历史长度,超出后丢弃最早的消息;二是用大模型对历史进行摘要压缩。我一般先用第一种方案,简单有效,等真的有需求了再上第二种。

3.4 错误处理与重试机制

实际跑起来之后,你会发现各种意外情况:API超时、返回格式不对、工具执行报错、大模型陷入死循环……这些都是家常便饭。一个健壮的Agent必须能处理这些异常。

最基本的错误处理包括:

import time def safe_tool_call(func_name, args, max_retries=3): for attempt in range(max_retries): try: if func_name not in tool_map: return f"错误:未知工具 {func_name}" result = tool_map[func_name](**args) return str(result) except Exception as e: if attempt == max_retries - 1: return f"工具执行失败:{str(e)}" time.sleep(1 * (attempt + 1)) # 指数退避

对于API调用,也要加上超时和重试:

def call_llm_with_retry(messages, tools, max_retries=3): for attempt in range(max_retries): try: response = client.chat.completions.create( model="gpt-4", messages=messages, tools=tools, timeout=30 ) return response except Exception as e: if attempt == max_retries - 1: raise e time.sleep(2 ** attempt)

还有一个容易被忽略的问题:大模型可能反复调用同一个工具。比如你问天气,它调了get_weather,拿到结果后不满意,又调一次,再调一次……这时候你需要设置一个最大轮次限制,超过就强制终止。

实操心得:我在生产环境里会把每次工具调用的记录都存下来,包括调用时间、参数、结果、耗时。出问题的时候回看这些日志,能快速定位是模型决策的问题还是工具本身的问题。

4. 常见问题与避坑指南

4.1 新手最容易踩的五个坑

第一个坑:API密钥泄露。我见过有人把密钥直接写在代码里然后传到公开仓库,结果被人盗刷了几百美元。记住:密钥永远放在环境变量或密钥管理服务里,代码里只引用变量名。

第二个坑:不设超时和重试。大模型API偶尔会抽风,没有超时机制的话你的程序会一直卡在那里。没有重试机制的话一次网络抖动就导致整个任务失败。这两个是基本配置,不是可选项。

第三个坑:忽略Token消耗。Agent的每一轮循环都在消耗Token,一个复杂任务可能跑十几轮,Token消耗是普通对话的几十倍。上线之前一定要算清楚成本,设置好预算上限。

第四个坑:工具描述写得太随意。大模型是根据你的工具描述来决定调不调、怎么调的。描述写得不清楚,模型就会调错或者不调。工具描述要写清楚:这个工具是干什么的、什么时候用、参数是什么含义、有什么限制。

第五个坑:不做输出校验。大模型返回的JSON不一定是合法的,参数类型不一定是正确的。在解析之前一定要做校验,解析失败要有降级方案。

4.2 框架选型的决策表

当你决定要用一个框架来提升开发效率时,可以参考这个决策表:

考量维度LangChainSpring AI
主要语言PythonJava
适合人群数据科学家、Python开发者Java后端开发者
生态丰富度非常高,集成多中等,但增长快
学习曲线中等偏陡对Java开发者平缓
企业级支持一般强,与Spring生态集成好
版本稳定性迭代快,偶有破坏性变更相对稳定
社区活跃度非常高高
部署方式灵活,可容器化天然适合企业Java环境

我的建议很简单:你的主力语言是什么,就用对应的框架。不要因为LangChain火就去学Python,也不要因为公司用Java就硬上Spring AI。工具是为你服务的,不是反过来。

4.3 从Demo到生产的差距在哪里

很多人跑通了一个Demo就觉得自己会了,但Demo和生产之间的距离可能比你想的大得多。我列几个关键的差距点:

稳定性:Demo不需要考虑API挂了怎么办、网络抖动了怎么办、大模型返回了意料之外的内容怎么办。生产环境这些都要处理。

可观测性:Demo不需要日志、不需要监控、不需要告警。生产环境你需要知道每一次调用的耗时、Token消耗、成功率、失败原因。

安全性:Demo不需要考虑提示词注入、工具滥用、数据泄露。生产环境这些是红线。

成本控制:Demo不在乎花了多少Token。生产环境你需要设置预算、做缓存、优化提示词来降低成本。

并发处理:Demo一次处理一个请求。生产环境可能同时有几十上百个请求,你需要考虑限流、排队、异步处理。

我的经验是,从Demo到生产,工作量至少翻三倍。所以在做技术方案的时候,不要把Demo的完成度当成项目的完成度。

4.4 学习资源推荐与学习节奏建议

最后说几个我觉得质量不错的学习资源。官方文档永远是最好的起点——LangChain的官方文档有完整的教程和API参考,Spring AI的文档对Java开发者很友好。MCP的官方规范文档值得通读一遍,不长,但能帮你建立正确的认知。

视频教程方面,我建议找那种“手写实现”而不是“框架演示”的。手写实现能让你看到底层发生了什么,框架演示只是让你看到API怎么调。

关于学习节奏,我的建议是:每天写代码,哪怕只写20行。AI应用开发是一个实践性极强的领域,看十篇文章不如自己跑通一个例子。遇到问题先自己排查,排查不出来再搜,搜不到再问。这个过程中积累的经验,才是真正属于你的。

我个人的习惯是维护一个“踩坑笔记”,每次遇到问题解决了就记下来。三个月后回看,你会发现大部分问题都是重复的,有了笔记就能快速定位。

这个领域变化很快,但底层的东西变化很慢。把基础打牢,把原理搞懂,不管上面出什么新框架,你都能快速上手。反过来,如果只追新框架不学原理,你会一直处于“学不完”的焦虑中。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询