You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

147 lines
4.5 KiB
TypeScript

import { router, publicProcedure, createCallerFactory } from "./server.js";
import type { DraftMessage } from "@/types.js";
import { MODEL_NAME, openrouter } from "@/server/provider.js";
import { generateObject, generateText, jsonSchema } from "ai";
const factsFromNewMessagesSystemPrompt = ({
previousRunningSummary,
messagesSincePreviousRunningSummary,
}: {
previousRunningSummary: string;
messagesSincePreviousRunningSummary: Array<DraftMessage>;
}) => `You are an expert at extracting facts from conversations.
An AI assistant is in the middle of a conversation whose data is given below. The data consists of a summary of a conversation, and optionally some messages exchanged since that summary was produced. The user will provide you with *new* messages.
Your task is to extract *new* facts that can be gleaned from the *new* messages that the user sends.
* You should not extract any facts that are already in the summary.
* The user should be referred to as "the user" in the fact text.
* The user's pronouns should be either he or she, NOT "they" or "them", because this summary will be read by an AI assistant to give it context; and excessive use of "they" or "them" will make what they refer to unclear or ambiguous.
* The assistant should be referred to as "I" or "me", because these facts will be read by an AI assistant to give it context.
<running_summary>
${previousRunningSummary}
</running_summary>
${messagesSincePreviousRunningSummary.map(
(message) =>
`<${message.role}_message>${message.parts
.filter((p) => p.type === "text")
.map((p) => p.text)
.join("\n")}</${message.role}_message>`
)}
`;
const factsFromNewMessagesUserPrompt = ({
newMessages,
}: {
newMessages: Array<DraftMessage>;
}) =>
`${newMessages.map(
(message) =>
`<${message.role}_message>${message.parts
.filter((p) => p.type === "text")
.map((p) => p.text)
.join("\n")}</${message.role}_message>`
)}
Extract new facts from these messages.`;
export const facts = router({
fetchByConversationId: publicProcedure
.input((x) => x as { conversationId: string })
.query(async ({ input: { conversationId }, ctx: { db } }) => {
return await db.facts.findByConversationId(conversationId);
}),
deleteOne: publicProcedure
.input(
(x) =>
x as {
factId: string;
}
)
.mutation(async ({ input: { factId }, ctx: { db } }) => {
await db.facts.delete(factId);
return { ok: true };
}),
update: publicProcedure
.input(
(x) =>
x as {
factId: string;
content: string;
}
)
.mutation(async ({ input: { factId, content }, ctx: { db } }) => {
await db.facts.update(factId, { content });
return { ok: true };
}),
extractFromNewMessages: publicProcedure
.input(
(x) =>
x as {
previousRunningSummary: string;
/** will *not* have facts extracted */
messagesSincePreviousRunningSummary: Array<DraftMessage>;
/** *will* have facts extracted */
newMessages: Array<DraftMessage>;
}
)
.query(
async ({
input: {
previousRunningSummary,
messagesSincePreviousRunningSummary,
newMessages,
},
ctx: { openrouter },
}) => {
const factsFromUserMessageResponse = await generateObject({
model: openrouter(MODEL_NAME),
messages: [
{
role: "system" as const,
content: factsFromNewMessagesSystemPrompt({
previousRunningSummary,
messagesSincePreviousRunningSummary,
}),
},
{
role: "user" as const,
content: factsFromNewMessagesUserPrompt({
newMessages,
}),
},
],
schema: jsonSchema<{
facts: Array<string>;
}>({
type: "object",
properties: {
facts: {
type: "array",
items: {
type: "string",
},
},
},
required: ["facts"],
}),
temperature: 0.4,
maxRetries: 0,
}).catch((err) => {
console.error(err);
return {
object: {
facts: [] as Array<string>,
},
};
});
return factsFromUserMessageResponse;
}
),
});
export const createCaller = createCallerFactory(facts);