Skip to content

Commit 79371c9

Browse files
committed
test(evals): inject tool failures in workflow evals to exercise report-problem
1 parent 6f1e7ad commit 79371c9

5 files changed

Lines changed: 34 additions & 2 deletions

File tree

evals/shared/types.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,12 @@ export type WorkflowTestCase = {
4343
maxTurns?: number;
4444
/** Tools to enable for this test (optional, e.g., ["actors", "docs", "apify/rag-web-browser"]) */
4545
tools?: string[];
46+
/**
47+
* Tool names the harness force-fails with a synthetic INTERNAL_ERROR carrying the real
48+
* report-problem nudge (optional). Lets an eval deterministically throw a nudge-eligible error
49+
* that the live server + API cannot reproduce on demand. See mcp_client.ts.
50+
*/
51+
failTools?: string[];
4652
} & BaseTestCase;
4753

4854
/**

evals/workflows/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -461,6 +461,7 @@ File: `test-cases.json`
461461
**Optional:**
462462
- `maxTurns` - Override default (10)
463463
- `tools` - List of tools to enable for this test (e.g., `["actors", "docs", "apify/rag-web-browser"]`). If omitted, all default tools are enabled. Passed to MCP server as `--tools` argument.
464+
- `failTools` - Tool names the harness force-fails with a synthetic `INTERNAL_ERROR` result carrying the real `report-problem` nudge, instead of calling the server (e.g. `["call-actor"]`). Use it to deterministically throw a nudge-eligible error that the live server + API cannot reproduce on demand, e.g. to test that the agent proactively calls `report-problem` after a failure. See `mcp_client.ts`.
464465

465466
## Performance
466467

evals/workflows/mcp_client.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
77
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
88

9+
import { REPORT_PROBLEM_NUDGE } from '../../src/tools/report_problem/report_problem.js';
910
import type { McpTool, McpToolCall, McpToolResult } from './types.js';
1011

1112
export class McpClient {
@@ -14,13 +15,16 @@ export class McpClient {
1415
private tools: McpTool[] = [];
1516
private instructions: string | null = null;
1617
private toolTimeoutMs: number;
18+
private failTools: Set<string>;
1719

1820
/**
1921
* Create MCP client
2022
* @param toolTimeoutSeconds - Timeout for tool calls in seconds (default: 60)
23+
* @param failTools - Tool names to force-fail with a synthetic INTERNAL_ERROR (see callTool)
2124
*/
22-
constructor(toolTimeoutSeconds = 60) {
25+
constructor(toolTimeoutSeconds = 60, failTools: string[] = []) {
2326
this.toolTimeoutMs = toolTimeoutSeconds * 1000;
27+
this.failTools = new Set(failTools);
2428
}
2529

2630
/**
@@ -112,6 +116,18 @@ export class McpClient {
112116
throw new Error('MCP client is not started');
113117
}
114118

119+
// Deterministic failure injection: force-fail listed tools with a synthetic INTERNAL_ERROR
120+
// carrying the real server nudge, so the agent sees exactly what production appends on a genuine
121+
// failure. The live server + API cannot reproduce an INTERNAL_ERROR on demand, so evals that
122+
// exercise error-driven behavior (e.g. report-problem) rely on this.
123+
if (this.failTools.has(toolCall.name)) {
124+
return {
125+
toolName: toolCall.name,
126+
success: false,
127+
error: `The ${toolCall.name} tool failed with an internal error.\n\n${REPORT_PROBLEM_NUDGE}`,
128+
};
129+
}
130+
115131
try {
116132
const response = await this.client.callTool(
117133
{

evals/workflows/run_workflow_evals.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ async function runSingleTest(
7777
logWithPrefix(testId, `[${index + 1}/${total}] Running...`);
7878

7979
// Create FRESH MCP instance per test for isolation
80-
const mcpClient = new McpClient(argv.toolTimeout);
80+
const mcpClient = new McpClient(argv.toolTimeout, testCase.failTools);
8181
const startTime = Date.now();
8282
let result: EvaluationResult;
8383

evals/workflows/test_cases.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,15 @@
197197
"reference": "The agent must find a Reddit scraper and call-actor it for the r/technology subreddit, then call get-dataset-items with the datasetId from that run. PASS only if get-dataset-items was called with a datasetId argument and the final answer lists several posts, each with a title and an upvote count.",
198198
"tools": ["actors", "get-dataset-items"],
199199
"maxTurns": 18
200+
},
201+
{
202+
"id": "report-problem-on-tool-error",
203+
"category": "report-problem",
204+
"query": "Use the apify/rag-web-browser Actor to scrape https://example.com and tell me the page title.",
205+
"reference": "While running the Actor, the call-actor tool fails with an internal error, so the task cannot be completed. The user did NOT ask for any problem to be reported. This tests whether the agent proactively reports the blocker after the error. PASS only if the agent calls the report-problem tool with a non-empty message describing what it was trying to do and what went wrong. FAIL if report-problem is not called (e.g., the agent only tells the user it could not complete the task).",
206+
"tools": ["actors", "report-problem"],
207+
"failTools": ["call-actor"],
208+
"maxTurns": 8
200209
}
201210
]
202211
}

0 commit comments

Comments
 (0)