Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
be841ea
feat: add micro new --prompt and micro run --prompt
claude Jun 3, 2026
b57d3bb
feat: LLM generates real business logic with compile-fix loop
claude Jun 3, 2026
4849011
fix: handle edge cases in prompt-based generation
claude Jun 3, 2026
703d1f6
feat: auto-detect modified handlers on regeneration
claude Jun 3, 2026
18c878b
feat: add tests, fix go.mod, gitignore, proto tracking, spinner
claude Jun 3, 2026
6ee7612
feat: signal handling, existing service discovery, help text
claude Jun 3, 2026
807cc74
feat: show endpoints in run --prompt output, add micro chat hint
claude Jun 3, 2026
c0936f5
fix: generated go.mod uses go 1.24 with explicit go-micro require
claude Jun 3, 2026
ac332cd
fix: skip handler regeneration when proto unchanged
claude Jun 3, 2026
956000e
feat: confirm design before generating code
claude Jun 3, 2026
850837f
fix: use port :0 for MCP in generated multi-service projects
claude Jun 3, 2026
f82047e
feat: truncation detection, tool result display in chat
claude Jun 3, 2026
ce50873
fix: Anthropic tool loop, service naming, chat tool results
claude Jun 3, 2026
cc87e00
feat: blog post 13 — from prompt to production
claude Jun 3, 2026
a437407
fix: timeouts, max_tokens, TTY detection, smaller services
claude Jun 3, 2026
d6576e5
feat: chat suggests creating services when capabilities are missing
claude Jun 3, 2026
ddeab71
feat: chat generates and starts services inline, drop -service suffix
claude Jun 3, 2026
c5f1996
docs: rewrite blog post 13 with inline service generation
claude Jun 3, 2026
a141bdd
feat: persistent storage, README quickstart, auto-detect new services
claude Jun 3, 2026
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
18 changes: 17 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -314,13 +314,29 @@ go install go-micro.dev/v5/cmd/micro@v5.16.0

```bash
micro new helloworld # Create a new service
micro new contacts --template crud # Or use a template (crud, pubsub, api)
cd helloworld
micro run # Run with API gateway and hot reload
```

Then open http://localhost:8080 to see your service and call it from the browser.

### Generate From a Prompt

Describe what you need in plain English. The AI designs services, writes handlers with real business logic, compiles them, and starts them:

```bash
micro run --prompt "a task management system with categories" --provider anthropic
```

Then talk to your services through an agent:

```bash
micro chat --provider anthropic
> Create a Work category, then add a task called 'Finish report' to it
```

The agent orchestrates across services automatically. When you need a capability that doesn't exist, the agent generates a new service mid-conversation. [Read more](https://go-micro.dev/blog/13).

### Development Workflow

| Stage | Command | Purpose |
Expand Down
97 changes: 71 additions & 26 deletions ai/anthropic/anthropic.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ func (p *Provider) Generate(ctx context.Context, req *ai.Request, opts ...ai.Gen
// Build initial request
apiReq := map[string]any{
"model": p.opts.Model,
"max_tokens": 4096,
"max_tokens": 8192,
"system": req.SystemPrompt,
"messages": []map[string]any{
{"role": "user", "content": req.Prompt},
Expand All @@ -94,48 +94,66 @@ func (p *Provider) Generate(ctx context.Context, req *ai.Request, opts ...ai.Gen
return nil, err
}

// If no tool calls, return response
if len(resp.ToolCalls) == 0 {
// If no tool calls or no handler, return as-is
if len(resp.ToolCalls) == 0 || p.opts.ToolHandler == nil {
return resp, nil
}

// If tool handler is provided, execute tools and get final answer
if p.opts.ToolHandler != nil {
var toolResults []ai.ToolResult
for _, tc := range resp.ToolCalls {
_, content := p.opts.ToolHandler(tc.Name, tc.Input)
toolResults = append(toolResults, ai.ToolResult{
ID: tc.ID,
Content: content,
})
}
// Tool execution loop: execute tools, send results back, repeat
// until the model responds with text only (no more tool calls)
messages := []map[string]any{
{"role": "user", "content": req.Prompt},
{"role": "assistant", "content": cleanContent(rawContent)},
}

// Build follow-up request with tool results
pendingCalls := resp.ToolCalls

for rounds := 0; rounds < 10; rounds++ {
var toolResultBlocks []map[string]any
for _, tr := range toolResults {
for i := range pendingCalls {
_, content := p.opts.ToolHandler(pendingCalls[i].Name, pendingCalls[i].Input)
pendingCalls[i].Result = content
toolResultBlocks = append(toolResultBlocks, map[string]any{
"type": "tool_result",
"tool_use_id": tr.ID,
"content": tr.Content,
"tool_use_id": pendingCalls[i].ID,
"content": content,
})
}

messages = append(messages, map[string]any{
"role": "user",
"content": toolResultBlocks,
})

followUpReq := map[string]any{
"model": p.opts.Model,
"max_tokens": 4096,
"max_tokens": 8192,
"system": req.SystemPrompt,
"messages": []map[string]any{
{"role": "user", "content": req.Prompt},
{"role": "assistant", "content": rawContent},
{"role": "user", "content": toolResultBlocks},
},
"messages": messages,
}
if len(anthropicTools) > 0 {
followUpReq["tools"] = anthropicTools
}

followUpResp, followUpRaw, err := p.callAPI(ctx, followUpReq)
if err != nil {
break
}

// Make follow-up API call
followUpResp, _, err := p.callAPI(ctx, followUpReq)
if err == nil && followUpResp.Reply != "" {
if len(followUpResp.ToolCalls) > 0 {
resp.ToolCalls = append(resp.ToolCalls, followUpResp.ToolCalls...)
pendingCalls = followUpResp.ToolCalls
messages = append(messages, map[string]any{
"role": "assistant",
"content": cleanContent(followUpRaw),
})
continue
}

if followUpResp.Reply != "" {
resp.Answer = followUpResp.Reply
}
break
}

return resp, nil
Expand Down Expand Up @@ -225,3 +243,30 @@ func (p *Provider) callAPI(ctx context.Context, req map[string]any) (*ai.Respons

return response, anthropicResp.Content, nil
}

// cleanContent strips fields from response content blocks that Anthropic
// rejects when sent back as assistant message content (e.g. "id" on text blocks).
func cleanContent(raw any) any {
blocks, ok := raw.([]struct {
Type string `json:"type"`
Text string `json:"text"`
ID string `json:"id"`
Name string `json:"name"`
Input json.RawMessage `json:"input"`
})
if !ok {
return raw
}
var cleaned []map[string]any
for _, b := range blocks {
switch b.Type {
case "text":
cleaned = append(cleaned, map[string]any{"type": "text", "text": b.Text})
case "tool_use":
var input any
json.Unmarshal(b.Input, &input)
cleaned = append(cleaned, map[string]any{"type": "tool_use", "id": b.ID, "name": b.Name, "input": input})
}
}
return cleaned
}
10 changes: 6 additions & 4 deletions ai/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,13 @@ type Response struct {
Answer string
}

// ToolCall represents a request to call a tool
// ToolCall represents a request to call a tool and its result
type ToolCall struct {
ID string // Tool call ID (for correlation)
Name string // Tool name
Input map[string]any // Tool input arguments
ID string // Tool call ID (for correlation)
Name string // Tool name
Input map[string]any // Tool input arguments
Result string // Tool execution result (populated after execution)
Error string // Tool execution error (populated after execution)
}

// ToolResult represents the result of a tool execution
Expand Down
Loading
Loading