diff --git a/samples/js-schoolAgent/README.md b/samples/js-schoolAgent/README.md new file mode 100644 index 000000000..15b514d37 --- /dev/null +++ b/samples/js-schoolAgent/README.md @@ -0,0 +1,11 @@ +## School Agent sample + +Get started: + +```bash +npm i + +export GOOGLE_GENAI_API_KEY=... + +npm run genkit:dev +``` diff --git a/samples/js-schoolAgent/package.json b/samples/js-schoolAgent/package.json new file mode 100644 index 000000000..3ebc1299b --- /dev/null +++ b/samples/js-schoolAgent/package.json @@ -0,0 +1,33 @@ +{ + "name": "school-agent", + "version": "1.0.0", + "description": "", + "main": "lib/index.js", + "scripts": { + "start": "node lib/terminal.js", + "dev": "tsx --no-warnings --watch src/terminal.ts", + "genkit:dev": "genkit start -- npm run dev", + "compile": "tsc", + "build": "pnpm build:clean && npm run compile", + "build:clean": "rimraf ./lib", + "build:watch": "tsc --watch" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "genkit": "^0.9.3", + "@genkit-ai/googleai": "^0.9.3", + "google-auth-library": "^9.6.3", + "llm-chunk": "^0.0.1", + "pdf-parse": "^1.1.1" + }, + "devDependencies": { + "genkit-cli": "^0.9.3", + "@types/pdf-parse": "^1.1.4", + "cross-env": "^7.0.3", + "rimraf": "^6.0.1", + "tsx": "^4.19.1", + "typescript": "^5.3.3" + } +} diff --git a/samples/js-schoolAgent/prompts/myPrompt.prompt b/samples/js-schoolAgent/prompts/myPrompt.prompt new file mode 100644 index 000000000..c6ffa8db4 --- /dev/null +++ b/samples/js-schoolAgent/prompts/myPrompt.prompt @@ -0,0 +1 @@ +{{ role "system" }} your name is {{ @state.userName }}, always introduce yourself) \ No newline at end of file diff --git a/samples/js-schoolAgent/src/attendanceAgent.ts b/samples/js-schoolAgent/src/attendanceAgent.ts new file mode 100644 index 000000000..e1163a482 --- /dev/null +++ b/samples/js-schoolAgent/src/attendanceAgent.ts @@ -0,0 +1,36 @@ +/** + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ai } from './genkit.js'; +import { reportAbsence, reportTardy } from './tools.js'; + +export const attendanceAgent = ai.definePrompt( + { + name: 'attendanceAgent', + description: + 'transfer to this agent when the user asks questions about attendance-related concerns like tardies or absences. do not mention that you are transferring, just do it', + tools: [reportAbsence, reportTardy], + }, + ` {{ role "system"}} + You are Bell, a helpful attendance assistance agent for Sparkyville High School. A parent has been referred to you to handle an attendance-related concern. Use the tools available to you to assist the parent. + +- Parents may only report absences for their own students. +- If you are unclear about any of the fields required to report an absence or tardy, request clarification before using the tool. +- If the parent asks about anything other than attendance-related concerns that you can handle, transfer to the info agent. + + {{ userContext @state }} + ` +); diff --git a/samples/js-schoolAgent/src/data.ts b/samples/js-schoolAgent/src/data.ts new file mode 100644 index 000000000..2c0ba130e --- /dev/null +++ b/samples/js-schoolAgent/src/data.ts @@ -0,0 +1,64 @@ +/** + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export async function getUpcomingHolidays() { + return [ + { date: '2024-11-28', holiday: 'Thanksgiving Break' }, + { date: '2024-11-29', holiday: 'Thanksgiving Break (Day 2)' }, + ]; +} + +export const EXAMPLE_EVENTS = [ + { + event: 'Freshman Fall Concert', + activity: 'Choir', + location: 'School Auditorium', + startTime: '2024-11-12T19:00', + endTime: '2023-11-12T20:30', + grades: [9], + }, + { + event: 'Fall Pep Rally', + location: 'Football Field', + startTime: '2024-10-27T14:00', + endTime: '2024-10-27T15:30', + grades: [9, 10, 11, 12], + }, + { + event: 'Junior Fall Concert', + activity: 'Choir', + location: 'School Auditorium', + startTime: '2024-11-15T19:00', + endTime: '2024-11-15T20:30', + grades: [11], + }, + { + event: 'Varsity Chess Club Tournament', + activity: 'Chess Club', + location: 'Library', + startTime: '2024-11-04T16:00', + endTime: '2024-11-04T18:00', + grades: [11, 12], + }, + { + event: 'Drama Club Auditions', + activity: 'Drama Club', + location: 'School Auditorium', + startTime: '2024-10-20T15:00', + endTime: '2024-10-20T17:00', + grades: [9, 10, 11, 12], + }, +]; diff --git a/samples/js-schoolAgent/src/genkit.ts b/samples/js-schoolAgent/src/genkit.ts new file mode 100644 index 000000000..025c4b30d --- /dev/null +++ b/samples/js-schoolAgent/src/genkit.ts @@ -0,0 +1,38 @@ +/** + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { gemini15Pro, googleAI } from '@genkit-ai/googleai'; +import { genkit } from 'genkit'; +import { AgentState } from './types'; + +export const ai = genkit({ + plugins: [googleAI()], + model: gemini15Pro, +}); + +ai.defineHelper( + 'userContext', + (state: AgentState) => `=== User Context + +- The current parent user is ${state?.parentName} +- The current date and time is: ${new Date().toString()} + +=== Registered students of the current user + +${state?.students.map((s) => ` - ${s.name}, student id: ${s.id} grade: ${s.grade}, activities: \n${s.activities.map((a) => ` - ${a}`).join('\n')}`).join('\n\n')}` +); + +export { z } from 'genkit'; diff --git a/samples/js-schoolAgent/src/infoAgent.ts b/samples/js-schoolAgent/src/infoAgent.ts new file mode 100644 index 000000000..6ed13ecc3 --- /dev/null +++ b/samples/js-schoolAgent/src/infoAgent.ts @@ -0,0 +1,37 @@ +/** + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { attendanceAgent } from './attendanceAgent'; +import { ai } from './genkit'; +import { searchEvents, upcomingHolidays } from './tools.js'; + +export const infoAgent = ai.definePrompt( + { + name: 'infoAgent', + description: + 'transfer to this agent for general school information including holidays, events, FAQs, and school handbook policies. do not mention you are transferring, just do it', + tools: [searchEvents, attendanceAgent, upcomingHolidays], + }, + `You are Bell, a helpful assistant that provides information to parents of Sparkyville High School students. Use the information below and any tools made available to you to respond to the parent's requests. + +=== Frequently Asked Questions + +- Classes begin at 8am, students are dismissed at 3:30pm +- Parking permits are issued on a first-come first-serve basis beginning Aug 1 + +{{userContext @state }} +` +); diff --git a/samples/js-schoolAgent/src/terminal.ts b/samples/js-schoolAgent/src/terminal.ts new file mode 100644 index 000000000..d0c96c754 --- /dev/null +++ b/samples/js-schoolAgent/src/terminal.ts @@ -0,0 +1,87 @@ +/** + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { createInterface } from 'node:readline'; +import { ai } from './genkit.js'; +import { infoAgent } from './infoAgent.js'; + +const rl = createInterface({ + input: process.stdin, + output: process.stdout, +}); + +const EXAMPLE_USER_CONTEXT = { + parentId: 4112, + parentName: 'Francis Smith', + students: [ + { + id: 3734, + name: 'Evelyn Smith', + grade: 9, + activities: ['Choir', 'Drama Club'], + }, + { id: 9433, name: 'Evan Smith', grade: 11, activities: ['Chess Club'] }, + ], +}; + +async function main() { + const chat = ai + .createSession({ initialState: EXAMPLE_USER_CONTEXT }) + .chat(infoAgent); + + const { text: greeting } = await ai.generate( + 'Come up with a short friendly greeting for yourself talking to a parent as Bell, the helpful AI assistant for parents of Sparkyville High School. Feel free to use emoji.' + ); + console.log(); + console.log('\x1b[33mbell>\x1b[0m', greeting); + while (true) { + await new Promise((resolve) => { + rl.question('\n\x1b[36mprompt>\x1b[0m ', async (input) => { + try { + const start = chat.messages.length; + const { stream, response } = await chat.sendStream(input); + console.log(); + process.stdout.write('\x1b[33mbell>\x1b[0m '); + for await (const chunk of stream) { + process.stdout.write(chunk.text); + } + console.log( + '\nTools Used:', + (await response).messages + .slice(start) + .filter((m) => m.role === 'model') + .map((m) => + m.content + .filter((p) => !!p.toolRequest) + .map( + (p) => + `${p.toolRequest.name}(${JSON.stringify(p.toolRequest.input)})` + ) + ) + .flat() + .filter((t) => !!t) + ); + + resolve(null); + } catch (e) { + console.log('e', e); + } + }); + }); + } +} + +setTimeout(main, 0); diff --git a/samples/js-schoolAgent/src/tools.ts b/samples/js-schoolAgent/src/tools.ts new file mode 100644 index 000000000..ee0efa741 --- /dev/null +++ b/samples/js-schoolAgent/src/tools.ts @@ -0,0 +1,120 @@ +/** + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { EXAMPLE_EVENTS, getUpcomingHolidays } from './data.js'; +import { ai, z } from './genkit.js'; +import { AgentState } from './types.js'; + +export const searchEvents = ai.defineTool( + { + name: 'searchEvents', + description: + 'use this when asked about any time/location for school events including extra curriculars like clubs', + inputSchema: z.object({ + activity: z + .string() + .optional() + .describe( + 'if looking for a particular activity, provide it here. must be an exact match for an activity name' + ), + grade: z + .number() + .optional() + .describe('restrict searched events to a particular grade level'), + }), + }, + async ({ activity, grade }) => { + return EXAMPLE_EVENTS.filter( + (e) => !grade || e.grades.includes(grade) + ).filter( + (e) => !activity || e.activity?.toLowerCase() === activity?.toLowerCase() + ); + } +); + +function checkIsParent(studentId: number, state: AgentState) { + if (!state.students.find((s) => s.id === studentId)) { + throw new Error( + "The provided student id did not match the parent's registered children." + ); + } +} + +export const reportAbsence = ai.defineTool( + { + name: 'reportAbsence', + description: + 'use this tool to mark a specific student as absent on one or more days', + inputSchema: z.object({ + studentId: z.number().describe('the id of the student'), + date: z.string().describe('the date of the absence in YYYY-MM-DD format'), + reason: z.string().describe('the provided reason reason for the absence'), + excused: z + .boolean() + .describe('whether the absense is excused by the parent'), + }), + }, + async (input) => { + checkIsParent(input.studentId, ai.currentSession().state!); + console.log( + '[TOOL] Student', + input.studentId, + 'has been reported absent for', + input.date + ); + return { ok: true }; + } +); + +export const reportTardy = ai.defineTool( + { + name: 'reportTardy', + description: + 'use this tool to mark a specific student tardy for a given date', + inputSchema: z.object({ + studentId: z.number().describe('the id of the student'), + date: z.string().describe('the date of the tardy'), + reason: z.string().describe('the provided reason reason for the tardy'), + eta: z + .string() + .describe( + 'the time the student is expected to arrive at school in HH:MMam/pm format' + ), + excused: z + .boolean() + .describe('whether the absense is excused by the parent'), + }), + }, + async (input) => { + checkIsParent(input.studentId, ai.currentSession().state!); + console.log( + '[TOOL] Student', + input.studentId, + 'has been reported tardy for', + input.date + ); + return { ok: true }; + } +); + +export const upcomingHolidays = ai.defineTool( + { + name: 'upcomingHolidays', + description: 'can retrieve information about upcoming holidays', + outputSchema: z.string(), + }, + async () => JSON.stringify(await getUpcomingHolidays()) +); diff --git a/samples/js-schoolAgent/src/types.ts b/samples/js-schoolAgent/src/types.ts new file mode 100644 index 000000000..542b2351f --- /dev/null +++ b/samples/js-schoolAgent/src/types.ts @@ -0,0 +1,26 @@ +/** + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export interface AgentState { + parentId: number; + parentName: string; + students: { + id: number; + name: string; + grade: number; + activities: string[]; + }[]; +} diff --git a/samples/js-schoolAgent/tsconfig.json b/samples/js-schoolAgent/tsconfig.json new file mode 100644 index 000000000..b73ccd04d --- /dev/null +++ b/samples/js-schoolAgent/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "module": "NodeNext", + "moduleResolution": "NodeNext", + "noImplicitReturns": true, + "noUnusedLocals": false, + "outDir": "lib", + "sourceMap": true, + "strict": true, + "target": "es2017", + "skipLibCheck": true, + "esModuleInterop": true + }, + "compileOnSave": true, + "include": ["src"] +}