generate and save facts

master
Avraham Sakal 3 months ago
parent d7716bd451
commit ee9e9424ab

@ -7,6 +7,14 @@ export type Conversation = {
userId: string;
};
export type Fact = {
id: string;
userId: string;
sourceMessageId: string;
content: string;
createdAt: string;
};
type DB = {
conversations: Array<Conversation>;
messages: Array<{
@ -18,11 +26,13 @@ type DB = {
createdAt: string;
runningSummary?: string;
}>;
facts: Array<Fact>;
};
export const db = new Low<DB>(new JSONFile("db.json"), {
conversations: [],
messages: [],
facts: [],
});
/** Initialize the database. Sets `db.data` to the default state if the file doesn't exist. */
await db.read();

@ -26,7 +26,6 @@ import { useEffect } from "react";
import { trpc } from "../trpc/client.js";
import { usePageContext } from "vike-react/usePageContext";
import "./hover.css";
import type { ConversationsId } from "../database/generated/public/Conversations.js";
export default function LayoutDefault({
children,
@ -54,9 +53,13 @@ export default function LayoutDefault({
// }
// }, [isConversationListExpanded]);
function handleDeleteConversation(conversationId: string) {
async function handleDeleteConversation(conversationId: string) {
removeConversation(conversationId);
trpc.chat.deleteConversation.mutate({ id: conversationId });
await trpc.chat.deleteConversation.mutate({ id: conversationId });
const res = await trpc.chat.createConversation.mutate();
if (!res?.id) return;
addConversation(res);
await navigate(`/chat/${res.id}`);
}
return (

@ -2,6 +2,7 @@ import {
Box,
Group,
JsonInput,
List,
Stack,
Tabs,
Textarea,
@ -30,6 +31,7 @@ export default function ChatPage() {
const message = useStore((state) => state.message);
const systemPrompt = useStore((state) => state.systemPrompt);
const parameters = useStore((state) => state.parameters);
const facts = useStore((state) => state.facts);
const loading = useStore((state) => state.loading);
const setConversationId = useStore((state) => state.setConversationId);
const setConversationTitle = useStore((state) => state.setConversationTitle);
@ -37,9 +39,14 @@ export default function ChatPage() {
const setMessage = useStore((state) => state.setMessage);
const setSystemPrompt = useStore((state) => state.setSystemPrompt);
const setParameters = useStore((state) => state.setParameters);
const setFacts = useStore((state) => state.setFacts);
const setLoading = useStore((state) => state.setLoading);
const { conversation, messages: initialMessages } = useData<Data>();
const {
conversation,
messages: initialMessages,
facts: initialFacts,
} = useData<Data>();
useEffect(() => {
setConversationId(conversationId);
@ -61,6 +68,10 @@ export default function ChatPage() {
setMessages(initialMessages);
}, [initialMessages, setMessages]);
useEffect(() => {
setFacts(initialFacts);
}, [initialFacts, setFacts]);
return (
<>
<div>
@ -84,6 +95,7 @@ export default function ChatPage() {
<Tabs.Tab value="message">Message</Tabs.Tab>
<Tabs.Tab value="system-prompt">System Prompt</Tabs.Tab>
<Tabs.Tab value="parameters">Parameters</Tabs.Tab>
<Tabs.Tab value="facts">Facts</Tabs.Tab>
</Tabs.List>
<Tabs.Panel value="message">
<Messages messages={messages} />
@ -131,6 +143,7 @@ export default function ChatPage() {
];
setMessages(messagesWithAssistantMessage);
setMessage("");
setFacts(response.insertedFacts);
setLoading(false);
}
}}
@ -153,6 +166,13 @@ export default function ChatPage() {
onChange={(value) => setParameters(JSON.parse(value))}
/>
</Tabs.Panel>
<Tabs.Panel value="facts">
<List>
{facts.map((fact) => (
<List.Item key={fact.id}>{fact.content}</List.Item>
))}
</List>
</Tabs.Panel>
</Tabs>
</>
);
@ -164,7 +184,6 @@ function Messages({
messages: Array<DraftMessage | CommittedMessage>;
}) {
const theme = useMantineTheme();
console.log("messages", messages);
return (
<Stack gap="md" justify="flex-start">
{messages.map((message, index) => (
@ -188,7 +207,7 @@ function Messages({
</div>
<Markdown>{message.content}</Markdown>
</Box>
{"runningSummary" in message && (
{"runningSummary" in message && message.runningSummary && (
<Box w="75%" bd="dotted" p="md" bdrs="md">
<div>
<strong>Running Summary:</strong>

@ -12,5 +12,5 @@ export const data = async (pageContext: PageContextServer) => {
const messages = await caller.fetchMessages({
conversationId: id,
});
return { conversation, messages };
return { conversation, messages, facts: [] };
};

@ -4,7 +4,7 @@ import {
createCallerFactory,
} from "../../trpc/server";
import { createOpenRouter } from "@openrouter/ai-sdk-provider";
import { generateText } from "ai";
import { generateObject, generateText, jsonSchema } from "ai";
import type { Message as UIMessage } from "ai";
import type {
OtherParameters,
@ -17,7 +17,7 @@ import { env } from "../../server/env.js";
// ConsistencyLevelEnum,
// type NumberArrayId,
// } from "@zilliz/milvus2-sdk-node";
import { db } from "../../database/lowdb";
import { db, type Fact } from "../../database/lowdb";
import { nanoid } from "nanoid";
const mainSystemPrompt = ({
@ -30,6 +30,15 @@ This is a summary of the conversation so far, from your point-of-view (so "I" an
${previousRunningSummary}
</running_summary>
`;
const factsFromUserMessageSystemPrompt = ({
previousRunningSummary,
}: {
previousRunningSummary: string;
}) => `Given the following summary of a conversation, coupled with the messages exchanged since that summary was produced, extract new facts that can be gleaned from the conversation.
<running_summary>
${previousRunningSummary}
</running_summary>
`;
const runningSummarySystemPrompt = ({
previousRunningSummary,
}: {
@ -69,10 +78,19 @@ export const chat = router({
deleteConversation: publicProcedure
.input((x) => x as { id: string })
.mutation(async ({ input: { id } }) => {
await db.data.conversations.splice(
db.data.conversations.splice(
db.data.conversations.findIndex((c) => c.id === id),
1,
);
const deletedMessageIds = db.data.messages
.filter((m) => m.conversationId === id)
.map((m) => m.id);
db.data.messages = db.data.messages.filter(
(m) => m.conversationId !== id,
);
db.data.facts = db.data.facts.filter(
(fact) => !deletedMessageIds.includes(fact.sourceMessageId),
);
db.write();
return { ok: true };
}),
@ -136,7 +154,7 @@ export const chat = router({
index: messages.length - 1,
createdAt: new Date().toISOString(),
};
await db.data.messages.push(insertedUserMessage);
db.data.messages.push(insertedUserMessage);
// do not db.write() until the end
/** Generate a new message from the model, but hold-off on adding it to
@ -172,6 +190,43 @@ export const chat = router({
* injection, because we're sending the user's message unadulterated to
* the model; there's no reason to inject the same Facts that the model is
* already using to generate its response.) */
const factsFromUserMessageResponse = await generateObject<{
facts: Array<string>;
}>({
model: openrouter("mistralai/mistral-nemo"),
messages: [
{
role: "system" as const,
content: factsFromUserMessageSystemPrompt({
previousRunningSummary,
}),
},
...messages.slice(previousRunningSummaryIndex + 1),
],
schema: jsonSchema({
type: "object",
properties: {
facts: {
type: "array",
items: {
type: "string",
},
},
},
}),
maxSteps: 3,
tools: undefined,
...parameters,
});
const insertedFacts: Array<Fact> =
factsFromUserMessageResponse.object.facts.map((fact) => ({
id: nanoid(),
userId: "1",
sourceMessageId: insertedUserMessage.id,
content: fact,
createdAt: new Date().toISOString(),
}));
db.data.facts.push(...insertedFacts);
/** Extract Facts from the model's response, and add them to the database,
* linking the Facts with the messages they came from. */
/** For each Fact produced in the two fact-extraction steps, generate
@ -246,12 +301,15 @@ export const chat = router({
index: messages.length,
createdAt: new Date().toISOString(),
};
await db.data.messages.push(insertedAssistantMessage);
db.data.messages.push(insertedAssistantMessage);
await db.write();
/** TODO: notify the caller, somehow, that some messages were saved to
* the database and/or were outfitted with runningSummaries, so the
* caller can update its UI state. */
return { insertedAssistantMessage, insertedUserMessage };
return {
insertedAssistantMessage,
insertedUserMessage,
insertedFacts,
};
},
),
});

@ -1,6 +1,5 @@
import { create } from "zustand";
import type { OtherParameters, Store } from "./types.js";
import type { ConversationsId } from "./database/generated/public/Conversations.js";
import { immer } from "zustand/middleware/immer";
export const defaultSystemPrompt = `You are a helpful assistant that answers questions based on the provided context. If you don't know the answer, just say that you don't know, don't try to make up an answer.`;
@ -17,6 +16,7 @@ export const useStore = create<Store>()(
message: "",
systemPrompt: defaultSystemPrompt,
parameters: defaultParameters,
facts: [],
loading: false,
setConversationId: (conversationId) =>
set((stateDraft) => {
@ -65,6 +65,10 @@ export const useStore = create<Store>()(
set((stateDraft) => {
stateDraft.parameters = parameters;
}),
setFacts: (facts) =>
set((stateDraft) => {
stateDraft.facts = facts;
}),
setLoading: (loading) =>
set((stateDraft) => {
stateDraft.loading = loading;

@ -1,6 +1,6 @@
import type { Message as UIMessage } from "ai";
import type { generateText } from "ai";
import type { Conversation } from "./database/lowdb.js";
import type { Conversation, Fact } from "./database/lowdb.js";
export type OtherParameters = Omit<
Parameters<typeof generateText>[0],
@ -18,6 +18,7 @@ export type Store = {
message: string;
systemPrompt: string;
parameters: OtherParameters;
facts: Array<Fact>;
loading: boolean;
setConversationId: (conversationId: string) => void;
setConversationTitle: (conversationTitle: string) => void;
@ -28,6 +29,7 @@ export type Store = {
setMessage: (message: string) => void;
setSystemPrompt: (systemPrompt: string) => void;
setParameters: (parameters: OtherParameters) => void;
setFacts: (facts: Array<Fact>) => void;
setLoading: (loading: boolean) => void;
};

Loading…
Cancel
Save