LLM 0.32a0 是一次重大的向后兼容重构LLM 0.32a0 is a major backwards-compatible refactor
Simon Willison 发布了 LLM 0.32a0 版本,这是一个重要的 alpha 版本,标志着该项目从传统的提示-响应模型向更复杂的对话状态管理架构的重大重构。新版本引入了对工具调用和对话历史的原生支持,为未来更强大的 AI 交互功能奠定了基础。
Simon Willison
2026年4月29日
我刚刚发布了 LLM 0.32a0,这是我用于访问大型语言模型(LLM)的 Python 库和命令行工具的 alpha 版本,其中包含一些经过长期努力才实现的重要变更。
在之前的版本中,LLM 将世界建模为提示词(prompt)与响应(response)的关系:向模型发送一段文本提示词,然后接收一段文本响应。
import llm
model = llm.get_model("gpt-5.5")
response = model.prompt("Capital of France?")
print(response.text())这在我于2023年4月开始开发这个库时是合理的。但自那时以来,情况已经发生了巨大变化!
通过其插件系统,LLM 为数千种不同的模型提供了抽象层。最初的抽象——即输入文本返回文本——已无法涵盖我所需要支持的所有功能。
随着时间推移,LLM 自身也逐步增加了对图像、音频和视频输入的支持,随后又引入了结构化 JSON 输出的模式,接着又加入了执行工具调用的能力。与此同时,LLMs 本身也在不断演进,增加了推理支持以及返回图像等多种有趣的功能。
LLM 需要进一步发展,以更好地处理当今前沿模型所能处理的多种类型的输入和输出。
0.32a0 alpha 版本包含两个关键变更:模型输入现在可以表示为一系列消息(messages),而模型响应则可以由不同类型的内容流组成。
作为消息序列的提示词
虽然 LLMs 接受文本形式的输入,但从 ChatGPT 展示双向对话界面的价值以来,最常见的方式是将这些输入视为一系列对话轮次。
第一轮可能如下所示:
user: Capital of France?
assistant: (然后模型会填充助手的回复内容。)
但后续每一轮都需要重放此前完整的对话历史,就像一份剧本一样:
user: Capital of France?
assistant: Paris
user: Germany?
assistant:大多数主流厂商的 JSON API 都遵循这一模式。以下是使用 OpenAI 聊天补全 API(该 API 已被其他供应商广泛模仿)所呈现的上述示例:
curl https://api.openai.com/v1/chat/completions \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-5.5",
"messages": [
{
"role": "user",
"content": "Capital of France?"
},
{
"role": "assistant",
"content": "Paris"
},
{
"role": "user",
"content": "Germany?"
}
]
}'在 0.32 版本之前,LLM 将这些对话建模为:
model = llm.get_model("gpt-5.5")
conversation = model.conversation()
r1 = conversation.prompt("Capital of France?")
print(r1.text())
# Outputs "Paris"
r2 = conversation.prompt("Germany?")
print(r2.text())
# Outputs "Berlin"如果你从头开始构建与模型的对话,这种方式是可行的;但它无法从一开始就引入之前的对话历史。这使得诸如模拟 OpenAI 聊天补全 API 等任务变得异常困难。
llm CLI 工具通过一个自定义机制绕过了这个问题,利用 SQLite 持久化和扩展对话内容,但这从未成为 LLM API 的稳定组成部分——而且在许多场景中,你可能希望在不依赖 SQLite 存储层的情况下使用 Python 库。
新的 alpha 版本现已支持以下方式:
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())llm.user() 和 llm.assistant() 函数是新引入的构建器函数,专门用于在 messages=[] 数组内部使用。
之前的 prompt= 选项仍然有效,但在后台 LLM 会自动将其升级为单条消息的数组形式。
你现在还可以直接回复某个响应,作为构建对话的替代方案:
response2 = response.reply("How about Hungary?")
print(response2) # Default __str__() calls .text()流式内容分块
alpha 版本中的另一个主要新接口涉及从提示词流式返回结果。
此前,LLM 支持如下流式处理方式:
response = model.prompt("Generate an SVG of a pelican riding a bicycle")
for chunk in response:
print(chunk, end="")或此异步变体:
import asyncio
import llm
model = llm.get_async_model("gpt-5.5")
response = model.prompt("Generate an SVG of a pelican riding a bicycle")
async def run():
async for chunk in response:
print(chunk, end="", flush=True)
asyncio.run(run())如今许多模型会返回混合类型的内容。例如,向 Claude 发出的提示可能先返回推理输出,然后是文本,接着是一个用于工具调用的 JSON 请求,最后再返回更多文本内容。
某些模型甚至可以在服务器端执行工具,比如 OpenAI 的代码解释器工具或 Anthropic 的网络搜索工具。这意味着模型的结果可以结合文本、工具调用、工具输出以及其他格式。
多模态输出模型也开始涌现,它们可以在流式响应中返回图像,甚至混合音频片段。
新的 LLM alpha 模型将这些内容视为一系列带类型的消息部分。以下是 Python API 消费者的使用示例:
import asyncio
import llm
model = llm.get_model("gpt-5.5")
prompt = "invent 3 cool dogs, first talk about your motivations"
def describe_dog(name: str, bio: str) -> str:
"""Record the name and biography of a hypothetical dog."""
return f"{name}: {bio}"
def sync_example():
response = model.prompt(
prompt,
tools=[describe_dog],
)
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="", flush=True)
elif event.type == "tool_call_args":
print(event.chunk, end="", flush=True)
async def async_example():
model = llm.get_async_model("gpt-5.5")
response = model.prompt(
prompt,
tools=[describe_dog],
)
async for event in response.astream_events():
if event.type == "text":
print(event.chunk, end="", flush=True)
elif event.type == "tool_call_name":
print(f"\nTool call: {event.chunk}(", end="", flush=True)
elif event.type == "tool_call_args":
print(event.chunk, end="", flush=True)
sync_example()
asyncio.run(async_example())示例输出(仅来自第一个同步示例):
我的目标:创造三只风格独特、令人难忘的狗狗——一只电影感十足的,一只探险家式的,还有一只魅力十足又有点混乱的——让每只都仿佛能主演自己的故事。 工具调用:describe_dog({"name": "Nova Jetpaw", "bio": "一只银灰色细犬,戴着小巧的飞行员护目镜,喜欢在月光下的海滩上疾驰。Nova 勇敢、优雅,据说还常为了好玩而跑赢无人机。"}) 工具调用:describe_dog({"name": "Mochi Thunderbark", "bio": "一只毛茸茸的柯基,系着一条戏剧性的黑金相间头巾,自信得像摇滚明星。Mochi 个子矮小却嗓门大,忠诚无比,还领导着一支由松鼠组成的‘社区巡逻队’。"}) 工具调用:describe_dog({"name": "Atlas Snowfang", "bio": "一只体型庞大的白色哈士奇,冰蓝色的眼睛,背包装满徒步零食。Atlas 冷静、英勇,无论暴风雪、浓雾还是迷路的露营之旅,总能找到回家的路。"})
响应结束时,你可以调用 response.execute_tool_calls() 来实际执行请求的工具函数,或者发送 response.reply() 让这些工具被调用并将返回值送回模型:
print(response.reply("Tell me about the dogs"))这种支持不同 token 类型流式传输的新机制,意味着 CLI 工具现在可以用不同颜色显示“思考”文本,与最终响应文本区分开来。思考文本会输出到 stderr,因此不会影响通过管道传递给其他工具的输出结果。
本例使用了 Claude Sonnet 4.6(配合 llm-anthropic 插件更新的流式事件版本),因为 Anthropic 的模型将推理文本作为响应的一部分返回:
llm -m claude-sonnet-4.6 'Think about 3 cool dogs then describe them' \
-o thinking_display 1你可以使用新添加的 -R/--no-reasoning 标志来抑制推理 token 的输出。令人惊讶的是,这成了本次发布中唯一面向 CLI 的改动。
响应序列化与反序列化的机制
如前所述,LLM 目前用于将对话持久化到 SQLite 的代码相当僵化。我在 0.32a0 版本中新增了一种机制,希望能为 Python API 用户提供自定义替代方案的空间:
serializable = response.to_dict()
# serializable is a JSON-style dictionary
# store it anywhere you like, then inflate it:
response = Response.from_dict(serializable)该字典实际上是一个 TypedDict,定义在全新的 llm/serialization.py 模块中。
接下来要做什么?
我将其作为 alpha 版本发布,以便能在真实环境中测试几天各种插件并验证新设计的可行性。除非 alpha 测试暴露出整体设计上的缺陷,否则我预计稳定版 0.32 将与当前 alpha 版本非常相似。
还有一个重要任务待完成:我希望重新设计 SQLite 日志系统,以更好地捕获这一新抽象层返回的更精细细节。
理想情况下,我希望将其建模为一个图结构,以最好地支持类似 OpenAI 聊天补全 API 的场景——同一对话不断被扩展,随后每次提示都会重复使用。我希望能够在不重复存储的情况下保存这些内容。
我尚未决定是否应将此功能包含在 0.32 版本中,还是推迟到 0.33 版本再实现。
需要完整排版与评论请前往来源站点阅读。