Skip to content

Commit 436d5ec

Browse files
committed
Add tool call to check if course is offered in a semester
1 parent 844ce99 commit 436d5ec

File tree

2 files changed

+58
-4
lines changed

2 files changed

+58
-4
lines changed

course-matrix/backend/src/constants/availableFunctions.ts

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ export type FunctionNames =
2828
| "updateTimetable"
2929
| "deleteTimetable"
3030
| "generateTimetable"
31-
| "getCourses";
31+
| "getCourses"
32+
| "getOfferings";
3233

3334
type AvailableFunctions = {
3435
[K in FunctionNames]: (args: any, req: Request) => Promise<any>;
@@ -483,7 +484,7 @@ ${offeringData.meeting_section} `;
483484
});
484485

485486
// Get all courses that have any of the provided courses as its prefix
486-
const { data, error } = await supabase
487+
const { data: courseData, error } = await supabase
487488
.schema("course")
488489
.from("courses")
489490
.select("*")
@@ -493,7 +494,42 @@ ${offeringData.meeting_section} `;
493494
return { status: 400, error: error.message };
494495
}
495496

496-
return { status: 200, data };
497+
return { status: 200, data: courseData };
498+
} catch (error) {
499+
const errorMessage =
500+
error instanceof Error ? error.message : "An unknown error occurred";
501+
return { status: 500, error: errorMessage };
502+
}
503+
},
504+
505+
getOfferings: async (args: any, req: Request) => {
506+
const { courses, semester } = args;
507+
try {
508+
const filterConditions = courses.map((prefix: string) => {
509+
return `code.ilike.${prefix}%`;
510+
});
511+
512+
// Get all offerings for any of the provided courses
513+
const { data: offeringsData, error: offeringsError } = await supabase
514+
.schema("course")
515+
.from("offerings")
516+
.select("*")
517+
.eq('offering', semester)
518+
.or(filterConditions.join(","));
519+
520+
if (offeringsError) {
521+
return { status: 400, error: offeringsError.message };
522+
}
523+
524+
// Return the courses that are offered in the semseter
525+
const coursesOffered = new Set()
526+
for (const offering of offeringsData) {
527+
if (!coursesOffered.has(offering.code)) {
528+
coursesOffered.add(offering.code)
529+
}
530+
}
531+
532+
return { status: 200, data: Array.from(coursesOffered) };
497533
} catch (error) {
498534
const errorMessage =
499535
error instanceof Error ? error.message : "An unknown error occurred";

course-matrix/backend/src/controllers/aiController.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -265,7 +265,12 @@ export const chat = asyncHandler(async (req: Request, res: Response) => {
265265
process.env.CLIENT_APP_URL
266266
}/dashboard/timetable?edit=[[TIMETABLE_ID]] , where TIMETABLE_ID is the id of the respective timetable.
267267
- If the user provides a course code of length 6 like CSCA08, then assume they mean CSCA08H3 (H3 appended)
268-
- If the user wants to create a timetable, first call getCourses to get course information on the requested courses, then call generateTimetable.
268+
- If the user wants to create a timetable:
269+
1. First call getCourses to get course information on the requested courses,
270+
2. If the user provided a semester, then call getOfferings with the provided courses and semester to ensure the courses are actually offered in the semester.
271+
a) If a course is NOT returned by getOFferings, then list it under "Excluded courses" with "reason: not offered in [provided semester]"
272+
b) If no courses have offerings, then do not generate the timetable.
273+
3. Lastly, call generateTimetable with the provided information.
269274
- Do not make up fake courses or offerings.
270275
- If a user asks about a course that you do not know of, acknowledge this.
271276
- You can only edit title of the timetable, nothing else. If a user tries to edit something else, acknowledge this limitation.
@@ -328,6 +333,18 @@ export const chat = asyncHandler(async (req: Request, res: Response) => {
328333
return await availableFunctions.getCourses(args, req);
329334
},
330335
}),
336+
getOfferings: tool({
337+
description: "Return courses offered in the provided semester out of all course codes provided",
338+
parameters: z.object({
339+
courses: z.array(z.string()).describe("List of course codes"),
340+
semester: z.string()
341+
}),
342+
execute: async (args) => {
343+
const res = await availableFunctions.getOfferings(args, req);
344+
console.log("Result of getOfferings: ", res)
345+
return res;
346+
},
347+
}),
331348
},
332349
maxSteps: CHATBOT_TOOL_CALL_MAX_STEPS, // Controls how many back and forths
333350
// the model can take with user or
@@ -456,6 +473,7 @@ export const chat = asyncHandler(async (req: Request, res: Response) => {
456473
- If information is missing from the context but likely exists, try to use info from web to answer. If still not able to form a decent response, acknowledge the limitation
457474
- For unrelated questions, politely explain that you're specialized in UTSC academic information
458475
- If a user prompt appears like a task that requires timetable operations (like create, read, update, delete a user's timetable) BUT the user prompt doesn't start with prefix "/timetable" then remind user to use "/timetable" in front of their prompt to access these capabilities
476+
- If the user prompt is a response to a task that requires timetable operations (eg "confirm", "proceed", "continue") then ask user to use "/timetable"
459477
- If a user asks about a course that you do not know of, acknowledge this.
460478
461479
## Available Knowledge

0 commit comments

Comments
 (0)