Skip to content

Commit baa36da

Browse files
committed
Refactor AI rule selection to include rule names and improve instruction formatting. Update response structure to use rule names instead of numbers for better clarity.
1 parent aa4fd3f commit baa36da

File tree

2 files changed

+39
-33
lines changed

2 files changed

+39
-33
lines changed

apps/web/utils/ai/choose-rule/ai-choose-rule.ts

+38-33
Original file line numberDiff line numberDiff line change
@@ -13,41 +13,49 @@ const braintrust = new Braintrust("choose-rule-1");
1313
type GetAiResponseOptions = {
1414
email: EmailForLLM;
1515
user: UserEmailWithAI;
16-
rules: { instructions: string }[];
16+
rules: { name: string; instructions: string }[];
1717
};
1818

1919
async function getAiResponse(options: GetAiResponseOptions) {
2020
const { email, user, rules } = options;
2121

22-
const specialRuleNumber = rules.length + 1;
23-
2422
const emailSection = stringifyEmail(email, 500);
2523

2624
const system = `You are an AI assistant that helps people manage their emails.
2725
2826
<instructions>
29-
IMPORTANT: Follow these instructions carefully when selecting a rule:
30-
31-
<priority>
32-
1. Match the email to a SPECIFIC user-defined rule that addresses the email's exact content or purpose.
33-
2. If the email doesn't match any specific rule but the user has a catch-all rule (like "emails that don't match other criteria"), use that catch-all rule.
34-
3. Only use rule #${specialRuleNumber} (system fallback) if no user-defined rule can reasonably apply.
35-
</priority>
36-
37-
<guidelines>
38-
- If a rule says to exclude certain types of emails, DO NOT select that rule for those excluded emails.
39-
- When multiple rules match, choose the more specific one that best matches the email's content.
40-
- Rules about requiring replies should be prioritized when the email clearly needs a response.
41-
- Rule #${specialRuleNumber} should ONLY be selected when there is absolutely no user-defined rule that could apply.
42-
</guidelines>
27+
IMPORTANT: Follow these instructions carefully when selecting a rule:
28+
29+
<priority>
30+
1. Match the email to a SPECIFIC user-defined rule that addresses the email's exact content or purpose.
31+
2. If the email doesn't match any specific rule but the user has a catch-all rule (like "emails that don't match other criteria"), use that catch-all rule.
32+
3. Only use rule system fallback if no user-defined rule can reasonably apply.
33+
</priority>
34+
35+
<guidelines>
36+
- If a rule says to exclude certain types of emails, DO NOT select that rule for those excluded emails.
37+
- When multiple rules match, choose the more specific one that best matches the email's content.
38+
- Rules about requiring replies should be prioritized when the email clearly needs a response.
39+
- The system fallback rule should ONLY be selected when there is absolutely no user-defined rule that could apply.
40+
</guidelines>
4341
</instructions>
4442
4543
<user_rules>
46-
${rules.map((rule, i) => `${i + 1}. ${rule.instructions}`).join("\n")}
44+
${rules
45+
.map(
46+
(rule) => `<rule>
47+
<name>${rule.name}</name>
48+
<instructions>${rule.instructions}</instructions>
49+
</rule>`,
50+
)
51+
.join("\n")}
4752
</user_rules>
4853
4954
<system_fallback>
50-
${specialRuleNumber}. None of the other rules match or not enough information to make a decision.
55+
<name>System fallback</name>
56+
<instructions>
57+
The system fallback rule should ONLY be selected when there is absolutely no user-defined rule that could apply.
58+
</instructions>
5159
</system_fallback>
5260
5361
${
@@ -64,7 +72,7 @@ ${
6472
<outputFormat>
6573
Respond with a JSON object with the following fields:
6674
"reason" - the reason you chose that rule. Keep it concise.
67-
"rule" - the number of the rule you want to apply
75+
"ruleName" - the exact name of the rule you want to apply
6876
</outputFormat>`;
6977

7078
const prompt = `Select a rule to apply to this email that was sent to me:
@@ -97,7 +105,7 @@ ${emailSection}
97105
],
98106
schema: z.object({
99107
reason: z.string(),
100-
rule: z.number(),
108+
ruleName: z.string(),
101109
}),
102110
userEmail: user.email || "",
103111
usageLabel: "Choose rule",
@@ -109,23 +117,22 @@ ${emailSection}
109117
id: email.id,
110118
input: {
111119
email: emailSection,
112-
rules: rules.map((rule, i) => ({
113-
ruleNumber: i + 1,
120+
rules: rules.map((rule) => ({
121+
name: rule.name,
114122
instructions: rule.instructions,
115123
})),
116124
hasAbout: !!user.about,
117125
userAbout: user.about,
118126
userEmail: user.email,
119-
specialRuleNumber,
120127
},
121-
expected: aiResponse.object.rule,
128+
expected: aiResponse.object.ruleName,
122129
});
123130

124131
return aiResponse.object;
125132
}
126133

127134
export async function aiChooseRule<
128-
T extends { instructions: string },
135+
T extends { name: string; instructions: string },
129136
>(options: { email: EmailForLLM; rules: T[]; user: UserEmailWithAI }) {
130137
const { email, rules, user } = options;
131138

@@ -137,13 +144,11 @@ export async function aiChooseRule<
137144
user,
138145
});
139146

140-
const ruleNumber = aiResponse ? aiResponse.rule - 1 : undefined;
141-
if (typeof ruleNumber !== "number") {
142-
logger.warn("No rule selected");
143-
return { reason: aiResponse?.reason };
144-
}
145-
146-
const selectedRule = rules[ruleNumber];
147+
const selectedRule = aiResponse.ruleName
148+
? rules.find(
149+
(rule) => rule.name.toLowerCase() === aiResponse.ruleName.toLowerCase(),
150+
)
151+
: undefined;
147152

148153
return {
149154
rule: selectedRule,

apps/web/utils/reply-tracker/inbound.ts

+1
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ export async function handleInboundReply(
132132
email: getEmailForLLM(message),
133133
rules: replyTrackingRules.map((rule) => ({
134134
id: rule.id,
135+
name: rule.name,
135136
instructions: rule.instructions || "",
136137
})),
137138
user,

0 commit comments

Comments
 (0)