news 2026/8/26 22:37:18

大模型Function Calling实战:从原理到构建智能天气新闻助手

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
大模型Function Calling实战:从原理到构建智能天气新闻助手

1. 从“聊天”到“做事”:Function Calling的本质与价值

如果你用过ChatGPT或者文心一言这类大模型,你可能会发现一个有趣的现象:它们很能聊,上知天文下知地理,但一旦你让它帮你查一下今天的天气、订一张机票,或者从你的数据库里拉一份销售报表,它就立刻“哑火”了。它会告诉你:“作为一个AI模型,我无法直接访问实时数据或执行外部操作。” 这感觉就像你有一个知识渊博但手脚被绑住的朋友,他知道所有理论,却无法帮你动手做任何具体的事。

这就是“Function Calling”(函数调用)要解决的核心问题。它不是一个具体的API或SDK,而是一种标准化的协议或机制。简单来说,它让大语言模型(LLM)从一个纯粹的“文本生成器”,转变为一个可以理解你的意图、并“指挥”外部工具去执行具体任务的“大脑”或“调度中心”。模型本身不执行代码,它只负责思考和决策:根据你的指令,判断是否需要调用工具、调用哪个工具、以及以什么参数调用。然后,由你的应用程序去真正执行这个调用,并将结果返回给模型,由模型组织成最终的回答告诉你。

为什么这件事如此重要?因为在真实的生产环境中,大模型的威力远不止于生成一段优美的文案或代码。它的真正价值在于成为连接用户自然语言与复杂数字世界(你的数据库、API、业务系统)的“万能接口”。想象一下这些场景:

  • 智能客服:用户问“我的订单到哪了?”,模型不是凭空编造,而是调用“查询物流状态”的函数,传入用户的订单号,获取真实数据后回答。
  • 数据分析助手:你说“帮我分析一下上季度华东区的销售情况”,模型理解后,会调用“执行SQL查询”和“生成图表”的函数,组合多个步骤,最终给你一份带图表的报告。
  • 自动化工作流:你只需要说“提醒王总明天下午三点开会,并把会议纪要发到项目群”,模型就能依次调用“创建日历事件”、“发送即时消息”的函数。

所以,Function Calling实战,就是教会这个大模型“大脑”如何与你的“手和脚”(外部工具)协同工作。这不仅仅是调用一个API那么简单,它涉及到意图识别、参数抽取、错误处理、多轮对话状态维护等一系列工程问题。接下来,我将以一个完整的实战项目为例,拆解其中的每一个核心环节。

2. 项目蓝图:构建一个智能天气与新闻查询助手

为了把Function Calling讲透,我们抛开那些复杂的商业案例,设计一个足够典型又易于理解的实战项目:一个能通过自然对话,同时查询实时天气和当日头条新闻的智能助手

这个项目麻雀虽小,五脏俱全。它要求模型能处理两种不同的工具调用(天气和新闻),能从一个模糊的用户 query 中精确提取参数(如城市名),还能在需要时组合调用多个函数。比如,用户说“北京和上海的天气怎么样,顺便看看科技新闻”,这就是一个组合任务。

我们的技术栈选择如下,这也是目前最主流、最成熟的方案:

  • 大模型服务:OpenAI GPT-4/GPT-3.5-Turbo。选择它的原因很简单,它在Function Calling的支持上最成熟、最稳定,文档和社区资源也最丰富。其他如Anthropic Claude、国内的一些大模型也陆续支持了类似功能,但OpenAI的这套方案是目前事实上的标准。
  • 开发语言:Python。生态完善,从HTTP请求到JSON处理都极其方便。
  • 关键库openai官方库(用于调用Chat Completions API)、requests(用于调用我们模拟的外部天气/新闻API)。
  • 外部工具模拟:我们将创建两个简单的本地HTTP服务(或用公开的免费API模拟),来扮演“天气查询接口”和“新闻获取接口”。这比直接使用真实API更可控,便于我们演示所有流程。

这个项目的核心目标不是做出一个多炫酷的产品,而是彻底走通“用户提问 -> 模型决定调用 -> 提取参数 -> 执行函数 -> 结果返回 -> 模型生成回答”这个完整闭环,并理解其中每一个环节可能遇到的“坑”。

