LLM 0.32a0 é um importante refator compatível com versões anteriores

LLM 0.32a0 é um importante refator compatível com versões anteriores


LLM 0.32a0 é um importante refator compatível com versões anteriores

29 de abril de 2026

Acabei de lançar o LLM 0.32a0, uma versão alfa da minha biblioteca LLM Python e ferramenta CLI para acessar LLMs, com algumas mudanças consequentes nas quais venho trabalhando há algum tempo.

Versões anteriores do LLM modelavam o mundo em termos de instruções e respostas. Envie ao modelo um prompt de texto e receba uma resposta de texto.

import llm

model = llm.get_model("gpt-5.5")
response = model.prompt("Capital of France?")
print(response.text())

Isso fez sentido quando comecei a trabalhar na biblioteca em abril de 2023. Muita coisa mudou desde então!

LLM fornece uma abstração de milhares de modelos diferentes por meio de seu sistema de plugins. A abstração original – de entrada de texto que retorna saída de texto – não era mais capaz de representar tudo o que eu precisava.

Com o tempo, o próprio LLM desenvolveu anexos para lidar com entrada de imagem, áudio e vídeo, depois esquemas para saída de JSON estruturado e, em seguida, ferramentas para executar chamadas de ferramenta. Enquanto isso, os LLMs continuaram evoluindo, adicionando suporte ao raciocínio e a capacidade de retornar imagens e todos os tipos de outros recursos interessantes.

O LLM precisa evoluir para lidar melhor com a diversidade de tipos de entrada e saída que podem ser processados ​​pelos modelos de fronteira atuais.

O alfa 0.32a0 tem duas mudanças principais: as entradas do modelo podem ser representadas como uma sequência de mensagens e as respostas do modelo podem ser compostas por um fluxo de partes de tipos diferentes.

Prompts como uma sequência de mensagens

LLMs aceitam entrada como texto, mas desde que o ChatGPT demonstrou o valor de uma interface conversacional bidirecional, a maneira mais comum de avisá-los tem sido tratar essa entrada como uma sequência de turnos de conversação.

A primeira curva pode ser assim:

user: Capital of France?
assistant: 

(A modelo então preenche a resposta do assistente.)

Mas cada turno subsequente precisa repetir toda a conversa até aquele ponto, como uma espécie de roteiro:

user: Capital of France?
assistant: Paris
user: Germany?
assistant:

A maioria das APIs JSON dos principais fornecedores segue esse padrão. Esta é a aparência acima usando a API de conclusão de bate-papo OpenAI, que foi amplamente imitada por outros provedores:

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?"
      }
    )
  }'

Antes da versão 0.32, o LLM modelava-os como conversas:

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"

Isso funcionava se você estivesse construindo uma conversa com o modelo do zero, mas não fornecia uma maneira de alimentar uma conversa anterior desde o início. Isso tornou tarefas como construir uma emulação da API de conclusão de bate-papo OpenAI muito mais difíceis do que deveriam.

O llm A ferramenta CLI contornou isso por meio de um mecanismo personalizado para persistir e aumentar conversas usando SQLite, mas isso nunca se tornou uma parte estável da API LLM – e há muitos lugares onde você pode querer usar a biblioteca Python sem se comprometer com o SQLite como camada de armazenamento.

O novo alfa agora suporta isto:

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())

O llm.user() e llm.assistant() funções são novas funções de construtor projetadas para serem usadas dentro desse messages=() variedade.

O anterior prompt= A opção ainda funciona, mas o LLM a atualiza para uma matriz de mensagens de item único nos bastidores.

Você também pode agora responder a uma resposta, como alternativa à construção de uma conversa:

response2 = response.reply("How about Hungary?")
print(response2) # Default __str__() calls .text()

Partes de streaming

A outra nova interface importante no alfa diz respeito ao streaming de resultados a partir de um prompt.

Anteriormente, o LLM suportava streaming assim:

response = model.prompt("Generate an SVG of a pelican riding a bicycle")
for chunk in response:
    print(chunk, end="")

Ou esta variante assíncrona:

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())

Muitos dos modelos atuais retornam tipos mistos de conteúdo. Uma execução de prompt contra Claude pode retornar uma saída de raciocínio, depois um texto, uma solicitação JSON para uma chamada de ferramenta e, em seguida, mais conteúdo de texto.

Alguns modelos podem até executar ferramentas no lado do servidor, por exemplo, a ferramenta de interpretação de código da OpenAI ou a pesquisa na web da Anthropic. Isso significa que os resultados do modelo podem combinar texto, chamadas de ferramentas, saídas de ferramentas e outros formatos.

