andreasmadsen's picture
Make all prompt templates configurable (#400)
447c0ca unverified
raw
history blame
4.33 kB
import { authCondition } from "$lib/server/auth";
import { collections } from "$lib/server/database";
import { defaultModel } from "$lib/server/models";
import { searchWeb } from "$lib/server/websearch/searchWeb";
import type { Message } from "$lib/types/Message";
import { error } from "@sveltejs/kit";
import { ObjectId } from "mongodb";
import { z } from "zod";
import type { WebSearch } from "$lib/types/WebSearch";
import { generateQuery } from "$lib/server/websearch/generateQuery";
import { parseWeb } from "$lib/server/websearch/parseWeb";
import { summarizeWeb } from "$lib/server/websearch/summarizeWeb";
interface GenericObject {
[key: string]: GenericObject | unknown;
}
function removeLinks(obj: GenericObject) {
for (const prop in obj) {
if (prop.endsWith("link")) delete obj[prop];
else if (typeof obj[prop] === "object") removeLinks(obj[prop] as GenericObject);
}
return obj;
}
export async function GET({ params, locals, url }) {
const model = defaultModel;
const convId = new ObjectId(params.id);
const searchId = new ObjectId();
const conv = await collections.conversations.findOne({
_id: convId,
...authCondition(locals),
});
if (!conv) {
throw error(404, "Conversation not found");
}
const prompt = z.string().trim().min(1).parse(url.searchParams.get("prompt"));
const messages = (() => {
return [...conv.messages, { content: prompt, from: "user", id: crypto.randomUUID() }];
})() satisfies Message[];
const stream = new ReadableStream({
async start(controller) {
const webSearch: WebSearch = {
_id: searchId,
convId: convId,
prompt: prompt,
searchQuery: "",
knowledgeGraph: "",
answerBox: "",
results: [],
summary: "",
messages: [],
createdAt: new Date(),
updatedAt: new Date(),
};
function appendUpdate(message: string, args?: string[], type?: "error" | "update") {
webSearch.messages.push({
type: type ?? "update",
message,
args,
});
controller.enqueue(JSON.stringify({ messages: webSearch.messages }));
}
try {
appendUpdate("Generating search query");
webSearch.searchQuery = await generateQuery(messages);
appendUpdate("Searching Google", [webSearch.searchQuery]);
const results = await searchWeb(webSearch.searchQuery);
let text = "";
webSearch.results =
(results.organic_results &&
results.organic_results.map((el: { link: string }) => el.link)) ??
[];
if (results.answer_box) {
// if google returns an answer box, we use it
webSearch.answerBox = JSON.stringify(removeLinks(results.answer_box));
text = webSearch.answerBox;
appendUpdate("Found a Google answer box");
} else if (results.knowledge_graph) {
// if google returns a knowledge graph, we use it
webSearch.knowledgeGraph = JSON.stringify(removeLinks(results.knowledge_graph));
text = webSearch.knowledgeGraph;
appendUpdate("Found a Google knowledge page");
} else if (webSearch.results.length > 0) {
let tries = 0;
while (!text && tries < 3) {
const searchUrl = webSearch.results[tries];
appendUpdate("Browsing result", [JSON.stringify(searchUrl)]);
try {
text = await parseWeb(searchUrl);
if (!text) throw new Error("text of the webpage is null");
} catch (e) {
appendUpdate("Error parsing webpage", [], "error");
tries++;
}
}
if (!text) throw new Error("No text found on the first 3 results");
} else {
throw new Error("No results found for this search query");
}
appendUpdate("Creating summary");
webSearch.summary = await summarizeWeb(text, webSearch.searchQuery, model);
appendUpdate("Injecting summary", [JSON.stringify(webSearch.summary)]);
} catch (searchError) {
if (searchError instanceof Error) {
webSearch.messages.push({
type: "error",
message: "An error occurred with the web search",
args: [JSON.stringify(searchError.message)],
});
}
}
const res = await collections.webSearches.insertOne(webSearch);
webSearch.messages.push({
type: "result",
id: res.insertedId.toString(),
});
controller.enqueue(JSON.stringify({ messages: webSearch.messages }));
},
});
return new Response(stream, { headers: { "Content-Type": "application/json" } });
}