Message与对话历史

Message与对话历史

2026年9月21日·#编程学习/langchain学习笔记LangChain/AI·4983 字 25 分钟
浏览量加载中...
AI 摘要

LangChain 的四种消息类型与两种写法、content 与 content_blocks、"必须传完整历史"的规则与历史裁剪优化

为什么需要 Message#

大模型没有记忆,它的输出只和”输入的上下文”有关;很多大模型 API 服务也不在服务端维护会话历史(就是第 3 章学过的:无状态)。如果应用需要”记住”对话,就得在程序里自己维护消息列表。

在 LangChain 中,Message(消息)是模型交互的最基本单元:它既代表模型的输入,也代表模型生成的输出。每条消息不仅包含文字内容,还携带描述上下文状态的元信息(metadata)——这样模型才能理解”谁在说话""说了什么""属于哪一轮”。

LangChain 1.0 提供了跨模型统一的 Message 标准(OpenAI / Anthropic / Gemini / 本地模型行为一致),好处:

好处说明
兼容性强不同模型的消息格式自动对齐
可扩展性高方便添加多模态内容或自定义字段
可追踪性好为 LangSmith 等调试工具提供一致的上下文结构

图:左边是”挑战”(大模型没有记忆、服务端不维护历史),右边是”方案”(应用维护消息列表)

消息的内部结构#

