构建可调用外部工具的AI助手:LangChain函数调用与API集成详解

前言

上节课中,我们详细学习了如何利用Function Calling机制实现文本标注、情感分析和结构化信息抽取任务,展示了Function Calling在处理结构化数据方面的巨大优势。我们可以不再仅仅基于提示词让其进行结构化的输出,而是可以通过定义类和函数的方式让其能够更稳定的进行输出。

本节课将继续深入,聚焦于如何通过LangChain创建和管理自定义工具(Tool),并将其与外部API服务无缝集成,从而搭建更加复杂且功能丰富的应用场景,例如实时天气查询、维基百科内容搜索以及第三方API服务的调用与管理。

工具(Tool)简介与自定义工具

工具(Tool)是LangChain提供的一种功能模块,可以封装特定任务或外部API调用,供大模型以Function Calling方式调用。工具定义通常包括以下三个要素:

  • name:工具的名称,用于调用。
  • description:工具的功能描述。
  • args:工具的参数,采用Pydantic定义输入数据结构。

在LangChain里其实有内置一些基本的工具,包括数学工具、搜索工具、SQL工具、Python工具等等,这些工具可以让我们无需创建便可直接使用。但是我们这节课关注的重点是如何自己创建新的工具来完成我们个性化的任务需求。

基础工具定义示例

我们以一个简单的天气查询工具为例,更深入地说明如何定义一个LangChain工具:

在定义工具时,需要使用@tool装饰器标注函数,这样LangChain才能将该函数识别为一个可供模型调用的工具。工具函数的参数需要清晰地描述,这样模型在调用工具时才能正确地提供参数。

以下是一个具体示例:

python 复制代码
from langchain.agents import tool

@tool
def search(query: str) -> str:
    """搜索在线天气信息。"""
    # 此处应调用真实的天气API,如OpenWeatherMap或其他服务
    return "42°F"

定义后,可以检查工具属性是否正确设置:

scss 复制代码
print(search.name)        # search
print(search.description) # 搜索在线天气信息。
print(search.args)        # {'query': {'title': 'Query', 'type': 'string'}}

此示例中的工具目前返回了一个固定的结果("42°F"),在实际应用中,可以将此函数连接到一个真实的API以动态返回实际天气信息。

进阶工具定义1------Open-Meteo天气查询

刚刚我们用了一个简单的案例讲解了一下工具的定义方法,下面我们将使用真实的天气API------Open-Meteo API,定义一个工具以获取实时天气数据。具体获取的方法如下所示:

python 复制代码
import requests
from pydantic import BaseModel, Field
import datetime

class OpenMeteoInput(BaseModel):
    latitude: float = Field(..., description="地点纬度")
    longitude: float = Field(..., description="地点经度")

@tool(args_schema=OpenMeteoInput)
def get_current_temperature(latitude: float, longitude: float) -> str:
    """获取给定坐标的当前温度。"""
    BASE_URL = "https://api.open-meteo.com/v1/forecast"
    params = {
        'latitude': latitude,
        'longitude': longitude,
        'hourly': 'temperature_2m',
        'forecast_days': 1
    }

    response = requests.get(BASE_URL, params=params)
    response.raise_for_status()
    results = response.json()

    current_utc_time = datetime.datetime.utcnow()
    time_list = [datetime.datetime.fromisoformat(time_str.replace('Z', '+00:00')) for time_str in results['hourly']['time']]
    temperature_list = results['hourly']['temperature_2m']

    closest_time_index = min(range(len(time_list)), key=lambda i: abs(time_list[i] - current_utc_time))
    current_temperature = temperature_list[closest_time_index]

    returnf'当前位置温度为 {current_temperature}°C'

这个函数用于获取指定地理坐标(纬度 (latitude)和经度 (longtiude) )当前的气温 。它通过调用 Open-Meteo API 的天气预报接口,获取该坐标点未来一整天(24小时)逐小时的气温数据。函数执行逻辑如下:

  1. 用户传入坐标参数(纬度和经度)。
  2. 函数向 Open-Meteo 接口发送请求,获取该区域未来 1 天的逐小时气温信息。
  3. 返回数据中包含时间序列和对应的气温序列,函数会查找最接近当前UTC时间的时间点,并取出该时刻的气温值。
  4. 最终返回一条格式化字符串,如 "当前位置温度为 18.3°C"

