Go SDK
The 2Signal Go SDK instruments your Go AI agents with automatic tracing, token counting, and cost tracking. Go 1.21+. Uses context.Context for propagation and goroutines for background export.
Installation
go get github.com/BryceKeeler720/2Signal/sdks/goQuick Start
package main
import (
"context"
"fmt"
twosignal "github.com/BryceKeeler720/2Signal/sdks/go"
)
func main() {
client := twosignal.New(twosignal.Options{
APIKey: "ts_...", // or set TWOSIGNAL_API_KEY env var
})
defer client.Shutdown()
ctx := context.Background()
result, err := twosignal.Observe(ctx, "support-agent", twosignal.SpanTypeAgent, "What is your refund policy?",
func(ctx context.Context) (string, error) {
// your agent logic here
return "Our refund policy is...", nil
},
)
if err != nil {
fmt.Println("Error:", err)
}
fmt.Println(result)
}Initialization
client := twosignal.New(twosignal.Options{
APIKey: "ts_...", // or TWOSIGNAL_API_KEY env var
BaseURL: "https://...", // or TWOSIGNAL_BASE_URL env var
Enabled: true, // set false to disable all tracing
FlushInterval: time.Second, // duration between batch flushes
MaxBatchSize: 100, // max events per batch
})
// the constructor sets the global instance automatically
instance := twosignal.GetInstance()The SDK is a singleton. It starts a background goroutine that batches and flushes events. Call Shutdown() before your process exits to flush remaining events.
Configuration
| Field | Env Var | Default | Description |
|---|---|---|---|
APIKey | TWOSIGNAL_API_KEY | — | Your project API key |
BaseURL | TWOSIGNAL_BASE_URL | http://localhost:3000 | API endpoint |
Enabled | — | true | Enable/disable all tracing |
FlushInterval | — | 1s | Duration between background flushes |
MaxBatchSize | — | 100 | Max events per HTTP request |
Creating Spans
span := client.Span(twosignal.SpanOpts{
Name: "retrieve-docs",
SpanType: twosignal.SpanTypeRetrieval,
Input: map[string]interface{}{"query": query},
Metadata: map[string]interface{}{"db": "pinecone", "topK": 5},
})
err := span.Run(ctx, func(ctx context.Context, s *twosignal.Span) error {
docs, err := vectorDB.Search(ctx, query)
if err != nil {
return err // span automatically marked as ERROR
}
s.Output = docs
s.Metadata = map[string]interface{}{"resultCount": len(docs)}
return nil
})Fields you can set on the span inside Run():
| Field | Type | Description |
|---|---|---|
s.Output | interface | Span output (any JSON-serializable value) |
s.Metadata | map[string]interface | Arbitrary metadata |
s.Model | string | Model name (for LLM spans) |
s.ModelParameters | map[string]interface | Temperature, max_tokens, etc. |
s.Usage | *SpanUsage | PromptTokens, CompletionTokens, TotalTokens |
s.Cost | *float64 | Cost in USD |
s.Status | SpanStatus | StatusOK or StatusError |
s.ErrorMessage | string | Error description |
If Run() returns an error, the span is automatically marked with StatusError and the error message is recorded. The error is still returned to the caller.
Observe Wrapper
Observe is a generic function that wraps any function call in a span:
result, err := twosignal.Observe(ctx, "classify", twosignal.SpanTypeTool, inputData,
func(ctx context.Context) (Classification, error) {
return classifier.Classify(ctx, inputData)
},
)
// result is typed as ClassificationThe span is created, the function runs inside it, and the result is set as the span's output. If the function returns an error, the span is marked as ERROR and the error is returned.
Context Propagation
The SDK uses standard Go context.Context to propagate trace information. Nested spans automatically become children of their parent:
outer := client.Span(twosignal.SpanOpts{Name: "outer", SpanType: twosignal.SpanTypeAgent})
err := outer.Run(ctx, func(ctx context.Context, s *twosignal.Span) error {
// ctx now carries the trace context
// this span automatically becomes a child of "outer"
inner := client.Span(twosignal.SpanOpts{Name: "inner", SpanType: twosignal.SpanTypeTool})
return inner.Run(ctx, func(ctx context.Context, s *twosignal.Span) error {
// nested correctly — same trace ID, parent set automatically
return nil
})
})Span Types
| Constant | Value | Use for |
|---|---|---|
SpanTypeAgent | AGENT | Top-level agent function |
SpanTypeLLM | LLM | LLM API calls |
SpanTypeTool | TOOL | Tool / function calls |
SpanTypeRetrieval | RETRIEVAL | RAG / vector search |
SpanTypeChain | CHAIN | Pipeline steps |
SpanTypeCustom | CUSTOM | Everything else (default) |
Cost Calculation
cost := twosignal.CalculateCost("gpt-4o", 100, 50)
// returns float64 (-1 if model not in pricing table)Supports OpenAI, Anthropic, Google Gemini, Mistral, Cohere, and Groq models.
Manual LLM Span Example
Since the Go SDK doesn't include provider wrappers, create LLM spans manually:
span := client.Span(twosignal.SpanOpts{
Name: "openai-chat",
SpanType: twosignal.SpanTypeLLM,
Input: messages,
})
err := span.Run(ctx, func(ctx context.Context, s *twosignal.Span) error {
resp, err := openaiClient.CreateChatCompletion(ctx, openai.ChatCompletionRequest{
Model: "gpt-4o",
Messages: messages,
})
if err != nil {
return err
}
s.Model = "gpt-4o"
s.Output = resp.Choices[0].Message.Content
s.Usage = &twosignal.SpanUsage{
PromptTokens: resp.Usage.PromptTokens,
CompletionTokens: resp.Usage.CompletionTokens,
TotalTokens: resp.Usage.TotalTokens,
}
cost := twosignal.CalculateCost("gpt-4o", resp.Usage.PromptTokens, resp.Usage.CompletionTokens)
if cost >= 0 {
s.Cost = &cost
}
return nil
})Lifecycle
// force-flush all pending events
client.Flush()
// graceful shutdown — flushes and stops the background goroutine (5s timeout)
client.Shutdown()Always defer client.Shutdown() after creating the client to ensure events are flushed before exit.