3. 核心机制拆解:对话中的“思考-行动”循环

在写第一行代码之前,我们必须先理解OpenAI的Chat Completions API在支持Function Calling时,一次完整的交互流程是怎样的。这不同于普通的聊天,它是一个多步骤的“思考-行动”循环。

3.1 第一步:定义“工具包”(函数描述)

首先,我们需要告诉模型,它手头有哪些“工具”可以用。这是通过一个名为tools的参数传递的,它是一个JSON数组,里面描述了每个函数的“说明书”。

[ { "type": "function", "function": { "name": "get_current_weather", "description": "获取指定城市的当前天气情况", "parameters": { "type": "object", "properties": { "location": { "type": "string", "description": "城市名称,例如:北京、San Francisco" }, "unit": { "type": "string", "enum": ["celsius", "fahrenheit"], "description": "温度单位,华氏度或摄氏度" } }, "required": ["location"] } } }, { "type": "function", "function": { "name": "get_top_news", "description": "获取指定类别的今日头条新闻", "parameters": { "type": "object", "properties": { "category": { "type": "string", "enum": ["technology", "business", "sports", "entertainment"], "description": "新闻分类" }, "max_results": { "type": "integer", "description": "返回新闻的最大条数,默认5条" } }, "required": ["category"] } } } ]

这里有三个关键点极易出错:

  1. description字段是灵魂:模型完全依赖这个描述来判断何时调用该函数。get_current_weather的描述必须清晰包含“天气”、“城市”等关键词。写得太模糊,模型可能不会调用;写得不准确,可能导致误调用。
  2. parameters的JSON Schema必须严谨:它定义了函数需要的参数类型、格式和是否必填。enum列表能极大提高模型提取参数的准确性。比如,如果你在这里把locationtype写成integer,模型在面对“北京天气”时就会困惑。
  3. required字段指明必填参数:这能帮助模型在用户未提供时主动追问。比如,如果locationrequired,但用户只说“今天天气如何?”,模型可能会在回复中要求用户提供城市信息,而不是盲目调用一个参数不全的函数。

3.2 第二步:模型的“思考”与“决策”

我们将用户消息和上面定义好的tools列表一起,发送给chat.completions.createAPI。此时,模型会进行关键决策:

  • 是否需要调用函数?基于对话历史和当前query,结合tools中每个函数的description,判断用户意图是否匹配某个函数的功能。
  • 调用哪个函数?如果匹配多个,模型会选择最合适的一个(或多个,如果支持并行)。
  • 参数是什么?从用户的自然语言中,精准地提取出符合parametersschema的JSON对象。

如果模型决定调用函数,API的返回会有一个关键变化:message对象中会包含一个tool_calls数组,而非常见的content。这个tool_calls里就包含了它想调用的函数名和它解析出来的参数。

# 假设用户输入:“上海今天气温多少度?” response = client.chat.completions.create( model="gpt-3.5-turbo", messages=[{"role": "user", "content": "上海今天气温多少度?"}], tools=weather_tools, # 传入之前定义的函数列表 tool_choice="auto", # 让模型自动决定是否调用 ) message = response.choices[0].message if message.tool_calls: # 模型决定调用函数了! tool_call = message.tool_calls[0] function_name = tool_call.function.name # “get_current_weather” function_args = json.loads(tool_call.function.arguments) # {"location": "上海", "unit": "celsius"}

这里的tool_choice参数很重要。设为“auto”是让模型自主决定;设为“none”则强制模型不调用任何函数,只生成文本;你还可以指定具体的函数名(如{“type”: “function”, “function”: {“name”: “get_current_weather”}})来强制模型调用某个函数,这在引导对话流程时很有用。

3.3 第三步:执行“行动”并反馈结果

我们的程序拿到function_namefunction_args后,就需要在本地真正执行这个函数了。这步完全由开发者控制。

