Skip to content

Knowledge for drafts and clean schema clean up #385

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 70 commits into from
Apr 6, 2025
Merged
Show file tree
Hide file tree
Changes from 60 commits
Commits
Show all changes
70 commits
Select commit Hold shift + click to select a range
a48a5b9
add basic knowledge page
elie222 Mar 31, 2025
22ceedd
Merge branch 'main' into knowledge
elie222 Mar 31, 2025
919067c
fetch knowledge
elie222 Mar 31, 2025
bf5e582
mutate on add entry
elie222 Mar 31, 2025
18c285e
Delete knowledge item
elie222 Mar 31, 2025
5747320
clean up code
elie222 Mar 31, 2025
385534c
add knowledge mdc
elie222 Mar 31, 2025
c70351b
Merge branch 'main' into knowledge
elie222 Mar 31, 2025
c20ab29
Merge branch 'main' into knowledge
elie222 Mar 31, 2025
c3fcce7
extract content
elie222 Mar 31, 2025
03db907
Merge branch 'main' into knowledge
elie222 Mar 31, 2025
9bdfc75
fix llm test file
elie222 Mar 31, 2025
838007d
extract knowledge test file
elie222 Mar 31, 2025
dec280d
rename
elie222 Mar 31, 2025
9138a38
logs
elie222 Mar 31, 2025
c2e3308
pass extractor tests
elie222 Mar 31, 2025
25fdd93
Merge branch 'main' into knowledge
elie222 Apr 1, 2025
439623d
package mdc
elie222 Apr 1, 2025
76df1fe
use tiptap for notes
elie222 Apr 1, 2025
803f88a
update package
elie222 Apr 1, 2025
fd56db2
Merge branch 'main' into knowledge
elie222 Apr 2, 2025
7536523
add extract history prompt
elie222 Apr 2, 2025
595467c
adjust extract
elie222 Apr 2, 2025
a0df6a7
generate reply with knowledge
elie222 Apr 3, 2025
10ce011
add economy model
elie222 Apr 3, 2025
5874894
update knowledge.mdc
elie222 Apr 3, 2025
9192d57
Merge branch 'main' into knowledge
elie222 Apr 3, 2025
4141bf9
Merge branch 'main' into knowledge
elie222 Apr 3, 2025
3339acf
adjust copy
elie222 Apr 3, 2025
bbd772a
knowledge migration
elie222 Apr 3, 2025
c8549b2
Merge branch 'main' into knowledge
elie222 Apr 3, 2025
c1c7c60
adjust tiptap styling
elie222 Apr 3, 2025
2af9ad0
fix knowledge not saving
elie222 Apr 3, 2025
3fc3497
Adjust needs reply check
elie222 Apr 3, 2025
c84dc8c
adjust prompt
elie222 Apr 3, 2025
f08719a
Refactor knowledge extraction logging and improve Gmail client access…
elie222 Apr 3, 2025
1624a6c
better logging in dev
elie222 Apr 3, 2025
13a3898
llm date helper
elie222 Apr 3, 2025
7acad75
add email date for llm
elie222 Apr 3, 2025
687531e
use economy model
elie222 Apr 3, 2025
d4c87a6
adjust copy
elie222 Apr 3, 2025
effa81c
show draft via knowledge base as default
elie222 Apr 3, 2025
fba31cd
Merge branch 'main' into knowledge
elie222 Apr 4, 2025
a6a4187
Adjust how we handle reply drafting and reply tracker labelling makin…
elie222 Apr 5, 2025
506b2cc
delete inline generate reply in favour of auto drafting
elie222 Apr 5, 2025
72e16de
migrate draftReplies to knowledge
elie222 Apr 6, 2025
79b2d85
refactor choose ai args into 2 files
elie222 Apr 6, 2025
ecd5ef3
massively restructure how we do reply tracking
elie222 Apr 6, 2025
0809351
migration
elie222 Apr 6, 2025
ac8db9e
fix onboarding
elie222 Apr 6, 2025
093f974
Merge branch 'main' into knowledge
elie222 Apr 6, 2025
a7a2a09
move feature rules to their own folder
elie222 Apr 6, 2025
13c8b75
add copy to kb page and show loading spinner for delete
elie222 Apr 6, 2025
9c7e57c
fix build
elie222 Apr 6, 2025
9eac680
Add reply tracker mdc
elie222 Apr 6, 2025
9c2a3e0
fix tests
elie222 Apr 6, 2025
27478db
remove old code
elie222 Apr 6, 2025
be79cde
adjust env var
elie222 Apr 6, 2025
e8121ac
adjust economy model handling
elie222 Apr 6, 2025
ecca97b
simplify to avoid recursion
elie222 Apr 6, 2025
ed8e5de
adjust open router usage
elie222 Apr 6, 2025
baacc47
delete old file
elie222 Apr 6, 2025
35f5c12
gracefully handle llm replying with string instead of boolean
elie222 Apr 6, 2025
17b1bbf
don't process reply tracker email if it's not the latest in the thread
elie222 Apr 6, 2025
fe886a6
fix build
elie222 Apr 6, 2025
ebe3ef3
label message instead of thread
elie222 Apr 6, 2025
821899e
adjust current time for llm
elie222 Apr 6, 2025
0f6b99b
adjust prompt
elie222 Apr 6, 2025
5007733
update getTodayForLLM function to accept a date parameter for improve…
elie222 Apr 6, 2025
b197f03
Add logging for missing API key in economy LLM provider selection
elie222 Apr 6, 2025
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
27 changes: 0 additions & 27 deletions .cursor/analytics.md

