LLM

O modulo wippy/llm fornece uma interface unificada para trabalhar com Modelos de Linguagem de Grande Escala (LLMs) de multiplos provedores (OpenAI, Anthropic, Google, modelos locais). Ele suporta geracao de texto, chamada de ferramentas, saida estruturada, embeddings e streaming.

Esta página é uma introdução à API com snippets de referência combináveis, não um tutorial independente. Os exemplos pressupõem um projeto Wippy existente, um modelo e um provedor registrados e quaisquer credenciais exigidas por esse provedor. Substitua os nomes de modelos dos exemplos por um modelo exposto pelo seu registry; chamadas remotas de geração e embeddings podem gerar custos do provedor. Para um projeto completo e executável, siga Criar um Agente LLM.

Configuracao

Adicione o modulo ao seu projeto:

wippy add wippy/llm
wippy install

Declare a dependencia no seu _index.yaml. O modulo LLM requer um armazenamento de ambiente (para chaves de API) e um host de processos:

version: "1.0"
namespace: app

entries:
  - name: dep.llm
    kind: ns.dependency
    component: wippy/llm
    version: "*"

O módulo fornece um armazenamento de ambiente do sistema operacional e usa wippy.terminal:host como host padrão dos processos em segundo plano. Sobrescreva o parâmetro de dependência env_storage ou process_host somente quando a aplicação precisar de outra entrada. Defina as chaves de API dos provedores por variáveis como OPENAI_API_KEY e ANTHROPIC_API_KEY.

Geracao de Texto

Importe a biblioteca llm na sua entrada e chame generate():

entries:
  - name: ask
    kind: function.lua
    source: file://ask.lua
    method: handler
    imports:
      llm: wippy.llm:llm
local llm = require("llm")

local function handler()
    local response, err = llm.generate("What are the three laws of robotics?", {
        model = "gpt-4o"
    })

    if err then
        return nil, err
    end

    return response.result
end

return { handler = handler }

O primeiro argumento de generate() pode ser uma string de prompt, um construtor de prompt ou uma tabela de mensagens. O segundo argumento e uma tabela de opcoes.

Opcoes de Generate

Opcao Tipo Descricao
model string Nome ou classe do modelo (obrigatorio)
temperature number Controle de aleatoriedade, 0-1
max_tokens number Maximo de tokens a gerar
top_p number Parametro de amostragem nucleus
top_k number Filtragem top-k
thinking_effort number Profundidade de raciocinio 0-100 (modelos com capacidade de raciocinio)
tools table Array de definicoes de ferramentas
tool_choice string "auto", "none", "any", ou nome da ferramenta
stream table Configuracao de streaming: { reply_to, topic, buffer_size }
timeout number Timeout da requisicao em segundos (padrao 600)

Estrutura da Resposta

Campo Tipo Descricao
result string Conteudo de texto gerado
tokens table Uso de tokens: prompt_tokens, completion_tokens, thinking_tokens, total_tokens, mais os opcionais cache_read_input_tokens, cache_read_tokens, cache_creation_input_tokens, cache_write_tokens
finish_reason string Motivo da interrupcao da geracao: "stop", "length", "tool_call", "filtered", "error"
tool_calls table? Array de chamadas de ferramentas (se o modelo invocou ferramentas)
metadata table Metadados especificos do provedor
usage_record table? Registro de uso

Construtor de Prompt

Para conversas com multiplos turnos e prompts complexos, use o construtor de prompt:

imports:
  llm: wippy.llm:llm
  prompt: wippy.llm:prompt
local llm = require("llm")
local prompt = require("prompt")

local conversation = prompt.new()
conversation:add_system("You are a helpful assistant.")
conversation:add_user("What is the capital of France?")

local response, err = llm.generate(conversation, {
    model = "gpt-4o",
    temperature = 0.7,
    max_tokens = 500
})

Metodos do Construtor

Metodo Descricao
prompt.new() Cria um construtor vazio
prompt.with_system(content) Cria um construtor com mensagem de sistema
:add_system(content, meta?) Adiciona mensagem de sistema
:add_user(content, meta?) Adiciona mensagem do usuario
:add_assistant(content, meta?) Adiciona mensagem do assistente
:add_developer(content, meta?) Adiciona mensagem do desenvolvedor
:add_message(role, content_parts, name?, meta?) Adiciona mensagem com papel e partes de conteudo
:add_function_call(name, arguments, id?, options?) Adiciona chamada de ferramenta do assistente (arguments é a string JSON bruta)
:add_function_result(name, result, id?) Adiciona resultado de execucao de ferramenta
:add_cache_marker(id?) Marca limite de cache (modelos Claude)
:get_messages() Obtem o array de mensagens
:build() Obtem tabela { messages = ... } para llm.generate()
:clone() Copia profunda do construtor
:clear() Remove todas as mensagens

