{"id":31027189,"url":"https://github.com/rizome-dev/go-openrouter","last_synced_at":"2025-09-13T18:57:18.987Z","repository":{"id":301242617,"uuid":"1003802487","full_name":"rizome-dev/go-openrouter","owner":"rizome-dev","description":"pure go openrouter.ai sdk","archived":false,"fork":false,"pushed_at":"2025-07-08T00:43:54.000Z","size":143,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-07-08T02:30:11.755Z","etag":null,"topics":["ai","openrouter"],"latest_commit_sha":null,"homepage":"https://pkg.go.dev/github.com/rizome-dev/go-openrouter","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/rizome-dev.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-06-17T17:31:20.000Z","updated_at":"2025-07-08T00:43:58.000Z","dependencies_parsed_at":"2025-07-01T01:26:36.506Z","dependency_job_id":"abe31752-b763-48d9-b728-372f9865a51d","html_url":"https://github.com/rizome-dev/go-openrouter","commit_stats":null,"previous_names":["rizome-dev/openroutergo","rizome-dev/go-openrouter"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/rizome-dev/go-openrouter","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rizome-dev%2Fgo-openrouter","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rizome-dev%2Fgo-openrouter/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rizome-dev%2Fgo-openrouter/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rizome-dev%2Fgo-openrouter/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rizome-dev","download_url":"https://codeload.github.com/rizome-dev/go-openrouter/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rizome-dev%2Fgo-openrouter/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":275013271,"owners_count":25390481,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-09-13T02:00:10.085Z","response_time":70,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ai","openrouter"],"created_at":"2025-09-13T18:57:14.957Z","updated_at":"2025-09-13T18:57:18.977Z","avatar_url":"https://github.com/rizome-dev.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# go-openrouter\n\n\n[![GoDoc](https://pkg.go.dev/badge/github.com/rizome-dev/go-openrouter)](https://pkg.go.dev/github.com/rizome-dev/go-openrouter)\n[![Go Report Card](https://goreportcard.com/badge/github.com/rizome-dev/go-openrouter)](https://goreportcard.com/report/github.com/rizome-dev/go-openrouter)\n\n```bash\ngo get github.com/rizome-dev/go-openrouter\n```\n\nbuilt by: [rizome labs](https://rizome.dev)\n\ncontact us: [hi (at) rizome.dev](mailto:hi@rizome.dev)\n\n## Quick Start\n\n```go\npackage main\n\nimport (\n    \"context\"\n    \"fmt\"\n    \"log\"\n    \n    \"github.com/rizome-dev/go-openrouter/pkg\"\n    \"github.com/rizome-dev/go-openrouter/pkg/models\"\n)\n\nfunc main() {\n    // Create a client\n    client := pkg.NewClient(\"your-api-key\",\n        pkg.WithHTTPReferer(\"https://your-app.com\"),\n        pkg.WithXTitle(\"Your App Name\"),\n    )\n\n    resp, err := client.CreateChatCompletion(context.Background(), models.ChatCompletionRequest{\n        Model: \"google/gemini-2.5-pro\", // You can also try \"anthropic/claude-3.5-sonnet\"\n        Messages: []models.Message{\n            models.NewTextMessage(models.RoleSystem, \"You are a helpful assistant.\"),\n            models.NewTextMessage(models.RoleUser, \"What is the capital of France?\"),\n        },\n        Temperature: float64Ptr(0.7),\n        MaxTokens:   intPtr(150), // Be careful with low limits on models that include reasoning\n    })\n\n    if err != nil {\n        log.Fatalf(\"Error creating completion: %v\", err)\n    }\n\n    // Print response\n    fmt.Printf(\"\\nModel used: %s\\n\", resp.Model)\n    if len(resp.Choices) \u003e 0 \u0026\u0026 resp.Choices[0].Message != nil {\n        msg := resp.Choices[0].Message\n        content := getMessageContent(msg)\n        fmt.Printf(\"Response: %s\\n\", content)\n        \n        // Check if reasoning is present (some models like Gemini include this)\n        // Note: When using low max_tokens with models that include reasoning,\n        // the actual content might be truncated in favor of the reasoning field\n        if msg.Reasoning != \"\" {\n            fmt.Printf(\"\\nReasoning: %s\\n\", msg.Reasoning)\n        }\n        \n        // Print finish reason\n        if resp.Choices[0].FinishReason != \"\" {\n            fmt.Printf(\"\\nFinish reason: %s\\n\", resp.Choices[0].FinishReason)\n        }\n    }\n\n    // Print usage if available\n    if resp.Usage != nil {\n        fmt.Printf(\"\\nToken Usage:\\n\")\n        fmt.Printf(\"  Prompt: %d\\n\", resp.Usage.PromptTokens)\n        fmt.Printf(\"  Completion: %d\\n\", resp.Usage.CompletionTokens)\n        fmt.Printf(\"  Total: %d\\n\", resp.Usage.TotalTokens)\n    }\n}\n```\n\n## Advanced Usage\n\n### Streaming Responses\n\n```go\nstream, err := client.CreateChatCompletionStream(ctx, models.ChatCompletionRequest{\n    Model: \"anthropic/claude-3.5-sonnet\",\n    Messages: []models.Message{\n        models.NewTextMessage(models.RoleUser, \"Write a short story\"),\n    },\n})\nif err != nil {\n    log.Fatal(err)\n}\ndefer stream.Close()\n\nfor {\n    chunk, err := stream.Read()\n    if err == io.EOF {\n        break\n    }\n    if err != nil {\n        log.Fatal(err)\n    }\n    \n    if chunk.Choices[0].Delta != nil {\n        content, _ := chunk.Choices[0].Delta.GetTextContent()\n        fmt.Print(content)\n    }\n}\n```\n\n### Tool Calling\n\n```go\n// Define a tool\ntool, _ := models.NewTool(\"search_books\", \n    \"Search for books in Project Gutenberg\",\n    map[string]interface{}{\n        \"type\": \"object\",\n        \"properties\": map[string]interface{}{\n            \"search_terms\": map[string]interface{}{\n                \"type\": \"array\",\n                \"items\": map[string]interface{}{\n                    \"type\": \"string\",\n                },\n                \"description\": \"Search terms to find books\",\n            },\n        },\n        \"required\": []string{\"search_terms\"},\n    },\n)\n\n// Make request with tool\nresp, err := client.CreateChatCompletion(ctx, models.ChatCompletionRequest{\n    Model: \"openai/gpt-4\",\n    Messages: []models.Message{\n        models.NewTextMessage(models.RoleUser, \"Find books by James Joyce\"),\n    },\n    Tools: []models.Tool{*tool},\n    ToolChoice: models.ToolChoiceAuto,\n})\n\n// Handle tool calls\nif resp.Choices[0].Message.ToolCalls != nil {\n    for _, toolCall := range resp.Choices[0].Message.ToolCalls {\n        // Execute your tool logic here\n        result := executeToolCall(toolCall)\n        \n        // Send tool result back\n        messages = append(messages, resp.Choices[0].Message)\n        messages = append(messages, models.NewToolMessage(\n            toolCall.ID,\n            toolCall.Function.Name,\n            result,\n        ))\n    }\n}\n```\n\n### Multi-Modal Inputs\n\n```go\n// With images\nimageMessage, _ := models.NewMultiContentMessage(models.RoleUser,\n    models.TextContent{\n        Type: models.ContentTypeText,\n        Text: \"What's in this image?\",\n    },\n    models.ImageContent{\n        Type: models.ContentTypeImageURL,\n        ImageURL: models.ImageURL{\n            URL: \"https://example.com/image.jpg\",\n        },\n    },\n)\n\n// With base64 encoded images\nimageData := base64.StdEncoding.EncodeToString(imageBytes)\nimageMessage, _ := models.NewMultiContentMessage(models.RoleUser,\n    models.TextContent{\n        Type: models.ContentTypeText,\n        Text: \"Analyze this image\",\n    },\n    models.ImageContent{\n        Type: models.ContentTypeImageURL,\n        ImageURL: models.ImageURL{\n            URL: \"data:image/jpeg;base64,\" + imageData,\n        },\n    },\n)\n\n// With PDFs\npdfData := base64.StdEncoding.EncodeToString(pdfBytes)\npdfMessage, _ := models.NewMultiContentMessage(models.RoleUser,\n    models.TextContent{\n        Type: models.ContentTypeText,\n        Text: \"Summarize this document\",\n    },\n    models.FileContent{\n        Type: models.ContentTypeFile,\n        File: models.File{\n            Filename: \"document.pdf\",\n            FileData: \"data:application/pdf;base64,\" + pdfData,\n        },\n    },\n)\n```\n\n### Provider Routing\n\n```go\n// Use specific providers with fallbacks\nresp, err := client.CreateChatCompletion(ctx, models.ChatCompletionRequest{\n    Model: \"meta-llama/llama-3.1-70b-instruct\",\n    Messages: messages,\n    Provider: models.NewProviderPreferences().\n        WithOrder(\"together\", \"deepinfra\").\n        WithFallbacks(true).\n        WithSort(models.SortByThroughput).\n        WithMaxPrice(1.0, 2.0), // $1/M prompt, $2/M completion\n})\n\n// Use fastest available provider\nresp, err := client.CreateChatCompletion(ctx, models.ChatCompletionRequest{\n    Model: \"meta-llama/llama-3.1-70b-instruct:nitro\",\n    Messages: messages,\n})\n\n// Require specific features\nresp, err := client.CreateChatCompletion(ctx, models.ChatCompletionRequest{\n    Model: \"openai/gpt-4\",\n    Messages: messages,\n    ResponseFormat: \u0026models.ResponseFormat{\n        Type: \"json_object\",\n    },\n    Provider: models.NewProviderPreferences().\n        WithRequireParameters(true), // Only use providers supporting JSON mode\n})\n```\n\n### Structured Outputs\n\n```go\nschema := map[string]interface{}{\n    \"type\": \"object\",\n    \"properties\": map[string]interface{}{\n        \"location\": map[string]interface{}{\n            \"type\": \"string\",\n            \"description\": \"City or location name\",\n        },\n        \"temperature\": map[string]interface{}{\n            \"type\": \"number\",\n            \"description\": \"Temperature in Celsius\",\n        },\n        \"conditions\": map[string]interface{}{\n            \"type\": \"string\",\n            \"description\": \"Weather conditions\",\n        },\n    },\n    \"required\": []string{\"location\", \"temperature\", \"conditions\"},\n    \"additionalProperties\": false,\n}\n\nschemaJSON, _ := json.Marshal(schema)\n\nresp, err := client.CreateChatCompletion(ctx, models.ChatCompletionRequest{\n    Model: \"openai/gpt-4\",\n    Messages: []models.Message{\n        models.NewTextMessage(models.RoleUser, \"What's the weather in London?\"),\n    },\n    ResponseFormat: \u0026models.ResponseFormat{\n        Type: \"json_schema\",\n        JSONSchema: \u0026models.JSONSchema{\n            Name:   \"weather\",\n            Strict: true,\n            Schema: schemaJSON,\n        },\n    },\n})\n```\n\n### Web Search Plugin\n\n```go\n// Enable web search with :online shortcut\nresp, err := client.CreateChatCompletion(ctx, models.ChatCompletionRequest{\n    Model: \"openai/gpt-4:online\",\n    Messages: []models.Message{\n        models.NewTextMessage(models.RoleUser, \"What happened in tech news today?\"),\n    },\n})\n\n// Or with plugin configuration\nresp, err := client.CreateChatCompletion(ctx, models.ChatCompletionRequest{\n    Model: \"openai/gpt-4\",\n    Messages: messages,\n    Plugins: []models.Plugin{\n        *models.NewWebPlugin().\n            WithMaxResults(10).\n            WithSearchPrompt(\"Recent tech news:\"),\n    },\n})\n\n// Access web citations\nfor _, annotation := range resp.Choices[0].Message.Annotations {\n    if annotation.Type == models.AnnotationTypeURLCitation {\n        fmt.Printf(\"Source: %s - %s\\n\", \n            annotation.URLCitation.Title,\n            annotation.URLCitation.URL,\n        )\n    }\n}\n```\n\n## Error Handling\n\n```go\nresp, err := client.CreateChatCompletion(ctx, req)\nif err != nil {\n    if apiErr, ok := err.(*errors.APIError); ok {\n        switch apiErr.Code {\n        case errors.ErrorCodeRateLimited:\n            // Handle rate limiting\n        case errors.ErrorCodeInsufficientCredits:\n            // Handle insufficient credits\n        case errors.ErrorCodeForbidden:\n            // Handle moderation\n            if moderation, ok := apiErr.GetModerationMetadata(); ok {\n                fmt.Printf(\"Flagged for: %v\\n\", moderation.Reasons)\n            }\n        }\n    }\n}\n```\n\n## Configuration Options\n\n### Client Options\n\n- `WithBaseURL(url)` - Use a different API endpoint\n- `WithHTTPClient(client)` - Use a custom HTTP client\n- `WithTimeout(duration)` - Set request timeout\n- `WithHTTPReferer(referer)` - Set referer for rankings\n- `WithXTitle(title)` - Set title for rankings\n- `WithUserAgent(agent)` - Set custom user agent\n\n### Request Parameters\n\nAll standard OpenAI parameters are supported:\n- `temperature`, `top_p`, `top_k`\n- `max_tokens`, `stop`, `seed`\n- `frequency_penalty`, `presence_penalty`, `repetition_penalty`\n- `logit_bias`, `top_logprobs`\n- `min_p`, `top_a`\n\n### OpenRouter-Specific Features\n\n- Model routing with fallbacks\n- Provider preferences and filtering\n- Transform pipelines\n- User tracking for billing\n- Cost and usage tracking\n\n## Examples\n\nSee the `/examples` directory for complete examples:\n- Basic chat completion\n- Streaming responses\n- Tool calling and agents\n- Multi-modal inputs\n- Advanced routing\n- Structured outputs\n\n## Testing\n\n### Running Tests\n\n```bash\n# Run all tests\ngo test ./...\n\n# Run tests with verbose output\ngo test -v ./...\n\n# Run specific test package\ngo test ./pkg/...\n\n# Run E2E tests (requires OPENROUTER_API_KEY)\ncd tests\nchmod +x run_e2e.sh\n./run_e2e.sh\n```\n\n### Test Suite\n\nThe project includes comprehensive test coverage:\n\n**Unit Tests** (`pkg/`):\n- `client_test.go` - Core client functionality\n- `structured_test.go` - Structured output handling\n- `tools_test.go` - Tool calling functionality\n- `api_management_test.go` - API key management\n- `errors_test.go` - Error handling\n- `message_test.go` - Message construction\n- `plugins_test.go` - Plugin system\n- `multimodal_test.go` - Multi-modal content\n- `routing_test.go` - Provider routing\n- `streaming_test.go` - Streaming responses\n- `response_format_test.go` - Response formatting\n- `websearch_test.go` - Web search plugin\n- `retry_test.go` - Retry logic\n\n**End-to-End Tests** (`tests/e2e/`):\n- `e2e_test.go` - Basic API functionality\n- `advanced_test.go` - Advanced features\n- `multimodal_test.go` - Multi-modal inputs\n- `streaming_test.go` - Streaming responses\n- `structured_test.go` - Structured outputs\n- `tools_test.go` - Tool calling\n\n### CI/CD\n\nTests are automatically run on GitHub Actions for all pull requests and pushes to main. The workflow includes:\n- Building the project\n- Running all unit tests\n- Running E2E tests (when API key is available)\n\n## Contributing\n\nContributions are welcome! Please feel free to submit a Pull Request.\n\n## License\n\nThis project is licensed under the MIT License - see the LICENSE file for details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frizome-dev%2Fgo-openrouter","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frizome-dev%2Fgo-openrouter","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frizome-dev%2Fgo-openrouter/lists"}