yao/agent/test/assert_agent_test.go
Max 88610da75b Implement Agent-Driven Assertions in Test Framework
- Added support for agent-driven assertions in the Asserter, allowing validation of responses using specified agents.
- Introduced the `Use` and `Options` fields in the Assertion struct to facilitate agent configuration.
- Enhanced the `evaluateAssertion` method to handle assertions of type "agent".
- Implemented the `assertAgent` method to manage agent interactions and validation logic.
- Updated `script_assert.go` to include the `assertAgentMethod` for JavaScript API integration.
- Revised documentation in DESIGN_V2.md and TODO_V2.md to reflect the new agent-driven assertion capabilities and implementation status.
2025-12-26 10:16:40 +08:00

275 lines
6.6 KiB
Go

package test_test
import (
"testing"
"github.com/stretchr/testify/assert"
v8 "github.com/yaoapp/gou/runtime/v8"
"github.com/yaoapp/yao/agent"
agenttest "github.com/yaoapp/yao/agent/test"
"github.com/yaoapp/yao/config"
"github.com/yaoapp/yao/test"
"rogchap.com/v8go"
)
func TestAsserter_AgentAssertion(t *testing.T) {
test.Prepare(t, config.Conf)
defer test.Clean()
// Load agent (includes assistants)
err := agent.Load(config.Conf)
if err != nil {
t.Fatalf("Failed to load agent: %v", err)
}
asserter := agenttest.NewAsserter()
tests := []struct {
name string
tc *agenttest.Case
output interface{}
expected bool
skipMsg string
}{
{
name: "agent assertion - pass",
tc: &agenttest.Case{
Assert: map[string]interface{}{
"type": "agent",
"use": "agents:tests.validator-agent",
"value": "Response should be a greeting",
},
},
output: "Hello! How can I help you today?",
expected: true,
},
{
name: "agent assertion - fail",
tc: &agenttest.Case{
Assert: map[string]interface{}{
"type": "agent",
"use": "agents:tests.validator-agent",
"value": "Response should provide a detailed technical answer",
},
},
output: "I don't know.",
expected: false,
},
{
name: "agent assertion - missing prefix",
tc: &agenttest.Case{
Assert: map[string]interface{}{
"type": "agent",
"use": "tests.validator-agent", // Missing agents: prefix
"value": "Should pass",
},
},
output: "Hello",
expected: false, // Should fail due to missing prefix
},
{
name: "agent assertion - with metadata",
tc: &agenttest.Case{
Assert: map[string]interface{}{
"type": "agent",
"use": "agents:tests.validator-agent",
"value": "Response is helpful",
"options": map[string]interface{}{
"metadata": map[string]interface{}{
"context": "customer support",
},
},
},
},
output: "I'd be happy to help you with your order. Let me look that up for you.",
expected: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.skipMsg != "" {
t.Skip(tt.skipMsg)
}
passed, errMsg := asserter.Validate(tt.tc, tt.output)
if passed != tt.expected {
t.Errorf("Expected passed=%v, got passed=%v, error: %s", tt.expected, passed, errMsg)
}
})
}
}
func TestAsserter_AgentAssertion_InvalidAgent(t *testing.T) {
test.Prepare(t, config.Conf)
defer test.Clean()
// Load agent (includes assistants)
err := agent.Load(config.Conf)
if err != nil {
t.Fatalf("Failed to load agent: %v", err)
}
asserter := agenttest.NewAsserter()
tc := &agenttest.Case{
Assert: map[string]interface{}{
"type": "agent",
"use": "agents:nonexistent.agent",
"value": "Should fail",
},
}
passed, errMsg := asserter.Validate(tc, "Hello")
assert.False(t, passed, "Should fail for nonexistent agent")
assert.Contains(t, errMsg, "failed to get validator agent", "Error should mention agent loading failure")
}
func TestAsserter_MapToAssertion_WithUseAndOptions(t *testing.T) {
test.Prepare(t, config.Conf)
defer test.Clean()
// Load agent (includes assistants)
err := agent.Load(config.Conf)
if err != nil {
t.Fatalf("Failed to load agent: %v", err)
}
asserter := agenttest.NewAsserter()
// Test that mapToAssertion correctly parses use and options fields
tc := &agenttest.Case{
Assert: map[string]interface{}{
"type": "agent",
"use": "agents:tests.validator-agent",
"value": "criteria here",
"options": map[string]interface{}{
"connector": "gpt-4o",
"metadata": map[string]interface{}{
"key": "value",
},
},
},
}
// Validate triggers parseAssertions internally
// We just verify it doesn't panic and processes correctly
_, _ = asserter.Validate(tc, "test output")
// If we get here without panic, the parsing worked
}
// TestTestingT_AssertAgent tests the JSAPI t.assert.Agent() method
func TestTestingT_AssertAgent(t *testing.T) {
test.Prepare(t, config.Conf)
defer test.Clean()
// Load agent (includes assistants)
err := agent.Load(config.Conf)
if err != nil {
t.Fatalf("Failed to load agent: %v", err)
}
tests := []struct {
name string
script string
shouldFail bool
}{
{
name: "JSAPI agent assertion - pass",
script: `
function test(t) {
var response = "Hello! How can I help you today?";
t.assert.Agent(response, "tests.validator-agent", {
criteria: "Response should be a friendly greeting"
});
}
test(__test_t);
`,
shouldFail: false,
},
{
name: "JSAPI agent assertion - JSON response",
script: `
function test(t) {
var response = {
status: "success",
data: { user: "john", email: "john@example.com" },
message: "User created successfully"
};
t.assert.Agent(response, "tests.validator-agent", {
criteria: "Response should be a successful API response with user data"
});
}
test(__test_t);
`,
shouldFail: false,
},
{
name: "JSAPI agent assertion - with metadata",
script: `
function test(t) {
var response = "I'd be happy to help you with your order.";
t.assert.Agent(response, "tests.validator-agent", {
criteria: "Response is helpful and professional",
metadata: { context: "customer support" }
});
}
test(__test_t);
`,
shouldFail: false,
},
{
name: "JSAPI agent assertion - fail case",
script: `
function test(t) {
var response = "I don't know.";
t.assert.Agent(response, "tests.validator-agent", {
criteria: "Response should provide a detailed technical explanation"
});
}
test(__test_t);
`,
shouldFail: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Create TestingT
testingT := agenttest.NewTestingT(tt.name)
// Create V8 isolate and context
iso := v8go.NewIsolate()
defer iso.Dispose()
v8ctx := v8go.NewContext(iso)
defer v8ctx.Close()
// Create testing object
testObj, err := agenttest.NewTestingTObject(v8ctx, testingT)
if err != nil {
t.Fatalf("Failed to create testing object: %v", err)
}
// Set testing object as global
global := v8ctx.Global()
global.Set("__test_t", testObj)
// Run the test script
_, err = v8ctx.RunScript(tt.script, "test.js")
// Check results
if tt.shouldFail {
assert.True(t, testingT.Failed(), "Test should have failed")
} else {
if err != nil {
t.Errorf("Script execution error: %v", err)
}
assert.False(t, testingT.Failed(), "Test should have passed, errors: %v", testingT.Errors())
}
})
}
}
// Ensure v8 is used (for script loading)
var _ = v8.Scripts