Todos os metodos add_* retornam o construtor para encadeamento.

Conversas com Multiplos Turnos

Construa o contexto ao longo dos turnos adicionando mensagens:

local conversation = prompt.new()
conversation:add_system("You are a helpful assistant.")

-- first turn
conversation:add_user("What is Lua?")
local r1 = llm.generate(conversation, { model = "gpt-4o" })
conversation:add_assistant(r1.result)

-- second turn with full context
conversation:add_user("What makes it different from Python?")
local r2 = llm.generate(conversation, { model = "gpt-4o" })

Conteudo Multimodal

Combine texto e imagens em uma unica mensagem:

local conversation = prompt.new()
conversation:add_message(prompt.ROLE.USER, {
    prompt.text("What's in this image?"),
    prompt.image("https://example.com/photo.jpg")
})
Funcao Descricao
prompt.text(content) Parte de conteudo de texto
prompt.image(url, mime_type?) Imagem a partir de URL
prompt.image_base64(mime_type, data) Imagem codificada em Base64

Constantes de Papel

Constante Valor
prompt.ROLE.SYSTEM "system"
prompt.ROLE.USER "user"
prompt.ROLE.ASSISTANT "assistant"
prompt.ROLE.DEVELOPER "developer"
prompt.ROLE.FUNCTION_CALL "function_call"
prompt.ROLE.FUNCTION_RESULT "function_result"
prompt.ROLE.CACHE_MARKER "cache_marker"

Clonagem

Clone um construtor para criar variacoes sem modificar o original:

local base = prompt.new()
base:add_system("You are a helpful assistant.")

local conv1 = base:clone()
conv1:add_user("What is AI?")

local conv2 = base:clone()
conv2:add_user("What is ML?")

Streaming

Transmita respostas em tempo real usando comunicacao de processos. Isso requer uma entrada process.lua:

local llm = require("llm")

local TOPIC = "llm_stream"

local function main()
    local stream_ch, listen_err = process.listen(TOPIC)
    if listen_err then
        return nil, listen_err
    end

    local function finish(text, response, err)
        local ok, cleanup_err = process.unlisten(stream_ch)
        if not ok then
            cleanup_err = cleanup_err or "Failed to remove LLM stream listener"
            if err then
                return nil, tostring(err) .. "; cleanup failed: " .. tostring(cleanup_err)
            end
            return nil, cleanup_err
        end
        if err then
            return nil, err
        end
        return text, response
    end

    local self_pid, pid_err = process.pid()
    if pid_err then
        return finish(nil, nil, pid_err)
    end

    local done_ch = channel.new(1)
    coroutine.spawn(function()
        local response, err = llm.generate("Write a short story", {
            model = "gpt-4o",
            stream = {
                reply_to = self_pid,
                topic = TOPIC,
            },
        })
        done_ch:send({ response = response, err = err })
    end)

    local full_text = ""
    local generation_result = nil
    local stream_done = false
    local stream_err = nil

    while true do
        local cases = {}
        if not stream_done then
            table.insert(cases, stream_ch:case_receive())
        end
        if not generation_result then
            table.insert(cases, done_ch:case_receive())
        end

        local result = channel.select(cases)
        if not result.ok then
            return finish(nil, nil, "LLM stream closed before completion")
        end

        if result.channel == done_ch then
            generation_result = result.value
            if generation_result.err then
                return finish(nil, nil, generation_result.err)
            end
            if stream_done then
                return finish(full_text, generation_result.response, stream_err)
            end
        else
            local chunk = result.value
            if chunk.type == "chunk" then
                local content = chunk.content or ""
                print(content)
                full_text = full_text .. content
            elseif chunk.type == "thinking" then
                print(chunk.content or "")
            elseif chunk.type == "error" then
                stream_done = true
                stream_err = chunk.error and chunk.error.message or "LLM stream failed"
            elseif chunk.type == "done" then
                stream_done = true
            end

            if stream_done and generation_result then
                return finish(full_text, generation_result.response, stream_err)
            end
        end
    end