def get_current_weather(location, unit="celsius"): # 这里应该是调用真实天气API,例如和风天气、OpenWeatherMap等 # 为了演示,我们模拟返回 print(f"[执行函数] 查询{location}的天气,单位:{unit}") # 模拟API调用延迟 time.sleep(0.5) return json.dumps({ "location": location, "temperature": 22 if unit == "celsius" else 72, "unit": unit, "description": "晴朗,微风", "humidity": 65 }) # 执行模型“想”调用的函数 available_functions = { "get_current_weather": get_current_weather, "get_top_news": get_top_news, } function_to_call = available_functions[function_name] function_response = function_to_call(**function_args)

执行完成后,我们得到了一个字符串格式的结果function_response。接下来,我们必须将这个结果以特定的格式反馈给模型,让它基于这个结果来组织最终对用户的回复。

3.4 第四步:完成循环,生成最终回复

我们将函数的执行结果,作为一个具有特定role的消息,追加到对话历史中,然后再次调用API。

# 将函数执行结果作为一条新消息追加 messages.append(response.choices[0].message) # 先追加模型上次返回的(包含tool_calls的消息) messages.append({ "role": "tool", "content": function_response, # 这里是函数执行的结果字符串 "tool_call_id": tool_call.id # 关键!必须对应之前的tool_call id }) # 再次调用模型,让它基于函数结果生成回答 second_response = client.chat.completions.create( model="gpt-3.5-turbo", messages=messages, ) final_answer = second_response.choices[0].message.content print(f"助手:{final_answer}") # 输出可能为:“上海目前天气晴朗,气温22摄氏度,湿度65%,微风。”

注意“role”: “tool”这条消息。它的content字段承载函数结果,tool_call_id必须与触发这次函数调用的tool_call.id严格对应,这样模型才知道哪次调用对应哪个结果。至此,一个完整的“用户提问 -> 模型思考并请求调用 -> 程序执行 -> 结果反馈 -> 模型生成回答”的循环就完成了。

4. 实战编码:从零搭建智能助手

理解了原理,我们开始动手编码。我会把重点放在那些容易出错的细节和提升体验的技巧上。

4.1 环境搭建与外部API模拟

首先安装依赖:pip install openai requests。你需要一个OpenAI的API Key。

接着,我们模拟两个外部服务。在实际项目中,你会替换成真实的API调用。这里我们用Flask快速搭建两个本地端点来模拟。

# simulate_api.py from flask import Flask, jsonify app = Flask(__name__) @app.route('/weather/<city>') def get_weather(city): # 模拟根据城市返回天气 weather_data = { "北京": {"temp": 18, "condition": "多云"}, "上海": {"temp": 22, "condition": "晴"}, "深圳": {"temp": 26, "condition": "小雨"}, } data = weather_data.get(city, {"temp": 20, "condition": "数据暂缺"}) return jsonify({"city": city, "temperature": data["temp"], "condition": data["condition"]}) @app.route('/news/<category>') def get_news(category): # 模拟返回新闻 news_map = { "technology": [{"title": "AI芯片取得新突破", "source": "科技网"}], "sports": [{"title": "国家队夺得冠军", "source": "体育周刊"}], } return jsonify({"category": category, "articles": news_map.get(category, [])}) if __name__ == '__main__': app.run(port=5000)

运行python simulate_api.py,你的本地就有了两个“外部API”:http://127.0.0.1:5000/weather/上海http://127.0.0.1:5000/news/technology

4.2 核心对话循环的实现

这是最核心的部分,我们将实现一个可以持续对话的循环。

