Skip to content

Commit d6ae7dd

Browse files
committed
Sending OpenAI Agent SDK spans through to the platform now works
1 parent 030f316 commit d6ae7dd

File tree

11 files changed

+693
-47
lines changed

11 files changed

+693
-47
lines changed

apps/webapp/app/v3/otlpExporter.server.ts

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import {
2727
import { logger } from "~/services/logger.server";
2828
import { trace, Tracer } from "@opentelemetry/api";
2929
import { startSpan } from "./tracing.server";
30+
import { enrichCreatableEvents } from "./utils/enrichCreatableEvents.server";
3031

3132
export type OTLPExporterConfig = {
3233
batchSize: number;
@@ -54,14 +55,16 @@ class OTLPExporter {
5455
return convertSpansToCreateableEvents(resourceSpan);
5556
});
5657

57-
this.#logEventsVerbose(events);
58+
const enrichedEvents = enrichCreatableEvents(events);
5859

59-
span.setAttribute("event_count", events.length);
60+
this.#logEventsVerbose(enrichedEvents);
61+
62+
span.setAttribute("event_count", enrichedEvents.length);
6063

6164
if (immediate) {
62-
await this._eventRepository.insertManyImmediate(events);
65+
await this._eventRepository.insertManyImmediate(enrichedEvents);
6366
} else {
64-
await this._eventRepository.insertMany(events);
67+
await this._eventRepository.insertMany(enrichedEvents);
6568
}
6669

6770
return ExportTraceServiceResponse.create();
@@ -79,14 +82,16 @@ class OTLPExporter {
7982
return convertLogsToCreateableEvents(resourceLog);
8083
});
8184

82-
this.#logEventsVerbose(events);
85+
const enrichedEvents = enrichCreatableEvents(events);
86+
87+
this.#logEventsVerbose(enrichedEvents);
8388

84-
span.setAttribute("event_count", events.length);
89+
span.setAttribute("event_count", enrichedEvents.length);
8590

8691
if (immediate) {
87-
await this._eventRepository.insertManyImmediate(events);
92+
await this._eventRepository.insertManyImmediate(enrichedEvents);
8893
} else {
89-
await this._eventRepository.insertMany(events);
94+
await this._eventRepository.insertMany(enrichedEvents);
9095
}
9196

9297
return ExportLogsServiceResponse.create();
@@ -135,16 +140,28 @@ class OTLPExporter {
135140
(attribute) => attribute.key === SemanticInternalAttributes.TRIGGER
136141
);
137142

138-
if (!triggerAttribute) {
143+
const executionEnvironmentAttribute = resourceSpan.resource?.attributes.find(
144+
(attribute) => attribute.key === SemanticInternalAttributes.EXECUTION_ENVIRONMENT
145+
);
146+
147+
if (!triggerAttribute && !executionEnvironmentAttribute) {
139148
logger.debug("Skipping resource span without trigger attribute", {
140149
attributes: resourceSpan.resource?.attributes,
141150
spans: resourceSpan.scopeSpans.flatMap((scopeSpan) => scopeSpan.spans),
142151
});
143152

144-
return;
153+
return true; // go ahead and let this resource span through
154+
}
155+
156+
const executionEnvironment = isStringValue(executionEnvironmentAttribute?.value)
157+
? executionEnvironmentAttribute.value.stringValue
158+
: undefined;
159+
160+
if (executionEnvironment === "trigger") {
161+
return true; // go ahead and let this resource span through
145162
}
146163

147-
return isBoolValue(triggerAttribute.value) ? triggerAttribute.value.boolValue : false;
164+
return isBoolValue(triggerAttribute?.value) ? triggerAttribute.value.boolValue : false;
148165
});
149166
}
150167

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import type { CreatableEvent } from "../eventRepository.server";
2+
3+
type StyleEnricher = {
4+
name: string;
5+
condition: (event: CreatableEvent) => boolean;
6+
enrich: (event: CreatableEvent) => Record<string, string> | undefined;
7+
};
8+
9+
// Define our style enrichers
10+
const styleEnrichers: StyleEnricher[] = [
11+
{
12+
name: "GenAI System",
13+
condition: (event) => typeof event.properties["gen_ai.system"] === "string",
14+
enrich: (event) => ({
15+
icon: `tabler-brand-${event.properties["gen_ai.system"]}`,
16+
}),
17+
},
18+
{
19+
name: "Agent workflow",
20+
condition: (event) =>
21+
typeof event.properties["name"] === "string" &&
22+
event.properties["name"].includes("Agent workflow"),
23+
enrich: () => ({
24+
icon: "tabler-brain",
25+
}),
26+
},
27+
];
28+
29+
export function enrichCreatableEvents(events: CreatableEvent[]) {
30+
return events.map((event) => {
31+
return enrichCreatableEvent(event);
32+
});
33+
}
34+
35+
function enrichCreatableEvent(event: CreatableEvent): CreatableEvent {
36+
const message = formatPythonStyle(event.message, event.properties);
37+
38+
event.message = message;
39+
event.style = enrichStyle(event);
40+
41+
return event;
42+
}
43+
44+
function enrichStyle(event: CreatableEvent) {
45+
// Keep existing style properties as base
46+
const baseStyle = event.style ?? {};
47+
48+
// Find the first matching enricher
49+
for (const enricher of styleEnrichers) {
50+
if (enricher.condition(event)) {
51+
const enrichedStyle = enricher.enrich(event);
52+
if (enrichedStyle) {
53+
return {
54+
...baseStyle,
55+
...enrichedStyle,
56+
};
57+
}
58+
}
59+
}
60+
61+
// Return original style if no enricher matched
62+
return baseStyle;
63+
}
64+
65+
function repr(value: any): string {
66+
if (typeof value === "string") {
67+
return `'${value}'`;
68+
}
69+
return String(value);
70+
}
71+
72+
function formatPythonStyle(template: string, values: Record<string, any>): string {
73+
return template.replace(/\{([^}]+?)(?:!r)?\}/g, (match, key) => {
74+
const hasRepr = match.endsWith("!r}");
75+
const actualKey = hasRepr ? key : key;
76+
const value = values[actualKey];
77+
78+
if (value === undefined) {
79+
return match;
80+
}
81+
82+
return hasRepr ? repr(value) : String(value);
83+
});
84+
}

0 commit comments

Comments
 (0)