我们会发现,这里输入的精度和纬度是基于OpenMeteoInput 来进行定义的,里面清晰的展示了需要输入的数据类型是浮点数,然后我们在具体的描述里看到了... ,这其实在Pydantic中是意味着说这个选项是必填项,是必须有的信息,当然也有其他类似的写法如下所示:

ini 复制代码
latitude: float  # 默认也是必填,但不能加描述信息
latitude: float = Field(..., description="说明文字")  # 必填 + 带说明
latitude: Optional[float] = Field(None, description="可以不填")  # 非必填

然后我们调用并测试工具:

arduino 复制代码
print(get_current_temperature.invoke({"latitude": 13, "longitude": 14}))

这个时候我们就能够得到具体的温度信息了,比如The current temperature is 30.5°C

进阶工具定义2------维基百科API进行信息检索

除了天气的API以外,其实我们可以在网络上获取到大量的公开API,其中里面知识含量最高的当属维基百科的API了,当然这个API在国内环境下无法直连,需要通过代理或镜像才能连接。。具体创建的方法如下所示:

python 复制代码
# 使用前需要先通过以下指令安装维基百科库
# pip install wikipedia -i https://pypi.org/simple  
import wikipedia

@tool
def search_wikipedia(query: str) -> str:
    """运行维基百科搜索并返回页面摘要。"""
    page_titles = wikipedia.search(query)
    summaries = []
    for page_title in page_titles[:3]:
        try:
            wiki_page = wikipedia.page(title=page_title, auto_suggest=False)
            summaries.append(f"页面: {page_title}\n摘要: {wiki_page.summary}")
        except wikipedia.exceptions.PageError:
            continue

    return"\n\n".join(summaries) if summaries else"未找到相关结果"

我们可以简单来看看这个函数里面具体执行的流程:

  1. 调用 wikipedia.search(query) 执行模糊搜索,获取相关页面标题列表;
  2. 遍历搜索到的前 3 个页面标题;
  3. 使用 wikipedia.page(title=..., auto_suggest=False) 获取每个页面的完整内容;
  4. 从中提取 summary(摘要部分),拼接成字符串返回;
  5. 如果某些页面获取失败(如重定向或不存在),自动跳过;
  6. 如果最终没有结果,返回提示 "未找到相关结果"

这样我们就能够实现传入一个问题然后传出一系列维基百科中可能的回复了!我们可以通过以下的代码调用维基百科工具:

arduino 复制代码
print(search_wikipedia.invoke({"query": "LangChain"}))

具体的输出如下所示:

vbnet 复制代码
'Page: LangChain\nSummary: LangChain is a software framework that helps facilitate the integration of large language models (LLMs) into applications. As a language model integration framework, LangChain's use-cases largely overlap with those of language models in general, including document analysis and summarization, chatbots, and code analysis.\n\nPage: Retrieval-augmented generation\nSummary: Retrieval-augmented generation (RAG) is a technique that enables large language models (LLMs) to retrieve and incorporate new information. With RAG, LLMs do not respond to user queries until they refer to a specified set of documents. These documents supplement information from the LLM's pre-existing training data. This allows LLMs to use domain-specific and/or updated information that is not available in the training data. For example, this helps LLM-based chatbots access internal company data or generate responses based on authoritative sources.\nRAG improves large language models (LLMs) by incorporating information retrieval before generating responses. Unlike traditional LLMs that rely on static training data, RAG pulls relevant text from databases, uploaded documents, or web sources. According to Ars Technica, "RAG is a way of improving LLM performance, in essence by blending the LLM process with a web search or other document look-up process to help LLMs stick to the facts." This method helps reduce AI hallucinations, which have caused chatbots to describe policies that don't exist, or recommend nonexistent legal cases to lawyers that are looking for citations to support their arguments.\nRAG also reduces the need to retrain LLMs with new data, saving on computational and financial costs . Beyond efficiency gains, RAG also allows LLMs to include sources in their responses, so users can verify the cited sources. This provides greater transparency, as users can cross-check retrieved content to ensure accuracy and relevance.\nThe term RAG was first introduced in a 2020 research paper from Meta.\n\nPage: Model Context Protocol\nSummary: The Model Context Protocol (MCP) is an open standard, open-source framework introduced by Anthropic to standardize the way artificial intelligence (AI) models like large language models (LLMs) integrate and share data with external tools, systems, and data sources. Designed to standardize context exchange between AI assistants and software environments, MCP provides a model-agnostic universal interface for reading files, executing functions, and handling contextual prompts. It was officially announced and open-sourced by Anthropic in November 2024, with subsequent adoption by major AI providers including OpenAI and Google DeepMind.

