godmod3-api / api /routes /feedback.ts
pliny-the-prompter's picture
Upload 22 files
c78c312 verified
/**
* Feedback Loop API Routes
*
* POST /v1/feedback — Submit a rating for a response
* GET /v1/feedback/stats — Get learning statistics
*
* The feedback loop learns from user ratings via Exponential Moving Average.
* Learned adjustments are applied to future AutoTune parameter computations.
*/
import { Router } from 'express'
import {
createInitialFeedbackState,
processFeedback,
computeHeuristics,
getFeedbackStats,
type FeedbackRecord,
type FeedbackState,
} from '../../src/lib/autotune-feedback'
import type { ContextType, AutoTuneParams } from '../../src/lib/autotune'
import { updateSharedProfiles } from './autotune'
export const feedbackRoutes = Router()
// In-memory feedback state (resets on restart — research preview)
let feedbackState: FeedbackState = createInitialFeedbackState()
const VALID_CONTEXTS: ContextType[] = ['code', 'creative', 'analytical', 'conversational', 'chaotic']
feedbackRoutes.post('/', (req, res) => {
try {
const {
message_id,
context_type,
model = 'unknown',
persona = 'default',
rating,
params,
response_text,
} = req.body
// Validate
if (!message_id || typeof message_id !== 'string') {
res.status(400).json({ error: 'message_id (string) is required' })
return
}
if (!VALID_CONTEXTS.includes(context_type)) {
res.status(400).json({
error: `Invalid context_type. Must be one of: ${VALID_CONTEXTS.join(', ')}`,
})
return
}
if (rating !== 1 && rating !== -1) {
res.status(400).json({ error: 'rating must be 1 (positive) or -1 (negative)' })
return
}
if (!params || typeof params !== 'object') {
res.status(400).json({ error: 'params (AutoTuneParams object) is required' })
return
}
// Compute heuristics from response text if provided
const heuristics = response_text
? computeHeuristics(String(response_text))
: { responseLength: 0, repetitionScore: 0, averageSentenceLength: 0, vocabularyDiversity: 0 }
const record: FeedbackRecord = {
messageId: message_id,
timestamp: Date.now(),
contextType: context_type,
model: String(model),
persona: String(persona),
params: params as AutoTuneParams,
rating,
heuristics,
}
// Process and update state
feedbackState = processFeedback(feedbackState, record)
// Sync learned profiles to the AutoTune route
updateSharedProfiles(feedbackState.learnedProfiles)
res.json({
accepted: true,
total_feedback: feedbackState.history.length,
context_type,
learned: feedbackState.learnedProfiles[context_type].sampleCount >= 3,
})
} catch (err: any) {
res.status(500).json({ error: err.message })
}
})
feedbackRoutes.get('/stats', (_req, res) => {
const stats = getFeedbackStats(feedbackState)
res.json({
total_feedback: stats.totalFeedback,
positive_rate: stats.positiveRate,
context_breakdown: stats.contextBreakdown,
oldest_record: stats.oldestRecord,
newest_record: stats.newestRecord,
})
})