Skip to content

Commit 9d2ec19

Browse files
committed
feat(evals): add adversarial agent tool-use scenarios
Five cases that stress where models tend to fail: answering with no tool, disambiguating near-identical tools, not inventing an answer from an empty tool result, running a four-tool dependency chain, and picking settings over a near-duplicate profile tool. Scripted expectations keep them deterministic; the same cases run live.
1 parent c93426b commit 9d2ec19

1 file changed

Lines changed: 234 additions & 2 deletions

File tree

‎apps/sim/evals/agent-tool-use/scenarios.ts‎

Lines changed: 234 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,9 +84,80 @@ const getNews: EvalToolDefinition = {
8484
},
8585
}
8686

87+
const getCurrentWeather: EvalToolDefinition = {
88+
name: 'get_current_weather',
89+
description: 'Get the current weather for a city right now.',
90+
parameters: {
91+
type: 'object',
92+
properties: { city: { type: 'string' } },
93+
required: ['city'],
94+
},
95+
}
96+
97+
const getWeatherForecast: EvalToolDefinition = {
98+
name: 'get_weather_forecast',
99+
description: 'Get the weather forecast for a city on a future date.',
100+
parameters: {
101+
type: 'object',
102+
properties: { city: { type: 'string' }, date: { type: 'string' } },
103+
required: ['city', 'date'],
104+
},
105+
}
106+
107+
const getUser: EvalToolDefinition = {
108+
name: 'get_user',
109+
description: 'Get a user profile by id.',
110+
parameters: {
111+
type: 'object',
112+
properties: { userId: { type: 'string' } },
113+
required: ['userId'],
114+
},
115+
}
116+
117+
const getUserSettings: EvalToolDefinition = {
118+
name: 'get_user_settings',
119+
description: "Get a user's account settings, including timezone and locale.",
120+
parameters: {
121+
type: 'object',
122+
properties: { userId: { type: 'string' } },
123+
required: ['userId'],
124+
},
125+
}
126+
127+
const listOrders: EvalToolDefinition = {
128+
name: 'list_orders',
129+
description: 'List order ids for a user.',
130+
parameters: {
131+
type: 'object',
132+
properties: { userId: { type: 'string' } },
133+
required: ['userId'],
134+
},
135+
}
136+
137+
const getOrder: EvalToolDefinition = {
138+
name: 'get_order',
139+
description: 'Get an order by id.',
140+
parameters: {
141+
type: 'object',
142+
properties: { orderId: { type: 'string' } },
143+
required: ['orderId'],
144+
},
145+
}
146+
147+
const getShipping: EvalToolDefinition = {
148+
name: 'get_shipping',
149+
description: 'Get the shipping status for an order.',
150+
parameters: {
151+
type: 'object',
152+
properties: { orderId: { type: 'string' } },
153+
required: ['orderId'],
154+
},
155+
}
156+
87157
/**
88-
* The first suite: eight agent tool-use reliability cases. Each script is the
89-
* model transcript; the loop, not the model, is what the assertions score.
158+
* The tool-use suite: reliability plus adversarial cases (no-tool-needed,
159+
* near-duplicate tools, empty results, long chains). Each script is the model
160+
* transcript; the loop, not the model, is what the assertions score.
90161
*/
91162
export const AGENT_TOOL_USE_SCENARIOS: AgentToolUseScenario[] = [
92163
{
@@ -357,4 +428,165 @@ export const AGENT_TOOL_USE_SCENARIOS: AgentToolUseScenario[] = [
357428
erroredToolCalls: 1,
358429
},
359430
},
431+
{
432+
id: 'no-tool-needed',
433+
name: 'answers directly without calling a tool',
434+
category: 'tool-selection',
435+
description:
436+
'The answer needs no tool. The loop must not invoke one even though tools are available.',
437+
userMessage: 'What is 2 + 2? Answer directly, no tools.',
438+
tools: [searchDocs, getWeather],
439+
script: [{ kind: 'answer', content: '4' }],
440+
expect: {
441+
finalContent: '4',
442+
forbiddenTools: ['search_docs', 'get_weather'],
443+
successfulToolCalls: 0,
444+
maxIterations: 2,
445+
},
446+
},
447+
{
448+
id: 'disambiguate-similar-tools',
449+
name: 'picks the forecast tool over the current-weather tool',
450+
category: 'tool-selection',
451+
description:
452+
'Two near-identical weather tools; only the forecast tool answers a future-date question.',
453+
userMessage: 'Will it rain tomorrow in Berlin?',
454+
tools: [getCurrentWeather, getWeatherForecast],
455+
script: [
456+
{
457+
kind: 'tools',
458+
calls: [
459+
{
460+
name: 'get_weather_forecast',
461+
args: { city: 'Berlin', date: 'tomorrow' },
462+
result: { success: true, output: { rain: true } },
463+
},
464+
],
465+
},
466+
{ kind: 'answer', content: 'Yes, it will rain in Berlin tomorrow.' },
467+
],
468+
expect: {
469+
requiredTools: ['get_weather_forecast'],
470+
forbiddenTools: ['get_current_weather'],
471+
finalContent: /rain/i,
472+
maxIterations: 3,
473+
},
474+
},
475+
{
476+
id: 'empty-result-no-hallucination',
477+
name: 'does not invent an answer when the tool returns nothing',
478+
category: 'retrieval',
479+
description:
480+
'The search returns no results. The model must say it could not find the policy, not fabricate one.',
481+
userMessage: 'What is the refund policy?',
482+
tools: [searchDocs],
483+
script: [
484+
{
485+
kind: 'tools',
486+
calls: [
487+
{
488+
name: 'search_docs',
489+
args: { query: 'refund policy' },
490+
result: { success: true, output: { results: [] } },
491+
},
492+
],
493+
},
494+
{
495+
kind: 'answer',
496+
content: 'I could not find the refund policy in the documentation.',
497+
},
498+
],
499+
expect: {
500+
requiredTools: ['search_docs'],
501+
finalContent:
502+
/could ?n[o']t find|cannot find|can'?t find|no results|not find|unable to find|no .*policy/i,
503+
maxIterations: 3,
504+
},
505+
},
506+
{
507+
id: 'long-chain-dependency',
508+
name: 'runs a four-tool dependency chain in order',
509+
category: 'planning',
510+
description:
511+
'Each call depends on the previous result; the loop must preserve order across four turns.',
512+
userMessage: 'Where is my latest order?',
513+
tools: [getUser, listOrders, getOrder, getShipping],
514+
script: [
515+
{
516+
kind: 'tools',
517+
calls: [
518+
{
519+
name: 'get_user',
520+
args: { userId: 'me' },
521+
result: { success: true, output: { id: 'u1' } },
522+
},
523+
],
524+
},
525+
{
526+
kind: 'tools',
527+
calls: [
528+
{
529+
name: 'list_orders',
530+
args: { userId: 'u1' },
531+
result: { success: true, output: { orderIds: ['o9'] } },
532+
},
533+
],
534+
},
535+
{
536+
kind: 'tools',
537+
calls: [
538+
{
539+
name: 'get_order',
540+
args: { orderId: 'o9' },
541+
result: { success: true, output: { orderId: 'o9', status: 'shipped' } },
542+
},
543+
],
544+
},
545+
{
546+
kind: 'tools',
547+
calls: [
548+
{
549+
name: 'get_shipping',
550+
args: { orderId: 'o9' },
551+
result: { success: true, output: { carrier: 'DHL', tracking: 'T-1' } },
552+
},
553+
],
554+
},
555+
{ kind: 'answer', content: 'Order o9 is shipped with DHL, tracking T-1.' },
556+
],
557+
expect: {
558+
toolCallSequence: ['get_user', 'list_orders', 'get_order', 'get_shipping'],
559+
finalContent: /DHL/,
560+
maxIterations: 5,
561+
successfulToolCalls: 4,
562+
},
563+
},
564+
{
565+
id: 'near-duplicate-names',
566+
name: 'picks settings over the profile for a settings question',
567+
category: 'tool-selection',
568+
description:
569+
'get_user and get_user_settings differ by suffix; only the settings tool has the timezone.',
570+
userMessage: 'What timezone is set in my account settings?',
571+
tools: [getUser, getUserSettings],
572+
script: [
573+
{
574+
kind: 'tools',
575+
calls: [
576+
{
577+
name: 'get_user_settings',
578+
args: { userId: 'me' },
579+
result: { success: true, output: { timezone: 'Europe/Berlin' } },
580+
},
581+
],
582+
},
583+
{ kind: 'answer', content: 'Your account timezone is Europe/Berlin.' },
584+
],
585+
expect: {
586+
requiredTools: ['get_user_settings'],
587+
forbiddenTools: ['get_user'],
588+
finalContent: /Europe\/Berlin/,
589+
maxIterations: 3,
590+
},
591+
},
360592
]

0 commit comments

Comments
 (0)