字段说明
Role消息所属的角色或类型:systemuserassistant(还有 tool
Content消息内容
Metadata(可选)元数据:消息 ID、响应时间、token 消耗量、消息标签等

图:模型返回的 AIMessage 字段解析(content、additional_kwargs、response_metadata、usage_metadata)

四种消息类型#

类型角色作用JSON 写法
系统消息system系统提示词:设定角色、行为准则、上下文背景(像给 AI 的”工作说明书”){"role": "system", "content": "你是个精通编程的软件架构师"}
用户消息user用户的一次输入,可含多模态内容(图片、音频、文档等){"role": "user", "content": "你好啊~"}
助手消息assistant模型的回复:文本、工具调用、元数据{"role": "assistant", "content": "我也很高兴认识你"}
工具调用消息tool工具执行结果,回传给模型让它接着生成(第 5 章详解){"role": "tool", "content": "今天天气很好", "tool_call_id": "call_00_..."}

助手消息里如果模型要调工具,长这样:

{
"role": "assistant",
"content": "",
"tool_calls": [
{
"name": "get_weather",
"args": {"location": "北京"},
"id": "call_00_nUD2NC9QRN5Cg1GaoIkBJQ4s"
}
]
}
Note

ToolMessage 里的 tool_call_id 必须和 AI 消息里那次调用的 id 匹配,否则模型不知道”这个结果对应哪个请求”。

为什么要区分消息类型? ① 明确角色 ② 通过 system 精确控制 AI 行为 ③ 构建完整的多轮上下文 ④ 更容易追踪和调试。

两种格式:JSON 与对象#

消息JSON 格式对象格式
系统{"role": "system", "content": "..."}SystemMessage(content="...")
用户{"role": "user", "content": "..."}HumanMessage(content="...")
助手{"role": "assistant", "content": "..."}AIMessage("...")
工具{"role": "tool", "content": "...", "tool_call_id": "..."}ToolMessage(content="...", tool_call_id="...")
Tip

JSON 字典格式更通用(易序列化、易存文件、易走网络);对象格式能携带更丰富的类型信息(如 tool_callscontent_blocks)。日常用字典就够了,需要精细控制时用对象。

各消息类型的参数列表#

四种消息类型都构造完了,再把每一类常用字段过一遍——此处仅说明常用字段,完整字段列表查阅官方手册或阅读源码

消息类型常用字段说明
SystemMessagecontent消息内容,字段名可以省略
HumanMessagecontent / metadata(含 nameid内容同上;nameid 用来区分发言者
AIMessagecontent / response_metadata / tool_calls / usage_metadata后三个是 AIMessage 特有属性
ToolMessagecontent / name / tool_call_id必须紧邻匹配的那条 AIMessage

SystemMessage:只有 content,且字段名可以省略。

SystemMessage("你是个善解人意的助手")
# 等价于
SystemMessage(content="你是个善解人意的助手")

HumanMessage:除了 content,还有 metadata 元数据字段——可以有很多、完全自定义。最常用的是 nameid

HumanMessage(
content="Hello!",
name="alice", # 可选,用户名
id="msg_123", # 可选,message 的 ID
)

nameid 都属于元数据字段,作用是当消息类型相同时把不同消息区分开。但不是所有模型都支持这一功能,是否支持取决于模型供应商,需要查官方手册——比如 OpenAI 的 API 手册就明确写了 nameChatCompletionUserMessageParam 的字段:

图:OpenAI 官方 API 手册中的 name 参数——“为同一 role 的参与者提供区分信息”

课程用 CloseAI 平台的 gpt-5.4-mini 演示了 name 的作用——多人对话的发言者抽取

from langchain_core.messages import SystemMessage, HumanMessage, AIMessage
from langchain.chat_models import init_chat_model
from dotenv import load_dotenv
import os
load_dotenv(override=True)
model = init_chat_model(
model="gpt-5.4-mini",
model_provider="openai",
api_key=os.getenv("CLOSEAI_API_KEY"),
base_url=os.getenv("CLOSEAI_BASE_URL"),
)
messages = [
SystemMessage(
"你是一个信息抽取器。你会收到多条来自不同发言者的 user 消息。每条消息可能带有 name 字段。"
"你的任务是:严格根据每条消息的 name 提取发言者及其观点,并输出 JSON。"
"禁止使用“第一个人/第二个人”这种相对称呼。若某条消息没有 name,则输出 unknown。"
'输出格式:{"speakers":[{"name":"...","claim":"..."}]}'
),
HumanMessage(content="我认为 1+1=2", name="Bob"),
HumanMessage(content="我认为 1+1>2", name="Tom"),
HumanMessage(content="请列出谁说了什么,不要判断对错。", name="audience"),
]
response = model.invoke(messages)
print(response.content)

输出——模型读到并利用了 name 传递的信息:

{"speakers":[{"name":"Bob","claim":"我认为 1+1=2"},
{"name":"Tom","claim":"我认为 1+1>2"},
{"name":"audience","claim":"请列出谁说了什么,不要判断对错。"}]}
Warning

name 能不能生效,取决于”客户端有没有传”和”服务端认不认”两件事。

  • 课程拓展:用 ChatOpenRouter 调用时,name 没有正确传给模型服务——同样的代码,输出里全是 unknown
  • 本机实测(ChatDeepSeek + 假服务端抓请求体)name 确实被发出去了{"content": "Hello!", "name": "alice", "role": "user"}),但同一个消息里的 id 没有出现在请求体中——id 只是本地标识,用于自己追踪消息,模型看不到。

所以”模型不认识 name”时,先分清是哪一环的问题:LangChain 客户端有没有传 → 平台有没有转发 → 模型认不认。课程实测的 DeepSeek 属于第三环(官方文档声明支持,实测无法识别)。

AIMessagecontent 是模型输出的原始内容(字段名可省);另外三个是它特有的属性——response_metadata(响应附加元数据,不同模型内容不同,可能含 token 用量)、tool_calls(工具调用信息,没调用时为空)、usage_metadata(用量信息)。

tool_calls 是一个 ToolCall 列表,每个 ToolCall 是字典,四个字段:

tool_calls = [
{
"name": "get_weather", # 应调用的工具名
"args": {"city": "杭州"}, # 调用工具的参数
"id": "call_00_gIXYOD1Q1OkEXmdDBqXR1578", # 工具调用的唯一标识 ID
"type": "tool_call",
},
{
"name": "get_news",
"args": {},
"id": "call_01_jD3phD5PEaIZf0mVLhKt0861",
"type": "tool_call",
},
]

两个典型场景对照着看:

# 举例1:给出最终答案
AIMessage(content="北京今天晴天,温度 15°C")
# 举例2:调用工具(content 通常是空的)
AIMessage(
content="",
tool_calls=[{
"name": "get_weather",
"args": {"city": "北京"},
"id": "call_xxx",
}],
)
# 举例3:手工构造工具调用后,交给模型接着算
response = model.invoke([
HumanMessage(content="北京天气如何"),
AIMessage(content="", tool_calls=[{
"name": "get_weather", "args": {"city": "北京"}, "id": "call_00_..."}]),
ToolMessage(content="今天北京天气晴朗,万里无云~", tool_call_id="call_00_..."),
])
print(response.content) # 今天北京天气晴朗,万里无云。

ToolMessage:三个字段——content(工具输出内容)、name(工具名称)、tool_call_id(工具调用唯一 ID)。

ToolMessage(
content="<工具输出>",
name="get_weather",
tool_call_id="call_00_nUD2NC9QRN5Cg1GaoIkBJQ4s", # 一定要和 AI 消息中的调用 ID 匹配
)
Warning

ToolMessage 必须紧邻匹配的那条 AIMessage:顺序是 [AIMessage(tool_calls), ToolMessage],中间的 tool_call_id 要和前者 tool_calls 里的 id 一致。 实测补充:客户端不做本地校验——把 ToolMessage 放到 AIMessage 前面、或写成不匹配的 tool_call_id,LangChain 都会原样发给服务端(用假服务端抓请求体可以看到 role: "tool" 照样发出去),报错要等服务端返回。所以顺序错了不会在本地被拦住,只会得到一个”莫名其妙的远程报错”。

content 与 content_blocks#

content:弱类型#

content 支持字符串列表(列表元素通常是字典):

from langchain.messages import HumanMessage
msg1 = HumanMessage(content="你好啊")
msg2 = HumanMessage("你好啊") # 只有纯字符串时,可以省略参数名
# 多模态(图片/音频)就用字典列表,具体格式遵循模型供应商的 API 规范

图:OpenAI 官方文档里 content 的字典列表写法(text + image_url 两种 block)

图:多模态示例中发给模型的测试图片(课程里保存为 image_test.png)

多模态实战:字典列表到底怎么写#

关键规则:字典里写什么内容、用什么键,遵循的是”模型供应商的 API 规范”(OpenAI 的写法当然是 OpenAI 说了算)。以 gpt-4.1 为例,把下面这张图放到代码所在目录(比如 chapter04_message_prompt/image_test.png),然后:

import base64
from langchain.chat_models import init_chat_model
from langchain.messages import HumanMessage
from dotenv import load_dotenv
import os
load_dotenv(override=True)
model = init_chat_model(
model="gpt-5.4-mini",
model_provider="openai",
api_key=os.getenv("CLOSEAI_API_KEY"),
base_url=os.getenv("CLOSEAI_BASE_URL"),
)
def encode_image(img_path, img_type="jpeg"):
"""将一张本地图片转换成 Base64 编码的 Data URI 字符串,方便在文本中嵌入图片数据"""
with open(img_path, "rb") as img_file:
return f"data:image/{img_type};base64,{base64.b64encode(img_file.read()).decode('utf-8')}"
img_path = "image_test.png"
base64_image = encode_image(img_path) # 得到 "data:image/jpeg;base64,...."
response = model.invoke(
[
HumanMessage(
content=[
{"type": "text", "text": "这张图里有什么?"},
{
"type": "image_url",
"image_url": base64_image, # Data URI 字符串直接放进来
},
]
)
]
)
print(response.content)
Note

encode_image() 做了两件事:读文件 → Base64 编码 → 拼成 Data URIimage_url 的值就是这个 data:image/xxx;base64,... 字符串——它和”URL 链接”是同一条通路,服务端要么去下载 URL,要么直接解码 Data URI。 参数 img_type 默认写死 "jpeg",而示例图片是 PNG——所以严格来说应该传 encode_image(img_path, "png"),前缀要和真实格式对上。

content_blocks:1.x 的重大升级#

在 LangChain 1.x 中,content_blocks 是消息对象的一项重大升级:提供跨模型供应商、标准化的多模态数据结构

过去处理图片、音频甚至模型的”思维链(Reasoning)“时,各厂商格式各异,要写大量适配代码;content_blocks 终结了这种混乱。

图:DeepSeek 思考模式的输出——思考内容藏在 additional_kwargs 的 reasoning_content 字段里

  • 数据结构:list[TypedDict]
  • 统一格式:每个 block 都有 type 字段区分内容类型
  • 支持类型:textimageaudiovideotool_callreasoning
Important

1.2 里 content 依然存在(向前兼容),但新增了 content_blocks,把 content 解析为标准、类型安全的表示。带图片或工具结果的复杂对话,建议用 content_blocks 构建——一套标准代码无缝切换不同厂商的模型。

支持的字段类型详见官方文档:https://docs.langchain.com/oss/python/langchain/messages#openai

用法①:输入格式化(一套代码跨厂商)#

对复杂的对话(带图片或工具结果),建议直接用 content_blocks 列表形式构建 HumanMessage / AIMessage——从此不用再记各家的键名。同一段代码,换个模型就行:

OpenAI 模型(gpt-5.4-mini

import base64
from langchain.messages import HumanMessage
from langchain.chat_models import init_chat_model
from dotenv import load_dotenv
import os
load_dotenv(override=True)
model = init_chat_model(
model="gpt-5.4-mini",
model_provider="openai",
api_key=os.getenv("CLOSEAI_API_KEY"),
base_url=os.getenv("CLOSEAI_BASE_URL"),
)
def encode_image(img_path):
"""将一张本地图片转换成 Base64 字符串(这里不带 Data URI 前缀)"""
with open(img_path, "rb") as img_file:
return base64.b64encode(img_file.read()).decode("utf-8")
base64_image = encode_image("image_test.png")
response = model.invoke(
[
# 老写法(按供应商规范拼字典,能用但要记格式)
# HumanMessage(content=[
# {"type": "text", "text": "这张图里有什么?"},
# {"type": "image_url", "image_url": base64_image},
# ]),
# 推荐的统一写法
HumanMessage(
content_blocks=[
{"type": "text", "text": "这张图里有什么?"},
{
"type": "image",
"base64": base64_image,
"mime_type": "image/png",
},
]
)
]
)
print(response.content)

Anthropic 模型(claude-haiku-4-5——代码结构完全一样,只换了模型名

model = init_chat_model(
model="claude-haiku-4-5",
model_provider="openai",
api_key=os.getenv("CLOSEAI_API_KEY"),
base_url=os.getenv("CLOSEAI_BASE_URL"),
)
response = model.invoke(
[
HumanMessage(
content_blocks=[
{"type": "text", "text": "这张图里有什么?"},
{
"type": "image",
"base64": base64_image,
"mime_type": "image/png",
},
]
)
]
)

两者对同一张图的回答(一个说”香水、金色瓶盖、暖色背景”,另一个说”化妆品或香水、玻璃瓶、光影效果”)——格式统一,语义各自发挥

Warning

两种写法的 base64_image 不是一回事content=[{"type":"image_url", ...}] 需要的是data:image/png;base64, 前缀的 Data URI;而 content_blocks{"type":"image","base64":...} 需要的是纯 Base64 字符串(格式由 mime_type 单独声明)。混着用会直接报错。

用法②:输出格式化与「懒加载」#

content_blocks 不只是输入方便——它还能把不同厂商五花八门的输出统一成标准格式。

以 DeepSeek 官方的 deepseek-v4-flash 为例,它的思考内容藏在 additional_kwargsreasoning_content 字段下;换个模型,思考内容可能又是别的字段——只为了提取思考内容就要改代码,非常不方便

from langchain.chat_models import init_chat_model
from dotenv import load_dotenv
load_dotenv(override=True)
model = init_chat_model(
model="deepseek:deepseek-v4-flash",
extra_body={"thinking": {"type": "enabled"}}, # 打开思考模式
)
response = model.invoke("你好,一句话回答")
print("response ->", response)
print("response.content->", response.content)
print("content_blocks ->", response.content_blocks)

response.content_blocks 的输出——思考和正文被拆成两个标准块

[
{"type": "reasoning", "reasoning": "好的,用户说“一句话回答”,那说明他希望我回答得简洁直接。…"},
{"type": "text", "text": "你好,请说出您的问题,我会用一句话回答。"},
]

对比一下同一份响应里的原始字段:思考内容在 additional_kwargs={'reasoning_content': '好的,用户说…'} 里,token 明细在 usage_metadata={'input_tokens': 8, 'output_tokens': 76, 'total_tokens': 84, 'output_token_details': {'reasoning': 63}} 里(63 个 token 花在思考上)。

Tip

优先检查 response.content_blocks,而不是 response.content——特别是当你需要获取”思维链(reasoning)“或者”引用(Citations)“信息时,content 里根本没有这些内容。

Warning

content_blocks 是懒加载的:调用(访问)时才解析。 所以它不会拖慢消息的构造与传递;但反过来说,别指望构造完那一刻它就已经算好了——真要用就在拿到响应后立刻访问它,避免消息被后续流程改动后再解析出意料之外的结果。

实测(本机 langchain-core 1.2.18):手工构造一条 OpenAI 风格的 content=[{"type":"image_url","image_url":{...}}],访问 content_blocks 会被自动规整成标准块——键名从 image_url.url 变成 base64,并补上 mime_type

h = HumanMessage(content=[
{"type": "text", "text": "这是什么"},
{"type": "image_url", "image_url": {"url": "data:image/png;base64,AAAA", "detail": "high"}},
])
print(h.content_blocks)
# [{'type': 'text', 'text': '这是什么'},
# {'type': 'image', 'id': 'lc_00f4...', 'base64': 'AAAA', 'mime_type': 'image/png',
# 'extras': {'detail': 'high'}}]

也就是说:旧格式能用,且在读取时会被翻译成新标准——这正是”向前兼容 + 统一表示”的落地方式。

对话历史管理(关键规则)#

每次调用必须传递完整的对话历史!

第 1 轮:[system, user] → AI 回复 → 保存回复
第 2 轮:[system, user, assistant, user] → AI 回复 → 保存回复
第 3 轮:[system, user, assistant, user, assistant, user] → AI 回复

注意:每轮都要在原有的消息列表上追加不可重新创建新列表

三种常见错误:

错误写法后果
❌ 没传历史每轮都 model.invoke("新问题")AI 完全不记得之前说过什么
❌ 重新创建列表每轮 conversation = [{"role": "user", ...}]历史被清空
❌ 忘记保存 AI 回复只 append 用户消息AI 不知道自己的回答,前后逻辑断裂

正确做法:

conversation = []
# 第一次
conversation.append({"role": "user", "content": "我叫张三"})
response1 = model.invoke(conversation)
# 关键:保存 AI 回复
conversation.append({"role": "assistant", "content": response1.content})
# 第二次(传递完整历史)
conversation.append({"role": "user", "content": "我叫什么?"})
response2 = model.invoke(conversation) # AI 记得!

对话历史优化:只保留最近 N 轮#

问题:历史越来越长,消耗大量 token 和成本

解决方案:总是保留 system 消息;只保留最近 N 轮对话,丢弃更早的历史。思路是先分离 system 与对话,再对对话列表做切片:

def keep_recent_messages(messages, max_pairs=3):
"""保留最近的 N 轮对话(每轮 = user + assistant)"""
system_msgs = [m for m in messages if m["role"] == "system"]
dialog_msgs = [m for m in messages if m["role"] != "system"]
# 每轮 2 条消息,保留最近 max_pairs 轮
recent_msgs = dialog_msgs[-max_pairs * 2:]
return system_msgs + recent_msgs
Tip

这就是最简单的”上下文工程”:system 不能丢(丢了 AI 就换了个人),历史可以截断。第 8 章的 SummarizationMiddleware 是更聪明的做法——不是直接丢弃,而是把旧对话压缩成摘要

多轮对话聊天机器人(实战)#

把前面学的拼起来:初始化模型 → 维护消息列表 → 循环输入 → 流式输出 → 追加历史(并裁剪)。

from langchain.chat_models import init_chat_model
import os
from dotenv import load_dotenv
load_dotenv(override=True)
MODEL_NAME = "deepseek-v4-flash"
MAX_PAIRS_HISTORY = 10 # 最多保留 10 轮对话
EXIT_WORD = "quit"
model = init_chat_model(
model=MODEL_NAME,
model_provider="openai",
api_key=os.getenv("DEEPSEEK_API_KEY"),
base_url=os.getenv("DEEPSEEK_BASE_URL"),
)
messages = [
{"role": "system", "content": "你是小谷姐姐,一名耐心、友好的智能助手。"},
]
while True:
user_input = input("\n你:")
if user_input.strip().lower() == EXIT_WORD:
print("再见!")
break
messages.append({"role": "user", "content": user_input})
# 调用前裁剪历史(system 必须保留)
messages = keep_recent_messages(messages, MAX_PAIRS_HISTORY)
print("AI:", end="")
full = ""
for chunk in model.stream(messages):
print(chunk.content, end="", flush=True)
full += chunk.content
print()
messages.append({"role": "assistant", "content": full})

相关#

练习题#

一、回忆填空(写完再展开对答案)#

  1. 大模型没有 ,且很多 API 不在服务端维护会话历史( 的),所以要在程序里维护 ____
  2. Message 的三个字段:(角色)、(内容)、____(元数据)
  3. 四种消息类型:____ 设定角色规则、____ 用户输入、____ 模型回复、____ 工具结果
  4. 工具消息里的 tool_call_id 必须和 AI 消息里那次调用的 ____ 匹配
  5. content 是弱类型的,支持 ____ 和 ____;多模态内容用 ____ 形式
  6. content_blocks 的每个块都有 ____ 字段区分类型,支持 text/image/audio/video//
  7. 对话历史的关键规则:每次调用必须传 ____;每轮要在原列表上 ____,不可重新创建列表
  8. 历史太长会消耗大量 ____;优化方案是保留 ____ + 最近 ____ 轮对话
  9. 助手消息要调工具时,tool_calls 里包含 name、args 和 ____
填空答案(做完再点开)
  1. 记忆 / 无状态 / 消息列表 2. Role / Content / Metadata 3. system / user / assistant / tool 4. id 5. 字符串 / 列表(字典列表) 6. type / tool_call / reasoning 7. 完整的对话历史 / 追加 8. token(成本)/ system 消息 / N 9. id

二、裸写题#

  • 2-1 手动维护对话历史 用一个列表维护对话:先告诉模型”我叫张三”,第二轮问”我叫什么?“,确认它答得出。关键是每轮把 AI 回复也追加进去

    提示(先自己想,实在想不出再点开)

    一级 · 思路:一个列表贯穿全程,追加用户消息 → 调用 → 追加 AI 回复 二级 · 方法conversation.append({"role": "assistant", "content": response.content}) 三级 · 骨架:别犯”每轮重新创建列表”的错误

  • 2-2 故意不传历史 把上面改成”每次只传当前这一句”,观察第二轮模型还能不能答对,解释原因。

    提示

    一级 · 思路:这是复现”AI 没有记忆”的实验 二级 · 方法model.invoke("我叫什么?")(不带历史) 三级 · 骨架:结论要落到”模型是无状态的,记忆靠客户端维护”

  • 2-3 给历史做裁剪 写一个 keep_recent_messages(messages, max_pairs=2),保留 system 消息 + 最近 2 轮对话,并打印裁剪前后的消息条数。

    提示

    一级 · 思路:先分离 system,再对对话部分切片 二级 · 方法:列表推导 + dialog_msgs[-max_pairs * 2:] 三级 · 骨架return system_msgs + recent_msgs

  • 2-4 多轮聊天机器人(综合) 写一个循环:输入 → 流式输出 → 追加历史 → 输入 quit 退出。要求最多保留 10 轮历史。

    提示

    一级 · 思路:把前 3 题拼起来 + 流式 + 退出判断 二级 · 方法while True + model.stream(messages) + input() 三级 · 骨架:裁剪要放在”调用模型之前”

参考答案(做完再点开)
import os
from dotenv import load_dotenv
from langchain.chat_models import init_chat_model
load_dotenv(override=True)
model = init_chat_model(
model="deepseek-v4-flash",
model_provider="openai",
base_url=os.getenv("DEEPSEEK_BASE_URL"),
api_key=os.getenv("DEEPSEEK_API_KEY"),
)
def keep_recent_messages(messages, max_pairs=2):
"""保留 system + 最近 N 轮对话"""
system_msgs = [m for m in messages if m["role"] == "system"]
dialog_msgs = [m for m in messages if m["role"] != "system"]
return system_msgs + dialog_msgs[-max_pairs * 2:]
# 2-1 正确维护历史
conversation = [{"role": "system", "content": "你是简洁的助手,回答不超过一句话"}]
conversation.append({"role": "user", "content": "我叫张三"})
r1 = model.invoke(conversation)
conversation.append({"role": "assistant", "content": r1.content})
conversation.append({"role": "user", "content": "我叫什么?"})
r2 = model.invoke(conversation)
print("[记得历史]", r2.content)
# 2-2 不传历史(AI 会答不出来)
r3 = model.invoke("我叫什么?")
print("[没传历史]", r3.content)
# 2-3 / 2-4 带裁剪的多轮机器人
messages = [{"role": "system", "content": "你是耐心友好的智能助手"}]
while True:
user_input = input("\n你:")
if user_input.strip().lower() == "quit":
print("再见!")
break
messages.append({"role": "user", "content": user_input})
before = len(messages)
messages = keep_recent_messages(messages, 2)
print(f"(历史 {before} 条 → 裁剪后 {len(messages)} 条)")
print("AI:", end="")
full = ""
for chunk in model.stream(messages):
print(chunk.content, end="", flush=True)
full += chunk.content
print()
messages.append({"role": "assistant", "content": full})

评论区

[ 标签 ]
# AI37# AI 编程2# AI工具1# Ajax2# Apifox1# AstrBot3# Astro2# CC Switch1# CDN2# Claude Code1# claudecode2# ClaudeCode1# Cloudflare2# CloudFlare2# CloudFlare-ImgBed3# coc3# CSS6# DeepSeek6# deepseek2# DELETE1# Docker1# EdgeOne3# Gist1# git1# GitHub1# hexo-circle-of-friends1# HTML6# HTTP5# ImageManager1# Java23# java13# JavaScript5# JDBC3# JSON2# JUnit1# LangChain25# Logback1# Maven6# Muse Spark1# Mybatis1# MyBatis4# MySQL28# MySql1# NapCat1# Node.js1# obsidian2# Obsidian5# OpenCode4# ORM1# PathVariable1# PicGo1# PyCharm1# Python65# RequestBody1# RequestMapping1# RESTful风格1# skills1# Slf4j1# SpringBoot11# SQL2# Streamlit5# Svelte2# TailwindCSS1# Telegram3# Tlias2# Vercel1# vscode2# Vue7# Waline3# WebDAV1# Web基础6# Web开发6# WinSCP1# YAML1# 三层架构1# 中二宣言1# 书籍1# 使用文档10# 写作1# 函数2# 刷步数1# 前端32# 动态1# 动漫1# 包1# 单词2# 博客7# 博客工作流1# 博客开发2# 参数接收1# 友链1# 反思2# 图床6# 地图1# 备份2# 大模型1# 奇思妙想1# 存储1# 学习方法6# 学校1# 宝塔面板3# 宝宝10# 对象1# 导航栏1# 工具2# 开发1# 开发工具1# 开发规范1# 开心1# 异常处理1# 影视2# 微信1# 性能优化2# 总结1# 想法15# 感受1# 感悟11# 指南1# 提示词工程1# 插件5# 故障排除1# 效率工具2# 教程10# 数据分析9# 数据库27# 数据结构1# 文件操作2# 斩神1# 日常92# 日志框架1# 朋友圈1# 朱元璋1# 模块1# 模板1# 正则表达式2# 测试1# 游戏2# 爬虫7# 生活迁移1# 电影2# 电脑1# 碎碎念1# 视觉识别1# 类1# 类型注解1# 网络基础2# 网络教室1# 羊毛2# 脚本2# 脚本工具1# 自动化2# 蓝奏云1# 订阅推荐2# 记录2# 评论系统1# 词根1# 词缀1# 说说1# 足迹1# 跑步2# 路径参数1# 转载2# 运动1# 部落冲突1# 配置1# 随机图1# 面向对象5# 音乐3# 音标1# 饮食1# 驼峰命名1# 高德地图1
[ 公告 ]

如果你喜欢,那么欢迎来到我的世界!

了解更多
[ 音乐 ]
封面

音乐

暂未播放

0:000:00
暂无歌词
找不到相关结果。
[ contents ]
[ 全部文章 ]