LangChain从入门到精通:工具与函数调用详解
大模型的能力边界:时效性挑战与解决方案
先看一段代码,猜猜大模型会输出什么?
# 通义千问大模型model = get_lc_model_client()output_parser = JsonOutputParser()resp = model.invoke("今天是几月几号?")print(resp)
结果令人意外——AI竟然不知道今天是几月几号。再换一个模型(DeepSeek)试试:
model = get_ali_model_client()
没错,它确实给了一个日期,但那并不是真实的今天。之前讨论“为什么需要RAG”时,我们提到过大模型的时效性局限:训练成本极高,模型不可能实时更新。因此,那些最新的事件、最新的知识,大模型很可能一无所知。
这就是大模型的一个能力边界。要突破这一限制,除了RAG技术,还有另一个关键手段——Tool(工具调用)。
Tool:大模型的能力延伸
Tool(工具)是LangChain中的一个核心概念。它封装了一个具体功能(比如一个Python函数),包含名称、描述和参数模式(Schema)。开发者通过@tool装饰器或继承BaseTool来创建工具,本质上是在告诉模型:“我这里有这些能力可以调用。” 整个工作流程如下:
- 定义工具:开发者将函数(例如
get_date())及其描述、参数格式提前告知LLM。 - 模型规划:用户提问后,LLM并不直接给出答案,而是分析用户意图,选择合适的工具,并严格按照格式生成一个结构化的调用请求。
- 外部执行:程序解析这个请求,安全地在本地或远端执行真实的函数,获取结果。
- 整合回复:将执行结果返回给LLM,由它组织成自然语言回复用户。
简单来说,LLM扮演“大脑”(负责理解和规划),外部函数扮演“手脚”(负责执行和获取信息),两者通过结构化的“神经接口”(Function Calling规范)协同工作。
bind_tools():动态绑定工具
bind_tools()的作用是把一个或多个工具(函数)转换成大模型能理解和遵循的“调用规范”(通常是JSON Schema格式),然后“绑定”到某个特定的对话模型实例上。绑定之后,这个模型在思考时,就具备了调用这些工具的能力。
- 动态性与组合性:
bind_tools()的最大优势在于动态性。你可以在运行时根据不同的场景,为同一个模型绑定不同的工具集,实现高度灵活。 - 底层协议屏蔽:它抽象了不同模型提供商(OpenAI、Anthropic、DeepSeek)在工具调用上的细节差异,提供统一接口。在OpenAI的上下文中,它本质上是在准备
tools参数。
# 大模型客户端绑定工具tool_llm = model.bind_tools([get_date, open_browser])resp = tool_llm.invoke("今天是几月几号?")
Agent:工具调用的智能控制器
工具的选择和调用执行,最终是由Agent来完成的。
# 创建Agentagent = create_agent(model=llm,# 聊天模型tools=tools, # 工具列表system_prompt=system_prompt,checkpointer=memory# 传入记忆组件)
在大模型使用工具时,你可以用bind_tools进行工具绑定,但在工程实践中,推荐优先使用带tools参数的Agent,因为它提供了更完善的工具管理和执行流程。
方式一:@tool装饰器
代码示例
获取今天具体日期的自定义工具:
# 注意:函数的描述必须写在函数体中的第一行def get_date():# 文本描述相当于工具说明说,大模型正是依靠这段说明来选择对应工具""" 获取今天的具体日期 """# """ 获取今天的北京的天气 """return datetime.date.today().strftime("%Y-%m-%d")
获取浏览器并打开指定网站的工具:
mport webbrowserdef open_browser(url, browser_name=None):# """ 获取浏览器,打开网站 """""" 获取浏览器,打开网站可以做很多事情,包括查询天气,汽车限号等 """if browser_name:# 获取特定浏览器的控制器browser = webbrowser.get(browser_name)else:# 使用默认浏览器browser = webbrowser# 打开浏览器并导航到指定的URLbrowser.open(url)
使用方式:
agent = create_agent(model=llm,# 聊天模型tools=[get_date, open_browser], # 工具列表system_prompt=system_prompt,checkpointer=memory# 传入记忆组件)# 获取今天的日期result = agent.invoke({"messages":[{"role":"user","content":"今天是几月几号?"}]})
注意事项
- 函数的描述必须写在函数体中的第一行,否则AI无法识别。
- 函数的描述必须准确,且与函数实际功能保持一致。否则会导致回答混乱。
比如,如果把get_date的描述改为“获取今天的北京的天气”:
def get_date():""" 获取今天的北京的天气 """return datetime.date.today().strftime("%Y-%m-%d")result = agent.invoke({"messages":[{"role":"user","content":"今天北京的天气怎么样?"}]})
用户问天气,结果却调用了日期函数——这就是描述错误带来的问题。
Log验证
前面我们已经介绍了核心流程,现在结合代码Debug来验证一下。从打印结果可以看到:
- 用户提问后,大模型发现自己无法直接回答。
- 它尝试寻找合适的工具,找到后触发了
tool_calls,finesh_reason=tool_calls。 tool_calls信息显示name=get_date。- 调用
get_date返回ToolMessage(2026-03-12)。 - AI拿到日期信息后,就能给出正确答案了。
果然,和我们前面讲解的核心流程基本一致。
方式二:Tool()类
除了@tool装饰器,还可以通过Tool()类来显式定义工具。
外部函数定义
# 定义工具函数def get_current_time(input: str = "") -> str:"""获取当前时间"""current_datetime = datetime.now()formatted_time = current_datetime.strftime('%Y-%m-%d %H:%M:%S')result = f"当前时间:{formatted_time}。"print(result)return resultdef recom_drink(input: str = "") -> str:"""推荐附近的饮品店"""result = '''距离您500米内有如下饮料店:n1、蜜雪冰城n2、茶颜悦色n另外距离您200米内有惠民便利店,里面应该有矿泉水或其他饮品'''return resultdef open_calc(input: str = "") -> str:"""打开计算器"""try:subprocess.Popen(['calc.exe'])return "计算器已打开"except Exception as e:return f"打开计算器失败: {str(e)}"def open_browser(url: str) -> str:"""打开浏览器访问指定网址"""try:webbrowser.open(url)return f"已打开浏览器访问 {url}"except Exception as e:return f"打开浏览器失败: {str(e)}"
Tool()定义
name:工具名称,可以自定义,但建议与函数名保持一致。func:关联的函数名,务必准确。description:函数的功能描述,作用与@tool中写在函数首行的描述相同,但此处函数首行就不必再写了。
# 创建LangChain工具列表# 方式二,自定义工具tools = [# 通过Tool来描述声明工具 name 工具名称 func 函数体 description 函数的功能描述Tool(name="get_current_time", #可以随意,但是建议跟函数名称一致func=get_current_time,# 函数的功能描述,说明书description="当你想知道现在的时间时非常有用。"),Tool(name="recom_drink",func=recom_drink,description="用户口渴,为其推荐附近的饮品店"),Tool(name="open_calc",func=open_calc,description="打开本地计算机上的计算器。"),Tool(name="open_browser",func=lambda url: open_browser(url),description="打开本地计算机上的网页浏览器,并接受网站的url作为参数。")]
Agent构建
# 这个时候函数工具选择和调用执行,都是通过Agent来完成的# 创建Agentagent = create_agent(model=llm,# 聊天模型tools=tools, # 工具列表system_prompt=system_prompt,checkpointer=memory# 传入记忆组件)
方式三:BaseTool继承
这种方式是将工具函数封装成一个类,必须继承自BaseTool。这样设计的目的是为了给AI工具调用建立一套“强类型、结构化”的输入契约。这绝不仅仅是语法规范,而是构建可靠AI Agent的工程基石。
BaseTool类定义
name:工具名称description:工具描述
class DateTool(BaseTool):"""一个获取当前具体日期的简单工具。它是继承BaseTool类的最简示例。"""# 1. 定义工具名称。这将是Agent在思考时提到的名字。name: str = "get_date"# 2. 定义工具描述。清晰准确的描述直接决定了Agent能否在正确场景下想起并使用它。description: str = "当需要知道今天的准确日期(年月日)时,使用此工具。"
args_schema(BaseModel)
args_schema是一个强类型、可自描述的合约规范,专门用于约束和指导大模型如何生成调用该工具的参数。
args_schema告诉LangChain框架:“我这个工具的所有输入参数,其名称、类型、约束和描述,都完整地定义在这个Pydantic BaseModel里。” 这样框架能自动完成以下几件事:
- 生成准确的JSON Schema:这是与OpenAI等模型API进行“函数调用”通信的协议基础。
- 自动化验证:在工具执行前,框架自动用schema验证模型生成的参数,无效输入会被拦截,避免工具函数崩溃。
- 工具发现的元数据:框架可以收集所有工具的schema,自动生成工具目录或用于路由。
# 3. 定义参数模式。即使此工具无需参数,也最好显式定义一个空的Schema,这是良好的实践。args_schema: Type[BaseModel] = DateToolInput
# 使用Pydantic定义输入参数的模型。本例中无需输入,所以模型为空。class DateToolInput(BaseModel):# 这个工具不需要任何输入参数。# 如果未来需要扩展,可以在这里添加字段,例如:# format: str = Field("YYYY-MM-DD", description="日期格式")query: Optional[str] = Field(default=None,description="查询日期的提示词,可为空")
_run方法实现
_run函数是工具“能力”的具体实现,是连接AI决策与现实世界接口的唯一执行终点。我们这里获取日期的真正实现就在这个函数里。
# 4. 实现核心的 _run 方法。这里是所有业务逻辑存放的地方。def _run(self, query: Optional[str] = None) -> str:"""执行工具,返回当前日期字符串。"""# 使用datetime模块获取当前日期,并格式化为易读的字符串。current_date = datetime.date.today().strftime("%Y-%m-%d")return f"今天是:{current_date}"# 5. (可选)实现 _arun 方法以支持异步调用。简单工具可暂不实现。async def _arun(self, query: Optional[str] = None):raise NotImplementedError("此工具暂不支持异步调用。")
调用方式
# 实例化工具date_tool = DateTool()
方式一(invoke)
直接使用date_tool:
# 方式一:invokeresult = date_tool.invoke("今天是几月几号?")result = date_tool.invoke("")print(result) # 输出:今天是:2026-03-13
方式二(run)
同样是直接使用date_tool:
# 方式二:runresult = date_tool.run("今天是几月几号?")result = date_tool.run("")print(result)
方式三(Agent)
# 方式三:Agent# 获取模型model = get_lc_model_client()# 创建工具列表tools = [date_tool]# 使用ReAct提示模板,让Agent具备“思考-行动”的推理能力prompt = "你是人工智能助手。需要帮助用户解决各种问题。"# 大模型客户端绑定工具# 创建Agentagent = create_agent(model=model,# 聊天模型tools=tools, # 工具列表system_prompt=prompt)# 调用示例result = agent.invoke({"messages": [{"role": "user", "content": "今天是几月几号?"}]},)
带参数的demo示例
后续我们会在实战案例中进一步巩固tool的使用,其中会涉及带参数的场景,具体可参见源码 tools-get_weather_demo.py。
自定义tool
这是开发者为了满足特定业务需求,通过继承BaseTool基类或使用@tool装饰器,自行创建的工具。它封装了专有的逻辑、数据或第三方服务,像上面举例的get_date/DateTool、open_browser、recom_drink都属于自定义工具。
- 本质:是你为AI智能体量身定做的“专属装备”,是实现业务差异化和核心价值的关键。
- 核心能力:连接一切——可以把任何函数、类方法、API、数据库查询、甚至遗留系统接口包装成一个工具。
- 典型场景:查询内部知识库或CRM系统;调用公司内部的订单或天气API;执行特定的数据分析或业务流程。
预置tool
定义
这是LangChain官方或活跃社区预先封装好、可直接调用的工具集合。它们解决的是AI应用中最常见、最通用的需求,属于“开箱即用”的标准化组件。
- 本质:经过良好测试、接口标准化的“功能黑盒”。只需关注配置(如API密钥),无需关心内部实现。
- 典型代表:
WikipediaQueryRun(查询维基百科)、DuckDuckGoSearchRun(网络搜索)、ArxivQueryRun(学术论文搜索)、PythonREPLTool(安全沙箱中运行Python代码)等。
示例
以 ArxivQueryRun 为例,展示简单调用方法。
- 使用
load_tools(["arxiv"])组装Agent:
# 组装Agentagent = create_agent( model=llm, tools=load_tools(["arxiv"]), #添加工具列表,绑定的论文查询的工具 system_prompt=system_prompt, checkpointer=memory# 传入记忆组件)
- 调用Agent查询论文:
# 调用Agent查询论文result = agent.invoke({"messages": [{"role": "user", "content": "请查询arxiv论文编号1605.08386的信息"}]},# 配置会话标识,用于区分不同用户config={"configurable": {"thread_id": "user_1"}}# 会话唯一标识,用于区分不同用户)
Function Calling:模型与工具的通信协议
Function Calling(函数调用)是模型在推理过程中生成的一个结构化输出。当模型决定要使用某个工具时,它不会直接执行代码,而是返回一个JSON对象,里面包含要调用的工具名称(name)和具体参数(arguments)。这是模型“思考”的结果。
- 向模型描述一个工具:必须提供一个符合特定JSON Schema格式的工具描述列表,包括工具名、描述和参数模式。
- 模型如何回应表示想要调用工具:模型不会在文本中说“请调用XXX工具”,而是在返回的特定结构(如OpenAI的
tool_calls数组)中,输出一个标准的JSON对象,包含要调用的工具名和参数。
前面讲到的bind_tools,当调用bind_tools(get_date)时,LangChain会自动将工具定义(args_schema和description)转换成底层模型(如GPT-4)所要求的Function Calling格式。
简单来说,Function Calling是“通信协议”,而LangChain Tool是“工程实现框架”。Tool是开发者提供的能力定义,Function Calling是模型发出的使用请求。在LangChain中,通过Tool来扩展Agent的能力,而Agent通过Function Calling来实际使用这些能力。
源码
github
相关攻略
这款硬朗像素风无衬线字体,带你体验未来感设计与9档字重自由 一、全文速览图 二、字体简介 本期免费商用字体:Sinkin Sans,由英国专业字体工作室K-Type出品。这是一款融合了inktrap凹槽工艺的现代无衬线体,兼具优雅气质与实用性能,视觉上现代而不失温度,清晰易读,辨识度极佳。全家族共提
基于Langchain-RAG实现网页摘要检索工具,通过WebBaseLoader加载网页并分割文档,构建向量存储和检索器。采用两种摘要方法:检索链结合文档链生成问答式摘要,或使用内置摘要链直接总结。需注意通过提示词模板显式控制输出语言,避免默认英文输出。
Roland是一款免费可商用的复古装饰字体,灵感源自中世纪哥特书写传统,融合历史厚重感与现代视觉张力。提供Regular、Contour、Shadow三种字重,适用于复古海报、文创包装、品牌标识等场景,无需署名,无隐藏条款。
QoderWake作为数字程序员需绑定身份与权限,通过监听仓库事件自动生成代码变更并创建PullRequest,随后在沙箱环境执行单元测试与集成验证,测试失败时输出分层诊断。最终生成交付包,经指定角色审批后方可合并部署,确保全流程可控可追溯。
QoderWake脚本执行错误可通过日志定位。调试核心五步:启用详细日志模式并重定向输出;按时间戳与进程ID筛选关键日志段;检查脚本内嵌变量与路径解析结果;复现失败步骤并注入临时调试语句;验证Python解释器与依赖模块兼容性。
热门专题
热门推荐
《Paralives》开发商承诺所有后续更新永久免费,拒绝付费DLC模式。15人小团队依靠首发销售额即可支撑多年运营,无需依赖额外内容包维持开发,展现了与《模拟人生》系列不同的差异化竞争思路。
2025年5月28日,比亚迪王朝网全新力作——宋Ultra DM-i正式推向市场,共推出5款配置车型,官方售价区间为12 99万至15 99万元。此次定价策略极具突破性:一款拥有310公里纯电续航能力的中型插电混动SUV,直接下探至13万元级别市场。作为王朝网络的新旗舰,该车明确瞄准高频出行需求场景
先来关注一个有趣的细节:苹果首款折叠屏手机,传闻将于今年秋季正式亮相。产品命名可能为iPhone Ultra,也有媒体称之为iPhone Fold——无论最终叫什么,这都将标志着苹果在折叠形态领域首次“出手”。 近日,配件厂商iFunSmart已率先上架iPhone Ultra的首批保护壳——这绝非
山寨币ETF迎来批量上市潮,首批项目市场表现如何?一文分析 Binance币安 欧易OKX ️ Huobi火币️ 最近,市场出现了一个不容忽视的新动向:XRP、DOGE、LTC、HBAR等现货ETF已经悄然登陆美国市场。与此同时,A VAX、LINK等资产的同类产品也正在审批流程中。进入11月以来,
近日,公司对SteamDeck1TBOLED版涨价300美元至949美元,上架短短不到24小时便再度售罄。据外界分析,该公司从中国大量补货并分批投放库存,高溢价未影响众多玩家的抢购热情与速度,其人气极其旺盛无比足以支撑快速清空。





