Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions apps/web/__tests__/ai-diff-rules.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { describe, it, expect, vi } from "vitest";
import { aiDiffRules } from "@/utils/ai/rule/diff-rules";
import { getEmailAccount } from "@/__tests__/helpers";

// pnpm test-ai ai-diff-rules
// RUN_AI_TESTS=true pnpm test-ai ai-diff-rules

const isAiTest = process.env.RUN_AI_TESTS === "true";

Expand Down Expand Up @@ -33,13 +33,14 @@ describe.runIf(isAiTest)("aiDiffRules", () => {
});

expect(result).toEqual({
addedRules: ['Label all emails from support@company.com as "Support"'],
addedRules: ['* Label all emails from support@company.com as "Support"'],
editedRules: [
{
oldRule: `Archive all newsletters and label them "Newsletter"`,
newRule: `Archive all newsletters and label them "Newsletter Updates"`,
oldRule: `* Archive all newsletters and label them "Newsletter"`,
newRule: `* Archive all newsletters and label them "Newsletter Updates"`,
},
],
removedRules: [`* Label receipts as "Receipt"`],
});
}, 15_000);

Expand Down
2 changes: 1 addition & 1 deletion apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
"@mux/mux-player-react": "3.4.0",
"@next/mdx": "15.3.3",
"@next/third-parties": "15.3.3",
"@openrouter/ai-sdk-provider": "1.0.0-beta.6",
"@openrouter/ai-sdk-provider": "1.1.0",
"@portabletext/react": "3.2.1",
"@prisma/client": "6.6.0",
"@radix-ui/react-alert-dialog": "1.1.14",
Expand Down
59 changes: 33 additions & 26 deletions apps/web/utils/ai/rule/diff-rules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,7 @@ import z from "zod";
import { createPatch } from "diff";
import type { EmailAccountWithAI } from "@/utils/llms/types";
import { getModel } from "@/utils/llms/model";
import { createGenerateText } from "@/utils/llms";

const inputSchema = z.object({
addedRules: z.array(z.string()).describe("The added rules"),
editedRules: z
.array(
z.object({
oldRule: z.string().describe("The old rule"),
newRule: z.string().describe("The new rule"),
}),
)
.describe("The edited rules"),
removedRules: z.array(z.string()).describe("The removed rules"),
});
import { createGenerateObject } from "@/utils/llms";

export async function aiDiffRules({
emailAccount,
Expand Down Expand Up @@ -53,11 +40,26 @@ Organize your response using the 'diff_rules' function.

IMPORTANT: Do not include a rule in more than one category. If a rule is edited, do not include it in the 'removedRules' category!
If a rule is edited, it is an edit and not a removal! Be extra careful to not make this mistake.

Return the result in JSON format. Do not include any other text in your response.

<example>
{
"addedRules": ["rule text1", "rule text2"],
"editedRules": [
{
"oldRule": "rule text3",
"newRule": "rule text4 updated"
},
],
"removedRules": ["rule text5", "rule text6"]
}
</example>
`;

const modelOptions = getModel(emailAccount.user, "chat");

const generateObject = createGenerateText({
const generateObject = createGenerateObject({
userEmail: emailAccount.email,
label: "Diff rules",
modelOptions,
Expand All @@ -67,17 +69,22 @@ If a rule is edited, it is an edit and not a removal! Be extra careful to not ma
...modelOptions,
system,
prompt,
tools: {
diff_rules: {
description:
"Analyze two prompt files and their diff to return the differences",
inputSchema,
},
},
schemaName: "diff_rules",
schemaDescription:
"The result of the diff rules analysis. Return the result in JSON format. Do not include any other text in your response.",
schema: z.object({
addedRules: z.array(z.string()).describe("The added rules"),
editedRules: z
.array(
z.object({
oldRule: z.string().describe("The old rule"),
newRule: z.string().describe("The new rule"),
}),
)
.describe("The edited rules"),
removedRules: z.array(z.string()).describe("The removed rules"),
}),
});

const parsedRules = result.toolCalls?.[0]?.input as z.infer<
typeof inputSchema
>;
return parsedRules;
return result.object;
}
10 changes: 6 additions & 4 deletions apps/web/utils/llms/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ import { createScopedLogger } from "@/utils/logger";

const logger = createScopedLogger("llms");

const MAX_LOG_LENGTH = 200;

const commonOptions: {
experimental_telemetry: { isEnabled: boolean };
headers?: Record<string, string>;
Expand All @@ -53,8 +55,8 @@ export function createGenerateText({
const generate = async (model: LanguageModelV2) => {
logger.trace("Generating text", {
label,
system: options.system?.slice(0, 200),
prompt: options.prompt?.slice(0, 200),
system: options.system?.slice(0, MAX_LOG_LENGTH),
prompt: options.prompt?.slice(0, MAX_LOG_LENGTH),
});

const result = await generateText(
Expand Down Expand Up @@ -128,8 +130,8 @@ export function createGenerateObject({

logger.trace("Generating object", {
label,
system: options.system?.slice(0, 200),
prompt: options.prompt?.slice(0, 200),
system: options.system?.slice(0, MAX_LOG_LENGTH),
prompt: options.prompt?.slice(0, MAX_LOG_LENGTH),
});

const result = await generateObject(
Expand Down
1 change: 1 addition & 0 deletions biome.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"useCollapsedElseIf": "off"
},
"suspicious": {
"noConsole": "warn",
"noExplicitAny": "off",
"noArrayIndexKey": "off",
"noEmptyBlockStatements": "off",
Expand Down
12 changes: 6 additions & 6 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading