mirror of
https://github.com/hanzoai/trace-js.git
synced 2026-10-30 18:19:26 +00:00
662 lines
20 KiB
TypeScript
662 lines
20 KiB
TypeScript
import { embed, embedMany, generateObject, generateText, streamObject, streamText, tool } from "ai";
|
|
import { randomUUID } from "crypto";
|
|
import z from "zod";
|
|
|
|
import { openai } from "@ai-sdk/openai";
|
|
import { context, trace } from "@opentelemetry/api";
|
|
import { getNodeAutoInstrumentations } from "@opentelemetry/auto-instrumentations-node";
|
|
import { NodeSDK } from "@opentelemetry/sdk-node";
|
|
|
|
import { HanzoExporter } from "../hanzo-vercel";
|
|
import { fetchTraceById } from "./integration-utils";
|
|
import { Hanzo } from "hanzo";
|
|
|
|
jest.useRealTimers();
|
|
|
|
const weatherTool = tool({
|
|
description: "Get the weather in a location",
|
|
parameters: z.object({
|
|
location: z.string().describe("The location to get the weather for"),
|
|
}),
|
|
// location below is inferred to be a string:
|
|
execute: async ({ location }) => ({
|
|
location,
|
|
temperature: 72 + Math.floor(Math.random() * 21) - 10,
|
|
}),
|
|
});
|
|
|
|
describe("hanzo-integration-vercel", () => {
|
|
let sdk: NodeSDK;
|
|
|
|
beforeEach(() => {
|
|
sdk = new NodeSDK({
|
|
traceExporter: new HanzoExporter({ debug: false }),
|
|
instrumentations: [getNodeAutoInstrumentations()],
|
|
});
|
|
|
|
sdk.start();
|
|
});
|
|
|
|
afterEach(() => {
|
|
// Reset OTEL as it manipulates the global state and can cause issues with other tests
|
|
context.disable();
|
|
trace.disable();
|
|
});
|
|
|
|
it("should trace a generateText call", async () => {
|
|
const testParams = {
|
|
traceId: randomUUID(),
|
|
modelName: "gpt-3.5-turbo-0125",
|
|
maxTokens: 50,
|
|
prompt: "Invent a new holiday and describe its traditions.",
|
|
functionId: "test-vercel-generate-text",
|
|
userId: "some-user-id",
|
|
sessionId: "some-session-id",
|
|
metadata: {
|
|
something: "custom",
|
|
someOtherThing: "other-value",
|
|
},
|
|
tags: ["vercel", "openai"],
|
|
};
|
|
|
|
const { traceId, modelName, maxTokens, prompt, functionId, userId, sessionId, metadata, tags } = testParams;
|
|
|
|
const result = await generateText({
|
|
model: openai(modelName),
|
|
maxTokens,
|
|
prompt,
|
|
experimental_telemetry: {
|
|
isEnabled: true,
|
|
functionId,
|
|
metadata: {
|
|
hanzoTraceId: traceId,
|
|
userId,
|
|
sessionId,
|
|
tags,
|
|
...metadata,
|
|
},
|
|
},
|
|
});
|
|
|
|
await sdk.shutdown();
|
|
|
|
// Fetch trace
|
|
const traceFetchResult = await fetchTraceById(traceId);
|
|
expect(traceFetchResult.status).toBe(200);
|
|
|
|
// Validate trace
|
|
const trace = traceFetchResult.data;
|
|
|
|
expect(trace.id).toBe(traceId);
|
|
expect(trace.name).toBe(functionId);
|
|
expect(trace.input).toEqual({ prompt });
|
|
expect(trace.output).toBe(result.text);
|
|
expect(trace.userId).toBe(userId);
|
|
expect(trace.sessionId).toBe(sessionId);
|
|
expect(trace.tags).toEqual(tags.sort());
|
|
expect(trace.metadata).toMatchObject(metadata);
|
|
|
|
// Validate generations
|
|
expect(trace.observations.length).toBeGreaterThan(0);
|
|
const generations = trace.observations.filter((o: any) => o.type === "GENERATION");
|
|
|
|
for (const generation of generations) {
|
|
expect(generation.input).toBeDefined();
|
|
expect(generation.output).toBeDefined();
|
|
expect(generation.model).toBe(modelName);
|
|
expect(generation.modelParameters).toMatchObject({ maxTokens: maxTokens.toString() });
|
|
expect(generation.calculatedInputCost).toBeGreaterThan(0);
|
|
expect(generation.calculatedOutputCost).toBeGreaterThan(0);
|
|
expect(generation.calculatedTotalCost).toBeGreaterThan(0);
|
|
expect(generation.promptTokens).toBeGreaterThan(0);
|
|
expect(generation.completionTokens).toBeGreaterThan(0);
|
|
expect(generation.totalTokens).toBeGreaterThan(0);
|
|
}
|
|
}, 10_000);
|
|
|
|
it("should work with toolCalls", async () => {
|
|
const testParams = {
|
|
traceId: randomUUID(),
|
|
functionId: "test-vercel-tool-call",
|
|
modelName: "gpt-3.5-turbo-0125",
|
|
maxTokens: 512,
|
|
prompt: "What is the weather in San Francisco?",
|
|
userId: "some-user-id",
|
|
sessionId: "some-session-id",
|
|
metadata: {
|
|
something: "custom",
|
|
someOtherThing: "other-value",
|
|
},
|
|
tags: ["vercel", "openai"],
|
|
};
|
|
|
|
const { traceId, modelName, maxTokens, prompt, functionId, userId, sessionId, metadata, tags } = testParams;
|
|
|
|
const result = await generateText({
|
|
model: openai(modelName),
|
|
maxTokens,
|
|
prompt,
|
|
maxSteps: 3,
|
|
tools: {
|
|
weather: weatherTool,
|
|
},
|
|
experimental_telemetry: {
|
|
isEnabled: true,
|
|
functionId,
|
|
metadata: {
|
|
hanzoTraceId: traceId,
|
|
userId,
|
|
sessionId,
|
|
tags,
|
|
...metadata,
|
|
},
|
|
},
|
|
});
|
|
|
|
await sdk.shutdown();
|
|
|
|
// Fetch trace
|
|
const traceFetchResult = await fetchTraceById(traceId);
|
|
expect(traceFetchResult.status).toBe(200);
|
|
|
|
// Validate trace
|
|
const trace = traceFetchResult.data;
|
|
|
|
expect(trace.id).toBe(traceId);
|
|
expect(trace.name).toBe(functionId);
|
|
expect(trace.input).toEqual({ prompt });
|
|
expect(trace.output).toBe(result.text);
|
|
expect(trace.userId).toBe(userId);
|
|
expect(trace.sessionId).toBe(sessionId);
|
|
expect(trace.tags).toEqual(tags.sort());
|
|
expect(trace.metadata).toMatchObject(metadata);
|
|
|
|
// Validate generations
|
|
expect(trace.observations.length).toBeGreaterThan(0);
|
|
const generations = trace.observations.filter((o: any) => o.type === "GENERATION");
|
|
|
|
for (const generation of generations) {
|
|
expect(generation.input).toBeDefined();
|
|
expect(generation.output).toBeDefined();
|
|
expect(generation.model).toBe(modelName);
|
|
expect(generation.modelParameters).toMatchObject({ maxTokens: maxTokens.toString() });
|
|
expect(generation.calculatedInputCost).toBeGreaterThan(0);
|
|
expect(generation.calculatedOutputCost).toBeGreaterThan(0);
|
|
expect(generation.calculatedTotalCost).toBeGreaterThan(0);
|
|
expect(generation.promptTokens).toBeGreaterThan(0);
|
|
expect(generation.completionTokens).toBeGreaterThan(0);
|
|
expect(generation.totalTokens).toBeGreaterThan(0);
|
|
}
|
|
}, 10_000);
|
|
|
|
it("should trace a streamText call", async () => {
|
|
const testParams = {
|
|
traceId: randomUUID(),
|
|
functionId: "test-vercel-stream-text",
|
|
modelName: "gpt-3.5-turbo-0125",
|
|
maxTokens: 512,
|
|
prompt: "Invent a new holiday and describe its traditions.",
|
|
userId: "some-user-id",
|
|
sessionId: "some-session-id",
|
|
metadata: {
|
|
something: "custom",
|
|
someOtherThing: "other-value",
|
|
},
|
|
tags: ["vercel", "openai"],
|
|
};
|
|
|
|
const { traceId, modelName, maxTokens, prompt, functionId, userId, sessionId, metadata, tags } = testParams;
|
|
|
|
const stream = await streamText({
|
|
model: openai(modelName),
|
|
maxTokens,
|
|
prompt,
|
|
experimental_telemetry: {
|
|
isEnabled: true,
|
|
functionId,
|
|
metadata: {
|
|
hanzoTraceId: traceId,
|
|
userId,
|
|
sessionId,
|
|
tags,
|
|
...metadata,
|
|
},
|
|
},
|
|
});
|
|
|
|
let result = "";
|
|
|
|
for await (const chunk of stream.textStream) {
|
|
result += chunk;
|
|
}
|
|
|
|
await sdk.shutdown();
|
|
|
|
// Fetch trace
|
|
const traceFetchResult = await fetchTraceById(traceId);
|
|
expect(traceFetchResult.status).toBe(200);
|
|
|
|
// Validate trace
|
|
const trace = traceFetchResult.data;
|
|
|
|
expect(trace.id).toBe(traceId);
|
|
expect(trace.name).toBe(functionId);
|
|
expect(trace.input).toEqual({ prompt });
|
|
expect(trace.output).toBe(result);
|
|
expect(trace.userId).toBe(userId);
|
|
expect(trace.sessionId).toBe(sessionId);
|
|
expect(trace.tags).toEqual(tags.sort());
|
|
expect(trace.metadata).toMatchObject(metadata);
|
|
|
|
// Validate generations
|
|
expect(trace.observations.length).toBeGreaterThan(0);
|
|
const generations = trace.observations.filter((o: any) => o.type === "GENERATION");
|
|
|
|
for (const generation of generations) {
|
|
expect(generation.input).toBeDefined();
|
|
expect(generation.output).toBeDefined();
|
|
expect(generation.model).toBe(modelName);
|
|
expect(generation.modelParameters).toMatchObject({ maxTokens: maxTokens.toString() });
|
|
expect(generation.calculatedInputCost).toBeGreaterThan(0);
|
|
expect(generation.calculatedOutputCost).toBeGreaterThan(0);
|
|
expect(generation.calculatedTotalCost).toBeGreaterThan(0);
|
|
expect(generation.promptTokens).toBeGreaterThan(0);
|
|
expect(generation.completionTokens).toBeGreaterThan(0);
|
|
expect(generation.totalTokens).toBeGreaterThan(0);
|
|
expect(generation.timeToFirstToken).toBeGreaterThan(0);
|
|
}
|
|
}, 10_000);
|
|
|
|
// Currently flaky from the AI SDK side, skipping for now
|
|
it.skip("should trace a generateObject call", async () => {
|
|
const testParams = {
|
|
traceId: randomUUID(),
|
|
functionId: "test-vercel-generate-object",
|
|
modelName: "gpt-4-turbo-2024-04-09",
|
|
maxTokens: 512,
|
|
prompt: "Generate a lasagna recipe.",
|
|
userId: "some-user-id",
|
|
sessionId: "some-session-id",
|
|
metadata: {
|
|
something: "custom",
|
|
someOtherThing: "other-value",
|
|
},
|
|
tags: ["vercel", "openai"],
|
|
};
|
|
|
|
const { traceId, modelName, maxTokens, prompt, functionId, userId, sessionId, metadata, tags } = testParams;
|
|
|
|
const result = await generateObject({
|
|
model: openai(modelName),
|
|
schema: z.object({
|
|
recipe: z.object({
|
|
name: z.string(),
|
|
ingredients: z.array(
|
|
z.object({
|
|
name: z.string(),
|
|
amount: z.string(),
|
|
})
|
|
),
|
|
steps: z.array(z.string()),
|
|
}),
|
|
}),
|
|
prompt,
|
|
experimental_telemetry: {
|
|
isEnabled: true,
|
|
functionId,
|
|
metadata: {
|
|
hanzoTraceId: traceId,
|
|
userId,
|
|
sessionId,
|
|
tags,
|
|
...metadata,
|
|
},
|
|
},
|
|
});
|
|
|
|
await sdk.shutdown();
|
|
|
|
// Fetch trace
|
|
const traceFetchResult = await fetchTraceById(traceId);
|
|
expect(traceFetchResult.status).toBe(200);
|
|
|
|
// Validate trace
|
|
const trace = traceFetchResult.data;
|
|
|
|
expect(trace.id).toBe(traceId);
|
|
expect(trace.name).toBe(functionId);
|
|
expect(trace.input).toEqual({ prompt });
|
|
expect(trace.output.length).toBeGreaterThan(100);
|
|
expect(trace.userId).toBe(userId);
|
|
expect(trace.sessionId).toBe(sessionId);
|
|
expect(trace.tags).toEqual(tags.sort());
|
|
expect(trace.metadata).toMatchObject(metadata);
|
|
|
|
// Validate generations
|
|
expect(trace.observations.length).toBeGreaterThan(0);
|
|
const generations = trace.observations.filter((o: any) => o.type === "GENERATION");
|
|
|
|
for (const generation of generations) {
|
|
expect(generation.input).toBeDefined();
|
|
expect(generation.output).toBeDefined();
|
|
expect(generation.model).toBe(modelName);
|
|
expect(generation.modelParameters).toMatchObject({ maxTokens: maxTokens.toString() });
|
|
expect(generation.calculatedInputCost).toBeGreaterThan(0);
|
|
expect(generation.calculatedOutputCost).toBeGreaterThan(0);
|
|
expect(generation.calculatedTotalCost).toBeGreaterThan(0);
|
|
expect(generation.promptTokens).toBeGreaterThan(0);
|
|
expect(generation.completionTokens).toBeGreaterThan(0);
|
|
expect(generation.totalTokens).toBeGreaterThan(0);
|
|
}
|
|
}, 30_000);
|
|
|
|
// Currently flaky from the AI SDK side, skipping for now
|
|
it("should trace a streamObject call", async () => {
|
|
const testParams = {
|
|
traceId: randomUUID(),
|
|
functionId: "test-vercel-streamObject",
|
|
modelName: "gpt-4-turbo-2024-04-09",
|
|
maxTokens: 512,
|
|
prompt: "Generate a lasagna recipe.",
|
|
userId: "some-user-id",
|
|
sessionId: "some-session-id",
|
|
metadata: {
|
|
something: "custom",
|
|
someOtherThing: "other-value",
|
|
},
|
|
tags: ["vercel", "openai"],
|
|
};
|
|
|
|
const { traceId, modelName, prompt, functionId, userId, sessionId, metadata, tags } = testParams;
|
|
|
|
const { partialObjectStream } = await streamObject({
|
|
model: openai(modelName),
|
|
schema: z.object({
|
|
recipe: z.object({
|
|
name: z.string(),
|
|
ingredients: z.array(
|
|
z.object({
|
|
name: z.string(),
|
|
amount: z.string(),
|
|
})
|
|
),
|
|
steps: z.array(z.string()),
|
|
}),
|
|
}),
|
|
prompt,
|
|
experimental_telemetry: {
|
|
isEnabled: true,
|
|
functionId,
|
|
metadata: {
|
|
hanzoTraceId: traceId,
|
|
userId,
|
|
sessionId,
|
|
tags,
|
|
...metadata,
|
|
},
|
|
},
|
|
});
|
|
|
|
let currentObject;
|
|
for await (const partialObject of partialObjectStream) {
|
|
currentObject = partialObject;
|
|
}
|
|
|
|
await sdk.shutdown();
|
|
|
|
// Fetch trace
|
|
const traceFetchResult = await fetchTraceById(traceId);
|
|
expect(traceFetchResult.status).toBe(200);
|
|
|
|
// Validate trace
|
|
const trace = traceFetchResult.data;
|
|
|
|
expect(trace.id).toBe(traceId);
|
|
expect(trace.name).toBe(functionId);
|
|
expect(trace.input).toEqual({ prompt });
|
|
expect(JSON.stringify(trace.output).length).toBeGreaterThan(100);
|
|
expect(trace.userId).toBe(userId);
|
|
expect(trace.sessionId).toBe(sessionId);
|
|
expect(trace.tags).toEqual(tags.sort());
|
|
expect(trace.metadata).toMatchObject(metadata);
|
|
|
|
// Validate generations
|
|
expect(trace.observations.length).toBeGreaterThan(0);
|
|
const generations = trace.observations.filter((o: any) => o.type === "GENERATION");
|
|
|
|
for (const generation of generations) {
|
|
expect(generation.input).toBeDefined();
|
|
expect(generation.output).toBeDefined();
|
|
expect(generation.model).toBe(modelName);
|
|
expect(generation.calculatedInputCost).toBeGreaterThan(0);
|
|
expect(generation.calculatedOutputCost).toBeGreaterThan(0);
|
|
expect(generation.calculatedTotalCost).toBeGreaterThan(0);
|
|
expect(generation.promptTokens).toBeGreaterThan(0);
|
|
expect(generation.completionTokens).toBeGreaterThan(0);
|
|
expect(generation.totalTokens).toBeGreaterThan(0);
|
|
expect(generation.timeToFirstToken).toBeGreaterThan(0);
|
|
}
|
|
}, 30_000);
|
|
|
|
it("should trace a embed call", async () => {
|
|
const testParams = {
|
|
traceId: randomUUID(),
|
|
modelName: "text-embedding-3-small",
|
|
functionId: "test-vercel-embed",
|
|
userId: "some-user-id",
|
|
sessionId: "some-session-id",
|
|
metadata: {
|
|
something: "custom",
|
|
someOtherThing: "other-value",
|
|
},
|
|
tags: ["vercel", "openai"],
|
|
};
|
|
|
|
const { traceId, modelName, functionId, userId, sessionId, metadata, tags } = testParams;
|
|
|
|
const result = await embed({
|
|
model: openai.embedding(modelName),
|
|
value: "sunny day at the beach",
|
|
experimental_telemetry: {
|
|
isEnabled: true,
|
|
functionId,
|
|
metadata: {
|
|
hanzoTraceId: traceId,
|
|
userId,
|
|
sessionId,
|
|
tags,
|
|
...metadata,
|
|
},
|
|
},
|
|
});
|
|
|
|
await sdk.shutdown();
|
|
|
|
// Fetch trace
|
|
const traceFetchResult = await fetchTraceById(traceId);
|
|
expect(traceFetchResult.status).toBe(200);
|
|
|
|
// Validate trace
|
|
const trace = traceFetchResult.data;
|
|
|
|
expect(trace.id).toBe(traceId);
|
|
expect(trace.name).toBe(functionId);
|
|
expect(trace.userId).toBe(userId);
|
|
expect(trace.sessionId).toBe(sessionId);
|
|
expect(trace.tags).toEqual(tags.sort());
|
|
expect(trace.metadata).toMatchObject(metadata);
|
|
|
|
// Validate generations
|
|
expect(trace.observations.length).toBeGreaterThan(0);
|
|
const generations = trace.observations.filter((o: any) => o.type === "GENERATION");
|
|
|
|
for (const generation of generations) {
|
|
expect(generation.model).toBe(modelName);
|
|
expect(generation.calculatedTotalCost).toBeGreaterThan(0);
|
|
expect(generation.totalTokens).toBeGreaterThan(0);
|
|
}
|
|
}, 10_000);
|
|
|
|
it("should trace a streamText call with linked prompts", async () => {
|
|
const promptName = randomUUID();
|
|
|
|
const hanzo = new Hanzo();
|
|
await hanzo.createPrompt({
|
|
name: promptName,
|
|
type: "text",
|
|
prompt: "Invent a new holiday and describe its traditions.",
|
|
labels: ["production"],
|
|
});
|
|
|
|
const fetchedPrompt = await hanzo.getPrompt(promptName);
|
|
|
|
const testParams = {
|
|
traceId: randomUUID(),
|
|
functionId: "test-vercel-stream-text",
|
|
modelName: "gpt-3.5-turbo-0125",
|
|
maxTokens: 512,
|
|
prompt: fetchedPrompt.prompt,
|
|
userId: "some-user-id",
|
|
sessionId: "some-session-id",
|
|
metadata: {
|
|
something: "custom",
|
|
someOtherThing: "other-value",
|
|
},
|
|
tags: ["vercel", "openai"],
|
|
};
|
|
|
|
const { traceId, modelName, maxTokens, prompt, functionId, userId, sessionId, metadata, tags } = testParams;
|
|
|
|
const stream = await streamText({
|
|
model: openai(modelName),
|
|
maxTokens,
|
|
prompt,
|
|
experimental_telemetry: {
|
|
isEnabled: true,
|
|
functionId,
|
|
metadata: {
|
|
hanzoTraceId: traceId,
|
|
hanzoPrompt: fetchedPrompt.toJSON(),
|
|
userId,
|
|
sessionId,
|
|
tags,
|
|
...metadata,
|
|
} as any,
|
|
},
|
|
});
|
|
|
|
let result = "";
|
|
|
|
for await (const chunk of stream.textStream) {
|
|
result += chunk;
|
|
}
|
|
|
|
await sdk.shutdown();
|
|
|
|
// Fetch trace
|
|
const traceFetchResult = await fetchTraceById(traceId);
|
|
expect(traceFetchResult.status).toBe(200);
|
|
|
|
// Validate trace
|
|
const trace = traceFetchResult.data;
|
|
|
|
expect(trace.id).toBe(traceId);
|
|
expect(trace.name).toBe(functionId);
|
|
expect(trace.input).toEqual({ prompt });
|
|
expect(trace.output).toBe(result);
|
|
expect(trace.userId).toBe(userId);
|
|
expect(trace.sessionId).toBe(sessionId);
|
|
expect(trace.tags).toEqual(tags.sort());
|
|
expect(trace.metadata).toMatchObject(metadata);
|
|
|
|
// Validate generations
|
|
expect(trace.observations.length).toBeGreaterThan(0);
|
|
const generations = trace.observations.filter((o: any) => o.type === "GENERATION");
|
|
|
|
for (const generation of generations) {
|
|
expect(generation.input).toBeDefined();
|
|
expect(generation.output).toBeDefined();
|
|
expect(generation.model).toBe(modelName);
|
|
expect(generation.modelParameters).toMatchObject({ maxTokens: maxTokens.toString() });
|
|
expect(generation.calculatedInputCost).toBeGreaterThan(0);
|
|
expect(generation.calculatedOutputCost).toBeGreaterThan(0);
|
|
expect(generation.calculatedTotalCost).toBeGreaterThan(0);
|
|
expect(generation.promptTokens).toBeGreaterThan(0);
|
|
expect(generation.completionTokens).toBeGreaterThan(0);
|
|
expect(generation.totalTokens).toBeGreaterThan(0);
|
|
expect(generation.timeToFirstToken).toBeGreaterThan(0);
|
|
expect(generation.promptName).toBe(promptName);
|
|
expect(generation.promptVersion).toBe(fetchedPrompt.version);
|
|
}
|
|
}, 10_000);
|
|
|
|
it("should nest multiple generateText call under a trace", async () => {
|
|
const hanzo = new Hanzo();
|
|
const traceName = "parent_generate_text_multiple";
|
|
|
|
// Create parent trace
|
|
const parentTraceId = randomUUID();
|
|
hanzo.trace({ id: parentTraceId, name: traceName });
|
|
const baseRootSpanName = "root-span";
|
|
|
|
const NESTED_RUN_COUNT = 3;
|
|
|
|
for (let i = 0; i < NESTED_RUN_COUNT; i++) {
|
|
const testParams = {
|
|
traceId: parentTraceId,
|
|
modelName: "gpt-3.5-turbo-0125",
|
|
maxTokens: 50,
|
|
prompt: "Invent a new holiday and describe its traditions.",
|
|
functionId: `${baseRootSpanName}-${i}`,
|
|
userId: "some-user-id",
|
|
sessionId: "some-session-id",
|
|
metadata: {
|
|
something: "custom",
|
|
someOtherThing: "other-value",
|
|
},
|
|
tags: ["vercel", "openai"],
|
|
};
|
|
|
|
const { traceId, modelName, maxTokens, prompt, functionId, userId, sessionId, metadata, tags } = testParams;
|
|
|
|
await generateText({
|
|
model: openai(modelName),
|
|
maxTokens,
|
|
prompt,
|
|
experimental_telemetry: {
|
|
isEnabled: true,
|
|
functionId,
|
|
metadata: {
|
|
hanzoTraceId: traceId,
|
|
hanzoUpdateParent: false,
|
|
userId,
|
|
sessionId,
|
|
tags,
|
|
...metadata,
|
|
},
|
|
},
|
|
});
|
|
}
|
|
|
|
await hanzo.flushAsync();
|
|
await sdk.shutdown();
|
|
|
|
// Fetch trace
|
|
const traceFetchResult = await fetchTraceById(parentTraceId);
|
|
expect(traceFetchResult.status).toBe(200);
|
|
|
|
// Validate trace
|
|
const fetchedTrace = traceFetchResult.data;
|
|
expect(fetchedTrace.name).toBe(traceName);
|
|
|
|
const rootObservations = fetchedTrace.observations
|
|
.filter((o: any) => !Boolean(o.parentObservationId))
|
|
.sort((a: any, b: any) => a.name.localeCompare(b.name));
|
|
|
|
for (let i = 0; i < NESTED_RUN_COUNT; i++) {
|
|
const obs = rootObservations[i];
|
|
expect(obs.name).toBe(`${baseRootSpanName}-${i}`);
|
|
}
|
|
}, 15_000);
|
|
});
|