函数选择与智能路由(Function Routing)

在学习完工具创建之后,我们可以一起来看看怎么把工具都串联起来,并且在需要的时候进行智能选择。LangChain 支持将多个工具函数(tools)注册到模型中,模型根据用户意图自动选择调用哪个函数。这种功能被称为函数路由(function routing),是 Function Calling 技术的重要组成部分。

下面以两个工具函数 search_wikipediaget_current_temperature 为例,演示如何构建一个具备自动决策能力的多函数模型。

工具函数格式化与注册

首先我们需要将我们自定义的 Python 工具函数(如查询天气、搜索百科)转换为 OpenAI 支持的标准函数结构,并将其绑定到大语言模型上。这样一来,模型便具备了"调用外部工具"的能力,可根据用户的自然语言自动决定是否调用函数以及使用哪些参数。

ini 复制代码
from langchain_openai import ChatOpenAI
from langchain_core.utils.function_calling import convert_to_openai_function 

# 将两个函数转换为 OpenAI 格式
functions = [
    convert_to_openai_function(f) for f in [
        search_wikipedia, get_current_temperature
    ]
]

# 将函数绑定到模型中
model = ChatOpenAI(
    model="qwen-turbo",
    base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
    api_key="sk-0"# 你的 API key
).bind(functions=functions)

工具模型调用

但是我们发现调用模型并不会直接返回人类可读的文本,而是会返回一个结构化结果,其中包含了模型决定调用的函数名称(function name)以及该函数所需的输入参数。这是 Function Calling 技术的核心特点------让模型成为"决策者",而工具函数成为"执行者"。

css 复制代码
from langchain.prompts import ChatPromptTemplate

prompt = ChatPromptTemplate.from_messages([    ("system", "You are helpful but sassy assistant"),    ("user", "{input}"),])

chain = prompt | model

print(chain.invoke({"input": "what is the weather in sf right now"}))

所得到的回复如下所示:

python 复制代码
content='' additional_kwargs={'function_call': {'arguments': '{"latitude": 37.7749, "longitude": -122.4194}', 'name': 'get_current_temperature'}, 'refusal': None} response_metadata={'token_usage': {'completion_tokens': 37, 'prompt_tokens': 280, 'total_tokens': 317, 'completion_tokens_details': None, 'prompt_tokens_details': {'audio_tokens': None, 'cached_tokens': 256}}, 'model_name': 'qwen-turbo', 'system_fingerprint': None, 'id': 'chatcmpl-1179ce14-5947-9898-9cd0-cb3772f4a6c4', 'finish_reason': 'function_call', 'logprobs': None} id='run-8310f8ac-7f65-43dc-b043-8d02fd58a72d-0' usage_metadata={'input_tokens': 280, 'output_tokens': 37, 'total_tokens': 317, 'input_token_details': {'cache_read': 256}, 'output_token_details': {}}

解析函数调用输出结构

为了能够实现模型调用工具完成任务,我们可以通过加入 OpenAIFunctionsAgentOutputParser 模块,将这一过程转化为标准的 AgentActionAgentFinish 对象,以便后续进行程序化处理:

ini 复制代码
from langchain.agents.output_parsers import OpenAIFunctionsAgentOutputParser

# 将 prompt 和 model 组成链,并加入输出解析器
chain = prompt | model | OpenAIFunctionsAgentOutputParser()

