π PromptKit SDK v2
Pack-first Go SDK that reduces boilerplate by ~80%
What is the PromptKit SDK?
The SDK v2 is a complete rewrite with a pack-first architecture that dramatically simplifies LLM application development:
- 5 lines to hello world - Open a pack, send a message, done
- Pack-first design - Load prompts tested with Arena, compiled with PackC
- Built-in tools - Register handlers with
OnTool, auto JSON serialization - Streaming support - Channel-based streaming with
Stream() - Human-in-the-Loop - Approval workflows for sensitive operations
- Type-safe variables -
SetVar/GetVarwith concurrent access - Observability - EventBus integration for monitoring
Quick Start
package main
import (
"context"
"fmt"
"log"
"github.com/AltairaLabs/PromptKit/sdk"
)
func main() {
// Open a conversation from a pack file
conv, err := sdk.Open("./hello.pack.json", "chat")
if err != nil {
log.Fatal(err)
}
defer conv.Close()
// Send a message and get a response
resp, _ := conv.Send(context.Background(), "Hello!")
fmt.Println(resp.Text())
}
Thatβs it. Five lines of functional code.
Core API
Opening Conversations
// Open from a pack file with a specific prompt
conv, err := sdk.Open("./myapp.pack.json", "assistant")
// Open with options
conv, err := sdk.Open("./myapp.pack.json", "assistant",
sdk.WithModel("gpt-4o"),
sdk.WithTemperature(0.7),
)
Sending Messages
// Simple send
resp, err := conv.Send(ctx, "What's the weather?")
fmt.Println(resp.Text())
// Multi-turn conversations (context is maintained)
resp1, _ := conv.Send(ctx, "My name is Alice")
resp2, _ := conv.Send(ctx, "What's my name?") // "Alice"
Template Variables
// Set variables for prompt templates
conv.SetVar("user_name", "Alice")
conv.SetVar("context", map[string]any{"role": "admin"})
// Get variables
name := conv.GetVar("user_name")
// Bulk operations
conv.SetVars(map[string]any{
"user_name": "Alice",
"language": "en",
})
Tool Handling
Register handlers that the LLM can call:
conv.OnTool("get_weather", func(args map[string]any) (any, error) {
city := args["city"].(string)
// Return any JSON-serializable value
return map[string]any{
"city": city,
"temperature": 22.5,
"conditions": "Sunny",
}, nil
})
// The LLM can now call this tool
resp, _ := conv.Send(ctx, "What's the weather in London?")
HTTP Tools
For external API calls:
import "github.com/AltairaLabs/PromptKit/sdk/tools"
conv.OnToolHTTP("stock_price", &tools.HTTPToolConfig{
BaseURL: "https://api.stocks.example.com",
Method: "GET",
Path: "/v1/price",
Headers: map[string]string{"Authorization": "Bearer " + apiKey},
})
Streaming
Real-time response streaming:
// Channel-based streaming
for chunk := range conv.Stream(ctx, "Tell me a story") {
if chunk.Error != nil {
log.Printf("Error: %v", chunk.Error)
break
}
if chunk.Type == sdk.ChunkDone {
break
}
fmt.Print(chunk.Text)
}
Human-in-the-Loop (HITL)
Approval workflows for sensitive operations:
import "github.com/AltairaLabs/PromptKit/sdk/tools"
conv.OnToolAsync(
"process_refund",
// Check if approval is needed
func(args map[string]any) tools.PendingResult {
amount := args["amount"].(float64)
if amount > 100 {
return tools.PendingResult{
Reason: "high_value",
Message: fmt.Sprintf("$%.2f refund requires approval", amount),
}
}
return tools.PendingResult{} // Auto-approve
},
// Execute after approval
func(args map[string]any) (any, error) {
return map[string]any{"status": "completed"}, nil
},
)
// Handle pending approvals
resp, _ := conv.Send(ctx, "Refund $150 for order #123")
for _, pending := range resp.PendingTools() {
fmt.Printf("Pending: %s - %s\n", pending.Name, pending.Message)
// Approve or reject
result, _ := conv.ResolveTool(pending.ID) // Approve
// result, _ := conv.RejectTool(pending.ID, "Not authorized") // Reject
}
Observability
Monitor events with hooks:
import "github.com/AltairaLabs/PromptKit/sdk/hooks"
// Subscribe to events
conv.Subscribe(hooks.EventSend, func(e hooks.Event) {
fmt.Printf("Sending: %s\n", e.Data["message"])
})
conv.Subscribe(hooks.EventToolCall, func(e hooks.Event) {
fmt.Printf("Tool called: %s\n", e.Data["tool"])
})
Error Handling
resp, err := conv.Send(ctx, input)
if err != nil {
switch {
case errors.Is(err, sdk.ErrPackNotFound):
// Pack file doesn't exist
case errors.Is(err, sdk.ErrPromptNotFound):
// Prompt ID not in pack
case errors.Is(err, sdk.ErrProviderError):
// LLM provider error
case errors.Is(err, sdk.ErrToolNotRegistered):
// Tool handler missing
default:
log.Printf("Unexpected error: %v", err)
}
}
Examples
Working examples are available in the sdk/examples/ directory:
- hello - Basic conversation in 5 lines
- tools - Tool registration and execution
- streaming - Real-time response streaming
- hitl - Human-in-the-loop approval workflows
Migration from v1
If youβre upgrading from SDK v1, see the Migration Guide for detailed before/after examples.
Key changes:
NewConversationManager()βsdk.Open()SendMessage()βSend()SendMessageStream()βStream()- Manual tool registration β
OnTool()
Getting Help
- Migration Guide: SDK Migration Guide
- Questions: GitHub Discussions
- Issues: Report a Bug
- Examples: SDK Examples
Related Tools
- Arena: Test prompts before using them
- PackC: Compile prompts into packs
- Runtime: Extend the SDK with custom providers
- Complete Workflow: See all tools together
Was this page helpful?