返回 2026-05-01
🛠 工具 / 开源

LLM 0.32a0 是一次重大的向后兼容重构LLM 0.32a0 is a major backwards-compatible refactor

simonwillison.net·2026-04-29

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 版本再实现。

需要完整排版与评论请前往来源站点阅读。