Strukturierte Ausgaben


Worum geht es?

Wenn man einem LLM eine Frage stellt, bekommt man im Normalfall eine Text-Antwort zurück. Möchte man aber in einem Programm ein LLM benutzen, möchte man in den meisten Fällen diesen Text nicht extra parsen. Wäre es nicht schöner, wenn wir dem Modell sagen könnten, wie die Antwort strukturiert sein soll, sodass man sie gleich programmseitig nutzen könnte?

Das kann man!

Die OpenAi-Api bietet hierfür die Option des Structured Output. Man gibt der Anfrage eine JSON-Struktur mit, und bekommt die Antwort in diesem Format.

Bei lokal gehosten LLM unterstützen dies nicht alle Modelle, und auch nicht alle Anwendungen. Ich habe gute Erfahrungen mit LM-Studio und Ollama gemacht, TGWUI kann hierbei leider nicht glänzen.

Im Folgenden findest Du ein Programm in GO, dass einen entsprechenden Zugriff demonstriert:

package main

import (
    "context"
    "encoding/json"
    "fmt"
    "log"

    "github.com/invopop/jsonschema"
    "github.com/openai/openai-go"
    "github.com/openai/openai-go/option"
)

const (
    baseUrl     = "http://localhost:1234/v1"
)

// CreateClient initializes and returns a new OpenAI API client using the preconfigured baseUrl.
func CreateClient() *openai.Client {
    client := openai.NewClient(
        option.WithBaseURL(baseUrl),
    )
    return &client
}

type Country struct {
    Name        string `json:"name"`
    Capital     string `json:"capital"`
    Inhabitants int    `json:"inhabitants"`
}

type CountryList struct {
    Countries []Country `json:"countries"`
}

// GenerateSchema generates a JSON schema for the given type for structured outputs
func GenerateSchema[T any]() interface{} {
    // Structured Outputs uses a subset of JSON schema
    // These flags are necessary to comply with the subset
    reflector := jsonschema.Reflector{
        AllowAdditionalProperties: false,
        DoNotReference:            true,
    }
    var v T
    schema := reflector.Reflect(v)
    return schema
}

func GetListOfCountries(cli *openai.Client) (countries CountryList, err error) {

    question := "Give me a list of the countries in the EU, their capital city and the number of inhabitants."

    var countrySchema = GenerateSchema[CountryList]()
    schemaParam := openai.ResponseFormatJSONSchemaJSONSchemaParam{
        Name:        "country_list",
        Description: openai.String("list of countries"),
        Schema:      countrySchema,
        Strict:      openai.Bool(true),
    }
    chatCompletion, err := cli.Chat.Completions.New(context.TODO(),
        openai.ChatCompletionNewParams{
            Messages: []openai.ChatCompletionMessageParamUnion{
                openai.UserMessage(question),
            },

            ResponseFormat: openai.ChatCompletionNewParamsResponseFormatUnion{
                OfJSONSchema: &openai.ResponseFormatJSONSchemaParam{
                    JSONSchema: schemaParam,
                },
            },

            Model: "text/models/gguf-gpt-oss-20b-uncensored_mradermacher.q8_0.gguf",
        })
    if err != nil {
        return CountryList{}, err
    }
    var c CountryList
    err = json.Unmarshal([]byte(chatCompletion.Choices[0].Message.Content), &c)
    if err != nil {
        return CountryList{}, err
    }
    return c, nil

}

func main() {
    // Erstelle OpenAi-Client
    openaiClient := CreateClient()

    countryList, err := GetListOfCountries(openaiClient)
    if err != nil {
        log.Fatal(err)
    }
    for _, country := range countryList.Countries {
        fmt.Printf("Country name: %v\n", country.Name)
    }
}