一、理论部分

1. 多轮对话的本质:模型无状态,历史由客户端维护

大语言模型在一次请求结束后不会“自动记住”上一轮对话内容。我们看到的“连续对话”能力,本质上来自客户端在每次请求时携带完整的历史消息列表(通常命名为 messages)。模型并不是在记忆,而是在“阅读你传入的历史”。

常见的消息结构包含三类角色:

  • system:系统指令(定义助手身份、输出风格与约束)
  • user:用户输入
  • assistant:模型输出

只要历史消息按顺序被发送,模型就能在当前轮生成时参考上下文,从而表现为“记得之前聊过什么”。

2. Context Window(上下文窗口):能力边界与工程约束

messages 会随着轮次增加而变长,而模型对单次输入的最大容量是有限的(上下文窗口,通常以 token 计)。当对话持续增长时,会出现三类典型问题:

  • 输入接近/超过上限,导致报错或被截断
  • 请求变慢、成本上升
  • 关键信息被长历史稀释,回答质量下降

本项目采用最简单的“把所有历史都带上”的方式,目的是让你先把多轮对话跑通;后续阶段再系统引入滑动窗口、摘要记忆与检索增强等策略。

3. Streaming(流式输出):提升交互体验的关键机制

非流式模式下,模型需要生成完整文本后一次性返回,用户会感受到明显的等待。开启流式输出后,服务端会以 chunk(增量片段)的方式持续返回内容,客户端可以边接收边展示,实现类似“打字机”的效果。

流式输出的工程要点:

  • 开启 stream=True
  • 循环消费 chunk 的增量内容
  • 既实时输出,也要拼接成完整回复写回历史,保证下一轮的上下文完整

二、实战部分

1. 项目目标

实现一个命令行聊天机器人,具备:

  • 多轮对话:能在一定范围内记住前文
  • 流式输出:回答内容逐步输出
  • 基础可用性:支持退出指令与异常处理

2. 运行准备

请确保本地已安装依赖,并配置好环境变量。

依赖安装(示例):

pip install -r requirements.txt

环境变量(示例):

  • DEEPSEEK_API_KEY:你的 API Key
  • DEEPSEEK_BASE_URL:兼容 OpenAI 协议的服务地址

你可以使用 .env 文件配合 python-dotenv 加载(项目代码已包含加载逻辑)。

3. 主要代码(核心实现)

import os
from openai import OpenAI
from dotenv import load_dotenv
 
load_dotenv()
 
client = OpenAI(
    api_key=os.getenv("DEEPSEEK_API_KEY"),
    base_url=os.getenv("DEEPSEEK_BASE_URL"),
)
 
def chat_loop():
    print("AI 聊天助手已启动(输入 'exit' 或 'quit' 退出)")
    print("-" * 50)
 
    messages = [
        {"role": "system", "content": "你是一个乐于助人的 AI 助手,请用简洁的中文回答用户的问题。"}
    ]
 
    while True:
        try:
            user_input = input("\n你: ").strip()
 
            if user_input.lower() in ["exit", "quit"]:
                print("\n再见。")
                break
 
            if not user_input:
                continue
 
            messages.append({"role": "user", "content": user_input})
 
            print("AI: ", end="", flush=True)
 
            stream = client.chat.completions.create(
                model="deepseek-chat",
                messages=messages,
                stream=True,
                temperature=0.7,
            )
 
            full_response = ""
            for chunk in stream:
                delta = chunk.choices[0].delta
                if delta and delta.content:
                    print(delta.content, end="", flush=True)
                    full_response += delta.content
 
            print()
            messages.append({"role": "assistant", "content": full_response})
 
        except KeyboardInterrupt:
            print("\n程序已中断。")
            break
        except Exception as e:
            print(f"\n发生错误:{e}")
 
if __name__ == "__main__":
    chat_loop()

这段代码对应三件关键事:

  • messages 作为对话历史容器,实现多轮上下文
  • stream=True + 循环 chunk,实现流式输出
  • full_response 拼接完整回答并写回 messages,形成“记忆闭环”

4. 如何运行

在项目根目录执行:

python stage_01_llm_basics/project_01_chatbot/chatbot.py

5. 运行结果示例

下面是一段典型的交互示例(为了展示多轮效果,连续问了两轮):

AI 聊天助手已启动(输入 'exit' 或 'quit' 退出)
--------------------------------------------------
 
你: 我叫小林,今天心情一般
AI: 好的,小林。我在这里,你可以和我说说发生了什么,或者我可以帮你做一个简单的情绪梳理。
 
你: 你还记得我叫什么吗?
AI: 你叫小林。
 
你: exit
 
再见。

6. 项目总结

  • 多轮对话并不神秘:关键在于客户端维护并回传历史 messages
  • 流式输出显著提升体验:通过持续消费 chunk 实现“边生成边展示”
  • 该项目是后续 Agent 能力的基础:工具调用、RAG、工作流、多智能体协作,本质上都建立在“对话状态管理 + 可控输出/输入”的工程结构之上
Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