This file was deleted.

24 changes: 0 additions & 24 deletions .cursor/rules/cursor-rules-analytics.mdc

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ alwaysApply: false
---
## Inbox Cleaner

This file explains the Inbox Cleaner feature and how it's implemented.

The inbox cleaner helps users do a deep clean of their inbox.
It helps them get from 10,000 items in their inbox to only a few.
It works by archiving/marking read low priority emails.
Expand Down
92 changes: 92 additions & 0 deletions .cursor/rules/features/knowledge.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
---
description:
globs:
alwaysApply: false
---
# Knowledge Base

This file explains the Knowledge Base feature and how it's implemented.

The knowledge base helps users store and manage information that can be used to help draft responses to emails. It acts as a personal database of information that can be referenced when composing replies.

## Overview

Users can create, edit, and delete knowledge base entries. Each entry consists of:

- A title for quick reference
- Content that contains the actual information
- Metadata like creation and update timestamps

## Database Schema

The `Knowledge` model in Prisma:

```prisma
model Knowledge {
id String @id @default(cuid())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
title String
content String

userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
}
```

Each knowledge entry belongs to a specific user and is automatically deleted if the user is deleted (cascade).

## Main Files and Directories

The knowledge base functionality is implemented in:

- `apps/web/app/(app)/automation/knowledge/KnowledgeBase.tsx` - Main UI component
- `apps/web/app/(app)/automation/knowledge/KnowledgeForm.tsx` - Form for creating/editing entries
- `apps/web/utils/actions/knowledge.ts` - Server actions for CRUD operations
- `apps/web/utils/actions/knowledge.validation.ts` - Zod validation schemas
- `apps/web/app/api/knowledge/route.ts` - API route for fetching entries

### AI Integration Files

- `apps/web/utils/ai/knowledge/extract.ts` - Extract relevant knowledge from knowledge base entries
- `apps/web/utils/ai/knowledge/extract-from-email-history.ts` - Extract context from previous emails
- `apps/web/utils/ai/reply/draft-with-knowledge.ts` - Generate email drafts using extracted knowledge
- `apps/web/utils/reply-tracker/generate-reply.ts` - Coordinates the extraction and drafting process
- `apps/web/utils/llms/model-selector.ts` - Economy LLM selection for high-volume tasks

