多轮Function Calling的最佳实践
1️⃣ 保持对话上下文完整
每一轮调用的 messages 都要保留历史,确保大模型知道之前发生了什么,才能根据函数调用结果做出新的判断。
2️⃣ 判断是否需要工具调用
使用 response.tool_calls 判断是否返回了函数调用请求,如果有就执行对应函数并追加结果。
3️⃣ 使用 while 循环处理多轮
大模型可能连续多轮调用函数(比如先找坐标再找周边),所以需要用 while 循环持续判断是否还有函数调用。
4️⃣ 将工具调用结果加入 messages
每次函数执行完后,要把结果包装成 tool role 的 message,加到 messages 中,再次发送给大模型。
5️⃣ 加入容错机制
加上 try…except 来处理 API 错误或参数问题,避免中断程序。
示例程序:
import os
import json
import requests
from openai import OpenAI
from dotenv import load_dotenv, find_dotenv# 加载环境变量
load_dotenv(find_dotenv())# 读取 Key 和基础 URL
amap_key = os.getenv("AMAP_KEY")
amap_base_url = os.getenv("AMAP_URL", "https://restapi.amap.com/v5")# 初始化 OpenAI 客户端
client = OpenAI()# JSON 打印工具
def print_json(data):if hasattr(data, 'model_dump'):data = data.model_dump()elif hasattr(data, '__dict__'):data = data.__dict__print(json.dumps(data, indent=2, ensure_ascii=False))# 生成大模型回复
def get_completion(messages, model="gpt-4o"):try:response = client.chat.completions.create(model=model,messages=messages,temperature=0,seed=1024,tool_choice="auto",tools=[{"type": "function","function": {"name": "get_location_coordinate","description": "根据POI名称,获得POI的经纬度坐标","parameters": {"type": "object","properties": {"location": {"type": "string", "description": "POI名称,必须是中文"},"city": {"type": "string", "description": "POI所在城市,必须是中文"}},"required": ["location", "city"]}}},{"type": "function","function": {"name": "search_nearby_pois","description": "搜索给定坐标附近的POI","parameters": {"type": "object","properties": {"longitude": {"type": "number", "description": "经度"},"latitude": {"type": "number", "description": "纬度"},"keyword": {"type": "string", "description": "关键词,如咖啡、酒店等"}},"required": ["longitude", "latitude", "keyword"]}}}])return response.choices[0].messageexcept Exception as e:print(f"调用大模型失败:{str(e)}")exit()# 实现获取坐标功能
def get_location_coordinate(location, city):url = f"{amap_base_url}/place/text?key={amap_key}&keywords={location}®ion={city}"r = requests.get(url)result = r.json()if "pois" in result and result["pois"]:poi = result["pois"][0]lng, lat = poi.get("location").split(",")return {"name": poi.get("name"),"longitude": float(lng),"latitude": float(lat)}return None# 实现搜索附近 POI 功能
def search_nearby_pois(longitude, latitude, keyword, debug=False):url = f"{amap_base_url}/place/around"params = {"key": amap_key,"keywords": keyword,"location": f"{longitude},{latitude}","radius": 3000,"s": "rsv3"}try:r = requests.get(url, params=params)if debug:print("请求的 URL:", r.url)print("HTTP 状态码:", r.status_code)print("原始响应内容:", r.text)if r.status_code != 200:return f"请求失败,状态码:{r.status_code}"result = r.json()ans = ""if "pois" in result and result["pois"]:for i in range(min(3, len(result["pois"]))):poi = result["pois"][i]ans += f"{poi['name']}\n{poi['address']}\n距离:{poi.get('distance', '?')}米\n\n"return ans.strip()else:return "附近没有搜索到相关的店铺,请尝试更换地点或关键词。"except Exception as e:return f"解析高德 API 响应时出错:{str(e)}"# 用户输入
prompt = "我想在五道口附近喝咖啡,给我推荐几个"
messages = [{"role": "system", "content": "你是一个地图通,你可以找到任何地址。"},{"role": "user", "content": prompt}
]# 初次调用大模型
response = get_completion(messages)
messages.append(response)
print("===== GPT 首轮回复 =====")
print_json(response)# 多轮函数调用
while getattr(response, "tool_calls", None):for tool_call in response.tool_calls:args = json.loads(tool_call.function.arguments)print("=== 调用函数参数 ===")print_json(args)# 执行对应函数if tool_call.function.name == "get_location_coordinate":result = get_location_coordinate(**args)elif tool_call.function.name == "search_nearby_pois":result = search_nearby_pois(**args, debug=True)else:result = "未知函数"print("=== 函数返回结果 ===")print_json(result)# 提交工具调用结果messages.append({"tool_call_id": tool_call.id,"role": "tool","name": tool_call.function.name,"content": json.dumps(result, ensure_ascii=False)})# 再次获取大模型回复response = get_completion(messages)messages.append(response)print("===== 最终大模型回复 =====")
print(response.content or "⚠️ 大模型未生成有效回复。")
GPT 的回复:
=====GPT回复=====
{"content": null,"refusal": null,"role": "assistant","function_call": null,"tool_calls": [{"id": "call_BEjN2hy7nriCqmWFGGvoyNmt","function": {"arguments": "{\"location\":\"五道口\",\"city\":\"北京\"}","name": "get_location_coordinate"},"type": "function"}]
}
函数参数展开:
{"location": "五道口","city": "北京"
}
Call: get_location_coordinate
=====函数返回=====
{"parent": "","address": "海淀区","distance": "","pcode": "110000","adcode": "110108","pname": "北京市","cityname": "北京市","type": "地名地址信息;热点地名;热点地名","typecode": "190700","adname": "海淀区","citycode": "010","name": "五道口","location": "116.338611,39.992552","id": "B000A8WSBH"
}
函数参数展开:
{"longitude": "116.338611","latitude": "39.992552","keyword": "咖啡"
}
Call: search_nearby_pois
=====函数返回=====
PAGEONE CAFE(五道口购物中心店)
成府路28号五道口购物中心(五道口地铁站B南口步行190米)
距离:9米星巴克(北京五道口购物中心店)
成府路28号1层101-10B及2层201-09号
距离:39米luckin coffee 瑞幸咖啡(五道口购物中心店)
成府路28号五道口购物中心负一层101号
距离:67米=====最终回复=====
在五道口附近有以下几家咖啡店推荐:1. **PAGEONE CAFE(五道口购物中心店)**- 地址:成府路28号五道口购物中心(五道口地铁站B南口步行190米)- 距离:9米2. **星巴克(北京五道口购物中心店)**- 地址:成府路28号1层101-10B及2层201-09号- 距离:39米3. **luckin coffee 瑞幸咖啡(五道口购物中心店)**- 地址:成府路28号五道口购物中心负一层101号- 距离:67米希望你能找到一个满意的地方享受咖啡时光!
=====对话历史=====
{"role": "system","content": "你是一个地图通,你可以找到任何地址。"
}
{"role": "user","content": "我想在五道口附近喝咖啡,给我推荐几个"
}
{"content": null,"refusal": null,"role": "assistant","function_call": null,"tool_calls": [{"id": "call_BEjN2hy7nriCqmWFGGvoyNmt","function": {"arguments": "{\"location\":\"五道口\",\"city\":\"北京\"}","name": "get_location_coordinate"},"type": "function"}]
}
{"tool_call_id": "call_BEjN2hy7nriCqmWFGGvoyNmt","role": "tool","name": "get_location_coordinate","content": "{'parent': '', 'address': '海淀区', 'distance': '', 'pcode': '110000', 'adcode': '110108', 'pname': '北京市', 'cityname': '北京市', 'type': '地名地址信息;热点地名;热点地名', 'typecode': '190700', 'adname': '海淀区', 'citycode': '010', 'name': '五道口', 'location': '116.338611,39.992552', 'id': 'B000A8WSBH'}"
}
{"content": null,"refusal": null,"role": "assistant","function_call": null,"tool_calls": [{"id": "call_PuOC0rCTct8cSbHoT3rAHTee","function": {"arguments": "{\"longitude\":\"116.338611\",\"latitude\":\"39.992552\",\"keyword\":\"咖啡\"}","name": "search_nearby_pois"},"type": "function"}]
}
{"tool_call_id": "call_PuOC0rCTct8cSbHoT3rAHTee","role": "tool","name": "search_nearby_pois","content": "PAGEONE CAFE(五道口购物中心店)\n成府路28号五道口购物中心(五道口地铁站B南口步行190米)\n距离:9米\n\n星巴克(北京五道口购物中心店)\n成府路28号1层101-10B及2层201-09号\n距离:39米\n\nluckin coffee 瑞幸咖啡(五道口购物中心店)\n成府路28号五道口购物中心负一层101号\n距离:67米\n\n"
}
{"content": "在五道口附近有以下几家咖啡店推荐:\n\n1. **PAGEONE CAFE(五道口购物中心店)**\n - 地址:成府路28号五道口购物中心(五道口地铁站B南口步行190米)\n - 距离:9米\n\n2. **星巴克(北京五道口购物中心店)**\n - 地址:成府路28号1层101-10B及2层201-09号\n - 距离:39米\n\n3. **luckin coffee 瑞幸咖啡(五道口购物中心店)**\n - 地址:成府路28号五道口购物中心负一层101号\n - 距离:67米\n\n希望你能找到一个满意的地方享受咖啡时光!","refusal": null,"role": "assistant","function_call": null,"tool_calls": null
}