Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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