-
Notifications
You must be signed in to change notification settings - Fork 1
Implement MVP Feedbot #195
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Changes from 2 commits
Commits
Show all changes
19 commits
Select commit
Hold shift + click to select a range
cb047a7
implement MVP feedbot
dbp 8643517
feedbot: refactor extra data to llm subobject, make more configurable…
dbp dfbb5a2
feedbot: add feedback
dbp e0977c0
Fix types, add more configurability, clean up UI to work with dark mo…
jon-bell 2fa9953
Embedded Pyret REPL (#170)
ironm00n 4a9a313
More metrics w/ grafana dashboard, improve SIS reliability, receive e…
jon-bell b4cb9d0
Update seed script, streamline feedback
jon-bell a496a61
Merge remote-tracking branch 'origin/staging' into feedbot
dbp 52cb789
oops, missing file
dbp aa0b2d7
Add configurable feedbot rate limiting
dbp 845a03b
unit test
jon-bell 927a87d
linting, remove 'any' types
jon-bell 9766a8e
also fix bad merge :(
jon-bell 93ccdd0
fix test setup, add metrics
jon-bell 3c1be06
fix for when model responds with nothing, due to running out of tokens
dbp f30281c
change llm output type to still show output, above form; that way out…
dbp bfe847c
final metrics edits
jon-bell c42e2b1
Merge branch 'feedbot' of github.com:pawtograder/platform into feedbot
jon-bell db6bd1b
sneak in an invite fix, last coderabbit stuff
jon-bell File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,165 @@ | ||
import { NextRequest, NextResponse } from "next/server"; | ||
import { createClient } from "@/utils/supabase/server"; | ||
import { createClient as createServiceClient } from "@supabase/supabase-js"; | ||
import OpenAI from "openai"; | ||
import * as Sentry from "@sentry/nextjs"; | ||
import { GraderResultTestExtraData } from "@/utils/supabase/DatabaseTypes"; | ||
|
||
const openai = new OpenAI({ | ||
apiKey: process.env.OPENAI_API_KEY | ||
}); | ||
|
||
export async function POST(request: NextRequest) { | ||
try { | ||
// Check for required OpenAI configuration | ||
if (!process.env.OPENAI_API_KEY) { | ||
Sentry.captureMessage("OpenAI API key missing", "error"); | ||
return NextResponse.json({ error: "OpenAI API key missing" }, { status: 500 }); | ||
} | ||
|
||
const { testId } = await request.json(); | ||
|
||
Sentry.setTag("testId", testId); | ||
|
||
if (!testId || typeof testId !== "number" || testId < 0 || !Number.isInteger(testId)) { | ||
return NextResponse.json({ error: "testId must be a non-negative integer" }, { status: 400 }); | ||
} | ||
|
||
// Verify user has access to this test result | ||
const supabase = await createClient(); | ||
const { | ||
data: { user } | ||
} = await supabase.auth.getUser(); | ||
|
||
if (!user) { | ||
return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); | ||
} | ||
|
||
Sentry.setUser({ id: user.id }); | ||
|
||
// Try to get the test result - RLS will handle access control automatically | ||
const { data: testResult, error: testError } = await supabase | ||
.from("grader_result_tests") | ||
.select( | ||
` | ||
id, | ||
extra_data, | ||
grader_results!inner ( | ||
submissions!inner ( | ||
id | ||
) | ||
) | ||
` | ||
) | ||
.eq("id", testId) | ||
.single(); | ||
|
||
if (testError || !testResult) { | ||
if (testError) { | ||
Sentry.captureException(testError, { | ||
tags: { operation: "fetch_test_result", testId: testId.toString() } | ||
}); | ||
} | ||
return NextResponse.json({ error: "Test result not found or access denied" }, { status: 404 }); | ||
} | ||
|
||
// Get the prompt from extra_data and check if hint already exists | ||
const extraData = testResult.extra_data as GraderResultTestExtraData | null; | ||
if (!extraData?.llm?.prompt) { | ||
return NextResponse.json({ error: "No LLM hint prompt found" }, { status: 400 }); | ||
} | ||
|
||
// Check if hint has already been generated | ||
if (extraData.llm.result) { | ||
return NextResponse.json({ | ||
success: true, | ||
response: extraData.llm.result, | ||
cached: true | ||
}); | ||
} | ||
|
||
// Call OpenAI API with the prompt from the database | ||
const apiParams: any = { | ||
model: extraData.llm.model || process.env.OPENAI_MODEL || "gpt-4o-mini", | ||
messages: [ | ||
{ | ||
role: "user", | ||
content: extraData.llm.prompt | ||
} | ||
], | ||
user: `pawtograder:${user.id}` | ||
}; | ||
|
||
// Only include optional parameters if they're explicitly set in extra_data | ||
if (extraData.llm.temperature !== undefined) { | ||
const temp = extraData.llm.temperature; | ||
if (typeof temp === "number" && temp >= 0 && temp <= 2) { | ||
apiParams.temperature = temp; | ||
} | ||
} | ||
|
||
if (extraData.llm.max_tokens !== undefined) { | ||
const maxTokens = extraData.llm.max_tokens; | ||
if (typeof maxTokens === "number" && maxTokens > 0) { | ||
apiParams.max_completion_tokens = maxTokens; | ||
} | ||
} | ||
|
||
const completion = await openai.chat.completions.create(apiParams); | ||
|
||
const aiResponse = completion.choices[0]?.message?.content; | ||
|
||
if (!aiResponse) { | ||
return NextResponse.json({ error: "No response from AI" }, { status: 500 }); | ||
} | ||
|
||
// Store the result in the database | ||
const updatedExtraData: GraderResultTestExtraData = { | ||
...extraData, | ||
llm: { | ||
...extraData.llm, | ||
result: aiResponse | ||
} | ||
}; | ||
|
||
// Use service role client for the update since users might not have update permissions | ||
const serviceSupabase = createServiceClient(process.env.SUPABASE_URL!, process.env.SUPABASE_SERVICE_ROLE_KEY!); | ||
|
||
const { error: updateError } = await serviceSupabase | ||
.from("grader_result_tests") | ||
.update({ extra_data: updatedExtraData }) | ||
.eq("id", testId); | ||
|
||
if (updateError) { | ||
Sentry.captureException(updateError, { | ||
tags: { | ||
operation: "store_llm_hint", | ||
testId: testId.toString() | ||
}, | ||
extra: { | ||
updateError: updateError.message, | ||
testId | ||
} | ||
}); | ||
// Still return the result even if storage fails | ||
jon-bell marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
return NextResponse.json({ | ||
success: true, | ||
response: aiResponse, | ||
usage: completion.usage, | ||
cached: false | ||
}); | ||
} catch (error) { | ||
Sentry.captureException(error, { | ||
tags: { | ||
operation: "llm_hint_api", | ||
testId: "unknown" | ||
}, | ||
extra: { | ||
error: error instanceof Error ? error.message : String(error) | ||
} | ||
}); | ||
return NextResponse.json({ error: "Internal server error" }, { status: 500 }); | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.