The agk eval command provides comprehensive automated testing for AI workflows using semantic matching, confidence scoring, and professional reporting.
- Overview
- Quick Start
- Test Configuration
- Semantic Matching Strategies
- EvalServer Integration
- Reports
- Best Practices
- Troubleshooting
The eval framework enables you to:
- Validate workflow outputs using semantic understanding (not exact string matching)
- Score confidence on a 0.0-1.0 scale for each test
- Generate professional reports with visualizations and detailed analysis
- Integrate with CI/CD for automated quality gates
- Debug failures using trace integration
┌─────────────────┐
│ Test Suite │
│ (YAML) │
└────────┬────────┘
│
▼
┌─────────────────┐ ┌──────────────────┐
│ AGK Eval │─────▶│ EvalServer │
│ Command │ │ (HTTP Server) │
└────────┬────────┘ └──────────────────┘
│ │
│ ▼
│ ┌──────────────────┐
│ │ Your Workflow │
│ └──────────────────┘
│
▼
┌─────────────────┐ ┌──────────────────┐
│ Semantic │─────▶│ Embedding or │
│ Matcher │ │ LLM Judge │
└────────┬────────┘ └──────────────────┘
│
▼
┌─────────────────┐
│ Report │
│ Generator │
└─────────────────┘
First, ensure your workflow supports EvalServer mode:
// main.go
package main
import (
"context"
"os"
agk "github.com/agenticgokit/agenticgokit/v1beta"
)
func main() {
if os.Getenv("AGK_EVAL_MODE") == "true" {
runEvalServer()
return
}
runNormal()
}
func runEvalServer() {
ctx := context.Background()
// Load your workflow
workflow, _ := agk.LoadWorkflowFromTOML("config.toml")
workflow.Initialize(ctx)
defer workflow.Shutdown(ctx)
// Start EvalServer
server := agk.NewEvalServer(
agk.WithEvalWorkflow("myworkflow", workflow),
agk.WithEvalPort(8787),
)
server.ListenAndServe()
}
func runNormal() {
// Your normal workflow execution
}# tests.yaml
name: "My Workflow Tests"
description: "Semantic evaluation of AI outputs"
evalserver:
url: "http://localhost:8787"
workflow_name: "myworkflow"
timeout: "180s"
semantic:
strategy: "llm-judge"
threshold: 0.70
llm:
provider: "ollama"
model: "llama3.2"
temperature: 0.0
max_tokens: 2000
tests:
- name: "Test Case 1"
input: "Your input here"
expected_output: |
Description of what you expect the output to contain,
not an exact string match# Terminal 1: Start your workflow in EvalServer mode
AGK_EVAL_MODE=true ./myworkflow
# Terminal 2: Run tests
agk eval tests.yaml --timeout 200
# View report
cat .agk/reports/eval-report-*.md# Test suite metadata
name: "Suite Name"
description: "What this test suite validates"
# EvalServer connection
evalserver:
url: "http://localhost:8787" # Server URL
workflow_name: "myworkflow" # Workflow identifier
timeout: "180s" # Max execution time per test
# Semantic matching configuration
semantic:
strategy: "llm-judge" # "embedding", "llm-judge", or "hybrid"
threshold: 0.70 # Pass threshold (0.0-1.0)
# For embedding strategy
embedding:
provider: "ollama"
model: "nomic-embed-text"
# For llm-judge or hybrid strategy
llm:
provider: "ollama"
model: "llama3.2"
temperature: 0.0
max_tokens: 2000
# Test cases
tests:
- name: "Test Case Name"
input: "Input to workflow"
expected_output: |
Multi-line description of expected output.
Focus on semantic meaning, not exact wording.
- name: "Another Test"
input: "Different input"
expected_output: "Short expected output"| Field | Type | Required | Description |
|---|---|---|---|
url |
string | Yes | HTTP endpoint of EvalServer |
workflow_name |
string | Yes | Workflow identifier (must match server registration) |
timeout |
duration | Yes | Max time per test (e.g., "180s", "3m") |
| Field | Type | Required | Description |
|---|---|---|---|
strategy |
string | Yes | Matching strategy: embedding, llm-judge, hybrid |
threshold |
float | Yes | Pass threshold 0.0-1.0 (typically 0.60-0.80) |
embedding |
object | Conditional | Required for embedding or hybrid |
llm |
object | Conditional | Required for llm-judge or hybrid |
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | Yes | Unique test identifier |
input |
string | Yes | Input sent to workflow |
expected_output |
string | Yes | Semantic description of expected output |
Uses vector embeddings to compute similarity between expected and actual outputs.
When to Use:
- Fast execution needed (< 1 second per test)
- Checking if outputs cover similar topics/concepts
- High-volume testing (100+ test cases)
- Deterministic results required
How It Works:
- Embeds expected output using
nomic-embed-text - Embeds actual workflow output
- Computes cosine similarity
- Passes if similarity ≥ threshold
Configuration:
semantic:
strategy: "embedding"
threshold: 0.70
embedding:
provider: "ollama"
model: "nomic-embed-text"Pros:
- ⚡ Very fast (< 1s)
- 🎯 Deterministic
- 📊 Good for semantic similarity
Cons:
- 🤔 Less nuanced than LLM judge
- ❌ May miss quality issues
- 📝 Better for content matching than quality
Example Results:
Test: Generate Article
Expected: "A technical article about AI safety"
Actual: "AI Safety: A Comprehensive Guide..."
Similarity: 0.82 ✓ PASSED
Uses an LLM to evaluate if actual output matches the expected description.
When to Use:
- Quality matters more than speed
- Nuanced evaluation needed (tone, completeness, accuracy)
- Expected outputs are descriptions, not exact text
- Need reasoning behind pass/fail decisions
How It Works:
- Constructs a prompt with expected and actual outputs
- Asks LLM: "Does actual match expected?"
- LLM responds with YES/NO and confidence score
- Provides reasoning for the decision
Configuration:
semantic:
strategy: "llm-judge"
threshold: 0.70
llm:
provider: "ollama"
model: "llama3.2"
temperature: 0.0 # Use 0 for consistency
max_tokens: 2000Custom Judge Prompt (Optional):
semantic:
strategy: "llm-judge"
threshold: 0.70
llm:
provider: "ollama"
model: "llama3.2"
judge_prompt: |
You are evaluating AI-generated content.
Expected: {expected}
Actual: {actual}
Does the actual output meet the expectations?
Respond: YES <confidence> <reasoning> or NO <confidence> <reasoning>Pros:
- 🧠 Nuanced understanding
- ✍️ Provides reasoning
- 🎯 Better quality assessment
- 📋 Handles complex criteria
Cons:
- 🐌 Slower (5-15s per test)
- 💰 More expensive (if using paid APIs)
- 🎲 Less deterministic
- 🔧 Requires good LLM
Example Results:
Test: Generate Report
Confidence: 0.90 ✓ PASSED
Reasoning:
"The actual output matches the expected description perfectly.
It contains a comprehensive technical report with structured
sections covering AI collaboration, applications, benefits,
and future directions as specified."
Combines both embedding and LLM judge strategies.
When to Use:
- Maximum coverage needed
- Balance speed and quality
- Critical workflows that need double validation
How It Works:
- Runs embedding similarity check
- If passed, marks as PASSED
- If embedding fails, runs LLM judge
- Uses best result from either strategy
Configuration:
semantic:
strategy: "hybrid"
threshold: 0.70
embedding:
provider: "ollama"
model: "nomic-embed-text"
llm:
provider: "ollama"
model: "llama3.2"Pros:
- ✅ Highest accuracy
- 🎯 Catches edge cases
- ⚡ Fast when embedding passes
Cons:
- 🐌 Slower on failures
- 🔧 More complex configuration
- 💾 More resource intensive
Strategy Comparison:
| Factor | Embedding | LLM Judge | Hybrid |
|---|---|---|---|
| Speed | ⚡⚡⚡ | ⚡ | ⚡⚡ |
| Accuracy | ⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐ |
| Cost | $ | $$$ | $$ |
| Reasoning | ❌ | ✅ | ✅ |
| Deterministic | ✅ |
EvalServer is an HTTP server mode that wraps your workflow for testing. It provides:
- Standardized HTTP endpoints
- Trace collection
- Timeout handling
- Error reporting
package main
import (
"context"
"os"
agk "github.com/agenticgokit/agenticgokit/v1beta"
)
func main() {
// Check for eval mode
if os.Getenv("AGK_EVAL_MODE") == "true" {
runEvalServer()
return
}
runNormal()
}
func runEvalServer() {
ctx := context.Background()
// Load workflow (TOML, builder, or programmatic)
workflow, err := agk.LoadWorkflowFromTOML("workflow-config.toml")
if err != nil {
log.Fatal(err)
}
if err := workflow.Initialize(ctx); err != nil {
log.Fatal(err)
}
defer workflow.Shutdown(ctx)
// Create server with options
server := agk.NewEvalServer(
agk.WithEvalWorkflow("myworkflow", workflow),
agk.WithEvalPort(8787),
agk.WithTraceDir("./eval-traces"),
)
fmt.Println("EvalServer listening on :8787")
if err := server.ListenAndServe(); err != nil {
log.Fatal(err)
}
}| Option | Description | Default |
|---|---|---|
WithEvalWorkflow(name, workflow) |
Register a workflow | Required |
WithEvalPort(port) |
HTTP port | 8787 |
WithTraceDir(dir) |
Trace storage directory | ./.agk/eval-traces |
| Method | Path | Description |
|---|---|---|
| GET | /health |
Health check |
| POST | /invoke |
Invoke default workflow |
| POST | /invoke/{name} |
Invoke named workflow |
| GET | /traces/{id} |
Get trace by ID |
{
"input": "Your workflow input",
"sessionID": "optional-session-id",
"options": {
"timeout": 120
}
}{
"output": "Workflow output text",
"success": true,
"duration": 45.2,
"trace_id": "run-20260207-123456-12345678"
}The eval framework auto-generates professional markdown reports with detailed analysis.
# Test Report: Suite Name
> **Status: PASSED** - 5/6 tests completed successfully
## Summary
| Metric | Value | Progress |
|--------|-------|----------|
| Total Tests | 6 | |
| Passed | 5 | ✓✓✓✓✓ |
| Failed | 1 | ✗ |
| Pass Rate | 83.3% | [████████████████░░░░] |
## Detailed Test Results
### 1. Test Name
**Status:** PASSED | **Duration:** 45.2s
**Confidence Score:** 85%
[Progress bar visualization]
<details>
<summary>View Judge's Reasoning</summary>
...
</details>
<details>
<summary>Expected Output</summary>
...
</details>
<details>
<summary>Actual Output</summary>
...
</details>Reports are saved to:
.agk/reports/eval-report-YYYYMMDD-HHMMSS.md
- ✅ Executive Summary: Quick pass/fail overview
- 📊 Progress Bars: Visual representation of success rates
- 📈 Confidence Scores: Numerical confidence with bar visualization
- 🔍 Collapsible Sections: Reduces clutter, expandable details
- 🔗 Trace Links: Direct links to execution traces
- 🎯 Judge Reasoning: Explanation for LLM judge decisions
- 🏷️ AGK Branding: Tool attribution footer
| Threshold | Use Case |
|---|---|
| 0.90+ | Strict quality gates, production deployments |
| 0.70-0.89 | Standard testing, most use cases |
| 0.60-0.69 | Lenient matching, exploratory testing |
| < 0.60 | Not recommended (too permissive) |
❌ Bad - Too specific:
expected_output: "The capital of France is Paris."✅ Good - Semantic description:
expected_output: |
A factually correct statement identifying Paris as
the capital city of France❌ Bad - Exact template:
expected_output: |
# Title
## Section 1
Content here
## Section 2
More content✅ Good - Structure description:
expected_output: |
A well-structured document with:
- A clear title
- Multiple sections with headings
- Professional formatting
- Comprehensive content# Group related tests
tests:
# Basic functionality
- name: "Basic Query"
input: "simple question"
expected_output: "direct answer"
# Edge cases
- name: "Empty Input"
input: ""
expected_output: "error message or helpful prompt"
# Complex scenarios
- name: "Multi-step Workflow"
input: "complex requirements"
expected_output: |
Detailed multi-section output with...- Use embedding for bulk tests: Switch to
embeddingstrategy for large test suites (50+ tests) - Parallel execution: Run multiple test suites in parallel
- Adjust timeouts: Set realistic timeouts based on workflow complexity
- Cache embeddings: Ollama automatically caches embeddings
Symptom:
Error: failed to connect to EvalServer at http://localhost:8787
Solution:
# Check if server is running
curl http://localhost:8787/health
# Start the server
AGK_EVAL_MODE=true ./myworkflow
# Verify correct port in tests.yaml
evalserver:
url: "http://localhost:8787"Symptom:
Error: test timed out after 180s
Solution:
# Increase timeout in YAML
evalserver:
timeout: "300s" # 5 minutes
# Or use CLI flag
agk eval tests.yaml --timeout 300Symptom:
All tests failing with confidence ~0.40
Solutions:
- Check expected output: Make it more semantic, less specific
- Lower threshold: Try 0.60 instead of 0.70
- Switch strategy: Try
llm-judgeif usingembedding - Verify workflow: Manually run workflow to check actual output
Symptom:
Error: failed to initialize LLM judge: model not found
Solution:
# Install required model
ollama pull llama3.2
# Verify model name in tests.yaml
semantic:
llm:
model: "llama3.2" # Must match exact model nameSymptom:
Error: embedding model not available
Solution:
# Install embedding model
ollama pull nomic-embed-text
# Verify configuration
semantic:
embedding:
provider: "ollama"
model: "nomic-embed-text"Override the default judge prompt for specialized evaluation:
semantic:
strategy: "llm-judge"
judge_prompt: |
You are a technical documentation reviewer.
Expected Requirements:
{expected}
Actual Content:
{actual}
Evaluate if the content meets professional documentation standards.
Consider: accuracy, clarity, completeness, formatting.
Respond: YES <0.0-1.0> <reasoning> or NO <0.0-1.0> <reasoning># .github/workflows/test.yml
name: AI Workflow Tests
on: [push, pull_request]
jobs:
eval:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Go
uses: actions/setup-go@v4
with:
go-version: '1.21'
- name: Install Ollama
run: curl -fsSL https://ollama.com/install.sh | sh
- name: Pull Models
run: |
ollama pull llama3.2
ollama pull nomic-embed-text
- name: Start EvalServer
run: |
cd myworkflow
AGK_EVAL_MODE=true ./myworkflow &
sleep 10
- name: Run Tests
run: |
cd agk
./agk eval ../tests/semantic-tests.yaml --timeout 300
- name: Upload Report
uses: actions/upload-artifact@v3
with:
name: eval-report
path: .agk/reports/Test multiple workflows in one suite:
# Start server with multiple workflows
server := agk.NewEvalServer(
agk.WithEvalWorkflow("workflow1", wf1),
agk.WithEvalWorkflow("workflow2", wf2),
)# Test different workflows
tests:
- name: "Test Workflow 1"
workflow_name: "workflow1"
input: "..."
- name: "Test Workflow 2"
workflow_name: "workflow2"
input: "..."name: "Docs Generator Tests"
description: "Validate technical documentation quality"
evalserver:
url: "http://localhost:8787"
workflow_name: "docs"
timeout: "120s"
semantic:
strategy: "llm-judge"
threshold: 0.75
llm:
provider: "ollama"
model: "llama3.2"
tests:
- name: "API Documentation"
input: "Document the /api/users endpoint"
expected_output: |
Professional API documentation including:
- Endpoint description
- HTTP method and path
- Request parameters
- Response format
- Example requests/responses
- Error codesname: "Code Review Tests"
description: "Automated code review quality"
evalserver:
url: "http://localhost:8787"
workflow_name: "reviewer"
timeout: "90s"
semantic:
strategy: "hybrid"
threshold: 0.80
embedding:
provider: "ollama"
model: "nomic-embed-text"
llm:
provider: "ollama"
model: "llama3.2"
tests:
- name: "Security Review"
input: "Review this authentication code"
expected_output: |
A thorough security review identifying:
- Potential vulnerabilities
- Best practice violations
- Specific recommendations
- Risk severity levels- Trace Documentation - Debugging with traces
- AGK CLI Reference - Full command reference
- Workflow Examples - Complete examples