Files
wehub-resource-sync e071084ebe
govulncheck / govulncheck (push) Has been cancelled
Lint / golangci-lint (push) Has been cancelled
Run Tests / Unit Tests (push) Has been cancelled
Run Tests / Etcd Integration Tests (push) Has been cancelled
Harness (E2E) / Harnesses (mock LLM) (push) Has been cancelled
Harness (E2E) / Provider harnesses (live LLM conformance) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:40:33 +08:00

455 lines
14 KiB
Go

// Provider conformance runs the same end-to-end harnesses across model
// providers whose API keys are configured. Missing keys are skipped so the
// command is safe in local development and scheduled CI; a configured provider
// that fails any harness makes the command fail.
//
// Run all live providers with configured keys:
//
// go run ./internal/harness/provider-conformance
//
// Run the deterministic mock path only:
//
// go run ./internal/harness/provider-conformance -providers mock
package main
import (
"context"
"encoding/json"
"flag"
"fmt"
"os"
"os/exec"
"path/filepath"
"slices"
"strings"
"time"
"go-micro.dev/v6/ai"
_ "go-micro.dev/v6/ai/anthropic"
_ "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/openai"
_ "go-micro.dev/v6/ai/together"
)
const defaultHarnesses = "agent,universe,agent-flow,plan-delegate,a2a-streaming,a2a-stream-fallback"
var harnessPhases = map[string]string{
"agent": "model call + tool call",
"universe": "service discovery + tool call",
"agent-flow": "workflow event + tool call",
"plan-delegate": "plan persistence + delegation + tool call",
"a2a-streaming": "A2A streaming + tool call",
"a2a-stream-fallback": "streaming fallback + tool call",
}
var providerEnv = map[string]string{
"anthropic": "ANTHROPIC_API_KEY",
"openai": "OPENAI_API_KEY",
"gemini": "GEMINI_API_KEY",
"groq": "GROQ_API_KEY",
"minimax": "MINIMAX_API_KEY",
"mistral": "MISTRAL_API_KEY",
"together": "TOGETHER_API_KEY",
"atlascloud": "ATLASCLOUD_API_KEY",
}
func main() {
providersFlag := flag.String("providers", defaultProviders(), "comma-separated providers to check; use mock for deterministic local checks")
harnessesFlag := flag.String("harnesses", defaultHarnesses, "comma-separated harness names under internal/harness; agent runs the provider tool-call conformance test")
timeoutFlag := flag.Duration("timeout", 10*time.Minute, "timeout per provider/harness run")
requireConfiguredFlag := flag.Bool("require-configured", false, "fail when a selected live provider is missing an API key")
capabilitiesFlag := flag.Bool("capabilities", true, "print the registered provider capability matrix before running conformance")
summaryJSONFlag := flag.String("summary-json", "", "write a machine-readable conformance summary to this path")
summaryMarkdownFlag := flag.String("summary-markdown", "", "write a human-readable conformance summary to this path")
capabilityMarkdownFlag := flag.String("capabilities-markdown", "", "write the registered provider capability matrix as a Markdown table")
flag.Parse()
providers := splitCSV(*providersFlag)
harnesses := splitCSV(*harnessesFlag)
if err := validateSelection(providers, harnesses); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(2)
}
if *capabilitiesFlag {
printCapabilityMatrix()
}
if *capabilityMarkdownFlag != "" {
if err := writeCapabilityMarkdown(*capabilityMarkdownFlag, ai.CapabilityRows()); err != nil {
fmt.Fprintf(os.Stderr, "write capabilities markdown: %v\n", err)
os.Exit(1)
}
}
var ran, skipped, failed int
var results []conformanceResult
for _, provider := range providers {
if provider != "mock" && providerKey(provider) == "" {
msg := fmt.Sprintf("set MICRO_AI_API_KEY or %s", providerEnv[provider])
if *requireConfiguredFlag {
fmt.Printf("FAIL %s: missing API key (%s)\n", provider, msg)
failed++
results = append(results, missingKeyResults(provider, harnesses, statusFailed, "missing API key: "+msg)...)
} else {
fmt.Printf("- %s: skipped (%s)\n", provider, msg)
skipped++
results = append(results, missingKeyResults(provider, harnesses, statusSkipped, msg)...)
}
continue
}
for _, harness := range harnesses {
phase := harnessPhase(harness)
fmt.Printf("\n==> %s / %s (%s)\n", provider, harness, phase)
if err := runHarness(provider, harness, *timeoutFlag); err != nil {
fmt.Printf("FAIL %s / %s (%s): %v\n", provider, harness, phase, err)
failed++
results = append(results, conformanceResult{Provider: provider, Harness: harness, Phase: phase, Status: statusFailed, Error: err.Error()})
continue
}
ran++
results = append(results, conformanceResult{Provider: provider, Harness: harness, Phase: phase, Status: statusPassed})
}
}
fmt.Printf("\nprovider conformance: %d passed, %d skipped providers, %d failed\n", ran, skipped, failed)
summary := conformanceSummary{
Providers: providers,
Harnesses: harnesses,
Capabilities: ai.CapabilityRows(),
Results: results,
Passed: ran,
Skipped: skipped,
Failed: failed,
}
if *summaryJSONFlag != "" {
if err := writeSummaryJSON(*summaryJSONFlag, summary); err != nil {
fmt.Fprintf(os.Stderr, "write summary: %v\n", err)
os.Exit(1)
}
}
if *summaryMarkdownFlag != "" {
if err := writeSummaryMarkdown(*summaryMarkdownFlag, summary); err != nil {
fmt.Fprintf(os.Stderr, "write summary markdown: %v\n", err)
os.Exit(1)
}
}
if failed > 0 {
os.Exit(1)
}
}
const (
statusPassed = "passed"
statusSkipped = "skipped"
statusFailed = "failed"
)
type conformanceResult struct {
Provider string `json:"provider"`
Harness string `json:"harness,omitempty"`
Phase string `json:"phase,omitempty"`
Status string `json:"status"`
Error string `json:"error,omitempty"`
}
type conformanceSummary struct {
Providers []string `json:"providers"`
Harnesses []string `json:"harnesses"`
Capabilities []ai.CapabilityRow `json:"capabilities"`
Results []conformanceResult `json:"results"`
Passed int `json:"passed"`
Skipped int `json:"skipped"`
Failed int `json:"failed"`
}
func missingKeyResults(provider string, harnesses []string, status, detail string) []conformanceResult {
results := make([]conformanceResult, 0, len(harnesses))
for _, harness := range harnesses {
results = append(results, conformanceResult{
Provider: provider,
Harness: harness,
Phase: harnessPhase(harness),
Status: status,
Error: detail,
})
}
return results
}
func writeSummaryJSON(path string, summary conformanceSummary) error {
b, err := json.MarshalIndent(summary, "", " ")
if err != nil {
return err
}
b = append(b, '\n')
return os.WriteFile(path, b, 0o644)
}
func writeCapabilityMarkdown(path string, rows []ai.CapabilityRow) error {
return os.WriteFile(path, []byte(capabilityMarkdown(rows)), 0o644)
}
func writeSummaryMarkdown(path string, summary conformanceSummary) error {
var b strings.Builder
b.WriteString("# Provider conformance summary\n\n")
fmt.Fprintf(&b, "Passed: %d. Skipped providers: %d. Failed: %d.\n\n", summary.Passed, summary.Skipped, summary.Failed)
fmt.Fprintf(&b, "Providers: %s.\n\n", markdownList(summary.Providers))
fmt.Fprintf(&b, "Harnesses: %s.\n\n", markdownList(summary.Harnesses))
b.WriteString("## Capability matrix\n\n")
b.WriteString(capabilityMarkdown(summary.Capabilities))
b.WriteString("\n## Harness results\n\n")
b.WriteString("| Provider | Harness | Phase | Status | Detail |\n")
b.WriteString("| --- | --- | --- | --- | --- |\n")
for _, result := range summary.Results {
harness := result.Harness
if harness == "" {
harness = "—"
}
phase := result.Phase
if phase == "" {
phase = "—"
}
fmt.Fprintf(&b, "| %s | %s | %s | %s | %s |\n", result.Provider, harness, markdownCell(phase), result.Status, markdownCell(result.Error))
}
return os.WriteFile(path, []byte(b.String()), 0o644)
}
func capabilityMarkdown(rows []ai.CapabilityRow) string {
var b strings.Builder
b.WriteString("| Provider | Model | Image | Video | Streaming | Tool 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))
}
return b.String()
}
func markdownList(values []string) string {
if len(values) == 0 {
return "—"
}
escaped := make([]string, 0, len(values))
for _, value := range values {
escaped = append(escaped, "`"+strings.ReplaceAll(value, "`", "\\`")+"`")
}
return strings.Join(escaped, ", ")
}
func harnessPhase(harness string) string {
if phase, ok := harnessPhases[harness]; ok {
return phase
}
return "harness"
}
func markdownCell(s string) string {
if s == "" {
return "—"
}
s = strings.ReplaceAll(s, "|", "\\|")
s = strings.ReplaceAll(s, "\n", "<br>")
return s
}
func mark(ok bool) string {
if ok {
return "✅"
}
return "—"
}
func printCapabilityMatrix() {
fmt.Println("Provider capability matrix:")
fmt.Println("provider model image video stream tool-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.Println()
}
func yesNo(ok bool) string {
if ok {
return "yes"
}
return "no"
}
func validateSelection(providers, harnesses []string) error {
if len(providers) == 0 {
return fmt.Errorf("no providers selected")
}
if len(harnesses) == 0 {
return fmt.Errorf("no harnesses selected")
}
for _, provider := range providers {
if provider == "mock" {
continue
}
if _, ok := providerEnv[provider]; !ok {
return fmt.Errorf("unknown provider %q (known: %s)", provider, knownProviders())
}
}
for _, harness := range harnesses {
if harness == "agent" {
continue
}
if strings.Contains(harness, string(os.PathSeparator)) || harness == "." || harness == ".." {
return fmt.Errorf("invalid harness name %q", harness)
}
info, err := os.Stat(filepath.Join(repoRoot(), "internal", "harness", harness))
if err != nil {
return fmt.Errorf("unknown harness %q: %w", harness, err)
}
if !info.IsDir() {
return fmt.Errorf("harness %q is not a directory", harness)
}
}
return nil
}
func repoRoot() string {
wd, err := os.Getwd()
if err != nil {
return "."
}
for {
if _, err := os.Stat(filepath.Join(wd, "go.mod")); err == nil {
return wd
}
parent := filepath.Dir(wd)
if parent == wd {
return "."
}
wd = parent
}
}
func defaultProviders() string {
providers := make([]string, 0, len(providerEnv))
for provider := range providerEnv {
providers = append(providers, provider)
}
slices.Sort(providers)
return strings.Join(providers, ",")
}
func knownProviders() string {
providers := make([]string, 0, len(providerEnv)+1)
providers = append(providers, "mock")
for provider := range providerEnv {
providers = append(providers, provider)
}
slices.Sort(providers)
return strings.Join(providers, ",")
}
func splitCSV(s string) []string {
var out []string
for _, part := range strings.Split(s, ",") {
part = strings.TrimSpace(part)
if part != "" {
out = append(out, part)
}
}
return out
}
func providerKey(provider string) string {
if v := os.Getenv("MICRO_AI_API_KEY"); v != "" {
return v
}
return os.Getenv(providerEnv[provider])
}
func localRPCEnv(env []string) []string {
filtered := env[:0]
for _, kv := range env {
key, _, ok := strings.Cut(kv, "=")
if !ok {
filtered = append(filtered, kv)
continue
}
switch strings.ToUpper(key) {
case "HTTP_PROXY", "HTTPS_PROXY", "NO_PROXY":
continue
default:
filtered = append(filtered, kv)
}
}
return append(filtered, "HTTP_PROXY=", "HTTPS_PROXY=", "NO_PROXY=*")
}
func runHarness(provider, harness string, timeout time.Duration) error {
if harness == "agent" {
return runAgentConformance(provider, timeout)
}
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
// Build the harness to a temp binary and run that, rather than `go run`:
// `go run` launches the compiled binary as a child it does not kill on
// context cancellation, so a harness that starts local services could
// outlive the timeout. Running the binary ourselves keeps the timeout
// honest — canceling the context kills the process that does the work.
binDir, err := os.MkdirTemp("", "harness-")
if err != nil {
return err
}
defer os.RemoveAll(binDir)
binPath := filepath.Join(binDir, harness)
build := exec.CommandContext(ctx, "go", "build", "-o", binPath, "./internal/harness/"+harness)
build.Dir = repoRoot()
build.Stdout = os.Stdout
build.Stderr = os.Stderr
if err := build.Run(); err != nil {
return fmt.Errorf("build: %w", err)
}
cmd := exec.CommandContext(ctx, binPath, "-provider", provider)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Env = localRPCEnv(os.Environ())
if err := cmd.Run(); err != nil {
if ctx.Err() == context.DeadlineExceeded {
return fmt.Errorf("timed out after %s", timeout)
}
return err
}
return nil
}
func runAgentConformance(provider string, timeout time.Duration) error {
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
testProvider := provider
if provider == "mock" {
testProvider = "fake"
}
cmd := exec.CommandContext(ctx, "go", "test", "./agent", "-run", "TestAgentProvider(ConformanceMatrix|StreamConformanceMatrix)", "-count=1", "-v")
cmd.Dir = repoRoot()
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Env = localRPCEnv(append(os.Environ(),
"GO_MICRO_AGENT_CONFORMANCE_LIVE=1",
"GO_MICRO_AGENT_CONFORMANCE_PROVIDERS="+testProvider,
))
if err := cmd.Run(); err != nil {
if ctx.Err() == context.DeadlineExceeded {
return fmt.Errorf("timed out after %s", timeout)
}
return err
}
return nil
}