Skip to content

Latest commit

 

History

History
275 lines (204 loc) · 7.71 KB

getting-started.md

File metadata and controls

275 lines (204 loc) · 7.71 KB

Getting Started

This guide walks you through installing Twilight AI and making your first LLM request.

Prerequisites

  • Go 1.25 or later
  • An API key from OpenAI, Anthropic, Google, or any OpenAI-compatible provider

Installation

go get github.com/felinics/twilight

Setup

1. Create a Provider

A Provider is the bridge between the SDK and a specific AI backend. The SDK ships with two OpenAI providers:

Chat Completions — broad compatibility with OpenAI and all OpenAI-compatible APIs:

import "github.com/felinics/twilight/provider/openai/completions"

provider := completions.New(
    completions.WithAPIKey("sk-..."),
)

Responses — OpenAI's newer API with native reasoning and citation support:

import "github.com/felinics/twilight/provider/openai/responses"

provider := responses.New(
    responses.WithAPIKey("sk-..."),
)

For OpenAI-compatible endpoints, add a custom base URL:

// Completions API
provider := completions.New(
    completions.WithAPIKey("your-key"),
    completions.WithBaseURL("https://api.deepseek.com/v1"),
)

// Responses API (e.g. via OpenRouter)
provider := responses.New(
    responses.WithAPIKey("sk-or-v1-..."),
    responses.WithBaseURL("https://openrouter.ai/api/v1"),
)

2. Get a Model

model := provider.ChatModel("gpt-4o-mini")

The model carries a reference to its provider, so the SDK knows which backend to call.

3. Generate Text

One model call takes a sdk.Request and returns a sdk.ModelResult:

import "github.com/felinics/twilight/sdk"

result, err := model.Generate(ctx, sdk.Request{
    Messages: []sdk.Message{
        sdk.UserMessage("What is the capital of France?"),
    },
})
// result.Text == "The capital of France is Paris."

4. Read the Full Result

The result carries the usage, the finish reason and the response metadata next to the text:

result, err := model.Generate(ctx, sdk.Request{
    System: "You are a helpful assistant.",
    Messages: []sdk.Message{
        sdk.UserMessage("What is the capital of France?"),
    },
})

fmt.Println(result.Text)                      // response text
fmt.Println(result.Usage.TotalTokens)         // token count
fmt.Println(result.FinishReason)              // "stop"
fmt.Println(result.Response.ModelID)          // "gpt-4o-mini"

Every sampling parameter is a field of the Request: Temperature, MaxTokens, Tools, ToolChoice, ResponseFormat and the rest. See the API Reference.

5. Stream the Response

For real-time output, Model.Stream returns a sdk.ModelStream: a channel of typed parts, and the assembled result once the channel is drained.

stream, err := model.Stream(ctx, sdk.Request{
    Messages: []sdk.Message{
        sdk.UserMessage("Tell me a short story."),
    },
})
if err != nil {
    log.Fatal(err)
}

for part := range stream.Parts {
    switch p := part.(type) {
    case *sdk.TextDeltaPart:
        fmt.Print(p.Text)
    case *sdk.ErrorPart:
        log.Fatal(p.Error)
    }
}
fmt.Println() // newline after streaming

result, err := stream.Result() // the same ModelResult Generate would have returned

To collect the whole stream without handling parts, sdk.CollectStream(ctx, stream.Parts) returns the ModelResult directly.

6. Test Provider Connectivity

Before making generation requests, you can verify that your API key and endpoint are working:

err := provider.Test(ctx)
var apiErr *sdk.APIError
switch {
case err == nil:
    fmt.Println("Ready to go!")
case sdk.KindOf(err) == sdk.KindAuthentication:
    fmt.Printf("Connected but auth failed: %v\n", err)
case errors.As(err, &apiErr):
    fmt.Printf("Connected but the check failed (HTTP %d): %v\n", apiErr.StatusCode, err)
default:
    fmt.Printf("Cannot reach endpoint: %v\n", err)
}

