FastAPI + OpenAI 兼容协议 + DeepSeek 实战:大模型 Function Calling 工具调用全拆解
2026/8/6 5:04:54 网站建设 项目流程

FastAPI + OpenAI 兼容协议 + DeepSeek 实战:大模型 Function Calling 工具调用全拆解

功能概览

功能核心内容
天气查询工具声明tools、单轮发起、解析tool_calls(打印函数名+参数,不执行)
学历查询多工具声明、get_xueli外部调用 + Redis 缓存、工具结果回灌第二轮合成

调用链路总览

用户问题 → messages + tools 发给模型 → 模型返回 tool_calls(函数名 + JSON 参数) → 本地执行对应函数(天气/学历) → 把 函数结果 以 role=tool 追加回 messages → 再次请求模型 → 模型生成最终自然语言回答

一、环境准备:OpenAI 依赖下载与配置(单列)

工具调用依赖 OpenAI 官方 SDK,配置单独拎出来讲,不与业务代码混在一起。

1.1 依赖下载

pipinstallopenai

代码顶部都是from openai import OpenAI,装好这个包即可。redisrequests是学历查询里缓存和调用外部 API 用到的,按需安装:pip install redis requests

1.2 密钥配置(环境变量)

raw_key=os.getenv("DASHSCOPE_API_KEY")api_key=raw_key.strip()

代码通过DASHSCOPE_API_KEY读取阿里云百炼的 API Key,需提前在系统或.env中导出该变量。

1.3 base_url 兼容端点