# 执行函数调用意图识别
result = chain.invoke({"input": "what is the weather in sf right now"})

# 打印模型选择的函数与参数
print(result.tool)        # 例如:"get_current_temperature"
print(result.tool_input)  # 例如:{"latitude": 37.7749, "longitude": -122.4194}

对于不涉及函数调用的输入,例如普通对话型问候,模型将直接返回输出值:

ini 复制代码
result = chain.invoke({"input": "hi!"})
print(result.return_values)  # {"output": "Hello! How can I assist you today?"}

通过这种机制,我们可以实现"函数调用意图识别"与"自然语言应答"的统一处理逻辑,形成完整的 AI 智能响应管道。

构建智能函数路由系统(Function Router)

在获取到模型返回的结构化调用信息后,我们可以进一步设计一个"函数路由器"来动态调用对应工具函数,实现真正的"模型决策 + 工具执行"闭环系统。

以下是一个简单而完整的路由逻辑实现:

python 复制代码
from langchain.schema.agent import AgentFinish

def route(result):
    if isinstance(result, AgentFinish):
        # 模型直接返回文本,不涉及函数调用
        return result.return_values['output']
    else:
        # 模型选择了一个工具函数,则执行该函数
        tools = {
            "search_wikipedia": search_wikipedia,
            "get_current_temperature": get_current_temperature,
        }
        return tools[result.tool].run(result.tool_input)

我们可以将其集成到整体链中:

ini 复制代码
chain = prompt | model | OpenAIFunctionsAgentOutputParser() | route

我们可以测试不同类型的用户输入,实现动态函数调用与结果返回:

ini 复制代码
result = chain.invoke({"input": "What is the weather in san francisco right now?"})
print(result)
# -> 例如:The current temperature is 14.9°C

result = chain.invoke({"input": "What is langchain?"})
print(result)
# -> 例如:LangChain is a framework for integrating LLMs into applications...

result = chain.invoke({"input": "hi!"})
print(result)
# -> Hello there! How can I assist you today?

通过这样的链式结构设计,我们实现了一个具备自然语言理解与函数调用能力的"轻量级智能助理"系统,它不仅可以对话,还能执行查询任务,是 LangChain 在多函数调用场景中的典型应用范式。

本节课完整的代码如下所示:

python 复制代码
from langchain.agents import tool
import requests
from pydantic import BaseModel, Field
import datetime

# Define the input schema
class OpenMeteoInput(BaseModel):
    latitude: float = Field(..., description="Latitude of the location to fetch weather data for")
    longitude: float = Field(..., description="Longitude of the location to fetch weather data for")

@tool(args_schema=OpenMeteoInput)
def get_current_temperature(latitude: float, longitude: float) -> dict:
    """Fetch current temperature for given coordinates."""
    
    BASE_URL = "https://api.open-meteo.com/v1/forecast"
    
    # Parameters for the request
    params = {
        'latitude': latitude,
        'longitude': longitude,
        'hourly': 'temperature_2m',
        'forecast_days': 1,
    }

    # Make the request
    response = requests.get(BASE_URL, params=params)
    
    if response.status_code == 200:
        results = response.json()
    else:
        raise Exception(f"API Request failed with status code: {response.status_code}")

    current_utc_time = datetime.datetime.utcnow()
    time_list = [datetime.datetime.fromisoformat(time_str.replace('Z', '+00:00')) for time_str in results['hourly']['time']]
    temperature_list = results['hourly']['temperature_2m']
    
    closest_time_index = min(range(len(time_list)), key=lambda i: abs(time_list[i] - current_utc_time))
    current_temperature = temperature_list[closest_time_index]
    
    returnf'The current temperature is {current_temperature}°C'

print(get_current_temperature.invoke({"latitude": 13, "longitude": 14}))

import wikipedia

@tool
def search_wikipedia(query: str) -> str:
    """运行维基百科搜索并返回页面摘要。"""
# 由于无法直连外网,因此这部分代码
    return'123'

print(search_wikipedia.invoke({"query": "LangChain"}))

from langchain_openai import ChatOpenAI
from langchain_core.utils.function_calling import convert_to_openai_function 

