Installation
go get github.com/raindrop-ai/go
import raindrop "github.com/raindrop-ai/go"
client, err := raindrop.New(
raindrop.WithWriteKey("rk_..."),
)
if err != nil {
log.Fatal(err)
}
defer client.Close()
Source code and releases live in raindrop-ai/go.
Quick Start: Interaction API
The Interaction API uses a simple three-step pattern:
Begin() – Create an interaction and log the initial user input
- Update – Optionally call
SetProperty, SetProperties, SetInput, or AddAttachments
Finish() – Record the AI’s final output and close the interaction
Example: Chat Completion
package main
import (
"context"
"log"
raindrop "github.com/raindrop-ai/go"
)
func main() {
client, err := raindrop.New(
raindrop.WithWriteKey("rk_..."),
)
if err != nil {
log.Fatal(err)
}
defer client.Close()
ctx := context.Background()
// 1. Start the interaction
interaction := client.Begin(ctx, raindrop.BeginOptions{
EventID: "evt_123",
UserID: "user-123",
Event: "chat_message",
Input: "Can you suggest a calm Saturday morning in San Francisco?",
Model: "gpt-4o",
ConvoID: "conv-123",
Properties: map[string]any{
"system_prompt": "you are a helpful...",
},
})
// 2. Make the LLM call
output := callLLM()
// 3. Finish the interaction
_ = interaction.Finish(raindrop.FinishOptions{
Output: output,
})
}
Updating an Interaction
Update an interaction at any point using SetProperty, SetProperties, SetInput, or AddAttachments:
_ = interaction.SetProperty("stage", "retrieving")
_ = interaction.SetProperties(map[string]any{
"surface": "chat",
"region": "us-west",
})
_ = interaction.SetInput("Can you make it a little more local?")
_ = interaction.AddAttachments([]raindrop.Attachment{{
Type: "text",
Role: "input",
Name: "preferences",
Value: "Prefers coffee, a quiet walk, and no museum stops.",
}})
Resuming an Interaction
If you no longer have the interaction object returned from Begin(), resume it with ResumeInteraction():
interaction := client.ResumeInteraction("evt_123")
_ = interaction.SetProperty("stage", "follow-up")
_ = interaction.Finish(raindrop.FinishOptions{
Output: "Here is a shorter version of that itinerary.",
})
ResumeInteraction() recovers an active in-memory interaction created by Begin() in the same process. It is not a cross-process restore mechanism. If the event ID is not found in memory, a new interaction handle is created for that ID.
Single-Shot Tracking (TrackAI)
For simple request-response interactions, you can use TrackAI() directly:
_ = client.TrackAI(ctx, raindrop.AIEvent{
UserID: "user-123",
Event: "chat_message",
Input: "Who won the 2023 AFL Grand Final?",
Output: "Collingwood by four points!",
Model: "gpt-4o",
ConvoID: "conv-123",
Properties: map[string]any{
"ai.usage.prompt_tokens": 10,
"ai.usage.completion_tokens": 5,
},
})
We recommend using Begin() → Finish() for new code to take advantage of partial-event buffering, tracing, and upcoming features like automatic token counts.
Use TrackEvent() for non-AI events:
_ = client.TrackEvent(ctx, raindrop.Event{
UserID: "user-123",
Event: "session_started",
Properties: map[string]any{
"entrypoint": "dashboard",
},
})
Tracking Signals (Feedback)
Signals capture quality ratings on AI events. Use TrackSignal() with the same event ID from Begin() or TrackAI():
| Parameter | Type | Description |
|---|
EventID | string | The ID of the AI event you’re evaluating |
Name | string | Signal name (e.g. "thumbs_up", "thumbs_down") |
Type | string | "default", "feedback", "edit", "agent", etc. Defaults to "default" |
Sentiment | string | "POSITIVE" or "NEGATIVE" |
Properties | map[string]any | Additional metadata |
AttachmentID | string | Optional attachment ID to associate the signal with |
_ = client.TrackSignal(ctx, raindrop.Signal{
EventID: "evt_123",
Name: "thumbs_down",
Type: "feedback",
Sentiment: "NEGATIVE",
Properties: map[string]any{
"comment": "Answer was off-topic",
},
})
Identifying Users
_ = client.Identify(ctx, raindrop.User{
UserID: "user-123",
Traits: map[string]any{
"name": "Jane",
"email": "jane@example.com",
"plan": "paid", // we recommend "free", "paid", "trial"
},
})
Attachments
Attachments let you include additional context—documents, images, code, or embedded content—with your events. They work with both Begin() interactions and TrackAI() calls.
| Property | Type | Description |
|---|
Type | string | "code", "text", "image", or "iframe" |
Name | string | Optional display name |
Value | string | Content or URL |
Role | string | "input" or "output" |
Language | string | Programming language (for code attachments) |
_ = interaction.AddAttachments([]raindrop.Attachment{
{
Type: "code",
Role: "input",
Language: "go",
Name: "example.go",
Value: "fmt.Println(\"hello\")",
},
{
Type: "text",
Name: "Additional Info",
Value: "Some extra text",
Role: "input",
},
{Type: "image", Value: "https://example.com/image.png", Role: "output"},
{Type: "iframe", Value: "https://example.com/embed", Role: "output"},
})
Configuration
client, err := raindrop.New(
raindrop.WithWriteKey("rk_..."),
raindrop.WithDebug(os.Getenv("ENV") != "production"),
)
| Option | Description | Default |
|---|
WithWriteKey(string) | Your Raindrop API key (required) | — |
WithDebug(bool) | Print debug logs to the configured logger | false |
WithEndpoint(string) | Override the API endpoint | Raindrop API |
WithHTTPClient(*http.Client) | Use a custom HTTP client | 15s timeout |
WithLogger(*slog.Logger) | Use a custom slog.Logger | slog.Default() |
WithServiceName(string) | Override the service name in traces | "raindrop.go-sdk" |
WithServiceVersion(string) | Override the service version in traces | SDK version |
WithPartialFlushInterval(time.Duration) | How often to flush buffered events | 1s |
WithTraceFlushInterval(time.Duration) | How often to flush buffered spans | 1s |
WithTraceBatchSize(int) | Max spans per trace export batch | 50 |
WithTraceQueueSize(int) | Max spans held in the queue | 5000 |
Call client.Close() before your process exits to flush any buffered events and spans. If writeKey is omitted, the client becomes a no-op instead of failing.
Tracing
Tracing captures detailed execution information from your AI pipelines—multi-model interactions, chained prompts, and tool calls. This helps you:
- Visualize the full execution flow of your AI application
- Debug and optimize prompt chains
- Understand the intermediate steps that led to a response
Using WithSpan
Use WithSpan to trace tasks or operations:
// Basic span
err := interaction.WithSpan(
raindrop.SpanOptions{Name: "generate_response"},
func(ctx context.Context, span *raindrop.Span) error {
// LLM calls or other work here
return nil
},
)
// Span with metadata
err := interaction.WithSpan(
raindrop.SpanOptions{
Name: "embedding_generation",
Properties: map[string]any{"model": "text-embedding-3-large"},
},
func(ctx context.Context, span *raindrop.Span) error {
if span != nil {
span.SetAttributes(raindrop.StringAttr("ai.model.id", "text-embedding-3-large"))
}
return nil
},
)
| Parameter | Type | Description |
|---|
Name | string | Name for identification in traces |
Properties | map[string]any | Additional metadata |
Attributes | []Attribute | OTLP attributes |
Use WithTool to trace agent tool calls with automatic input/output capture:
result, err := raindrop.WithTool(interaction, "search_tool", raindrop.ToolOptions{
Input: map[string]any{"query": "best coffee near Dolores Park"},
}, func() (map[string]any, error) {
return map[string]any{"winner": "Ritual Coffee Roasters"}, nil
})
WithTool is a generic function—the return type matches your callback’s return type.
For more control over tool span tracking, use TrackTool or StartToolSpan.
Use TrackTool to log a tool call after it has completed:
interaction := client.Begin(ctx, raindrop.BeginOptions{
EventID: "evt_123",
Event: "agent_run",
UserID: "user-123",
Input: "Search for weather data",
})
// Log a completed tool call
interaction.TrackTool(raindrop.TrackToolOptions{
Name: "web_search",
Input: map[string]any{"query": "weather in NYC"},
Output: map[string]any{"results": []string{"Sunny, 72°F", "Clear skies"}},
Duration: 150 * time.Millisecond,
Properties: map[string]any{"engine": "google"},
})
// Log a failed tool call
interaction.TrackTool(raindrop.TrackToolOptions{
Name: "database_query",
Input: map[string]any{"query": "SELECT * FROM users"},
Duration: 50 * time.Millisecond,
Error: fmt.Errorf("connection timeout"),
})
_ = interaction.Finish(raindrop.FinishOptions{Output: "Weather search complete"})
| Parameter | Type | Description |
|---|
Name | string | Name of the tool |
Input | any | Input passed to the tool |
Output | any | Output returned by the tool |
Duration | time.Duration | Duration of the tool call |
StartTime | time.Time | When the tool started (defaults to now - Duration) |
EndTime | time.Time | When the tool ended |
Error | error | Error if the tool failed |
Properties | map[string]any | Additional metadata |
Use StartToolSpan to track a tool as it executes:
toolSpan := interaction.StartToolSpan("api_call", raindrop.ToolOptions{
Input: map[string]any{"method": "GET", "path": "/api/data"},
Properties: map[string]any{"endpoint": "/api/data"},
})
result, err := fetchData()
if err != nil {
toolSpan.SetError(err)
} else {
toolSpan.SetOutput(result)
}
toolSpan.End()
| Method | Description |
|---|
SetInput(any) | Set the input (JSON stringified if object) |
SetOutput(any) | Set the output (JSON stringified if object) |
SetError(error) | Mark the span as failed |
End() | End the span (required when done) |
Standalone Tracer
Use Tracer() for batch jobs or non-conversation work where you still want spans and tool traces:
tracer := client.Tracer(map[string]any{"job_id": "batch-123"})
_ = tracer.WithSpan(raindrop.SpanOptions{
Name: "offline_enrichment",
Properties: map[string]any{"step": "embed"},
}, func(ctx context.Context, span *raindrop.Span) error {
if span != nil {
span.SetAttributes(raindrop.StringAttr("job.kind", "offline"))
}
return nil
})
tracer.TrackTool(raindrop.TrackToolOptions{
Name: "vector_lookup",
Input: map[string]any{"query": "mission coffee"},
Output: map[string]any{"winner": "Ritual Coffee Roasters"},
Properties: map[string]any{"step": "retrieve"},
})
Span Attributes
The SDK provides helpers for creating OTLP-compatible attributes:
span.SetAttributes(
raindrop.StringAttr("ai.model.id", "gpt-4o"),
raindrop.IntAttr("ai.usage.prompt_tokens", 150),
raindrop.FloatAttr("ai.latency_seconds", 1.23),
raindrop.BoolAttr("ai.stream", true),
raindrop.StringSliceAttr("ai.tools", []string{"search", "calculator"}),
)
That’s it! You’re ready to explore your events in the Raindrop dashboard. Ping us on Slack or email us if you get stuck!