Function Call实战:用GPT-4调用天气API,实现实时信息查询
在过去,与AI模型交互时,我们只能从它已有的知识库中获取信息。但如果我们需要查询的是“此刻”的天气,“此刻”的股票价格,或者“此刻”的特定新闻,模型自身的知识储备就显得捉襟见肘了。 为了让AI能够触及真实世界、获取实时动态的信息,Function Calling (函数调用) 技术应运而生。作为GPT-4等先进模型的一大亮点,Function Calling允许AI模型“理解”外部可用的函数(或其他API),并在对话过程中,判断何时需要调用这些函数,以及如何准备调用所需的参数。 本文将带大家进入Function Calling的实战世界,以一个经典的案例——调用天气API查询实时天气——来展示如何利用GPT-4的这一强大功能,让AI真正“动起来”并获取最新信息。 一、 Function Calling:AI模型的新能力 核心思想: Function Calling的核心在于,你向LLM提供一个可用函数的列表(描述其名称、功能、参数等),LLM在处理用户输入时,如果认为某个函数可以帮助它更好地回答问题,它就会输出一个结构化的JSON对象,其中包含要调用的函数名及其所需的参数。 工作流程: 定义可用函数 (You): 你向API描述你想让模型能够使用的函数(例如,get_weather(location, unit))。 用户提问 (User): 用户与模型对话,提出一个问题,例如“北京今天的天气怎么样?” 模型判断与输出 (GPT-4): GPT-4解析用户问题,识别出需要获取天气信息,并根据你提供的函数描述,生成一个JSON对象,指示调用get_weather函数,并填充参数location="北京"和unit="celsius"。 执行函数 (You): 你的应用程序接收到这个JSON对象,然后执行实际的Python函数(或调用外部API)。 模型接收结果 (GPT-4): 你将函数的执行结果(例如,一个JSON格式的天气数据),连同用户原始问题和模型先前的输出,再次发送给GPT-4。 模型生成最终回答 (GPT-4): GPT-4融合了函数结果和用户问题,生成一个最终的、易于理解的自然语言回答,例如“北京今天的天气是晴朗,气温 25 摄氏度。” 二、 实战场景:用GPT-4调用天气API 我们将使用以下组件来实现这个功能: GPT-4 API: 作为智能的核心,负责理解用户意图和生成函数调用。 OpenWeatherMap API: 一个免费(有使用限制)的天气信息提供商,我们将通过它获取真实的天气数据。 Python: 作为主要的开发语言,用来处理API请求、执行函数调用以及与GPT-4 API交互。 LangChain: 为了简化API调用、Prompt管理和整体流程的构建,我们将利用LangChain框架。 1. 环境准备 安装必要的库: <BASH> pip install openai langchain-openai python-dotenv requests 获取OpenWeatherMap API Key: 访问 OpenWeatherMap官网 注册一个免费账户,获取API Key。 获取OpenAI API Key: 如果你还没有,访问 OpenAI官网 注册并获取API Key。 创建.env文件: 在项目根目录下创建 .env 文件,并填入你的API Keys: <TEXT> OPENAI_API_KEY=sk-your_openai_api_key OPENWEATHER_API_KEY=your_openweathermap_api_key 2. 定义天气查询函数 我们的第一个步骤是定义一个Python函数,它能够接收城市名称和单位(摄氏度/华氏度)作为输入,并返回该城市的天气信息。我们将把这个函数暴露给GPT-4,以便它可以调用。 <PYTHON> import os import requests from dotenv import load_dotenv from typing import Optional from langchain_core.tools import tool # LangChain's decorator for defining tools # 加载API Keys load_dotenv() OPENWEATHER_API_KEY = os.getenv("OPENWEATHER_API_KEY") # --- 定义一个核心的工具函数 --- @tool("get_weather", args_schema={ "location": {"type": "string", "description": "The city to get the weather for"}, "unit": {"type": "string", "description": "The unit of temperature: 'celsius' or 'fahrenheit'"}, }, return_direct=False # Set to False to indicate the result should be passed to the LLM ) def get_weather(location: str, unit: str = "celsius") -> str: """ Fetches the current weather for a specified city and unit. Valid units are 'celsius' and 'fahrenheit'. """ if not OPENWEATHER_API_KEY: return "OpenWeatherMap API key not found. Please set OPENWEATHER_API_KEY in your .env file." base_url = "http://api.openweathermap.org/data/2.5/weather?" # Map units for the API call if unit.lower() == "celsius": api_unit = "metric" elif unit.lower() == "fahrenheit": api_unit = "imperial" else: return f"Invalid unit '{unit}'. Please use 'celsius' or 'fahrenheit'." complete_url = base_url + "appid=" + OPENWEATHER_API_KEY + "&q=" + location + "&units=" + api_unit response = requests.get(complete_url) data = response.json() if data["cod"] != "404": main = data["main"] weather_description = data["weather"][0]["description"] temp = main["temp"] return f"The weather in {location} is {weather_description} with a temperature of {temp:.1f} degrees {unit}." else: return f"City '{location}' not found. Please check the spelling." # --- Example of using the function directly (for testing) --- if __name__ == "__main__": print(get_weather(location="London", unit="celsius")) print(get_weather(location="New York", unit="fahrenheit")) print(get_weather(location="InvalidCity", unit="celsius")) print(get_weather(location="Paris", unit="kelvin")) # Test invalid unit 3. 构建 Agent 并集成 Function Calling 现在,我们将使用LangChain的ChatOpenAI的function_calling功能,并将其包装在一个Agent中。LangChain的Agent能够自动处理Tool的定义和LLM的函数调用。 <PYTHON> import os from dotenv import load_dotenv from langchain_openai import ChatOpenAI from langchain.agents import AgentExecutor, create_tool_calling_agent from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder from typing import Any, Dict, List # Load API Keys (ensure these are set in .env) load_dotenv() # Initialize the LLM with function calling enabled # Use a powerful model like gpt-4 for best results with function calling llm = ChatOpenAI(model="gpt-4", temperature=0, api_key=os.getenv("OPENAI_API_KEY")) # --- Define the tools --- # The `get_weather` function is already decorated with `@tool`. # LangChain's `from_function` can also be used, but `@tool` is more concise here. tools = [get_weather] # --- Create the Agent Executor --- # 1. Define the prompt for the agent prompt = ChatPromptTemplate.from_messages([ ("system", """You are a helpful assistant. You can answer questions about the weather by calling the appropriate tool. If the user asks for information not related to weather, politely inform them that you can only provide weather information. When you are done answering, say "TERMINATE". """), MessagesPlaceholder(variable_name="chat_history", optional=True), # For conversation memory ("human", "{input}"), MessagesPlaceholder(variable_name="agent_scratchpad"), # For tool calls and observations ]) # 2. Create an agent that uses the tools # `create_tool_calling_agent` is designed for models that support tool calling. agent = create_tool_calling_agent(llm, tools, prompt) # 3. Create the Agent Executor # This object will orchestrate the agent's interaction with the tools. agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True, handle_parsing_errors=True) # --- Run the example --- if __name__ == "__main__": print("--- Starting Weather Query Agent ---") # Example 1: Ask about a city and specific unit user_query_1 = "What is the weather like in Tokyo, in Fahrenheit?" print(f"\nUser Query: {user_query_1}") response_1 = agent_executor.invoke({"input": user_query_1, "chat_history": []}) print(f"Agent Response: {response_1['output']}") # Example 2: Ask about another city, default unit (celsius) user_query_2 = "How is the weather in Berlin?" print(f"\nUser Query: {user_query_2}") response_2 = agent_executor.invoke({"input": user_query_2, "chat_history": []}) print(f"Agent Response: {response_2['output']}") # Example 3: Ask a question that does not involve weather user_query_3 = "Tell me a joke." print(f"\nUser Query: {user_query_3}") response_3 = agent_executor.invoke({"input": user_query_3, "chat_history": []}) print(f"Agent Response: {response_3['output']}") # Example 4: Ask about a non-existent city user_query_4 = "What's the weather in the moon?" print(f"\nUser Query: {user_query_4}") response_4 = agent_executor.invoke({"input": user_query_4, "chat_history": []}) print(f"Agent Response: {response_4['output']}") 4. 运行与解释 当你运行上面的Python脚本时,你会看到如下的交互过程(verbose=True会显示Agent的内部思考和工具调用): Query 1: "What is the weather like in Tokyo, in Fahrenheit?" Agent 接收到你的问题。 Agent 识别出需要获取天气信息,并且需要使用 "Tokyo" 作为城市,"Fahrenheit" 作为单位。 Agent 调用 get_weather 工具,参数为 location="Tokyo" 和 unit="fahrenheit"。 Python 端的 get_weather 函数被执行,它调用 OpenWeatherMap API,获取东京的天气数据。 get_weather 函数返回一个包含天气描述的字符串。 Agent 接收到这个结果,并将其作为最终答案返回给你。 输出可能如下(具体天气数据会变化): <TEXT> User Query: What is the weather like in Tokyo, in Fahrenheit? > Entering new AgentExecutor chain... Tool Call: ```json { "action": "get_weather", "action_input": { "location": "Tokyo", "unit": "fahrenheit" } } get_weather: The weather in Tokyo is clear sky with a temperature of 75.2 degrees fahrenheit. Observation: The weather in Tokyo is clear sky with a temperature of 75.2 degrees fahrenheit. Thought:I have successfully retrieved the weather information for Tokyo in Fahrenheit. I can now provide this to the user. Final Answer: The weather in Tokyo is clear sky with a temperature of 75.2 degrees fahrenheit. Agent Response: The weather in Tokyo is clear sky with a temperature of 75.2 degrees fahrenheit. Query 2: "How is the weather in Berlin?" Agent 识别出需要天气信息。 用户没有指定单位,Agent会使用默认单位(我们已经在 @tool 装饰器中设定为 "celsius")。 Agent 调用 get_weather 工具,参数为 location="Berlin" 和 unit="celsius"。 Python 函数执行,返回柏林的天气信息。 Query 3: "Tell me a joke." Agent 接收到这个问题,发现它与天气无关。 根据系统Prompt中的指示(“If the user asks for information not related to weather, politely inform them that you can only provide weather information.”),Agent不会调用任何工具,而是直接生成一个包含此信息的回答。 输出可能如下: <TEXT> User Query: Tell me a joke. > Entering new AgentExecutor chain... Thought:I cannot tell jokes as my functionality is limited to providing weather information. I need to inform the user about this limitation. Final Answer: I apologize, but I can only provide weather information. Agent Response: I apologize, but I can only provide weather information. Query 4: "What's the weather in the moon?" Agent 识别需要天气信息,“the moon”作为位置。 get_weather 函数会被调用,但 OpenWeatherMap API 可能无法识别“the moon”作为一个有效地点。 get_weather 函数会返回“City 'the moon' not found.”。 Agent 接收到这个错误信息,并将其作为最终答案呈现。 三、 总结与进阶 通过这个实战案例,我们掌握了如何利用GPT-4的Function Calling能力: 定义工具: 使用@tool装饰器,为Python函数提供清晰的描述、参数和返回值说明。 集成到LangChain Agent: 使用create_tool_calling_agent 和 AgentExecutor 将工具与LLM连接起来。 处理函数调用: Agent会自动识别何时调用哪个函数,并传递正确的参数。 接收函数结果: Agent能接收函数返回的结果,并用它来生成最终的用户回答。 场景限定: 通过Prompt设计,可以约束Agent的行为,使其专注于特定任务(如天气查询)。 进阶方向: 多工具集成: 集成更多类型的工具,如股票行情API、计算器、搜索引擎等,构建更全面的Agent。 复杂逻辑与决策: 让Agent能够根据检索结果,决定下一步是再调用一个工具,还是进行组合,或是直接回答。 错误处理与鲁棒性: 增强get_weather等函数内或Agent层面的错误处理,例如API调用失败、数据格式错误等,并设计相应的重试或回退机制。 对话记忆: 集成chat_history,让Agent能够理解多轮对话中的上下文,例如用户在一个问题中提到“它”指代的是上一次提到的城市。 多Agent协作: 将Function Calling Agent与其他类型的Agent(如专门负责生成文档的Agent、专门负责测试代码的Agent)结合,实现更复杂的自动化流程。 Function Calling技术极大地扩展了AI的能力边界,使其能够与我们的数字世界更紧密地结合。通过实践,您将更能体会到这种“让AI调用工具”的力量。 |