LLM

Das Modul wippy/llm bietet eine einheitliche Schnittstelle zur Arbeit mit Large Language Models verschiedener Anbieter (OpenAI, Anthropic, Google, lokale Modelle). Es unterstuetzt Textgenerierung, Tool-Aufrufe, strukturierte Ausgabe, Embeddings und Streaming.

Einrichtung

Fuege das Modul deinem Projekt hinzu:

wippy add wippy/llm
wippy install

Deklariere die Abhaengigkeit in deiner _index.yaml. Das LLM-Modul benoetigt einen Environment-Speicher (fuer API-Schluessel) und einen Process-Host:

version: "1.0"
namespace: app

entries:
  - name: os_env
    kind: env.storage.os

  - name: processes
    kind: process.host
    lifecycle:
      auto_start: true

  - name: dep.llm
    kind: ns.dependency
    component: wippy/llm
    version: "*"
    parameters:
      - name: env_storage
        value: app:os_env
      - name: process_host
        value: app:processes

Der Eintrag env.storage.os stellt OS-Umgebungsvariablen den LLM-Anbietern zur Verfuegung. Setze deine API-Schluessel als Umgebungsvariablen (z.B. OPENAI_API_KEY, ANTHROPIC_API_KEY).

Textgenerierung

Importiere die llm-Bibliothek in deinen Eintrag und rufe generate() auf:

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 }

Das erste Argument von generate() kann ein String-Prompt, ein Prompt-Builder oder eine Tabelle von Nachrichten sein. Das zweite Argument ist eine Options-Tabelle.

Generate-Optionen

Option Typ Beschreibung
model string Modellname oder -klasse (erforderlich)
temperature number Zufallskontrolle, 0-1
max_tokens number Maximale Anzahl zu generierender Tokens
top_p number Nucleus-Sampling-Parameter
top_k number Top-k-Filterung
thinking_effort number Denktiefe 0-100 (Modelle mit Denkfaehigkeit)
tools table Array von Tool-Definitionen
tool_choice string "auto", "none", "any" oder Tool-Name
stream table Streaming-Konfiguration: { reply_to, topic, buffer_size }
timeout number Anfrage-Timeout in Sekunden (Standard 600)

Antwortstruktur

Feld Typ Beschreibung
result string Generierter Textinhalt
tokens table Token-Nutzung: prompt_tokens, completion_tokens, thinking_tokens, total_tokens
finish_reason string Grund fuer das Ende der Generierung: "stop", "length", "tool_call", "filtered", "error"
tool_calls table? Array von Tool-Aufrufen (wenn das Modell Tools aufgerufen hat)
metadata table Anbieterspezifische Metadaten
usage_record table? Nutzungsdatensatz

Prompt-Builder

Fuer Konversationen mit mehreren Durchgaengen und komplexe Prompts verwende den Prompt-Builder:

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

Builder-Methoden

Methode Beschreibung
prompt.new() Leeren Builder erstellen
prompt.with_system(content) Builder mit Systemnachricht erstellen
:add_system(content, meta?) Systemnachricht hinzufuegen
:add_user(content, meta?) Benutzernachricht hinzufuegen
:add_assistant(content, meta?) Assistenznachricht hinzufuegen
:add_developer(content, meta?) Entwicklernachricht hinzufuegen
:add_message(role, content_parts, name?, meta?) Nachricht mit Rolle und Inhaltsteilen hinzufuegen
:add_function_call(name, args, id?) Tool-Aufruf des Assistenten hinzufuegen
:add_function_result(name, result, id?) Tool-Ausfuehrungsergebnis hinzufuegen
:add_cache_marker(id?) Cache-Grenze markieren (Claude-Modelle)
:get_messages() Nachrichtenarray abrufen
:build() { messages = ... }-Tabelle fuer llm.generate() abrufen
:clone() Tiefe Kopie des Builders
:clear() Alle Nachrichten entfernen

Alle add_*-Methoden geben den Builder fuer Verkettung zurueck.

Konversationen mit mehreren Durchgaengen

Baue Kontext ueber mehrere Durchgaenge auf, indem du Nachrichten anfuegst:

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

Multimodale Inhalte

Kombiniere Text und Bilder in einer einzelnen Nachricht:

local conversation = prompt.new()
conversation:add_message(prompt.ROLE.USER, {
    prompt.text("What's in this image?"),
    prompt.image("https://example.com/photo.jpg")
})
Funktion Beschreibung
prompt.text(content) Textinhalt-Teil
prompt.image(url, mime_type?) Bild von URL
prompt.image_base64(mime_type, data) Base64-kodiertes Bild

Rollenkonstanten

Konstante Wert
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"

Klonen

Klone einen Builder, um Variationen zu erstellen, ohne das Original zu veraendern:

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

Streame Antworten in Echtzeit mithilfe der Prozesskommunikation. Dies erfordert einen process.lua-Eintrag:

local llm = require("llm")

local TOPIC = "llm_stream"

local function main()
    local stream_ch = process.listen(TOPIC)

    local response = llm.generate("Write a short story", {
        model = "gpt-4o",
        stream = {
            reply_to = process.pid(),
            topic = TOPIC,
        },
    })

    while true do
        local chunk, ok = stream_ch:receive()
        if not ok then break end

        if chunk.type == "chunk" then
            io.write(chunk.content)
        elseif chunk.type == "thinking" then
            io.write(chunk.content)
        elseif chunk.type == "error" then
            io.print("Error: " .. chunk.error.message)
            break
        elseif chunk.type == "done" then
            break
        end
    end

    process.unlisten(stream_ch)
end

Chunk-Typen