# assistant_core.py import json import requests from openai import OpenAI client = OpenAI(api_key='your-api-key') # 替换为你的key BASE_URL = "http://127.0.0.1:5000" # 1. 定义工具(函数)列表 tools = [ { "type": "function", "function": { "name": "get_weather", "description": "查询指定城市的当前天气和温度。当用户询问天气、气温、气候时使用。", "parameters": { "type": "object", "properties": { "city": { "type": "string", "description": "中国的城市名称,必须是中文,如:北京、上海、广州。" } }, "required": ["city"] } } }, { "type": "function", "function": { "name": "get_news", "description": "获取指定分类的最新头条新闻。当用户询问新闻、资讯、消息时使用。", "parameters": { "type": "object", "properties": { "category": { "type": "string", "enum": ["科技", "体育", "财经", "娱乐"], "description": "新闻分类" } }, "required": ["category"] } } } ] # 2. 实现具体的函数逻辑 def execute_get_weather(city): """实际调用天气API""" try: resp = requests.get(f"{BASE_URL}/weather/{city}", timeout=5) resp.raise_for_status() data = resp.json() # 将API返回的数据格式化成模型容易理解的文本 return f"城市:{data['city']},气温:{data['temperature']}度,天气状况:{data['condition']}" except requests.exceptions.RequestException as e: return f"查询天气时出错:{str(e)}。请检查城市名称或网络连接。" def execute_get_news(category): """实际调用新闻API""" try: resp = requests.get(f"{BASE_URL}/news/{category}", timeout=5) resp.raise_for_status() data = resp.json() articles = data.get('articles', []) if not articles: return f"当前没有{category}类别的新闻。" news_list = [f"{idx+1}. {item['title']} ({item['source']})" for idx, item in enumerate(articles)] return f"{category}新闻:\n" + "\n".join(news_list) except requests.exceptions.RequestException as e: return f"获取新闻时出错:{str(e)}。" # 函数名到实际函数的映射 available_functions = { "get_weather": execute_get_weather, "get_news": execute_get_news, } # 3. 主对话循环 def run_conversation(): messages = [{"role": "system", "content": "你是一个乐于助人的助手,可以查询天气和新闻。请根据用户需求,使用工具获取信息后回答。"}] print("智能助手已启动。输入‘退出’或‘quit’结束对话。") while True: user_input = input("\n你:") if user_input.lower() in ['退出', 'quit', 'exit']: break messages.append({"role": "user", "content": user_input}) # 第一次调用:模型决定是否调用工具 try: response = client.chat.completions.create( model="gpt-3.5-turbo-1106", # 推荐使用明确支持function calling的版本 messages=messages, tools=tools, tool_choice="auto", ) except Exception as e: print(f"调用模型API失败:{e}") continue assistant_message = response.choices[0].message messages.append(assistant_message) # 将助手的回复(可能包含tool_calls)加入历史 # 检查是否需要调用函数 if assistant_message.tool_calls: print(f"[助手正在调用工具...]") for tool_call in assistant_message.tool_calls: function_name = tool_call.function.name function_args = json.loads(tool_call.function.arguments) # 执行函数 if function_name in available_functions: function_to_call = available_functions[function_name] function_response = function_to_call(**function_args) print(f"[工具 {function_name} 执行完毕]") # 将函数结果作为tool消息追加 messages.append({ "role": "tool", "content": function_response, "tool_call_id": tool_call.id }) else: # 如果函数名未定义,返回错误 messages.append({ "role": "tool", "content": f"错误:函数 {function_name} 未找到或不可用。", "tool_call_id": tool_call.id }) # 第二次调用:让模型基于函数结果生成最终回复 try: second_response = client.chat.completions.create( model="gpt-3.5-turbo-1106", messages=messages, ) except Exception as e: print(f"第二次调用模型API失败:{e}") continue final_message = second_response.choices[0].message messages.append(final_message) print(f"助手:{final_message.content}") else: # 模型没有调用工具,直接输出内容 print(f"助手:{assistant_message.content}") if __name__ == '__main__': run_conversation()

运行这个脚本,你就可以体验一个完整的智能助手了。试试以下对话:

  • “北京天气怎么样?” -> 它会调用get_weather,参数{"city": "北京"}
  • “给我看看科技新闻” -> 调用get_news,参数{"category": "科技"}
  • “上海和广州的天气呢?再看看体育新闻” ->这里模型可能会发起多个并行的tool_calls,我们的循环需要处理这种情况(当前代码已通过for tool_call in assistant_message.tool_calls:支持)。

5. 避坑指南与进阶技巧

在实际开发中,你会遇到比示例更复杂的情况。下面是我从多个项目中总结出的关键经验和避坑点。

5.1 参数提取的模糊性与边界处理

