Skip to content

Add Sampling #2

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

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
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
2 changes: 1 addition & 1 deletion src/db/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -447,7 +447,7 @@ export class DB {
`)

const insertResult = await ps
.bind(validatedTag.name, validatedTag.description, userId)
.bind(validatedTag.name, validatedTag.description ?? null, userId)
.run()

if (!insertResult.success || !insertResult.meta.last_row_id) {
Expand Down
2 changes: 1 addition & 1 deletion src/db/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ export const tagSchema = z.object({

export const newTagSchema = z.object({
name: z.string(),
description: z.string().nullable().optional(),
description: z.string().optional(),
})

export const entryTagSchema = z.object({
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export class EpicMeMCP extends McpAgent<Env, State, Props> {
resources: {},
completions: {},
logging: {},
sampling: {},
},
instructions: `
EpicMe is a journaling app that allows users to write about and review their experiences, thoughts, and reflections.
Expand Down
91 changes: 89 additions & 2 deletions src/tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,9 +163,96 @@ Please ask them explicitely for their email address and don't just guess.
})
}
}
return createReply(
`Entry "${createdEntry.title}" created successfully with ID "${createdEntry.id}"`,

// At this point, we're technically done, but we're going to ask their
// LLM to suggest tags to be added to the entry.
// TODO: make this happen after the response is sent to the user.

const existingTags = await agent.db.getTags(user.id)
const existingTagNames = existingTags.map((t) => t.name)
const currentTags = await agent.db.getEntryTags(
user.id,
createdEntry.id,
)
const currentTagNames = currentTags.map((t) => t.name)

const result = await agent.server.server.createMessage({
messages: [
{
role: 'user',
content: {
type: 'text',
mimeType: 'text/plain',
text: `
Based on this journal entry, suggest relevant tags. Consider the title, content, mood, location, and weather. Only suggest tags that are not already applied to this entry. Feel free to suggest new tags that are not currently in the database and they will be created.

<entry
id="${createdEntry.id}"
title="${createdEntry.title}"
mood="${createdEntry.mood || 'not specified'}"
location="${createdEntry.location || 'not specified'}"
weather="${createdEntry.weather || 'not specified'}"
is-private="${createdEntry.isPrivate ? 'true' : 'false'}"
is-favorite="${createdEntry.isFavorite ? 'true' : 'false'}"
tags="${currentTagNames.join(', ')}"
>
${createdEntry.content}
</entry>

Existing tags already in the database: ${existingTagNames.join(', ')}

Respond with a JSON array of tag names only. If you don't suggest any tags, respond with an empty array.
`.trim(),
},
},
],
systemPrompt:
'You are a helpful assistant that suggests relevant tags for journal entries. Only suggest tags that would be useful for categorizing and finding entries later. Respond with a JSON array of tag names only.',
maxTokens: 1000,
})

const responseSchema = z.object({
content: z.object({
type: z.literal('text'),
text: z
.string()
.transform((text) =>
z.array(z.string()).parse(JSON.parse(text)),
),
}),
})
const parsedResult = responseSchema.parse(result)
const suggestedTags = parsedResult.content.text

const newTags = suggestedTags.filter(
(tag: string) => !existingTagNames.includes(tag),
)

if (newTags.length > 0) {
for (const tagName of newTags) {
await agent.db.createTag(user.id, { name: tagName })
}
}

const userTags = await agent.db.getTags(user.id)

for (const tag of userTags.filter((t) =>
suggestedTags.includes(t.name),
)) {
await agent.db.addTagToEntry(user.id, {
entryId: createdEntry.id,
tagId: tag.id,
})
}

return createReply({
entry: `Entry "${createdEntry.title}" created successfully with ID "${createdEntry.id}"`,
suggestedTags: newTags,
message:
newTags.length > 0
? `Here are some suggested tags for your entry: ${newTags.join(', ')}`
: 'No new tags to suggest for this entry.',
})
} catch (error) {
return createErrorReply(error)
}
Expand Down