Skip to content

Commit f41c8fa

Browse files
dmealingclaude
andcommitted
test(ai): recorder<->base contract gate + shipped-base e2e + raw/typed PG round-trip
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent bf173a8 commit f41c8fa

3 files changed

Lines changed: 257 additions & 231 deletions

File tree

server/typescript/packages/integration-tests/test/ai-trace-sti-persistence.test.ts

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,7 @@ import { Pool } from "pg";
2222

2323
import { MetaDataLoader } from "@metaobjectsdev/metadata";
2424
import { buildExpectedSchema, diff, emit } from "@metaobjectsdev/migrate-ts";
25-
import {
26-
Format,
27-
LlmCallDbRecorder,
28-
ObjectManager,
29-
recordLlmCall,
30-
} from "@metaobjectsdev/runtime-ts";
25+
import { buildLlmCallRow, ObjectManager } from "@metaobjectsdev/runtime-ts";
3126
import { kyselyDriver } from "@metaobjectsdev/runtime-ts/drivers";
3227

3328
import { startPostgres } from "../src/postgres-container.ts";
@@ -65,6 +60,9 @@ const META = JSON.stringify({
6560
{ "field.uuid": { name: "parentSpanId" } },
6661
{ "field.string": { name: "sessionId" } },
6762
{ "field.string": { name: "callType" } },
63+
// `system` (gen_ai.system) is part of the 18-field base row that
64+
// buildLlmCallRow writes; the hand-rolled TPH base must declare it.
65+
{ "field.string": { name: "system" } },
6866
{ "field.string": { name: "requestModel" } },
6967
{ "field.string": { name: "responseModel" } },
7068
{ "field.int": { name: "inputTokens" } },
@@ -76,6 +74,7 @@ const META = JSON.stringify({
7674
{ "field.string": { name: "errorDetail" } },
7775
{ "field.string": { name: "startedAt" } },
7876
{ "field.string": { name: "llmRequest", "@dbColumnType": "jsonb" } },
77+
{ "field.string": { name: "llmResponse", "@dbColumnType": "jsonb" } },
7978
{ "identity.primary": { "@fields": "spanId" } },
8079
],
8180
},
@@ -114,8 +113,6 @@ describe("ai-trace #1c — shared-table persistence (real Postgres)", () => {
114113
const result = await MetaDataLoader.fromString(META, "json");
115114
expect(result.errors).toHaveLength(0);
116115
const root = result.root;
117-
const classifyRes = root.findObject("ClassifyRes")!;
118-
const summarizeRes = root.findObject("SummarizeRes")!;
119116

120117
// ONE-TABLE assertion: the STI subtypes do NOT each get their own table —
121118
// exactly one prompt_llm_call table is synthesised for the whole hierarchy.
@@ -137,28 +134,35 @@ describe("ai-trace #1c — shared-table persistence (real Postgres)", () => {
137134
const driver = kyselyDriver({ db: kysely as never, dialect: "postgres" });
138135
const om = new ObjectManager({ metadata: root, driver, columnNamingStrategy: "literal" });
139136

140-
await recordLlmCall(
141-
{
137+
// The generic recorder writes only the base envelope + raw I/O; the typed
138+
// voResponse is supplied by the generated typed helper. Here we model that
139+
// helper's output directly: buildLlmCallRow(base) spread with the typed VO.
140+
await om.create("ClassifyCall", {
141+
...buildLlmCallRow({
142142
spanId: C_SPAN,
143143
traceId: C_TRACE,
144144
callType: "classify",
145145
startedAt: "2026-06-05T00:00:00.000Z",
146146
llmRequest: { text: "hi" },
147147
llmResponseText: '{"label":"greeting","score":1}',
148-
},
149-
{ recorder: new LlmCallDbRecorder(om, "ClassifyCall"), responseMo: classifyRes, format: Format.JSON },
150-
);
151-
await recordLlmCall(
152-
{
148+
status: "ok",
149+
errorDetail: null,
150+
}),
151+
voResponse: { label: "greeting", score: 1 },
152+
});
153+
await om.create("SummarizeCall", {
154+
...buildLlmCallRow({
153155
spanId: S_SPAN,
154156
traceId: S_TRACE,
155157
callType: "summarize",
156158
startedAt: "2026-06-05T00:00:00.000Z",
157159
llmRequest: { doc: "..." },
158160
llmResponseText: '{"summary":"short"}',
159-
},
160-
{ recorder: new LlmCallDbRecorder(om, "SummarizeCall"), responseMo: summarizeRes, format: Format.JSON },
161-
);
161+
status: "ok",
162+
errorDetail: null,
163+
}),
164+
voResponse: { summary: "short" },
165+
});
162166

163167
const c = await om.findById("ClassifyCall", C_SPAN) as Record<string, unknown>;
164168
expect(c.callType).toBe("classify");

0 commit comments

Comments
 (0)