## Features

- **Create**: Users can add new knowledge entries with a title and content
- **Read**: Entries are displayed in a table with title and last updated date
- **Update**: Users can edit existing entries
- **Delete**: Entries can be deleted with a confirmation dialog

## Usage in Email Responses

The knowledge base entries are used to help draft responses to emails. When composing a reply, the system can reference these entries to include relevant information, ensuring consistent and accurate responses.

When drafting responses, we use two LLMs:

1. A cheaper LLM that can process a lot of data (e.g. Google Gemini 2 Flash)
2. A more expensive LLM to draft the response (e.g. Anthropic Sonnet 3.7)

The cheaper LLM is an agent that extracts the key information needed for the drafter LLM.
For example, the knowledge base may include 100 pages of content, and the LLM extracts half a page of knowledge to pass to the more expensive drafter LLM.

## Dual LLM Architecture

The dual LLM approach is implemented as follows:

1. **Knowledge Extraction (Economy LLM)**:

- Uses a more cost-efficient model like Gemini Flash for processing large volumes of knowledge base content
- Analyzes all knowledge entries and extracts only relevant information based on the email content
- Configured via environment variables (`ECONOMY_LLM_PROVIDER` and `ECONOMY_LLM_MODEL`)
- If no specific economy model is configured, defaults to Gemini Flash when Google API key is available

2. **Email Draft Generation (Core LLM)**:
- Uses the default model (e.g., Anthropic Claude 3.7 Sonnet) for high-quality content generation
- Receives the extracted relevant knowledge from the economy LLM
- Generates the final email draft based on the provided context

This architecture optimizes for both cost efficiency (using cheaper models for high-volume tasks) and quality (using premium models for user-facing content).
25 changes: 25 additions & 0 deletions .cursor/rules/features/reply-tracker.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
---
description:
globs:
alwaysApply: false
---
# Reply Tracker

Reply Tracker (also known as Reply Zero) lets the user which emails need a reply for them and those they're awaiting a reply on.
It updates the labels for the thread automatically in Gmail.

The database models and fields that are used for this feature:

- ThreadTracker
- User.outboundReplyTracking
- ActionType.TRACK_THREAD

The system uses rules. The AI can choose which rule to use each time an email comes in. Rules are for incoming emails only. Not ongoing.
When enabling the reply tracker, we create a rule for the user that has the following actions associated with it:
- LABEL: "To Reply"
- TRACK_THREAD
- DRAFT_EMAIL (optional)

We'll draft a reply for the user automatically if DRAFT_EMAIL is set. The draft will be generated using the email history for this sender as well as the Knowledge base. See `.cursor/rules/features/knowledge.mdc` for more on the Knowledge Base feature.

Enabling `User.outboundReplyTracking` means that when a user sends an email, we'll run an LLM over the email and check if it needs a reply. If it does we'll mark it as Awaiting Reply.
12 changes: 12 additions & 0 deletions .cursor/rules/installing-packages.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
description: How to install packages
globs:
alwaysApply: false
---
- Use `pnpm`.
- Don't install in root. Install in `apps/web`:

```sh
cd apps/web
pnpm add ...
```
8 changes: 4 additions & 4 deletions .cursor/rules/llm-test.mdc
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,8 @@ Tests for LLM-related functionality should follow these guidelines to ensure con

```
apps/web/__tests__/
├── ai/
│ └── your-feature.test.ts
│ └── another-feature.test.ts
│ └── your-feature.test.ts
│ └── another-feature.test.ts
└── ...
```

Expand All @@ -25,7 +24,8 @@ Tests for LLM-related functionality should follow these guidelines to ensure con
import { describe, expect, test, vi, beforeEach } from "vitest";
import { yourFunction } from "@/utils/ai/your-feature";