模型在提取参数时并非百分百准确,尤其是面对中文的模糊表达。

  • 问题:用户说“帮我查下帝都的天气”。你的函数参数定义期望的是“北京”,但模型可能直接提取出“帝都”。
  • 解决方案
    1. 在函数描述和参数描述中尽可能明确。例如,在city参数的description里写上“必须是标准的中国城市中文名,如北京、上海、广州,不要使用别名或简称”。
    2. 在本地函数执行层做一层映射和清洗。在execute_get_weather函数内部,可以维护一个小型的别名映射字典:{"帝都": "北京", "魔都": "上海", "羊城": "广州"}。如果API不支持别名,就在这里进行转换。
    3. 设计更鲁棒的参数Schema。对于非enum的字符串参数,可以增加pattern正则表达式约束,虽然模型不一定完全遵守,但能起到提示作用。

5.2 多轮对话中的状态管理

我们的示例是单次交互循环。在真实的聊天机器人中,对话历史会很长。Function Calling必须融入这个历史上下文。

  • 关键点:每次调用API时,messages列表必须包含完整的对话历史(包括之前所有的user,assistant,tool消息)。模型正是依靠这个完整的历史来理解上下文,避免重复询问已提供的信息。
  • 一个常见坑:用户说“今天天气如何?”,模型反问“请问您想查询哪个城市?”。用户回答“北京”。在第二次API调用时,messages里必须同时有第一次的问答和第二次的用户输入,模型才能综合理解,并调用get_weather(“北京”)。如果你只发送了最后一句“北京”,模型就失去了上下文。
  • Token成本注意:长上下文意味着更多的Token消耗。需要定期清理或总结过长的历史,尤其是在tool消息返回的数据量很大(如一大段新闻列表)时。一个策略是,将过长的函数结果进行摘要后再放入content

5.3 错误处理与用户反馈

外部API调用可能失败(网络超时、服务错误、无效参数)。我们的程序不能崩溃,也不能给用户返回原始的Python错误栈。

  • 在函数内部捕获异常:就像示例中execute_get_weather用了try...except,返回一个对用户友好的错误信息字符串,例如“天气服务暂时不可用,请稍后再试”。
  • 模型如何处理错误:当tool消息的content是一个错误描述时,模型通常会理解并生成相应的道歉或重试建议。例如,它可能会说“抱歉,查询天气时遇到了点问题,可能是网络原因。您可以稍后再试或告诉我另一个城市。”
  • 设置超时和重试:对于关键的外部调用,使用requests时务必设置timeout参数,并可以考虑加入简单的重试逻辑。

5.4 并行函数调用与执行顺序

从OpenAI的gpt-3.5-turbo-1106gpt-4-turbo等较新模型开始,支持在单个响应中返回多个tool_calls。这极大地提升了效率。

  • 场景:用户问“北京天气如何?另外有什么科技新闻?”
  • 模型行为:模型可能在一个响应里,同时返回两个tool_calls,一个调用get_weather,另一个调用get_news
  • 代码处理:我们的循环需要遍历assistant_message.tool_calls列表,并发或按顺序执行这些函数。这里就引出一个问题:这些函数调用有依赖关系吗?需要按顺序执行吗?
  • 经验:大多数情况下,模型发起的并行调用是独立的,可以并发执行以提升速度。但如果你设计的函数之间有依赖(比如函数A的输出是函数B的输入),那么你应该在函数描述中通过description明确说明,并且大概率模型会按顺序发起调用,或者你需要设计更复杂的流程控制逻辑。

5.5 系统提示词(System Prompt)的精心设计

system消息的角色是设定助手的“人格”和行为准则,对于Function Calling的成功至关重要。

  • 不要只说“你可以使用工具”。要更具体地指导它何时、如何用。
  • 好的示例:“你是一个查询助手。当用户询问天气时,请务必使用get_weather工具,并主动向用户询问未提供的城市名。当用户询问新闻时,请使用get_news工具。如果用户的问题不涉及这些功能,请直接回答,不要调用工具。”
  • 控制“工具滥用”:有些模型可能会过度调用工具。你可以在system prompt中强调“仅在必要时使用工具”,“如果用户只是普通聊天或问题很简单,无需调用工具”。
  • 处理模糊指令:对于“今天热吗?”,system prompt可以指示模型:“如果用户询问天气但未指明城市,且对话历史中未提及,请先反问用户所在城市。”

6. 超越基础:复杂工作流的编排