end

Tipos de Chunk

Tipo Campos Descricao
"chunk" content Fragmento de conteudo de texto
"thinking" content Processo de raciocinio do modelo
"tool_call" name, arguments, id Invocacao de ferramenta
"error" error.message, error.type Erro no stream
"done" meta Stream concluido
O streaming requer uma entrada process.lua porque utiliza o sistema de comunicacao de processos do Wippy (process.pid(), process.listen()).

Chamada de Ferramentas

Defina ferramentas como schemas inline e passe-as para generate():

local llm = require("llm")
local prompt = require("prompt")
local json = require("json")

local tools = {
    {
        name = "get_weather",
        description = "Get current weather for a location",
        schema = {
            type = "object",
            properties = {
                location = { type = "string", description = "City name" },
            },
            required = { "location" },
        },
    },
}

local conversation = prompt.new()
conversation:add_user("What's the weather in Tokyo?")

local response = llm.generate(conversation, {
    model = "gpt-4o",
    tools = tools,
    tool_choice = "auto",
})

if response.tool_calls and #response.tool_calls > 0 then
    for _, tc in ipairs(response.tool_calls) do
        -- execute the tool and get a result
        local result = { temperature = 22, condition = "sunny" }

        -- add the exchange to the conversation
        conversation:add_function_call(tc.name, json.encode(tc.arguments), tc.id)
        conversation:add_function_result(tc.name, json.encode(result), tc.id)
    end

    -- continue generation with tool results
    local final = llm.generate(conversation, { model = "gpt-4o" })
    print(final.result)
end

Campos de Chamada de Ferramenta

Campo Tipo Descricao
id string Identificador unico da chamada
name string Nome da ferramenta
arguments table Argumentos parseados correspondentes ao schema

Escolha de Ferramenta

Valor Comportamento
"auto" O modelo decide quando usar ferramentas (padrao)
"none" Nunca usar ferramentas
"any" Deve usar pelo menos uma ferramenta
"tool_name" Deve usar a ferramenta especificada

Saida Estruturada

Gere JSON validado correspondente a um schema:

local llm = require("llm")

local schema = {
    type = "object",
    properties = {
        name = { type = "string" },
        age = { type = "number" },
        hobbies = {
            type = "array",
            items = { type = "string" },
        },
    },
    required = { "name", "age", "hobbies" },
    additionalProperties = false,
}

local response, err = llm.structured_output(schema, "Describe a fictional character", {
    model = "gpt-4o",
})

if not err then
    print(response.result.name)
    print(response.result.age)
end
Para modelos OpenAI, todas as propriedades devem estar no array required. Use tipos de uniao para campos opcionais: type = {"string", "null"}. Defina additionalProperties = false.

Configuracao de Modelo

Modelos sao definidos como entradas de registro com meta.type: llm.model:

entries:
  - name: gpt-4o
    kind: registry.entry
    meta:
      name: gpt-4o
      type: llm.model
      title: GPT-4o
      comment: OpenAI's flagship model
      capabilities:
        - generate
        - tool_use
        - structured_output
        - vision
      class:
        - balanced
      priority: 100
    max_tokens: 128000
    output_tokens: 16384
    pricing:
      input: 2.5
      output: 10
    providers:
      - id: wippy.llm.openai:provider
        provider_model: gpt-4o

Campos da Entrada de Modelo

Campo Descricao
meta.name Identificador do modelo usado nas chamadas de API
meta.type Deve ser llm.model
meta.capabilities Lista de funcionalidades: generate, tool_use, structured_output, embed, thinking, vision, caching
meta.class Pertencimento a classe: fast, balanced, reasoning, etc.
meta.priority Prioridade numerica para resolucao baseada em classe (maior vence)
max_tokens Janela de contexto maxima
output_tokens Maximo de tokens de saida
pricing Custo por milhao de tokens: input, output
providers Array com id (entrada do provedor) e provider_model (nome do modelo especifico do provedor)

Modelos Locais

Para modelos hospedados localmente (LM Studio, Ollama), defina uma entrada de provedor separada com uma base_url customizada:

  - name: local_provider
    kind: registry.entry
    meta:
      name: ollama
      type: llm.provider
      title: Ollama Local
    driver:
      id: wippy.llm.openai:driver
      options:
        api_key_env: none
        base_url: http://127.0.0.1:11434/v1

  - name: local-llama
    kind: registry.entry
    meta:
      name: local-llama
      type: llm.model
      title: Local Llama
      capabilities:
        - generate
    max_tokens: 4096
    output_tokens: 4096
    pricing:
      input: 0
      output: 0
    providers:
      - id: app:local_provider
        provider_model: llama-3.2

