From 9a8f84c3659558b88d2148a796cb3c72e3f883d1 Mon Sep 17 00:00:00 2001 From: Nir Gazit Date: Mon, 17 Jun 2024 20:18:37 +0300 Subject: [PATCH] =?UTF-8?q?fix(sdk):=20run=20workflows=20in=20parallel=20w?= =?UTF-8?q?ith=20different=20association=20proper=E2=80=A6=20(#329)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../recording.har | 450 ++++++++++++++++++ .../src/lib/tracing/decorators.ts | 10 +- .../traceloop-sdk/test/decorators.test.ts | 71 ++- 3 files changed, 527 insertions(+), 4 deletions(-) create mode 100644 packages/traceloop-sdk/recordings/Test-SDK-Decorators_847855269/should-not-mix-association-properties-for-traces-that-run-in-parallel_4012223284/recording.har diff --git a/packages/traceloop-sdk/recordings/Test-SDK-Decorators_847855269/should-not-mix-association-properties-for-traces-that-run-in-parallel_4012223284/recording.har b/packages/traceloop-sdk/recordings/Test-SDK-Decorators_847855269/should-not-mix-association-properties-for-traces-that-run-in-parallel_4012223284/recording.har new file mode 100644 index 00000000..01cf5022 --- /dev/null +++ b/packages/traceloop-sdk/recordings/Test-SDK-Decorators_847855269/should-not-mix-association-properties-for-traces-that-run-in-parallel_4012223284/recording.har @@ -0,0 +1,450 @@ +{ + "log": { + "_recordingName": "Test SDK Decorators/should not mix association properties for traces that run in parallel", + "creator": { + "comment": "persister:fs", + "name": "Polly.JS", + "version": "6.0.6" + }, + "entries": [ + { + "_id": "80b48c70cae76a3c38b9af59d5273e33", + "_order": 0, + "cache": {}, + "request": { + "bodySize": 139, + "cookies": [], + "headers": [ + { + "_fromType": "array", + "name": "content-length", + "value": "139" + }, + { + "_fromType": "array", + "name": "accept", + "value": "application/json" + }, + { + "_fromType": "array", + "name": "content-type", + "value": "application/json" + }, + { + "_fromType": "array", + "name": "user-agent", + "value": "OpenAI/JS 4.51.0" + }, + { + "_fromType": "array", + "name": "x-stainless-lang", + "value": "js" + }, + { + "_fromType": "array", + "name": "x-stainless-package-version", + "value": "4.51.0" + }, + { + "_fromType": "array", + "name": "x-stainless-os", + "value": "MacOS" + }, + { + "_fromType": "array", + "name": "x-stainless-arch", + "value": "arm64" + }, + { + "_fromType": "array", + "name": "x-stainless-runtime", + "value": "node" + }, + { + "_fromType": "array", + "name": "x-stainless-runtime-version", + "value": "v20.11.1" + }, + { + "_fromType": "array", + "name": "accept-encoding", + "value": "gzip,deflate" + }, + { + "name": "host", + "value": "api.openai.com" + } + ], + "headersSize": 470, + "httpVersion": "HTTP/1.1", + "method": "POST", + "postData": { + "mimeType": "application/json", + "params": [], + "text": "{\n \"messages\": [\n {\n \"role\": \"user\",\n \"content\": \"Tell me a joke about OpenTelemetry\"\n }\n ],\n \"model\": \"gpt-3.5-turbo\"\n}" + }, + "queryString": [], + "url": "https://api.openai.com/v1/chat/completions" + }, + "response": { + "bodySize": 519, + "content": { + "encoding": "base64", + "mimeType": "application/json", + "size": 519, + "text": "[\"H4sIAAAAAAAAA1RRTU8CMRC9768Ye/ECBlCU5WJiwoGD0YPEgxrSbcdtpdup7axKDP/ddFkgXnp4X3nz+lsACKvFHIQyklUT3LCsyuXivpR6tSydXC6tXtnF6vmhNrPHiRhkB1UfqPjgulDUBIdsye9pFVEy5tTxzXh2fTWaza47oiGNLtvqwMPLi+mQ21jRcDSeTHunIaswiTm8FAAAv92bO3qNP2IOo8EBaTAlWaOYH0UAIpLLiJAp2cTSsxicSEWe0Xe1n80WtNXABuEhoH9Chw1y3ILGL3QUMEIVUW6gDfBt2WSljaAly0omvIVX/+rvUMk2IQ==\",\"WAZFrdP+nMFIrx328v0wPxDRyTxPMjZAxM/WRmzQczoTfb3d8S5HdYhU5Q1869wRf7feJrOOKBP5fENiCnv7rgB46/Zr/00iQqQm8Jppgz4Hjqf7OHH6sRM5uepJJpbuhF+WRd9PpG1ibNbv1tcYQ7TdmLllsSv+AAAA//8DADQASwlLAgAA\"]" + }, + "cookies": [ + { + "domain": ".api.openai.com", + "expires": "2024-06-17T16:44:47.000Z", + "httpOnly": true, + "name": "__cf_bm", + "path": "/", + "sameSite": "None", + "secure": true, + "value": "eatebJBz4_fBGo3SzpNvN3SCFOI0F0D8WD4xQHIzevE-1718640887-1.0.1.1-ttvBofwTxkxrVhyGb36TOLrjXQsa.R5p3.pCMt3c.aUKNXKTg5Y6w9Erl8Bu18Fn0LW0F24OzS9LyOgdTtyoaQ" + }, + { + "domain": ".api.openai.com", + "httpOnly": true, + "name": "_cfuvid", + "path": "/", + "sameSite": "None", + "secure": true, + "value": "HsgxH2p0yuj7ecY6GJU6eHHL0LZIT0thMvg9sncOFVg-1718640887067-0.0.1.1-604800000" + } + ], + "headers": [ + { + "name": "date", + "value": "Mon, 17 Jun 2024 16:14:47 GMT" + }, + { + "name": "content-type", + "value": "application/json" + }, + { + "name": "transfer-encoding", + "value": "chunked" + }, + { + "name": "connection", + "value": "keep-alive" + }, + { + "name": "openai-organization", + "value": "traceloop" + }, + { + "name": "openai-processing-ms", + "value": "512" + }, + { + "name": "openai-version", + "value": "2020-10-01" + }, + { + "name": "strict-transport-security", + "value": "max-age=15724800; includeSubDomains" + }, + { + "name": "x-ratelimit-limit-requests", + "value": "5000" + }, + { + "name": "x-ratelimit-limit-tokens", + "value": "160000" + }, + { + "name": "x-ratelimit-remaining-requests", + "value": "4999" + }, + { + "name": "x-ratelimit-remaining-tokens", + "value": "159974" + }, + { + "name": "x-ratelimit-reset-requests", + "value": "12ms" + }, + { + "name": "x-ratelimit-reset-tokens", + "value": "9ms" + }, + { + "name": "x-request-id", + "value": "req_be57115d5df92236c362166b468bb765" + }, + { + "name": "cf-cache-status", + "value": "DYNAMIC" + }, + { + "_fromType": "array", + "name": "set-cookie", + "value": "__cf_bm=eatebJBz4_fBGo3SzpNvN3SCFOI0F0D8WD4xQHIzevE-1718640887-1.0.1.1-ttvBofwTxkxrVhyGb36TOLrjXQsa.R5p3.pCMt3c.aUKNXKTg5Y6w9Erl8Bu18Fn0LW0F24OzS9LyOgdTtyoaQ; path=/; expires=Mon, 17-Jun-24 16:44:47 GMT; domain=.api.openai.com; HttpOnly; Secure; SameSite=None" + }, + { + "_fromType": "array", + "name": "set-cookie", + "value": "_cfuvid=HsgxH2p0yuj7ecY6GJU6eHHL0LZIT0thMvg9sncOFVg-1718640887067-0.0.1.1-604800000; path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None" + }, + { + "name": "server", + "value": "cloudflare" + }, + { + "name": "cf-ray", + "value": "895455a2f9a809c1-HFA" + }, + { + "name": "content-encoding", + "value": "gzip" + }, + { + "name": "alt-svc", + "value": "h3=\":443\"; ma=86400" + } + ], + "headersSize": 1100, + "httpVersion": "HTTP/1.1", + "redirectURL": "", + "status": 200, + "statusText": "OK" + }, + "startedDateTime": "2024-06-17T16:14:45.966Z", + "time": 1034, + "timings": { + "blocked": -1, + "connect": -1, + "dns": -1, + "receive": 0, + "send": 0, + "ssl": -1, + "wait": 1034 + } + }, + { + "_id": "9ddadc43d7726526740736861e3f407c", + "_order": 0, + "cache": {}, + "request": { + "bodySize": 136, + "cookies": [], + "headers": [ + { + "_fromType": "array", + "name": "content-length", + "value": "136" + }, + { + "_fromType": "array", + "name": "accept", + "value": "application/json" + }, + { + "_fromType": "array", + "name": "content-type", + "value": "application/json" + }, + { + "_fromType": "array", + "name": "user-agent", + "value": "OpenAI/JS 4.51.0" + }, + { + "_fromType": "array", + "name": "x-stainless-lang", + "value": "js" + }, + { + "_fromType": "array", + "name": "x-stainless-package-version", + "value": "4.51.0" + }, + { + "_fromType": "array", + "name": "x-stainless-os", + "value": "MacOS" + }, + { + "_fromType": "array", + "name": "x-stainless-arch", + "value": "arm64" + }, + { + "_fromType": "array", + "name": "x-stainless-runtime", + "value": "node" + }, + { + "_fromType": "array", + "name": "x-stainless-runtime-version", + "value": "v20.11.1" + }, + { + "_fromType": "array", + "name": "accept-encoding", + "value": "gzip,deflate" + }, + { + "name": "host", + "value": "api.openai.com" + } + ], + "headersSize": 470, + "httpVersion": "HTTP/1.1", + "method": "POST", + "postData": { + "mimeType": "application/json", + "params": [], + "text": "{\n \"messages\": [\n {\n \"role\": \"user\",\n \"content\": \"Tell me a joke about Typescript\"\n }\n ],\n \"model\": \"gpt-3.5-turbo\"\n}" + }, + "queryString": [], + "url": "https://api.openai.com/v1/chat/completions" + }, + "response": { + "bodySize": 479, + "content": { + "encoding": "base64", + "mimeType": "application/json", + "size": 479, + "text": "[\"H4sIAAAAAAAAA1SQy07DMBBF9/mKwRs2bZXQdzZI/QIQFQ9RVCXONDF1PJY9KYSq/46cvmDjxb1zx2fuPgIQqhApCFllLGur+/N8/pDM3uLF82j8lTwuf5odk5rMXqftfCF6IUH5J0o+pwaSaquRFZmjLR1mjGFrMk1mk1EyjKedUVOBOsRKy/3hYNznxuXUj5O78SlZkZLoRQrvEQDAvnsDoynwW6QQ985Kjd5nJYr0MgQgHOmgiMx75TkzLHpXU5JhNB32S9VCoQrgCmHZWnySTlmGAneoyaKDkiB3tMX7lVmZBcqs8QgVgqRGF+aWYaNMAZXywA==\",\"rcUbcfrlcMHTVFpHeTjFNFpf9I0yyldrh5knE1A8kz3GDxHAR1dD8+8yYR3VltdMWzRhYTI6rhPX4v+Yk5PJxJm+6sM4OvEJ33rGer1RpkRnneo6CZTRIfoFAAD//wMAoVnDcRICAAA=\"]" + }, + "cookies": [ + { + "domain": ".api.openai.com", + "expires": "2024-06-17T16:51:47.000Z", + "httpOnly": true, + "name": "__cf_bm", + "path": "/", + "sameSite": "None", + "secure": true, + "value": "7nnAQ2jVopmntqx9HIjIDiO2WYkoUfWl1L.HD8N9_Vc-1718641307-1.0.1.1-7u_dnzkKdPru1AoXJgJRgHwWjJ3PIU7h.LwbL_E_P5dueiGC3dYhI0EU.3DbCKMmJurt4LXqwnmf.RYgUmXclg" + }, + { + "domain": ".api.openai.com", + "httpOnly": true, + "name": "_cfuvid", + "path": "/", + "sameSite": "None", + "secure": true, + "value": "csY.VnNv6RR_R2xrYWb678tAFF6VtBvFWDyTj4pn1CA-1718641307658-0.0.1.1-604800000" + } + ], + "headers": [ + { + "name": "date", + "value": "Mon, 17 Jun 2024 16:21:47 GMT" + }, + { + "name": "content-type", + "value": "application/json" + }, + { + "name": "transfer-encoding", + "value": "chunked" + }, + { + "name": "connection", + "value": "keep-alive" + }, + { + "name": "openai-organization", + "value": "traceloop" + }, + { + "name": "openai-processing-ms", + "value": "392" + }, + { + "name": "openai-version", + "value": "2020-10-01" + }, + { + "name": "strict-transport-security", + "value": "max-age=15724800; includeSubDomains" + }, + { + "name": "x-ratelimit-limit-requests", + "value": "5000" + }, + { + "name": "x-ratelimit-limit-tokens", + "value": "160000" + }, + { + "name": "x-ratelimit-remaining-requests", + "value": "4999" + }, + { + "name": "x-ratelimit-remaining-tokens", + "value": "159975" + }, + { + "name": "x-ratelimit-reset-requests", + "value": "12ms" + }, + { + "name": "x-ratelimit-reset-tokens", + "value": "9ms" + }, + { + "name": "x-request-id", + "value": "req_a96da3f13aa728d26a1d588fbc792794" + }, + { + "name": "cf-cache-status", + "value": "DYNAMIC" + }, + { + "_fromType": "array", + "name": "set-cookie", + "value": "__cf_bm=7nnAQ2jVopmntqx9HIjIDiO2WYkoUfWl1L.HD8N9_Vc-1718641307-1.0.1.1-7u_dnzkKdPru1AoXJgJRgHwWjJ3PIU7h.LwbL_E_P5dueiGC3dYhI0EU.3DbCKMmJurt4LXqwnmf.RYgUmXclg; path=/; expires=Mon, 17-Jun-24 16:51:47 GMT; domain=.api.openai.com; HttpOnly; Secure; SameSite=None" + }, + { + "_fromType": "array", + "name": "set-cookie", + "value": "_cfuvid=csY.VnNv6RR_R2xrYWb678tAFF6VtBvFWDyTj4pn1CA-1718641307658-0.0.1.1-604800000; path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None" + }, + { + "name": "server", + "value": "cloudflare" + }, + { + "name": "cf-ray", + "value": "89545fe8bd5709c3-HFA" + }, + { + "name": "content-encoding", + "value": "gzip" + }, + { + "name": "alt-svc", + "value": "h3=\":443\"; ma=86400" + } + ], + "headersSize": 1100, + "httpVersion": "HTTP/1.1", + "redirectURL": "", + "status": 200, + "statusText": "OK" + }, + "startedDateTime": "2024-06-17T16:21:46.776Z", + "time": 782, + "timings": { + "blocked": -1, + "connect": -1, + "dns": -1, + "receive": 0, + "send": 0, + "ssl": -1, + "wait": 782 + } + } + ], + "pages": [], + "version": "1.2" + } +} diff --git a/packages/traceloop-sdk/src/lib/tracing/decorators.ts b/packages/traceloop-sdk/src/lib/tracing/decorators.ts index 9fd85a5e..a0675db1 100644 --- a/packages/traceloop-sdk/src/lib/tracing/decorators.ts +++ b/packages/traceloop-sdk/src/lib/tracing/decorators.ts @@ -199,15 +199,19 @@ function entity( const originalMethod = descriptor.value; descriptor.value = function (...args: unknown[]) { + let actualConfig; + if (typeof config === "function") { - config = config(this, ...args); + actualConfig = config(this, ...args); + } else { + actualConfig = config; } - const entityName = config.name ?? originalMethod.name; + const entityName = actualConfig.name ?? originalMethod.name; return withEntity( type, - { ...config, name: entityName }, + { ...actualConfig, name: entityName }, originalMethod, this, ...args, diff --git a/packages/traceloop-sdk/test/decorators.test.ts b/packages/traceloop-sdk/test/decorators.test.ts index f5770267..7db6d038 100644 --- a/packages/traceloop-sdk/test/decorators.test.ts +++ b/packages/traceloop-sdk/test/decorators.test.ts @@ -385,7 +385,7 @@ describe("Test SDK Decorators", () => { it("should create spans for manual LLM instrumentation", async () => { const result = await traceloop.withWorkflow( - { name: "joke_generator" }, + { name: "joke_generator", associationProperties: { userId: "123" } }, () => traceloop.withLLMCall( { vendor: "openai", type: "chat" }, @@ -427,6 +427,19 @@ describe("Test SDK Decorators", () => { workflowSpan.spanContext().spanId, ); + assert.strictEqual( + workflowSpan.attributes[ + `${SpanAttributes.TRACELOOP_ASSOCIATION_PROPERTIES}.userId` + ], + "123", + ); + assert.strictEqual( + completionSpan.attributes[ + `${SpanAttributes.TRACELOOP_ASSOCIATION_PROPERTIES}.userId` + ], + "123", + ); + assert.strictEqual( completionSpan.attributes[`${SpanAttributes.LLM_REQUEST_TYPE}`], "chat", @@ -464,4 +477,60 @@ describe("Test SDK Decorators", () => { ]! > 0, ); }); + + it("should not mix association properties for traces that run in parallel", async () => { + class TestOpenAI { + constructor(private userId: string) {} + + @traceloop.workflow((thisArg) => ({ + name: "chat", + associationProperties: { userId: (thisArg as TestOpenAI).userId }, + })) + async chat(subject: string) { + const chatCompletion = await openai.chat.completions.create({ + messages: [ + { role: "user", content: `Tell me a joke about ${subject}` }, + ], + model: "gpt-3.5-turbo", + }); + + return chatCompletion.choices[0].message.content; + } + } + + const result1 = await new TestOpenAI("123").chat("OpenTelemetry"); + const result2 = await new TestOpenAI("456").chat("Typescript"); + + const spans = memoryExporter.getFinishedSpans(); + + assert.ok(result1); + assert.ok(result2); + + const openAI1Span = spans.find( + (span) => + span.attributes[`${SpanAttributes.LLM_PROMPTS}.0.content`] === + "Tell me a joke about OpenTelemetry", + ); + const openAI2Span = spans.find( + (span) => + span.attributes[`${SpanAttributes.LLM_PROMPTS}.0.content`] === + "Tell me a joke about Typescript", + ); + + assert.ok(openAI1Span); + assert.ok(openAI2Span); + + assert.strictEqual( + openAI1Span.attributes[ + `${SpanAttributes.TRACELOOP_ASSOCIATION_PROPERTIES}.userId` + ], + "123", + ); + assert.strictEqual( + openAI2Span.attributes[ + `${SpanAttributes.TRACELOOP_ASSOCIATION_PROPERTIES}.userId` + ], + "456", + ); + }); });