langai

package module
v0.1.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 10, 2026 License: MIT Imports: 12 Imported by: 0

README

langai

Pragmatic AI SDK for Go focused on tool calling (agentic workflows).

Status: early draft. API will change.

Goals

  • Simple, readable code (no heavy abstractions)
  • First-class tool calling and structured (JSON) outputs
  • Support OpenAI, Anthropic (Claude) and Google Gemini
  • Track usage including prompt caching (where available)

Install

go get github.com/andreyvit/langai@latest

Quick Start

Simple Completion
import (
    "github.com/andreyvit/langai"
    "github.com/andreyvit/langai/openai"
)

client := openai.New(openai.Config{APIKey: os.Getenv("OPENAI_API_KEY")})

resp, err := client.Complete(ctx, langai.Request{
    Messages: []langai.Message{
        langai.SystemMsg("You are a helpful assistant."),
        langai.UserMsg("Hello!"),
    },
    Options: langai.Options{
        Model: "gpt-4o-mini",
    },
})
fmt.Println(resp.Text())
Agentic Tool Calling
import (
    "github.com/andreyvit/langai"
    "github.com/andreyvit/langai/agent"
    "github.com/andreyvit/langai/anthropic"
    "github.com/andreyvit/langai/fstools"
)

client := anthropic.New(anthropic.Config{APIKey: os.Getenv("ANTHROPIC_API_KEY")})

fs, _ := fstools.New(map[string]string{
    "/": ".",
}, fstools.Options{})

result, err := agent.Run(ctx, client, fs.ReadOnlyTools(), langai.Request{
    Messages: []langai.Message{
        langai.SystemMsg("You are a coding assistant."),
        langai.UserMsg("What does this project do?"),
    },
    Options: langai.Options{
        Model: "claude-sonnet-4-20250514",
    },
}, agent.RunOptions{})

fmt.Println(result.Final.Text())
fmt.Printf("Used %d tokens, cost %s\n", result.Usage.TotalTokens, result.Cost)

Packages

Package Description
langai Core types, interfaces, and client abstraction
langai/openai OpenAI API implementation
langai/anthropic Anthropic Claude API implementation
langai/gemini Google Gemini API implementation
langai/agent Agentic loop runner for tool calling workflows
langai/fstools Filesystem toolkit with virtual mount points

Core Types

Messages
// Create messages
msg := langai.SystemMsg("You are helpful.")
msg := langai.UserMsg("Hello!")
msg := langai.AssistantMsg("Hi there!")

// Or with parts for multimodal content
msg := langai.Message{
    Role: langai.RoleUser,
    Parts: []*langai.Part{
        langai.Text("What's in this image?"),
        langai.ImageURL("image/png", "https://example.com/image.png"),
    },
}
Request and Response
resp, err := client.Complete(ctx, langai.Request{
    Messages: messages,
    Options: langai.Options{
        Model:           "gpt-4o",
        MaxOutputTokens: 1000,
        Temperature:     0.7,
        Tools:           tools,
        ToolChoice:      langai.AutoToolChoice(),
    },
})

// Response
text := resp.Text()           // Extract text content
calls := resp.ToolCalls()     // Extract tool calls
usage := resp.Usage           // Token usage (with cache tracking)
cost := resp.Cost             // Cost in dollars
costKnown := resp.CostOK      // false if pricing for (provider, model) is unknown
Tools
tool := &langai.Tool{
    Name:        "get_weather",
    Description: "Get current weather for a location",
    InputSchema: langai.MustMarshalJSON(map[string]any{
        "type": "object",
        "properties": map[string]any{
            "location": map[string]any{"type": "string"},
        },
        "required": []string{"location"},
    }),
    Run: func(ctx context.Context, call langai.ToolCall) (langai.ToolResult, error) {
        var input struct{ Location string }
        call.UnmarshalInput(&input)

        weather := fetchWeather(input.Location)
        return langai.ToolResult{Content: weather}, nil
    },
}

Provider Support

All three providers implement the langai.Client interface:

// OpenAI
client := openai.New(openai.Config{
    APIKey:  os.Getenv("OPENAI_API_KEY"),
    BaseURL: "https://api.openai.com", // optional
})

// Anthropic
client := anthropic.New(anthropic.Config{
    APIKey: os.Getenv("ANTHROPIC_API_KEY"),
})

