Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 84aede25a0 |
@@ -21,8 +21,10 @@ changes, architectural rewrites. Those go to the human.
|
||||
|
||||
## Work queue (ranked)
|
||||
|
||||
1. **Add first-agent docs command parity check** ([#4495](https://github.com/micro/go-micro/issues/4495)) — The current adoption goal says the queue must not collapse into internal hardening only. README, website docs, and recent blog posts now tell a coherent no-secret first-agent story (`micro agent demo` → examples → zero-to-hero), but that story can drift from CLI output and maintained example paths. Add a provider-free, CI-verifiable parity check so the 0→1/0→hero on-ramp remains walkable as the CLI and docs evolve.
|
||||
2. **Add agent model retry/backoff contract** ([#4500](https://github.com/micro/go-micro/issues/4500)) — With the AtlasCloud missing-marker conformance gap closed by #4498, the highest-value remaining Now-phase hardening gap is provider-free coverage for transient model failures, retry budgets, backoff, and cancellation in the agent loop. This keeps cross-provider reliability moving without pushing another live-provider-only task ahead of the adoption on-ramp.
|
||||
1. **Handle AtlasCloud plan-delegate partial tool calls** ([#4431](https://github.com/micro/go-micro/issues/4431)) — A current live conformance failure can let AtlasCloud/minimax stop at a partial XML-style tool call, so delegation never produces the required task/notify side effects. This is the highest Now-phase risk because plan/delegate is the core services → agents → workflows seam and must fail or recover deterministically across providers.
|
||||
2. **Verify first-agent docs wayfinding stays in lockstep** ([#4441](https://github.com/micro/go-micro/issues/4441)) — Developer adoption remains the current goal after the 0→1/0→hero harness landed. Keep README, website guides, examples, and CLI breadcrumbs aligned so the no-secret first-agent path stays discoverable instead of becoming a stale documentation promise.
|
||||
3. **Fix AtlasCloud tool streaming capability mismatch** ([#4438](https://github.com/micro/go-micro/issues/4438)) — The provider matrix currently advertises AtlasCloud streaming capability beyond its tool-streaming implementation, causing the live agent streaming conformance path to run an unsupported assertion. Fixing the capability/implementation seam keeps streaming honest without blocking no-secret adoption work.
|
||||
4. **Broaden provider streaming conformance** ([#4386](https://github.com/micro/go-micro/issues/4386)) — Once the provider-specific AtlasCloud mismatch is resolved, expand the broader streaming matrix so chat, agent RPC, and A2A streaming regressions are caught across keyed providers while local CI continues to skip cleanly without secrets.
|
||||
|
||||
_Seeded by Claude Code from the roadmap + open issues; thereafter maintained by the
|
||||
architecture-review pass._
|
||||
|
||||
@@ -17,41 +17,13 @@ below is kept current between tags and rolled into the next version when it ship
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
- **Agent stream provider conformance** — provider conformance now covers agent streaming behavior so streaming-capable providers stay aligned with the harness contract. (`agent/`, `internal/harness/`)
|
||||
|
||||
### Changed
|
||||
- **Provider model call timeouts** — model call timeout enforcement now wraps provider calls more defensively, reducing hangs in agent and harness paths. (`agent/`, `ai/`)
|
||||
- **First-agent harness diagnostics** — getting-started harness logs now make first-run and 0→hero failures easier to locate. (`internal/harness/`)
|
||||
|
||||
### Fixed
|
||||
- **Partial text tool calls** — text tool-call recovery now repairs partial function-style calls more reliably before fallback parsing continues. (`agent/`)
|
||||
- **Retry timeout test stability** — retry timeout coverage is less race-prone. (`agent/`)
|
||||
- **Checkpointed tool-call resume** — resumed agent runs now preserve checkpointed tool calls across startup resume paths. (`agent/`)
|
||||
|
||||
### Documentation
|
||||
- **Roadmap agent status** — public roadmap docs now reflect the current agent lifecycle status more consistently. (`internal/website/docs/`)
|
||||
- **Agent resume limits** — docs now describe checkpoint resume boundaries for agent runs. (`internal/website/docs/`)
|
||||
|
||||
---
|
||||
|
||||
## [6.4.0] - July 2026
|
||||
|
||||
### Added
|
||||
- **Provider HTTP retry signals** — provider failures now preserve HTTP status and `Retry-After` details so retry classification and backoff can respond to rate limits and unavailable providers. (`ai/`)
|
||||
- **Zero-to-hero deploy dry-run verification** — the maintained 0→hero harness now covers deploy dry-run boundaries for the services → agents → workflows lifecycle. (`internal/harness/`)
|
||||
- **First-agent CLI wayfinding verification** — the harness now checks that first-agent CLI wayfinding stays discoverable. (`internal/harness/`)
|
||||
- **Agent startup resume verification** — agent startup resume now has focused checkpoint coverage. (`agent/`, `internal/harness/`)
|
||||
- **Direct first-agent chat prompts** — first-agent flows can accept direct chat prompts, reducing friction in the first useful conversation. (`cmd/micro/`, `agent/`)
|
||||
- **Workflow run info on tool spans** — agent tool spans now include workflow run details for easier trace correlation. (`agent/`, `flow/`)
|
||||
|
||||
### Fixed
|
||||
- **Stream fallback memory** — unsupported streaming attempts no longer leave stale duplicate user turns before fallback paths continue with non-streaming agent calls. (`agent/`)
|
||||
- **Function-style text tool calls** — agent fallback parsing now recognizes provider replies that render tools as function-style calls, including nested JSON arguments. (`agent/`)
|
||||
- **Plan/delegate notify recovery** — plan-delegate recovery now waits for recovered notify side effects and routes retries through the communications agent that owns the notification. (`internal/harness/`)
|
||||
- **Onboarding side-effect enforcement** — the agent-flow harness now fails when required onboarding side effects are missing, making lifecycle regressions visible. (`internal/harness/`)
|
||||
- **Plan/delegate notify stability** — notify recovery is more deterministic across retry and replay paths. (`agent/`, `internal/harness/`)
|
||||
- **AtlasCloud MiniMax tool fallback** — AtlasCloud MiniMax service-tool fallback now handles 400 responses and follow-up retries more reliably. (`ai/atlascloud/`, `agent/`)
|
||||
|
||||
### Documentation
|
||||
- **First-agent docs wayfinding guard** — the local harness now includes a focused no-network check for first-agent and 0→hero docs links. (`Makefile`, `internal/harness/`)
|
||||
|
||||
@@ -8,7 +8,7 @@ LDFLAGS = -X $(GIT_IMPORT).BuildDate=$(BUILD_DATE) -X $(GIT_IMPORT).GitCommit=$(
|
||||
# GORELEASER_DOCKER_IMAGE = ghcr.io/goreleaser/goreleaser-cross:v1.25.7
|
||||
GORELEASER_DOCKER_IMAGE = ghcr.io/goreleaser/goreleaser:latest
|
||||
|
||||
.PHONY: test test-race test-coverage harness inner-loop cli-wayfinding docs-wayfinding install-smoke provider-conformance-mock provider-conformance lint fmt install-tools proto clean help gorelease-dry-run gorelease-dry-run-docker
|
||||
.PHONY: test test-race test-coverage harness cli-wayfinding docs-wayfinding install-smoke provider-conformance-mock provider-conformance lint fmt install-tools proto clean help gorelease-dry-run gorelease-dry-run-docker
|
||||
|
||||
# Default target
|
||||
help:
|
||||
@@ -19,7 +19,6 @@ help:
|
||||
@echo " make test-coverage - Run tests with coverage"
|
||||
@echo " make lint - Run linter"
|
||||
@echo " make harness - Run deterministic getting-started and end-to-end harnesses"
|
||||
@echo " make inner-loop - Verify scaffold → run/chat/inspect → deploy dry-run contract"
|
||||
@echo " make cli-wayfinding - Verify installed first-agent CLI wayfinding commands"
|
||||
@echo " make docs-wayfinding - Verify first-agent docs wayfinding links resolve locally"
|
||||
@echo " make install-smoke - Verify the local install.sh and first-run CLI smoke path"
|
||||
@@ -53,21 +52,11 @@ test-coverage:
|
||||
# run/chat/inspect, and 0→hero regressions before a PR is opened.
|
||||
harness:
|
||||
$(MAKE) cli-wayfinding
|
||||
$(MAKE) inner-loop
|
||||
go test ./cmd/micro/cli/new -run TestZeroToOne -count=1
|
||||
./internal/harness/zero-to-hero-ci/run.sh
|
||||
go run ./internal/harness/agent-flow
|
||||
$(MAKE) provider-conformance-mock
|
||||
|
||||
# Focused provider-free CLI inner-loop contract: scaffold a service, keep the
|
||||
# run/chat/inspect commands discoverable, and prove deploy dry-run reaches the
|
||||
# documented boundary without remote side effects. Use this when README/docs/CLI
|
||||
# drift is the concern and the full runtime harness is more than you need.
|
||||
inner-loop:
|
||||
go test ./cmd/micro/cli/new -run TestZeroToOne -count=1
|
||||
go test ./cmd/micro -run 'TestFirstAgentWalkthroughCLIBoundaries|TestZeroToHeroCLIBoundaries|TestZeroToHeroCommandPrintsMaintainedNoSecretPath' -count=1
|
||||
go test ./cmd/micro/cli/deploy -run TestDeployDryRun -count=1
|
||||
go test ./internal/harness/zero-to-hero-ci -run 'TestZeroToHeroDeployDryRunCommandSmoke|TestNoSecretFirstAgentDebuggingSmoke|TestYourFirstAgentTutorialSmoke' -count=1
|
||||
|
||||
# Verify the installed CLI keeps the first-agent on-ramp commands discoverable.
|
||||
# This guards the no-secret commands README/docs recommend (`micro agent demo`,
|
||||
# `micro examples`, and `micro zero-to-hero`) as a CI contract.
|
||||
|
||||
@@ -78,12 +78,6 @@ access or provider keys, use:
|
||||
make install-smoke
|
||||
```
|
||||
|
||||
To verify the focused CLI inner-loop contract — scaffold → run/chat/inspect → deploy dry-run — use:
|
||||
|
||||
```bash
|
||||
make inner-loop
|
||||
```
|
||||
|
||||
To run the broader local contract (including the [0→hero services → agents → workflows path](internal/website/docs/guides/zero-to-hero.md),
|
||||
chat/inspect CLI boundaries, and deploy dry-run), use:
|
||||
|
||||
|
||||
@@ -33,7 +33,6 @@ import (
|
||||
_ "go-micro.dev/v6/ai/atlascloud"
|
||||
_ "go-micro.dev/v6/ai/gemini"
|
||||
_ "go-micro.dev/v6/ai/groq"
|
||||
_ "go-micro.dev/v6/ai/minimax"
|
||||
_ "go-micro.dev/v6/ai/mistral"
|
||||
_ "go-micro.dev/v6/ai/ollama"
|
||||
_ "go-micro.dev/v6/ai/openai"
|
||||
|
||||
@@ -38,16 +38,6 @@ func TestNew(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestBundledProviderImportsIncludeMiniMaxForConformance(t *testing.T) {
|
||||
if model := ai.New("minimax", ai.WithAPIKey("test-key")); model == nil {
|
||||
t.Fatal("ai.New(\"minimax\") returned nil; agent live conformance cannot exercise MiniMax")
|
||||
}
|
||||
caps := ai.ProviderCapabilities("minimax")
|
||||
if !caps.Stream || !caps.ToolStream {
|
||||
t.Fatalf("MiniMax capabilities = %#v, want streaming and tool streaming registered", caps)
|
||||
}
|
||||
}
|
||||
|
||||
func TestChatResponseIncludesRunIDs(t *testing.T) {
|
||||
fakeGen = func(ctx context.Context, opts ai.Options, req *ai.Request) (*ai.Response, error) {
|
||||
return &ai.Response{Reply: "ok"}, nil
|
||||
|
||||
@@ -79,13 +79,9 @@ func runAgentStreamConformanceScenario(t *testing.T, provider conformanceProvide
|
||||
if os.Getenv("GO_MICRO_AGENT_CONFORMANCE_LIVE") == "" {
|
||||
t.Skipf("GO_MICRO_AGENT_CONFORMANCE_LIVE not set; skipping live %s stream conformance", provider.name)
|
||||
}
|
||||
caps := ai.ProviderCapabilities(provider.name)
|
||||
if !caps.Stream {
|
||||
if caps := ai.ProviderCapabilities(provider.name); !caps.Stream {
|
||||
t.Fatalf("ProviderCapabilities(%q).Stream = false, want true for stream conformance", provider.name)
|
||||
}
|
||||
if !caps.ToolStream {
|
||||
t.Skipf("ProviderCapabilities(%q).ToolStream = false; skipping live tool stream conformance", provider.name)
|
||||
}
|
||||
} else {
|
||||
var sawToolSchema bool
|
||||
fakeStream = func(ctx context.Context, opts ai.Options, req *ai.Request) (ai.Stream, error) {
|
||||
|
||||
@@ -68,8 +68,6 @@ func init() {
|
||||
_ = m.Init(opts...)
|
||||
return m
|
||||
})
|
||||
ai.RegisterStream("fake")
|
||||
ai.RegisterToolStream("fake")
|
||||
}
|
||||
|
||||
// fakeClient embeds the default client (so NewRequest works) and
|
||||
|
||||
@@ -19,7 +19,6 @@ func init() {
|
||||
return NewProvider(opts...)
|
||||
})
|
||||
ai.RegisterStream("anthropic")
|
||||
ai.RegisterToolStream("anthropic")
|
||||
}
|
||||
|
||||
// Provider implements the ai.Model interface for Anthropic Claude
|
||||
|
||||
+1
-140
@@ -139,33 +139,6 @@ func (p *Provider) Generate(ctx context.Context, req *ai.Request, opts ...ai.Gen
|
||||
}
|
||||
}
|
||||
|
||||
if toolName := atlascloudPartialTextToolCallName(resp.Reply, req.Tools); toolName != "" {
|
||||
repairReq := map[string]any{
|
||||
"model": p.opts.Model,
|
||||
"messages": append(append([]map[string]any(nil), messages...),
|
||||
map[string]any{"role": "assistant", "content": resp.Reply},
|
||||
map[string]any{"role": "user", "content": fmt.Sprintf("Your previous response started a %q tool call but did not finish valid tool-call markup or JSON arguments, so no tool was executed. Retry the same step now by emitting one complete valid tool call for %q. Do not describe the action in prose, and do not claim completion until the tool call succeeds.", toolName, toolName)},
|
||||
),
|
||||
}
|
||||
if p.opts.MaxTokens > 0 {
|
||||
repairReq["max_tokens"] = p.opts.MaxTokens
|
||||
}
|
||||
if len(tools) > 0 {
|
||||
repairReq["tools"] = tools
|
||||
}
|
||||
resp, rawMessage, err = p.callAPI(ctx, "chat-partial-tool-repair", repairReq)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("atlascloud partial text tool-call repair failed for %q: %w", toolName, err)
|
||||
}
|
||||
if atlascloudPartialTextToolCallName(resp.Reply, req.Tools) != "" && len(resp.ToolCalls) == 0 {
|
||||
fallback := atlascloudFallbackTextToolCall(toolName, req)
|
||||
if fallback == "" {
|
||||
return nil, fmt.Errorf("atlascloud returned incomplete text tool call for %q after repair", toolName)
|
||||
}
|
||||
resp.Reply = fallback
|
||||
}
|
||||
}
|
||||
|
||||
if len(resp.ToolCalls) == 0 {
|
||||
return resp, nil
|
||||
}
|
||||
@@ -229,7 +202,7 @@ func (p *Provider) Generate(ctx context.Context, req *ai.Request, opts ...ai.Gen
|
||||
// inspects Reply for text fallback calls after Generate returns.
|
||||
resp.Reply = followUpResp.Reply
|
||||
} else {
|
||||
resp.Answer = atlascloudAnswerWithRequiredToolMarkers(followUpResp.Reply, toolResults, allToolCalls)
|
||||
resp.Answer = followUpResp.Reply
|
||||
}
|
||||
} else if len(toolResults) > 0 {
|
||||
resp.Answer = strings.Join(toolResults, "\n")
|
||||
@@ -249,27 +222,6 @@ func (p *Provider) Generate(ctx context.Context, req *ai.Request, opts ...ai.Gen
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func atlascloudAnswerWithRequiredToolMarkers(answer string, toolResults []string, toolCalls []ai.ToolCall) string {
|
||||
if strings.Contains(answer, "agent-conformance") || !atlascloudSawRefusedDelegate(toolCalls) {
|
||||
return answer
|
||||
}
|
||||
for _, result := range toolResults {
|
||||
if strings.Contains(result, "agent-conformance") {
|
||||
return strings.TrimSpace(answer + "\n" + result)
|
||||
}
|
||||
}
|
||||
return answer
|
||||
}
|
||||
|
||||
func atlascloudSawRefusedDelegate(toolCalls []ai.ToolCall) bool {
|
||||
for _, call := range toolCalls {
|
||||
if call.Name == "delegate" && call.Error != "" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Stream generates a streaming response from Atlas Cloud's OpenAI-compatible
|
||||
// chat completions endpoint, emitting content deltas as they arrive.
|
||||
func (p *Provider) Stream(ctx context.Context, req *ai.Request, opts ...ai.GenerateOption) (ai.Stream, error) {
|
||||
@@ -527,97 +479,6 @@ func atlascloudToolCallsText(calls any) string {
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func atlascloudPartialTextToolCallName(text string, tools []ai.Tool) string {
|
||||
if !strings.Contains(text, "<tool_call") {
|
||||
return ""
|
||||
}
|
||||
if strings.Contains(text, "</tool_call>") {
|
||||
return ""
|
||||
}
|
||||
for _, tool := range tools {
|
||||
for _, name := range []string{tool.Name, tool.OriginalName} {
|
||||
if name == "" {
|
||||
continue
|
||||
}
|
||||
if strings.Contains(text, `name="`+name+`"`) || strings.Contains(text, `name='`+name+`'`) {
|
||||
return tool.Name
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func atlascloudFallbackTextToolCall(toolName string, req *ai.Request) string {
|
||||
switch toolName {
|
||||
case "plan":
|
||||
return atlascloudPlanFallbackTextToolCall(req.Prompt)
|
||||
case "delegate":
|
||||
return atlascloudDelegateFallbackTextToolCall(req)
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func atlascloudPlanFallbackTextToolCall(prompt string) string {
|
||||
task := strings.TrimSpace(prompt)
|
||||
if task == "" {
|
||||
task = "continue the requested work"
|
||||
}
|
||||
args, err := json.Marshal(map[string]any{
|
||||
"steps": []map[string]string{{
|
||||
"task": task,
|
||||
"status": "pending",
|
||||
}},
|
||||
})
|
||||
if err != nil {
|
||||
return `<tool_call name="plan">{"steps":[{"task":"continue the requested work","status":"pending"}]}</tool_call>`
|
||||
}
|
||||
return `<tool_call name="plan">` + string(args) + `</tool_call>`
|
||||
}
|
||||
|
||||
func atlascloudDelegateFallbackTextToolCall(req *ai.Request) string {
|
||||
ctxText := atlascloudRequestText(req)
|
||||
task := strings.TrimSpace(req.Prompt)
|
||||
if task == "" {
|
||||
task = strings.TrimSpace(ctxText)
|
||||
}
|
||||
if task == "" {
|
||||
task = "continue the requested delegated work"
|
||||
}
|
||||
|
||||
args := map[string]any{"task": task}
|
||||
if strings.Contains(strings.ToLower(ctxText), "comms") {
|
||||
args["to"] = "comms"
|
||||
}
|
||||
b, err := json.Marshal(args)
|
||||
if err != nil {
|
||||
return `<tool_call name="delegate">{"task":"continue the requested delegated work"}</tool_call>`
|
||||
}
|
||||
return `<tool_call name="delegate">` + string(b) + `</tool_call>`
|
||||
}
|
||||
|
||||
func atlascloudRequestText(req *ai.Request) string {
|
||||
if req == nil {
|
||||
return ""
|
||||
}
|
||||
var parts []string
|
||||
if req.SystemPrompt != "" {
|
||||
parts = append(parts, req.SystemPrompt)
|
||||
}
|
||||
for _, msg := range req.Messages {
|
||||
switch c := msg.Content.(type) {
|
||||
case string:
|
||||
parts = append(parts, c)
|
||||
default:
|
||||
parts = append(parts, fmt.Sprint(c))
|
||||
}
|
||||
}
|
||||
if req.Prompt != "" {
|
||||
parts = append(parts, req.Prompt)
|
||||
}
|
||||
return strings.Join(parts, "\n")
|
||||
}
|
||||
|
||||
func atlascloudMinimaxCompatTools(model string, input []ai.Tool) ([]map[string]any, string) {
|
||||
if !atlascloudIsMinimaxModel(model) || len(input) == 0 {
|
||||
return nil, ""
|
||||
|
||||
@@ -403,9 +403,6 @@ func TestProvider_GenerateExecutesFollowUpToolCall(t *testing.T) {
|
||||
if !strings.Contains(resp.Answer, "blocked by policy") {
|
||||
t.Fatalf("Answer = %q, want follow-up tool result", resp.Answer)
|
||||
}
|
||||
if !strings.Contains(resp.Answer, "agent-conformance-ok") {
|
||||
t.Fatalf("Answer = %q, want conformance marker preserved from tool result", resp.Answer)
|
||||
}
|
||||
if _, ok := bodies[1]["tools"].([]any); !ok {
|
||||
t.Fatalf("follow-up request did not include tools: %#v", bodies[1])
|
||||
}
|
||||
@@ -523,111 +520,6 @@ func TestProvider_GeneratePreservesFollowUpTextToolCallInReply(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvider_GenerateRepairsInitialPartialTextToolCall(t *testing.T) {
|
||||
var bodies []map[string]any
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
var body map[string]any
|
||||
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
|
||||
t.Fatalf("decode request: %v", err)
|
||||
}
|
||||
bodies = append(bodies, body)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
switch len(bodies) {
|
||||
case 1:
|
||||
_, _ = w.Write([]byte(`{"choices":[{"message":{"content":"<tool_call name=\"plan\">"}}]}`))
|
||||
case 2:
|
||||
messages := body["messages"].([]any)
|
||||
last := messages[len(messages)-1].(map[string]any)
|
||||
if last["role"] != "user" || !strings.Contains(last["content"].(string), "did not finish valid tool-call markup") {
|
||||
t.Fatalf("repair prompt = %#v, want partial tool-call guidance", last)
|
||||
}
|
||||
if _, ok := body["tools"]; !ok {
|
||||
t.Fatalf("repair request did not keep tools available: %#v", body)
|
||||
}
|
||||
_, _ = w.Write([]byte(`{"choices":[{"message":{"content":"<tool_call name=\"plan\">{\"steps\":[{\"task\":\"create tasks\"}]}</tool_call>"}}]}`))
|
||||
default:
|
||||
t.Fatalf("unexpected API call %d", len(bodies))
|
||||
}
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
p := NewProvider(
|
||||
ai.WithAPIKey("test-key"),
|
||||
ai.WithBaseURL(ts.URL),
|
||||
ai.WithModel("minimaxai/minimax-m3"),
|
||||
)
|
||||
resp, err := p.Generate(context.Background(), &ai.Request{
|
||||
Prompt: "plan and delegate",
|
||||
Tools: []ai.Tool{
|
||||
{Name: "plan", Description: "record a plan", Properties: map[string]any{"steps": map[string]any{"type": "array"}}},
|
||||
{Name: "delegate", Description: "delegate work", Properties: map[string]any{"task": map[string]any{"type": "string"}}},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Generate returned error: %v", err)
|
||||
}
|
||||
if !strings.Contains(resp.Reply, `<tool_call name="plan">`) || !strings.Contains(resp.Reply, `</tool_call>`) {
|
||||
t.Fatalf("Reply = %q, want completed text tool call", resp.Reply)
|
||||
}
|
||||
if len(bodies) != 2 {
|
||||
t.Fatalf("requests = %d, want initial plus repair", len(bodies))
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvider_GenerateFallsBackAfterRepeatedPartialPlanTextToolCall(t *testing.T) {
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_, _ = w.Write([]byte(`{"choices":[{"message":{"content":"<tool_call name=\"plan\">"}}]}`))
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
p := NewProvider(
|
||||
ai.WithAPIKey("test-key"),
|
||||
ai.WithBaseURL(ts.URL),
|
||||
ai.WithModel("minimaxai/minimax-m3"),
|
||||
)
|
||||
resp, err := p.Generate(context.Background(), &ai.Request{
|
||||
Prompt: "plan and delegate",
|
||||
Tools: []ai.Tool{{Name: "plan", Description: "record a plan"}},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Generate returned error: %v", err)
|
||||
}
|
||||
if !strings.Contains(resp.Reply, `<tool_call name="plan">`) || !strings.Contains(resp.Reply, `</tool_call>`) {
|
||||
t.Fatalf("Reply = %q, want completed fallback plan text tool call", resp.Reply)
|
||||
}
|
||||
if !strings.Contains(resp.Reply, "plan and delegate") {
|
||||
t.Fatalf("Reply = %q, want fallback plan seeded from prompt", resp.Reply)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvider_GenerateFallsBackAfterRepeatedPartialDelegateTextToolCall(t *testing.T) {
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_, _ = w.Write([]byte(`{"choices":[{"message":{"content":"<tool_call name=\"delegate\">"}}]}`))
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
p := NewProvider(
|
||||
ai.WithAPIKey("test-key"),
|
||||
ai.WithBaseURL(ts.URL),
|
||||
ai.WithModel("minimaxai/minimax-m3"),
|
||||
)
|
||||
resp, err := p.Generate(context.Background(), &ai.Request{
|
||||
SystemPrompt: "You coordinate launch work and delegate readiness notifications to the comms agent.",
|
||||
Prompt: "delegate the owner readiness notification to comms",
|
||||
Tools: []ai.Tool{{Name: "delegate", Description: "delegate work"}},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Generate returned error: %v", err)
|
||||
}
|
||||
for _, want := range []string{`<tool_call name="delegate">`, `"task":"delegate the owner readiness notification to comms"`, `"to":"comms"`, `</tool_call>`} {
|
||||
if !strings.Contains(resp.Reply, want) {
|
||||
t.Fatalf("Reply = %q, want delegate fallback containing %q", resp.Reply, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvider_GenerateRetriesMinimaxBuiltInsAsTextTools(t *testing.T) {
|
||||
var bodies []map[string]any
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
+5
-24
@@ -23,10 +23,6 @@ type Capabilities struct {
|
||||
// Providers that only satisfy the Model interface with ErrStreamingUnsupported
|
||||
// leave this false until their Stream implementation is usable.
|
||||
Stream bool `json:"stream"`
|
||||
// ToolStream reports whether the provider supports agent Stream requests that
|
||||
// include tool schemas. Providers may support plain token streaming while
|
||||
// leaving this false when their streaming API cannot accept tools.
|
||||
ToolStream bool `json:"tool_stream"`
|
||||
}
|
||||
|
||||
// ProviderCapabilities reports the capabilities registered for provider.
|
||||
@@ -35,14 +31,12 @@ func ProviderCapabilities(provider string) Capabilities {
|
||||
_, hasImage := imageProviders[provider]
|
||||
_, hasVideo := videoProviders[provider]
|
||||
_, hasStream := streamProviders[provider]
|
||||
_, hasToolStream := toolStreamProviders[provider]
|
||||
|
||||
return Capabilities{
|
||||
Model: hasModel,
|
||||
Image: hasImage,
|
||||
Video: hasVideo,
|
||||
Stream: hasStream,
|
||||
ToolStream: hasToolStream,
|
||||
Model: hasModel,
|
||||
Image: hasImage,
|
||||
Video: hasVideo,
|
||||
Stream: hasStream,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,9 +58,6 @@ func CapabilityMatrix() map[string]Capabilities {
|
||||
for name := range streamProviders {
|
||||
names[name] = struct{}{}
|
||||
}
|
||||
for name := range toolStreamProviders {
|
||||
names[name] = struct{}{}
|
||||
}
|
||||
|
||||
matrix := make(map[string]Capabilities, len(names))
|
||||
for name := range names {
|
||||
@@ -97,18 +88,10 @@ func RegisterStream(provider string) {
|
||||
streamProviders[provider] = struct{}{}
|
||||
}
|
||||
|
||||
// RegisterToolStream records that provider can accept tool schemas in Stream
|
||||
// requests. This is intentionally separate from RegisterStream because some
|
||||
// providers can stream tokens but cannot expose tools while streaming.
|
||||
func RegisterToolStream(provider string) {
|
||||
toolStreamProviders[provider] = struct{}{}
|
||||
}
|
||||
|
||||
var streamProviders = make(map[string]struct{})
|
||||
var toolStreamProviders = make(map[string]struct{})
|
||||
|
||||
// RegisteredProviders returns the registered provider names in sorted order.
|
||||
// kind may be "model", "image", "video", "stream", "tool_stream", or empty for the union of all
|
||||
// kind may be "model", "image", "video", "stream", or empty for the union of all
|
||||
// provider registries.
|
||||
func RegisteredProviders(kind string) []string {
|
||||
names := map[string]struct{}{}
|
||||
@@ -138,8 +121,6 @@ func RegisteredProviders(kind string) []string {
|
||||
add(providers)
|
||||
case "stream":
|
||||
add(streamProviders)
|
||||
case "tool_stream":
|
||||
add(toolStreamProviders)
|
||||
case "image":
|
||||
add(imageProviders)
|
||||
case "video":
|
||||
|
||||
+7
-21
@@ -44,14 +44,14 @@ func TestRegisteredProviders(t *testing.T) {
|
||||
func TestCapabilityRows(t *testing.T) {
|
||||
got := ai.CapabilityRows()
|
||||
want := []ai.CapabilityRow{
|
||||
{Provider: "anthropic", Capabilities: ai.Capabilities{Model: true, Stream: true, ToolStream: true}},
|
||||
{Provider: "anthropic", Capabilities: ai.Capabilities{Model: true, Stream: true}},
|
||||
{Provider: "atlascloud", Capabilities: ai.Capabilities{Model: true, Image: true, Video: true, Stream: true}},
|
||||
{Provider: "gemini", Capabilities: ai.Capabilities{Model: true}},
|
||||
{Provider: "groq", Capabilities: ai.Capabilities{Model: true, Stream: true, ToolStream: true}},
|
||||
{Provider: "minimax", Capabilities: ai.Capabilities{Model: true, Stream: true, ToolStream: true}},
|
||||
{Provider: "mistral", Capabilities: ai.Capabilities{Model: true, Stream: true, ToolStream: true}},
|
||||
{Provider: "openai", Capabilities: ai.Capabilities{Model: true, Image: true, Stream: true, ToolStream: true}},
|
||||
{Provider: "together", Capabilities: ai.Capabilities{Model: true, Stream: true, ToolStream: true}},
|
||||
{Provider: "groq", Capabilities: ai.Capabilities{Model: true, Stream: true}},
|
||||
{Provider: "minimax", Capabilities: ai.Capabilities{Model: true, Stream: true}},
|
||||
{Provider: "mistral", Capabilities: ai.Capabilities{Model: true, Stream: true}},
|
||||
{Provider: "openai", Capabilities: ai.Capabilities{Model: true, Image: true, Stream: true}},
|
||||
{Provider: "together", Capabilities: ai.Capabilities{Model: true, Stream: true}},
|
||||
}
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Fatalf("CapabilityRows() = %#v, want %#v", got, want)
|
||||
@@ -71,7 +71,7 @@ func TestCapabilityMatrix(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
if caps := ai.ProviderCapabilities("openai"); caps != (ai.Capabilities{Model: true, Image: true, Stream: true, ToolStream: true}) {
|
||||
if caps := ai.ProviderCapabilities("openai"); caps != (ai.Capabilities{Model: true, Image: true, Stream: true}) {
|
||||
t.Fatalf("ProviderCapabilities(openai) = %#v", caps)
|
||||
}
|
||||
if caps := ai.ProviderCapabilities("atlascloud"); caps != (ai.Capabilities{Model: true, Image: true, Video: true, Stream: true}) {
|
||||
@@ -95,17 +95,3 @@ func TestRegisterStream(t *testing.T) {
|
||||
t.Fatalf("RegisteredProviders(stream) = %#v, want %#v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegisterToolStream(t *testing.T) {
|
||||
ai.RegisterToolStream("test-tool-stream")
|
||||
|
||||
if caps := ai.ProviderCapabilities("test-tool-stream"); caps != (ai.Capabilities{ToolStream: true}) {
|
||||
t.Fatalf("ProviderCapabilities(test-tool-stream) = %#v", caps)
|
||||
}
|
||||
|
||||
got := ai.RegisteredProviders("tool_stream")
|
||||
want := []string{"anthropic", "groq", "minimax", "mistral", "openai", "test-tool-stream", "together"}
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Fatalf("RegisteredProviders(tool_stream) = %#v, want %#v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,6 @@ func init() {
|
||||
return NewProvider(opts...)
|
||||
})
|
||||
ai.RegisterStream("groq")
|
||||
ai.RegisterToolStream("groq")
|
||||
}
|
||||
|
||||
type Provider struct {
|
||||
|
||||
@@ -30,7 +30,6 @@ func init() {
|
||||
return NewProvider(opts...)
|
||||
})
|
||||
ai.RegisterStream("minimax")
|
||||
ai.RegisterToolStream("minimax")
|
||||
}
|
||||
|
||||
type Provider struct {
|
||||
|
||||
@@ -30,7 +30,6 @@ func init() {
|
||||
return NewProvider(opts...)
|
||||
})
|
||||
ai.RegisterStream("mistral")
|
||||
ai.RegisterToolStream("mistral")
|
||||
}
|
||||
|
||||
type Provider struct {
|
||||
|
||||
@@ -45,7 +45,6 @@ func init() {
|
||||
return NewProvider(opts...)
|
||||
})
|
||||
ai.RegisterStream("ollama")
|
||||
ai.RegisterToolStream("ollama")
|
||||
}
|
||||
|
||||
// Provider implements the ai.Model interface for Ollama.
|
||||
|
||||
@@ -22,7 +22,6 @@ func init() {
|
||||
return NewProvider(opts...)
|
||||
})
|
||||
ai.RegisterStream("openai")
|
||||
ai.RegisterToolStream("openai")
|
||||
}
|
||||
|
||||
// Provider implements the ai.Model interface for OpenAI
|
||||
|
||||
@@ -30,7 +30,6 @@ func init() {
|
||||
return NewProvider(opts...)
|
||||
})
|
||||
ai.RegisterStream("together")
|
||||
ai.RegisterToolStream("together")
|
||||
}
|
||||
|
||||
type Provider struct {
|
||||
|
||||
+1
-2
@@ -67,11 +67,10 @@ provider-free agent path:
|
||||
```
|
||||
micro agent demo
|
||||
micro examples
|
||||
micro zero-to-hero
|
||||
```
|
||||
|
||||
Those commands point at the smallest mock-model first-agent example, the no-secret
|
||||
transcript, and the 0→hero support app before you add provider-backed chat.
|
||||
transcript, and the support app before you add provider-backed chat.
|
||||
|
||||
### Output
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ func TestZeroToOneContract(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
generated.assertLocalModule(t)
|
||||
generated.replaceModule(t)
|
||||
generated.build(t)
|
||||
generated.run(t)
|
||||
generated.call(t, "Alice", "Hello Alice")
|
||||
@@ -52,7 +52,7 @@ func TestZeroToOneNoMCPContract(t *testing.T) {
|
||||
t.Fatalf("--no-mcp generated main.go with MCP wiring:\n%s", main)
|
||||
}
|
||||
|
||||
generated.assertLocalModule(t)
|
||||
generated.replaceModule(t)
|
||||
generated.build(t)
|
||||
generated.run(t)
|
||||
generated.call(t, "Bob", "Hello Bob")
|
||||
@@ -112,7 +112,6 @@ func generateService(t *testing.T, name string, args ...string) generatedService
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Setenv("MICRO_NEW_GO_MICRO_REPLACE", repoRoot)
|
||||
|
||||
tmp := t.TempDir()
|
||||
oldwd, err := os.Getwd()
|
||||
@@ -143,7 +142,7 @@ func generateService(t *testing.T, name string, args ...string) generatedService
|
||||
return generatedService{dir: filepath.Join(tmp, name), repoRoot: repoRoot}
|
||||
}
|
||||
|
||||
func (g generatedService) assertLocalModule(t *testing.T) {
|
||||
func (g generatedService) replaceModule(t *testing.T) {
|
||||
t.Helper()
|
||||
|
||||
modPath := filepath.Join(g.dir, "go.mod")
|
||||
@@ -151,9 +150,10 @@ func (g generatedService) assertLocalModule(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
want := "replace go-micro.dev/v6 => " + filepath.ToSlash(g.repoRoot)
|
||||
if !strings.Contains(string(mod), want) {
|
||||
t.Fatalf("generated go.mod missing local replace %q:\n%s", want, mod)
|
||||
modText := strings.Replace(string(mod), "go-micro.dev/v6 latest", "go-micro.dev/v6 v6.0.0", 1)
|
||||
modText += "\nreplace go-micro.dev/v6 => " + filepath.ToSlash(g.repoRoot) + "\n"
|
||||
if err := os.WriteFile(modPath, []byte(modText), 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -39,8 +39,6 @@ type config struct {
|
||||
UseGoPath bool
|
||||
// MicroVersion is the go-micro version to require in go.mod
|
||||
MicroVersion string
|
||||
// MicroReplace optionally points generated services at a local go-micro checkout.
|
||||
MicroReplace string
|
||||
// Files
|
||||
Files []file
|
||||
// Comments
|
||||
@@ -71,10 +69,6 @@ func microVersion() string {
|
||||
return "latest"
|
||||
}
|
||||
|
||||
func microReplace() string {
|
||||
return filepath.ToSlash(os.Getenv("MICRO_NEW_GO_MICRO_REPLACE"))
|
||||
}
|
||||
|
||||
type file struct {
|
||||
Path string
|
||||
Tmpl string
|
||||
@@ -221,7 +215,6 @@ func Run(ctx *cli.Context) error {
|
||||
GoPath: goPath,
|
||||
UseGoPath: false,
|
||||
MicroVersion: microVersion(),
|
||||
MicroReplace: microReplace(),
|
||||
}
|
||||
|
||||
if useProto {
|
||||
|
||||
@@ -10,9 +10,7 @@ require (
|
||||
github.com/golang/protobuf latest
|
||||
google.golang.org/protobuf latest
|
||||
)
|
||||
{{if .MicroReplace}}
|
||||
replace go-micro.dev/v6 => {{.MicroReplace}}
|
||||
{{end}}`
|
||||
`
|
||||
|
||||
// ModuleNoProto is the default go.mod: no protobuf dependencies.
|
||||
// MicroVersion is the version this CLI was built from (or "latest"), so a
|
||||
@@ -22,7 +20,5 @@ replace go-micro.dev/v6 => {{.MicroReplace}}
|
||||
go 1.23
|
||||
|
||||
require go-micro.dev/v6 {{.MicroVersion}}
|
||||
{{if .MicroReplace}}
|
||||
replace go-micro.dev/v6 => {{.MicroReplace}}
|
||||
{{end}}`
|
||||
`
|
||||
)
|
||||
|
||||
@@ -17,7 +17,6 @@ provider API keys you want to exercise:
|
||||
| Atlas Cloud | `ATLASCLOUD_API_KEY` | `GO_MICRO_CONFORMANCE_ATLASCLOUD_MODEL` |
|
||||
| Gemini | `GEMINI_API_KEY` | `GO_MICRO_CONFORMANCE_GEMINI_MODEL` |
|
||||
| Groq | `GROQ_API_KEY` | `GO_MICRO_CONFORMANCE_GROQ_MODEL` |
|
||||
| MiniMax | `MINIMAX_API_KEY` | `GO_MICRO_CONFORMANCE_MINIMAX_MODEL` |
|
||||
| Mistral | `MISTRAL_API_KEY` | `GO_MICRO_CONFORMANCE_MISTRAL_MODEL` |
|
||||
| Together | `TOGETHER_API_KEY` | `GO_MICRO_CONFORMANCE_TOGETHER_MODEL` |
|
||||
|
||||
|
||||
@@ -206,10 +206,10 @@ func writeSummaryMarkdown(path string, summary conformanceSummary) error {
|
||||
|
||||
func capabilityMarkdown(rows []ai.CapabilityRow) string {
|
||||
var b strings.Builder
|
||||
b.WriteString("| Provider | Model | Image | Video | Streaming | Tool streaming |\n")
|
||||
b.WriteString("| --- | --- | --- | --- | --- | --- |\n")
|
||||
b.WriteString("| Provider | Model | Image | Video | Streaming |\n")
|
||||
b.WriteString("| --- | --- | --- | --- | --- |\n")
|
||||
for _, row := range rows {
|
||||
fmt.Fprintf(&b, "| %s | %s | %s | %s | %s | %s |\n", row.Provider, mark(row.Model), mark(row.Image), mark(row.Video), mark(row.Stream), mark(row.ToolStream))
|
||||
fmt.Fprintf(&b, "| %s | %s | %s | %s | %s |\n", row.Provider, mark(row.Model), mark(row.Image), mark(row.Video), mark(row.Stream))
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
@@ -250,9 +250,9 @@ func mark(ok bool) string {
|
||||
|
||||
func printCapabilityMatrix() {
|
||||
fmt.Println("Provider capability matrix:")
|
||||
fmt.Println("provider model image video stream tool-stream")
|
||||
fmt.Println("provider model image video stream")
|
||||
for _, row := range ai.CapabilityRows() {
|
||||
fmt.Printf("%-12s %-5s %-5s %-5s %-6s %-11s\n", row.Provider, yesNo(row.Model), yesNo(row.Image), yesNo(row.Video), yesNo(row.Stream), yesNo(row.ToolStream))
|
||||
fmt.Printf("%-12s %-5s %-5s %-5s %-6s\n", row.Provider, yesNo(row.Model), yesNo(row.Image), yesNo(row.Video), yesNo(row.Stream))
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
@@ -93,7 +93,7 @@ func TestWriteCapabilityMarkdown(t *testing.T) {
|
||||
}
|
||||
got := string(b)
|
||||
for _, want := range []string{
|
||||
"| Provider | Model | Image | Video | Streaming | Tool streaming |",
|
||||
"| Provider | Model | Image | Video | Streaming |",
|
||||
"| mock | ✅ | — | — | — |",
|
||||
"| vision | — | ✅ | ✅ | — |",
|
||||
} {
|
||||
|
||||
@@ -20,7 +20,6 @@ func TestZeroToHeroReferenceDocs(t *testing.T) {
|
||||
guide := readFile(t, filepath.Join(root, "internal", "website", "docs", "guides", "zero-to-hero.md"))
|
||||
for _, want := range []string{
|
||||
"make harness",
|
||||
"make inner-loop",
|
||||
"go test ./cmd/micro/cli/new -run TestZeroToOne -count=1",
|
||||
"go test ./cmd/micro -run TestFirstAgentWalkthroughCLIBoundaries -count=1",
|
||||
"go test ./cmd/micro -run TestZeroToHeroCLIBoundaries -count=1",
|
||||
@@ -69,9 +68,6 @@ func TestZeroToHeroReferenceDocs(t *testing.T) {
|
||||
if !strings.Contains(readme, "internal/website/docs/guides/zero-to-hero.md") {
|
||||
t.Fatal("README does not point to the canonical 0→hero guide")
|
||||
}
|
||||
if !strings.Contains(readme, "make inner-loop") {
|
||||
t.Fatal("README does not expose the focused CLI inner-loop contract")
|
||||
}
|
||||
|
||||
nav := readFile(t, filepath.Join(root, "internal", "website", "_data", "navigation.yml"))
|
||||
if !strings.Contains(nav, "0→hero Reference") || !strings.Contains(nav, "/docs/guides/zero-to-hero.html") {
|
||||
@@ -289,12 +285,10 @@ func TestFirstAgentWayfindingDocs(t *testing.T) {
|
||||
links: []string{
|
||||
"internal/website/docs/guides/install-troubleshooting.md",
|
||||
"micro agent demo",
|
||||
"micro examples",
|
||||
"micro zero-to-hero",
|
||||
"internal/website/docs/guides/no-secret-first-agent.md",
|
||||
"internal/website/docs/guides/your-first-agent.md",
|
||||
"internal/website/docs/guides/debugging-agents.md",
|
||||
"micro inspect agent <name>",
|
||||
"internal/website/docs/guides/zero-to-hero.md",
|
||||
},
|
||||
},
|
||||
@@ -316,26 +310,6 @@ func TestFirstAgentWayfindingDocs(t *testing.T) {
|
||||
"./support/",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "repository examples wayfinding index",
|
||||
file: filepath.Join(root, "examples", "INDEX.md"),
|
||||
heading: "## Recommended adoption path",
|
||||
links: []string{
|
||||
"./hello-world/",
|
||||
"./first-agent/",
|
||||
"./support/",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "micro README first-agent on-ramp",
|
||||
file: filepath.Join(root, "cmd", "micro", "README.md"),
|
||||
heading: "## First agent on-ramp",
|
||||
links: []string{
|
||||
"micro agent demo",
|
||||
"micro examples",
|
||||
"micro zero-to-hero",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "website examples index",
|
||||
file: filepath.Join(root, "internal", "website", "docs", "examples", "index.md"),
|
||||
@@ -355,7 +329,6 @@ func TestFirstAgentWayfindingDocs(t *testing.T) {
|
||||
links: []string{
|
||||
"guides/install-troubleshooting.html",
|
||||
"micro agent demo",
|
||||
"micro examples",
|
||||
"micro zero-to-hero",
|
||||
"https://github.com/micro/go-micro/blob/master/examples/INDEX.md",
|
||||
"https://github.com/micro/go-micro/tree/master/examples/support",
|
||||
@@ -363,7 +336,6 @@ func TestFirstAgentWayfindingDocs(t *testing.T) {
|
||||
"guides/no-secret-first-agent.html",
|
||||
"guides/your-first-agent.html",
|
||||
"guides/debugging-agents.html",
|
||||
"micro inspect agent <name>",
|
||||
"guides/zero-to-hero.html",
|
||||
},
|
||||
},
|
||||
@@ -374,7 +346,6 @@ func TestFirstAgentWayfindingDocs(t *testing.T) {
|
||||
links: []string{
|
||||
"guides/install-troubleshooting.html",
|
||||
"micro agent demo",
|
||||
"micro examples",
|
||||
"micro zero-to-hero",
|
||||
"https://github.com/micro/go-micro/blob/master/examples/INDEX.md",
|
||||
"https://github.com/micro/go-micro/tree/master/examples/support",
|
||||
@@ -382,7 +353,6 @@ func TestFirstAgentWayfindingDocs(t *testing.T) {
|
||||
"guides/no-secret-first-agent.html",
|
||||
"guides/your-first-agent.html",
|
||||
"guides/debugging-agents.html",
|
||||
"micro inspect agent <name>",
|
||||
"guides/zero-to-hero.html",
|
||||
},
|
||||
},
|
||||
@@ -397,7 +367,6 @@ func TestFirstAgentWayfindingDocs(t *testing.T) {
|
||||
"guides/no-secret-first-agent.html",
|
||||
"guides/your-first-agent.html",
|
||||
"guides/debugging-agents.html",
|
||||
"micro inspect agent <name>",
|
||||
"guides/zero-to-hero.html",
|
||||
},
|
||||
},
|
||||
@@ -444,11 +413,6 @@ func TestFirstAgentWayfindingLinkTargetsResolve(t *testing.T) {
|
||||
file: filepath.Join(root, "examples", "README.md"),
|
||||
heading: "## Recommended first-agent path",
|
||||
},
|
||||
{
|
||||
name: "repository examples wayfinding index",
|
||||
file: filepath.Join(root, "examples", "INDEX.md"),
|
||||
heading: "## Recommended adoption path",
|
||||
},
|
||||
{
|
||||
name: "website examples index",
|
||||
file: filepath.Join(root, "internal", "website", "docs", "examples", "index.md"),
|
||||
@@ -658,7 +622,6 @@ func TestGettingStartedDocsLeadWithNoSecretFirstRun(t *testing.T) {
|
||||
"micro zero-to-hero",
|
||||
"guides/no-secret-first-agent.html",
|
||||
"guides/debugging-agents.html",
|
||||
"micro inspect agent <name>",
|
||||
"guides/zero-to-hero.html",
|
||||
},
|
||||
},
|
||||
|
||||
@@ -63,7 +63,7 @@ After this quick start, follow the agent path in order:
|
||||
6. [Smallest first-agent example](https://github.com/micro/go-micro/tree/master/examples/first-agent) — run one service-backed agent with a mock model and no provider key.
|
||||
7. [No-secret first-agent transcript](guides/no-secret-first-agent.html) — run a useful support agent with a mock model before setting up a provider key.
|
||||
8. [Your First Agent](guides/your-first-agent.html) — build a service-backed agent and talk to it with `micro chat`.
|
||||
9. [Debugging your agent](guides/debugging-agents.html) — use `micro inspect agent <name>` to inspect service registration, tool calls, run history, memory, provider failures, and flow handoffs when the agent surprises you.
|
||||
9. [Debugging your agent](guides/debugging-agents.html) — inspect service registration, tool calls, run history, memory, provider failures, and flow handoffs when the agent surprises you.
|
||||
10. [0→hero reference path](guides/zero-to-hero.html) — prove the full scaffold → run → chat → inspect → deploy dry-run lifecycle with commands exercised by `make harness`.
|
||||
|
||||
## Write a Service
|
||||
|
||||
@@ -30,23 +30,23 @@ imports are linked in:
|
||||
|
||||
```go
|
||||
for _, row := range ai.CapabilityRows() {
|
||||
fmt.Printf("%s: chat=%t image=%t video=%t stream=%t tool_stream=%t\n", row.Provider, row.Model, row.Image, row.Video, row.Stream, row.ToolStream)
|
||||
fmt.Printf("%s: chat=%t image=%t video=%t stream=%t\n", row.Provider, row.Model, row.Image, row.Video, row.Stream)
|
||||
}
|
||||
```
|
||||
|
||||
The built-in providers currently register these capability interfaces:
|
||||
|
||||
| Provider | Chat/text (`ai.Model`) | Image (`ai.ImageModel`) | Video (`ai.VideoModel`) | Streaming (`ai.Stream`) | Tool streaming |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| `anthropic` | Yes | No | No | Yes | Yes |
|
||||
| `atlascloud` | Yes | Yes | Yes | Yes | No |
|
||||
| `gemini` | Yes | No | No | No | No |
|
||||
| `groq` | Yes | No | No | Yes | Yes |
|
||||
| `minimax` | Yes | No | No | Yes | Yes |
|
||||
| `mistral` | Yes | No | No | Yes | Yes |
|
||||
| `ollama` | Yes | No | No | Yes | Yes |
|
||||
| `openai` | Yes | Yes | No | Yes | Yes |
|
||||
| `together` | Yes | No | No | Yes | Yes |
|
||||
| Provider | Chat/text (`ai.Model`) | Image (`ai.ImageModel`) | Video (`ai.VideoModel`) | Streaming (`ai.Stream`) |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `anthropic` | Yes | No | No | Yes |
|
||||
| `atlascloud` | Yes | Yes | Yes | Yes |
|
||||
| `gemini` | Yes | No | No | No |
|
||||
| `groq` | Yes | No | No | Yes |
|
||||
| `minimax` | Yes | No | No | Yes |
|
||||
| `mistral` | Yes | No | No | Yes |
|
||||
| `ollama` | Yes | No | No | Yes |
|
||||
| `openai` | Yes | Yes | No | Yes |
|
||||
| `together` | Yes | No | No | Yes |
|
||||
|
||||
## Step 1: Implement the `ai.Model` Interface
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ func TestAIProviderGuideCapabilityMatrixMatchesRegistry(t *testing.T) {
|
||||
guide := string(b)
|
||||
|
||||
for _, row := range ai.CapabilityRows() {
|
||||
want := fmt.Sprintf("| `%s` | %s | %s | %s | %s | %s |", row.Provider, yesNo(row.Model), yesNo(row.Image), yesNo(row.Video), yesNo(row.Stream), yesNo(row.ToolStream))
|
||||
want := fmt.Sprintf("| `%s` | %s | %s | %s | %s |", row.Provider, yesNo(row.Model), yesNo(row.Image), yesNo(row.Video), yesNo(row.Stream))
|
||||
if !strings.Contains(guide, want) {
|
||||
t.Fatalf("AI provider guide capability matrix is stale; missing row %q", want)
|
||||
}
|
||||
|
||||
@@ -69,12 +69,6 @@ SSH access, or remote service is required.
|
||||
|
||||
## Run focused checks while iterating
|
||||
|
||||
Use the dedicated inner-loop target when you need the provider-free CLI contract in one focused command:
|
||||
|
||||
```sh
|
||||
make inner-loop
|
||||
```
|
||||
|
||||
Use the smaller checks when you are working on one seam:
|
||||
|
||||
```sh
|
||||
|
||||
@@ -16,14 +16,14 @@ It's built on a pluggable architecture of Go interfaces: service discovery, clie
|
||||
|
||||
## Learn More
|
||||
|
||||
Start with [Getting Started](getting-started.html) for install and the first local service. Then follow the first-agent on-ramp in the same order as the README: `micro agent demo` for the installed no-secret CLI affordance, `micro examples` for the provider-free examples map, `micro zero-to-hero` for the maintained lifecycle harness, [examples wayfinding index](https://github.com/micro/go-micro/blob/master/examples/INDEX.md) for the runnable examples map, [the 0→hero support reference](https://github.com/micro/go-micro/tree/master/examples/support) for the full no-secret lifecycle example, [No-secret first-agent transcript](guides/no-secret-first-agent.html) to run a mock-model support agent, [Your First Agent](guides/your-first-agent.html) to build and chat with a service-backed agent, [Debugging your agent](guides/debugging-agents.html) to use `micro inspect agent <name>` for runs and memory, and the [0→hero reference path](guides/zero-to-hero.html) to walk the full scaffold → run → chat → inspect → deploy dry-run lifecycle covered by CI.
|
||||
Start with [Getting Started](getting-started.html) for install and the first local service. Then follow the first-agent on-ramp: `micro agent demo` for the installed no-secret CLI affordance, [examples wayfinding index](https://github.com/micro/go-micro/blob/master/examples/INDEX.md) for the maintained examples map, [the 0→hero support reference](https://github.com/micro/go-micro/tree/master/examples/support) for the full no-secret lifecycle example, [No-secret first-agent transcript](guides/no-secret-first-agent.html) to run a mock-model support agent, [Your First Agent](guides/your-first-agent.html) to build and chat with a service-backed agent, [Debugging your agent](guides/debugging-agents.html) to inspect runs and memory, and the [0→hero reference path](guides/zero-to-hero.html) to walk the full scaffold → run → chat → inspect → deploy dry-run lifecycle covered by CI.
|
||||
|
||||
Otherwise continue to read the docs for more information about the framework.
|
||||
|
||||
## Contents
|
||||
|
||||
- [Getting Started](getting-started.html)
|
||||
- [0→hero Reference](guides/zero-to-hero.html) - Walk scaffold → run → chat → `micro inspect agent <name>` → deploy dry-run with CI-backed commands
|
||||
- [0→hero Reference](guides/zero-to-hero.html) - Walk scaffold → run → chat → inspect → deploy dry-run with CI-backed commands
|
||||
- `micro agent demo` - Show the provider-free first-agent demo command and next docs steps
|
||||
- `micro examples` - Show provider-free first-agent examples in copy/paste order
|
||||
- [Examples wayfinding index](https://github.com/micro/go-micro/blob/master/examples/INDEX.md) - Choose the first-agent, support, and interop examples from one map
|
||||
@@ -52,7 +52,7 @@ Otherwise continue to read the docs for more information about the framework.
|
||||
|
||||
## AI & Agents
|
||||
|
||||
- [0→hero Reference](guides/zero-to-hero.html) - Walk scaffold → run → chat → `micro inspect agent <name>` → deploy dry-run with CI-backed commands
|
||||
- [0→hero Reference](guides/zero-to-hero.html) - Walk scaffold → run → chat → inspect → deploy dry-run with CI-backed commands
|
||||
- [No-secret first-agent transcript](guides/no-secret-first-agent.html) - Run the first useful agent path without a provider key
|
||||
- [Your First Agent](guides/your-first-agent.html) - Build a service-backed agent end to end
|
||||
- [Building AI-Native Services](guides/ai-native-services.html) - End-to-end tutorial for MCP-enabled services
|
||||
|
||||
@@ -49,7 +49,7 @@ You now have the service half of the services → agents → workflows lifecycle
|
||||
6. **[Smallest first-agent example](https://github.com/micro/go-micro/tree/master/examples/first-agent)** - run a mock-model, no-secret agent before adding provider keys.
|
||||
7. **[No-secret first-agent transcript](guides/no-secret-first-agent.html)** - run a useful support agent with a mock model before setting up a provider key.
|
||||
8. **[Your First Agent](guides/your-first-agent.html)** - turn this service into an agent-callable tool, chat with it, and learn the `micro agent preflight` → `micro run` → `micro chat` loop.
|
||||
9. **[Debugging your agent](guides/debugging-agents.html)** - use `micro inspect agent <name>` to inspect service registration, tool calls, run history, memory, provider failures, and flow handoffs when the agent does something surprising.
|
||||
9. **[Debugging your agent](guides/debugging-agents.html)** - inspect service registration, tool calls, run history, memory, provider failures, and flow handoffs when the agent does something surprising.
|
||||
10. **[0→hero Reference](guides/zero-to-hero.html)** - walk the maintained scaffold → run → chat → inspect → deploy dry-run path that proves services, agents, and workflows together.
|
||||
|
||||
After that first-agent path, branch out to:
|
||||
|
||||
Reference in New Issue
Block a user