Resolucao de Modelo

Modelos podem ser referenciados por nome exato, classe ou prefixo de classe explicito:

-- exact model name
llm.generate("Hello", { model = "gpt-4o" })

-- model class (picks highest priority in that class)
llm.generate("Hello", { model = "fast" })

-- explicit class syntax
llm.generate("Hello", { model = "class:reasoning" })

Ordem de resolucao:

  1. Corresponder por meta.name exato
  2. Corresponder por nome de classe (maior meta.priority vence)
  3. Com prefixo class:, buscar apenas naquela classe

Descoberta de Modelos

Consulte os modelos disponiveis e suas capacidades em tempo de execucao:

local llm = require("llm")

-- all models
local models = llm.available_models()

-- filter by capability
local tool_models = llm.available_models("tool_use")
local embed_models = llm.available_models("embed")

-- list model classes
local classes = llm.get_classes()
for _, c in ipairs(classes) do
    print(c.name .. ": " .. c.title)
end

Embeddings

Gere embeddings vetoriais para busca semantica:

local llm = require("llm")

-- A single input still returns an array of vectors.
local single_response, single_err = llm.embed("The quick brown fox", {
    model = "text-embedding-3-small",
    dimensions = 512,
})
if single_err then
    error("Embedding failed: " .. tostring(single_err))
end
local vector = single_response.result[1]

-- Multiple inputs return one vector per input.
local batch_response, batch_err = llm.embed({
    "First document",
    "Second document",
}, { model = "text-embedding-3-small" })
if batch_err then
    error("Batch embedding failed: " .. tostring(batch_err))
end
local vectors = batch_response.result

Status do Provedor

Sonde um provedor antes de enviar trabalho. Util para verificacoes de prontidao e monitoramento leve de saude:

local status, err = llm.status({
    model = "gpt-4o",
})
Opcao Descricao
model Obrigatorio. Modelo a verificar.
provider_id Opcional. Ignora a resolucao do modelo e direciona para um provedor especifico.

Retorna o StatusResponse do provedor (o conteudo depende do provedor).

Tratamento de Erros

Erros sao retornados como o segundo valor de retorno. Em caso de erro, o primeiro valor de retorno e nil:

local response, err = llm.generate("Hello", { model = "gpt-4o" })

if err then
    print("Error: " .. tostring(err))
    return
end

print(response.result)

Tipos de Erro

Constante Descricao
llm.ERROR_TYPE.INVALID_REQUEST Requisicao malformada
llm.ERROR_TYPE.AUTHENTICATION Chave de API invalida
llm.ERROR_TYPE.RATE_LIMIT Limite de taxa do provedor excedido
llm.ERROR_TYPE.SERVER_ERROR Erro no servidor do provedor
llm.ERROR_TYPE.CONTEXT_LENGTH Entrada excede a janela de contexto
llm.ERROR_TYPE.CONTENT_FILTER Conteudo filtrado pelos sistemas de seguranca
llm.ERROR_TYPE.TIMEOUT Timeout da requisicao
llm.ERROR_TYPE.MODEL_ERROR Modelo invalido ou indisponivel

Motivos de Finalizacao

Constante Descricao
llm.FINISH_REASON.STOP Conclusao normal
llm.FINISH_REASON.LENGTH Atingiu o maximo de tokens
llm.FINISH_REASON.CONTENT_FILTER Conteudo filtrado
llm.FINISH_REASON.TOOL_CALL O modelo fez uma chamada de ferramenta
llm.FINISH_REASON.ERROR Erro durante a geracao

Capacidades

Constante Descricao
llm.CAPABILITY.GENERATE Geracao de texto
llm.CAPABILITY.TOOL_USE Chamada de ferramentas/funcoes
llm.CAPABILITY.STRUCTURED_OUTPUT Saida estruturada JSON
llm.CAPABILITY.EMBED Embeddings vetoriais
llm.CAPABILITY.THINKING Raciocinio estendido
llm.CAPABILITY.VISION Compreensao de imagens
llm.CAPABILITY.CACHING Cache de prompt

Veja Tambem