调用大模型API上下文关联

一、引言

在使用大模型API(如OpenAI GPT系列、DeepSeek、Anthropic Claude等)时,我们经常需要实现多轮对话------让模型记住前几句聊天内容,而不是每次都"失忆"。很多新手会疑惑:明明只是调了一个接口,模型怎么知道我上一句问了啥?

答案是:大语言模型本身是无状态的,它并不会自动保存任何历史记录。要实现上下文关联,我们必须自己在调用API时,把完整的对话历史"喂"给它。

二、核心原理:把对话历史拼成数组发给模型

目前主流的大模型Chat接口(Chat Completions API)都接收一个叫 messages 的数组参数。这个数组里存放的就是从对话开始到现在的所有消息。

比如你第一次问"我叫小明",第二次问"我叫什么名字",如果不传历史,模型根本不知道你是谁。正确的做法是:

  • 第一次请求时,messages 里只有:

    • {"role": "user", "content": "我叫小明"}
  • 模型回答后,我们把模型的回答 和这个新问题 都追加到 messages 中。

  • 第二次请求时,messages 变成了:

    • {"role": "user", "content": "我叫小明"}

    • {"role": "assistant", "content": "好的小明,记住啦!"}

    • {"role": "user", "content": "我叫什么名字?"}

模型看到完整的列表,自然就能"回忆"起之前说过的话,从而给出正确回答:"你叫小明"。

一句话总结:上下文就是你自己维护的一个消息列表,每次请求都全量发给模型。

三、三个重要角色

在 messages 数组中,每条消息都有 role 属性,主要包含以下三种角色:

role 含义 示例
system 系统提示词,用于设定AI的人设、规则,通常放在数组最前面。 "你是一个专业的菜谱顾问,回答要简洁。"
user 用户说的每一句话。 "西红柿炒鸡蛋怎么做?"
assistant 模型之前的回答,需要手动追加回数组。 "先准备西红柿、鸡蛋、葱......"

保持这些角色的正确顺序,就能模拟真实、连续的对话。

四、动手实现:Python简易多轮对话

下面是一个完整的、可运行的控制台多轮对话程序(使用 OpenAI 风格接口,DeepSeek 等也可通用)。

复制代码
import openai

# 初始化客户端(请替换为你的API Key和Base URL)
client = openai.OpenAI(
    api_key="your-api-key",
    base_url="https://api.deepseek.com"  # 如果使用DeepSeek,这样配置
)

# 初始化上下文,system prompt 作为基础设定
messages = [
    {"role": "system", "content": "你是一个幽默风趣的菜谱顾问。"}
]

print("开始聊天!输入 quit 退出。\n")

while True:
    user_input = input("你: ")
    if user_input.lower() == "quit":
        break

    # 1. 把用户新问题加入上下文
    messages.append({"role": "user", "content": user_input})

    # 2. 带着完整的 messages 数组请求模型
    response = client.chat.completions.create(
        model="deepseek-chat",  # 改成你用的模型名
        messages=messages       # 关键:每次都传入全部历史
    )

    # 3. 获取模型回复
    assistant_reply = response.choices[0].message.content
    print(f"AI: {assistant_reply}\n")

    # 4. 把模型的回答也加入上下文,下一秒它会记住
    messages.append({"role": "assistant", "content": assistant_reply})

运行效果:

复制代码
你: 我叫小明,今天想吃鱼。
AI: 小明你好!吃鱼好啊,聪明人爱吃鱼。你想怎么吃?清蒸、红烧还是水煮?

你: 你刚才叫我什么?
AI: 我叫你小明呀,你自己说的,我可记着呢!

五、实际工程中的4个关键优化

上述方案简单直接,但对话一长,messages 会越来越庞大。每次请求都把几千字的聊天记录发过去,成本高、速度慢,还可能超过模型的最大上下文限制。所以真正用在产品里时,我们一定会做下面的优化。

1. 限制轮次(保留最近 N 轮)

只保留最近的几十条消息,旧消息直接丢弃。

