feat(*): Add AI agent feedback

This commit is contained in:
Josh Creek
2025-04-20 23:05:17 +01:00
parent 010a1b8e2b
commit 98ff117f73
8 changed files with 658 additions and 97 deletions
+4 -11
View File
@@ -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
+6 -1
View File
@@ -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"
}
}
+417 -22
View File
@@ -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:
+47
View File
@@ -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 };
+13
View File
@@ -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']
}
});
+14
View File
@@ -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']
}
});
+154 -60
View File
@@ -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: <number>/10
Strengths: <text>
Areas for Improvement: <text>
Individualized Activity: <text>
Reflection Question: <text>
Teacher Suggestion: <text>
Reasoning: <step-by-step explanation>`;
RESPONSE FORMAT (respond with a single JSON object, no extra text):
{
"grade": "<number or string, e.g. 8/10 or 8>",
"strengths": "<text>",
"areas_for_improvement": "<text>",
"individualized_activity": "<text>",
"reflection_question": "<text>",
"teacher_suggestion": "<text>",
"reasoning": "<step-by-step explanation>"
}
export async function gradeWithOpenAI(submission: string, task: string): Promise<OpenAIResponse> {
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<OpenAIResponse> {
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);
}
}
+3 -3
View File
@@ -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,