@obsunified/collector 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +60 -0
- package/dist/auth.d.ts +2 -0
- package/dist/auth.js +9 -0
- package/dist/auth.js.map +1 -0
- package/dist/chunk-UGWDOGGH.js +319 -0
- package/dist/chunk-UGWDOGGH.js.map +1 -0
- package/dist/chunk-XKQTVIRE.js +671 -0
- package/dist/chunk-XKQTVIRE.js.map +1 -0
- package/dist/index.d.ts +1197 -0
- package/dist/index.js +17707 -0
- package/dist/index.js.map +1 -0
- package/dist/ingest-auth-HFVO1zIp.d.ts +165 -0
- package/dist/openai-FJYOQ7WO.js +384 -0
- package/dist/openai-FJYOQ7WO.js.map +1 -0
- package/package.json +73 -0
- package/src/ai-parity/ai-parity.acceptance.test.ts +449 -0
- package/src/analyses/derive.ts +642 -0
- package/src/analyses/index.ts +50 -0
- package/src/analyses/investigations.ts +277 -0
- package/src/analyses/tier0.ts +568 -0
- package/src/auth/dashboard-auth.ts +269 -0
- package/src/auth/ingest-auth.ts +147 -0
- package/src/durable-objects/tail-hub.test.ts +32 -0
- package/src/durable-objects/tail-hub.ts +158 -0
- package/src/framework/collector.ts +467 -0
- package/src/framework/env.ts +90 -0
- package/src/framework/logger.ts +81 -0
- package/src/index.ts +211 -0
- package/src/lib/action-aggregates.ts +1031 -0
- package/src/lib/action-identity.ts +106 -0
- package/src/lib/actions-store.ts +397 -0
- package/src/lib/ai-pricing.ts +80 -0
- package/src/lib/ai-store/calls.ts +148 -0
- package/src/lib/ai-store/evaluations.test.ts +47 -0
- package/src/lib/ai-store/evaluations.ts +125 -0
- package/src/lib/ai-store/helpers.ts +37 -0
- package/src/lib/ai-store/retention.ts +17 -0
- package/src/lib/ai-store/sessions.ts +277 -0
- package/src/lib/ai-store/spans.ts +109 -0
- package/src/lib/ai-store/types.ts +112 -0
- package/src/lib/ai-store.ts +76 -0
- package/src/lib/alerts-store.ts +458 -0
- package/src/lib/analyses-runner.ts +439 -0
- package/src/lib/analyses-store.test.ts +243 -0
- package/src/lib/analyses-store.ts +454 -0
- package/src/lib/ask.test.ts +302 -0
- package/src/lib/ask.ts +390 -0
- package/src/lib/blob-store-s3.ts +213 -0
- package/src/lib/blob-store.ts +366 -0
- package/src/lib/eval-cases-store.test.ts +171 -0
- package/src/lib/eval-cases-store.ts +1033 -0
- package/src/lib/evidence-references.test.ts +10 -0
- package/src/lib/evidence-references.ts +684 -0
- package/src/lib/hash.ts +26 -0
- package/src/lib/identity-index/action-lookups.ts +805 -0
- package/src/lib/identity-index/constants.ts +1 -0
- package/src/lib/identity-index/key-lookups.ts +199 -0
- package/src/lib/identity-index/mappers.ts +349 -0
- package/src/lib/identity-index/types.ts +196 -0
- package/src/lib/identity-index/user-lookup.ts +137 -0
- package/src/lib/identity-index.test.ts +624 -0
- package/src/lib/identity-index.ts +85 -0
- package/src/lib/json.ts +39 -0
- package/src/lib/llm.ts +263 -0
- package/src/lib/logs-store.ts +188 -0
- package/src/lib/metrics-store.test.ts +189 -0
- package/src/lib/metrics-store.ts +312 -0
- package/src/lib/narrate-gate.test.ts +260 -0
- package/src/lib/narrate-gate.ts +253 -0
- package/src/lib/openai.test.ts +300 -0
- package/src/lib/openai.ts +512 -0
- package/src/lib/otlp.ts +212 -0
- package/src/lib/parse-pprof.test.ts +64 -0
- package/src/lib/parse-pprof.ts +184 -0
- package/src/lib/payload-capture.ts +22 -0
- package/src/lib/projects-store.ts +207 -0
- package/src/lib/propagation-metric.test.ts +196 -0
- package/src/lib/propagation-metric.ts +175 -0
- package/src/lib/sql-db-dialect.test.ts +22 -0
- package/src/lib/sql-db-postgres.ts +229 -0
- package/src/lib/sql-db.ts +166 -0
- package/src/lib/stage6.test.ts +216 -0
- package/src/lib/store/export.ts +65 -0
- package/src/lib/store/helpers.ts +367 -0
- package/src/lib/store/ingest.ts +68 -0
- package/src/lib/store/issues.ts +158 -0
- package/src/lib/store/overview.ts +127 -0
- package/src/lib/store/retention.ts +10 -0
- package/src/lib/store/service-map.ts +384 -0
- package/src/lib/store/trace-detail.bench.ts +76 -0
- package/src/lib/store/trace-detail.test.ts +410 -0
- package/src/lib/store/trace-detail.ts +427 -0
- package/src/lib/store/trace-queries.test.ts +29 -0
- package/src/lib/store/trace-queries.ts +89 -0
- package/src/lib/store-overview.test.ts +77 -0
- package/src/lib/store.ts +96 -0
- package/src/lib/tail-publisher.ts +64 -0
- package/src/lib/test-utils/mem-sql-db.test.ts +93 -0
- package/src/lib/test-utils/mem-sql-db.ts +86 -0
- package/src/lib/ua-parser.ts +61 -0
- package/src/lib/usage-store.ts +567 -0
- package/src/migrations/001_telemetry_spans.sql +28 -0
- package/src/migrations/002_usage_events.sql +24 -0
- package/src/migrations/003_usage_analytics_columns.sql +13 -0
- package/src/migrations/004_logs.sql +19 -0
- package/src/migrations/005_ai_calls.sql +25 -0
- package/src/migrations/006_user_profiles.sql +11 -0
- package/src/migrations/007_session_replay_metadata.sql +10 -0
- package/src/migrations/008_session_replay_storage_bytes.sql +1 -0
- package/src/migrations/009_projects_and_keys.sql +22 -0
- package/src/migrations/010_signal_project_id.sql +15 -0
- package/src/migrations/011_default_project_seed.sql +3 -0
- package/src/migrations/012_alert_rules.sql +17 -0
- package/src/migrations/013_alert_evaluations.sql +13 -0
- package/src/migrations/014_alert_state.sql +9 -0
- package/src/migrations/015_metrics.sql +58 -0
- package/src/migrations/016_metrics_exp_histogram_summary.sql +5 -0
- package/src/migrations/017_span_dropped_counts.sql +11 -0
- package/src/migrations/018_log_flags.sql +8 -0
- package/src/migrations/019_ai_span_payloads.sql +30 -0
- package/src/migrations/020_ai_span_evaluations.sql +33 -0
- package/src/migrations/021_ai_span_session.sql +13 -0
- package/src/migrations/022_span_log_session_id.sql +13 -0
- package/src/migrations/023_service_map_indexes.sql +32 -0
- package/src/migrations/024_analysis_results.sql +49 -0
- package/src/migrations/025_stage6_autopinning_alerts.sql +38 -0
- package/src/migrations/026_obs_dashboard_project_seed.sql +18 -0
- package/src/migrations/027_identity_propagation.sql +50 -0
- package/src/migrations/028_profile_blobs.sql +57 -0
- package/src/migrations/029_telemetry_sdk_name.sql +20 -0
- package/src/migrations/030_analysis_claim.sql +18 -0
- package/src/migrations/031_agent_action_graph.sql +154 -0
- package/src/migrations/032_payload_capture_flags.sql +8 -0
- package/src/migrations/033_eval_cases.sql +52 -0
- package/src/migrations/034_eval_case_results.sql +23 -0
- package/src/migrations/035_metric_exemplars.sql +33 -0
- package/src/migrations/036_trace_instrumentation_gaps.sql +19 -0
- package/src/migrations/037_eval_runs.sql +37 -0
- package/src/migrations/038_tool_call_evidence.sql +11 -0
- package/src/migrations/039_drop_trace_instrumentation_gaps.sql +11 -0
- package/src/migrations/040_evidence_retrieval_refs.sql +50 -0
- package/src/migrations-postgres/001_telemetry_spans.sql +33 -0
- package/src/migrations-postgres/002_usage_events.sql +24 -0
- package/src/migrations-postgres/003_usage_analytics_columns.sql +13 -0
- package/src/migrations-postgres/004_logs.sql +19 -0
- package/src/migrations-postgres/005_ai_calls.sql +25 -0
- package/src/migrations-postgres/006_user_profiles.sql +11 -0
- package/src/migrations-postgres/007_session_replay_metadata.sql +10 -0
- package/src/migrations-postgres/008_session_replay_storage_bytes.sql +2 -0
- package/src/migrations-postgres/009_projects_and_keys.sql +25 -0
- package/src/migrations-postgres/010_signal_project_id.sql +18 -0
- package/src/migrations-postgres/011_default_project_seed.sql +5 -0
- package/src/migrations-postgres/012_alert_rules.sql +17 -0
- package/src/migrations-postgres/013_alert_evaluations.sql +13 -0
- package/src/migrations-postgres/014_alert_state.sql +9 -0
- package/src/migrations-postgres/015_metrics.sql +47 -0
- package/src/migrations-postgres/016_metrics_exp_histogram_summary.sql +1 -0
- package/src/migrations-postgres/017_span_dropped_counts.sql +4 -0
- package/src/migrations-postgres/018_log_flags.sql +2 -0
- package/src/migrations-postgres/019_ai_span_payloads.sql +18 -0
- package/src/migrations-postgres/020_ai_span_evaluations.sql +21 -0
- package/src/migrations-postgres/021_ai_span_session.sql +7 -0
- package/src/migrations-postgres/022_span_log_session_id.sql +7 -0
- package/src/migrations-postgres/023_service_map_indexes.sql +9 -0
- package/src/migrations-postgres/024_analysis_results.sql +41 -0
- package/src/migrations-postgres/025_stage6_autopinning_alerts.sql +17 -0
- package/src/migrations-postgres/026_obs_dashboard_project_seed.sql +14 -0
- package/src/migrations-postgres/027_identity_propagation.sql +27 -0
- package/src/migrations-postgres/028_profile_blobs.sql +48 -0
- package/src/migrations-postgres/029_telemetry_sdk_name.sql +5 -0
- package/src/migrations-postgres/030_analysis_claim.sql +5 -0
- package/src/migrations-postgres/031_agent_action_graph.sql +190 -0
- package/src/migrations-postgres/032_payload_capture_flags.sql +8 -0
- package/src/migrations-postgres/033_eval_cases.sql +55 -0
- package/src/migrations-postgres/034_eval_case_results.sql +26 -0
- package/src/migrations-postgres/035_metric_exemplars.sql +31 -0
- package/src/migrations-postgres/036_trace_instrumentation_gaps.sql +19 -0
- package/src/migrations-postgres/037_eval_runs.sql +40 -0
- package/src/migrations-postgres/038_tool_call_evidence.sql +11 -0
- package/src/migrations-postgres/039_drop_trace_instrumentation_gaps.sql +11 -0
- package/src/migrations-postgres/040_evidence_retrieval_refs.sql +47 -0
- package/src/migrations-postgres/README.md +40 -0
- package/src/otlp/acceptance.test.ts +373 -0
- package/src/otlp/decode/body.ts +127 -0
- package/src/otlp/decode/logs.ts +96 -0
- package/src/otlp/decode/metrics.ts +402 -0
- package/src/otlp/decode/traces.ts +92 -0
- package/src/otlp/decode/values.ts +150 -0
- package/src/otlp/decode.test.ts +800 -0
- package/src/otlp/decode.ts +11 -0
- package/src/otlp/gen/opentelemetry/proto/collector/logs/v1/logs_service_pb.ts +132 -0
- package/src/otlp/gen/opentelemetry/proto/collector/metrics/v1/metrics_service_pb.ts +158 -0
- package/src/otlp/gen/opentelemetry/proto/collector/trace/v1/trace_service_pb.ts +158 -0
- package/src/otlp/gen/opentelemetry/proto/common/v1/common_pb.ts +289 -0
- package/src/otlp/gen/opentelemetry/proto/logs/v1/logs_pb.ts +397 -0
- package/src/otlp/gen/opentelemetry/proto/metrics/v1/metrics_pb.ts +1329 -0
- package/src/otlp/gen/opentelemetry/proto/resource/v1/resource_pb.ts +86 -0
- package/src/otlp/gen/opentelemetry/proto/trace/v1/trace_pb.ts +710 -0
- package/src/otlp/response.test.ts +158 -0
- package/src/otlp/response.ts +129 -0
- package/src/plugins/_context.ts +15 -0
- package/src/plugins/action-graph-processor/enrichers.ts +45 -0
- package/src/plugins/action-graph-processor/redaction.ts +84 -0
- package/src/plugins/action-graph-processor.test.ts +1023 -0
- package/src/plugins/action-graph-processor.ts +1172 -0
- package/src/plugins/action-routes.test.ts +716 -0
- package/src/plugins/action-routes.ts +343 -0
- package/src/plugins/ai-receiver.ts +254 -0
- package/src/plugins/ai-span-payloads-processor.test.ts +90 -0
- package/src/plugins/ai-span-payloads-processor.ts +180 -0
- package/src/plugins/alerts-evaluator.test.ts +91 -0
- package/src/plugins/alerts-evaluator.ts +328 -0
- package/src/plugins/alerts-routes.ts +101 -0
- package/src/plugins/analyses-routes.ts +138 -0
- package/src/plugins/ask-routes.ts +182 -0
- package/src/plugins/bot-filter.ts +19 -0
- package/src/plugins/connected-routes/manifest.ts +531 -0
- package/src/plugins/connected-routes.test.ts +1791 -0
- package/src/plugins/connected-routes.ts +1103 -0
- package/src/plugins/dashboard-routes.ts +41 -0
- package/src/plugins/default-span-enrichment.ts +92 -0
- package/src/plugins/eval-cases-routes.test.ts +391 -0
- package/src/plugins/eval-cases-routes.ts +529 -0
- package/src/plugins/evidence-retrieval-routes.benchmark.test.ts +245 -0
- package/src/plugins/evidence-retrieval-routes.test.ts +932 -0
- package/src/plugins/evidence-retrieval-routes.ts +2179 -0
- package/src/plugins/gen-ai-normalizer.test.ts +680 -0
- package/src/plugins/gen-ai-normalizer.ts +302 -0
- package/src/plugins/identity-receiver.ts +96 -0
- package/src/plugins/issue-enrichment.ts +132 -0
- package/src/plugins/issue-insights.ts +81 -0
- package/src/plugins/logs-receiver.ts +160 -0
- package/src/plugins/metrics-receiver.ts +91 -0
- package/src/plugins/onboarding-routes.ts +54 -0
- package/src/plugins/otlp-receiver.ts +124 -0
- package/src/plugins/platform-routes.ts +165 -0
- package/src/plugins/profile-routes.ts +468 -0
- package/src/plugins/projects-routes.ts +92 -0
- package/src/plugins/query-routes.ts +220 -0
- package/src/plugins/redaction-processor.ts +132 -0
- package/src/plugins/replay-query-routes.test.ts +38 -0
- package/src/plugins/replay-query-routes.ts +199 -0
- package/src/plugins/replay-receiver.ts +81 -0
- package/src/plugins/tail-routes.test.ts +47 -0
- package/src/plugins/tail-routes.ts +42 -0
- package/src/plugins/timeline-routes.ts +244 -0
- package/src/plugins/ua-enrichment.ts +27 -0
- package/src/plugins/usage-normalization.ts +46 -0
- package/src/plugins/usage-privacy.ts +55 -0
- package/src/plugins/usage-query-routes.ts +102 -0
- package/src/plugins/usage-receiver.ts +129 -0
- package/src/plugins/users-query-routes.ts +78 -0
- package/src/plugins/utm-enrichment.ts +36 -0
|
@@ -0,0 +1,1033 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
EvidenceReference,
|
|
3
|
+
EvidenceReferenceContract as EvidenceReferenceContractType,
|
|
4
|
+
JsonValue,
|
|
5
|
+
} from "@obsunified/types";
|
|
6
|
+
import { EVIDENCE_REFERENCE_CONTRACT } from "@obsunified/types";
|
|
7
|
+
import { sourceLinkEvidenceReferences } from "./evidence-references";
|
|
8
|
+
import { randomHex } from "./hash";
|
|
9
|
+
import { parseJsonArray, parseJsonValue } from "./json";
|
|
10
|
+
import type { SqlDb } from "./sql-db";
|
|
11
|
+
|
|
12
|
+
export type EvalCaseSourceType =
|
|
13
|
+
| "agent_run"
|
|
14
|
+
| "action"
|
|
15
|
+
| "ai_call"
|
|
16
|
+
| "tool_call"
|
|
17
|
+
| "trace";
|
|
18
|
+
|
|
19
|
+
export interface EvalCaseSourceLinks {
|
|
20
|
+
sourceAgentRunId?: string | null;
|
|
21
|
+
sourceActionId?: string | null;
|
|
22
|
+
sourceAiCallId?: string | null;
|
|
23
|
+
sourceToolCallId?: string | null;
|
|
24
|
+
sourceTraceId?: string | null;
|
|
25
|
+
sourceSpanId?: string | null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface EvalCaseInput {
|
|
29
|
+
sourceEntityType: EvalCaseSourceType;
|
|
30
|
+
sourceEntityId: string;
|
|
31
|
+
name: string;
|
|
32
|
+
expectedOutcome?: string | null;
|
|
33
|
+
rubric?: JsonValue | null;
|
|
34
|
+
redactedPrompt?: JsonValue | null;
|
|
35
|
+
referencePayload?: JsonValue | null;
|
|
36
|
+
metadata?: Record<string, JsonValue>;
|
|
37
|
+
source?: EvalCaseSourceLinks;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface EvalCase extends Required<EvalCaseSourceLinks> {
|
|
41
|
+
id: string;
|
|
42
|
+
projectId: string;
|
|
43
|
+
sourceEntityType: EvalCaseSourceType;
|
|
44
|
+
sourceEntityId: string;
|
|
45
|
+
name: string;
|
|
46
|
+
expectedOutcome: string | null;
|
|
47
|
+
rubric: JsonValue | null;
|
|
48
|
+
redactedPrompt: JsonValue | null;
|
|
49
|
+
referencePayload: JsonValue | null;
|
|
50
|
+
metadata: Record<string, JsonValue>;
|
|
51
|
+
createdAt: string;
|
|
52
|
+
updatedAt: string;
|
|
53
|
+
evidenceReferences?: EvidenceReference[];
|
|
54
|
+
evidenceContract?: EvidenceReferenceContractType;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface EvalCaseListOptions {
|
|
58
|
+
projectId: string;
|
|
59
|
+
sourceEntityType?: EvalCaseSourceType;
|
|
60
|
+
sourceEntityId?: string;
|
|
61
|
+
limit?: number;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export type EvalRunStatus =
|
|
65
|
+
| "pending"
|
|
66
|
+
| "running"
|
|
67
|
+
| "completed"
|
|
68
|
+
| "failed"
|
|
69
|
+
| "canceled";
|
|
70
|
+
|
|
71
|
+
export interface EvalRunCandidate {
|
|
72
|
+
agentId: string | null;
|
|
73
|
+
agentVersion: string | null;
|
|
74
|
+
promptId: string | null;
|
|
75
|
+
promptVersion: string | null;
|
|
76
|
+
modelProvider: string | null;
|
|
77
|
+
model: string | null;
|
|
78
|
+
modelVersion: string | null;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export interface EvalRunSourceCase {
|
|
82
|
+
id: string;
|
|
83
|
+
name: string;
|
|
84
|
+
sourceEntityType: string;
|
|
85
|
+
sourceEntityId: string;
|
|
86
|
+
sourceAgentRunId: string | null;
|
|
87
|
+
sourceActionId: string | null;
|
|
88
|
+
sourceAiCallId: string | null;
|
|
89
|
+
sourceToolCallId: string | null;
|
|
90
|
+
sourceTraceId: string | null;
|
|
91
|
+
sourceSpanId: string | null;
|
|
92
|
+
evidenceReferences?: EvidenceReference[];
|
|
93
|
+
evidenceContract?: EvidenceReferenceContractType;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export interface EvalRun {
|
|
97
|
+
id: string;
|
|
98
|
+
projectId: string;
|
|
99
|
+
evalCaseId: string | null;
|
|
100
|
+
status: EvalRunStatus;
|
|
101
|
+
candidate: EvalRunCandidate;
|
|
102
|
+
startedAt: string | null;
|
|
103
|
+
endedAt: string | null;
|
|
104
|
+
totalCount: number;
|
|
105
|
+
passCount: number;
|
|
106
|
+
failCount: number;
|
|
107
|
+
averageScore: number | null;
|
|
108
|
+
metadata: Record<string, JsonValue>;
|
|
109
|
+
createdAt: string;
|
|
110
|
+
sourceEvalCase?: EvalRunSourceCase | null;
|
|
111
|
+
evidenceReferences?: EvidenceReference[];
|
|
112
|
+
evidenceContract?: EvidenceReferenceContractType;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export interface EvalRunInput {
|
|
116
|
+
id?: string;
|
|
117
|
+
evalCaseId?: string | null;
|
|
118
|
+
status?: EvalRunStatus;
|
|
119
|
+
candidate?: Partial<EvalRunCandidate>;
|
|
120
|
+
startedAt?: string | null;
|
|
121
|
+
endedAt?: string | null;
|
|
122
|
+
totalCount?: number;
|
|
123
|
+
passCount?: number;
|
|
124
|
+
failCount?: number;
|
|
125
|
+
averageScore?: number | null;
|
|
126
|
+
metadata?: Record<string, JsonValue>;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export interface EvalRunsListOptions {
|
|
130
|
+
projectId: string;
|
|
131
|
+
evalCaseId?: string;
|
|
132
|
+
status?: EvalRunStatus;
|
|
133
|
+
limit?: number;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
interface EvalRunRow {
|
|
137
|
+
id: string;
|
|
138
|
+
project_id: string;
|
|
139
|
+
eval_case_id: string | null;
|
|
140
|
+
candidate_agent_id: string | null;
|
|
141
|
+
candidate_agent_version: string | null;
|
|
142
|
+
candidate_prompt_id: string | null;
|
|
143
|
+
candidate_prompt_version: string | null;
|
|
144
|
+
candidate_model_provider: string | null;
|
|
145
|
+
candidate_model: string | null;
|
|
146
|
+
candidate_model_version: string | null;
|
|
147
|
+
status: string;
|
|
148
|
+
started_at: string | null;
|
|
149
|
+
ended_at: string | null;
|
|
150
|
+
total_count: number;
|
|
151
|
+
pass_count: number;
|
|
152
|
+
fail_count: number;
|
|
153
|
+
average_score: number | null;
|
|
154
|
+
metadata_json: unknown;
|
|
155
|
+
created_at: string;
|
|
156
|
+
case_id?: string | null;
|
|
157
|
+
case_name?: string | null;
|
|
158
|
+
case_source_entity_type?: string | null;
|
|
159
|
+
case_source_entity_id?: string | null;
|
|
160
|
+
case_source_agent_run_id?: string | null;
|
|
161
|
+
case_source_action_id?: string | null;
|
|
162
|
+
case_source_ai_call_id?: string | null;
|
|
163
|
+
case_source_tool_call_id?: string | null;
|
|
164
|
+
case_source_trace_id?: string | null;
|
|
165
|
+
case_source_span_id?: string | null;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
interface EvalCaseRow {
|
|
169
|
+
id: string;
|
|
170
|
+
project_id: string;
|
|
171
|
+
source_entity_type: string;
|
|
172
|
+
source_entity_id: string;
|
|
173
|
+
source_agent_run_id: string | null;
|
|
174
|
+
source_action_id: string | null;
|
|
175
|
+
source_ai_call_id: string | null;
|
|
176
|
+
source_tool_call_id: string | null;
|
|
177
|
+
source_trace_id: string | null;
|
|
178
|
+
source_span_id: string | null;
|
|
179
|
+
name: string;
|
|
180
|
+
expected_outcome: string | null;
|
|
181
|
+
rubric_json: unknown;
|
|
182
|
+
redacted_prompt_json: unknown;
|
|
183
|
+
reference_payload_json: unknown;
|
|
184
|
+
metadata_json: unknown;
|
|
185
|
+
created_at: string;
|
|
186
|
+
updated_at: string;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
interface ActionSourceRow {
|
|
190
|
+
id: string;
|
|
191
|
+
agent_run_id: string | null;
|
|
192
|
+
tool_call_id: string | null;
|
|
193
|
+
trace_id: string | null;
|
|
194
|
+
span_id: string | null;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
interface AiCallSourceRow {
|
|
198
|
+
call_id: string;
|
|
199
|
+
trace_id: string | null;
|
|
200
|
+
span_id: string | null;
|
|
201
|
+
request_json: unknown;
|
|
202
|
+
response_json: unknown;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
interface ToolCallSourceRow {
|
|
206
|
+
id: string;
|
|
207
|
+
action_id: string | null;
|
|
208
|
+
tool_name: string | null;
|
|
209
|
+
args_hash: string | null;
|
|
210
|
+
result_hash: string | null;
|
|
211
|
+
mcp_audit_json?: unknown;
|
|
212
|
+
mutation_before_json?: unknown;
|
|
213
|
+
mutation_after_json?: unknown;
|
|
214
|
+
mutation_diff_json?: unknown;
|
|
215
|
+
mutation_artifact_id?: string | null;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
interface SpanSourceRow {
|
|
219
|
+
trace_id: string;
|
|
220
|
+
span_id: string;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
interface PayloadRow {
|
|
224
|
+
input_json: unknown;
|
|
225
|
+
output_json: unknown;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
interface RetrievalEventRow {
|
|
229
|
+
documents_json: unknown;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
export class EvalCaseSourceNotFoundError extends Error {
|
|
233
|
+
constructor(
|
|
234
|
+
readonly projectId: string,
|
|
235
|
+
readonly sourceEntityType: EvalCaseSourceType,
|
|
236
|
+
readonly sourceEntityId: string,
|
|
237
|
+
) {
|
|
238
|
+
super(
|
|
239
|
+
`Source ${sourceEntityType}:${sourceEntityId} not found in project ${projectId}`,
|
|
240
|
+
);
|
|
241
|
+
this.name = "EvalCaseSourceNotFoundError";
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const SOURCE_TYPES: EvalCaseSourceType[] = [
|
|
246
|
+
"agent_run",
|
|
247
|
+
"action",
|
|
248
|
+
"ai_call",
|
|
249
|
+
"tool_call",
|
|
250
|
+
"trace",
|
|
251
|
+
];
|
|
252
|
+
|
|
253
|
+
const EVAL_RUN_STATUSES: EvalRunStatus[] = [
|
|
254
|
+
"pending",
|
|
255
|
+
"running",
|
|
256
|
+
"completed",
|
|
257
|
+
"failed",
|
|
258
|
+
"canceled",
|
|
259
|
+
];
|
|
260
|
+
|
|
261
|
+
export const isEvalCaseSourceType = (
|
|
262
|
+
value: unknown,
|
|
263
|
+
): value is EvalCaseSourceType =>
|
|
264
|
+
typeof value === "string" &&
|
|
265
|
+
SOURCE_TYPES.includes(value as EvalCaseSourceType);
|
|
266
|
+
|
|
267
|
+
export const isEvalRunStatus = (value: unknown): value is EvalRunStatus =>
|
|
268
|
+
typeof value === "string" &&
|
|
269
|
+
EVAL_RUN_STATUSES.includes(value as EvalRunStatus);
|
|
270
|
+
|
|
271
|
+
const parseJsonField = (value: unknown): JsonValue | null => {
|
|
272
|
+
if (value === null || value === undefined) return null;
|
|
273
|
+
if (typeof value === "string") return parseJsonValue(value);
|
|
274
|
+
return value as JsonValue;
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
const parseJsonRecordField = (value: unknown): Record<string, JsonValue> => {
|
|
278
|
+
const parsed = parseJsonField(value);
|
|
279
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed)
|
|
280
|
+
? (parsed as Record<string, JsonValue>)
|
|
281
|
+
: {};
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
const jsonString = (value: JsonValue | null | undefined): string | null =>
|
|
285
|
+
value === undefined || value === null ? null : JSON.stringify(value);
|
|
286
|
+
|
|
287
|
+
const compactLinks = (
|
|
288
|
+
links: EvalCaseSourceLinks,
|
|
289
|
+
): Record<string, JsonValue> => {
|
|
290
|
+
const out: Record<string, JsonValue> = {};
|
|
291
|
+
if (links.sourceAgentRunId) out.agentRunId = links.sourceAgentRunId;
|
|
292
|
+
if (links.sourceActionId) out.actionId = links.sourceActionId;
|
|
293
|
+
if (links.sourceAiCallId) out.aiCallId = links.sourceAiCallId;
|
|
294
|
+
if (links.sourceToolCallId) out.toolCallId = links.sourceToolCallId;
|
|
295
|
+
if (links.sourceTraceId) out.traceId = links.sourceTraceId;
|
|
296
|
+
if (links.sourceSpanId) out.spanId = links.sourceSpanId;
|
|
297
|
+
return out;
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
const clampLimit = (limit: number | undefined): number =>
|
|
301
|
+
Math.min(
|
|
302
|
+
200,
|
|
303
|
+
Math.max(1, Number.isFinite(limit ?? NaN) ? (limit ?? 50) : 50),
|
|
304
|
+
);
|
|
305
|
+
|
|
306
|
+
const nonNegativeInteger = (value: number | undefined): number => {
|
|
307
|
+
if (value === undefined) return 0;
|
|
308
|
+
if (!Number.isFinite(value) || value < 0) {
|
|
309
|
+
throw new Error("summary counters must be non-negative numbers");
|
|
310
|
+
}
|
|
311
|
+
return Math.floor(value);
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
const optionalScore = (value: number | null | undefined): number | null => {
|
|
315
|
+
if (value === undefined || value === null) return null;
|
|
316
|
+
if (!Number.isFinite(value)) throw new Error("averageScore must be a number");
|
|
317
|
+
return value;
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
const evalCaseEvidenceReferences = (evalCase: EvalCase): EvidenceReference[] =>
|
|
321
|
+
sourceLinkEvidenceReferences(
|
|
322
|
+
{
|
|
323
|
+
sourceLabel: `Eval case "${evalCase.name}"`,
|
|
324
|
+
sourceId: evalCase.id,
|
|
325
|
+
sourceKind: "eval_case",
|
|
326
|
+
sourceRoute: `#/evaluations?case=${encodeURIComponent(evalCase.id)}`,
|
|
327
|
+
sourceName: evalCase.name,
|
|
328
|
+
},
|
|
329
|
+
evalCase,
|
|
330
|
+
);
|
|
331
|
+
|
|
332
|
+
export const rowToEvalCase = (row: EvalCaseRow): EvalCase => {
|
|
333
|
+
const evalCase: EvalCase = {
|
|
334
|
+
id: row.id,
|
|
335
|
+
projectId: row.project_id,
|
|
336
|
+
sourceEntityType: row.source_entity_type as EvalCaseSourceType,
|
|
337
|
+
sourceEntityId: row.source_entity_id,
|
|
338
|
+
sourceAgentRunId: row.source_agent_run_id ?? null,
|
|
339
|
+
sourceActionId: row.source_action_id ?? null,
|
|
340
|
+
sourceAiCallId: row.source_ai_call_id ?? null,
|
|
341
|
+
sourceToolCallId: row.source_tool_call_id ?? null,
|
|
342
|
+
sourceTraceId: row.source_trace_id ?? null,
|
|
343
|
+
sourceSpanId: row.source_span_id ?? null,
|
|
344
|
+
name: row.name,
|
|
345
|
+
expectedOutcome: row.expected_outcome ?? null,
|
|
346
|
+
rubric: parseJsonField(row.rubric_json),
|
|
347
|
+
redactedPrompt: parseJsonField(row.redacted_prompt_json),
|
|
348
|
+
referencePayload: parseJsonField(row.reference_payload_json),
|
|
349
|
+
metadata: parseJsonRecordField(row.metadata_json),
|
|
350
|
+
createdAt: row.created_at,
|
|
351
|
+
updatedAt: row.updated_at,
|
|
352
|
+
};
|
|
353
|
+
evalCase.evidenceReferences = evalCaseEvidenceReferences(evalCase);
|
|
354
|
+
evalCase.evidenceContract = EVIDENCE_REFERENCE_CONTRACT;
|
|
355
|
+
return evalCase;
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
const evalRunEvidenceReferences = (run: EvalRun): EvidenceReference[] => {
|
|
359
|
+
if (!run.sourceEvalCase) return [];
|
|
360
|
+
return sourceLinkEvidenceReferences(
|
|
361
|
+
{
|
|
362
|
+
sourceLabel: `Eval run "${run.id}"`,
|
|
363
|
+
sourceId: run.id,
|
|
364
|
+
sourceKind: "eval_run",
|
|
365
|
+
sourceRoute: `#/evaluations?run=${encodeURIComponent(run.id)}`,
|
|
366
|
+
sourceName: run.sourceEvalCase.name,
|
|
367
|
+
},
|
|
368
|
+
run.sourceEvalCase,
|
|
369
|
+
);
|
|
370
|
+
};
|
|
371
|
+
|
|
372
|
+
const rowToEvalRun = (row: EvalRunRow): EvalRun => {
|
|
373
|
+
const sourceEvalCase: EvalRunSourceCase | null =
|
|
374
|
+
row.case_id && row.case_name
|
|
375
|
+
? {
|
|
376
|
+
id: row.case_id,
|
|
377
|
+
name: row.case_name,
|
|
378
|
+
sourceEntityType: row.case_source_entity_type ?? "",
|
|
379
|
+
sourceEntityId: row.case_source_entity_id ?? "",
|
|
380
|
+
sourceAgentRunId: row.case_source_agent_run_id ?? null,
|
|
381
|
+
sourceActionId: row.case_source_action_id ?? null,
|
|
382
|
+
sourceAiCallId: row.case_source_ai_call_id ?? null,
|
|
383
|
+
sourceToolCallId: row.case_source_tool_call_id ?? null,
|
|
384
|
+
sourceTraceId: row.case_source_trace_id ?? null,
|
|
385
|
+
sourceSpanId: row.case_source_span_id ?? null,
|
|
386
|
+
}
|
|
387
|
+
: null;
|
|
388
|
+
if (sourceEvalCase) {
|
|
389
|
+
sourceEvalCase.evidenceReferences = sourceLinkEvidenceReferences(
|
|
390
|
+
{
|
|
391
|
+
sourceLabel: `Eval case "${sourceEvalCase.name}"`,
|
|
392
|
+
sourceId: sourceEvalCase.id,
|
|
393
|
+
sourceKind: "eval_case",
|
|
394
|
+
sourceRoute: `#/evaluations?case=${encodeURIComponent(sourceEvalCase.id)}`,
|
|
395
|
+
sourceName: sourceEvalCase.name,
|
|
396
|
+
},
|
|
397
|
+
sourceEvalCase,
|
|
398
|
+
);
|
|
399
|
+
sourceEvalCase.evidenceContract = EVIDENCE_REFERENCE_CONTRACT;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
const run: EvalRun = {
|
|
403
|
+
id: row.id,
|
|
404
|
+
projectId: row.project_id,
|
|
405
|
+
evalCaseId: row.eval_case_id ?? null,
|
|
406
|
+
status: isEvalRunStatus(row.status) ? row.status : "pending",
|
|
407
|
+
candidate: {
|
|
408
|
+
agentId: row.candidate_agent_id ?? null,
|
|
409
|
+
agentVersion: row.candidate_agent_version ?? null,
|
|
410
|
+
promptId: row.candidate_prompt_id ?? null,
|
|
411
|
+
promptVersion: row.candidate_prompt_version ?? null,
|
|
412
|
+
modelProvider: row.candidate_model_provider ?? null,
|
|
413
|
+
model: row.candidate_model ?? null,
|
|
414
|
+
modelVersion: row.candidate_model_version ?? null,
|
|
415
|
+
},
|
|
416
|
+
startedAt: row.started_at ?? null,
|
|
417
|
+
endedAt: row.ended_at ?? null,
|
|
418
|
+
totalCount: row.total_count ?? 0,
|
|
419
|
+
passCount: row.pass_count ?? 0,
|
|
420
|
+
failCount: row.fail_count ?? 0,
|
|
421
|
+
averageScore: row.average_score ?? null,
|
|
422
|
+
metadata: parseJsonRecordField(row.metadata_json),
|
|
423
|
+
createdAt: row.created_at,
|
|
424
|
+
sourceEvalCase,
|
|
425
|
+
};
|
|
426
|
+
run.evidenceReferences = evalRunEvidenceReferences(run);
|
|
427
|
+
run.evidenceContract = EVIDENCE_REFERENCE_CONTRACT;
|
|
428
|
+
return run;
|
|
429
|
+
};
|
|
430
|
+
|
|
431
|
+
export class EvalCasesStore {
|
|
432
|
+
constructor(private readonly db: SqlDb) {}
|
|
433
|
+
|
|
434
|
+
async createCase(projectId: string, input: EvalCaseInput): Promise<EvalCase> {
|
|
435
|
+
if (!projectId)
|
|
436
|
+
throw new Error("EvalCasesStore.createCase: projectId is required");
|
|
437
|
+
if (!isEvalCaseSourceType(input.sourceEntityType)) {
|
|
438
|
+
throw new Error("sourceEntityType is invalid");
|
|
439
|
+
}
|
|
440
|
+
const sourceEntityId = input.sourceEntityId.trim();
|
|
441
|
+
if (!sourceEntityId) throw new Error("sourceEntityId is required");
|
|
442
|
+
const name = input.name.trim();
|
|
443
|
+
if (!name) throw new Error("name is required");
|
|
444
|
+
|
|
445
|
+
const hydrated = await this.hydrateSource(
|
|
446
|
+
projectId,
|
|
447
|
+
input.sourceEntityType,
|
|
448
|
+
sourceEntityId,
|
|
449
|
+
input.source ?? {},
|
|
450
|
+
);
|
|
451
|
+
const payload = await this.findPayload(projectId, hydrated.links);
|
|
452
|
+
const autoMetadata = await this.buildMetadata(projectId, hydrated.links);
|
|
453
|
+
const metadata = {
|
|
454
|
+
...(input.metadata ?? {}),
|
|
455
|
+
...autoMetadata,
|
|
456
|
+
sourceLinks: compactLinks(hydrated.links),
|
|
457
|
+
};
|
|
458
|
+
const now = new Date().toISOString();
|
|
459
|
+
const evalCase: EvalCase = {
|
|
460
|
+
id: randomHex(16),
|
|
461
|
+
projectId,
|
|
462
|
+
sourceEntityType: input.sourceEntityType,
|
|
463
|
+
sourceEntityId,
|
|
464
|
+
sourceAgentRunId: hydrated.links.sourceAgentRunId ?? null,
|
|
465
|
+
sourceActionId: hydrated.links.sourceActionId ?? null,
|
|
466
|
+
sourceAiCallId: hydrated.links.sourceAiCallId ?? null,
|
|
467
|
+
sourceToolCallId: hydrated.links.sourceToolCallId ?? null,
|
|
468
|
+
sourceTraceId: hydrated.links.sourceTraceId ?? null,
|
|
469
|
+
sourceSpanId: hydrated.links.sourceSpanId ?? null,
|
|
470
|
+
name,
|
|
471
|
+
expectedOutcome: input.expectedOutcome ?? null,
|
|
472
|
+
rubric: input.rubric ?? null,
|
|
473
|
+
redactedPrompt: input.redactedPrompt ?? payload?.input ?? null,
|
|
474
|
+
referencePayload: input.referencePayload ?? payload?.output ?? null,
|
|
475
|
+
metadata,
|
|
476
|
+
createdAt: now,
|
|
477
|
+
updatedAt: now,
|
|
478
|
+
};
|
|
479
|
+
evalCase.evidenceReferences = evalCaseEvidenceReferences(evalCase);
|
|
480
|
+
|
|
481
|
+
await this.db
|
|
482
|
+
.prepare(
|
|
483
|
+
`INSERT INTO eval_cases (
|
|
484
|
+
id, project_id, source_entity_type, source_entity_id,
|
|
485
|
+
source_agent_run_id, source_action_id, source_ai_call_id,
|
|
486
|
+
source_tool_call_id, source_trace_id, source_span_id,
|
|
487
|
+
name, expected_outcome, rubric_json, redacted_prompt_json,
|
|
488
|
+
reference_payload_json, metadata_json, created_at, updated_at
|
|
489
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
490
|
+
)
|
|
491
|
+
.bind(
|
|
492
|
+
evalCase.id,
|
|
493
|
+
evalCase.projectId,
|
|
494
|
+
evalCase.sourceEntityType,
|
|
495
|
+
evalCase.sourceEntityId,
|
|
496
|
+
evalCase.sourceAgentRunId,
|
|
497
|
+
evalCase.sourceActionId,
|
|
498
|
+
evalCase.sourceAiCallId,
|
|
499
|
+
evalCase.sourceToolCallId,
|
|
500
|
+
evalCase.sourceTraceId,
|
|
501
|
+
evalCase.sourceSpanId,
|
|
502
|
+
evalCase.name,
|
|
503
|
+
evalCase.expectedOutcome,
|
|
504
|
+
jsonString(evalCase.rubric),
|
|
505
|
+
jsonString(evalCase.redactedPrompt),
|
|
506
|
+
jsonString(evalCase.referencePayload),
|
|
507
|
+
JSON.stringify(evalCase.metadata),
|
|
508
|
+
evalCase.createdAt,
|
|
509
|
+
evalCase.updatedAt,
|
|
510
|
+
)
|
|
511
|
+
.run();
|
|
512
|
+
|
|
513
|
+
return evalCase;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
async getCase(projectId: string, id: string): Promise<EvalCase | null> {
|
|
517
|
+
const row = await this.db
|
|
518
|
+
.prepare(
|
|
519
|
+
`SELECT * FROM eval_cases WHERE project_id = ? AND id = ? LIMIT 1`,
|
|
520
|
+
)
|
|
521
|
+
.bind(projectId, id)
|
|
522
|
+
.first<EvalCaseRow>();
|
|
523
|
+
return row ? rowToEvalCase(row) : null;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
async listCases(options: EvalCaseListOptions): Promise<EvalCase[]> {
|
|
527
|
+
if (!options.projectId)
|
|
528
|
+
throw new Error("EvalCasesStore.listCases: projectId is required");
|
|
529
|
+
const limit = clampLimit(options.limit);
|
|
530
|
+
let sql = `SELECT * FROM eval_cases WHERE project_id = ?`;
|
|
531
|
+
const params: unknown[] = [options.projectId];
|
|
532
|
+
if (options.sourceEntityType) {
|
|
533
|
+
sql += ` AND source_entity_type = ?`;
|
|
534
|
+
params.push(options.sourceEntityType);
|
|
535
|
+
}
|
|
536
|
+
if (options.sourceEntityId) {
|
|
537
|
+
sql += ` AND source_entity_id = ?`;
|
|
538
|
+
params.push(options.sourceEntityId);
|
|
539
|
+
}
|
|
540
|
+
sql += ` ORDER BY created_at DESC LIMIT ?`;
|
|
541
|
+
params.push(limit);
|
|
542
|
+
|
|
543
|
+
const rows = await this.db
|
|
544
|
+
.prepare(sql)
|
|
545
|
+
.bind(...params)
|
|
546
|
+
.all<EvalCaseRow>();
|
|
547
|
+
return rows.results.map(rowToEvalCase);
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
async createRun(projectId: string, input: EvalRunInput): Promise<EvalRun> {
|
|
551
|
+
if (!projectId)
|
|
552
|
+
throw new Error("EvalCasesStore.createRun: projectId is required");
|
|
553
|
+
const id = input.id?.trim() || `run_${randomHex(8)}`;
|
|
554
|
+
const evalCaseId = input.evalCaseId?.trim() || null;
|
|
555
|
+
const status = input.status ?? "running";
|
|
556
|
+
if (!isEvalRunStatus(status)) throw new Error("status is invalid");
|
|
557
|
+
if (evalCaseId) {
|
|
558
|
+
const evalCase = await this.getCase(projectId, evalCaseId);
|
|
559
|
+
if (!evalCase) throw new Error("evalCaseId does not reference a case");
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
const candidate = input.candidate ?? {};
|
|
563
|
+
const totalCount = nonNegativeInteger(input.totalCount);
|
|
564
|
+
const passCount = nonNegativeInteger(input.passCount);
|
|
565
|
+
const failCount = nonNegativeInteger(input.failCount);
|
|
566
|
+
const averageScore = optionalScore(input.averageScore);
|
|
567
|
+
const now = new Date().toISOString();
|
|
568
|
+
const startedAt =
|
|
569
|
+
input.startedAt === undefined
|
|
570
|
+
? status === "pending"
|
|
571
|
+
? null
|
|
572
|
+
: now
|
|
573
|
+
: input.startedAt;
|
|
574
|
+
const endedAt = input.endedAt ?? null;
|
|
575
|
+
|
|
576
|
+
await this.db
|
|
577
|
+
.prepare(
|
|
578
|
+
`INSERT INTO eval_runs (
|
|
579
|
+
id, project_id, eval_case_id,
|
|
580
|
+
candidate_agent_id, candidate_agent_version,
|
|
581
|
+
candidate_prompt_id, candidate_prompt_version,
|
|
582
|
+
candidate_model_provider, candidate_model, candidate_model_version,
|
|
583
|
+
status, started_at, ended_at,
|
|
584
|
+
total_count, pass_count, fail_count, average_score,
|
|
585
|
+
metadata_json, created_at
|
|
586
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
587
|
+
)
|
|
588
|
+
.bind(
|
|
589
|
+
id,
|
|
590
|
+
projectId,
|
|
591
|
+
evalCaseId,
|
|
592
|
+
candidate.agentId ?? null,
|
|
593
|
+
candidate.agentVersion ?? null,
|
|
594
|
+
candidate.promptId ?? null,
|
|
595
|
+
candidate.promptVersion ?? null,
|
|
596
|
+
candidate.modelProvider ?? null,
|
|
597
|
+
candidate.model ?? null,
|
|
598
|
+
candidate.modelVersion ?? null,
|
|
599
|
+
status,
|
|
600
|
+
startedAt,
|
|
601
|
+
endedAt,
|
|
602
|
+
totalCount,
|
|
603
|
+
passCount,
|
|
604
|
+
failCount,
|
|
605
|
+
averageScore,
|
|
606
|
+
JSON.stringify(input.metadata ?? {}),
|
|
607
|
+
now,
|
|
608
|
+
)
|
|
609
|
+
.run();
|
|
610
|
+
|
|
611
|
+
const run = await this.getRun(projectId, id);
|
|
612
|
+
if (!run) throw new Error("EvalCasesStore.createRun: insert failed");
|
|
613
|
+
return run;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
async getRun(projectId: string, id: string): Promise<EvalRun | null> {
|
|
617
|
+
const row = await this.db
|
|
618
|
+
.prepare(
|
|
619
|
+
`${this.evalRunSelectSql()}
|
|
620
|
+
WHERE r.project_id = ? AND r.id = ?
|
|
621
|
+
LIMIT 1`,
|
|
622
|
+
)
|
|
623
|
+
.bind(projectId, id)
|
|
624
|
+
.first<EvalRunRow>();
|
|
625
|
+
return row ? rowToEvalRun(row) : null;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
async listRuns(options: EvalRunsListOptions): Promise<EvalRun[]> {
|
|
629
|
+
if (!options.projectId)
|
|
630
|
+
throw new Error("EvalCasesStore.listRuns: projectId is required");
|
|
631
|
+
if (options.status && !isEvalRunStatus(options.status)) {
|
|
632
|
+
throw new Error("status is invalid");
|
|
633
|
+
}
|
|
634
|
+
const limit = clampLimit(options.limit);
|
|
635
|
+
let sql = `${this.evalRunSelectSql()} WHERE r.project_id = ?`;
|
|
636
|
+
const params: unknown[] = [options.projectId];
|
|
637
|
+
if (options.evalCaseId) {
|
|
638
|
+
sql += ` AND r.eval_case_id = ?`;
|
|
639
|
+
params.push(options.evalCaseId);
|
|
640
|
+
}
|
|
641
|
+
if (options.status) {
|
|
642
|
+
sql += ` AND r.status = ?`;
|
|
643
|
+
params.push(options.status);
|
|
644
|
+
}
|
|
645
|
+
sql += ` ORDER BY r.created_at DESC LIMIT ?`;
|
|
646
|
+
params.push(limit);
|
|
647
|
+
|
|
648
|
+
const rows = await this.db
|
|
649
|
+
.prepare(sql)
|
|
650
|
+
.bind(...params)
|
|
651
|
+
.all<EvalRunRow>();
|
|
652
|
+
return rows.results.map(rowToEvalRun);
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
async refreshRunSummary(projectId: string, id: string): Promise<void> {
|
|
656
|
+
const found = await this.db
|
|
657
|
+
.prepare(
|
|
658
|
+
`SELECT id FROM eval_runs WHERE project_id = ? AND id = ? LIMIT 1`,
|
|
659
|
+
)
|
|
660
|
+
.bind(projectId, id)
|
|
661
|
+
.first<{ id: string }>();
|
|
662
|
+
if (!found) return;
|
|
663
|
+
|
|
664
|
+
await this.db
|
|
665
|
+
.prepare(
|
|
666
|
+
`UPDATE eval_runs
|
|
667
|
+
SET total_count = (
|
|
668
|
+
SELECT COUNT(*) FROM eval_case_results
|
|
669
|
+
WHERE project_id = ? AND run_id = ?
|
|
670
|
+
),
|
|
671
|
+
pass_count = (
|
|
672
|
+
SELECT COUNT(*) FROM eval_case_results
|
|
673
|
+
WHERE project_id = ? AND run_id = ? AND passed = 1
|
|
674
|
+
),
|
|
675
|
+
fail_count = (
|
|
676
|
+
SELECT COUNT(*) FROM eval_case_results
|
|
677
|
+
WHERE project_id = ? AND run_id = ? AND passed = 0
|
|
678
|
+
),
|
|
679
|
+
average_score = (
|
|
680
|
+
SELECT AVG(score) FROM eval_case_results
|
|
681
|
+
WHERE project_id = ? AND run_id = ? AND score IS NOT NULL
|
|
682
|
+
)
|
|
683
|
+
WHERE project_id = ? AND id = ?`,
|
|
684
|
+
)
|
|
685
|
+
.bind(
|
|
686
|
+
projectId,
|
|
687
|
+
id,
|
|
688
|
+
projectId,
|
|
689
|
+
id,
|
|
690
|
+
projectId,
|
|
691
|
+
id,
|
|
692
|
+
projectId,
|
|
693
|
+
id,
|
|
694
|
+
projectId,
|
|
695
|
+
id,
|
|
696
|
+
)
|
|
697
|
+
.run();
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
private evalRunSelectSql(): string {
|
|
701
|
+
return `SELECT
|
|
702
|
+
r.*,
|
|
703
|
+
c.id AS case_id,
|
|
704
|
+
c.name AS case_name,
|
|
705
|
+
c.source_entity_type AS case_source_entity_type,
|
|
706
|
+
c.source_entity_id AS case_source_entity_id,
|
|
707
|
+
c.source_agent_run_id AS case_source_agent_run_id,
|
|
708
|
+
c.source_action_id AS case_source_action_id,
|
|
709
|
+
c.source_ai_call_id AS case_source_ai_call_id,
|
|
710
|
+
c.source_tool_call_id AS case_source_tool_call_id,
|
|
711
|
+
c.source_trace_id AS case_source_trace_id,
|
|
712
|
+
c.source_span_id AS case_source_span_id
|
|
713
|
+
FROM eval_runs r
|
|
714
|
+
LEFT JOIN eval_cases c
|
|
715
|
+
ON c.project_id = r.project_id AND c.id = r.eval_case_id`;
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
private async hydrateSource(
|
|
719
|
+
projectId: string,
|
|
720
|
+
sourceEntityType: EvalCaseSourceType,
|
|
721
|
+
sourceEntityId: string,
|
|
722
|
+
explicit: EvalCaseSourceLinks,
|
|
723
|
+
): Promise<{ links: EvalCaseSourceLinks }> {
|
|
724
|
+
const links: EvalCaseSourceLinks = { ...explicit };
|
|
725
|
+
|
|
726
|
+
switch (sourceEntityType) {
|
|
727
|
+
case "action": {
|
|
728
|
+
const action = await this.getActionSource(projectId, sourceEntityId);
|
|
729
|
+
if (!action)
|
|
730
|
+
throw new EvalCaseSourceNotFoundError(
|
|
731
|
+
projectId,
|
|
732
|
+
sourceEntityType,
|
|
733
|
+
sourceEntityId,
|
|
734
|
+
);
|
|
735
|
+
links.sourceActionId = action.id;
|
|
736
|
+
links.sourceAgentRunId ??= action.agent_run_id;
|
|
737
|
+
links.sourceToolCallId ??= action.tool_call_id;
|
|
738
|
+
links.sourceTraceId ??= action.trace_id;
|
|
739
|
+
links.sourceSpanId ??= action.span_id;
|
|
740
|
+
break;
|
|
741
|
+
}
|
|
742
|
+
case "agent_run": {
|
|
743
|
+
const found = await this.db
|
|
744
|
+
.prepare(
|
|
745
|
+
`SELECT id FROM agent_runs WHERE project_id = ? AND id = ? LIMIT 1`,
|
|
746
|
+
)
|
|
747
|
+
.bind(projectId, sourceEntityId)
|
|
748
|
+
.first<{ id: string }>();
|
|
749
|
+
if (!found)
|
|
750
|
+
throw new EvalCaseSourceNotFoundError(
|
|
751
|
+
projectId,
|
|
752
|
+
sourceEntityType,
|
|
753
|
+
sourceEntityId,
|
|
754
|
+
);
|
|
755
|
+
links.sourceAgentRunId = sourceEntityId;
|
|
756
|
+
const action = await this.db
|
|
757
|
+
.prepare(
|
|
758
|
+
`SELECT id, agent_run_id, tool_call_id, trace_id, span_id
|
|
759
|
+
FROM actions
|
|
760
|
+
WHERE project_id = ? AND (id = ? OR agent_run_id = ?)
|
|
761
|
+
ORDER BY started_at ASC LIMIT 1`,
|
|
762
|
+
)
|
|
763
|
+
.bind(projectId, sourceEntityId, sourceEntityId)
|
|
764
|
+
.first<ActionSourceRow>();
|
|
765
|
+
if (action) {
|
|
766
|
+
links.sourceActionId ??= action.id;
|
|
767
|
+
links.sourceTraceId ??= action.trace_id;
|
|
768
|
+
links.sourceSpanId ??= action.span_id;
|
|
769
|
+
}
|
|
770
|
+
break;
|
|
771
|
+
}
|
|
772
|
+
case "ai_call": {
|
|
773
|
+
const call = explicit.sourceTraceId
|
|
774
|
+
? await this.db
|
|
775
|
+
.prepare(
|
|
776
|
+
`SELECT call_id, trace_id, span_id, request_json, response_json
|
|
777
|
+
FROM ai_calls
|
|
778
|
+
WHERE project_id = ?
|
|
779
|
+
AND trace_id = ?
|
|
780
|
+
AND (call_id = ? OR span_id = ?)
|
|
781
|
+
LIMIT 1`,
|
|
782
|
+
)
|
|
783
|
+
.bind(
|
|
784
|
+
projectId,
|
|
785
|
+
explicit.sourceTraceId,
|
|
786
|
+
sourceEntityId,
|
|
787
|
+
sourceEntityId,
|
|
788
|
+
)
|
|
789
|
+
.first<AiCallSourceRow>()
|
|
790
|
+
: await this.db
|
|
791
|
+
.prepare(
|
|
792
|
+
`SELECT call_id, trace_id, span_id, request_json, response_json
|
|
793
|
+
FROM ai_calls
|
|
794
|
+
WHERE project_id = ? AND (call_id = ? OR span_id = ?)
|
|
795
|
+
LIMIT 1`,
|
|
796
|
+
)
|
|
797
|
+
.bind(projectId, sourceEntityId, sourceEntityId)
|
|
798
|
+
.first<AiCallSourceRow>();
|
|
799
|
+
if (!call)
|
|
800
|
+
throw new EvalCaseSourceNotFoundError(
|
|
801
|
+
projectId,
|
|
802
|
+
sourceEntityType,
|
|
803
|
+
sourceEntityId,
|
|
804
|
+
);
|
|
805
|
+
links.sourceAiCallId = call.call_id;
|
|
806
|
+
links.sourceTraceId ??= call.trace_id;
|
|
807
|
+
links.sourceSpanId ??= call.span_id;
|
|
808
|
+
break;
|
|
809
|
+
}
|
|
810
|
+
case "tool_call": {
|
|
811
|
+
const tool = await this.getToolCallSource(projectId, sourceEntityId);
|
|
812
|
+
if (!tool)
|
|
813
|
+
throw new EvalCaseSourceNotFoundError(
|
|
814
|
+
projectId,
|
|
815
|
+
sourceEntityType,
|
|
816
|
+
sourceEntityId,
|
|
817
|
+
);
|
|
818
|
+
links.sourceToolCallId = tool.id;
|
|
819
|
+
links.sourceActionId ??= tool.action_id;
|
|
820
|
+
if (tool.action_id) {
|
|
821
|
+
const action = await this.getActionSource(projectId, tool.action_id);
|
|
822
|
+
links.sourceAgentRunId ??= action?.agent_run_id;
|
|
823
|
+
links.sourceTraceId ??= action?.trace_id;
|
|
824
|
+
links.sourceSpanId ??= action?.span_id;
|
|
825
|
+
}
|
|
826
|
+
break;
|
|
827
|
+
}
|
|
828
|
+
case "trace": {
|
|
829
|
+
const span = await this.db
|
|
830
|
+
.prepare(
|
|
831
|
+
`SELECT trace_id, span_id
|
|
832
|
+
FROM telemetry_spans
|
|
833
|
+
WHERE project_id = ? AND trace_id = ?
|
|
834
|
+
ORDER BY start_time ASC LIMIT 1`,
|
|
835
|
+
)
|
|
836
|
+
.bind(projectId, sourceEntityId)
|
|
837
|
+
.first<SpanSourceRow>();
|
|
838
|
+
if (!span)
|
|
839
|
+
throw new EvalCaseSourceNotFoundError(
|
|
840
|
+
projectId,
|
|
841
|
+
sourceEntityType,
|
|
842
|
+
sourceEntityId,
|
|
843
|
+
);
|
|
844
|
+
links.sourceTraceId = span.trace_id;
|
|
845
|
+
links.sourceSpanId ??= span.span_id;
|
|
846
|
+
break;
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
return { links };
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
private async getActionSource(
|
|
854
|
+
projectId: string,
|
|
855
|
+
actionId: string,
|
|
856
|
+
): Promise<ActionSourceRow | null> {
|
|
857
|
+
return this.db
|
|
858
|
+
.prepare(
|
|
859
|
+
`SELECT id, agent_run_id, tool_call_id, trace_id, span_id
|
|
860
|
+
FROM actions
|
|
861
|
+
WHERE project_id = ? AND id = ? LIMIT 1`,
|
|
862
|
+
)
|
|
863
|
+
.bind(projectId, actionId)
|
|
864
|
+
.first<ActionSourceRow>();
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
private async getToolCallSource(
|
|
868
|
+
projectId: string,
|
|
869
|
+
toolCallId: string,
|
|
870
|
+
): Promise<ToolCallSourceRow | null> {
|
|
871
|
+
return this.db
|
|
872
|
+
.prepare(
|
|
873
|
+
`SELECT id, action_id, tool_name, args_hash, result_hash,
|
|
874
|
+
mcp_audit_json, mutation_before_json, mutation_after_json,
|
|
875
|
+
mutation_diff_json, mutation_artifact_id
|
|
876
|
+
FROM tool_calls
|
|
877
|
+
WHERE project_id = ? AND id = ? LIMIT 1`,
|
|
878
|
+
)
|
|
879
|
+
.bind(projectId, toolCallId)
|
|
880
|
+
.first<ToolCallSourceRow>();
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
private async findPayload(
|
|
884
|
+
projectId: string,
|
|
885
|
+
links: EvalCaseSourceLinks,
|
|
886
|
+
): Promise<{ input: JsonValue | null; output: JsonValue | null } | null> {
|
|
887
|
+
let row: PayloadRow | null = null;
|
|
888
|
+
if (links.sourceTraceId && links.sourceSpanId) {
|
|
889
|
+
row = await this.db
|
|
890
|
+
.prepare(
|
|
891
|
+
`SELECT input_json, output_json
|
|
892
|
+
FROM ai_span_payloads
|
|
893
|
+
WHERE project_id = ? AND trace_id = ? AND span_id = ?
|
|
894
|
+
LIMIT 1`,
|
|
895
|
+
)
|
|
896
|
+
.bind(projectId, links.sourceTraceId, links.sourceSpanId)
|
|
897
|
+
.first<PayloadRow>();
|
|
898
|
+
}
|
|
899
|
+
if (!row && links.sourceActionId) {
|
|
900
|
+
row = await this.db
|
|
901
|
+
.prepare(
|
|
902
|
+
`SELECT input_json, output_json
|
|
903
|
+
FROM ai_span_payloads
|
|
904
|
+
WHERE project_id = ? AND action_id = ?
|
|
905
|
+
ORDER BY received_at DESC LIMIT 1`,
|
|
906
|
+
)
|
|
907
|
+
.bind(projectId, links.sourceActionId)
|
|
908
|
+
.first<PayloadRow>();
|
|
909
|
+
}
|
|
910
|
+
if (!row && links.sourceTraceId) {
|
|
911
|
+
row = await this.db
|
|
912
|
+
.prepare(
|
|
913
|
+
`SELECT input_json, output_json
|
|
914
|
+
FROM ai_span_payloads
|
|
915
|
+
WHERE project_id = ? AND trace_id = ?
|
|
916
|
+
ORDER BY received_at DESC LIMIT 1`,
|
|
917
|
+
)
|
|
918
|
+
.bind(projectId, links.sourceTraceId)
|
|
919
|
+
.first<PayloadRow>();
|
|
920
|
+
}
|
|
921
|
+
if (!row) return null;
|
|
922
|
+
return {
|
|
923
|
+
input: parseJsonField(row.input_json),
|
|
924
|
+
output: parseJsonField(row.output_json),
|
|
925
|
+
};
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
private async buildMetadata(
|
|
929
|
+
projectId: string,
|
|
930
|
+
links: EvalCaseSourceLinks,
|
|
931
|
+
): Promise<Record<string, JsonValue>> {
|
|
932
|
+
const linkedSpans: JsonValue[] = [];
|
|
933
|
+
if (links.sourceTraceId && links.sourceSpanId) {
|
|
934
|
+
linkedSpans.push({
|
|
935
|
+
traceId: links.sourceTraceId,
|
|
936
|
+
spanId: links.sourceSpanId,
|
|
937
|
+
});
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
const toolHashes: JsonValue[] = [];
|
|
941
|
+
if (links.sourceToolCallId) {
|
|
942
|
+
const tool = await this.getToolCallSource(
|
|
943
|
+
projectId,
|
|
944
|
+
links.sourceToolCallId,
|
|
945
|
+
);
|
|
946
|
+
if (tool) toolHashes.push(toolHashMetadata(tool));
|
|
947
|
+
} else if (links.sourceActionId) {
|
|
948
|
+
const rows = await this.db
|
|
949
|
+
.prepare(
|
|
950
|
+
`SELECT id, action_id, tool_name, args_hash, result_hash,
|
|
951
|
+
mcp_audit_json, mutation_before_json, mutation_after_json,
|
|
952
|
+
mutation_diff_json, mutation_artifact_id
|
|
953
|
+
FROM tool_calls
|
|
954
|
+
WHERE project_id = ? AND action_id = ?
|
|
955
|
+
LIMIT 25`,
|
|
956
|
+
)
|
|
957
|
+
.bind(projectId, links.sourceActionId)
|
|
958
|
+
.all<ToolCallSourceRow>();
|
|
959
|
+
for (const row of rows.results) {
|
|
960
|
+
toolHashes.push(toolHashMetadata(row));
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
const documentRefs: JsonValue[] = [];
|
|
965
|
+
if (links.sourceActionId) {
|
|
966
|
+
const rows = await this.db
|
|
967
|
+
.prepare(
|
|
968
|
+
`SELECT documents_json
|
|
969
|
+
FROM retrieval_events
|
|
970
|
+
WHERE project_id = ? AND action_id = ?
|
|
971
|
+
LIMIT 25`,
|
|
972
|
+
)
|
|
973
|
+
.bind(projectId, links.sourceActionId)
|
|
974
|
+
.all<RetrievalEventRow>();
|
|
975
|
+
for (const row of rows.results) {
|
|
976
|
+
for (const doc of parseJsonArray(
|
|
977
|
+
typeof row.documents_json === "string"
|
|
978
|
+
? row.documents_json
|
|
979
|
+
: JSON.stringify(row.documents_json ?? []),
|
|
980
|
+
)) {
|
|
981
|
+
documentRefs.push(doc as JsonValue);
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
const metadata: Record<string, JsonValue> = {};
|
|
987
|
+
if (linkedSpans.length > 0) metadata.linkedSpans = linkedSpans;
|
|
988
|
+
if (toolHashes.length > 0) metadata.toolHashes = toolHashes;
|
|
989
|
+
if (documentRefs.length > 0) metadata.documentRefs = documentRefs;
|
|
990
|
+
return metadata;
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
const toolHashMetadata = (tool: ToolCallSourceRow): JsonValue => {
|
|
995
|
+
const metadata: Record<string, JsonValue> = {
|
|
996
|
+
toolCallId: tool.id,
|
|
997
|
+
toolName: tool.tool_name,
|
|
998
|
+
argsHash: tool.args_hash,
|
|
999
|
+
resultHash: tool.result_hash,
|
|
1000
|
+
};
|
|
1001
|
+
|
|
1002
|
+
const mcpAudit = parseEvidenceJson(tool.mcp_audit_json);
|
|
1003
|
+
if (mcpAudit !== null) metadata.mcpAudit = mcpAudit;
|
|
1004
|
+
|
|
1005
|
+
const mutationEvidence: Record<string, JsonValue> = {};
|
|
1006
|
+
const before = parseEvidenceJson(tool.mutation_before_json);
|
|
1007
|
+
const after = parseEvidenceJson(tool.mutation_after_json);
|
|
1008
|
+
const diff = parseEvidenceJson(tool.mutation_diff_json);
|
|
1009
|
+
if (before !== null) mutationEvidence.before = before;
|
|
1010
|
+
if (after !== null) mutationEvidence.after = after;
|
|
1011
|
+
if (diff !== null) mutationEvidence.diff = diff;
|
|
1012
|
+
if (tool.mutation_artifact_id) {
|
|
1013
|
+
mutationEvidence.artifactId = tool.mutation_artifact_id;
|
|
1014
|
+
}
|
|
1015
|
+
if (Object.keys(mutationEvidence).length > 0) {
|
|
1016
|
+
metadata.mutationEvidence = mutationEvidence;
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
return metadata;
|
|
1020
|
+
};
|
|
1021
|
+
|
|
1022
|
+
const parseEvidenceJson = (value: unknown): JsonValue | null => {
|
|
1023
|
+
if (value === null || value === undefined) return null;
|
|
1024
|
+
if (typeof value === "string") return parseJsonValue(value);
|
|
1025
|
+
if (
|
|
1026
|
+
typeof value === "object" ||
|
|
1027
|
+
typeof value === "number" ||
|
|
1028
|
+
typeof value === "boolean"
|
|
1029
|
+
) {
|
|
1030
|
+
return value as JsonValue;
|
|
1031
|
+
}
|
|
1032
|
+
return null;
|
|
1033
|
+
};
|