forked from prudhvi1709/iitmdocs
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathworker.js
More file actions
137 lines (125 loc) · 4.49 KB
/
worker.js
File metadata and controls
137 lines (125 loc) · 4.49 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
export default {
async fetch(request, env) {
// Handle CORS preflight
if (request.method === "OPTIONS") {
return new Response(null, {
status: 200,
headers: {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "POST, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type",
},
});
}
// Only handle POST /answer
const url = new URL(request.url);
if (request.method == "POST" && url.pathname == "/answer") {
return await answer(request, env);
}
return env.ASSETS.fetch(request);
},
};
async function answer(request, env) {
const { q: question, ndocs = 5 } = await request.json();
if (!question) return new Response('Missing "q" parameter', { status: 400 });
const encoder = new TextEncoder();
const stream = new ReadableStream({
async start(controller) {
// Search Weaviate for relevant documents
const documents = await searchWeaviate(question, ndocs, env);
// Stream documents first (single enqueue)
if (documents?.length) {
const sseDocs = documents
.map(
(doc) =>
`data: ${JSON.stringify({
role: "assistant",
choices: [
{
delta: {
tool_calls: [
{
function: {
name: "document",
arguments: JSON.stringify({
relevance: doc.relevance,
name: doc.filename.replace(/\.md$/, ""),
link: `https://github.com/study-iitm/iitmdocs/blob/main/src/${doc.filename}`,
}),
},
},
],
},
},
],
})}\n\n`,
)
.join("");
controller.enqueue(encoder.encode(sseDocs));
}
// Generate AI answer using documents as context and stream via piping
const answer = await generateAnswer(question, documents, env);
await answer.body.pipeTo(
new WritableStream({
write: (chunk) => controller.enqueue(chunk),
close: () => controller.close(),
abort: (reason) => controller.error(reason),
}),
);
},
});
return new Response(stream, {
headers: {
"Content-Type": "text/event-stream",
"Access-Control-Allow-Origin": "*",
},
});
}
async function searchWeaviate(query, limit, env) {
const response = await fetch(`${env.WEAVIATE_URL}/v1/graphql`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${env.WEAVIATE_API_KEY}`,
"X-OpenAI-Api-Key": env.OPENAI_API_KEY,
},
body: JSON.stringify({
query: `{
Get {
Document(nearText: { concepts: ["${query}"] } limit: ${limit}) {
filename filepath content file_size
_additional { distance }
}
}
}`,
}),
});
const data = await response.json();
if (data.errors) throw new Error(`Weaviate error: ${data.errors.map((e) => e.message).join(", ")}`);
const documents = data.data?.Get?.Document || [];
return documents.map((doc) => ({ ...doc, relevance: doc._additional?.distance ? 1 - doc._additional.distance : 0 }));
}
async function generateAnswer(question, documents, env) {
const context = documents.map((doc) => `<document filename="${doc.filename}">${doc.content}</document>`).join("\n\n");
const systemPrompt = `You are a helpful assistant answering questions about the IIT Madras BS programme.
Answer directly in VERY simple, CONCISE Markdown.
If the question is unclear, infer, state your assumption, and then respond accordingly.
Current date: ${new Date().toISOString().split("T")[0]}.
Use the information from documents provided.`;
const response = await fetch("https://api.openai.com/v1/chat/completions", {
method: "POST",
headers: { "Content-Type": "application/json", Authorization: `Bearer ${env.OPENAI_API_KEY}` },
body: JSON.stringify({
model: "gpt-5-mini",
messages: [
{ role: "system", content: systemPrompt },
{ role: "assistant", content: context },
{ role: "user", content: question },
],
store: true,
stream: true,
}),
});
if (!response.ok) throw new Error(`OpenAI API error: ${response.status} ${response.statusText}`);
return response;
}