Skip to content

Commit e34aaf2

Browse files
committed
Started working on tool calling
1 parent d3569d5 commit e34aaf2

File tree

4 files changed

+134
-28
lines changed

4 files changed

+134
-28
lines changed

apps/webapp/app/presenters/v3/QueueListPresenter.server.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
1+
import {
2+
TaskQueueType,
3+
type RunEngineVersion,
4+
type RuntimeEnvironmentType,
5+
} from "@trigger.dev/database";
16
import { type AuthenticatedEnvironment } from "~/services/apiAuth.server";
27
import { determineEngineVersion } from "~/v3/engineVersion.server";
38
import { engine } from "~/v3/runEngine.server";
49
import { BasePresenter } from "./basePresenter.server";
510
import { toQueueItem } from "./QueueRetrievePresenter.server";
6-
import { TaskQueueType } from "@trigger.dev/database";
711

812
const DEFAULT_ITEMS_PER_PAGE = 25;
913
const MAX_ITEMS_PER_PAGE = 100;

apps/webapp/app/presenters/v3/VersionListPresenter.server.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import { type AuthenticatedEnvironment } from "~/services/apiAuth.server";
2-
import { BasePresenter } from "./basePresenter.server";
31
import { CURRENT_DEPLOYMENT_LABEL } from "@trigger.dev/core/v3/isomorphic";
2+
import { type RuntimeEnvironment } from "@trigger.dev/database";
3+
import { BasePresenter } from "./basePresenter.server";
44

