大模型应用多轮对话prompt工程
大模型应用:多轮对话(prompt工程)
概述
在与大型语言模型(如ChatGPT)交互的过程中,我们常常体验到与智能助手进行连贯多轮对话的便利性。那么,当我们开启一个新的聊天时,系统是如何管理聊天上下文的呢?
一、初始上下文的建立
1 创建新会话
当用户开启一个新的聊天时,应用程序后端会为该对话创建一个独立的会话(session),并分配一个唯一的会话ID。这确保了每个对话都是独立的,防止不同对话之间的混淆。
2 系统提示的引入
在新会话的开始,系统会向模型提供一段隐藏的系统提示(System Prompt) 。这段提示用于设定模型在整个对话中的角色、语气和行为准则。例如:
- 角色设定 :让模型扮演助理、教师、技术专家等特定身份。
- 语言风格 :规定回复使用正式、友好、幽默等特定语气。
- 行为准则 :避免生成不适当内容,遵守伦理规范。 系统提示对用户是不可见的,但对模型的回复有着深远影响,它确保了模型在整个对话过程中保持一致的行为。
二、上下文的积累与管理
1 对话历史的记录
随着用户与模型的交互进行,系统会将每一次的用户输入和模型回复按照时间顺序累积,形成当前会话的消息队列 。这使得模型在生成回复时,可以参考之前的对话内容,保持连贯性和一致性。
2 上下文窗口的限制
大型语言模型在处理输入时,有一个固定的上下文窗口(Context Window) ,表示模型一次能处理的最大文本长度。例如,GPT-3的上下文窗口为4096个Token。 当对话长度超过上下文窗口时,系统需要对输入进行截断。为了确保模型继续遵循最初的系统提示,应用程序会:
- 优先保留系统提示 :系统提示始终位于输入的开头,不被截断。
- 截断早期对话 :从最早的用户和模型对话开始移除,保留最近的交互内容。
3 上下文组装
在生成回复时,应用程序会将以下内容按顺序拼接,形成当前的输入上下文:
- 系统提示 :设定模型行为的隐藏指令。
- 重要信息 :用户提供的关键数据或参数(如果有)。
- 最近的对话历史 :包括最近几轮的用户输入和模型回复。 通过这种方式,模型能够在一次交互中获得必要的上下文信息,生成符合预期的回复。
三、系统提示的重要作用
1 保证模型行为一致性
由于模型在每次生成回复时,只能参考当前的输入文本,因此系统提示需要在每次输入中提供,确保模型始终按照设定的角色和风格进行回复。
2 防止不当内容生成
系统提示中包含的行为准则和禁止事项,有助于模型避免生成不合规或不适当的内容,提升对话的安全性和可靠性。
3 提高用户体验
通过精心设计的系统提示,模型能够更好地理解用户意图,提供高质量、个性化的回复,提升用户的交互体验。
四、技术实现细节
1 会话管理
- 创建会话ID :为每个新对话分配唯一的会话ID,用于区分不同用户的会话。
- 状态跟踪 :记录每个会话的状态信息,便于后续的上下文管理。
2 消息队列维护
- 记录交互内容 :保存当前会话中的所有用户输入和模型回复。
- 长度检查 :在发送给模型之前,检查输入的总长度,确保不超过上下文窗口限制。
3 上下文优化
- 截断策略 :当超过上下文窗口限制时,从早期对话开始移除内容。
- 摘要处理 :对于重要但较早的内容,通过生成摘要的方式保留关键信息。
五、模型与应用的职责划分
需要明确的是,大型语言模型本身并不具备会话管理、消息队列维护或上下文组装的能力 。这些功能由应用程序在模型之上实现。具体来说:
- 模型的职责 :根据输入生成下一段文本。
- 应用的职责 :管理对话上下文、用户会话、内容过滤等。 通过合理的职责划分,应用程序能够充分发挥模型的能力,提供丰富多样的应用场景。
六、用户数据的安全与隐私
- 独立的会话 :每个新对话都是独立的,模型不会记住之前会话中的信息,保护用户隐私。
- 数据限制 :用户的输入和模型的回复都严格限定在当前会话内,不会跨会话传播。
七、总结
大型语言模型在新聊天中管理上下文,主要通过以下方式实现:
- 创建新会话,重置上下文 :确保每个对话的独立性。
- 使用系统提示 :设定模型的角色、风格和行为准则,确保模型行为一致。
- 维护消息队列 :记录对话历史,供模型参考,提高回复的连贯性。
- 上下文管理 :在上下文窗口限制内,优化输入内容,保证模型有效处理。
示例
使用多轮会话示例代码
下面的代码,演示如何在代码中实现与大型语言模型的多轮对话。我们将引入一个循环,允许用户多次输入,并维护会话的上下文,使模型的回复能够参考之前的对话内容。
代码
import torch import logging from transformers import Qwen2_5_VLForConditionalGeneration, AutoProcessor from qwen_vl_utils import process_vision_info
设置日志配置,包含Transformers库的日志
logging.basicConfig( format=’%(asctime)s - %(name)s - %(levelname)s - %(message)s’, level=logging.INFO # 设置全局日志级别为INFO,避免过多日志输出 )
获取Transformers库的logger并设置级别为INFO
transformers_logger = logging.getLogger(’transformers’) transformers_logger.setLevel(logging.INFO)
设置模型缓存目录
cache_dir = ‘/data/model/’
加载模型,启用GPU加速
model = Qwen2_5_VLForConditionalGeneration.from_pretrained( “Qwen/Qwen2.5-VL-3B-Instruct”, torch_dtype=torch.bfloat16, attn_implementation=“sdpa”, device_map=“auto”, cache_dir=cache_dir ) logging.info(“模型已加载到设备:%s,使用attn_implementation=‘sdpa’”, model.device)
设置视觉令牌范围以平衡性能和成本
min_pixels = 256 * 28 * 28 max_pixels = 1280 * 28 * 28
加载处理器
processor = AutoProcessor.from_pretrained( “Qwen/Qwen2.5-VL-3B-Instruct”, min_pixels=min_pixels, max_pixels=max_pixels, cache_dir=cache_dir ) logging.info(“处理器已加载,设置了自定义的视觉令牌范围。”)
初始化消息内容列表,包含系统提示(可选)
messages = [
可以添加系统提示,设定模型的行为
{ “role”: “system”, “content”: [ {“type”: “text”, “text”: “你是一位友好的智能助手,乐于回答用户的问题并提供帮助。”}, ], } ]
多轮会话循环
while True: user_input = input(“用户:”) if user_input.lower() in [‘退出’, ’exit’, ‘quit’]: print(“结束对话。”) break
将用户输入添加到消息列表
messages.append({ “role”: “user”, “content”: [ {“type”: “text”, “text”: user_input} ] })
准备推理输入
logging.info(“开始准备推理输入…”) text = processor.apply_chat_template( messages, tokenize=False, add_generation_prompt=True ) image_inputs, video_inputs = process_vision_info(messages) inputs = processor( text=[text], images=image_inputs, videos=video_inputs, padding=True, return_tensors=“pt”, ) inputs = inputs.to(model.device) logging.info(“推理输入已准备完毕。”)
进行推理并生成输出
logging.info(“开始生成输出…”) generated_ids = model.generate(**inputs, max_new_tokens=512) logging.info(“输出生成完毕。”)
处理生成的输出
generated_ids_trimmed = [ out_ids[len(in_ids):] for in_ids, out_ids in zip(inputs.input_ids, generated_ids) ] output_text = processor.batch_decode( generated_ids_trimmed, skip_special_tokens=True, clean_up_tokenization_spaces=False )[0] # 取第一个元素 print(“助手:” + output_text.strip())
将模型的回复添加到消息列表
messages.append({ “role”: “assistant”, “content”: [ {“type”: “text”, “text”: output_text.strip()} ] })
为了防止超过上下文长度限制,可以在这里检查并截断消息列表
例如,只保留最近的n轮对话
max_history = 5 # 保留最近5轮对话(可根据需要调整)
保留系统提示加上最近的max_history*2条消息(用户和助手各一条,所以乘以2)
if len(messages) > max_history * 2 + 1: # +1是因为系统提示算一条 messages = [messages[0]] + messages[-max_history*2:] logging.info(“消息列表已截断,保留最近的 %d 轮对话。”, max_history)
代码说明
- 引入多轮会话循环 :使用
while True
循环,不断读取用户输入,实现多轮对话。 - 管理消息列表 :使用
messages
列表维护对话历史,在每一轮中将用户和助手的消息添加到列表中。 - 处理用户退出指令 :如果用户输入
退出
、exit
或quit
,程序将结束对话循环。 - 准备推理输入 :在每一轮对话中,使用
processor.apply_chat_template
方法将messages
列表转换为模型可接受的输入格式。 - 调用模型生成回复 :使用
model.generate
方法生成模型的回复,并将其解码为文本。 - 显示模型回复并添加到对话历史 :将模型的回复打印出来,并添加到
messages
列表中,以在后续对话中提供上下文。 - 管理上下文长度 :为了防止超过模型的上下文窗口限制(即最大输入长度),在每轮对话后检查
messages
列表的长度,并截断早期的对话内容,只保留最近的max_history
轮对话。
示例运行
拖到最右侧,重点看输入给大模型的messages在不断的累积 2025-02-27 04:00:07,656 - root - INFO - 处理器已加载,设置了自定义的视觉令牌范围。 用户:你好! 2025-02-27 04:00:49,596 - root - INFO - 开始准备推理输入… 2025-02-27 04:00:49,596 - root - INFO - [{‘role’: ‘system’, ‘content’: [{’type’: ’text’, ’text’: ‘你是一位友好的智能助手,乐于回答用户的问题并提供帮助。’}]}, {‘role’: ‘user’, ‘content’: [{’type’: ’text’, ’text’: ‘你好!’}]}] 2025-02-27 04:00:49,609 - root - INFO - 推理输入已准备完毕。 2025-02-27 04:00:49,609 - root - INFO - 开始生成输出… 2025-02-27 04:00:59,579 - root - INFO - 输出生成完毕。 助手:你好!有什么可以帮助你的吗? 用户:你能给我讲个笑话吗? 2025-02-27 04:01:11,942 - root - INFO - 开始准备推理输入… 2025-02-27 04:01:11,942 - root - INFO - [{‘role’: ‘system’, ‘content’: [{’type’: ’text’, ’text’: ‘你是一位友好的智能助手,乐于回答用户的问题并提供帮助。’}]}, {‘role’: ‘user’, ‘content’: [{’type’: ’text’, ’text’: ‘你好!’}]}, {‘role’: ‘assistant’, ‘content’: [{’type’: ’text’, ’text’: ‘你好!有什么可以帮助你的吗?’}]}, {‘role’: ‘user’, ‘content’: [{’type’: ’text’, ’text’: ‘你能给我讲个笑话吗?’}]}] 2025-02-27 04:01:11,943 - root - INFO - 推理输入已准备完毕。 2025-02-27 04:01:11,943 - root - INFO - 开始生成输出… 2025-02-27 04:01:32,729 - root - INFO - 输出生成完毕。 助手:当然可以!这是一个经典的笑话:为什么电脑经常生病?因为它的窗户(Windows)总是开着! 用户:哈哈,很有趣。再讲一个脑筋急转弯? 2025-02-27 04:02:08,591 - root - INFO - 开始准备推理输入… 2025-02-27 04:02:08,591 - root - INFO - [{‘role’: ‘system’, ‘content’: [{’type’: ’text’, ’text’: ‘你是一位友好的智能助手,乐于回答用户的问题并提供帮助。’}]}, {‘role’: ‘user’, ‘content’: [{’type’: ’text’, ’text’: ‘你好!’}]}, {‘role’: ‘assistant’, ‘content’: [{’type’: ’text’, ’text’: ‘你好!有什么可以帮助你的吗?’}]}, {‘role’: ‘user’, ‘content’: [{’type’: ’text’, ’text’: ‘你能给我讲个笑话吗?’}]}, {‘role’: ‘assistant’, ‘content’: [{’type’: ’text’, ’text’: ‘当然可以!这是一个经典的笑话:为什么电脑经常生病?因为它的窗户(Windows)总是开着!’}]}, {‘role’: ‘user’, ‘content’: [{’type’: ’text’, ’text’: ‘哈哈,很有趣。再讲一个脑筋急转弯?’}]}] 2025-02-27 04:02:08,592 - root - INFO - 推理输入已准备完毕。 2025-02-27 04:02:08,593 - root - INFO - 开始生成输出… 2025-02-27 04:02:34,326 - root - INFO - 输出生成完毕。 助手:好的,这个脑筋急转弯挺有趣的:什么东西越洗越脏?答案是水。 用户:谢谢你的回答 2025-02-27 04:03:03,807 - root - INFO - 开始准备推理输入… 2025-02-27 04:03:03,807 - root - INFO - [{‘role’: ‘system’, ‘content’: [{’type’: ’text’, ’text’: ‘你是一位友好的智能助手,乐于回答用户的问题并提供帮助。’}]}, {‘role’: ‘user’, ‘content’: [{’type’: ’text’, ’text’: ‘你好!’}]}, {‘role’: ‘assistant’, ‘content’: [{’type’: ’text’, ’text’: ‘你好!有什么可以帮助你的吗?’}]}, {‘role’: ‘user’, ‘content’: [{’type’: ’text’, ’text’: ‘你能给我讲个笑话吗?’}]}, {‘role’: ‘assistant’, ‘content’: [{’type’: ’text’, ’text’: ‘当然可以!这是一个经典的笑话:为什么电脑经常生病?因为它的窗户(Windows)总是开着!’}]}, {‘role’: ‘user’, ‘content’: [{’type’: ’text’, ’text’: ‘哈哈,很有趣。再讲一个脑筋急转弯?’}]}, {‘role’: ‘assistant’, ‘content’: [{’type’: ’text’, ’text’: ‘好的,这个脑筋急转弯挺有趣的:什么东西越洗越脏?答案是水。’}]}, {‘role’: ‘user’, ‘content’: [{’type’: ’text’, ’text’: ‘谢谢你的回答’}]}] 2025-02-27 04:03:03,809 - root - INFO - 推理输入已准备完毕。 2025-02-27 04:03:03,809 - root - INFO - 开始生成输出… 2025-02-27 04:03:27,048 - root - INFO - 输出生成完毕。 助手:不客气,随时欢迎你来提问! 用户:退出 结束对话。 另外多模态大模型可以支持复杂的会话messages,单次输入给大模型的输入可以如下: conversation = [ { “role”: “user”, “content”: [{“type”: “image”}, {“type”: “text”, “text”: “Hello, how are you?”}], }, { “role”: “assistant”, “content”: “I’m doing well, thank you for asking. How can I assist you today?”, }, { “role”: “user”, “content”: [ {“type”: “text”, “text”: “Can you describe these images and video?”}, {“type”: “image”}, {“type”: “image”}, {“type”: “video”}, {“type”: “text”, “text”: “These are from my vacation.”}, ], }, { “role”: “assistant”, “content”: “I’d be happy to describe the images and video for you. Could you please provide more context about your vacation?”, }, { “role”: “user”, “content”: “It was a trip to the mountains. Can you see the details in the images and video?”, }, ]
default:
prompt_without_id = processor.apply_chat_template( conversation, add_generation_prompt=True )
Excepted output: ‘<|im_start|>system\nYou are a helpful assistant.<|im_end|>\n<|im_start|>user\n<|vision_start|><|image_pad|><|vision_end|>Hello, how are you?<|im_end|>\n<|im_start|>assistant\nI’m doing well, thank you for asking. How can I assist you today?<|im_end|>\n<|im_start|>user\nCan you describe these images and video?<|vision_start|><|image_pad|><|vision_end|><|vision_start|><|image_pad|><|vision_end|><|vision_start|><|video_pad|><|vision_end|>These are from my vacation.<|im_end|>\n<|im_start|>assistant\nI’d be happy to describe the images and video for you. Could you please provide more context about your vacation?<|im_end|>\n<|im_start|>user\nIt was a trip to the mountains. Can you see the details in the images and video?<|im_end|>\n<|im_start|>assistant\n’
add ids
prompt_with_id = processor.apply_chat_template( conversation, add_generation_prompt=True, add_vision_id=True )
Excepted output: ‘<|im_start|>system\nYou are a helpful assistant.<|im_end|>\n<|im_start|>user\nPicture 1: <|vision_start|><|image_pad|><|vision_end|>Hello, how are you?<|im_end|>\n<|im_start|>assistant\nI’m doing well, thank you for asking. How can I assist you today?<|im_end|>\n<|im_start|>user\nCan you describe these images and video?Picture 2: <|vision_start|><|image_pad|><|vision_end|>Picture 3: <|vision_start|><|image_pad|><|vision_end|>Video 1: <|vision_start|><|video_pad|><|vision_end|>These are from my vacation.<|im_end|>\n<|im_start|>assistant\nI’d be happy to describe the images and video for you. Could you please provide more context about your vacation?<|im_end|>\n<|im_start|>user\nIt was a trip to the mountains. Can you see the details in the images and video?<|im_end|>\n<|im_start|>assistant\n’
注意事项
- 上下文长度限制 :大型语言模型对输入文本的长度是有最大限制的(例如4096个Token)。在实际应用中,需要根据模型的实际限制,调整
max_history
的值,或者采用更加复杂的截断和摘要策略。 - 视觉信息处理 :示例代码中包含了对图像和视频输入的处理。如果当前对话不涉及图像或视频,可以简化相关处理,或者在需要时动态地添加图像或视频信息到
messages
中。 - 系统提示的作用 :在
messages
列表中添加"role": "system"
的消息,可以设定模型的整体行为和风格。系统提示通常只需在对话开始时添加一次,后续对话中无需重复。 - 日志级别设置 :为了避免过多的日志输出,将全局日志级别从
DEBUG
调整为INFO
。根据需要,可以进一步调整日志级别。 - 模型性能与资源 :运行此代码需要具备支持相应模型大小的计算资源(例如GPU内存)。在实际应用中,根据硬件条件选择合适的模型规模。