LLM 0.32 重大重构:消息序列与流式多类型响应详解

LLM 0.32a0 重构核心抽象,支持消息序列输入和流式多类型输出
Simon Willison 发布了 LLM 库 0.32a0 alpha 版本,对核心抽象进行了重大但向后兼容的重构。两大核心变化:一是引入消息序列 API,允许直接传入完整对话历史,对齐 Chat Completions API 的无状态范式;二是新增流式多类型部件(streaming parts),支持在单次响应中区分处理文本、工具调用、推理过程等不同类型输出。此外还新增了通用序列化机制,让开发者摆脱 SQLite 存储绑定。
Simon Willison 刚刚发布了 LLM 0.32a0 alpha 版本,这是他维护的 LLM Python 库和 CLI 工具的一次重大但向后兼容的重构。这次更新从根本上重新设计了模型交互的抽象方式,以适应当今前沿模型日益复杂的输入输出需求。
Simon Willison 是 Django Web 框架的联合创始人,也是 Datasette(一个用于探索和发布数据的开源工具)的作者,他长期关注数据工具和 AI 基础设施的交叉领域。LLM 库是他在 2023 年初大语言模型爆发期启动的项目,定位为一个统一的命令行和 Python 接口,让开发者无需关心底层是 OpenAI、Anthropic、本地模型还是其他提供商,都能通过一致的 API 进行交互。该库的插件生态系统是其核心竞争力——社区贡献了数百个插件,覆盖了从 Claude、Gemini 到 Ollama 本地模型等几乎所有主流模型服务。
为什么 LLM 库需要这次重构?
LLM 库始于 2023 年 4 月,最初的设计非常简洁:发送文本提示,获取文本响应。这在当时完全够用,但两年多来,大语言模型的能力发生了翻天覆地的变化。
LLM 通过插件系统提供了对数千个不同模型的抽象访问。随着时间推移,它陆续增加了处理图像、音频、视频输入的 attachments 功能,输出结构化 JSON 的 schemas 功能,以及执行工具调用的 tools 功能。另一边,LLM 模型本身也在不断进化——推理能力、图像生成、多模态输出等新特性层出不穷。
原有的「文本输入→文本输出」抽象已经无法承载这些复杂场景。0.32a0 的两个核心变化正是为此而来:输入支持消息序列,输出支持多类型流式部件。
核心变化一:消息序列 API(Messages)
旧模式的局限
在 0.32 之前,LLM 通过 conversation 对象来管理多轮对话:
conversation = model.conversation()
r1 = conversation.prompt("Capital of France?")
r2 = conversation.prompt("Germany?")
这种方式只能从头开始构建对话,无法直接注入一段已有的对话历史。如果你想基于 LLM 库构建一个兼容 OpenAI Chat Completions API 的服务,就会遇到很大的困难——因为 API 的每次请求都需要携带完整的消息序列。
这里需要理解 Chat Completions API 确立的行业范式:OpenAI 在 2023 年 3 月推出的这一 API 采用了无状态设计,每次请求都携带一个完整的消息数组,每条消息标注角色(system、user、assistant)。服务端不保存对话上下文,客户端需要在每次请求中重新发送完整历史。这个模式后来被 Anthropic、Google、Mistral 等几乎所有模型提供商采纳,成为事实上的行业标准。LLM 库此前基于 conversation 对象的有状态设计与这一范式存在根本性的不匹配——开发者无法将外部系统中已有的对话历史直接「喂」给 LLM 库,而必须通过逐条 prompt 的方式重新构建。
虽然 CLI 工具通过 SQLite 实现了对话持久化的变通方案,但这从未成为稳定的 Python API 的一部分,而且很多场景下开发者并不想绑定 SQLite 作为存储层。
新的消息序列 API 用法
0.32a0 引入了 llm.user() 和 llm.assistant() 构建函数,允许开发者直接传入完整的消息序列:
import llm
from llm import user, assistant
model = llm.get_model("gpt-5.5")
response = model.prompt(messages=[
user("Capital of France?"),
assistant("Paris"),
user("Germany?"),
])
print(response.text())
原有的 prompt= 参数仍然有效,LLM 会在底层将其升级为单条消息的数组。此外,新版还支持通过 response.reply() 直接回复一个响应,作为构建对话的替代方式:
response2 = response.reply("How about Hungary?")
这个设计既保持了向后兼容,又大幅提升了灵活性。
核心变化二:流式多类型部件(Streaming Parts)
混合类型输出的挑战
现代大语言模型的响应早已不是纯文本。一次 Claude 的调用可能依次返回:推理过程、文本内容、工具调用的 JSON 请求、再接着更多文本。OpenAI 的代码解释器、Anthropic 的网页搜索等服务端工具,意味着响应中可能混合文本、工具调用、工具输出等多种格式。多模态输出模型甚至能在流式响应中穿插图片或音频片段。
要理解这里的复杂性,需要区分两种不同的工具调用模式。客户端工具调用是指模型在响应中请求客户端执行某个函数(如查询数据库、调用外部 API),客户端执行后将结果发回模型继续生成。服务端工具调用则是模型提供商在自己的基础设施上直接执行工具(如 OpenAI 的代码解释器在沙箱中运行 Python 代码,Anthropic 的网页搜索在服务端抓取网页),然后将工具执行结果作为响应流的一部分返回给客户端。服务端工具调用使得单次响应中可能交替出现文本片段和工具执行结果,传统的纯文本流式输出完全无法表达这种结构。
流式响应(streaming)本身是指模型在生成过程中逐 token 返回结果,而非等待完整生成后一次性返回,通常基于 Server-Sent Events(SSE)协议实现。这对用户体验至关重要——用户可以在模型「思考」时就开始阅读输出,而不必等待数秒甚至数十秒的完整生成。
stream_events API 的使用方式
0.32a0 将响应建模为类型化消息部件的流。开发者可以通过 stream_events() 或异步的 astream_events() 逐个处理不同类型的事件:
for event in response.stream_events():
if event.type == "text":
print(event.chunk, end="", flush=True)
elif event.type == "tool_call_name":
print(f"\nTool call: {event.chunk}(", end="")
elif event.type == "tool_call_args":
print(event.chunk, end="")
这种设计带来了立竿见影的实际效果。例如,CLI 工具现在可以用不同颜色显示「思考」文本和最终响应文本,思考内容输出到 stderr 以避免影响管道操作。配合更新后的 llm-anthropic 插件,Claude 模型的推理过程可以以灰色文字实时流式展示,而最终回答则以正常颜色呈现。
这里涉及的「推理 token」是 OpenAI o1/o3 系列和 Claude 3.5/4 等新一代模型引入的重要概念。这些模型在生成最终答案之前,会先进行一段内部「思考」过程,产生的 token 被称为推理 token 或思维链(chain-of-thought)。不同模型对推理 token 的处理方式各异:有的完全隐藏(如早期的 o1),有的以特殊标记返回(如 Claude 的 thinking 块),有的允许用户选择是否查看。推理 token 通常会计入 token 用量和费用,但不一定直接出现在最终输出中。LLM 0.32a0 通过 streaming parts 的类型系统将推理文本和最终响应文本明确区分,让开发者和终端用户可以灵活控制是否展示这些中间过程。
用户还可以通过 -R/--no-reasoning 标志完全隐藏推理 token 的输出——这也是本次发布中唯一的 CLI 层面变化。
序列化与反序列化机制
针对当前 SQLite 持久化方案过于僵化的问题,0.32a0 新增了通用的序列化机制:
serializable = response.to_dict()
# 存储到任意位置
response = Response.from_dict(serializable)
返回的字典是一个 TypedDict,定义在新的 llm/serialization.py 模块中。TypedDict 是 Python 类型系统中的一个特性(来自 typing 模块),它允许为字典的每个键指定值的类型,从而在保持字典灵活性的同时获得静态类型检查的好处。这让开发者可以自由选择存储后端——无论是 PostgreSQL、Redis、文件系统还是任何其他存储方案,不再被 SQLite 绑定。
后续版本计划与路线图
Simon 将这个版本作为 alpha 发布,目的是在真实环境中验证新设计。他预计稳定版 0.32 会与 alpha 非常接近,除非测试暴露出设计缺陷。
剩余的一个重要任务是重新设计 SQLite 日志系统,以更好地捕获新抽象返回的细粒度信息。理想方案是将对话建模为图结构,这样在类似 Chat Completions API 的场景中,不断延伸的对话可以避免在数据库中重复存储。
传统的对话存储通常是线性的——每轮对话按时间顺序追加到一条记录中。但在实际应用中,对话往往呈现树状或图状结构:用户可能从某一轮对话分叉出多个不同的后续方向(比如对同一个问题尝试不同的追问策略),或者多个独立请求共享同一段前缀历史。如果采用线性存储,每次分叉都需要复制完整的历史记录,造成大量数据冗余。图结构存储则将每条消息作为独立节点,通过有向边表示前后关系,共享的历史前缀只需存储一份。这在 Chat Completions API 的典型使用场景中尤为关键——每次请求都携带完整历史意味着大量重复数据,图结构可以通过共享前缀节点将存储开销从 O(n²) 降低到 O(n)。这个特性可能在 0.32 或 0.33 中实现。
这次重构的设计哲学启示
这次重构体现了一个优秀开源项目的演进智慧:在保持向后兼容的前提下,重新定义核心抽象。从「文本进文本出」到「消息序列进、多类型流式部件出」,LLM 库的抽象层终于追上了大模型能力的演进速度。对于依赖 LLM 库构建应用的开发者来说,这意味着更少的 hack、更清晰的代码结构,以及对未来模型能力的更好适应性。
这种「抽象层追赶能力层」的模式在软件工程中反复出现。早期的 Web 框架只需处理同步的请求-响应模型,后来 WebSocket 和 Server-Sent Events 的普及迫使框架引入异步抽象;数据库 ORM 最初只需映射简单的 CRUD 操作,后来复杂查询和分布式事务的需求推动了查询构建器和事务管理器的重新设计。LLM 库的这次重构遵循了同样的规律——当底层能力发生质变时,中间抽象层必须跟进演化,否则就会成为创新的瓶颈。
核心要点
- LLM 0.32a0 引入消息序列(messages)机制,允许直接传入完整对话历史,解决了此前无法灵活注入已有对话的痛点
- 新增流式多类型部件(streaming parts)API,支持在一次响应中区分处理文本、工具调用、推理过程等不同类型的输出
- CLI 工具现在可以用不同颜色区分显示推理文本和最终响应,推理内容输出到 stderr 不影响管道操作
- 新增 response.to_dict() / Response.from_dict() 序列化机制,让开发者摆脱 SQLite 存储的绑定
- 重构保持完全向后兼容,原有的 prompt= 参数和迭代流式输出方式仍然有效
相关推荐
深度解读OpenClaw开源小龙虾AI Agent运作原理深度解析
深度解析OpenClaw(开源小龙虾)AI Agent的底层运作原理,涵盖System Prompt、工具调用、SubAgent分身、Skill系统、记忆机制与Context Engineering等核心概念,帮你彻底理解AI Agent与普通语言模型的本质区别。
深度解读Transformer本质解析:一个被拆解的文字接龙函数
用文字接龙的视角理解Transformer本质。将复杂的语言生成任务拆解为Embedding、Transformer Block、概率输出三大模块,帮助深度学习初学者快速建立直觉。
深度解读Claude Code与普通AI对话的五大核心差异
详细对比Claude Code与普通AI对话工具在交互方式、上下文理解、执行力、记忆能力和工具调用五个维度的核心差异,帮你理解AI编程助手的真正价值。