Send your first prompt

This guide takes you from an empty project to a working LLM call in each of the seven SDKs. The shape is identical everywhere: construct a provider-bound client, chain the text builder with a model and a prompt, read the reply off the response.

Before you start

Set the API key for the provider you want to call. The keys are read from the environment:

export ANTHROPIC_API_KEY="sk-ant-..."
export OPENAI_API_KEY="sk-..."
export GOOGLE_API_KEY="..."

The examples below call Anthropic with claude-opus-4-7. Swap the builder (anthropic -> openai, google, …) and the model string to target a different provider — the call surface does not change.

Go

package main

import (
	"context"
	"fmt"
	"log"
	"os"

	llmkit "github.com/aktagon/llmkit-go/v3"
)

func main() {
	c := llmkit.New("anthropic", os.Getenv("ANTHROPIC_API_KEY"))
	resp, err := c.Text.
		Model("claude-opus-4-7").
		Prompt(context.Background(), "Explain prompt caching in two sentences.")
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(resp.Text)
	fmt.Printf("tokens: in=%d out=%d\n", resp.Usage.Input, resp.Usage.Output)
}

c.Text is a field on *Client, not a method call. Chain methods clone the prototype and return a fresh builder.

TypeScript

import { anthropic } from "@aktagon/llmkit-ts/builders";

const c = anthropic(process.env.ANTHROPIC_API_KEY!);
const resp = await c.text
  .model("claude-opus-4-7")
  .prompt("Explain prompt caching in two sentences.");

console.log(resp.text);
console.log(`tokens: in=${resp.usage.input} out=${resp.usage.output}`);

Python

import os
import asyncio
from llmkit.builders import anthropic

async def main():
    c = anthropic(os.environ["ANTHROPIC_API_KEY"])
    resp = await (
        c.text
        .model("claude-opus-4-7")
        .prompt("Explain prompt caching in two sentences.")
    )
    print(resp.text)
    print(f"tokens: in={resp.usage.input} out={resp.usage.output}")

asyncio.run(main())

Rust

use llmkit::builders::anthropic;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let c = anthropic(std::env::var("ANTHROPIC_API_KEY")?);
    let resp = c
        .text()
        .model("claude-opus-4-7")
        .prompt("Explain prompt caching in two sentences.")
        .await?;

    println!("{}", resp.text);
    println!("tokens: in={} out={}", resp.usage.input, resp.usage.output);
    Ok(())
}

Swift

import Foundation
import LLMKit

let key = ProcessInfo.processInfo.environment["ANTHROPIC_API_KEY"] ?? ""
let client = Client(provider: .anthropic, apiKey: key)
let resp = try await client.text
    .model("claude-opus-4-7")
    .prompt("Explain prompt caching in two sentences.")

print(resp.text)
print("tokens: in=\(resp.usage.input) out=\(resp.usage.output)")

Java

import com.aktagon.llmkit.Client;
import com.aktagon.llmkit.providers.generated.ProviderName;
import com.aktagon.llmkit.providers.generated.Response;

public class Quickstart {
    public static void main(String[] args) {
        Client client = new Client(ProviderName.ANTHROPIC, System.getenv("ANTHROPIC_API_KEY"));
        Response resp = client.text()
                .model("claude-opus-4-7")
                .prompt("Explain prompt caching in two sentences.");

        System.out.println(resp.text());
        System.out.println("tokens: in=" + resp.usage().input() + " out=" + resp.usage().output());
    }
}

Note Rust and Java reach the text builder as a method (c.text() / client.text()), where Go, TypeScript, Python, and Swift expose it as a field or property. Swift and Java construct the client with a provider value (Client(provider: .anthropic, apiKey: key) / new Client(ProviderName.ANTHROPIC, key)) instead of a per-provider builder function.

Zig

const std = @import("std");
const llmkit = @import("llmkit");

pub fn main(init: std.process.Init) !void {
    var arena_state = std.heap.ArenaAllocator.init(init.gpa);
    defer arena_state.deinit();
    const arena = arena_state.allocator();

    const key = init.environ_map.get("ANTHROPIC_API_KEY") orelse return error.MissingKey;
    const client = llmkit.Client.init(init.gpa, init.io, .anthropic);
    const resp = try client.text()
        .model("claude-opus-4-7")
        .prompt(arena, key, "Explain prompt caching in two sentences.");

    std.debug.print("{s}\n", .{resp.text});
    // null means the provider reported no count, not a count of 0.
    if (resp.usage.input) |n| std.debug.print("tokens: in={d}\n", .{n});
}

Zig passes the key and an allocator to the terminal (prompt(arena, key, ...)), and everything the call returns lives in that allocator.

  • Each SDK’s README.md documents the full call surface — streaming, tool-calling, caching, batches, and image/video/music generation.
  • docs/runbooks/001-deploy-the-llmkit-site.md (RUN-001) covers cutting and publishing a release once your change lands.