2026-07-17 22:10:34 +02:00

216 lines
6.0 KiB
TypeScript

import type { Sql } from 'postgres'
export type FeedbackOutcome = 'success' | 'fallback' | 'timeout' | 'error' | 'user_rejected'
export interface FeedbackRequest {
requestId: string
agentId: string
outcome: FeedbackOutcome
completionQuality?: number // 0-10
latencyMs?: number
tokenCount?: number
userSatisfaction?: number // 0-10 from UI
metadata?: Record<string, unknown>
}
export interface FeedbackStats {
agentId: string
totalFeedback: number
outcomeBreakdown: Record<FeedbackOutcome, number>
avgQuality: number
avgSatisfaction: number
}
export class FeedbackProcessor {
constructor(private db: Sql) {}
async processFeedback(feedback: FeedbackRequest): Promise<void> {
const timestamp = new Date()
await this.db`
INSERT INTO agent_feedback (
request_id, agent_id, outcome, completion_quality, latency_ms,
token_count, metadata, created_at
) VALUES (
${feedback.requestId},
${feedback.agentId},
${feedback.outcome},
${feedback.completionQuality || null},
${feedback.latencyMs || null},
${feedback.tokenCount || null},
${JSON.stringify({
userSatisfaction: feedback.userSatisfaction,
...feedback.metadata
})},
${timestamp}
)
`
}
async getFeedbackStats(
agentId: string,
hours: number = 24
): Promise<FeedbackStats> {
const cutoff = new Date(Date.now() - hours * 60 * 60 * 1000)
const results = await this.db`
SELECT
outcome,
COUNT(*) as count,
AVG(completion_quality) as avg_quality,
AVG((metadata->>'userSatisfaction')::int) as avg_satisfaction
FROM agent_feedback
WHERE agent_id = ${agentId} AND created_at > ${cutoff}
GROUP BY outcome
`
const outcomeBreakdown: Record<FeedbackOutcome, number> = {
success: 0,
fallback: 0,
timeout: 0,
error: 0,
user_rejected: 0
}
let totalFeedback = 0
let totalQuality = 0
let qualityCount = 0
let totalSatisfaction = 0
let satisfactionCount = 0
for (const row of results as any[]) {
const outcome = row.outcome as FeedbackOutcome
const count = Number(row.count)
outcomeBreakdown[outcome] = count
totalFeedback += count
if (row.avg_quality) {
totalQuality += Number(row.avg_quality)
qualityCount++
}
if (row.avg_satisfaction) {
totalSatisfaction += Number(row.avg_satisfaction)
satisfactionCount++
}
}
return {
agentId,
totalFeedback,
outcomeBreakdown,
avgQuality: qualityCount > 0 ? totalQuality / qualityCount : 0,
avgSatisfaction: satisfactionCount > 0 ? totalSatisfaction / satisfactionCount : 0
}
}
async getOutcomeDistribution(
agentId: string,
hours: number = 24
): Promise<{ outcome: FeedbackOutcome; percentage: number }[]> {
const cutoff = new Date(Date.now() - hours * 60 * 60 * 1000)
const results = await this.db`
SELECT outcome, COUNT(*) as count
FROM agent_feedback
WHERE agent_id = ${agentId} AND created_at > ${cutoff}
GROUP BY outcome
`
const total = results.reduce((sum, row: any) => sum + Number(row.count), 0)
if (total === 0) return []
return results.map((row: any) => ({
outcome: row.outcome as FeedbackOutcome,
percentage: (Number(row.count) / total) * 100
}))
}
async identifySuccessfulModels(agentId: string): Promise<string[]> {
const cutoff = new Date(Date.now() - 24 * 60 * 60 * 1000)
const results = await this.db`
SELECT DISTINCT log.model
FROM agent_request_log log
INNER JOIN agent_feedback fb ON log.request_id = fb.request_id
WHERE log.agent_id = ${agentId}
AND log.created_at > ${cutoff}
AND fb.outcome = 'success'
ORDER BY log.model
`
return results.map((row: any) => row.model)
}
async computeConfidenceScore(
agentId: string,
model: string
): Promise<number> {
const day = new Date()
day.setDate(day.getDate() - 1)
const feedback = await this.db`
SELECT
COUNT(CASE WHEN outcome = 'success' THEN 1 END)::float / COUNT(*) as success_rate,
AVG(completion_quality) as avg_quality,
AVG((metadata->>'userSatisfaction')::int) as avg_satisfaction
FROM agent_feedback fb
INNER JOIN agent_request_log log ON fb.request_id = log.request_id
WHERE fb.agent_id = ${agentId}
AND log.model = ${model}
AND fb.created_at > ${day}
`
if (feedback.length === 0) return 0.5 // Default neutral confidence
const row = feedback[0] as any
const successRate = Number(row.success_rate || 0)
const avgQuality = Number(row.avg_quality || 5) / 10 // Normalize to 0-1
const avgSatisfaction = Number(row.avg_satisfaction || 5) / 10 // Normalize to 0-1
// Weighted average: 50% success, 25% quality, 25% satisfaction
const score = successRate * 0.5 + avgQuality * 0.25 + avgSatisfaction * 0.25
return Math.min(1, Math.max(0, score))
}
async shouldUpdateConfidence(
agentId: string,
model: string,
newScore: number,
threshold: number = 0.1
): Promise<boolean> {
// Get current confidence
const current = await this.db`
SELECT score FROM agent_confidence_scores
WHERE agent_id = ${agentId} AND model = ${model}
`
if (current.length === 0) return true // Always update if no history
const currentScore = Number(current[0].score)
return Math.abs(newScore - currentScore) > threshold
}
async processUserFeedback(
requestId: string,
agentId: string,
satisfaction: number // 0-10
): Promise<void> {
const metadata = { userSatisfaction: satisfaction }
await this.db`
INSERT INTO agent_feedback (
request_id, agent_id, outcome, metadata
) VALUES (
${requestId},
${agentId},
'success',
${JSON.stringify(metadata)}
)
ON CONFLICT (request_id) DO UPDATE SET
metadata = ${JSON.stringify(metadata)}
`
}
}
export default FeedbackProcessor