Academy tutor
One workspace per course, so students get answers from their own lessons, any hour of the day.
Students ask questions about their course — the slides, handouts and recorded classes — inside your own app. Each answer shows which lesson file it came from.
Architecture
One workspace per course. A student of one course can never get an answer from another course's files, because retrieval never crosses a workspace.
- Map each course to a workspace id, and each lesson to its resource ids, in your own config.
- Pass the student's id as
external_user_id, so each student keeps their own history. - To answer from this week's lesson only, send its ids as
resource_ids. Leave it out to search the whole course.
Setup
Create one workspace per course and upload the course files into it: PDFs, slides as images, and class recordings. Wait until each file is ready before students ask about it.
The code
The course lookup comes first, so an unknown course fails fast instead of searching the wrong files:
const BASE = "https://api.daneshyar.info/api/v1";
const KEY = process.env.DANESHYAR_API_KEY;
/**
* One workspace per course. A student of Biology 101 never gets an answer
* from Chemistry 201's notes, because retrieval cannot cross a workspace.
*/
const COURSES = {
"biology-101": "8f14e45f-ceea-467a-9f4c-1a2b3c4d5e6f",
"chemistry-201": "1d4c7b90-3e58-4a21-bf06-9c2e5d8a7f43",
};
/**
* Keep each lesson's resource ids in your own config. Pass them as
* resource_ids to answer from this week's lesson only.
*/
const LESSONS = {
"biology-101": {
"week-3": ["3c9a1b77-0d24-4e8b-b0f1-7a5e9c2d4b81"],
},
};
/** Called from your own /tutor/ask route, which holds the key. */
export async function askTutor({ studentId, course, lesson, question, sessionId }) {
const workspaceId = COURSES[course];
if (!workspaceId) throw new Error(`Unknown course: ${course}`);
const res = await fetch(`${BASE}/workspaces/${workspaceId}/chat/`, {
method: "POST",
headers: { "X-API-Key": KEY, "Content-Type": "application/json" },
body: JSON.stringify({
// Your student id, so each student keeps their own history.
external_user_id: studentId,
session_id: sessionId,
message: question,
// Leave it out to search the whole course.
resource_ids: LESSONS[course]?.[lesson],
}),
});
if (!res.ok) {
const body = await res.json().catch(() => ({}));
throw new Error(body.message ?? body.detail ?? `HTTP ${res.status}`);
}
const { data } = await res.json();
const reply = data.assistant_message;
return {
sessionId: data.session.id,
answer: reply.content,
// Show the student which lesson file the answer came from.
sources: [
...new Map(
reply.citations.map((c) => [c.resource_id, c.resource_title]),
),
].map(([id, title]) => ({ id, title })),
};
}What you get
Every student gets answers from exactly their course's files, with the lesson named under each answer and their own history. Adding a course is one workspace and one config line.