Skip to content

Commit 69db0ce

Browse files
authored
Kl/scrum 158 ai hallucinating (#115)
Co-authored-by: kevin-lann <[email protected]>
1 parent 9ad7166 commit 69db0ce

File tree

3 files changed

+64
-7
lines changed

3 files changed

+64
-7
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/constants/constants.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ export const TEST_DATE_NOW = new Date(2025, 4, 14, 8, 45, 1);
4646

4747
// Set minimum results wanted for a similarity search on the associated namespace.
4848
export const namespaceToMinResults = new Map();
49-
namespaceToMinResults.set("courses_v3", 10);
49+
namespaceToMinResults.set("courses_v3", 16);
5050
namespaceToMinResults.set("offerings", 16); // Typically, more offering info is wanted.
5151
namespaceToMinResults.set("prerequisites", 5);
5252
namespaceToMinResults.set("corequisites", 5);

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

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ export async function reformulateQuery(
138138
- DO replace pronouns and references with specific names and identifiers
139139
- DO include course codes, names and specific details for academic entities
140140
- If the query is not about university courses & offerings, return exactly a copy of the user's query.
141-
- Append "code: " before course codes For example: "CSCC01" -> "code: CSCC01"
141+
- Append "code: " before each course code For example: "CSCC01, BIOA01" -> "code: CSCC01, code: BIOA01"
142142
- If a course year level is written as "first year", "second year", etc. Then replace "first" with "1st" and "second" with "2nd" etc.
143143
144144
Examples:
@@ -265,8 +265,14 @@ 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.
275+
- If a user asks about a course that you do not know of, acknowledge this.
270276
- You can only edit title of the timetable, nothing else. If a user tries to edit something else, acknowledge this limitation.
271277
- For delete timetable requests, if the user asks to delete an ambiguous timetable name (i.e many with similar name exist) then ask them to clarify which one
272278
- For delete timetable requests, first check that the timetable the user is refering to exists
@@ -327,6 +333,19 @@ export const chat = asyncHandler(async (req: Request, res: Response) => {
327333
return await availableFunctions.getCourses(args, req);
328334
},
329335
}),
336+
getOfferings: tool({
337+
description:
338+
"Return courses offered in the provided semester out of all course codes provided",
339+
parameters: z.object({
340+
courses: z.array(z.string()).describe("List of course codes"),
341+
semester: z.string(),
342+
}),
343+
execute: async (args) => {
344+
const res = await availableFunctions.getOfferings(args, req);
345+
console.log("Result of getOfferings: ", res);
346+
return res;
347+
},
348+
}),
330349
},
331350
maxSteps: CHATBOT_TOOL_CALL_MAX_STEPS, // Controls how many back and forths
332351
// the model can take with user or
@@ -455,7 +474,9 @@ export const chat = asyncHandler(async (req: Request, res: Response) => {
455474
- 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
456475
- For unrelated questions, politely explain that you're specialized in UTSC academic information
457476
- 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
458-
477+
- If the user prompt is a response to a task that requires timetable operations (eg "confirm", "proceed", "continue") then ask user to use "/timetable"
478+
- If a user asks about a course that you do not know of, acknowledge this.
479+
459480
## Available Knowledge
460481
${
461482
context === "[No context provided]"

0 commit comments

Comments
 (0)