大模型基础知识--多轮对话与函数调用
一、大模型多轮对话实现
大模型本身是没有记忆,每次API调用都是独立的,因此体现出来的记忆功能,实际是请求时消息的上下文;即实现多轮对话的关键在于客户端维护上下文,并在每次请求时将完整的历史对话传回模型。
核心原理
-
维护对话历史:用数组(messages)保存用户提问和模型回答。
-
累积传递:每次调用 API 时,将完整的 messages 发送给模型。
-
动态更新:收到新回答后追加到 messages,用于下一轮对话。
from agently import Agently
Agently.set_settings(
"OpenAICompatible",
{
"model":"qwen2.5:7b",
"base_url":"http://127.0.0.1:11434/v1/",
"api_key":"nothing",
},
)
llm = Agently.create_agent()
print(
llm.set_chat_history(
[
{"role": "user", "content": "帮我记一个笔记,知乎知学堂每周一、周三有课"},
{"role": "assistant", "content": "好的"},
]
)
.input("知乎知学堂每周周几有课?")
.start()
)
结果:

Python 示例(OpenAI 兼容接口):
from openai import OpenAI
import os
client = OpenAI(api_key=os.getenv("API_KEY"))
messages = []
while True:
user_input = input("你: ")
if user_input.lower() == "exit":
break
messages.append({"role": "user", "content": user_input})
resp = client.chat.completions.create(model="gpt-4o", messages=messages)
ai_msg = resp.choices[0].message.content
print(f"AI: {ai_msg}")
messages.append({"role": "assistant", "content": ai_msg})
上下文长度控制
多轮对话或长输入、长输出场景下,需要同时考虑模型的上下文窗口限制和输入、输出长度限制,达到长度限制后模型会截断或报错。
实现超长多轮对话现有的解决方案:
| 解放方案 | 优势 | 劣势 | |
| 保存历史记录 | 简单直接,完整存储了历史对话记录,对历史对话的理解肯定是对全面的 | token消耗大,内容冗余,超过限制会损失信息 | |
| 摘要生成:对历史对话记录总结记录 | 相比直接记录历史对话减少了冗余内容,只抓关键点,大大增强了多轮对话能力 | 摘要效果取决于模型,模型不好可能会大量丢失关键信息,且需要额外的token去总结摘要 | |
| 外部存储 | 增加记忆数据库,可以存储更多多轮对话的内容,在时间和容量上跨度很大 | 需要构建外部记忆系统,并需要具备对应的检索能力 | |
| 基于主题的上下文聚焦 | 这种方法帮助模型在复杂多主题对话中保持清晰的上下文结构,并避免无关信息占用上下文窗口。 |
多轮对话技术优化主要看重时间上的跨度和有用信息的集中程度 效果取决于检索能力,关键点把握不住,会存在语义偏差 |
一般是通过历史对话管理 + 上下文传递,即可让无记忆的大模型实现连贯的多轮对话,并可结合摘要、外部存储等技术突破超长对话限制。
二. 工具调用(Function Calling)
1.定义:
为大模型提供了与外部工具或代码相连的功能,通过该功能增强大模型在实时性、数据计算等应用场景的推理效果或进行其他外部操作,包括信息检索、数据库操作、图谱搜索与处理等工具调用场景。
- Function Calling成立的模型能力基础:
- 问题理解和行动规划
- 结构化数据输出
- In-Context Learning
Function Calling让模型输出不再局限于自身推理输出,而是可以与外部系统交互,完成更复杂的任务
- 常见Function Calling应用场景包括:
- 查询检索,补充额外信息(如RAG、搜索)
- 理解用户输入,向外部系统写入信息(如表单填写)
- 调用外部系统能力,完成实际行为动作(如下订单)
2 工作原理
OpenAI官方说明文档:https://platform.openai.com/docs/guides/function-calling

