feat: persist rectification analysis traces
This commit is contained in:
@@ -26,15 +26,19 @@ export function createRectificationV4CaseService(
|
||||
const realizeQuestion = options.regenerateQuestion ?? regenerateQuestionRealization;
|
||||
|
||||
async function response(userId: string, caseValue: RectificationV4Case, jobId?: string): Promise<RectificationV4ApiResponse> {
|
||||
const [events, turns] = await Promise.all([
|
||||
const [events, turns, analysis] = await Promise.all([
|
||||
store.loadEvents(userId, caseValue.id),
|
||||
store.loadTurns(userId, caseValue.id),
|
||||
caseValue.deploymentMode === "v5_agent"
|
||||
? store.loadAnalysisMessages(userId, caseValue.id)
|
||||
: Promise.resolve([]),
|
||||
]);
|
||||
return {
|
||||
case: caseValue,
|
||||
job: jobId ? await store.loadJob(userId, jobId) : null,
|
||||
events: [...events],
|
||||
turns: [...turns],
|
||||
analysis: [...analysis],
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -270,11 +270,49 @@ export const rectificationV4JobSchema = z.object({
|
||||
}).strict();
|
||||
export type RectificationV4Job = z.infer<typeof rectificationV4JobSchema>;
|
||||
|
||||
export const rectificationAnalysisStageSchema = z.object({
|
||||
phase: z.enum([
|
||||
"extracting_evidence",
|
||||
"scoring_candidates",
|
||||
"checking_robustness",
|
||||
"planning_question",
|
||||
"reasoning",
|
||||
"rendering",
|
||||
]),
|
||||
label: z.string().trim().min(1).max(120),
|
||||
status: z.enum(["completed", "failed"]),
|
||||
durationMs: z.number().int().min(0).max(300_000).nullable(),
|
||||
}).strict();
|
||||
|
||||
export const rectificationAnalysisToolCallSchema = z.object({
|
||||
category: z.enum(["candidate_engine", "diagnostic", "agent_diagnostic"]),
|
||||
label: z.string().trim().min(1).max(120),
|
||||
outcome: z.enum(["succeeded", "failed", "rejected"]),
|
||||
durationMs: z.number().int().min(0).max(300_000).nullable(),
|
||||
}).strict();
|
||||
|
||||
export const rectificationAnalysisTraceSchema = z.object({
|
||||
status: z.enum(["completed", "failed", "legacy"]),
|
||||
stages: z.array(rectificationAnalysisStageSchema).max(12),
|
||||
toolCalls: z.array(rectificationAnalysisToolCallSchema).max(16),
|
||||
techniques: z.array(z.string().trim().min(1).max(120)).max(24),
|
||||
reasoningSummary: z.string().trim().min(1).max(500).nullable(),
|
||||
reasoningSource: z.enum(["provider_summary", "none"]),
|
||||
}).strict();
|
||||
export type RectificationAnalysisTrace = z.infer<typeof rectificationAnalysisTraceSchema>;
|
||||
|
||||
export const rectificationAnalysisItemSchema = z.object({
|
||||
sourceTurnId: z.string().uuid(),
|
||||
trace: rectificationAnalysisTraceSchema,
|
||||
}).strict();
|
||||
export type RectificationAnalysisItem = z.infer<typeof rectificationAnalysisItemSchema>;
|
||||
|
||||
export const rectificationV4ApiResponseSchema = z.object({
|
||||
case: rectificationV4CaseSchema,
|
||||
job: rectificationV4JobSchema.nullable(),
|
||||
events: z.array(lifeEventRevisionSchema),
|
||||
turns: z.array(rectificationV4TurnSchema),
|
||||
analysis: z.array(rectificationAnalysisItemSchema).optional(),
|
||||
}).strict();
|
||||
export type RectificationV4ApiResponse = z.infer<typeof rectificationV4ApiResponseSchema>;
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { AgentRun, CandidateFeatureSnapshot, DiagnosticsSummary, PublicMessage, ValidatedDecision } from "../rectification-agent/contracts.ts";
|
||||
import type { AgentRun, CandidateFeatureSnapshot, DiagnosticsSummary, StoredPublicMessage, ValidatedDecision } from "../rectification-agent/contracts.ts";
|
||||
import type {
|
||||
LifeEventRevision,
|
||||
PendingEvidence,
|
||||
@@ -20,7 +20,7 @@ export function createRectificationV4MemoryStore(): RectificationV4Store & {
|
||||
readonly diagnostics: Map<string, DiagnosticsSummary>;
|
||||
readonly featureSnapshots: Map<string, CandidateFeatureSnapshot>;
|
||||
readonly agentRuns: Map<string, AgentRun>;
|
||||
readonly publicMessages: Map<string, PublicMessage>;
|
||||
readonly publicMessages: Map<string, StoredPublicMessage>;
|
||||
readonly validatedDecisions: Map<string, ValidatedDecision>;
|
||||
readonly pendingEvidence: Map<string, PendingEvidence>;
|
||||
} {
|
||||
@@ -32,7 +32,7 @@ export function createRectificationV4MemoryStore(): RectificationV4Store & {
|
||||
const diagnostics = new Map<string, DiagnosticsSummary>();
|
||||
const featureSnapshots = new Map<string, CandidateFeatureSnapshot>();
|
||||
const agentRuns = new Map<string, AgentRun>();
|
||||
const publicMessages = new Map<string, PublicMessage>();
|
||||
const publicMessages = new Map<string, StoredPublicMessage>();
|
||||
const validatedDecisions = new Map<string, ValidatedDecision>();
|
||||
const pendingEvidence = new Map<string, PendingEvidence>();
|
||||
|
||||
@@ -69,6 +69,13 @@ export function createRectificationV4MemoryStore(): RectificationV4Store & {
|
||||
.filter((turn) => turn.caseId === caseId)
|
||||
.sort((left, right) => left.caseVersion - right.caseVersion || left.createdAt.localeCompare(right.createdAt));
|
||||
},
|
||||
async loadAnalysisMessages(userId, caseId) {
|
||||
owned(userId, caseId);
|
||||
return [...jobs.values()]
|
||||
.filter((job) => job.caseId === caseId && publicMessages.get(job.id)?.analysisTrace)
|
||||
.sort((left, right) => turns.get(left.turnId)!.caseVersion - turns.get(right.turnId)!.caseVersion)
|
||||
.map((job) => ({ sourceTurnId: job.turnId, trace: publicMessages.get(job.id)!.analysisTrace! }));
|
||||
},
|
||||
async loadLatestValidatedDecision(userId, caseId) {
|
||||
const caseValue = cases.get(caseId);
|
||||
if (!caseValue || caseValue.userId !== userId) return null;
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import type { AgentRun, CandidateFeatureSnapshot, DiagnosticsSummary, PublicMessage, ValidatedDecision } from "../rectification-agent/contracts.ts";
|
||||
import type { AgentRun, CandidateFeatureSnapshot, DiagnosticsSummary, StoredPublicMessage, ValidatedDecision } from "../rectification-agent/contracts.ts";
|
||||
import type {
|
||||
CandidateSnapshot,
|
||||
LifeEventRevision,
|
||||
PendingEvidence,
|
||||
RectificationAnalysisItem,
|
||||
RectificationV4Case,
|
||||
RectificationV4Job,
|
||||
RectificationV4Phase,
|
||||
@@ -33,7 +34,7 @@ export type CompleteRectificationV4JobInput = Readonly<{
|
||||
diagnostics: DiagnosticsSummary | null;
|
||||
featureSnapshot: CandidateFeatureSnapshot | null;
|
||||
validatedDecision: ValidatedDecision;
|
||||
publicMessage: PublicMessage;
|
||||
publicMessage: StoredPublicMessage;
|
||||
agentRun: AgentRun;
|
||||
nextQuestion: RectificationV4Question | null;
|
||||
status: RectificationV4Case["status"];
|
||||
@@ -45,6 +46,7 @@ export interface RectificationV4Store {
|
||||
loadCase(userId: string, caseId: string): Promise<RectificationV4Case | null>;
|
||||
loadEvents(userId: string, caseId: string): Promise<readonly LifeEventRevision[]>;
|
||||
loadTurns(userId: string, caseId: string): Promise<readonly RectificationV4Turn[]>;
|
||||
loadAnalysisMessages(userId: string, caseId: string): Promise<readonly RectificationAnalysisItem[]>;
|
||||
loadLatestValidatedDecision(userId: string, caseId: string): Promise<ValidatedDecision | null>;
|
||||
loadActionCase(userId: string, actionId: string): Promise<RectificationV4Case | null>;
|
||||
createCase(input: { readonly case: RectificationV4Case; readonly actionId: string }): Promise<RectificationV4Case>;
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
import type { SupabaseClient } from "@supabase/supabase-js";
|
||||
import { validatedDecisionSchema, type ValidatedDecision } from "../rectification-agent/contracts.ts";
|
||||
import { storedPublicMessageSchema, validatedDecisionSchema, type ValidatedDecision } from "../rectification-agent/contracts.ts";
|
||||
import {
|
||||
candidateSnapshotSchema,
|
||||
lifeEventRevisionSchema,
|
||||
rectificationAnalysisItemSchema,
|
||||
rectificationV4CaseSchema,
|
||||
rectificationV4JobSchema,
|
||||
rectificationV4TurnSchema,
|
||||
type CandidateSnapshot,
|
||||
type LifeEventRevision,
|
||||
type RectificationAnalysisItem,
|
||||
type RectificationV4Case,
|
||||
type RectificationV4Job,
|
||||
type RectificationV4Turn,
|
||||
@@ -22,6 +24,24 @@ import { evidenceSetHash, rectificationFingerprint } from "./fingerprints.ts";
|
||||
|
||||
type Row = Record<string, unknown>;
|
||||
|
||||
export function projectAnalysisMessages(
|
||||
publicMessageRows: readonly Readonly<Row>[],
|
||||
jobRows: readonly Readonly<Row>[],
|
||||
): readonly RectificationAnalysisItem[] {
|
||||
const turnByJob = new Map(jobRows.map((row) => [String(row.id), row.turn_id]));
|
||||
return [...publicMessageRows]
|
||||
.sort((left, right) => timestamp(left.created_at).localeCompare(timestamp(right.created_at)))
|
||||
.flatMap((row) => {
|
||||
const message = storedPublicMessageSchema.safeParse(row.message);
|
||||
if (!message.success || !message.data.analysisTrace) return [];
|
||||
const item = rectificationAnalysisItemSchema.safeParse({
|
||||
sourceTurnId: turnByJob.get(String(row.job_id)),
|
||||
trace: message.data.analysisTrace,
|
||||
});
|
||||
return item.success ? [item.data] : [];
|
||||
});
|
||||
}
|
||||
|
||||
function timestamp(value: unknown): string {
|
||||
return value instanceof Date ? value.toISOString() : String(value);
|
||||
}
|
||||
@@ -183,6 +203,21 @@ export function createRectificationV4SupabaseStore(supabase: SupabaseClient): Re
|
||||
return ((data ?? []) as Row[]).map(turnValue);
|
||||
}
|
||||
|
||||
async function loadAnalysisMessagesByCase(userId: string, caseId: string): Promise<readonly RectificationAnalysisItem[]> {
|
||||
if (!await loadCaseById(userId, caseId)) throw new RectificationV4StoreError("not_found");
|
||||
const { data, error } = await supabase.from("birth_time_rectification_public_messages")
|
||||
.select("job_id,message,created_at").eq("case_id", caseId).eq("user_id", userId)
|
||||
.order("created_at", { ascending: true });
|
||||
if (error) throw storeError(error);
|
||||
const rows = (data ?? []) as Row[];
|
||||
if (rows.length === 0) return [];
|
||||
const jobIds = rows.map((row) => String(row.job_id));
|
||||
const { data: jobData, error: jobError } = await supabase.from("birth_time_rectification_v4_jobs")
|
||||
.select("id,turn_id").eq("case_id", caseId).eq("user_id", userId).in("id", jobIds);
|
||||
if (jobError) throw storeError(jobError);
|
||||
return projectAnalysisMessages(rows, (jobData ?? []) as Row[]);
|
||||
}
|
||||
|
||||
async function rpc(name: string, args: Row): Promise<unknown> {
|
||||
const { data, error } = await supabase.rpc(name, args);
|
||||
if (error) throw storeError(error);
|
||||
@@ -200,6 +235,7 @@ export function createRectificationV4SupabaseStore(supabase: SupabaseClient): Re
|
||||
loadCase: loadCaseById,
|
||||
loadEvents: loadEventsByCase,
|
||||
loadTurns: loadTurnsByCase,
|
||||
loadAnalysisMessages: loadAnalysisMessagesByCase,
|
||||
async loadLatestValidatedDecision(userId, caseId): Promise<ValidatedDecision | null> {
|
||||
const { data, error } = await supabase.from("birth_time_rectification_agent_runs")
|
||||
.select("validated_decision_json").eq("case_id", caseId).eq("user_id", userId)
|
||||
|
||||
Reference in New Issue
Block a user