Skip to content

Commit a191c9d

Browse files
fix(sdk-js) Chained entity name on Task & Tool spans (#383)
1 parent 5019c20 commit a191c9d

File tree

5 files changed

+331
-2
lines changed

5 files changed

+331
-2
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
{
2+
"log": {
3+
"_recordingName": "Test SDK Decorators/should create workflow and tasks spans with chained entity names",
4+
"creator": {
5+
"comment": "persister:fs",
6+
"name": "Polly.JS",
7+
"version": "6.0.6"
8+
},
9+
"entries": [
10+
{
11+
"_id": "1f5e5f3a0f77648b2315477cbbf33879",
12+
"_order": 0,
13+
"cache": {},
14+
"request": {
15+
"bodySize": 133,
16+
"cookies": [],
17+
"headers": [
18+
{
19+
"_fromType": "array",
20+
"name": "content-length",
21+
"value": "133"
22+
},
23+
{
24+
"_fromType": "array",
25+
"name": "accept",
26+
"value": "application/json"
27+
},
28+
{
29+
"_fromType": "array",
30+
"name": "content-type",
31+
"value": "application/json"
32+
},
33+
{
34+
"_fromType": "array",
35+
"name": "user-agent",
36+
"value": "OpenAI/JS 4.51.0"
37+
},
38+
{
39+
"_fromType": "array",
40+
"name": "x-stainless-lang",
41+
"value": "js"
42+
},
43+
{
44+
"_fromType": "array",
45+
"name": "x-stainless-package-version",
46+
"value": "4.51.0"
47+
},
48+
{
49+
"_fromType": "array",
50+
"name": "x-stainless-os",
51+
"value": "MacOS"
52+
},
53+
{
54+
"_fromType": "array",
55+
"name": "x-stainless-arch",
56+
"value": "arm64"
57+
},
58+
{
59+
"_fromType": "array",
60+
"name": "x-stainless-runtime",
61+
"value": "node"
62+
},
63+
{
64+
"_fromType": "array",
65+
"name": "x-stainless-runtime-version",
66+
"value": "v22.0.0"
67+
},
68+
{
69+
"_fromType": "array",
70+
"name": "accept-encoding",
71+
"value": "gzip,deflate"
72+
},
73+
{
74+
"name": "host",
75+
"value": "api.openai.com"
76+
}
77+
],
78+
"headersSize": 469,
79+
"httpVersion": "HTTP/1.1",
80+
"method": "POST",
81+
"postData": {
82+
"mimeType": "application/json",
83+
"params": [],
84+
"text": "{\n \"messages\": [\n {\n \"role\": \"user\",\n \"content\": \"Tell me a joke about pirates\"\n }\n ],\n \"model\": \"gpt-3.5-turbo\"\n}"
85+
},
86+
"queryString": [],
87+
"url": "https://api.openai.com/v1/chat/completions"
88+
},
89+
"response": {
90+
"bodySize": 451,
91+
"content": {
92+
"encoding": "base64",
93+
"mimeType": "application/json",
94+
"size": 451,
95+
"text": "[\"H4sIAAAAAAAAA1SQwU7DMBBE7/mKxecGNalCaS4IJJA4cQDUIoQqx9kmBsfr2luJUvXfkdO0hcseZjyzb71LAISuRQlCtZJV50w6o/v8+SFU6/l8dfWq5k/ju8efFnGxvl28iVFMUPWJio+pS0WdM8ia7MFWHiVjbM2meTYtppNZ3hsd1WhirHGcTi6LlDe+onSc5cWQbEkrDKKE9wQAYNfPyGhr/BYljEdHpcMQZIOiPD0CEJ5MVIQMQQeWlsXobCqyjLbHnrdbqHUN\",\"3CI47SUjNARMvVBJfwMvBA0ytDpAQAkGm3Ahhq79CcJQ4zxVEdhujDnpK211aJceZSAbFwYmd4jvE4CP/tjNP37hPHWOl0xfaGNhNjnUifP3/jGLwWRiac56fp0MfCJsA2O3XGnboHde95dHymSf/AIAAP//AwC01euq+AEAAA==\"]"
96+
},
97+
"cookies": [
98+
{
99+
"domain": ".api.openai.com",
100+
"expires": "2024-07-23T18:26:32.000Z",
101+
"httpOnly": true,
102+
"name": "__cf_bm",
103+
"path": "/",
104+
"sameSite": "None",
105+
"secure": true,
106+
"value": "H91GR4kfVHvrZNLG6C4p7cTy0Rje3q95RXaNRra.E7w-1721757392-1.0.1.1-THuuXeeZjJ_kAnzn8lv.3KorJ7IgwtB9_uFiWMgJmsAZgN16Q6Y3ilq2JBkbQtS8SyVJjCY9Dup01866tcpTYQ"
107+
},
108+
{
109+
"domain": ".api.openai.com",
110+
"httpOnly": true,
111+
"name": "_cfuvid",
112+
"path": "/",
113+
"sameSite": "None",
114+
"secure": true,
115+
"value": ".sNQn3QGP675mXVWZPtdu1NGO9R3ZBNRvsCPus1F6mY-1721757392468-0.0.1.1-604800000"
116+
}
117+
],
118+
"headers": [
119+
{
120+
"name": "date",
121+
"value": "Tue, 23 Jul 2024 17:56:32 GMT"
122+
},
123+
{
124+
"name": "content-type",
125+
"value": "application/json"
126+
},
127+
{
128+
"name": "transfer-encoding",
129+
"value": "chunked"
130+
},
131+
{
132+
"name": "connection",
133+
"value": "keep-alive"
134+
},
135+
{
136+
"name": "openai-organization",
137+
"value": "traceloop"
138+
},
139+
{
140+
"name": "openai-processing-ms",
141+
"value": "311"
142+
},
143+
{
144+
"name": "openai-version",
145+
"value": "2020-10-01"
146+
},
147+
{
148+
"name": "strict-transport-security",
149+
"value": "max-age=15552000; includeSubDomains; preload"
150+
},
151+
{
152+
"name": "x-ratelimit-limit-requests",
153+
"value": "5000"
154+
},
155+
{
156+
"name": "x-ratelimit-limit-tokens",
157+
"value": "4000000"
158+
},
159+
{
160+
"name": "x-ratelimit-remaining-requests",
161+
"value": "4999"
162+
},
163+
{
164+
"name": "x-ratelimit-remaining-tokens",
165+
"value": "3999976"
166+
},
167+
{
168+
"name": "x-ratelimit-reset-requests",
169+
"value": "12ms"
170+
},
171+
{
172+
"name": "x-ratelimit-reset-tokens",
173+
"value": "0s"
174+
},
175+
{
176+
"name": "x-request-id",
177+
"value": "req_992d6f84a8255b5722ef98d224d589a3"
178+
},
179+
{
180+
"name": "cf-cache-status",
181+
"value": "DYNAMIC"
182+
},
183+
{
184+
"_fromType": "array",
185+
"name": "set-cookie",
186+
"value": "__cf_bm=H91GR4kfVHvrZNLG6C4p7cTy0Rje3q95RXaNRra.E7w-1721757392-1.0.1.1-THuuXeeZjJ_kAnzn8lv.3KorJ7IgwtB9_uFiWMgJmsAZgN16Q6Y3ilq2JBkbQtS8SyVJjCY9Dup01866tcpTYQ; path=/; expires=Tue, 23-Jul-24 18:26:32 GMT; domain=.api.openai.com; HttpOnly; Secure; SameSite=None"
187+
},
188+
{
189+
"_fromType": "array",
190+
"name": "set-cookie",
191+
"value": "_cfuvid=.sNQn3QGP675mXVWZPtdu1NGO9R3ZBNRvsCPus1F6mY-1721757392468-0.0.1.1-604800000; path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None"
192+
},
193+
{
194+
"name": "x-content-type-options",
195+
"value": "nosniff"
196+
},
197+
{
198+
"name": "server",
199+
"value": "cloudflare"
200+
},
201+
{
202+
"name": "cf-ray",
203+
"value": "8a7d8c339925b077-ATL"
204+
},
205+
{
206+
"name": "content-encoding",
207+
"value": "gzip"
208+
},
209+
{
210+
"name": "alt-svc",
211+
"value": "h3=\":443\"; ma=86400"
212+
}
213+
],
214+
"headersSize": 1143,
215+
"httpVersion": "HTTP/1.1",
216+
"redirectURL": "",
217+
"status": 200,
218+
"statusText": "OK"
219+
},
220+
"startedDateTime": "2024-07-23T17:56:31.166Z",
221+
"time": 1192,
222+
"timings": {
223+
"blocked": -1,
224+
"connect": -1,
225+
"dns": -1,
226+
"receive": 0,
227+
"send": 0,
228+
"ssl": -1,
229+
"wait": 1192
230+
}
231+
}
232+
],
233+
"pages": [],
234+
"version": "1.2"
235+
}
236+
}

packages/traceloop-sdk/src/lib/tracing/decorators.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { Span, context } from "@opentelemetry/api";
22
import {
33
ASSOCATION_PROPERTIES_KEY,
4+
ENTITY_NAME_KEY,
5+
getChainedEntityName,
46
getTracer,
57
WORKFLOW_NAME_KEY,
68
} from "./tracing";
@@ -43,6 +45,16 @@ function withEntity<
4345
) {
4446
entityContext = entityContext.setValue(WORKFLOW_NAME_KEY, name);
4547
}
48+
49+
let entityName = name;
50+
if (
51+
type === TraceloopSpanKindValues.TOOL ||
52+
type === TraceloopSpanKindValues.TASK
53+
) {
54+
entityName = getChainedEntityName(entityContext, name);
55+
entityContext = entityContext.setValue(ENTITY_NAME_KEY, entityName);
56+
}
57+
4658
if (overrideTraceContent != undefined) {
4759
entityContext = entityContext.setValue(
4860
CONTEXT_KEY_ALLOW_TRACE_CONTENT,
@@ -68,8 +80,8 @@ function withEntity<
6880
) {
6981
span.setAttribute(SpanAttributes.TRACELOOP_WORKFLOW_NAME, name);
7082
}
83+
span.setAttribute(SpanAttributes.TRACELOOP_ENTITY_NAME, entityName);
7184
span.setAttribute(SpanAttributes.TRACELOOP_SPAN_KIND, type);
72-
span.setAttribute(SpanAttributes.TRACELOOP_ENTITY_NAME, name);
7385

7486
if (version) {
7587
span.setAttribute(SpanAttributes.TRACELOOP_ENTITY_VERSION, version);

packages/traceloop-sdk/src/lib/tracing/index.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,11 @@ import { Resource } from "@opentelemetry/resources";
1111
import { SEMRESATTRS_SERVICE_NAME } from "@opentelemetry/semantic-conventions";
1212
import { Instrumentation } from "@opentelemetry/instrumentation";
1313
import { InitializeOptions } from "../interfaces";
14-
import { ASSOCATION_PROPERTIES_KEY, WORKFLOW_NAME_KEY } from "./tracing";
14+
import {
15+
ASSOCATION_PROPERTIES_KEY,
16+
ENTITY_NAME_KEY,
17+
WORKFLOW_NAME_KEY,
18+
} from "./tracing";
1519
import { Telemetry } from "../telemetry/telemetry";
1620
import { _configuration } from "../configuration";
1721
import {
@@ -257,6 +261,14 @@ export const startTracing = (options: InitializeOptions) => {
257261
);
258262
}
259263

264+
const entityName = context.active().getValue(ENTITY_NAME_KEY);
265+
if (entityName) {
266+
span.setAttribute(
267+
SpanAttributes.TRACELOOP_ENTITY_NAME,
268+
entityName as string,
269+
);
270+
}
271+
260272
const associationProperties = context
261273
.active()
262274
.getValue(ASSOCATION_PROPERTIES_KEY);
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,22 @@
11
import { trace, createContextKey } from "@opentelemetry/api";
2+
import { Context } from "@opentelemetry/api/build/src/context/types";
23

34
const TRACER_NAME = "traceloop.tracer";
45
export const WORKFLOW_NAME_KEY = createContextKey("workflow_name");
6+
export const ENTITY_NAME_KEY = createContextKey("entity_name");
57
export const ASSOCATION_PROPERTIES_KEY = createContextKey(
68
"association_properties",
79
);
810

911
export const getTracer = () => {
1012
return trace.getTracer(TRACER_NAME);
1113
};
14+
15+
export const getChainedEntityName = (
16+
entityContext: Context,
17+
entityName: string,
18+
): string => {
19+
const parentEntityName = entityContext.getValue(ENTITY_NAME_KEY);
20+
21+
return parentEntityName ? `${parentEntityName}.${entityName}` : entityName;
22+
};

packages/traceloop-sdk/test/decorators.test.ts

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -537,4 +537,62 @@ describe("Test SDK Decorators", () => {
537537
"456",
538538
);
539539
});
540+
541+
it("should create workflow and tasks spans with chained entity names", async () => {
542+
class TestOpenAI {
543+
@traceloop.workflow({ name: "joke_creation_chat", version: 3 })
544+
async chat() {
545+
return await this.jokeCreationTaskWrapper();
546+
}
547+
548+
@traceloop.task({ name: "joke_creation_task_wrapper", version: 2 })
549+
async jokeCreationTaskWrapper() {
550+
return await this.jokeCreation();
551+
}
552+
553+
@traceloop.task({ name: "joke_creation", version: 2 })
554+
async jokeCreation() {
555+
const chatCompletion = await openai.chat.completions.create({
556+
messages: [{ role: "user", content: "Tell me a joke about pirates" }],
557+
model: "gpt-3.5-turbo",
558+
});
559+
560+
return chatCompletion.choices[0].message.content;
561+
}
562+
}
563+
564+
const testOpenAI = new TestOpenAI();
565+
const result = await testOpenAI.chat();
566+
const spans = memoryExporter.getFinishedSpans();
567+
const jokeCreationTaskWrapperSpan = spans.find(
568+
(span) => span.name === "joke_creation_task_wrapper.task",
569+
);
570+
const jokeCreationSpan = spans.find(
571+
(span) => span.name === "joke_creation.task",
572+
);
573+
const openAiChatSpans = spans.find((span) => span.name === "openai.chat");
574+
575+
assert.ok(result);
576+
assert.ok(jokeCreationTaskWrapperSpan);
577+
assert.ok(jokeCreationSpan);
578+
assert.ok(openAiChatSpans);
579+
assert.strictEqual(
580+
jokeCreationTaskWrapperSpan.attributes[
581+
`${SpanAttributes.TRACELOOP_ENTITY_NAME}`
582+
],
583+
"joke_creation_task_wrapper",
584+
);
585+
assert.strictEqual(
586+
jokeCreationSpan.attributes[`${SpanAttributes.TRACELOOP_ENTITY_NAME}`],
587+
"joke_creation_task_wrapper.joke_creation",
588+
);
589+
assert.strictEqual(
590+
openAiChatSpans.attributes[`${SpanAttributes.TRACELOOP_ENTITY_NAME}`],
591+
"joke_creation_task_wrapper.joke_creation",
592+
);
593+
assert.strictEqual(
594+
jokeCreationSpan.attributes[`${SpanAttributes.TRACELOOP_ENTITY_OUTPUT}`],
595+
JSON.stringify(result),
596+
);
597+
});
540598
});

0 commit comments

Comments
 (0)