Também estão começando a surgir modelos de saída multimodais, que podem retornar imagens ou até mesmo trechos de áudio misturados nessa resposta de streaming.

O novo LLM alfa os modela como um fluxo de partes de mensagens digitadas. Esta é a aparência de um consumidor da API Python:

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())

Exemplo de saída (apenas do primeiro exemplo de sincronização):

My motivation: create three memorable dogs with distinct “cool” styles—one cinematic, one adventurous, and one charmingly chaotic—so each feels like they could star in their own story.
Tool call: describe_dog({"name": "Nova Jetpaw", "bio": "A sleek silver-gray whippet who wears tiny aviator goggles and loves sprinting along moonlit beaches. Nova is fearless, elegant, and rumored to outrun drones just for fun."}
Tool call: describe_dog({"name": "Mochi Thunderbark", "bio": "A fluffy corgi with a dramatic black-and-gold bandana and the confidence of a rock star. Mochi is short, loud, loyal, and leads a neighborhood 'security patrol' made entirely of squirrels."}
Tool call: describe_dog({"name": "Atlas Snowfang", "bio": "A massive white husky with ice-blue eyes and a backpack full of trail snacks. Atlas is calm, heroic, and always knows the way home—even during blizzards, fog, or confusing camping trips."}

No final da resposta você pode ligar response.execute_tool_calls() para realmente executar as funções que foram solicitadas ou enviar um response.reply() para que essas ferramentas sejam chamadas e seus valores de retorno sejam enviados de volta ao modelo:

print(response.reply("Tell me about the dogs"))

Este novo mecanismo para transmitir diferentes tipos de token significa que a ferramenta CLI agora pode exibir texto “pensativo” em uma cor diferente do texto na resposta final. O texto de reflexão vai para stderr para que não afete os resultados canalizados para outras ferramentas.

Este exemplo usa Claude Sonnet 4.6 (com uma versão atualizada do evento de streaming do plugin llm-anthropic) já que os modelos do Anthropic retornam seu texto de raciocínio como parte da resposta:

llm -m claude-sonnet-4.6 'Think about 3 cool dogs then describe them' \
  -o thinking_display 1

Demonstração animada. Começa com ~/dev/scratch/llm-anthropic % uv run llm -m claude-sonnet-4.6 'Pense em 3 cachorros legais e depois descreva-os' -o thinking_display 1 - o texto então flui em cinza: O usuário quer que eu pense em 3 cachorros legais e depois os descreva. Deixe-me pensar em três cachorros interessantes e legais e descrevê-los. Em seguida, muda para texto colorido normal para a saída que descreve os cães.

Você pode suprimir a saída de tokens de raciocínio usando o novo -R/--no-reasoning bandeira. Surpreendentemente, essa acabou sendo a única mudança voltada para CLI nesta versão.

Um mecanismo para serializar e desserializar respostas

Conforme mencionado anteriormente, o LLM possui um código bastante inflexível no momento para persistir conversas com SQLite. Adicionei um novo mecanismo em 0.32a0 que deve fornecer aos usuários da API Python uma maneira de lançar sua própria alternativa:

serializable = response.to_dict()
# serializable is a JSON-style dictionary
# store it anywhere you like, then inflate it:
response = Response.from_dict(serializable)

O dicionário que isso retorna é na verdade um TypedDict definido no novo módulo llm/serialization.py.

O que vem a seguir?

Estou lançando isso como um alfa para poder atualizar vários plugins e exercitar o novo design em ambientes do mundo real por alguns dias. Espero que a versão estável 0.32 seja muito semelhante a este alfa, a menos que o teste alfa revele alguma falha de design na maneira como juntei tudo isso.

Resta uma grande tarefa: gostaria de redesenhar o sistema de log SQLite para capturar melhor os detalhes mais refinados que são retornados por esta nova abstração.

Idealmente, eu gostaria de modelar isso como um gráfico, para melhor suportar situações como uma API de conclusão de bate-papo no estilo OpenAI, onde as mesmas conversas são constantemente estendidas e repetidas a cada prompt. Quero poder armazená-los sem duplicá-los no banco de dados.

Estou indeciso se isso deveria ser um recurso no 0,32 ou se deveria mantê-lo no 0,33.



Source link

Postagens Similares

Deixe um comentário

O seu endereço de email não será publicado. Campos obrigatórios marcados com *