2.1.解释说明:
Calling是结果,理解和选择才是第一步
- 除了代表用户诉求的Prompt之外,Function Calling还需要将可用的工具信息(Function Definitions)也提供给模型
- 在第一次请求时,模型的核心工作如下:
- 理解Prompt所代表的“诉求”和Definitions所代表的“行动可能性”
- “选择”完成“诉求”所需要进行的“行动”(从“行动可能性”中获得)
- 根据所选择的“行动”,给出执行“行动”所需的“行动参数”(Parameters)
- 那么想一想:
- 什么影响“选择”的效果?诉求的清晰表达,准确的描述工具情况,模型的选择
- 什么影响“行动”的可执行性和效果?
作为可选项的结果回调和最终回复输出
- 在对话流中,将Function Calling的结果(Function Result)与初始的Prompt诉求再次组合,提供给模型以获得最终的回复输出,是常见的流程(RAG就是一个典型的例子)
- 但如果我们将Function Calling用于非对话流场景,最终回复输出就不一定是必选项了,例如:
- 【只需要完成Calling动作】我们只是希望通过Function Calling完成行动选择和发起,接下来就进入业务处理流程,例如:理解用户表达并代替用户下单
- 【只需要完成行动参数Parameters生成】我们只是希望将Function Calling做好工具使用决策,并完成部分请求参数的生成,接下来需要走业务流程补全其他参数(比如鉴权信息),例如:敏感数据查询
在实际生产中,不给出最终回复输出,而只是使用Function Calling返回的调用方法数据,是很常见的用法。
2.2 实践
第一步:调用决策
import requests
from typing import Dict, Optional
def get_weather_by_location(lat: str, lon: str) -> Dict:
"""
根据经纬度获取天气信息(使用免费API)
Args:
lat: 纬度
lon: 经度
Returns:
包含天气信息的字典,如果获取失败则返回None
"""
try:
# 使用免费的天气API(示例,实际使用时可能需要更换)
base_url = f"https://api.open-meteo.com/v1/forecast"
# 构建请求参数
params = {
"latitude": float(lat),
"longitude": float(lon),
"current_weather": "true",
"timezone": "auto"
}
# 发送请求
response = requests.get(base_url, params=params, timeout=10)
response.raise_for_status()
# 解析响应数据
data = response.json()
if "current_weather" in data:
current = data["current_weather"]
weather_info = {
"temperature": current["temperature"],
"windspeed": current["windspeed"],
"winddirection": current["winddirection"],
"weathercode": current["weathercode"],
"time": current["time"],
"latitude": data["latitude"],
"longitude": data["longitude"],
"timezone": data["timezone"]
}
return weather_info
else:
print("API响应中未找到天气数据")
return None
except requests.exceptions.RequestException as e:
print(f"请求天气API时出错: {e}")
return None
except ValueError as e:
print(f"经纬度格式错误: {e}")
return None
except Exception as e:
print(f"获取天气信息时发生未知错误: {e}")
return None
function_mappings = {
"get_weather": get_weather_by_location,
}
#给出工具定义
tools = [
{
"type": "function",
"function": {
"name": "get_weather", # 工具名(类似ID)
"description": "获取当前指定经纬度位置的天气", # 工具描述
"parameters": { # 描述参数细节
"type": "object",
"properties": {
"lat": {"type": "string"}, # lat参数描述
"lon": {"type": "string"}, # lon参数描述
},
"required": ["lat", "lon"],
"additionalProperties": False,
},
},
},
]
import os
import dotenv
dotenv.load_dotenv(dotenv.find_dotenv())
from openai import OpenAI
client = OpenAI(
# DeepSeek
api_key=os.environ.get("DEEPSEEK_API_KEY"),
base_url=os.environ.get("DEEPSEEK_BASE_URL"),
)
# 给出诉求表达
messages = [{"role": "user", "content": "What's the weather like in Beijing today?"}]
# 发起请求
completion = client.chat.completions.create(
model="deepseek-chat",
messages=messages, # type: ignore
# 把工具定义提交给模型,就已经默认启用了Function Calling
tools=tools, # type: ignore
)
print(completion.choices[0].message.model_dump_json(indent=2))
if completion.choices[0].message.tool_calls:
print(completion.choices[0].message.tool_calls[0])
else:
print("No function is called.")
运行结果:注:不同的模型输出结果不同
下面是用deepseek模型输出结果

这个是使用本地模型ollama的qwen模型的输出结果

第二步:实际的工具调用
function_calling_message = completion.choices[0].message
function_calling = completion.choices[0].message.tool_calls[0]
print("Call Function Name:", function_calling.function.name)
print("Call Function Arguments:", function_calling.function.arguments)
#调用函数
function_result = function_mappings[function_calling.function.name](**json.loads(function_calling.function.arguments))
print(fucnction_result)
运行结果:

第三步:将工具调用结果告知大模型
# 必须:让模型知道之前给了一个什么指令(包含tool_call_id)
messages.append(function_calling_message)
# 包含了tool_call_id的结果加入消息列
messages.append(
{
"role": "tool", # 角色固定写"tool"
"tool_call_id": function_calling.id, # 要写模型上次返回的ID,帮助模型去定位这是之前发出的哪个指定的结果
"content": str(function_result), # 工具调用结果
}
)
print(messages)
final_result = client.chat.completions.create(
model="deepseek-chat",
messages=messages,
tools=tools,
)
print(final_result.choices[0].message.content)
运行结果:


浙公网安备 33010602011771号