mirror of
https://github.com/Mail-0/Zero.git
synced 2026-06-28 14:56:48 +00:00
Make AutoRAG optional and improve thread summary fallback (#1888)
# READ CAREFULLY THEN REMOVE Remove bullet points that are not relevant. PLEASE REFRAIN FROM USING AI TO WRITE YOUR CODE AND PR DESCRIPTION. IF YOU DO USE AI TO WRITE YOUR CODE PLEASE PROVIDE A DESCRIPTION AND REVIEW IT CAREFULLY. MAKE SURE YOU UNDERSTAND THE CODE YOU ARE SUBMITTING USING AI. - Pull requests that do not follow these guidelines will be closed without review or comment. - If you use AI to write your PR description your pr will be close without review or comment. - If you are unsure about anything, feel free to ask for clarification. ## Description Please provide a clear description of your changes. --- ## Type of Change Please delete options that are not relevant. - [ ] 🐛 Bug fix (non-breaking change which fixes an issue) - [ ] ✨ New feature (non-breaking change which adds functionality) - [ ] 💥 Breaking change (fix or feature with breaking changes) - [ ] 📝 Documentation update - [ ] 🎨 UI/UX improvement - [ ] 🔒 Security enhancement - [ ] ⚡ Performance improvement ## Areas Affected Please check all that apply: - [ ] Email Integration (Gmail, IMAP, etc.) - [ ] User Interface/Experience - [ ] Authentication/Authorization - [ ] Data Storage/Management - [ ] API Endpoints - [ ] Documentation - [ ] Testing Infrastructure - [ ] Development Workflow - [ ] Deployment/Infrastructure ## Testing Done Describe the tests you've done: - [ ] Unit tests added/updated - [ ] Integration tests added/updated - [ ] Manual testing performed - [ ] Cross-browser testing (if UI changes) - [ ] Mobile responsiveness verified (if UI changes) ## Security Considerations For changes involving data or authentication: - [ ] No sensitive data is exposed - [ ] Authentication checks are in place - [ ] Input validation is implemented - [ ] Rate limiting is considered (if applicable) ## Checklist - [ ] I have read the [CONTRIBUTING](https://github.com/Mail-0/Zero/blob/staging/.github/CONTRIBUTING.md) document - [ ] My code follows the project's style guidelines - [ ] I have performed a self-review of my code - [ ] I have commented my code, particularly in complex areas - [ ] I have updated the documentation - [ ] My changes generate no new warnings - [ ] I have added tests that prove my fix/feature works - [ ] All tests pass locally - [ ] Any dependent changes are merged and published ## Additional Notes Add any other context about the pull request here. ## Screenshots/Recordings Add screenshots or recordings here if applicable. --- _By submitting this pull request, I confirm that my contribution is made under the terms of the project's license._ <!-- This is an auto-generated description by cubic. --> --- ## Summary by cubic Made AutoRAG optional so it only runs when configured, and improved thread summary fallback to always return basic info if a summary is missing. - **Bug Fixes** - Thread summary now returns subject, sender, and date even if no summary is found. - AutoRAG is only used when enabled, with a proper fallback to raw search results. <!-- End of auto-generated description by cubic. --> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Introduced a new inbox search tool supporting natural language queries with configurable result limits. * Enabled conditional use of advanced search features based on environment settings. * **Improvements** * Clarified tool parameter descriptions. * Enhanced tool registration to accept dynamic header values. * **Refactor** * Improved internal handling of request headers and tool configuration. * **Chores** * Removed default fallback server URL in tool registration scripts. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -1371,21 +1371,29 @@ export class ZeroDriver extends Agent<ZeroEnv> {
|
||||
}).then((r) => r.threads.map((t) => t.id)),
|
||||
).pipe(Effect.catchAll(() => Effect.succeed([])));
|
||||
|
||||
const effects: Effect.Effect<string[]>[] = [rawEffect];
|
||||
if (this.env.AUTORAG_ID) effects.unshift(ragEffect as Effect.Effect<string[]>);
|
||||
|
||||
// Run both in parallel and wait for results
|
||||
const results = await Effect.runPromise(
|
||||
Effect.all([ragEffect, rawEffect], { concurrency: 'unbounded' }),
|
||||
);
|
||||
const results = await Effect.runPromise(Effect.all(effects, { concurrency: 'unbounded' }));
|
||||
if (this.env.AUTORAG_ID) {
|
||||
const [ragIds, rawIds] = results;
|
||||
|
||||
const [ragIds, rawIds] = results;
|
||||
// Return InboxRag results if found, otherwise fallback to raw
|
||||
if (ragIds.length > 0) {
|
||||
return {
|
||||
threadIds: ragIds,
|
||||
source: 'autorag' as const,
|
||||
};
|
||||
}
|
||||
|
||||
// Return InboxRag results if found, otherwise fallback to raw
|
||||
if (ragIds.length > 0) {
|
||||
return {
|
||||
threadIds: ragIds,
|
||||
source: 'autorag' as const,
|
||||
threadIds: rawIds,
|
||||
source: 'raw' as const,
|
||||
nextPageToken: pageToken,
|
||||
};
|
||||
}
|
||||
|
||||
const [rawIds] = results;
|
||||
return {
|
||||
threadIds: rawIds,
|
||||
source: 'raw' as const,
|
||||
|
||||
@@ -136,7 +136,9 @@ const getThreadSummary = (connectionId: string) =>
|
||||
const thread = await driver.getThread(id);
|
||||
if (response.length && response?.[0]?.metadata?.['summary'] && thread?.latest?.subject) {
|
||||
const result = response[0].metadata as { summary: string; connection: string };
|
||||
if (result.connection !== connectionId) return null;
|
||||
if (result.connection !== connectionId) {
|
||||
return null;
|
||||
}
|
||||
const shortResponse = await env.AI.run('@cf/facebook/bart-large-cnn', {
|
||||
input_text: result.summary,
|
||||
});
|
||||
@@ -147,7 +149,11 @@ const getThreadSummary = (connectionId: string) =>
|
||||
date: thread.latest?.receivedOn,
|
||||
};
|
||||
}
|
||||
return null;
|
||||
return {
|
||||
subject: thread.latest?.subject,
|
||||
sender: thread.latest?.sender,
|
||||
date: thread.latest?.receivedOn,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
@@ -448,8 +454,8 @@ export const webSearch = () =>
|
||||
},
|
||||
});
|
||||
|
||||
export const tools = async (connectionId: string) => {
|
||||
return {
|
||||
export const tools = async (connectionId: string, ragEffect: boolean = false) => {
|
||||
const _tools = {
|
||||
[Tools.GetThread]: getEmail(),
|
||||
[Tools.GetThreadSummary]: getThreadSummary(connectionId),
|
||||
[Tools.ComposeEmail]: composeEmailTool(connectionId),
|
||||
@@ -464,6 +470,23 @@ export const tools = async (connectionId: string) => {
|
||||
[Tools.DeleteLabel]: deleteLabel(connectionId),
|
||||
[Tools.BuildGmailSearchQuery]: buildGmailSearchQuery(),
|
||||
[Tools.GetCurrentDate]: getCurrentDate(),
|
||||
[Tools.InboxRag]: tool({
|
||||
description:
|
||||
'Search the inbox for emails using natural language. Returns only an array of threadIds.',
|
||||
parameters: z.object({
|
||||
query: z.string().describe('The query to search the inbox for'),
|
||||
maxResults: z.number().describe('The maximum number of results to return').default(10),
|
||||
}),
|
||||
execute: async ({ query, maxResults }) => {
|
||||
const agent = await getZeroAgent(connectionId);
|
||||
const res = await agent.searchThreads({ query, maxResults });
|
||||
return res.threadIds;
|
||||
},
|
||||
}),
|
||||
};
|
||||
if (ragEffect) return _tools;
|
||||
return {
|
||||
..._tools,
|
||||
[Tools.InboxRag]: tool({
|
||||
description:
|
||||
'Search the inbox for emails using natural language. Returns only an array of threadIds.',
|
||||
|
||||
@@ -2,6 +2,7 @@ import { systemPrompt } from '../services/call-service/system-prompt';
|
||||
import { openai } from '@ai-sdk/openai';
|
||||
import { tools } from './agent/tools';
|
||||
import { generateText } from 'ai';
|
||||
import { Tools } from '../types';
|
||||
import { createDb } from '../db';
|
||||
import { env } from '../env';
|
||||
import { Hono } from 'hono';
|
||||
@@ -28,11 +29,12 @@ aiRouter.post('/do/:action', async (c) => {
|
||||
// if (env.DISABLE_CALLS) return c.json({ success: false, error: 'Not implemented' }, 400);
|
||||
if (env.VOICE_SECRET !== c.req.header('X-Voice-Secret'))
|
||||
return c.json({ success: false, error: 'Unauthorized' }, 401);
|
||||
if (!c.req.header('X-Caller')) return c.json({ success: false, error: 'Unauthorized' }, 401);
|
||||
const caller = c.req.header('X-Caller');
|
||||
if (!caller) return c.json({ success: false, error: 'Unauthorized' }, 401);
|
||||
const { db, conn } = createDb(env.HYPERDRIVE.connectionString);
|
||||
const user = await db.query.user.findFirst({
|
||||
where: (user, { eq, and }) =>
|
||||
and(eq(user.phoneNumber, c.req.header('X-Caller')!), eq(user.phoneNumberVerified, true)),
|
||||
and(eq(user.phoneNumber, caller), eq(user.phoneNumberVerified, true)),
|
||||
});
|
||||
if (!user) return c.json({ success: false, error: 'Unauthorized' }, 401);
|
||||
|
||||
@@ -44,12 +46,12 @@ aiRouter.post('/do/:action', async (c) => {
|
||||
if (!connection) return c.json({ success: false, error: 'Unauthorized' }, 401);
|
||||
|
||||
try {
|
||||
const action = c.req.param('action');
|
||||
const action = c.req.param('action') as keyof ToolsReturnType;
|
||||
const body = await c.req.json();
|
||||
console.log('[DEBUG] action', action, body);
|
||||
|
||||
// Get all tools for this connection
|
||||
const toolset: ToolsReturnType = await tools(connection.id);
|
||||
const toolset: ToolsReturnType = await tools(connection.id, action === Tools.InboxRag);
|
||||
const tool = toolset[action as keyof ToolsReturnType];
|
||||
|
||||
if (!tool) {
|
||||
|
||||
@@ -40,7 +40,7 @@ export const toolDefinitions: ToolDefinition[] = [
|
||||
name: Tools.GetThreadSummary,
|
||||
description: 'Get the summary of a specific email thread',
|
||||
parameters: z.object({
|
||||
id: z.string().describe('The ID of the email thread to get the summary of'),
|
||||
id: z.string().describe('The threadId of the email thread to get the summary of'),
|
||||
}),
|
||||
},
|
||||
{
|
||||
@@ -189,6 +189,7 @@ export const toolDefinitions: ToolDefinition[] = [
|
||||
'Search the inbox for emails using natural language. Returns only an array of threadIds.',
|
||||
parameters: z.object({
|
||||
query: z.string().describe('The query to search the inbox for'),
|
||||
maxResults: z.number().describe('The maximum number of results to return').default(10),
|
||||
}),
|
||||
},
|
||||
];
|
||||
@@ -206,7 +207,7 @@ interface ElevenLabsToolRequest {
|
||||
properties: any;
|
||||
required?: string[];
|
||||
};
|
||||
request_headers: Record<string, string>;
|
||||
request_headers: Record<string, string | { variable_name: string }>;
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -477,7 +478,7 @@ async function updateAgent(apiKey: string, agentId: string, toolIds: string[]):
|
||||
|
||||
async function main() {
|
||||
const apiKey = process.env.ELEVENLABS_API_KEY;
|
||||
const serverUrl = process.env.SERVER_URL || 'https://staging.0.email';
|
||||
const serverUrl = process.env.SERVER_URL;
|
||||
const voiceSecret = process.env.VOICE_SECRET;
|
||||
const agentId = process.env.ELEVENLABS_AGENT_ID;
|
||||
|
||||
@@ -557,7 +558,9 @@ async function main() {
|
||||
request_headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Voice-Secret': voiceSecret,
|
||||
'X-Caller': 'system__caller_id',
|
||||
'X-Caller': {
|
||||
variable_name: 'system__caller_id',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user