// Google Gemini
client := gemini.New(gemini.Config{
    APIKey: os.Getenv("GEMINI_API_KEY"),
})
Feature Matrix
Feature OpenAI Anthropic Gemini
Text completion
Tool calling
Images (URL)
Images (bytes)
JSON mode
Prompt caching

Agent Runner

The agent package provides an automatic tool execution loop:

result, err := agent.Run(ctx, client, tools, req, agent.RunOptions{
    MaxTurns:     8,   // Max model calls
    MaxToolCalls: 64,  // Max total tool invocations
    OnResponse: func(resp *langai.Response) {
        log.Println("Model responded")
    },
    OnToolCall: func(call *langai.ToolCall) {
        log.Printf("Calling tool: %s", call.Name)
    },
})

// Result includes:
result.Final       // Final response
result.Messages    // Full conversation history
result.Turns       // Number of model calls
result.ToolCalls   // Number of tool calls
result.Usage       // Aggregated token usage
result.Cost        // Total cost

Filesystem Tools

The fstools package provides a production-ready filesystem toolkit for AI agents:

fs, err := fstools.New(map[string]string{
    "/":         "/path/to/project",
    "/lifebase": "/path/to/notes",
}, fstools.Options{
    MaxReadBytes: 10 * 1024 * 1024, // 10 MB limit
})

// Read-only tools (safe for untrusted agents)
tools := fs.ReadOnlyTools()
// Includes: list_mounts, glob, ls, grep, read, stat

// All tools (including write operations)
tools := fs.AllTools()
// Adds: write, append_file, mkdir, patch, edit, delete, move, copy
Available Tools
Tool Description
list_mounts List virtual mount points
glob Match files by pattern (doublestar syntax)
ls List directory contents
grep Search file contents (regex support)
read Read files with line numbers
stat Get file metadata
write Write/overwrite files
append_file Append to files
mkdir Create directories
edit Replace exact strings in files
patch Apply multi-file patches (Codex format)
delete Delete files/directories
move Move/rename files
copy Copy files/directories
Safety Features
  • Virtual mount points: Agents see virtual paths like /README.md
  • Read-first requirement: Files must be read before editing
  • Binary detection: Automatically rejects binary files
  • Size limits: Configurable max read size

Prompt Caching (Anthropic)

Track and utilize Anthropic's prompt caching:

// Mark content for caching
msg := langai.Message{
    Role: langai.RoleSystem,
    Parts: []*langai.Part{
        {Type: langai.PartText, Text: longSystemPrompt, CacheControl: langai.CacheEphemeral},
    },
}

// Usage includes cache stats
resp.Usage.CacheReadInputTokens     // Tokens read from cache
resp.Usage.CacheCreationInputTokens // Tokens that created cache

Structured Output

resp, err := client.Complete(ctx, langai.Request{
    Messages: messages,
    Options: langai.Options{
        Model: "gpt-4o",
        ResponseFormat: langai.ResponseFormat{
            Type: langai.ResponseFormatJSONSchema,
            JSONSchema: &langai.JSONSchema{
                Name:   "response",
                Strict: true,
                Schema: langai.MustMarshalJSON(yourSchema),
            },
        },
    },
})

Example: Coding Agent

See examples/smith for a complete coding agent that:

  • Works with all three providers
  • Uses filesystem tools for code navigation and modification
  • Runs verification (gofmt, go vet, go test) after changes
go run ./examples/smith --provider openai "add a --verbose flag to main.go"

Dependencies

  • github.com/andreyvit/httpcall — HTTP client abstraction
  • github.com/bmatcuk/doublestar/v4 — Glob pattern matching

Documentation

Index

Constants

View Source
const PriceDenomination = 100_000_000_000

Variables

This section is empty.

Functions

func AutoToolChoice

func AutoToolChoice() func(Turn) ToolChoice

func EstimateTokens

func EstimateTokens(text string) int

EstimateTokens returns an approximate token count for the given text. Uses an older GPT-style BPE tokenizer. The count is approximate because: - Different models use different tokenizers - This tokenizer is from an older OpenAI model Generally overestimates slightly, which is fine for threshold checks.

func ForceToolChoice

func ForceToolChoice(name string) func(Turn) ToolChoice

