https://github.com/rizome-dev/go-openrouter
pure go openrouter.ai sdk
https://github.com/rizome-dev/go-openrouter
ai openrouter
Last synced: 9 months ago
JSON representation
pure go openrouter.ai sdk
- Host: GitHub
- URL: https://github.com/rizome-dev/go-openrouter
- Owner: rizome-dev
- License: mit
- Created: 2025-06-17T17:31:20.000Z (about 1 year ago)
- Default Branch: main
- Last Pushed: 2025-07-08T00:43:54.000Z (12 months ago)
- Last Synced: 2025-07-08T02:30:11.755Z (12 months ago)
- Topics: ai, openrouter
- Language: Go
- Homepage: https://pkg.go.dev/github.com/rizome-dev/go-openrouter
- Size: 140 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# go-openrouter
[](https://pkg.go.dev/github.com/rizome-dev/go-openrouter)
[](https://goreportcard.com/report/github.com/rizome-dev/go-openrouter)
```bash
go get github.com/rizome-dev/go-openrouter
```
built by: [rizome labs](https://rizome.dev)
contact us: [hi (at) rizome.dev](mailto:hi@rizome.dev)
## Quick Start
```go
package main
import (
"context"
"fmt"
"log"
"github.com/rizome-dev/go-openrouter/pkg"
"github.com/rizome-dev/go-openrouter/pkg/models"
)
func main() {
// Create a client
client := pkg.NewClient("your-api-key",
pkg.WithHTTPReferer("https://your-app.com"),
pkg.WithXTitle("Your App Name"),
)
resp, err := client.CreateChatCompletion(context.Background(), models.ChatCompletionRequest{
Model: "google/gemini-2.5-pro", // You can also try "anthropic/claude-3.5-sonnet"
Messages: []models.Message{
models.NewTextMessage(models.RoleSystem, "You are a helpful assistant."),
models.NewTextMessage(models.RoleUser, "What is the capital of France?"),
},
Temperature: float64Ptr(0.7),
MaxTokens: intPtr(150), // Be careful with low limits on models that include reasoning
})
if err != nil {
log.Fatalf("Error creating completion: %v", err)
}
// Print response
fmt.Printf("\nModel used: %s\n", resp.Model)
if len(resp.Choices) > 0 && resp.Choices[0].Message != nil {
msg := resp.Choices[0].Message
content := getMessageContent(msg)
fmt.Printf("Response: %s\n", content)
// Check if reasoning is present (some models like Gemini include this)
// Note: When using low max_tokens with models that include reasoning,
// the actual content might be truncated in favor of the reasoning field
if msg.Reasoning != "" {
fmt.Printf("\nReasoning: %s\n", msg.Reasoning)
}
// Print finish reason
if resp.Choices[0].FinishReason != "" {
fmt.Printf("\nFinish reason: %s\n", resp.Choices[0].FinishReason)
}
}
// Print usage if available
if resp.Usage != nil {
fmt.Printf("\nToken Usage:\n")
fmt.Printf(" Prompt: %d\n", resp.Usage.PromptTokens)
fmt.Printf(" Completion: %d\n", resp.Usage.CompletionTokens)
fmt.Printf(" Total: %d\n", resp.Usage.TotalTokens)
}
}
```
## Advanced Usage
### Streaming Responses
```go
stream, err := client.CreateChatCompletionStream(ctx, models.ChatCompletionRequest{
Model: "anthropic/claude-3.5-sonnet",
Messages: []models.Message{
models.NewTextMessage(models.RoleUser, "Write a short story"),
},
})
if err != nil {
log.Fatal(err)
}
defer stream.Close()
for {
chunk, err := stream.Read()
if err == io.EOF {
break
}
if err != nil {
log.Fatal(err)
}
if chunk.Choices[0].Delta != nil {
content, _ := chunk.Choices[0].Delta.GetTextContent()
fmt.Print(content)
}
}
```
### Tool Calling
```go
// Define a tool
tool, _ := models.NewTool("search_books",
"Search for books in Project Gutenberg",
map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"search_terms": map[string]interface{}{
"type": "array",
"items": map[string]interface{}{
"type": "string",
},
"description": "Search terms to find books",
},
},
"required": []string{"search_terms"},
},
)
// Make request with tool
resp, err := client.CreateChatCompletion(ctx, models.ChatCompletionRequest{
Model: "openai/gpt-4",
Messages: []models.Message{
models.NewTextMessage(models.RoleUser, "Find books by James Joyce"),
},
Tools: []models.Tool{*tool},
ToolChoice: models.ToolChoiceAuto,
})
// Handle tool calls
if resp.Choices[0].Message.ToolCalls != nil {
for _, toolCall := range resp.Choices[0].Message.ToolCalls {
// Execute your tool logic here
result := executeToolCall(toolCall)
// Send tool result back
messages = append(messages, resp.Choices[0].Message)
messages = append(messages, models.NewToolMessage(
toolCall.ID,
toolCall.Function.Name,
result,
))
}
}
```
### Multi-Modal Inputs
```go
// With images
imageMessage, _ := models.NewMultiContentMessage(models.RoleUser,
models.TextContent{
Type: models.ContentTypeText,
Text: "What's in this image?",
},
models.ImageContent{
Type: models.ContentTypeImageURL,
ImageURL: models.ImageURL{
URL: "https://example.com/image.jpg",
},
},
)
// With base64 encoded images
imageData := base64.StdEncoding.EncodeToString(imageBytes)
imageMessage, _ := models.NewMultiContentMessage(models.RoleUser,
models.TextContent{
Type: models.ContentTypeText,
Text: "Analyze this image",
},
models.ImageContent{
Type: models.ContentTypeImageURL,
ImageURL: models.ImageURL{
URL: "data:image/jpeg;base64," + imageData,
},
},
)
// With PDFs
pdfData := base64.StdEncoding.EncodeToString(pdfBytes)
pdfMessage, _ := models.NewMultiContentMessage(models.RoleUser,
models.TextContent{
Type: models.ContentTypeText,
Text: "Summarize this document",
},
models.FileContent{
Type: models.ContentTypeFile,
File: models.File{
Filename: "document.pdf",
FileData: "data:application/pdf;base64," + pdfData,
},
},
)
```
### Provider Routing
```go
// Use specific providers with fallbacks
resp, err := client.CreateChatCompletion(ctx, models.ChatCompletionRequest{
Model: "meta-llama/llama-3.1-70b-instruct",
Messages: messages,
Provider: models.NewProviderPreferences().
WithOrder("together", "deepinfra").
WithFallbacks(true).
WithSort(models.SortByThroughput).
WithMaxPrice(1.0, 2.0), // $1/M prompt, $2/M completion
})
// Use fastest available provider
resp, err := client.CreateChatCompletion(ctx, models.ChatCompletionRequest{
Model: "meta-llama/llama-3.1-70b-instruct:nitro",
Messages: messages,
})
// Require specific features
resp, err := client.CreateChatCompletion(ctx, models.ChatCompletionRequest{
Model: "openai/gpt-4",
Messages: messages,
ResponseFormat: &models.ResponseFormat{
Type: "json_object",
},
Provider: models.NewProviderPreferences().
WithRequireParameters(true), // Only use providers supporting JSON mode
})
```
### Structured Outputs
```go
schema := map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"location": map[string]interface{}{
"type": "string",
"description": "City or location name",
},
"temperature": map[string]interface{}{
"type": "number",
"description": "Temperature in Celsius",
},
"conditions": map[string]interface{}{
"type": "string",
"description": "Weather conditions",
},
},
"required": []string{"location", "temperature", "conditions"},
"additionalProperties": false,
}
schemaJSON, _ := json.Marshal(schema)
resp, err := client.CreateChatCompletion(ctx, models.ChatCompletionRequest{
Model: "openai/gpt-4",
Messages: []models.Message{
models.NewTextMessage(models.RoleUser, "What's the weather in London?"),
},
ResponseFormat: &models.ResponseFormat{
Type: "json_schema",
JSONSchema: &models.JSONSchema{
Name: "weather",
Strict: true,
Schema: schemaJSON,
},
},
})
```
### Web Search Plugin
```go
// Enable web search with :online shortcut
resp, err := client.CreateChatCompletion(ctx, models.ChatCompletionRequest{
Model: "openai/gpt-4:online",
Messages: []models.Message{
models.NewTextMessage(models.RoleUser, "What happened in tech news today?"),
},
})
// Or with plugin configuration
resp, err := client.CreateChatCompletion(ctx, models.ChatCompletionRequest{
Model: "openai/gpt-4",
Messages: messages,
Plugins: []models.Plugin{
*models.NewWebPlugin().
WithMaxResults(10).
WithSearchPrompt("Recent tech news:"),
},
})
// Access web citations
for _, annotation := range resp.Choices[0].Message.Annotations {
if annotation.Type == models.AnnotationTypeURLCitation {
fmt.Printf("Source: %s - %s\n",
annotation.URLCitation.Title,
annotation.URLCitation.URL,
)
}
}
```
## Error Handling
```go
resp, err := client.CreateChatCompletion(ctx, req)
if err != nil {
if apiErr, ok := err.(*errors.APIError); ok {
switch apiErr.Code {
case errors.ErrorCodeRateLimited:
// Handle rate limiting
case errors.ErrorCodeInsufficientCredits:
// Handle insufficient credits
case errors.ErrorCodeForbidden:
// Handle moderation
if moderation, ok := apiErr.GetModerationMetadata(); ok {
fmt.Printf("Flagged for: %v\n", moderation.Reasons)
}
}
}
}
```
## Configuration Options
### Client Options
- `WithBaseURL(url)` - Use a different API endpoint
- `WithHTTPClient(client)` - Use a custom HTTP client
- `WithTimeout(duration)` - Set request timeout
- `WithHTTPReferer(referer)` - Set referer for rankings
- `WithXTitle(title)` - Set title for rankings
- `WithUserAgent(agent)` - Set custom user agent
### Request Parameters
All standard OpenAI parameters are supported:
- `temperature`, `top_p`, `top_k`
- `max_tokens`, `stop`, `seed`
- `frequency_penalty`, `presence_penalty`, `repetition_penalty`
- `logit_bias`, `top_logprobs`
- `min_p`, `top_a`
### OpenRouter-Specific Features
- Model routing with fallbacks
- Provider preferences and filtering
- Transform pipelines
- User tracking for billing
- Cost and usage tracking
## Examples
See the `/examples` directory for complete examples:
- Basic chat completion
- Streaming responses
- Tool calling and agents
- Multi-modal inputs
- Advanced routing
- Structured outputs
## Testing
### Running Tests
```bash
# Run all tests
go test ./...
# Run tests with verbose output
go test -v ./...
# Run specific test package
go test ./pkg/...
# Run E2E tests (requires OPENROUTER_API_KEY)
cd tests
chmod +x run_e2e.sh
./run_e2e.sh
```
### Test Suite
The project includes comprehensive test coverage:
**Unit Tests** (`pkg/`):
- `client_test.go` - Core client functionality
- `structured_test.go` - Structured output handling
- `tools_test.go` - Tool calling functionality
- `api_management_test.go` - API key management
- `errors_test.go` - Error handling
- `message_test.go` - Message construction
- `plugins_test.go` - Plugin system
- `multimodal_test.go` - Multi-modal content
- `routing_test.go` - Provider routing
- `streaming_test.go` - Streaming responses
- `response_format_test.go` - Response formatting
- `websearch_test.go` - Web search plugin
- `retry_test.go` - Retry logic
**End-to-End Tests** (`tests/e2e/`):
- `e2e_test.go` - Basic API functionality
- `advanced_test.go` - Advanced features
- `multimodal_test.go` - Multi-modal inputs
- `streaming_test.go` - Streaming responses
- `structured_test.go` - Structured outputs
- `tools_test.go` - Tool calling
### CI/CD
Tests are automatically run on GitHub Actions for all pull requests and pushes to main. The workflow includes:
- Building the project
- Running all unit tests
- Running E2E tests (when API key is available)
## Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
## License
This project is licensed under the MIT License - see the LICENSE file for details.