client=OpenAI(api_key=api_key,base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",)

关键是base_url必须带/compatible-mode/v1后缀——百炼把 OpenAI 协议做成了兼容模式,漏写这一截会直接 404。

二、知识点讲解

2.1 什么是 Function Calling

模型本身不能直接查天气、查数据库。我们告诉模型"你有这些函数可用",模型在觉得需要时返回"我想调用某个函数、参数是这样",真正的函数由我们本地执行,再把结果交还给模型整理成话。这就是"模型决策 + 本地执行"的混合模式。

2.2 tools 工具声明结构

每个工具是一个 JSON-Schema:type: function+function.name(函数名)+function.description(给模型看的功能描述)+function.parameters(入参的 JSON Schema)。description写得好不好,直接决定模型会不会在该调用时调用。

2.3 tool_calls 是什么

模型认为需要调用工具时,返回的消息里message.tool_calls不为None,里面是列表,每项含function.name(函数名)、function.arguments(JSON 字符串参数)、id(该次调用的标识,回灌结果时必须带上)。

2.4 单轮识别 vs 完整闭环

  • 单轮:拿到tool_calls就结束,仅解析函数名/参数;
  • 闭环:执行函数 → 把结果以role=tool追加 → 再请求一次模型,由模型产出最终回答。少了"回灌"这步,用户永远看不到自然语言结果。

三、代码逻辑拆解

3.1 客户端初始化与模型选择

两个功能的顶部完全一致:

raw_key=os.getenv("DASHSCOPE_API_KEY")api_key=raw_key.strip()client=OpenAI(api_key=api_key,base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",)
  • 第 1 行:从环境变量取密钥原始值;
  • 第 2 行:.strip()去掉首尾空白,避免复制粘贴带换行导致鉴权失败;
  • 第 3–5 行:构造 OpenAI 客户端,base_url指向百炼兼容端点。模型名不在客户端里写,而是在每次create时指定(见 3.3)。

3.2 第一个功能:天气查询

3.2.1 工具声明与本地函数
tools=[{"type":"function","function":{"name":"get_current_weather","description":"当你想查询指定城市的天气时非常有用。","parameters":{"type":"object","properties":{"location":{"type":"string","description":"城市或县区,比如北京市、杭州市、余杭区等。",}},"required":["location"],},},},]defget_current_weather(arguments):weather_conditions=["晴天","多云","雨天"]random_weather=random.choice(weather_conditions)location=arguments["location"]returnf"{location}今天是{random_weather}。"
  • name:函数标识,必须与本地真实函数同名,模型返回时原样带回;
  • description:模型的"使用说明书",决定何时触发;
  • parameters.properties.location:入参 schema,required声明该参数必填,模型被要求必须产出它;
  • get_current_weather是本地实现:从参数里取location,随机返回一个天气(演示用,真实场景替换为气象 API)。
3.2.2 发起请求与单轮解析
defget_response(messages):completion=client.chat.completions.create(model="deepseek-v4-flash-0731",messages=messages,tools=tools,)returncompletion user_message=[{"role":"user","content":"你是谁"}]messages.extend(user_message)completion=get_response(messages)messages.append(completion.choices[0].message)ifcompletion.choices[0].message.tool_callsisNone:print(f"无需调用工具,直接回复:{completion.choices[0].message.content}")else:print("需要调用工具:")tool_calls=completion.choices[0].message.tool_callsforiintool_calls:f_name=i.function.name f_arg=i.function.argumentsprint(f"调用工具是:{f_name},参数:{f_arg}")
  • model="deepseek-v4-flash-0731":具体模型名,写在每次请求里;
  • tools=tools:把工具清单一并提交,模型才会返回tool_calls
  • messages.extend:把用户问题追加进对话列表(模块级messages = []);
  • completion.choices[0].message:模型原始消息,先整体appendmessages,保证多轮上下文连续;
  • if ... tool_calls is None:判断是否触发工具——None说明模型直接回答了,否则进入工具分支;
  • 工具分支里i.function.name取函数名、i.function.arguments取参数字符串(是字符串不是字典,要用json.loads解析);
  • 注意:这一段到这里只print没真正执行函数、也没回灌,所以它只是"识别"演示。

3.3 第二个功能:学历查询

3.3.1 Redis 客户端与外部学历查询工具
r=Redis(host='127.0.0.1',port=6379,db=11,decode_responses=True)defget_xueli(arguments):vcode=arguments["vcode"]key=f"boos:llm:academic_credential_verification:{vcode}"redis_vreif=r.get(key)ifredis_vreifisNone:API_KEY="MY_KEY_le0KRXAsCh6cNphDEURqJCs02jt3x1"BASE_URL="https://www.apimy.cn/api/xxw/bgcx"params={"key":API_KEY,"vcode":arguments["vcode"]}headers={"Content-Type":"application/json"}response=requests.get(BASE_URL,params=params,timeout=30)response.raise_for_status()data=response.json()r.set(key,json.dumps(data,ensure_ascii=False))returnjson.dumps(data,ensure_ascii=False)else:returnredis_vreif
  • 第 1–4 行:Redis 客户端,db=11与多轮对话的db=10区分开,decode_responses=True让取出的 value 直接是字符串;
  • key:用学历验证码拼键,同一验证码只查一次;
  • if redis_vreif is None:缓存未命中才真打外部 API,命中直接返回,省额度;
  • requests.get(..., timeout=30)timeout必带,外部接口抽风时不会把连接挂死;
  • r.set(key, json.dumps(data)):把结果以 JSON 字符串写回 Redis;返回时json.dumps(data)与缓存写入格式保持一致,工具结果对模型而言都是字符串即可。
3.3.2 多工具声明
tools=[{"type":"function","function":{"name":"get_current_weather","description":"当你想查询指定城市的天气时非常有用。","parameters":{"type":"object","properties":{"location":{"type":"string","description":"城市或县区,比如北京市、杭州市、余杭区等。",}},"required":["location"],},},},{"type":"function","function":{"name":"get_xueli","description":"当你想查询学历或验证学历时非常有用。","parameters":{"type":"object","properties":{"vcode":{"type":"string","description":"学历验证码",}},"required":["vcode"],},},},]

在天气工具之外还声明了get_xueli工具,结构一致,参数换成了vcode(学历验证码),required: ["vcode"]。两个工具并列放在同一个tools列表里,模型可自由选其一或都用。

3.3.3 完整闭环:执行工具 + 结果回灌
user_message=[{"role":"user","content":"帮我查下一下学历验证码是:你自己的学历吗"}]messages.extend(user_message)completion=get_response(messages)messages.append(completion.choices[0].message)ifcompletion.choices[0].message.tool_callsisNone:print(f"无需调用工具,直接回复:{completion.choices[0].message.content}")else:print("需要调用工具:")tool_calls=completion.choices[0].message.tool_calls fun={"get_current_weather":get_current_weather,"get_xueli":get_xueli}foriintool_calls:f_name=i.function.name f_arg=i.function.argumentsprint(f"调用工具是:{f_name},参数:{f_arg}")tool_result=fun[f_name](json.loads(f_arg))print(f"工具返回结果是:{tool_result}")# 把工具结果追加消息,再次请求模型生成自然文本messages.append({"role":"tool","tool_call_id":i.id,"content":tool_result})final_resp=get_response(messages)print("模型整理后的文本回答:",final_resp.choices[0].message.content)
  • fun = {...}:函数名到本地函数对象的映射表,模型返回函数名后据此分发执行;
  • fun[f_name](json.loads(f_arg)):用json.loads把参数字符串还原成字典后调用;
  • messages.append({"role": "tool", ...}):这是闭环的关键——role必须是"tool"tool_call_id填模型给的i.idcontent填工具返回值;
  • get_response(messages)再次发起,此时模型已拿到工具结果,会生成最终自然语言回答。源码注释也点明"把工具结果追加消息,再次请求模型生成自然文本"就是回灌 + 二次请求。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询