func MarshalJSON

func MarshalJSON(v any) (json.RawMessage, error)

func MustMarshalJSON

func MustMarshalJSON(v any) json.RawMessage

func NoToolChoice

func NoToolChoice() func(Turn) ToolChoice

func RegisterModelPricing

func RegisterModelPricing(provider ProviderID, model string, p ModelPricing)

RegisterModelPricing overrides pricing for a (provider, model) pair. Model matching is exact against the normalized model string (trimmed, lowercased).

func RequireToolChoice

func RequireToolChoice() func(Turn) ToolChoice

Types

type CacheControl

type CacheControl string
const (
	CacheDefault   CacheControl = ""
	CacheEphemeral CacheControl = "ephemeral"
)

type CacheMode

type CacheMode int
const (
	CacheModeNone CacheMode = iota
	CacheModeShortTerm
	CacheModeLongTerm
)

func ParseCacheMode

func ParseCacheMode(s string) (CacheMode, error)

func (CacheMode) MarshalText

func (v CacheMode) MarshalText() ([]byte, error)

func (CacheMode) String

func (v CacheMode) String() string

func (*CacheMode) UnmarshalText

func (v *CacheMode) UnmarshalText(b []byte) error

type Client

type Client interface {
	Complete(ctx context.Context, req Request) (*Response, error)
}

type Document

type Document struct {
	Name      string
	MediaType string
	Text      string
}

type Image

type Image struct {
	MediaType string

	// Exactly one of these should be set:
	URL  string
	Data []byte
}

type JSONSchema

type JSONSchema struct {
	Name   string
	Schema json.RawMessage
	Strict bool
}

type Message

type Message struct {
	Role  Role
	Name  string
	Parts []*Part

	// IsCacheBreakpoint marks this message as a prompt-caching breakpoint (if supported by the provider).
	// When Request.CacheMode != CacheModeNone, providers may translate this into their own cache breakpoint markers.
	IsCacheBreakpoint bool
}

func Assistant

func Assistant(parts ...*Part) Message

func AssistantMsg

func AssistantMsg(text string) Message

func System

func System(parts ...*Part) Message

func SystemMsg

func SystemMsg(text string) Message

func ToolResultMsg

func ToolResultMsg(res ToolResult) Message

func User

func User(parts ...*Part) Message

func UserMsg

func UserMsg(text string) Message

type ModelPricing

type ModelPricing struct {
	InputTokenPrice  Price
	OutputTokenPrice Price

	// Cached input token prices (if the provider reports these token types).
	CacheReadInputTokenPrice     Price
	CacheCreationInputTokenPrice Price
}

ModelPricing defines per-token prices for a model.

Price is in 1/1_000_000_000 of a cent (see usage.go).

func LookupModelPricing

func LookupModelPricing(provider ProviderID, model string) (ModelPricing, bool)

type Options

type Options struct {
	Model string

	MaxOutputTokens int
	Temperature     float64
	TopP            float64
	Stop            []string

	Tools      []*Tool
	ToolChoice func(Turn) ToolChoice

	ResponseFormat ResponseFormat
}

func DefaultOptions

func DefaultOptions() Options

type Part

type Part struct {
	Type PartType

	Text     string
	Image    *Image
	Document *Document

	Thinking *ThinkingBlock
	Redacted *RedactedThinkingBlock

	ToolCall   *ToolCall
	ToolResult *ToolResult

	CacheControl CacheControl
}

func DocumentText

func DocumentText(name, mediaType, text string) *Part

func ImageBytes

func ImageBytes(mediaType string, data []byte) *Part

func ImageURL

func ImageURL(mediaType, url string) *Part

func RedactedThinking

func RedactedThinking(data string) *Part

func Text

func Text(s string) *Part

func Thinking

func Thinking(thinking, signature string) *Part

func ToolCallPart

func ToolCallPart(call ToolCall) *Part

func ToolResultPart

func ToolResultPart(res ToolResult) *Part

func (*Part) WithCacheControl

func (p *Part) WithCacheControl(c CacheControl) *Part

type PartType

type PartType string
const (
	PartText       PartType = "text"
	PartImage      PartType = "image"
	PartDocument   PartType = "document"
	PartThinking   PartType = "thinking"
	PartRedacted   PartType = "redacted_thinking"
	PartToolCall   PartType = "tool_call"
	PartToolResult PartType = "tool_result"
)