当你能熟练处理单个或并行函数调用后,就可以挑战更复杂的场景:多步骤工作流编排。这不再是模型一次思考就能完成的,需要开发者设计状态机或利用LangChain、AutoGPT等框架。

例如,一个“旅行规划”助手的工作流可能是:

  1. 用户:“我想去三亚旅行。”
  2. 模型调用search_flights(目的地=“三亚”),返回航班列表。
  3. 你将结果反馈给模型。
  4. 模型基于航班日期,调用search_hotels(目的地=“三亚”, 入住日期=XXX)
  5. 你将酒店结果反馈。
  6. 模型综合信息,生成一份包含航班和酒店建议的摘要。

在这个流程中,你需要维护一个复杂的对话状态,记录当前进行到哪一步、已经获取了哪些信息。这通常需要引入一个“工作流引擎”或“智能体(Agent)”框架来管理。其核心思想是:将大模型作为决策核心,根据中间结果动态决定下一步调用哪个函数,循环往复,直到达成用户目标或无法继续。

实现这样的系统,除了扎实的Function Calling基础,还需要良好的软件架构设计,例如使用“规划-执行-观察”(Plan-Execute-Observe)循环,并妥善处理可能出现的循环调用或失败分支。

Function Calling将大模型从“世界的观察者”变成了“世界的参与者”。通过这次从原理到实战的深度拆解,你应该已经掌握了让大模型学会调用工具的核心技能。记住,清晰准确的函数描述、健壮的错误处理、严谨的对话状态管理,是构建可靠智能应用的三块基石。从今天这个简单的天气新闻助手开始,尝试为你自己的业务系统接上这个强大的“自然语言大脑”吧。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/26 22:33:11

半人马机器人“小橙”技术解析:轮腿式架构与ROS 2开发实践

最近航天领域公开亮相了一款自研半人马机器人“小橙”&#xff0c;相关报道不长&#xff0c;但“半人马”构型加上“未来有望奔赴太空作业”的定位&#xff0c;已经能拆出很多技术方向。轮腿式机器人在月面、火星表面干活&#xff0c;面对的不是工厂产线那种固定环境&#xff0…

作者头像 李华
网站建设 2026/8/26 22:31:37

AI智能体框架选型实战:从OpenClaw到国产替代方案深度解析

1. 项目概述&#xff1a;从OpenClaw的兴衰看国产AI智能体生态的十字路口 最近在AI智能体开发圈子里&#xff0c;一个话题讨论得挺热&#xff1a;OpenClaw这个曾经风头无两的开源项目&#xff0c;似乎正在快速“退潮”。随之而来的&#xff0c;是一大批被戏称为“国产龙虾Agent”…

作者头像 李华
网站建设 2026/8/26 22:28:48

Claude Code精简80%提示词背后:上下文工程实战指南

如果你持续关注 AI 编程工具&#xff0c;大概率已经看到了这个有点“反直觉”的消息&#xff1a;Claude Code 的核心维护者在一次更新里&#xff0c;大幅精简了系统提示词&#xff0c;据社区讨论&#xff0c;删减比例接近 80%。 这听起来很矛盾。我们一直以为&#xff0c;给模…

作者头像 李华
网站建设 2026/8/26 22:21:38

VisDrone航拍目标检测实战:基于YOLO的小目标优化全攻略

简介&#xff1a;在计算机视觉领域&#xff0c;目标检测技术正从常规场景向无人机航拍等复杂场景延伸。航拍图像分辨率高、目标尺寸小且密集&#xff0c;尺度差异极大&#xff0c;给经典检测模型带来严峻挑战。YOLO系列作为高效的实时检测算法&#xff0c;凭借其快速迭代和灵活…

作者头像 李华
网站建设 2026/8/26 22:17:47

AI模型路由中间件实践:5分钟接入多平台,一键切换200+大模型

1. 项目概述&#xff1a;为什么你需要一个“模型路由器”&#xff1f;最近在折腾AI应用落地的朋友&#xff0c;估计都遇到过这个头疼事&#xff1a;手头攒了好几个大模型API的密钥&#xff0c;有闭源的GPT-4、Claude&#xff0c;也有开源的DeepSeek、通义千问&#xff0c;还有一…

作者头像 李华