7. Discover Available Models

List all models your API key has access to:

models, err := provider.ListModels(ctx)
for _, m := range models {
    fmt.Println(m.ID)
}

Or check a single model:

model := provider.ChatModel("gpt-4o")
testResult, _ := model.Test(ctx)
fmt.Println(testResult.Supported) // true or false

6. Generate Speech

Speech synthesis converts text to audio. Speech providers are separate from chat providers:

import "github.com/felinics/twilight/provider/edge/speech"

speechProvider := speech.New()
speechModel := speechProvider.SpeechModel("edge-read-aloud")

result, err := sdk.GenerateSpeech(ctx,
    sdk.WithSpeechModel(speechModel),
    sdk.WithText("Hello, this is a speech test."),
    sdk.WithSpeechConfig(map[string]any{
        "voice": "en-US-EmmaMultilingualNeural",
    }),
)
// result.Audio is []byte (raw audio data)
// result.ContentType is "audio/mpeg"

// Or stream for low-latency playback:
sr, err := sdk.StreamSpeech(ctx,
    sdk.WithSpeechModel(speechModel),
    sdk.WithText("Streaming speech synthesis."),
)
for chunk := range sr.Stream {
    // write each chunk...
}

Edge TTS is free and requires no API key. See Speech for all available voices and configuration options.

7. Generate Embeddings

Embeddings convert text into numeric vectors for search, retrieval, and similarity comparison. Embedding providers are separate from chat providers:

import "github.com/felinics/twilight/provider/openai/embedding"

embProvider := embedding.New(
    embedding.WithAPIKey("sk-..."),
)
embModel := embProvider.EmbeddingModel("text-embedding-3-small")

vec, err := sdk.Embed(ctx, "What is the capital of France?",
    sdk.WithEmbeddingModel(embModel),
)
// vec is a []float64 vector

// Embed multiple texts at once
result, err := sdk.EmbedMany(ctx, []string{"Paris", "London", "Berlin"},
    sdk.WithEmbeddingModel(embModel),
)
// result.Embeddings[0] is the vector for "Paris"
// result.Usage.Tokens reports total tokens consumed

For Google embeddings, use the Google embedding provider:

import "github.com/felinics/twilight/provider/google/embedding"

embProvider := embedding.New(
    embedding.WithAPIKey("AIza..."),
    embedding.WithTaskType("RETRIEVAL_QUERY"),
)
embModel := embProvider.EmbeddingModel("gemini-embedding-001")

vec, err := sdk.Embed(ctx, "search query here",
    sdk.WithEmbeddingModel(embModel),
)

Other Modalities

Model.Generate and Model.Stream are the only chat entry points. Embedding, image, speech, transcription and video calls are package-level functions (sdk.Embed, sdk.GenerateImage, ...) and, equivalently, methods of a sdk.Client:

client := sdk.NewClient()
vec, err := client.Embed(ctx, "search query here", sdk.WithEmbeddingModel(embModel))

Environment Variables

The SDK itself does not read environment variables.

go test ./... runs only offline tests against local HTTP fixtures; it never contacts a real provider, even when API keys are set. Tests that call real APIs are compiled only with the integration build tag:

go test -tags integration -count=1 -run '^TestIntegration_' ./...

These send real requests that may be billed. Each test skips when its credentials are missing. Credentials are read from the environment, or from a .env file in the repository root (see .env.example):

OPENAI_API_KEY=sk-...
OPENAI_BASE_URL=https://api.openai.com/v1
OPENAI_MODEL=gpt-4o-mini

Next Steps

  • Providers — learn about the Provider interface and OpenAI options
  • Embeddings — generate vector embeddings with OpenAI and Google
  • Speech — speech synthesis with Edge TTS and custom providers
  • Tool Calling — define tools, run the calls the model makes, build the next request
  • Streaming — understand StreamPart types and advanced patterns
  • API Reference — complete type and function reference