在上一篇使用 Python 调用 Ollama 的 /api/generate 接口做单轮问答时,每次请求只发送当前 prompt,模型没有上下文,因此先问“我叫小明”,再问“我叫什么名字”,模型无法记住。解决办法是改用 Ollama 的 /api/chat 接口,并在客户端维护一个 messages 消息历史列表。每次请求都把完整的 messages 发给模型,模型通过历史文本推断上下文,这就是多轮对话的核心,也是 Agent 短期记忆最朴素的实现。
/api/generate 与 /api/chat 的区别如下:/api/generate 只接受 prompt 单条字符串,没有角色概念,适合文本生成、补全;/api/chat 接受 messages 消息列表,原生支持结构化传递历史消息,支持 system、user、assistant 三种角色,适合多轮对话。messages 中每条消息包含 role 和 content 两个字段:system 定义模型行为和人设,对话开始时设置一次;user 是用户发送的消息;assistant 是模型回复。
下面用 ContinueChatBot 类实现 CLI 多轮聊天机器人。创建文件 continueChat.py:
- import requests
- import json
- class ContinueChatBot:
- def __init__(self, model="qwen2:7b", system_prompt="你是一个有帮助的AI助手。"):
- self.model = model
- self.url = "http://localhost:11434/api/chat"
- # 消息历史:system + 对话记录
- self.messages = [
- {"role": "system", "content": system_prompt}
- ]
- def ask(self, user_input):
- # 把用户的问题加入历史
- self.messages.append({"role": "user", "content": user_input})
- payload = {
- "model": self.model,
- "messages": self.messages,
- "stream": False
- }
- response = requests.post(self.url, json=payload)
- data = response.json()
- # 提取模型的回答
- assistant_reply = data["message"]["content"]
- # 把模型的回答也加入历史,下次对话就能记得
- self.messages.append({"role": "assistant", "content": assistant_reply})
- return assistant_reply
- def chat_loop(self):
- print("=== 多轮对话模式 ===")
- print("输入你的问题,回车发送。输入 'exit' 或 'quit' 结束。\n")
- while True:
- user_input = input("你: ").strip()
- if user_input.lower() in ("exit", "quit", "退出"):
- print("\n对话结束。")
- break
- if not user_input:
- continue
- print("模型思考中...")
- reply = self.ask(user_input)
- print(f"模型: {reply}\n")
- if __name__ == "__main__":
- bot = ContinueChatBot(
- model="qwen2:7b",
- system_prompt="你是一个耐心、幽默的AI助手,喜欢用简洁的方式回答问题。"
- )
- bot.chat_loop()
复制代码
代码逻辑可以拆成四部分。
1. 初始化:构造函数接收 model 和 system_prompt,默认模型为 qwen2:7b,默认系统提示词为“你是一个有帮助的AI助手。”。self.messages 初始只放入一条 system 消息,系统提示词始终位于历史最前面,用来设定角色和行为风格。
2. 提问:ask 方法先追加用户消息,再构造 payload 发送 POST 请求到 http://localhost:11434/api/chat。payload 包括 model、messages,并设置 stream=False。收到响应后,从 data["message"]["content"] 取出模型回答,再把这条 assistant 消息追加到历史,最后返回回答。顺序很关键:发送前追加 user,返回前追加 assistant,这样下一轮请求就同时带上用户问题和模型回复。
3. 对话循环:chat_loop 用 while True 读取 input(),空输入直接 continue;输入 exit、quit 或“退出”时结束。每轮调用 ask 并打印模型回复。
4. 启动:实例化 ContinueChatBot 时可以替换 model 和 system_prompt。例如把 model 改成 deepseek-r1:7b 等本地已拉取的模型名,就可以切换模型。
运行前要确保 Ollama 服务正在运行,浏览器访问 http://localhost:11434 能返回 Ollama is running。然后执行:
交互效果如下:
=== 多轮对话模式 ===
输入你的问题,回车发送。输入 'exit' 或 'quit' 结束。
你: 你好,我叫小明,请记住这个名字
模型思考中...
模型: 你好,小明!很高兴遇到你。你的名字会牢牢地刻在我的记忆中。有什么我能帮你解答的问题吗?
你: 我叫什么名字?
模型思考中...
模型: 你是小明。这个名字挺有亲切感的!有什么其他问题我可以帮助你解答的吗?
你: exit
对话结束。
可以看到,模型之所以能回答“你是小明”,是因为 messages 列表记录了第一轮的用户输入和模型回复。完整流程是:system 设定 → 用户提问 → 携带全部历史请求 /api/chat → 模型回复 → 追加 assistant → 下一轮继续携带全部历史。每轮请求都会发送整个 messages 列表。
目前这个实现还有两个明显局限。第一,历史会无限增长。对话轮数一多,messages 越来越长,最终受模型上下文窗口限制;原文提到 qwen2:7b 为 32K tokens,超出后 API 会直接报错。改进方向是设置最大轮数,超过后丢弃最早的对话,或者在历史过长时让模型把早期对话总结成摘要,再用摘要替换原文,也就是上下文压缩。第二,记忆只在内存中,程序退出就丢失。重新运行脚本时 messages 会重新初始化,模型又“失忆”。改进方向是把 messages 定期保存到文件或数据库,启动时加载,这就演进为 Agent 的长期记忆。
总结:从 /api/generate 切换到 /api/chat,并维护 system、user、assistant 三种角色的 messages 历史列表,就能用 Python 实现一个支持多轮对话的 CLI 聊天机器人。这个 messages 列表就是 Agent 短期记忆的最简实现;后续可继续处理上下文压缩和记忆持久化。 |