Skip to content

Commit 5625968

Browse files
committed
Tool calling is working
1 parent e34aaf2 commit 5625968

File tree

1 file changed

+68
-13
lines changed

1 file changed

+68
-13
lines changed

apps/webapp/app/v3/services/aiRunFilterService.server.ts

Lines changed: 68 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { openai } from "@ai-sdk/openai";
2-
import { generateText, Output } from "ai";
2+
import { generateText, Output, tool } from "ai";
33
import { z } from "zod";
44
import { TaskRunListSearchFilters } from "~/components/runs/v3/RunFilters";
55
import { $replica } from "~/db.server";
@@ -12,7 +12,7 @@ import { type AuthenticatedEnvironment } from "~/services/apiAuth.server";
1212
import { logger } from "~/services/logger.server";
1313

1414
const AIFilterResponseSchema = z.object({
15-
filters: TaskRunListSearchFilters,
15+
filters: TaskRunListSearchFilters.omit({ environments: true }),
1616
explanation: z
1717
.string()
1818
.describe("A short human-readable explanation of what filters were applied"),
@@ -49,10 +49,10 @@ export async function processAIFilter(
4949
const queuePresenter = new QueueListPresenter();
5050

5151
const result = await generateText({
52-
model: openai("gpt-4o"),
52+
model: openai("gpt-4o-mini"),
5353
experimental_output: Output.object({ schema: AIFilterResponseSchema }),
5454
tools: {
55-
lookupTags: {
55+
lookupTags: tool({
5656
description: "Look up available tags in the environment",
5757
parameters: z.object({
5858
query: z.string().optional().describe("Optional search query to filter tags"),
@@ -69,8 +69,8 @@ export async function processAIFilter(
6969
total: tags.tags.length,
7070
};
7171
},
72-
},
73-
lookupVersions: {
72+
}),
73+
lookupVersions: tool({
7474
description:
7575
"Look up available versions in the environment. If you specify `isCurrent` it will return a single version string if it finds one. Otherwise it will return an array of version strings.",
7676
parameters: z.object({
@@ -107,8 +107,8 @@ export async function processAIFilter(
107107
versions: versions.versions.map((v) => v.version),
108108
};
109109
},
110-
},
111-
lookupQueues: {
110+
}),
111+
lookupQueues: tool({
112112
description: "Look up available queues in the environment",
113113
parameters: z.object({
114114
query: z.string().optional().describe("Optional search query to filter queues"),
@@ -129,8 +129,8 @@ export async function processAIFilter(
129129
total: queues.success ? queues.queues.length : 0,
130130
};
131131
},
132-
},
133-
lookupTasks: {
132+
}),
133+
lookupTasks: tool({
134134
description:
135135
"Look up available tasks in the environment. It will return each one. The `slug` is used for the filtering. You also get the triggerSource which is either `STANDARD` or `SCHEDULED`",
136136
parameters: z.object({}),
@@ -141,8 +141,9 @@ export async function processAIFilter(
141141
total: tasks.length,
142142
};
143143
},
144-
},
144+
}),
145145
},
146+
maxSteps: 5,
146147
prompt: `You are an AI assistant that converts natural language descriptions into structured filter parameters for a task run filtering system.
147148
148149
Available filter options:
@@ -161,6 +162,7 @@ Available filter options:
161162
162163
Common patterns to recognize:
163164
- "failed runs" → statuses: ["COMPLETED_WITH_ERRORS", "CRASHED", "TIMED_OUT", "SYSTEM_FAILURE"].
165+
- "runs not dequeued yet" → statuses: ["PENDING", "PENDING_VERSION", "DELAYED"]
164166
- If they say "only failed" then only use "COMPLETED_WITH_ERRORS".
165167
- "successful runs" → statuses: ["COMPLETED_SUCCESSFULLY"]
166168
- "running runs" → statuses: ["EXECUTING", "RETRYING_AFTER_FAILURE", "WAITING_TO_RESUME"]
@@ -177,20 +179,73 @@ Use the available tools to look up actual tags, versions, queues, and tasks in t
177179
178180
Unless they specify they only want root runs, set rootOnly to false.
179181
182+
IMPORTANT: Return ONLY the filters that are explicitly mentioned or can be reasonably inferred. If the description is unclear or doesn't match any known patterns, return an empty filters object {} and explain why in the explanation field.
183+
184+
The filters object should only contain the fields that are actually being filtered. Do not include fields with empty arrays or undefined values.
185+
186+
CRITICAL: The response must be a valid JSON object with exactly this structure:
187+
{
188+
"filters": {
189+
// only include fields that have actual values
190+
},
191+
"explanation": "string explaining what filters were applied"
192+
}
193+
180194
Convert the following natural language description into structured filters:
181195
182196
"${text}"
183197
184198
Return only the filters that are explicitly mentioned or can be reasonably inferred. If the description is unclear or doesn't match any known patterns, return an empty filters object and explain why in the explanation field.`,
185199
});
186200

201+
// Add debugging to see what the AI returned
202+
logger.info("AI filter response", {
203+
text,
204+
environmentId: environment.id,
205+
result: result.experimental_output,
206+
filters: result.experimental_output.filters,
207+
});
208+
209+
// Validate the filters against the schema to catch any issues
210+
const validationResult = TaskRunListSearchFilters.omit({ environments: true }).safeParse(
211+
result.experimental_output.filters
212+
);
213+
if (!validationResult.success) {
214+
logger.error("AI filter validation failed", {
215+
errors: validationResult.error.errors,
216+
filters: result.experimental_output.filters,
217+
});
218+
219+
return {
220+
success: false,
221+
error: "AI response validation failed",
222+
suggestions:
223+
"The AI response contained invalid filter values. Try rephrasing your request.",
224+
};
225+
}
226+
187227
return {
188228
success: true,
189-
filters: result.experimental_output.filters,
229+
filters: validationResult.data,
190230
explanation: result.experimental_output.explanation,
191231
};
192232
} catch (error) {
193-
logger.error("AI filter processing failed", { error, text, environmentId: environment.id });
233+
logger.error("AI filter processing failed", {
234+
error,
235+
errorMessage: error instanceof Error ? error.message : String(error),
236+
text,
237+
environmentId: environment.id,
238+
});
239+
240+
// If it's a schema validation error, provide more specific feedback
241+
if (error instanceof Error && error.message.includes("schema")) {
242+
return {
243+
success: false,
244+
error: "AI response format error",
245+
suggestions:
246+
"The AI response didn't match the expected format. Try rephrasing your request or being more specific about what you want to filter.",
247+
};
248+
}
194249

195250
return {
196251
success: false,

0 commit comments

Comments
 (0)