type Price

type Price int64

Price is an amount in 1/1_000_000_000 of a cent (1e-9 cent).

I.e. $2 per 1M tokens = $0.002 per 1K tokens = Price(200_000) per token.

func EstimateCost

func EstimateCost(provider ProviderID, model string, u Usage) (Price, bool)

EstimateCost returns the estimated cost for a response (usage + model pricing). If the model pricing is unknown, it returns (0, false).

func (Price) String

func (p Price) String() string

type ProviderID

type ProviderID string
const (
	ProviderOpenAI    ProviderID = "openai"
	ProviderAnthropic ProviderID = "anthropic"
	ProviderGemini    ProviderID = "gemini"
)

type RedactedThinkingBlock

type RedactedThinkingBlock struct {
	Data string
}

type Request

type Request struct {
	Turn     Turn
	Messages []Message
	Options  Options

	Metadata map[string]string

	// ConfigureRequest is an optional hook invoked by provider clients that use
	// httpcall (OpenAI/Anthropic/Gemini) right before executing the request.
	//
	// This allows call sites to attach logging, metrics, retry tweaks, etc.
	ConfigureRequest func(r *httpcall.Request)

	// CacheMode controls provider-specific prompt caching behavior (if supported).
	CacheMode CacheMode

	// ThinkingBudget enables provider-specific extended thinking (if supported).
	// Zero disables extended thinking.
	ThinkingBudget int
}

type Response

type Response struct {
	Provider ProviderID
	Model    string

	Message Message
	Usage   Usage
	Cost    Price
	CostOK  bool

	RawResponse []byte
}

func (*Response) Text

func (r *Response) Text() string

func (*Response) ToolCalls

func (r *Response) ToolCalls() []*ToolCall

type ResponseFormat

type ResponseFormat struct {
	Type       ResponseFormatType
	JSONSchema *JSONSchema
}

type ResponseFormatType

type ResponseFormatType string
const (
	ResponseFormatText       ResponseFormatType = "text"
	ResponseFormatJSONObject ResponseFormatType = "json_object"
	ResponseFormatJSONSchema ResponseFormatType = "json_schema"
)

type Role

type Role string
const (
	RoleSystem    Role = "system"
	RoleUser      Role = "user"
	RoleAssistant Role = "assistant"
	RoleTool      Role = "tool"
)

type ThinkingBlock

type ThinkingBlock struct {
	Thinking  string
	Signature string
}

type Tool

type Tool struct {
	Name        string
	Description string

	// InputSchema is a JSON Schema object.
	InputSchema json.RawMessage

	// Run executes the tool.
	//
	// If you use tool calling with agent.Run, this function is invoked whenever
	// the model requests a tool call with Tool.Name.
	//
	// It should typically return a ToolResult with ToolCallID set to call.ID
	// and Name set to call.Name (but agent.Run fills missing fields
	// defensively).
	Run func(ctx context.Context, call ToolCall) (ToolResult, error)
}

type ToolCall

type ToolCall struct {
	ID               string
	Name             string
	Input            json.RawMessage
	ThoughtSignature string
}

func (*ToolCall) UnmarshalInput

func (c *ToolCall) UnmarshalInput(out any) error

type ToolChoice

type ToolChoice struct {
	Type ToolChoiceType
	Name string
}

type ToolChoiceType

type ToolChoiceType string
const (
	ToolChoiceAuto     ToolChoiceType = "auto"
	ToolChoiceNone     ToolChoiceType = "none"
	ToolChoiceRequired ToolChoiceType = "required"
	ToolChoiceTool     ToolChoiceType = "tool"
)

type ToolResult

type ToolResult struct {
	ToolCallID string
	Name       string

	Content     string
	ContentJSON json.RawMessage
	Terminate   bool

	IsError bool
}

type Turn

type Turn struct {
	CurrentTurn    int
	MaxTurns       int
	CurrentToolUse int
	MaxToolUses    int
}

type Usage

type Usage struct {
	InputTokens  int
	OutputTokens int
	TotalTokens  int

	CacheReadInputTokens     int
	CacheCreationInputTokens int
}

Directories

Path Synopsis
examples
smith command
tools

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL