diff --git a/.env.example b/.env.example index f33da34..5a74113 100644 --- a/.env.example +++ b/.env.example @@ -1,11 +1,4 @@ -# Example .env file for Automated Assessment Feedback Agent -# Copy this file to .env and fill in your Azure OpenAI credentials - -# Your Azure OpenAI API key -AZURE_OPENAI_API_KEY=your-azure-openai-key-here - -# Your Azure OpenAI endpoint (e.g., https://your-resource-name.openai.azure.com) -AZURE_OPENAI_ENDPOINT=https://your-resource-name.openai.azure.com - -# The deployment name for your model (e.g., gpt-4, gpt-35-turbo) -AZURE_OPENAI_DEPLOYMENT=gpt-4 +AI_FOUNDRY_PROJECT_CONNECTION_STRING= +BING_GROUNDING_CONNECTION_ID= +AI_SEARCH_CONNECTION_ID= +AI_MODEL=gpt-4o \ No newline at end of file diff --git a/package.json b/package.json index 15fdd2b..93e3e0f 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "@sveltejs/vite-plugin-svelte": "^5.0.0", "@tailwindcss/typography": "^0.5.15", "@tailwindcss/vite": "^4.0.0", + "@types/node": "^22.14.1", "eslint": "^9.18.0", "eslint-config-prettier": "^10.0.1", "eslint-plugin-svelte": "^3.0.0", @@ -40,5 +41,9 @@ "esbuild" ] }, - "packageManager": "pnpm@9.15.4+sha512.b2dc20e2fc72b3e18848459b37359a32064663e5627a51e4c74b2c29dd8e8e0491483c3abb40789cfd578bf362fb6ba8261b05f0387d76792ed6e23ea3b1b6a0" + "packageManager": "pnpm@9.15.4+sha512.b2dc20e2fc72b3e18848459b37359a32064663e5627a51e4c74b2c29dd8e8e0491483c3abb40789cfd578bf362fb6ba8261b05f0387d76792ed6e23ea3b1b6a0", + "dependencies": { + "@azure/ai-projects": "1.0.0-beta.4", + "@azure/identity": "^4.9.0" + } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 04eb21e..c54557d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7,6 +7,13 @@ settings: importers: .: + dependencies: + '@azure/ai-projects': + specifier: 1.0.0-beta.4 + version: 1.0.0-beta.4 + '@azure/identity': + specifier: ^4.9.0 + version: 4.9.0 devDependencies: '@eslint/compat': specifier: ^1.2.5 @@ -16,19 +23,22 @@ importers: version: 9.24.0 '@sveltejs/adapter-auto': specifier: ^4.0.0 - version: 4.0.0(@sveltejs/kit@2.20.7(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.27.0)(vite@6.3.0(jiti@2.4.2)(lightningcss@1.29.2)))(svelte@5.27.0)(vite@6.3.0(jiti@2.4.2)(lightningcss@1.29.2))) + version: 4.0.0(@sveltejs/kit@2.20.7(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.27.0)(vite@6.3.0(@types/node@22.14.1)(jiti@2.4.2)(lightningcss@1.29.2)))(svelte@5.27.0)(vite@6.3.0(@types/node@22.14.1)(jiti@2.4.2)(lightningcss@1.29.2))) '@sveltejs/kit': specifier: ^2.16.0 - version: 2.20.7(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.27.0)(vite@6.3.0(jiti@2.4.2)(lightningcss@1.29.2)))(svelte@5.27.0)(vite@6.3.0(jiti@2.4.2)(lightningcss@1.29.2)) + version: 2.20.7(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.27.0)(vite@6.3.0(@types/node@22.14.1)(jiti@2.4.2)(lightningcss@1.29.2)))(svelte@5.27.0)(vite@6.3.0(@types/node@22.14.1)(jiti@2.4.2)(lightningcss@1.29.2)) '@sveltejs/vite-plugin-svelte': specifier: ^5.0.0 - version: 5.0.3(svelte@5.27.0)(vite@6.3.0(jiti@2.4.2)(lightningcss@1.29.2)) + version: 5.0.3(svelte@5.27.0)(vite@6.3.0(@types/node@22.14.1)(jiti@2.4.2)(lightningcss@1.29.2)) '@tailwindcss/typography': specifier: ^0.5.15 version: 0.5.16(tailwindcss@4.1.4) '@tailwindcss/vite': specifier: ^4.0.0 - version: 4.1.4(vite@6.3.0(jiti@2.4.2)(lightningcss@1.29.2)) + version: 4.1.4(vite@6.3.0(@types/node@22.14.1)(jiti@2.4.2)(lightningcss@1.29.2)) + '@types/node': + specifier: ^22.14.1 + version: 22.14.1 eslint: specifier: ^9.18.0 version: 9.24.0(jiti@2.4.2) @@ -67,7 +77,7 @@ importers: version: 8.30.1(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3) vite: specifier: ^6.2.5 - version: 6.3.0(jiti@2.4.2)(lightningcss@1.29.2) + version: 6.3.0(@types/node@22.14.1)(jiti@2.4.2)(lightningcss@1.29.2) packages: @@ -75,6 +85,70 @@ packages: resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} engines: {node: '>=6.0.0'} + '@azure-rest/core-client@2.3.5': + resolution: {integrity: sha512-vC8sDkT1xH7jx9erJWQqznMhfPY9k+mrM+4R/rAeb+F+KQCohYYmOEAQmT9CEQxStJ3erjbtpN3GMjAS5tdiYg==} + engines: {node: '>=18.0.0'} + + '@azure/abort-controller@2.1.2': + resolution: {integrity: sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==} + engines: {node: '>=18.0.0'} + + '@azure/ai-projects@1.0.0-beta.4': + resolution: {integrity: sha512-+sRwbmNVQd/2atMF9lyfeIIhk7f0POyFiTUvdtiqRfqO6oeVOD4VhA3UaAFITD1JOIeAmRe4l+BwdX1Xy9FGzQ==} + engines: {node: '>=18.0.0'} + + '@azure/core-auth@1.9.0': + resolution: {integrity: sha512-FPwHpZywuyasDSLMqJ6fhbOK3TqUdviZNF8OqRGA4W5Ewib2lEEZ+pBsYcBa88B2NGO/SEnYPGhyBqNlE8ilSw==} + engines: {node: '>=18.0.0'} + + '@azure/core-client@1.9.3': + resolution: {integrity: sha512-/wGw8fJ4mdpJ1Cum7s1S+VQyXt1ihwKLzfabS1O/RDADnmzVc01dHn44qD0BvGH6KlZNzOMW95tEpKqhkCChPA==} + engines: {node: '>=18.0.0'} + + '@azure/core-lro@3.2.0': + resolution: {integrity: sha512-SJEAe8fMDnjz2vw9dvEUi0DhAWe1sYkWQVTDc/HDcgNALaszGRopx3U+z1Pg/C9fsYiArp5pTBEcq29Ddph0WA==} + engines: {node: '>=18.0.0'} + + '@azure/core-paging@1.6.2': + resolution: {integrity: sha512-YKWi9YuCU04B55h25cnOYZHxXYtEvQEbKST5vqRga7hWY9ydd3FZHdeQF8pyh+acWZvppw13M/LMGx0LABUVMA==} + engines: {node: '>=18.0.0'} + + '@azure/core-rest-pipeline@1.19.1': + resolution: {integrity: sha512-zHeoI3NCs53lLBbWNzQycjnYKsA1CVKlnzSNuSFcUDwBp8HHVObePxrM7HaX+Ha5Ks639H7chNC9HOaIhNS03w==} + engines: {node: '>=18.0.0'} + + '@azure/core-sse@2.2.0': + resolution: {integrity: sha512-6Xg/CeW0jRyMoWt+puw2x6Qqkml3tr76Cn/oA9goIcUXtsi3ngmTwCVbwqkUWfhsOfo4F+78LGgiswSxTHN0sg==} + engines: {node: '>=18.0.0'} + + '@azure/core-tracing@1.2.0': + resolution: {integrity: sha512-UKTiEJPkWcESPYJz3X5uKRYyOcJD+4nYph+KpfdPRnQJVrZfk0KJgdnaAWKfhsBBtAf/D58Az4AvCJEmWgIBAg==} + engines: {node: '>=18.0.0'} + + '@azure/core-util@1.11.0': + resolution: {integrity: sha512-DxOSLua+NdpWoSqULhjDyAZTXFdP/LKkqtYuxxz1SCN289zk3OG8UOpnCQAz/tygyACBtWp/BoO72ptK7msY8g==} + engines: {node: '>=18.0.0'} + + '@azure/identity@4.9.0': + resolution: {integrity: sha512-dz2ZvKxDFoTUmJgmkCBVcyuKckgqE1qVxrRPzUhyKN7FyvUbtNPUrGzqSllOAf1OL9TMGgYqZWbIyD0b/AE15g==} + engines: {node: '>=18.0.0'} + + '@azure/logger@1.1.4': + resolution: {integrity: sha512-4IXXzcCdLdlXuCG+8UKEwLA1T1NHqUfanhXYHiQTn+6sfWCZXduqbtXDGceg3Ce5QxTGo7EqmbV6Bi+aqKuClQ==} + engines: {node: '>=18.0.0'} + + '@azure/msal-browser@4.11.0': + resolution: {integrity: sha512-0p5Ut3wORMP+975AKvaSPIO4UytgsfAvJ7RxaTx+nkP+Hpkmm93AuiMkBWKI2x9tApU/SLgIyPz/ZwLYUIWb5Q==} + engines: {node: '>=0.8.0'} + + '@azure/msal-common@15.5.1': + resolution: {integrity: sha512-oxK0khbc4Bg1bKQnqDr7ikULhVL2OHgSrIq0Vlh4b6+hm4r0lr6zPMQE8ZvmacJuh+ZZGKBM5iIObhF1q1QimQ==} + engines: {node: '>=0.8.0'} + + '@azure/msal-node@3.5.1': + resolution: {integrity: sha512-dkgMYM5B6tI88r/oqf5bYd93WkenQpaWwiszJDk7avVjso8cmuKRTW97dA1RMi6RhihZFLtY1VtWxU9+sW2T5g==} + engines: {node: '>=16'} + '@esbuild/aix-ppc64@0.25.2': resolution: {integrity: sha512-wCIboOL2yXZym2cgm6mlA742s9QeJ8DjGVaL39dLN4rRwrOgOyYSnOaFPhKZGLb2ngj4EyfAFjsNJwPXZvseag==} engines: {node: '>=18'} @@ -567,6 +641,9 @@ packages: '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + '@types/node@22.14.1': + resolution: {integrity: sha512-u0HuPQwe/dHrItgHHpmw3N2fYCR6x4ivMNbPHRkBVP4CvN+kiRrKHWk3i8tXiO/joPwXLMYvF9TTF0eqgHIuOw==} + '@typescript-eslint/eslint-plugin@8.30.1': resolution: {integrity: sha512-v+VWphxMjn+1t48/jO4t950D6KR8JaJuNXzi33Ve6P8sEmPr5k6CEXjdGwT6+LodVnEa91EQCtwjWNUCPweo+Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -624,6 +701,10 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + agent-base@7.1.3: + resolution: {integrity: sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==} + engines: {node: '>= 14'} + ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} @@ -655,6 +736,13 @@ packages: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} + buffer-equal-constant-time@1.0.1: + resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} + + bundle-name@4.1.0: + resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==} + engines: {node: '>=18'} + callsites@3.1.0: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} @@ -710,6 +798,18 @@ packages: resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} engines: {node: '>=0.10.0'} + default-browser-id@5.0.0: + resolution: {integrity: sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA==} + engines: {node: '>=18'} + + default-browser@5.2.1: + resolution: {integrity: sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg==} + engines: {node: '>=18'} + + define-lazy-prop@3.0.0: + resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==} + engines: {node: '>=12'} + detect-libc@2.0.3: resolution: {integrity: sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==} engines: {node: '>=8'} @@ -717,6 +817,9 @@ packages: devalue@5.1.1: resolution: {integrity: sha512-maua5KUiapvEwiEAe+XnlZ3Rh0GD+qI1J/nb9vrJc3muPXvcF/8gXYTWF76+5DAqHyDUtOIImEuo0YKE9mshVw==} + ecdsa-sig-formatter@1.0.11: + resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} + enhanced-resolve@5.18.1: resolution: {integrity: sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==} engines: {node: '>=10.13.0'} @@ -868,6 +971,14 @@ packages: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} + http-proxy-agent@7.0.2: + resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} + engines: {node: '>= 14'} + + https-proxy-agent@7.0.6: + resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} + engines: {node: '>= 14'} + ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} @@ -883,6 +994,11 @@ packages: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} + is-docker@3.0.0: + resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + hasBin: true + is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} @@ -891,6 +1007,11 @@ packages: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} + is-inside-container@1.0.0: + resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==} + engines: {node: '>=14.16'} + hasBin: true + is-number@7.0.0: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} @@ -898,6 +1019,10 @@ packages: is-reference@3.0.3: resolution: {integrity: sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==} + is-wsl@3.1.0: + resolution: {integrity: sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==} + engines: {node: '>=16'} + isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} @@ -918,6 +1043,16 @@ packages: json-stable-stringify-without-jsonify@1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + jsonwebtoken@9.0.2: + resolution: {integrity: sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==} + engines: {node: '>=12', npm: '>=6'} + + jwa@1.4.1: + resolution: {integrity: sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==} + + jws@3.2.2: + resolution: {integrity: sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==} + keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} @@ -1010,12 +1145,30 @@ packages: lodash.castarray@4.4.0: resolution: {integrity: sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==} + lodash.includes@4.3.0: + resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==} + + lodash.isboolean@3.0.3: + resolution: {integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==} + + lodash.isinteger@4.0.4: + resolution: {integrity: sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==} + + lodash.isnumber@3.0.3: + resolution: {integrity: sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==} + lodash.isplainobject@4.0.6: resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} + lodash.isstring@4.0.1: + resolution: {integrity: sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==} + lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + lodash.once@4.1.1: + resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==} + magic-string@0.30.17: resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} @@ -1053,6 +1206,10 @@ packages: natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + open@10.1.1: + resolution: {integrity: sha512-zy1wx4+P3PfhXSEPJNtZmJXfhkkIaxU1VauWIrDZw1O7uJRDRJtKr9n3Ic4NgbA16KyOxOXO2ng9gYwCdXuSXA==} + engines: {node: '>=18'} + optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} @@ -1218,6 +1375,10 @@ packages: engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true + run-applescript@7.0.0: + resolution: {integrity: sha512-9by4Ij99JUr/MCFBUkDKLWK3G9HVXmabKz9U5MlIAIuvuzkiOicRYs8XJLxX+xahD+mLiiCYDqF9dKAgtzKP1A==} + engines: {node: '>=18'} + run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} @@ -1225,6 +1386,9 @@ packages: resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==} engines: {node: '>=6'} + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + semver@7.7.1: resolution: {integrity: sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==} engines: {node: '>=10'} @@ -1303,6 +1467,9 @@ packages: peerDependencies: typescript: '>=4.8.4' + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} @@ -1319,12 +1486,19 @@ packages: engines: {node: '>=14.17'} hasBin: true + undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + uuid@8.3.2: + resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} + hasBin: true + vite@6.3.0: resolution: {integrity: sha512-9aC0n4pr6hIbvi1YOpFjwQ+QOTGssvbJKoeYkuHHGWwlXfdxQlI8L2qNMo9awEEcCPSiS+5mJZk5jH1PAqoDeQ==} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} @@ -1400,6 +1574,124 @@ snapshots: '@jridgewell/gen-mapping': 0.3.8 '@jridgewell/trace-mapping': 0.3.25 + '@azure-rest/core-client@2.3.5': + dependencies: + '@azure/abort-controller': 2.1.2 + '@azure/core-auth': 1.9.0 + '@azure/core-rest-pipeline': 1.19.1 + '@azure/core-tracing': 1.2.0 + '@azure/core-util': 1.11.0 + tslib: 2.8.1 + transitivePeerDependencies: + - supports-color + + '@azure/abort-controller@2.1.2': + dependencies: + tslib: 2.8.1 + + '@azure/ai-projects@1.0.0-beta.4': + dependencies: + '@azure-rest/core-client': 2.3.5 + '@azure/abort-controller': 2.1.2 + '@azure/core-auth': 1.9.0 + '@azure/core-lro': 3.2.0 + '@azure/core-paging': 1.6.2 + '@azure/core-rest-pipeline': 1.19.1 + '@azure/core-sse': 2.2.0 + '@azure/core-tracing': 1.2.0 + '@azure/core-util': 1.11.0 + '@azure/logger': 1.1.4 + tslib: 2.8.1 + transitivePeerDependencies: + - supports-color + + '@azure/core-auth@1.9.0': + dependencies: + '@azure/abort-controller': 2.1.2 + '@azure/core-util': 1.11.0 + tslib: 2.8.1 + + '@azure/core-client@1.9.3': + dependencies: + '@azure/abort-controller': 2.1.2 + '@azure/core-auth': 1.9.0 + '@azure/core-rest-pipeline': 1.19.1 + '@azure/core-tracing': 1.2.0 + '@azure/core-util': 1.11.0 + '@azure/logger': 1.1.4 + tslib: 2.8.1 + transitivePeerDependencies: + - supports-color + + '@azure/core-lro@3.2.0': + dependencies: + '@azure/abort-controller': 2.1.2 + '@azure/core-util': 1.11.0 + '@azure/logger': 1.1.4 + tslib: 2.8.1 + + '@azure/core-paging@1.6.2': + dependencies: + tslib: 2.8.1 + + '@azure/core-rest-pipeline@1.19.1': + dependencies: + '@azure/abort-controller': 2.1.2 + '@azure/core-auth': 1.9.0 + '@azure/core-tracing': 1.2.0 + '@azure/core-util': 1.11.0 + '@azure/logger': 1.1.4 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + tslib: 2.8.1 + transitivePeerDependencies: + - supports-color + + '@azure/core-sse@2.2.0': + dependencies: + tslib: 2.8.1 + + '@azure/core-tracing@1.2.0': + dependencies: + tslib: 2.8.1 + + '@azure/core-util@1.11.0': + dependencies: + '@azure/abort-controller': 2.1.2 + tslib: 2.8.1 + + '@azure/identity@4.9.0': + dependencies: + '@azure/abort-controller': 2.1.2 + '@azure/core-auth': 1.9.0 + '@azure/core-client': 1.9.3 + '@azure/core-rest-pipeline': 1.19.1 + '@azure/core-tracing': 1.2.0 + '@azure/core-util': 1.11.0 + '@azure/logger': 1.1.4 + '@azure/msal-browser': 4.11.0 + '@azure/msal-node': 3.5.1 + open: 10.1.1 + tslib: 2.8.1 + transitivePeerDependencies: + - supports-color + + '@azure/logger@1.1.4': + dependencies: + tslib: 2.8.1 + + '@azure/msal-browser@4.11.0': + dependencies: + '@azure/msal-common': 15.5.1 + + '@azure/msal-common@15.5.1': {} + + '@azure/msal-node@3.5.1': + dependencies: + '@azure/msal-common': 15.5.1 + jsonwebtoken: 9.0.2 + uuid: 8.3.2 + '@esbuild/aix-ppc64@0.25.2': optional: true @@ -1635,14 +1927,14 @@ snapshots: dependencies: acorn: 8.14.1 - '@sveltejs/adapter-auto@4.0.0(@sveltejs/kit@2.20.7(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.27.0)(vite@6.3.0(jiti@2.4.2)(lightningcss@1.29.2)))(svelte@5.27.0)(vite@6.3.0(jiti@2.4.2)(lightningcss@1.29.2)))': + '@sveltejs/adapter-auto@4.0.0(@sveltejs/kit@2.20.7(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.27.0)(vite@6.3.0(@types/node@22.14.1)(jiti@2.4.2)(lightningcss@1.29.2)))(svelte@5.27.0)(vite@6.3.0(@types/node@22.14.1)(jiti@2.4.2)(lightningcss@1.29.2)))': dependencies: - '@sveltejs/kit': 2.20.7(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.27.0)(vite@6.3.0(jiti@2.4.2)(lightningcss@1.29.2)))(svelte@5.27.0)(vite@6.3.0(jiti@2.4.2)(lightningcss@1.29.2)) + '@sveltejs/kit': 2.20.7(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.27.0)(vite@6.3.0(@types/node@22.14.1)(jiti@2.4.2)(lightningcss@1.29.2)))(svelte@5.27.0)(vite@6.3.0(@types/node@22.14.1)(jiti@2.4.2)(lightningcss@1.29.2)) import-meta-resolve: 4.1.0 - '@sveltejs/kit@2.20.7(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.27.0)(vite@6.3.0(jiti@2.4.2)(lightningcss@1.29.2)))(svelte@5.27.0)(vite@6.3.0(jiti@2.4.2)(lightningcss@1.29.2))': + '@sveltejs/kit@2.20.7(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.27.0)(vite@6.3.0(@types/node@22.14.1)(jiti@2.4.2)(lightningcss@1.29.2)))(svelte@5.27.0)(vite@6.3.0(@types/node@22.14.1)(jiti@2.4.2)(lightningcss@1.29.2))': dependencies: - '@sveltejs/vite-plugin-svelte': 5.0.3(svelte@5.27.0)(vite@6.3.0(jiti@2.4.2)(lightningcss@1.29.2)) + '@sveltejs/vite-plugin-svelte': 5.0.3(svelte@5.27.0)(vite@6.3.0(@types/node@22.14.1)(jiti@2.4.2)(lightningcss@1.29.2)) '@types/cookie': 0.6.0 cookie: 0.6.0 devalue: 5.1.1 @@ -1655,27 +1947,27 @@ snapshots: set-cookie-parser: 2.7.1 sirv: 3.0.1 svelte: 5.27.0 - vite: 6.3.0(jiti@2.4.2)(lightningcss@1.29.2) + vite: 6.3.0(@types/node@22.14.1)(jiti@2.4.2)(lightningcss@1.29.2) - '@sveltejs/vite-plugin-svelte-inspector@4.0.1(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.27.0)(vite@6.3.0(jiti@2.4.2)(lightningcss@1.29.2)))(svelte@5.27.0)(vite@6.3.0(jiti@2.4.2)(lightningcss@1.29.2))': + '@sveltejs/vite-plugin-svelte-inspector@4.0.1(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.27.0)(vite@6.3.0(@types/node@22.14.1)(jiti@2.4.2)(lightningcss@1.29.2)))(svelte@5.27.0)(vite@6.3.0(@types/node@22.14.1)(jiti@2.4.2)(lightningcss@1.29.2))': dependencies: - '@sveltejs/vite-plugin-svelte': 5.0.3(svelte@5.27.0)(vite@6.3.0(jiti@2.4.2)(lightningcss@1.29.2)) + '@sveltejs/vite-plugin-svelte': 5.0.3(svelte@5.27.0)(vite@6.3.0(@types/node@22.14.1)(jiti@2.4.2)(lightningcss@1.29.2)) debug: 4.4.0 svelte: 5.27.0 - vite: 6.3.0(jiti@2.4.2)(lightningcss@1.29.2) + vite: 6.3.0(@types/node@22.14.1)(jiti@2.4.2)(lightningcss@1.29.2) transitivePeerDependencies: - supports-color - '@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.27.0)(vite@6.3.0(jiti@2.4.2)(lightningcss@1.29.2))': + '@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.27.0)(vite@6.3.0(@types/node@22.14.1)(jiti@2.4.2)(lightningcss@1.29.2))': dependencies: - '@sveltejs/vite-plugin-svelte-inspector': 4.0.1(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.27.0)(vite@6.3.0(jiti@2.4.2)(lightningcss@1.29.2)))(svelte@5.27.0)(vite@6.3.0(jiti@2.4.2)(lightningcss@1.29.2)) + '@sveltejs/vite-plugin-svelte-inspector': 4.0.1(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.27.0)(vite@6.3.0(@types/node@22.14.1)(jiti@2.4.2)(lightningcss@1.29.2)))(svelte@5.27.0)(vite@6.3.0(@types/node@22.14.1)(jiti@2.4.2)(lightningcss@1.29.2)) debug: 4.4.0 deepmerge: 4.3.1 kleur: 4.1.5 magic-string: 0.30.17 svelte: 5.27.0 - vite: 6.3.0(jiti@2.4.2)(lightningcss@1.29.2) - vitefu: 1.0.6(vite@6.3.0(jiti@2.4.2)(lightningcss@1.29.2)) + vite: 6.3.0(@types/node@22.14.1)(jiti@2.4.2)(lightningcss@1.29.2) + vitefu: 1.0.6(vite@6.3.0(@types/node@22.14.1)(jiti@2.4.2)(lightningcss@1.29.2)) transitivePeerDependencies: - supports-color @@ -1745,12 +2037,12 @@ snapshots: postcss-selector-parser: 6.0.10 tailwindcss: 4.1.4 - '@tailwindcss/vite@4.1.4(vite@6.3.0(jiti@2.4.2)(lightningcss@1.29.2))': + '@tailwindcss/vite@4.1.4(vite@6.3.0(@types/node@22.14.1)(jiti@2.4.2)(lightningcss@1.29.2))': dependencies: '@tailwindcss/node': 4.1.4 '@tailwindcss/oxide': 4.1.4 tailwindcss: 4.1.4 - vite: 6.3.0(jiti@2.4.2)(lightningcss@1.29.2) + vite: 6.3.0(@types/node@22.14.1)(jiti@2.4.2)(lightningcss@1.29.2) '@types/cookie@0.6.0': {} @@ -1758,6 +2050,10 @@ snapshots: '@types/json-schema@7.0.15': {} + '@types/node@22.14.1': + dependencies: + undici-types: 6.21.0 + '@typescript-eslint/eslint-plugin@8.30.1(@typescript-eslint/parser@8.30.1(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3)': dependencies: '@eslint-community/regexpp': 4.12.1 @@ -1841,6 +2137,8 @@ snapshots: acorn@8.14.1: {} + agent-base@7.1.3: {} + ajv@6.12.6: dependencies: fast-deep-equal: 3.1.3 @@ -1873,6 +2171,12 @@ snapshots: dependencies: fill-range: 7.1.1 + buffer-equal-constant-time@1.0.1: {} + + bundle-name@4.1.0: + dependencies: + run-applescript: 7.0.0 + callsites@3.1.0: {} chalk@4.1.2: @@ -1912,10 +2216,23 @@ snapshots: deepmerge@4.3.1: {} + default-browser-id@5.0.0: {} + + default-browser@5.2.1: + dependencies: + bundle-name: 4.1.0 + default-browser-id: 5.0.0 + + define-lazy-prop@3.0.0: {} + detect-libc@2.0.3: {} devalue@5.1.1: {} + ecdsa-sig-formatter@1.0.11: + dependencies: + safe-buffer: 5.2.1 + enhanced-resolve@5.18.1: dependencies: graceful-fs: 4.2.11 @@ -2110,6 +2427,20 @@ snapshots: has-flag@4.0.0: {} + http-proxy-agent@7.0.2: + dependencies: + agent-base: 7.1.3 + debug: 4.4.0 + transitivePeerDependencies: + - supports-color + + https-proxy-agent@7.0.6: + dependencies: + agent-base: 7.1.3 + debug: 4.4.0 + transitivePeerDependencies: + - supports-color + ignore@5.3.2: {} import-fresh@3.3.1: @@ -2121,18 +2452,28 @@ snapshots: imurmurhash@0.1.4: {} + is-docker@3.0.0: {} + is-extglob@2.1.1: {} is-glob@4.0.3: dependencies: is-extglob: 2.1.1 + is-inside-container@1.0.0: + dependencies: + is-docker: 3.0.0 + is-number@7.0.0: {} is-reference@3.0.3: dependencies: '@types/estree': 1.0.7 + is-wsl@3.1.0: + dependencies: + is-inside-container: 1.0.0 + isexe@2.0.0: {} jiti@2.4.2: {} @@ -2147,6 +2488,30 @@ snapshots: json-stable-stringify-without-jsonify@1.0.1: {} + jsonwebtoken@9.0.2: + dependencies: + jws: 3.2.2 + lodash.includes: 4.3.0 + lodash.isboolean: 3.0.3 + lodash.isinteger: 4.0.4 + lodash.isnumber: 3.0.3 + lodash.isplainobject: 4.0.6 + lodash.isstring: 4.0.1 + lodash.once: 4.1.1 + ms: 2.1.3 + semver: 7.7.1 + + jwa@1.4.1: + dependencies: + buffer-equal-constant-time: 1.0.1 + ecdsa-sig-formatter: 1.0.11 + safe-buffer: 5.2.1 + + jws@3.2.2: + dependencies: + jwa: 1.4.1 + safe-buffer: 5.2.1 + keyv@4.5.4: dependencies: json-buffer: 3.0.1 @@ -2215,10 +2580,22 @@ snapshots: lodash.castarray@4.4.0: {} + lodash.includes@4.3.0: {} + + lodash.isboolean@3.0.3: {} + + lodash.isinteger@4.0.4: {} + + lodash.isnumber@3.0.3: {} + lodash.isplainobject@4.0.6: {} + lodash.isstring@4.0.1: {} + lodash.merge@4.6.2: {} + lodash.once@4.1.1: {} + magic-string@0.30.17: dependencies: '@jridgewell/sourcemap-codec': 1.5.0 @@ -2248,6 +2625,13 @@ snapshots: natural-compare@1.4.0: {} + open@10.1.1: + dependencies: + default-browser: 5.2.1 + define-lazy-prop: 3.0.0 + is-inside-container: 1.0.0 + is-wsl: 3.1.0 + optionator@0.9.4: dependencies: deep-is: 0.1.4 @@ -2361,6 +2745,8 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.40.0 fsevents: 2.3.3 + run-applescript@7.0.0: {} + run-parallel@1.2.0: dependencies: queue-microtask: 1.2.3 @@ -2369,6 +2755,8 @@ snapshots: dependencies: mri: 1.2.0 + safe-buffer@5.2.1: {} + semver@7.7.1: {} set-cookie-parser@2.7.1: {} @@ -2452,6 +2840,8 @@ snapshots: dependencies: typescript: 5.8.3 + tslib@2.8.1: {} + type-check@0.4.0: dependencies: prelude-ls: 1.2.1 @@ -2468,13 +2858,17 @@ snapshots: typescript@5.8.3: {} + undici-types@6.21.0: {} + uri-js@4.4.1: dependencies: punycode: 2.3.1 util-deprecate@1.0.2: {} - vite@6.3.0(jiti@2.4.2)(lightningcss@1.29.2): + uuid@8.3.2: {} + + vite@6.3.0(@types/node@22.14.1)(jiti@2.4.2)(lightningcss@1.29.2): dependencies: esbuild: 0.25.2 fdir: 6.4.3(picomatch@4.0.2) @@ -2483,13 +2877,14 @@ snapshots: rollup: 4.40.0 tinyglobby: 0.2.12 optionalDependencies: + '@types/node': 22.14.1 fsevents: 2.3.3 jiti: 2.4.2 lightningcss: 1.29.2 - vitefu@1.0.6(vite@6.3.0(jiti@2.4.2)(lightningcss@1.29.2)): + vitefu@1.0.6(vite@6.3.0(@types/node@22.14.1)(jiti@2.4.2)(lightningcss@1.29.2)): optionalDependencies: - vite: 6.3.0(jiti@2.4.2)(lightningcss@1.29.2) + vite: 6.3.0(@types/node@22.14.1)(jiti@2.4.2)(lightningcss@1.29.2) which@2.0.2: dependencies: diff --git a/src/lib/agent/services/agentService.ts b/src/lib/agent/services/agentService.ts new file mode 100644 index 0000000..86adca6 --- /dev/null +++ b/src/lib/agent/services/agentService.ts @@ -0,0 +1,47 @@ +import { DefaultAzureCredential } from '@azure/identity'; +import { AIProjectsClient } from '@azure/ai-projects'; +import { rubricMatcherTool } from '../tools/rubricMatcherTool'; +import { essayAnalyzerTool } from '../tools/essayAnalyzerTool'; +import { ToolUtility } from '@azure/ai-projects'; +import { AI_FOUNDRY_PROJECT_CONNECTION_STRING, AI_MODEL } from '$env/static/private'; + +const connectionString = AI_FOUNDRY_PROJECT_CONNECTION_STRING; +if (!connectionString) { + throw new Error('AI_FOUNDRY_PROJECT_CONNECTION_STRING is not set in environment variables'); +} + +const aiModel = AI_MODEL || ''; +if (!connectionString) { + throw new Error('AI_MODEL is not set in environment variables'); +} + +const client = AIProjectsClient.fromConnectionString( + connectionString, + new DefaultAzureCredential() +); + +const codeInterpreterTool = ToolUtility.createCodeInterpreterTool([]); + +const tools = [codeInterpreterTool.definition, rubricMatcherTool.definition, essayAnalyzerTool.definition]; + +const instructions = "You are a helpful agent that can assist with providing feedback on a student's work for a teacher."; + +const toolResources = { + ...codeInterpreterTool.resources, + matchRubric: {}, + analyzeEssay: {} +}; + +const agent = await client.agents.createAgent(aiModel, { + name: `agent-assessment-feedback`, + instructions, + temperature: 0.5, + tools, + toolResources, + + requestOptions: { + headers: { "x-ms-enable-preview": "true" }, + }, +}); + +export { client, agent }; \ No newline at end of file diff --git a/src/lib/agent/tools/essayAnalyzerTool.ts b/src/lib/agent/tools/essayAnalyzerTool.ts new file mode 100644 index 0000000..2382d38 --- /dev/null +++ b/src/lib/agent/tools/essayAnalyzerTool.ts @@ -0,0 +1,13 @@ +import { ToolUtility } from '@azure/ai-projects'; + +export const essayAnalyzerTool = ToolUtility.createFunctionTool({ + name: 'analyzeEssay', + description: 'Analyzes an essay for structure, argument, and evidence.', + parameters: { + type: 'object', + properties: { + essay: { type: 'string', description: 'The essay text' } + }, + required: ['essay'] + } +}); diff --git a/src/lib/agent/tools/rubricMatcherTool.ts b/src/lib/agent/tools/rubricMatcherTool.ts new file mode 100644 index 0000000..ca79717 --- /dev/null +++ b/src/lib/agent/tools/rubricMatcherTool.ts @@ -0,0 +1,14 @@ +import { ToolUtility } from '@azure/ai-projects'; + +export const rubricMatcherTool = ToolUtility.createFunctionTool({ + name: 'matchRubric', + description: 'Matches a submission to a rubric and returns a score/criteria breakdown.', + parameters: { + type: 'object', + properties: { + submission: { type: 'string', description: 'The student submission text' }, + rubric: { type: 'string', description: 'The rubric criteria' } + }, + required: ['submission', 'rubric'] + } +}); diff --git a/src/lib/utils/ai.ts b/src/lib/utils/ai.ts index 4005d3d..20bcf5f 100644 --- a/src/lib/utils/ai.ts +++ b/src/lib/utils/ai.ts @@ -1,4 +1,5 @@ -import type { OpenAIResponse } from './types'; +import { client, agent } from '../agent/services/agentService'; +import type { MessageTextContentOutput, ToolOutput } from '@azure/ai-projects'; const OPENAI_API_KEY = process.env['AZURE_OPENAI_API_KEY']; const OPENAI_ENDPOINT = process.env['AZURE_OPENAI_ENDPOINT']; @@ -26,69 +27,162 @@ Follow these steps: 6. Suggest to the teacher one way to support this student in the next lesson. 7. Show your reasoning step by step (chain-of-thought). -RESPONSE FORMAT: -Grade: /10 -Strengths: -Areas for Improvement: -Individualized Activity: -Reflection Question: -Teacher Suggestion: -Reasoning: `; +RESPONSE FORMAT (respond with a single JSON object, no extra text): + +{ + "grade": "", + "strengths": "", + "areas_for_improvement": "", + "individualized_activity": "", + "reflection_question": "", + "teacher_suggestion": "", + "reasoning": "" } -export async function gradeWithOpenAI(submission: string, task: string): Promise { - if (!OPENAI_API_KEY || !OPENAI_ENDPOINT) { - // Fallback for local/demo mode - return { - grade: '7/10', - strengths: 'Clear argument and good evidence (in response to the task: ' + task + ").", - areas_for_improvement: 'Needs deeper analysis and more examples.', - individualized_activity: 'Write a paragraph expanding on your main point and provide two additional examples to support your argument.', - reflection_question: 'What was the most challenging part of this assignment for you, and why?', - teacher_suggestion: 'In the next lesson, review how to develop arguments with supporting evidence and provide a model answer for comparison.', - reasoning: 'The submission demonstrates a good structure and evidence, but lacks depth and breadth of analysis. The activity and suggestions are designed to target this gap.' - }; - } +Respond ONLY with the JSON object, with no preamble or explanation. +`; +} - const prompt = buildGradingPrompt(submission, task); - - const res = await fetch(`${OPENAI_ENDPOINT}/openai/deployments/${OPENAI_DEPLOYMENT}/chat/completions?api-version=2024-02-15-preview`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'api-key': OPENAI_API_KEY - }, - body: JSON.stringify({ - messages: [ - { role: 'system', content: 'You are a helpful, fair, and transparent grading assistant.' }, - { role: 'user', content: prompt } - ], - max_tokens: 500, - temperature: 0.2, - stream: false - }) - }); - - if (!res.ok) throw new Error('OpenAI API error'); - const data = await res.json(); - const content = data.choices?.[0]?.message?.content || ''; - - // Parse the response using regex - const gradeMatch = content.match(/Grade:\s*(.*)/i); - const strengthsMatch = content.match(/Strengths:\s*([\s\S]*?)Areas for Improvement:/i); - const improvementMatch = content.match(/Areas for Improvement:\s*([\s\S]*?)Individualized Activity:/i); - const activityMatch = content.match(/Individualized Activity:\s*([\s\S]*?)Reflection Question:/i); - const reflectionMatch = content.match(/Reflection Question:\s*([\s\S]*?)Teacher Suggestion:/i); - const teacherSuggestionMatch = content.match(/Teacher Suggestion:\s*([\s\S]*?)Reasoning:/i); - const reasoningMatch = content.match(/Reasoning:\s*([\s\S]*)/i); +export type OpenAIResponse = { + grade: string; + strengths: string; + areas_for_improvement: string; + individualized_activity: string; + reflection_question: string; + teacher_suggestion: string; + reasoning: string; +}; +function fallbackGrade(submission: string, task: string): OpenAIResponse { + console.log('using fallback'); return { - grade: gradeMatch?.[1]?.trim() || '', - strengths: strengthsMatch?.[1]?.trim() || '', - areas_for_improvement: improvementMatch?.[1]?.trim() || '', - individualized_activity: activityMatch?.[1]?.trim() || '', - reflection_question: reflectionMatch?.[1]?.trim() || '', - teacher_suggestion: teacherSuggestionMatch?.[1]?.trim() || '', - reasoning: reasoningMatch?.[1]?.trim() || '' + grade: '7/10', + strengths: 'Clear argument and good evidence (in response to the task: ' + task + ").", + areas_for_improvement: 'Needs deeper analysis and more examples.', + individualized_activity: 'Write a paragraph expanding on your main point and provide two additional examples to support your argument.', + reflection_question: 'What was the most challenging part of this assignment for you, and why?', + teacher_suggestion: 'In the next lesson, review how to develop arguments with supporting evidence and provide a model answer for comparison.', + reasoning: 'The submission demonstrates a good structure and evidence, but lacks depth and breadth of analysis. The activity and suggestions are designed to target this gap.' }; } + +export async function gradeSubmissionWithAgent(submission: string, task: string): Promise { + try { + if (!client || !agent) throw new Error('Agent not available'); + + // 1. Create a new thread + const thread = await client.agents.createThread(); + + // 2. Add a message to the thread + await client.agents.createMessage(thread.id, { + role: 'user', + content: buildGradingPrompt(submission, task) + }); + + // 3. Run the agent + const run = await client.agents.createRun(thread.id, agent.id, {}); + + // 4. Poll for run completion + let runResult; + for (let i = 0; i < 60; i++) { // up to ~60 seconds + runResult = await client.agents.getRun(thread.id, run.id); + console.log('Agent run status:', runResult.status); + + if (runResult.status === 'completed') break; + if (runResult.status === 'failed') { + const messages = await client.agents.listMessages(thread.id); + const agentMessage = messages.data.find((m: { role: string; content: any[] }) => m.role === 'agent'); + let errorText = ''; + if (agentMessage && Array.isArray(agentMessage.content) && agentMessage.content.length > 0) { + const contentItem = agentMessage.content[0]; + if (contentItem.type === 'text' && contentItem.text && typeof contentItem.text.value === 'string') { + errorText = contentItem.text.value; + } else if (typeof contentItem.value === 'string') { + errorText = contentItem.value; + } + } + throw new Error(`Agent run failed. Status: failed. Message: ${errorText}`); + } + + // Handle tool calls if agent requires action + if (runResult.status === 'requires_action' && runResult.requiredAction?.submitToolOutputs.toolCalls) { + const toolCalls = runResult.requiredAction.submitToolOutputs.toolCalls; + const toolOutputs: ToolOutput[] = await Promise.all(toolCalls.map(async (call: any) => { + let output = ''; + const args = JSON.parse(call.function.arguments); + + if ( + call.function.name === 'matchRubric' || + call.function.name === 'analyzeEssay' + ) { + // LLM will handle the tool logic, just submit empty output + output = ''; + } else { + output = `Unknown tool: ${call.function.name}`; + } + + return { + toolCallId: call.id, + output: typeof output === 'string' ? output : JSON.stringify(output) + }; + })); + await client.agents.submitToolOutputsToRun(thread.id, run.id, toolOutputs); + } + + await new Promise(res => setTimeout(res, 1000)); + } + if (!runResult || runResult.status !== 'completed') { + const messages = await client.agents.listMessages(thread.id); + const agentMessage = messages.data.find((m: { role: string; content: any[] }) => m.role === 'agent'); + let errorText = ''; + if (agentMessage && Array.isArray(agentMessage.content) && agentMessage.content.length > 0) { + const contentItem = agentMessage.content[0]; + if (contentItem.type === 'text' && contentItem.text && typeof contentItem.text.value === 'string') { + errorText = contentItem.text.value; + } else if (typeof contentItem.value === 'string') { + errorText = contentItem.value; + } + } + throw new Error(`Agent run did not complete in time. Last status: ${runResult?.status}. Last message: ${errorText}`); + } + + // 5. Get agent's feedback message + const messages = await client.agents.listMessages(thread.id); + const agentMessage = messages.data.find((m: { role: string; content: any[] }) => m.role === 'assistant'); + // Safely extract text content from agent message + let text = ''; + if (agentMessage && Array.isArray(agentMessage.content) && agentMessage.content.length > 0) { + const contentItem = agentMessage.content[0]; + if (contentItem.type === 'text' && 'text' in contentItem) { + text = (contentItem as MessageTextContentOutput).text.value; + } else if ('value' in contentItem && typeof (contentItem as any).value === 'string') { + text = (contentItem as any).value; + } + } + + // 6. Parse agent's response into OpenAIResponse format + let parsed: OpenAIResponse | null = null; + try { + parsed = JSON.parse(text); + } catch (err) { + console.error(err); + } + if (parsed && typeof parsed === 'object' && parsed.grade) { + return parsed; + } + // If parsing fails, return raw text in reasoning + return { + grade: '', + strengths: '', + areas_for_improvement: '', + individualized_activity: '', + reflection_question: '', + teacher_suggestion: '', + reasoning: text || 'No feedback generated.' + }; + } catch (err) { + console.error(err); + // Fallback to local/demo logic + return fallbackGrade(submission, task); + } +} diff --git a/src/routes/api/grade/+server.ts b/src/routes/api/grade/+server.ts index fd76029..8a4993c 100644 --- a/src/routes/api/grade/+server.ts +++ b/src/routes/api/grade/+server.ts @@ -1,6 +1,6 @@ import type { RequestHandler } from '@sveltejs/kit'; import type { RequestEvent } from '@sveltejs/kit'; -import { gradeWithOpenAI } from '$lib/utils/ai'; +import { gradeSubmissionWithAgent } from '$lib/utils/ai'; export const POST: RequestHandler = async (event: RequestEvent) => { try { @@ -28,8 +28,8 @@ export const POST: RequestHandler = async (event: RequestEvent) => { return new Response(JSON.stringify({ error: 'No valid input provided.' }), { status: 400 }); } - // Call the AI grading agent - const aiResult = await gradeWithOpenAI(submission, task.trim()); + // Call the AI grading agent with threading and fallback + const aiResult = await gradeSubmissionWithAgent(submission, task.trim()); return new Response(JSON.stringify({ success: true,