复制代码
MAX_HISTORY = 20  # 保留最近20条消息(10轮对话)
if len(messages) > MAX_HISTORY:
    # 保留 system prompt,裁剪掉最早的用户和助手消息
    messages = [messages[0]] + messages[-(MAX_HISTORY-1):]

2. 滑动窗口 + Token 计数

更精确的做法是按 token 数量裁剪。确保总 token 数不超过模型上限(如 4K、8K、128K)和你设定的阈值。

复制代码
import tiktoken  # OpenAI 的 token 计算库

def count_tokens(messages):
    encoding = tiktoken.encoding_for_model("gpt-4")
    text = "".join([m["content"] for m in messages])
    return len(encoding.encode(text))

MAX_TOKENS = 3000
while count_tokens(messages) > MAX_TOKENS:
    # 保留 system prompt,从第二早的消息开始删除
    messages.pop(1)  # 删除第一条非system消息

3. 对话摘要压缩

当历史太长时,用模型把前面的对话总结成一段摘要,然后只保留摘要 + 最近的原始消息。

复制代码
# 伪逻辑:
if len(messages) > 20:
    # 将前15轮对话拿出来,让模型生成摘要
    old_messages = messages[:15]
    summary = summarize(old_messages)   # 调用模型总结
    # 新上下文 = system + 摘要 + 最近5轮原始对话
    messages = [system_msg, {"role": "system", "content": f"对话历史摘要:{summary}"}] + messages[-10:]

4. 关键信息外部持久化

不要依赖上下文记住用户的永久信息(如姓名、会员等级、地址)。第一次获取后就存入数据库,每次请求时动态注入到 system prompt 中。

复制代码
user_name = get_name_from_db(user_id)  # 从数据库取
messages = [
    {"role": "system", "content": f"当前用户叫{user_name},是金牌会员。"},
    {"role": "user", "content": "我的订单到哪了?"}
]

这样即使对话历史被清空,关键信息也不会丢失。

六、注意事项

  1. 模型的最大上下文长度:每个模型都有最大窗口(如 4k、128k),在裁剪历史时一定要留出模型输出的空间。

  2. system prompt 要保护:裁剪时永远保留第一条系统设定,否则人设会崩。

  3. 成本意识:每次全量发送历史会让 token 消耗翻倍增长,上生产一定要做裁剪或摘要。

  4. 同一请求可传多轮:有些时候为了省事,一次请求就塞入完整的几轮历史,这也是允许的,关键是你要自己维护。

七、总结

  • 直接API调用实现上下文关联,本质就是你在客户端维护一个 messages 数组,每次请求时全量(或截断后)发送。

  • 模型本身不存储任何会话信息,所有的"记忆"都靠你手动传递。

  • 实际应用时必须配合裁剪、摘要、持久化等策略,才能在保证对话连贯性的同时控制成本与性能。

相关推荐
Li_RuiQi16 小时前
DAgger与交互式模仿学习_ACT学习笔记
笔记·学习
He BianGu16 小时前
【笔记】C# 中BindingFlags的详细作用
笔记·c#
AI视觉网奇16 小时前
turtle 入门教程笔记
笔记
跨知易知识产权17 小时前
美国专利确权是什么,和专利诉讼有什么不一样?
笔记
杨逢昌工厂6S管理17 小时前
126-杨逢昌6S三环体系:越做越乱的现场熵增机理与基准化治理——一线现场诊断笔记
经验分享·笔记·职场和发展·学习方法
悟道书生17 小时前
若依 RBAC 权限控制学习笔记
java·网络·笔记·sql·学习·vue
陈哈哈111118 小时前
折腾了半天,总算把 Vim 9.2 装明白了
笔记
陈哈哈111118 小时前
折腾了半天,总算把 OpenGL 的开发环境给装明白了
笔记
深圳老胡19 小时前
STM32F103 硬件 SPI 与软件 IO 模拟 SPI 最高速度差异详解
笔记·stm32·单片机·代码规范·1024程序员节
正弦波_Wave19 小时前
【Orcad原理图打印Pdf】
笔记·pdf