综合练习:智能问答机器人
这是什么:多轮对话 = 靠程序记住历史消息,撑起"模型没记忆、聊天有记忆"的假象。
为什么用:前四章的三件套备齐了:接龙原理(懂它)、提示词(管它)、API 调用(用它)。现在合体,做一个能记住你说过什么的问答机器人。
如何用:本章用一个 history 列表存下每轮问答,每问一句就把整个列表重新发出去。
多轮上下文的原理
先戳破一个幻想:模型本身没有任何记忆。你觉得它"记得"前面聊过什么,是因为每次提问,程序都把历史消息整个重新发一遍——它接龙时能看到全部前文,自然就"接得上"。
所以多轮对话的实现套路就一句话:一问一答都记进 history 列表,每次把整个列表发出去。
完整代码
⚠️ 需要自备 API key(并
pip install --upgrade openai)才能实际运行;以下运行效果均为示意。
import os
from openai import OpenAI
client = OpenAI(
api_key=os.environ["LLM_API_KEY"],
base_url=os.environ.get("LLM_BASE_URL", "https://api.openai.com/v1"),
)
system = {"role": "system", "content": "你是青蛙助手,回答简短友好。"}
history = [system] # "记忆"本体:一个消息列表
print("智能问答机器人(输入 退出 结束)")
while True:
user = input("你:").strip()
if user == "退出":
break
history.append({"role": "user", "content": user})
resp = client.chat.completions.create(
model="gpt-4o-mini", # 换成你所选服务支持的模型名
messages=history,
)
reply = resp.choices[0].message.content
history.append({"role": "assistant", "content": reply})
print("机器人:" + reply)
你会看到(示意):
智能问答机器人(输入 退出 结束)
你:你好,我叫小蛙
机器人:你好小蛙!很高兴认识你,想聊点什么?
你:我叫什么名字?
机器人:你叫小蛙呀。
你:退出
第二次问"我叫什么名字"能答对——不是模型记性好,是 history 里躺着呢。
| 代码 | 作用 |
|---|---|
history 列表 |
全部对话的账本,就是"记忆" |
system 消息 |
人设,永远放队首 |
每次 append |
用户的问、模型的答都要记账 |
每轮发整个 history |
模型看到全部前文,才接得上 |
小提醒:history 越长,每次发送的字越多、开销越大。实际产品一般只保留 system 加最近几轮。
⚠️ 新手翻车现场
翻车一:忘记把 system 放进 history
# 错误:system 单独存在,没加进 history
history = [] # 没有 system
history.append({"role": "user", "content": user})
resp = client.chat.completions.create(model=..., messages=history)
问题:模型没有人设,语气和风格可能不符合预期。
修正:history = [system],system 永远放在队首,每轮都发。
翻车二:只把用户的问题放进 history,模型的回答不记
# 错误:只 append user,不 append assistant
history.append({"role": "user", "content": user})
# ... 调用模型 ...
# 漏掉了 history.append({"role": "assistant", "content": reply})
问题:下一轮时,模型只看到用户的问题,看不到自己之前的回答,对话就断了,"记不住"了。
修正:用户问和模型答,都要 append 进 history。
📦 换皮小示例:带人设的代码助手
把同一套代码,换个人设就变成代码助手:
system = {"role": "system", "content": "你是一位 Python 高级工程师,擅长写简洁、带类型注解的代码,并解释原理。"}
history = [system]
# 然后同样循环:用户问问题,模型回答代码,双方都 append 进 history
用户:"帮我写个读取 CSV 的函数"
机器人:给出带类型注解的 read_csv(path: str) -> list[dict] 函数并解释。
🎯 什么时候用多轮对话
- 用户需要连续问几个相关问题(如先问"什么是 LLM",再问"怎么调用 API")
- 需要模型记住前面的上下文(如用户之前说过自己的名字、偏好)
- 做角色扮演或连续修改代码(先写代码,再说"把上面的代码加注释")
📝 本章小结
- 模型本身没有记忆,"记住"靠程序把历史重新发一遍
history列表就是账本,system 放队首,问答都要 append- 每轮发整个
history,模型才能接上前文 history越长开销越大,实际产品通常只保留最近几轮- 记忆口诀:模型没有脑,记忆 history 捞;问答都记账,发全才不忘。
动手改
- 把
system人设改成"爱抬杠的辩论手",感受一句话改变整个对话风格 - 只保留 system 和最近 6 条消息,超出就丢最旧的,防止列表越滚越长
- 结合上一章:让模型先用 JSON 判断这句话是"闲聊"还是"问知识",再分别回答
LLM 毕业 🎉 下一站 Transformer 原理,拆开大模型看内部