Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,34 @@ micro run

Use `micro mcp serve` for local AI tools like Claude Code, or connect any MCP-compatible agent to the HTTP endpoint.

### micro chat

For an interactive terminal session that lets you talk to your services through an LLM:

```bash
ANTHROPIC_API_KEY=sk-ant-... micro chat --provider anthropic
> list all users
> create an order for product 42
```

`micro chat` discovers every service in the registry, exposes each endpoint as a tool, and lets the model orchestrate calls. The same building blocks (`ai/tools`) work from your own services:

```go
import "go-micro.dev/v5/ai/tools"

set := tools.New(service.Registry())
discovered, _ := set.Discover()

m := ai.New("anthropic",
ai.WithAPIKey(key),
ai.WithToolHandler(set.Handler(service.Client())),
)
resp, _ := m.Generate(ctx, &ai.Request{
Prompt: userInput,
Tools: discovered,
})
```

See the [MCP guide](https://go-micro.dev/docs/mcp.html) for authentication, scopes, and advanced usage.

## Multi-Service Binaries
Expand Down Expand Up @@ -247,6 +275,7 @@ Every service gets `Client()`, `Server()`, and `Model()` — call services, hand

Check out [/examples](examples/) for runnable code:
- [hello-world](examples/hello-world/) - Basic RPC service
- [grpc-interop](examples/grpc-interop/) - Call go-micro from any gRPC client
- [web-service](examples/web-service/) - HTTP REST API
- [multi-service](examples/multi-service/) - Multiple services in one binary
- [mcp](examples/mcp/) - MCP integration with AI agents
Expand Down Expand Up @@ -396,7 +425,11 @@ Go Micro’s `ai` package gives every provider the same interface: `Init`, `Gene
| Provider | Import | Default Model |
|----------|--------|---------------|
| **Anthropic** | `go-micro.dev/v5/ai/anthropic` | `claude-sonnet-4-20250514` |
| **Google Gemini** | `go-micro.dev/v5/ai/gemini` | `gemini-2.5-flash` |
| **Groq** | `go-micro.dev/v5/ai/groq` | `llama-3.3-70b-versatile` |
| **Mistral** | `go-micro.dev/v5/ai/mistral` | `mistral-large-latest` |
| **OpenAI** | `go-micro.dev/v5/ai/openai` | `gpt-4o` |
| **Together AI** | `go-micro.dev/v5/ai/together` | `Llama-3.3-70B-Instruct-Turbo` |
| **Atlas Cloud** | `go-micro.dev/v5/ai/atlascloud` | `llama-3.3-70b` |

Any provider that exposes an OpenAI-compatible API can also be used directly:
Expand Down
56 changes: 56 additions & 0 deletions ai/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,62 @@ m := ai.New("openai",
Default model: `gpt-4o`
Default base URL: `https://api.openai.com`

### Google Gemini

```go
m := ai.New("gemini",
ai.WithAPIKey("your-key"),
ai.WithModel("gemini-2.5-flash"), // default
)
```

Default model: `gemini-2.5-flash`
Default base URL: `https://generativelanguage.googleapis.com`

Google Gemini uses its own API format with `system_instruction`, `contents` (not `messages`), and `functionDeclarations` for tool calling. The provider handles the translation automatically.

### Groq

```go
m := ai.New("groq",
ai.WithAPIKey("your-key"),
ai.WithModel("llama-3.3-70b-versatile"), // default
)
```

Default model: `llama-3.3-70b-versatile`
Default base URL: `https://api.groq.com/openai`

Groq provides ultra-fast inference for open-weight models via an OpenAI-compatible endpoint.

### Mistral

```go
m := ai.New("mistral",
ai.WithAPIKey("your-key"),
ai.WithModel("mistral-large-latest"), // default
)
```

Default model: `mistral-large-latest`
Default base URL: `https://api.mistral.ai`

Mistral AI is a European AI company offering high-performance models via an OpenAI-compatible endpoint.

### Together AI

```go
m := ai.New("together",
ai.WithAPIKey("your-key"),
ai.WithModel("meta-llama/Llama-3.3-70B-Instruct-Turbo"), // default
)
```

Default model: `meta-llama/Llama-3.3-70B-Instruct-Turbo`
Default base URL: `https://api.together.xyz`

Together AI provides fast inference for open-weight models via an OpenAI-compatible endpoint.

### Atlas Cloud

```go
Expand Down
226 changes: 226 additions & 0 deletions ai/gemini/gemini.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
// Package gemini implements the Google Gemini model provider.
//
// Usage:
//
// import _ "go-micro.dev/v5/ai/gemini"
//
// m := ai.New("gemini",
// ai.WithAPIKey("your-api-key"),
// )
package gemini

import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"strings"

"go-micro.dev/v5/ai"
)

func init() {
ai.Register("gemini", func(opts ...ai.Option) ai.Model {
return NewProvider(opts...)
})
}

// Provider implements the ai.Model interface for Google Gemini.
type Provider struct {
opts ai.Options
}

// NewProvider creates a new Gemini provider.
func NewProvider(opts ...ai.Option) *Provider {
options := ai.NewOptions(opts...)

if options.Model == "" {
options.Model = "gemini-2.5-flash"
}
if options.BaseURL == "" {
options.BaseURL = "https://generativelanguage.googleapis.com"
}

return &Provider{opts: options}
}

func (p *Provider) Init(opts ...ai.Option) error {
for _, o := range opts {
o(&p.opts)
}
return nil
}

func (p *Provider) Options() ai.Options { return p.opts }
func (p *Provider) String() string { return "gemini" }

func (p *Provider) Generate(ctx context.Context, req *ai.Request, opts ...ai.GenerateOption) (*ai.Response, error) {
var tools []map[string]any
for _, t := range req.Tools {
tools = append(tools, map[string]any{
"name": t.Name,
"description": t.Description,
"parameters": map[string]any{
"type": "object",
"properties": t.Properties,
},
})
}

contents := []map[string]any{
{"role": "user", "parts": []map[string]any{{"text": req.Prompt}}},
}

apiReq := map[string]any{
"contents": contents,
}

if req.SystemPrompt != "" {
apiReq["system_instruction"] = map[string]any{
"parts": []map[string]any{{"text": req.SystemPrompt}},
}
}

if len(tools) > 0 {
apiReq["tools"] = []map[string]any{
{"functionDeclarations": tools},
}
}

resp, rawParts, err := p.callAPI(ctx, apiReq)
if err != nil {
return nil, err
}

if len(resp.ToolCalls) == 0 {
return resp, nil
}

if p.opts.ToolHandler != nil {
var resultParts []map[string]any
for _, tc := range resp.ToolCalls {
result, _ := p.opts.ToolHandler(tc.Name, tc.Input)
resultParts = append(resultParts, map[string]any{
"functionResponse": map[string]any{
"name": tc.Name,
"id": tc.ID,
"response": result,
},
})
}

followUpContents := append(contents,
map[string]any{"role": "model", "parts": rawParts},
map[string]any{"role": "user", "parts": resultParts},
)

followUpReq := map[string]any{
"contents": followUpContents,
}
if req.SystemPrompt != "" {
followUpReq["system_instruction"] = map[string]any{
"parts": []map[string]any{{"text": req.SystemPrompt}},
}
}

followUpResp, _, err := p.callAPI(ctx, followUpReq)
if err == nil && followUpResp.Reply != "" {
resp.Answer = followUpResp.Reply
}
}

return resp, nil
}

func (p *Provider) Stream(ctx context.Context, req *ai.Request, opts ...ai.GenerateOption) (ai.Stream, error) {
return nil, fmt.Errorf("streaming not yet implemented for gemini provider")
}

func (p *Provider) callAPI(ctx context.Context, req map[string]any) (*ai.Response, []map[string]any, error) {
reqBody, err := json.Marshal(req)
if err != nil {
return nil, nil, fmt.Errorf("failed to marshal request: %w", err)
}

apiURL := strings.TrimRight(p.opts.BaseURL, "/") +
"/v1beta/models/" + p.opts.Model + ":generateContent"

httpReq, err := http.NewRequestWithContext(ctx, "POST", apiURL, bytes.NewReader(reqBody))
if err != nil {
return nil, nil, fmt.Errorf("failed to create request: %w", err)
}

httpReq.Header.Set("Content-Type", "application/json")
httpReq.Header.Set("x-goog-api-key", p.opts.APIKey)

httpResp, err := http.DefaultClient.Do(httpReq)
if err != nil {
return nil, nil, fmt.Errorf("API request failed: %w", err)
}
defer httpResp.Body.Close()

respBody, _ := io.ReadAll(httpResp.Body)
if httpResp.StatusCode != 200 {
return nil, nil, fmt.Errorf("API error (%s): %s", httpResp.Status, string(respBody))
}

var geminiResp struct {
Candidates []struct {
Content struct {
Parts []struct {
Text string `json:"text"`
FunctionCall *functionCallPB `json:"functionCall"`
} `json:"parts"`
} `json:"content"`
} `json:"candidates"`
}

if err := json.Unmarshal(respBody, &geminiResp); err != nil {
return nil, nil, fmt.Errorf("failed to parse response: %w", err)
}

if len(geminiResp.Candidates) == 0 {
return nil, nil, fmt.Errorf("no response from API")
}

parts := geminiResp.Candidates[0].Content.Parts
response := &ai.Response{}

var replyParts []string
var rawParts []map[string]any

for _, part := range parts {
if part.Text != "" {
replyParts = append(replyParts, part.Text)
rawParts = append(rawParts, map[string]any{"text": part.Text})
}
if part.FunctionCall != nil {
response.ToolCalls = append(response.ToolCalls, ai.ToolCall{
ID: part.FunctionCall.ID,
Name: part.FunctionCall.Name,
Input: part.FunctionCall.Args,
})
rawParts = append(rawParts, map[string]any{
"functionCall": map[string]any{
"id": part.FunctionCall.ID,
"name": part.FunctionCall.Name,
"args": part.FunctionCall.Args,
},
})
}
}

if len(replyParts) > 0 {
response.Reply = strings.Join(replyParts, "\n")
}

return response, rawParts, nil
}

type functionCallPB struct {
ID string `json:"id"`
Name string `json:"name"`
Args map[string]any `json:"args"`
}
Loading
Loading