Typ Felder Beschreibung
"chunk" content Textinhalt-Fragment
"thinking" content Denkprozess des Modells
"tool_call" name, arguments, id Tool-Aufruf
"error" error.message, error.type Stream-Fehler
"done" meta Stream abgeschlossen
Streaming erfordert einen process.lua-Eintrag, da es das Prozesskommunikationssystem von Wippy verwendet (process.pid(), process.listen()).

Tool-Aufrufe

Definiere Tools als Inline-Schemas und uebergib sie an 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, 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

Tool-Aufruf-Felder

Feld Typ Beschreibung
id string Eindeutiger Aufruf-Identifikator
name string Tool-Name
arguments table Geparste Argumente gemaess dem Schema

Tool-Auswahl

Wert Verhalten
"auto" Modell entscheidet, wann Tools verwendet werden (Standard)
"none" Niemals Tools verwenden
"any" Muss mindestens ein Tool verwenden
"tool_name" Muss das angegebene Tool verwenden

Strukturierte Ausgabe

Generiere validiertes JSON gemaess einem 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
Bei OpenAI-Modellen muessen alle Properties im required-Array enthalten sein. Verwende Union-Typen fuer optionale Felder: type = {"string", "null"}. Setze additionalProperties = false.

Modellkonfiguration

Modelle werden als Registry-Eintraege mit meta.type: llm.model definiert:

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

Felder des Modelleintrags

Feld Beschreibung
meta.name Modellbezeichner fuer API-Aufrufe
meta.type Muss llm.model sein
meta.capabilities Feature-Liste: generate, tool_use, structured_output, embed, thinking, vision, caching
meta.class Klassenzugehoerigkeit: fast, balanced, reasoning, etc.
meta.priority Numerische Prioritaet fuer klassenbasierte Aufloesung (hoeher gewinnt)
max_tokens Maximales Kontextfenster
output_tokens Maximale Ausgabe-Tokens
pricing Kosten pro Million Tokens: input, output
providers Array mit id (Anbieter-Eintrag) und provider_model (anbieterspezifischer Modellname)

Lokale Modelle

Fuer lokal gehostete Modelle (LM Studio, Ollama) definiere einen separaten Anbieter-Eintrag mit einer eigenen base_url:

  - 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

Modellaufloesung

Modelle koennen per exaktem Namen, Klasse oder explizitem Klassenpraefix referenziert werden:

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

Aufloesungsreihenfolge:

  1. Abgleich per exaktem meta.name
  2. Abgleich per Klassenname (hoechste meta.priority gewinnt)
  3. Mit class:-Praefix wird nur in dieser Klasse gesucht

Modellerkennung

Verfuegbare Modelle und ihre Faehigkeiten zur Laufzeit abfragen:

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

Generiere Vektor-Embeddings fuer semantische Suche:

local llm = require("llm")

-- single text
local response = llm.embed("The quick brown fox", {
    model = "text-embedding-3-small",
    dimensions = 512,
})
-- response.result is a float array

-- multiple texts
local response = llm.embed({
    "First document",
    "Second document",
}, { model = "text-embedding-3-small" })
-- response.result is an array of float arrays

Anbieterstatus

Pruefe einen Anbieter, bevor Arbeit gesendet wird. Nuetzlich fuer Bereitschaftspruefungen und leichtgewichtiges Health-Monitoring:

local status, err = llm.status({
    model = "gpt-4o",
})
Option Beschreibung
model Erforderlich. Zu pruefendes Modell.
provider_id Optional. Ueberspringt die Modellaufloesung und zielt auf einen bestimmten Anbieter.

Gibt die StatusResponse des Anbieters zurueck (Inhalt ist anbieterabhaengig).

Fehlerbehandlung

Fehler werden als zweiter Rueckgabewert zurueckgegeben. Bei einem Fehler ist der erste Rueckgabewert nil:

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

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

io.print(response.result)

Fehlertypen

Konstante Beschreibung
llm.ERROR_TYPE.INVALID_REQUEST Fehlerhafte Anfrage
llm.ERROR_TYPE.AUTHENTICATION Ungueltiger API-Schluessel
llm.ERROR_TYPE.RATE_LIMIT Rate-Limit des Anbieters ueberschritten
llm.ERROR_TYPE.SERVER_ERROR Serverfehler des Anbieters
llm.ERROR_TYPE.CONTEXT_LENGTH Eingabe ueberschreitet das Kontextfenster
llm.ERROR_TYPE.CONTENT_FILTER Inhalt durch Sicherheitssysteme gefiltert
llm.ERROR_TYPE.TIMEOUT Anfrage-Zeitueberschreitung
llm.ERROR_TYPE.MODEL_ERROR Ungueltiges oder nicht verfuegbares Modell

Abschlussgruende

Konstante Beschreibung
llm.FINISH_REASON.STOP Normale Fertigstellung
llm.FINISH_REASON.LENGTH Maximale Token-Anzahl erreicht
llm.FINISH_REASON.CONTENT_FILTER Inhalt gefiltert
llm.FINISH_REASON.TOOL_CALL Modell hat einen Tool-Aufruf ausgefuehrt
llm.FINISH_REASON.ERROR Fehler waehrend der Generierung

Faehigkeiten

Konstante Beschreibung
llm.CAPABILITY.GENERATE Textgenerierung
llm.CAPABILITY.TOOL_USE Tool-/Funktionsaufrufe
llm.CAPABILITY.STRUCTURED_OUTPUT Strukturierte JSON-Ausgabe
llm.CAPABILITY.EMBED Vektor-Embeddings
llm.CAPABILITY.THINKING Erweitertes Denken
llm.CAPABILITY.VISION Bildverstaendnis
llm.CAPABILITY.CACHING Prompt-Caching

Siehe auch