Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 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
2 changes: 1 addition & 1 deletion cmd/waza/cmd_run_suggest_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ func TestBuildRunSuggestionPrompt_IncludesOnlyFailureEvidence(t *testing.T) {
{
Kind: models.GraderKindText,
Identifier: "must-mention-foo",
Parameters: map[string]any{"contains": "foo"},
Parameters: models.TextGraderParameters{Contains: []string{"foo"}},
},
},
}
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ require (
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.4
github.com/azure/azure-dev/cli/azd v0.0.0-20260304180858-75d26d278d36
github.com/charmbracelet/huh v0.8.0
github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834
github.com/github/copilot-sdk/go v0.1.30
github.com/go-viper/mapstructure/v2 v2.5.0
github.com/mattn/go-runewidth v0.0.20
Expand Down Expand Up @@ -43,6 +42,7 @@ require (
github.com/charmbracelet/bubbletea v1.3.10 // indirect
github.com/charmbracelet/colorprofile v0.4.2 // indirect
github.com/charmbracelet/glamour v0.10.0 // indirect
github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834 // indirect
github.com/charmbracelet/x/ansi v0.11.6 // indirect
github.com/charmbracelet/x/cellbuf v0.0.15 // indirect
github.com/charmbracelet/x/exp/slice v0.0.0-20260304084025-7dd5c0ab408e // indirect
Expand Down
35 changes: 10 additions & 25 deletions internal/graders/action_sequence_grader.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,39 +8,24 @@ import (
"github.com/microsoft/waza/internal/models"
)

// ActionSequenceMatchingMode controls how actual tool calls are compared to expected actions.
type ActionSequenceMatchingMode string

const (
MatchingModeExact ActionSequenceMatchingMode = "exact_match"
MatchingModeInOrder ActionSequenceMatchingMode = "in_order_match"
MatchingModeAnyOrder ActionSequenceMatchingMode = "any_order_match"
)

// actionSequenceGrader compares the agent's actual tool call sequence against
// an expected action path. It supports three matching modes and calculates
// precision, recall, and F1 scores.
type actionSequenceGrader struct {
name string
matchingMode ActionSequenceMatchingMode
matchingMode models.ActionSequenceMatchingMode
expectedActions []string
}

// ActionSequenceGraderParams holds the mapstructure-decoded parameters for the action sequence grader.
type ActionSequenceGraderParams struct {
MatchingMode string `mapstructure:"matching_mode"`
ExpectedActions []string `mapstructure:"expected_actions"`
}

// NewActionSequenceGrader creates an actionSequenceGrader from decoded parameters.
func NewActionSequenceGrader(name string, params ActionSequenceGraderParams) (*actionSequenceGrader, error) {
func NewActionSequenceGrader(name string, params models.ActionSequenceGraderParameters) (*actionSequenceGrader, error) {
if len(params.ExpectedActions) == 0 {
return nil, fmt.Errorf("action_sequence grader '%s' must have at least one expected_actions entry", name)
}

mode := ActionSequenceMatchingMode(params.MatchingMode)
mode := params.MatchingMode
switch mode {
case MatchingModeExact, MatchingModeInOrder, MatchingModeAnyOrder:
case models.ActionSequenceMatchingModeExact, models.ActionSequenceMatchingModeInOrder, models.ActionSequenceMatchingModeAnyOrder:
// valid
default:
return nil, fmt.Errorf("action_sequence grader '%s' has invalid matching_mode %q (must be exact_match, in_order_match, or any_order_match)", name, params.MatchingMode)
Expand Down Expand Up @@ -101,11 +86,11 @@ func (g *actionSequenceGrader) Grade(ctx context.Context, gradingContext *Contex
// checkMatch returns true if the actual sequence satisfies the matching mode constraint.
func (g *actionSequenceGrader) checkMatch(actual []string) bool {
switch g.matchingMode {
case MatchingModeExact:
case models.ActionSequenceMatchingModeExact:
return g.exactMatch(actual)
case MatchingModeInOrder:
case models.ActionSequenceMatchingModeInOrder:
return g.inOrderMatch(actual)
case MatchingModeAnyOrder:
case models.ActionSequenceMatchingModeAnyOrder:
return g.anyOrderMatch(actual)
default:
return false
Expand Down Expand Up @@ -211,13 +196,13 @@ func (g *actionSequenceGrader) buildFailureFeedback(actual []string) string {
var parts []string

switch g.matchingMode {
case MatchingModeExact:
case models.ActionSequenceMatchingModeExact:
parts = append(parts, fmt.Sprintf("Exact match failed: expected %d actions %v, got %d actions %v",
len(g.expectedActions), g.expectedActions, len(actual), actual))
case MatchingModeInOrder:
case models.ActionSequenceMatchingModeInOrder:
parts = append(parts, fmt.Sprintf("In-order match failed: not all expected actions %v appeared in order within actual %v",
g.expectedActions, actual))
case MatchingModeAnyOrder:
case models.ActionSequenceMatchingModeAnyOrder:
// Identify which expected actions are missing or insufficient
expectedCounts := make(map[string]int, len(g.expectedActions))
for _, e := range g.expectedActions {
Expand Down
Loading
Loading