// Required for Next.js compatibility
// Run with: pnpm test-ai TEST

vi.mock("server-only", () => ({}));

// Skip tests unless explicitly running AI tests
Expand Down
50 changes: 37 additions & 13 deletions apps/web/__tests__/ai-choose-args.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { describe, expect, test, vi } from "vitest";
import { getActionItemsWithAiArgs } from "@/utils/ai/choose-rule/ai-choose-args";
import { type Action, ActionType, LogicalOperator } from "@prisma/client";
import type { RuleWithActions } from "@/utils/types";
import type { ParsedMessage, RuleWithActions } from "@/utils/types";
import { getActionItemsWithAiArgs } from "@/utils/ai/choose-rule/choose-args";

// pnpm test-ai ai-choose-args

Expand All @@ -15,9 +15,13 @@ describe.skipIf(!isAiTest)("getActionItemsWithAiArgs", () => {
const rule = getRule("Test rule", actions);

const result = await getActionItemsWithAiArgs({
email: getEmail(),
message: getParsedMessage({
subject: "Test subject",
content: "Test content",
}),
user: getUser(),
selectedRule: rule,
gmail: {} as any,
});

expect(result).toEqual(actions);
Expand All @@ -33,12 +37,13 @@ describe.skipIf(!isAiTest)("getActionItemsWithAiArgs", () => {
const rule = getRule("Choose this rule for meeting requests", actions);

const result = await getActionItemsWithAiArgs({
email: getEmail({
message: getParsedMessage({
subject: "Quick question",
content: "When is the meeting tomorrow?",
}),
user: getUser(),
selectedRule: rule,
gmail: {} as any,
});

expect(result).toHaveLength(1);
Expand All @@ -59,12 +64,13 @@ describe.skipIf(!isAiTest)("getActionItemsWithAiArgs", () => {
);

const result = await getActionItemsWithAiArgs({
email: getEmail({
message: getParsedMessage({
subject: "Quick question",
content: "How much are pears?",
}),
user: getUser(),
selectedRule: rule,
gmail: {} as any,
});

expect(result).toHaveLength(1);
Expand All @@ -85,12 +91,13 @@ describe.skipIf(!isAiTest)("getActionItemsWithAiArgs", () => {
const rule = getRule("Test rule", actions);

const result = await getActionItemsWithAiArgs({
email: getEmail({
message: getParsedMessage({
subject: "Project status",
content: "Can you update me on the project status?",
}),
user: getUser(),
selectedRule: rule,
gmail: {} as any,
});

expect(result).toHaveLength(2);
Expand Down Expand Up @@ -120,13 +127,14 @@ Matt`,
);

const result = await getActionItemsWithAiArgs({
email: getEmail({
message: getParsedMessage({
from: "[email protected]",
subject: "fruits",
content: "how much do apples cost?",
}),
user: getUser(),
selectedRule: rule,
gmail: {} as any,
});

expect(result).toHaveLength(2);
Expand Down Expand Up @@ -190,24 +198,40 @@ function getRule(
categoryFilterType: null,
conditionalOperator: LogicalOperator.AND,
type: null,
trackReplies: null,
};
}

function getEmail({
function getParsedMessage({
from = "[email protected]",
subject = "subject",
content = "content",
}: { from?: string; subject?: string; content?: string } = {}) {
}): ParsedMessage {
return {
from,
subject,
content,
id: "id",
threadId: "thread-id",
snippet: "",
attachments: [],
historyId: "history-id",
sizeEstimate: 100,
internalDate: new Date().toISOString(),
inline: [],
textPlain: content,
// ...message,
headers: {
from,
to: "[email protected]",
subject,
date: new Date().toISOString(),
references: "",
"message-id": "message-id",
// ...message.headers,
},
};
}

function getUser() {
return {
id: "userId",
aiModel: null,
aiProvider: null,
email: "[email protected]",
Expand Down
Loading