
1. 从“玩具”到“武器”为什么工具与函数调用是LangChain实战的分水岭如果你跟着LangChain的教程一路走过来可能已经用ConversationChain、RetrievalQA这些链Chain搭建过一些能聊天的问答机器人。它们看起来挺酷能回答文档里的问题甚至能进行多轮对话。但当你真的想把它用到一个实际业务里比如让它帮你查一下今天的股票行情、给客户发一封邮件或者从公司内部系统里拉取一份销售数据时你大概率会卡住。你会发现这个模型好像被困在了一个由训练数据构成的“信息茧房”里它只能基于你喂给它的文本进行推理和生成对于外部世界正在发生的事情、对于你公司里那些没有公开的API它一无所知也无能为力。这就是“玩具”和“武器”的区别。一个只会聊天的AI是玩具而一个能调用工具、执行动作、影响现实世界的AI才能成为解决问题的武器。LangChain的“工具Tools”与“函数调用Function Calling”机制正是实现这一跃迁的核心。它让大语言模型LLM从一个纯粹的语言理解与生成引擎转变为一个可以协调和操作外部系统的“智能中枢”。今天我们就抛开那些简单的示例深入LangChain实战的腹地拆解如何让模型真正“动”起来去查询、计算、执行完成那些你真正需要它做的事情。2. 核心概念拆解工具、函数调用与智能体Agent的三位一体在深入代码之前我们必须厘清几个核心概念以及它们是如何协同工作的。很多初学者会混淆这些概念导致代码写出来却跑不通或者逻辑混乱。2.1 工具Tool模型的手和脚你可以把“工具”理解为模型可以使用的、一个定义明确的能力单元。它本质上是一个包装好的函数但这个函数有一个清晰的描述告诉模型“这个工具是干什么用的”、“它需要什么输入”。在LangChain中一个工具通常包含以下几个关键部分名称name 工具的标识符模型会通过这个名称来调用它。描述description这是最重要的部分。你需要用自然语言清晰、准确地描述这个工具的功能、适用场景以及输入参数的格式。模型完全依赖这个描述来决定在什么情况下使用这个工具以及如何构造输入。一个糟糕的描述会导致模型无法正确调用工具。函数func 工具背后实际执行的Python函数。参数模式args_schema 可选但强烈推荐。用于严格定义函数所需的参数及其类型如Pydantic模型。这能帮助模型更精确地生成调用参数。例如一个查询天气的工具描述可能是“一个用于查询指定城市当前天气情况的工具。输入参数‘location’应为城市名称的字符串例如‘北京’或‘New York’。”2.2 函数调用Function Calling模型与工具的沟通协议“函数调用”是连接模型和工具的桥梁。它不是一个具体的API而是一种交互模式。其工作流程如下模型决策 用户提出一个请求如“北京今天天气怎么样”。模型根据请求内容结合所有可用工具的描述判断是否需要调用工具以及调用哪一个工具。生成结构化请求 如果决定调用模型不会直接执行代码而是生成一个结构化的调用请求。这个请求通常包含tool_name工具名和tool_input一个符合工具参数模式的字典。例如{tool_name: get_weather, tool_input: {location: 北京}}。系统执行 LangChain框架或你的代码接收到这个结构化请求后在安全的环境中找到对应的工具函数并传入参数执行它。结果返回 工具执行的结果可能是字符串、字典等被返回给模型。模型整合回复 模型拿到工具返回的真实数据如“北京晴25摄氏度”再组织语言生成最终面向用户的自然语言回复。这个过程的关键在于模型本身不执行任何代码它只负责“思考”和“规划”生成一个可执行的指令。真正的执行发生在你控制的安全环境中。这既保证了安全性也使得模型能够利用远超其训练数据范围的外部能力。2.3 智能体Agent决策与执行循环的大脑“智能体”是LangChain中封装了上述决策-执行循环的高级抽象。你给它一组工具Tool和一个大模型LLM它就能自动地处理用户查询。智能体的核心是它的“决策逻辑”通常由AgentType指定它决定了模型在何时、以何种方式使用工具。一个典型的智能体工作流程是接收用户输入。智能体内部的“大脑”LLM决策逻辑根据当前输入和对话历史决定下一步行动是直接回答还是调用某个工具如果调用工具则通过函数调用机制生成请求执行工具并将结果作为新的上下文。重复步骤2和3直到智能体认为已经收集到足够的信息可以给出最终答案。生成最终答案并输出。所以工具是“能力”函数调用是“沟通方式”而智能体是运用这些能力和沟通方式来自主完成任务的“执行实体”。在实战中我们通常是通过配置一个智能体并将工具赋予它来构建一个可用的AI应用。3. 实战构建从零创建一个股票查询智能助手理论说得再多不如一行代码。让我们构建一个实用的股票查询助手。它需要能理解用户关于股票的问题如“腾讯的股价现在是多少”或“AAPL今天涨了吗”然后调用金融数据API获取实时信息最后组织成友好的回答。3.1 第一步定义核心工具函数首先我们需要一个真正能获取数据的函数。这里我们使用一个免费的金融API例如yfinance库需安装pip install yfinance作为演示。在实际生产中你可能会换成腾讯、阿里云、聚宽等更稳定、数据更全的API。import yfinance as yf from typing import Optional from pydantic import BaseModel, Field # 首先定义工具的输入参数模型。这能让模型更准确地理解需要什么。 class StockQueryInput(BaseModel): 查询单只股票信息的输入参数。 symbol: str Field(description股票代码例如00700.HK 代表腾讯港股AAPL 代表苹果美股。对于A股通常使用代码加后缀如000001.SZ平安银行-深市。) # 然后实现工具函数本身。 def get_stock_price(symbol: str) - str: 根据股票代码获取最新股价和基本信息。 参数: symbol: 股票代码字符串。 返回: 格式化的股票信息字符串。 try: # 使用 yfinance 获取股票ticker对象 ticker yf.Ticker(symbol) # 获取最近一天的市场数据 info ticker.info history ticker.history(period1d) if history.empty: return f未能获取到股票代码 {symbol} 的数据请检查代码是否正确注意市场后缀。 # 提取关键信息 current_price history[Close].iloc[-1] previous_close info.get(previousClose, history[Close].iloc[-2] if len(history) 1 else current_price) change current_price - previous_close change_percent (change / previous_close) * 100 if previous_close else 0 company_name info.get(longName, info.get(shortName, N/A)) result ( f股票{company_name} ({symbol})\n f当前价{current_price:.2f}\n f昨收{previous_close:.2f}\n f涨跌{change:.2f} ({change_percent:.2f}%) ) return result except Exception as e: # 在实际应用中这里应该有更细致的错误处理和日志记录 return f查询股票 {symbol} 时发生错误{str(e)}。请确认网络连接或代码有效性。 # 注意这里只是一个简单示例。yfinance 的信息获取可能因地区、网络有所延迟。 # 对于A股可能需要专门的库如 akshare或API。3.2 第二步将函数包装为LangChain工具有了函数我们需要用LangChain的Tool类把它包装起来并附上模型能理解的描述。from langchain.tools import Tool # 创建股票查询工具 stock_tool Tool( nameget_stock_price, # 工具名称模型通过这个名称调用 funcget_stock_price, # 背后实际执行的函数 description这是一个关键且详细的描述 用于查询指定股票代码的最新交易价格、涨跌幅和公司名称。 当用户询问某只股票的股价、今日表现、涨了还是跌了等问题时应使用此工具。 输入必须是一个明确的股票代码字符串。 对于港股代码通常以.HK结尾例如00700.HK代表腾讯。 对于美股使用通常的代码如AAPL代表苹果TSLA代表特斯拉。 对于A股需要完整的市场标识例如000001.SZ平安银行-深市600519.SH贵州茅台-沪市。 如果用户只提供了公司名如‘腾讯’你需要根据常识将其转换为可能的股票代码如‘00700.HK’但这可能存在歧义。 , args_schemaStockQueryInput # 关联我们定义的Pydantic参数模型让输入更规范 )注意description字段是灵魂。你写得越精确模型调用工具的准确率就越高。要站在模型的角度思考它只看到这段文字必须从中推断出使用场景和输入格式。3.3 第三步选择模型并创建智能体接下来我们需要一个大模型作为智能体的“大脑”并选择一个合适的智能体类型。这里我们使用OpenAI的模型你需要设置OPENAI_API_KEY和ReAct代理它是一种经典的推理行动模式。from langchain_openai import ChatOpenAI from langchain.agents import initialize_agent, AgentType from langchain.memory import ConversationBufferMemory import os # 设置你的OpenAI API Key (确保已设置环境变量 OPENAI_API_KEY) # os.environ[OPENAI_API_KEY] your-api-key-here # 1. 初始化大语言模型 # 使用 gpt-3.5-turbo 性价比高对于复杂任务可考虑 gpt-4 llm ChatOpenAI(modelgpt-3.5-turbo, temperature0) # temperature0 使输出更确定减少随机性在工具调用场景下更可靠。 # 2. 创建对话记忆可选但能让智能体记住上下文实现多轮对话 memory ConversationBufferMemory(memory_keychat_history, return_messagesTrue) # 3. 定义工具列表 tools [stock_tool] # 我们可以把多个工具放在这个列表里 # 4. 初始化智能体 # AgentType.CHAT_CONVERSATIONAL_REACT_DESCRIPTION 适合有记忆的对话场景 # 它基于ReAct框架适合需要多步推理和工具调用的任务。 agent initialize_agent( tools, llm, agentAgentType.CHAT_CONVERSATIONAL_REACT_DESCRIPTION, memorymemory, verboseTrue, # 设为True可以看到智能体的思考过程调试时非常有用 handle_parsing_errorsTrue # 优雅地处理模型输出解析错误 )3.4 第四步运行与测试现在让我们来测试这个智能助手。# 测试查询 query_1 腾讯控股今天的股价是多少 print(f用户: {query_1}) response_1 agent.invoke({input: query_1}) print(f助手: {response_1[output]}\n) # 测试多轮对话因为我们有memory query_2 那苹果公司呢 print(f用户: {query_2}) response_2 agent.invoke({input: query_2}) print(f助手: {response_2[output]}\n) # 测试一个需要模型“思考”并转换代码的查询 query_3 我想知道特斯拉股票的最新情况。 print(f用户: {query_3}) response_3 agent.invoke({input: query_3}) print(f助手: {response_3[output]})当你运行并将verboseTrue时你会在控制台看到类似以下的详细思考过程这是理解智能体工作的关键 Entering new AgentExecutor chain... Thought: 用户问的是腾讯控股的股价。我有一个工具叫 get_stock_price描述说可以查询股票价格并且提到腾讯的港股代码是00700.HK。我应该使用这个工具。 Action:{ action: get_stock_price, action_input: {symbol: 00700.HK} }Observation: 股票Tencent Holdings Ltd (00700.HK) 当前价320.60 昨收318.00 涨跌2.60 (0.82%) Thought: 我已经拿到了腾讯控股的股价信息现在可以组织语言回答用户了。 Action:{ action: Final Answer, action_input: 腾讯控股00700.HK的最新股价是320.60港元较昨日收盘价上涨2.60港元涨幅约0.82%。 } 最终答案腾讯控股00700.HK的最新股价是320.60港元较昨日收盘价上涨2.60港元涨幅约0.82%。这个过程完美展示了“思考Thought—行动Action—观察Observation”的ReAct循环。智能体成功地将自然语言问题“腾讯控股今天的股价是多少”解析为对工具get_stock_price的调用并正确生成了参数{symbol: 00700.HK}。4. 进阶实战处理复杂查询与多工具协作单一的股票查询工具已经很有用但真实世界的需求更复杂。用户可能会问“对比一下腾讯和阿里巴巴今天的股价表现。”或者“茅台股价现在是多少人民币换算成美元呢”这要求智能体具备多步推理和协调多个工具的能力。4.1 创建更多工具汇率换算与信息检索让我们再添加两个工具一个用于货币换算另一个用于获取公司简介模拟从数据库或知识库检索。# 工具2货币换算工具 (使用一个模拟的汇率API) from pydantic import BaseModel, Field class CurrencyConversionInput(BaseModel): 货币换算输入参数。 amount: float Field(description需要换算的金额数量。) from_currency: str Field(description源货币代码例如CNY, USD, HKD。) to_currency: str Field(description目标货币代码例如CNY, USD, HKD。) def convert_currency(amount: float, from_currency: str, to_currency: str) - str: 一个简单的货币换算函数此处使用固定汇率模拟生产环境应调用真实API。 # 模拟汇率表 exchange_rates { USD: {CNY: 7.2, HKD: 7.8}, CNY: {USD: 1/7.2, HKD: 1.1}, HKD: {USD: 1/7.8, CNY: 1/1.1}, } from_currency from_currency.upper() to_currency to_currency.upper() if from_currency to_currency: return f{amount} {from_currency} 等于 {amount} {to_currency}。 if from_currency in exchange_rates and to_currency in exchange_rates[from_currency]: rate exchange_rates[from_currency][to_currency] converted amount * rate return f{amount} {from_currency} 约等于 {converted:.2f} {to_currency} (汇率: ~{rate:.4f})。 else: return f抱歉暂不支持从 {from_currency} 到 {to_currency} 的换算。 currency_tool Tool( namecurrency_converter, funcconvert_currency, description用于在不同货币之间进行换算。当用户需要将一种货币金额转换为另一种货币时使用此工具。输入需要金额、源货币代码和目标货币代码。, args_schemaCurrencyConversionInput ) # 工具3公司信息检索工具 (模拟从向量数据库检索) from langchain_community.document_loaders import WebBaseLoader from langchain.text_splitter import RecursiveCharacterTextSplitter from langchain_openai import OpenAIEmbeddings from langchain_community.vectorstores import Chroma from langchain.tools.retriever import create_retriever_tool # 假设我们有一个关于科技公司的知识库这里用加载一个网页并创建向量库来模拟 loader WebBaseLoader(https://en.wikipedia.org/wiki/Tencent) docs loader.load() text_splitter RecursiveCharacterTextSplitter(chunk_size1000, chunk_overlap200) splits text_splitter.split_documents(docs) vectorstore Chroma.from_documents(documentssplits, embeddingOpenAIEmbeddings()) retriever vectorstore.as_retriever() # 使用LangChain提供的便捷函数创建检索工具 retriever_tool create_retriever_tool( retriever, search_company_info, 当用户询问某家公司的背景、主营业务、历史等详细信息时使用此工具进行检索。输入应是与公司相关的关键词或问题。, )4.2 配置多工具智能体并测试复杂任务现在我们将三个工具股票、汇率、检索都赋予智能体。# 更新工具列表 tools [stock_tool, currency_tool, retriever_tool] # 重新初始化智能体使用更强大的Zero-Shot React代理它更擅长处理未知的、需要规划的任务 from langchain.agents import AgentExecutor, create_react_agent from langchain import hub # 拉取一个预设的ReAct提示词模板 prompt hub.pull(hwchase17/react) # 创建智能体 agent create_react_agent(llm, tools, prompt) # 创建执行器 agent_executor AgentExecutor(agentagent, toolstools, verboseTrue, handle_parsing_errorsTrue) # 测试复杂查询 complex_query_1 腾讯现在的股价是多少港元如果我有1000美元能买多少股 print(f\n用户: {complex_query_1}) result_1 agent_executor.invoke({input: complex_query_1}) print(f助手: {result_1[output]}) complex_query_2 告诉我一些关于腾讯公司的主要业务信息然后查一下它今天的股价。 print(f\n用户: {complex_query_2}) result_2 agent_executor.invoke({input: complex_query_2}) print(f助手: {result_2[output]})在verboseTrue模式下你会看到智能体精彩的推理过程。对于第一个问题它可能会先调用get_stock_price获取腾讯股价假设是320 HKD。意识到需要将1000 USD转换为HKD于是调用currency_converter1000 USD to HKD。拿到换算后的港元金额例如7800 HKD再手动计算或继续让模型计算可购买的股数7800 / 320 ≈ 24股。这个过程完全由模型自主规划完成展示了多工具协作解决复杂问题的强大能力。5. 避坑指南与性能优化来自一线的实战经验在实际项目中使用工具和智能体绝不会像示例代码一样一帆风顺。下面是我在多个项目中总结出的关键坑点和优化建议。5.1 工具描述的艺术精确 vs. 泛化工具的description是成败的关键。写得太模糊模型无法准确调用写得太死板模型又不会灵活运用。坏描述“获取数据。”太模糊什么数据好描述“查询指定城市未来三天的天气预报。输入参数‘city’应为字符串格式的城市名例如‘北京’、‘上海’。返回信息包含日期、天气状况、最高最低温度和降水概率。”技巧在描述中列举1-2个典型的调用示例。模型非常擅长从例子中学习模式。例如可以在描述末尾加上“例如对于输入‘北京’工具将返回北京的未来三天预报。”5.2 解析错误Parsing Errors与智能体崩溃这是最常见的问题。模型输出的内容可能不符合LangChain智能体预期的严格JSON格式导致解析失败整个链中断。解决方案1初始化智能体时务必设置handle_parsing_errorsTrue。这能捕获错误并以更友好的方式重试或报错。解决方案2使用更强大的模型。gpt-3.5-turbo在复杂推理和严格格式化输出上不如gpt-4。如果工具调用逻辑复杂升级模型是立竿见影的方法。解决方案3自定义输出解析器。对于极其复杂的场景你可以继承AgentOutputParser类编写更鲁棒的解析逻辑来处理模型的“非标准”输出。5.3 控制成本与延迟工具调用的开销每次工具调用都意味着一次额外的模型API请求用于生成下一步的Thought/Action。在复杂任务中这可能导致高昂的API成本和较长的响应时间。优化1批量处理。如果用户问题可能触发多个同类工具调用如“比较A、B、C三只股票”可以考虑设计一个能批量查询的工具而不是让模型循环调用三次单一查询工具。优化2设置最大迭代次数。使用max_iterations和max_execution_time参数限制智能体的“思考”步数防止它在死循环中耗尽你的预算。agent_executor AgentExecutor( agentagent, toolstools, verboseTrue, max_iterations5, # 最多思考5步 early_stopping_methodgenerate # 达到上限后强制生成最终答案 )优化3缓存与异步。对于耗时的工具如网络请求确保它们是异步的async并考虑对结果进行缓存避免对相同参数的重复调用。5.4 安全性考量给模型戴上“镣铐”让模型调用外部工具存在潜在风险。一个恶意的用户输入可能诱导模型调用危险工具如删除文件、发送邮件的工具。最小权限原则每个工具只赋予完成其功能所需的最小权限。例如一个发送邮件的工具其发件人地址应该被预先固定而不是由模型动态生成。输入验证与净化在工具函数内部对传入的参数进行严格的验证和净化。例如对于文件路径工具要检查路径是否在允许的目录内。用户确认机制对于高风险操作如“发送邮件”、“下订单”可以在工具执行前设计一个让智能体先向用户确认的环节。这可以通过在工具链中插入一个需要用户确认的步骤来实现。5.5 调试技巧看清模型的“思考”过程当智能体行为不符合预期时verboseTrue是你的第一道防线。但除此之外检查提示词Prompt智能体的表现极大程度上受提示词影响。你可以通过agent.agent.llm_chain.prompt.template查看它使用的完整提示词。有时在提示词开头加入一些具体的指令如“你必须使用工具来获取实时信息”能显著改变模型的行为。模拟工具调用单独测试你的工具函数确保它们在不同输入下都能返回正确、格式化的结果。一个返回错误或异常的工具会打乱整个智能体的节奏。使用更具体的AgentTypeLangChain提供了多种AgentType如OPENAI_FUNCTIONS专为OpenAI函数调用优化、STRUCTURED_CHAT_ZERO_SHOT_REACT_DESCRIPTION适合需要复杂结构化输入的工具。根据你的工具特性选择合适的类型能减少很多解析问题。工具与函数调用将LangChain从一个有趣的对话框架提升为了一个能够构建真正自动化、智能化应用的强大平台。其核心思想——让LLM作为规划者和决策者让专业工具作为执行者——是构建下一代AI应用的主流架构。掌握它意味着你能够将AI的能力无缝嵌入到现有的业务流程和系统中解决那些以前需要大量定制开发才能解决的问题。从今天这个股票查询助手开始尝试为你自己的领域创建专属的工具集你会发现AI能做的事情远比你想象的要多。