55
const DEFAULT_ITEMS_PER_PAGE = 25;
66
const MAX_ITEMS_PER_PAGE = 100;
@@ -17,7 +17,7 @@ export class VersionListPresenter extends BasePresenter {
1717
environment,
1818
query,
1919
}: {
20-
environment: AuthenticatedEnvironment;
20+
environment: Pick<RuntimeEnvironment, "id" | "type">;
2121
query?: string;
2222
}) {
2323
const hasFilters = query !== undefined && query.length > 0;

apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.ai-filter.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { findProjectBySlug } from "~/models/project.server";
66
import { findEnvironmentBySlug } from "~/models/runtimeEnvironment.server";
77
import { processAIFilter } from "~/v3/services/aiRunFilterService.server";
88
import { type TaskRunListSearchFilters } from "~/components/runs/v3/RunFilters";
9+
import { tryCatch } from "@trigger.dev/core";
910

1011
const RequestSchema = z.object({
1112
text: z.string().min(1),
@@ -47,11 +48,10 @@ export async function action({ request, params }: ActionFunctionArgs) {
4748

4849
const { text } = submission.data;
4950

50-
const result = await processAIFilter(text, environment.id);
51-
52-
if (result.success) {
53-
return json(result);
54-
} else {
55-
return json(result, { status: 400 });
51+
const [error, result] = await tryCatch(processAIFilter(text, environment));
52+
if (error) {
53+
return json({ success: false, error: error.message }, { status: 400 });
5654
}
55+
56+
return json(result);
5757
}

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

Lines changed: 120 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
11
import { openai } from "@ai-sdk/openai";
2-
import { generateObject } from "ai";
2+
import { generateText, Output } from "ai";
33
import { z } from "zod";
44
import { TaskRunListSearchFilters } from "~/components/runs/v3/RunFilters";
5+
import { $replica } from "~/db.server";
56
import { env } from "~/env.server";
7+
import { getAllTaskIdentifiers } from "~/models/task.server";
8+
import { QueueListPresenter } from "~/presenters/v3/QueueListPresenter.server";
9+
import { RunTagListPresenter } from "~/presenters/v3/RunTagListPresenter.server";
10+
import { VersionListPresenter } from "~/presenters/v3/VersionListPresenter.server";
11+
import { type AuthenticatedEnvironment } from "~/services/apiAuth.server";
612
import { logger } from "~/services/logger.server";
713

814
const AIFilterResponseSchema = z.object({
@@ -21,36 +27,133 @@ export type AIFilterResult =
2127
| {
2228
success: false;
2329
error: string;
24-
suggestions?: string[];
30+
suggestions: string;
2531
};
2632

2733
export async function processAIFilter(
2834
text: string,
29-
environmentId: string
35+
environment: AuthenticatedEnvironment
3036
): Promise<AIFilterResult> {
3137
if (!env.OPENAI_API_KEY) {
3238
return {
3339
success: false,
3440
error: "OpenAI API key is not configured",
35-
suggestions: ["Contact your administrator to configure AI features"],
41+
suggestions: "Contact your administrator to configure AI features",
3642
};
3743
}
3844

3945
try {
40-
const result = await generateObject({
46+
// Create presenter instances for lookups
47+
const tagPresenter = new RunTagListPresenter();
48+
const versionPresenter = new VersionListPresenter();
49+
const queuePresenter = new QueueListPresenter();
50+
51+
const result = await generateText({
4152
model: openai("gpt-4o"),
42-
schema: AIFilterResponseSchema,
53+
experimental_output: Output.object({ schema: AIFilterResponseSchema }),
54+
tools: {
55+
lookupTags: {
56+
description: "Look up available tags in the environment",
57+
parameters: z.object({
58+
query: z.string().optional().describe("Optional search query to filter tags"),
59+
}),
60+
execute: async ({ query }) => {
61+
const tags = await tagPresenter.call({
62+
projectId: environment.projectId,
63+
name: query,
64+
page: 1,
65+
pageSize: 50,
66+
});
67+
return {
68+
tags: tags.tags.map((tag) => tag.name),
69+
total: tags.tags.length,
70+
};
71+
},
72+
},
73+
lookupVersions: {
74+
description:
75+
"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.",
76+
parameters: z.object({
77+
isCurrent: z.boolean().optional().describe("If true, only return the current version"),
78+
versionPrefix: z
79+
.string()
80+
.optional()
81+
.describe(
82+
"Optional version name to filter (e.g. 20250701.1), it uses contains to compare. Don't pass `latest` or `current`, the query has to be in the reverse date format specified. Leave out to get all recent versions."
83+
),
84+
}),
85+
execute: async ({ versionPrefix, isCurrent }) => {
86+
const versions = await versionPresenter.call({
87+
environment,
88+
query: versionPrefix ? versionPrefix : undefined,
89+
});
90+
91+
if (isCurrent) {
92+
const currentVersion = versions.versions.find((v) => v.isCurrent);
93+
if (currentVersion) {
94+
return {
95+
version: currentVersion.version,
96+
};
97+
}
98+
99+
if (versions.versions.length > 0) {
100+
return {
101+
version: versions.versions.at(0)?.version,
102+
};
103+
}
104+
}
105+
106+
return {
107+
versions: versions.versions.map((v) => v.version),
108+
};
109+
},
110+
},
111+
lookupQueues: {
112+
description: "Look up available queues in the environment",
113+
parameters: z.object({
114+
query: z.string().optional().describe("Optional search query to filter queues"),
115+
type: z
116+
.enum(["task", "custom"])
117+
.optional()
118+
.describe("Filter by queue type, only do this if the user specifies it explicitly."),
119+
}),
120+
execute: async ({ query, type }) => {
121+
const queues = await queuePresenter.call({
122+
environment,
123+
query,
124+
page: 1,
125+
type,
126+
});
127+
return {
128+
queues: queues.success ? queues.queues.map((q) => q.name) : [],
129+
total: queues.success ? queues.queues.length : 0,
130+
};
131+
},
132+
},
133+
lookupTasks: {
134+
description:
135+
"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`",
136+
parameters: z.object({}),
137+
execute: async () => {
138+
const tasks = await getAllTaskIdentifiers($replica, environment.id);
139+
return {
140+
tasks,
141+
total: tasks.length,
142+
};
143+
},
144+
},
145+
},
43146
prompt: `You are an AI assistant that converts natural language descriptions into structured filter parameters for a task run filtering system.
44147
45148
Available filter options:
46149
- statuses: Array of run statuses (PENDING, EXECUTING, COMPLETED_SUCCESSFULLY, COMPLETED_WITH_ERRORS, CANCELED, TIMED_OUT, CRASHED, etc.)
47150
- period: Time period string (e.g., "1h", "7d", "30d", "1y")
48151
- from/to: Unix ms timestamps for specific time ranges. You'll need to use a converter if they give you a date. Today's date is ${new Date().toISOString()}, if they only specify a day use the current month. If they don't specify a year use the current year. If they don't specify a time of day use midnight to midnight.
49-
- tags: Array of tag names to filter by
50-
- tasks: Array of task identifiers to filter by
152+
- tags: Array of tag names to filter by. Use the lookupTags tool to get the tags.
153+
- tasks: Array of task identifiers to filter by. Use the lookupTasks tool to get the tasks.
51154
- machines: Array of machine presets (micro, small, small-2x, medium, large, xlarge, etc.)
52-
- queues: Array of queue names to filter by
53-
- versions: Array of version identifiers to filter by
155+
- queues: Array of queue names to filter by. Use the lookupQueues tool to get the queues.
156+
- versions: Array of version identifiers to filter by. Use the lookupVersions tool to get the versions. The "latest" version will be the first returned. The "current" or "deployed" version will have isCurrent set to true.
54157
- rootOnly: Boolean to show only root runs (not child runs)
55158
- runId: Array of specific run IDs to filter by
56159
- batchId: Specific batch ID to filter by
@@ -70,6 +173,8 @@ Common patterns to recognize:
70173
- "using large machine" → machines: ["large-1x", "large-2x"]
71174
- "root only" → rootOnly: true
72175
176+
Use the available tools to look up actual tags, versions, queues, and tasks in the environment when the user mentions them. This will help you provide accurate filter values.
177+
73178
Unless they specify they only want root runs, set rootOnly to false.
74179
75180
Convert the following natural language description into structured filters:
@@ -81,20 +186,17 @@ Return only the filters that are explicitly mentioned or can be reasonably infer
81186

82187
return {
83188
success: true,
84-
filters: result.object.filters,
85-
explanation: result.object.explanation,
189+
filters: result.experimental_output.filters,
190+
explanation: result.experimental_output.explanation,
86191
};
87192
} catch (error) {
88-
logger.error("AI filter processing failed", { error, text, environmentId });
193+
logger.error("AI filter processing failed", { error, text, environmentId: environment.id });
89194

90195
return {
91196
success: false,
92197
error: "Failed to process AI filter request",
93-
suggestions: [
94-
"Try being more specific about what you want to filter",
95-
"Use common terms like 'failed runs', 'last 7 days', 'with tag X'",
96-
"Check that your description is clear and unambiguous",
97-
],
198+
suggestions:
199+
"Try being more specific about what you want to filter. Use common terms like 'failed runs', 'last 7 days', 'with tag X'. Check that your description is clear and unambiguous",
98200
};
99201
}
100202
}

0 commit comments

Comments
 (0)