# 将两个函数转换为 OpenAI 格式
functions = [
    convert_to_openai_function(f) for f in [
        search_wikipedia, get_current_temperature
    ]
]

# 将函数绑定到模型中
model = ChatOpenAI(
    model="qwen-turbo",
    base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
    api_key="sk-0"# 你的 API key
).bind(functions=functions)

from langchain.prompts import ChatPromptTemplate

prompt = ChatPromptTemplate.from_messages([
    ("system", "You are helpful but sassy assistant"),
    ("user", "{input}"),
])

chain = prompt | model

print(chain.invoke({"input": "what is the weather in sf right now"}))

from langchain.agents.output_parsers import OpenAIFunctionsAgentOutputParser

chain = prompt | model | OpenAIFunctionsAgentOutputParser()
result = chain.invoke({"input": "what is the weather in sf right now"})

print(result.tool)        # "get_current_temperature"
print(result.tool_input)  # {"latitude": 37.7749, "longitude": -122.4194}

result = chain.invoke({"input": "hi!"})
print(result.return_values)  # {"output": "Hello! How can I assist you today?"}

from langchain.schema.agent import AgentFinish

def route(result):
    if isinstance(result, AgentFinish):
        return result.return_values['output']
    else:
        tools = {
            "search_wikipedia": search_wikipedia,
            "get_current_temperature": get_current_temperature,
        }
        return tools[result.tool].run(result.tool_input)

chain = prompt | model | OpenAIFunctionsAgentOutputParser() | route

result = chain.invoke({"input": "What is the weather in san francisco right now?"})
# -> 'The current temperature is 15.9°C'
print(result)
result = chain.invoke({"input": "What is langchain?"})
# -> 'Page: LangChain\nSummary: LangChain is a software framework...'
print(result)
result = chain.invoke({"input": "hi!"})
# -> 'Hello! How can I assist you today?'
print(result)

总结

通过本节内容,我们构建了一个具备函数自动调用能力的 LangChain 智能系统。从工具的定义出发,我们一步步实现了模型对函数的识别与选择,并借助函数调用解析器和路由机制,完成了"从理解到执行"的闭环。在整个过程中,LangChain 的 Function Calling 架构不仅提升了系统的可扩展性,也让自然语言的交互具备了更强的实用性和控制力。

无论是调用天气 API、搜索百科知识,还是未来对接更复杂的业务接口,这种设计模式都能很好地支撑实际项目开发。借助大语言模型的理解能力和函数的执行能力,我们正逐步搭建起"AI+工具"的智能交互中台。

下一节课,我们将进一步升级这个系统,从单轮任务执行扩展到多轮对话交互。我们将基于 LangChain 的 Memory 模块构建一个能够持续记忆上下文的聊天 Agent,让 AI 助理不仅能调用工具,还能理解用户的"前后语境"和"任务意图"。

敬请期待,我们即将进入对话式智能体的实战篇章。

原文地址:https://mp.weixin.qq.com/s/VvQLZ5nriNQrVneHN0XwbQ

相关推荐
马可奥勒留4 小时前
睡前幻想——基于透明化黄金锚定的超主权货币体系设计:一种解决政府货币滥用的奥地利学派方案
程序员
小兵张健6 小时前
要不要选计算机?写给不知道怎么选专业的同学
程序员
2501_915374356 小时前
LangChain开发智能问答(RAG)系统实战教程:从零构建知识驱动型AI助手
人工智能·langchain
聚客AI8 小时前
Masked LM革命:解析BERT如何用15%掩码率颠覆NLP预训练
人工智能·llm·掘金·日新计划
土豆12508 小时前
MCP图片处理工具完整教程 - 动态发现与Cursor集成
llm·cursor·mcp
DeepSeek忠实粉丝9 小时前
微调篇--超长文本微调训练
人工智能·程序员·llm
虎鲸不是鱼10 小时前
Spring Boot3流式访问Dify聊天助手接口
java·spring boot·后端·大模型·llm
同志们13 小时前
一文弄懂用Go实现MCP服务:从STDIO到Streamable HTTP的完整实现
后端·llm
程序员麻辣烫13 小时前
模型Function Call
后端·llm