From d20f8b64242424a1ced6167ccf03d207aae7d7d8 Mon Sep 17 00:00:00 2001 From: Sam Brenner <106700075+sabrenner@users.noreply.github.com> Date: Wed, 4 Sep 2024 14:14:21 -0400 Subject: [PATCH] fix(openai): content and tool calls in streamed responses do not error (#4651) * fix * test * remove .only --- .../datadog-instrumentations/src/openai.js | 6 +- .../datadog-plugin-openai/test/index.spec.js | 55 +++++++++++++++++++ .../chat.completions.tool.and.content.txt | 33 +++++++++++ 3 files changed, 92 insertions(+), 2 deletions(-) create mode 100644 packages/datadog-plugin-openai/test/streamed-responses/chat.completions.tool.and.content.txt diff --git a/packages/datadog-instrumentations/src/openai.js b/packages/datadog-instrumentations/src/openai.js index 326e0d3092f..940b5919d24 100644 --- a/packages/datadog-instrumentations/src/openai.js +++ b/packages/datadog-instrumentations/src/openai.js @@ -165,10 +165,12 @@ function addStreamedChunk (content, chunk) { if (tools) { oldChoice.delta.tool_calls = tools.map((newTool, toolIdx) => { - const oldTool = oldChoice.delta.tool_calls[toolIdx] + const oldTool = oldChoice.delta.tool_calls?.[toolIdx] if (oldTool) { oldTool.function.arguments += newTool.function.arguments + } else { + return newTool } return oldTool @@ -247,7 +249,7 @@ function wrapStreamIterator (response, options, n, ctx) { return res }) .catch(err => { - finish(undefined, err) + finish(ctx, undefined, err) throw err }) diff --git a/packages/datadog-plugin-openai/test/index.spec.js b/packages/datadog-plugin-openai/test/index.spec.js index 228cafaa3b8..b10b912dbb4 100644 --- a/packages/datadog-plugin-openai/test/index.spec.js +++ b/packages/datadog-plugin-openai/test/index.spec.js @@ -3615,6 +3615,61 @@ describe('Plugin', () => { await checkTraces }) + + it('makes a successful chat completion call with tools and content', async () => { + nock('https://api.openai.com:443') + .post('/v1/chat/completions') + .reply(200, function () { + return fs.createReadStream( + Path.join(__dirname, 'streamed-responses/chat.completions.tool.and.content.txt') + ) + }, { + 'Content-Type': 'text/plain', + 'openai-organization': 'kill-9' + }) + + const checkTraces = agent + .use(traces => { + const span = traces[0][0] + + expect(span).to.have.property('name', 'openai.request') + expect(span).to.have.property('type', 'openai') + expect(span).to.have.property('error', 0) + expect(span.meta).to.have.property('openai.organization.name', 'kill-9') + expect(span.meta).to.have.property('openai.request.method', 'POST') + expect(span.meta).to.have.property('openai.request.endpoint', '/v1/chat/completions') + expect(span.meta).to.have.property('openai.request.model', 'gpt-4') + expect(span.meta).to.have.property('openai.request.messages.0.content', 'Hello, OpenAI!') + expect(span.meta).to.have.property('openai.request.messages.0.role', 'user') + expect(span.meta).to.have.property('openai.request.messages.0.name', 'hunter2') + expect(span.meta).to.have.property('openai.response.choices.0.message.role', 'assistant') + expect(span.meta).to.have.property('openai.response.choices.0.message.content', + 'THOUGHT: Hi') + expect(span.meta).to.have.property('openai.response.choices.0.finish_reason', 'tool_calls') + expect(span.meta).to.have.property('openai.response.choices.0.logprobs', 'returned') + expect(span.meta).to.have.property('openai.response.choices.0.message.tool_calls.0.function.name', + 'finish') + expect(span.meta).to.have.property( + 'openai.response.choices.0.message.tool_calls.0.function.arguments', + '{\n"answer": "5"\n}' + ) + }) + + const stream = await openai.chat.completions.create({ + model: 'gpt-4', + messages: [{ role: 'user', content: 'Hello, OpenAI!', name: 'hunter2' }], + temperature: 0.5, + tools: [], // dummy tools, the response is hardcoded + stream: true + }) + + for await (const part of stream) { + expect(part).to.have.property('choices') + expect(part.choices[0]).to.have.property('delta') + } + + await checkTraces + }) } }) } diff --git a/packages/datadog-plugin-openai/test/streamed-responses/chat.completions.tool.and.content.txt b/packages/datadog-plugin-openai/test/streamed-responses/chat.completions.tool.and.content.txt new file mode 100644 index 00000000000..3947339157d --- /dev/null +++ b/packages/datadog-plugin-openai/test/streamed-responses/chat.completions.tool.and.content.txt @@ -0,0 +1,33 @@ +data: {"id":"chatcmpl-A3juVlDlz6tV3bfCY2WZfYxRlKiAH","object":"chat.completion.chunk","created":1725454827,"model":"gpt-4-0613","system_fingerprint":null,"choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-A3juVlDlz6tV3bfCY2WZfYxRlKiAH","object":"chat.completion.chunk","created":1725454827,"model":"gpt-4-0613","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"TH"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-A3juVlDlz6tV3bfCY2WZfYxRlKiAH","object":"chat.completion.chunk","created":1725454827,"model":"gpt-4-0613","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"O"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-A3juVlDlz6tV3bfCY2WZfYxRlKiAH","object":"chat.completion.chunk","created":1725454827,"model":"gpt-4-0613","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"UGHT"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-A3juVlDlz6tV3bfCY2WZfYxRlKiAH","object":"chat.completion.chunk","created":1725454827,"model":"gpt-4-0613","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":":"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-A3juVlDlz6tV3bfCY2WZfYxRlKiAH","object":"chat.completion.chunk","created":1725454827,"model":"gpt-4-0613","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" Hi"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-A3juVlDlz6tV3bfCY2WZfYxRlKiAH","object":"chat.completion.chunk","created":1725454827,"model":"gpt-4-0613","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"id":"call_Tg0o5wgoNSKF2iggAPmfWwem","type":"function","function":{"name":"finish","arguments":""}}]},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-A3juVlDlz6tV3bfCY2WZfYxRlKiAH","object":"chat.completion.chunk","created":1725454827,"model":"gpt-4-0613","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"{\n"}}]},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-A3juVlDlz6tV3bfCY2WZfYxRlKiAH","object":"chat.completion.chunk","created":1725454827,"model":"gpt-4-0613","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\""}}]},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-A3juVlDlz6tV3bfCY2WZfYxRlKiAH","object":"chat.completion.chunk","created":1725454827,"model":"gpt-4-0613","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"answer"}}]},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-A3juVlDlz6tV3bfCY2WZfYxRlKiAH","object":"chat.completion.chunk","created":1725454827,"model":"gpt-4-0613","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\":"}}]},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-A3juVlDlz6tV3bfCY2WZfYxRlKiAH","object":"chat.completion.chunk","created":1725454827,"model":"gpt-4-0613","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" \""}}]},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-A3juVlDlz6tV3bfCY2WZfYxRlKiAH","object":"chat.completion.chunk","created":1725454827,"model":"gpt-4-0613","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"5"}}]},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-A3juVlDlz6tV3bfCY2WZfYxRlKiAH","object":"chat.completion.chunk","created":1725454827,"model":"gpt-4-0613","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\"\n"}}]},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-A3juVlDlz6tV3bfCY2WZfYxRlKiAH","object":"chat.completion.chunk","created":1725454827,"model":"gpt-4-0613","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"}"}}]},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-A3juVlDlz6tV3bfCY2WZfYxRlKiAH","object":"chat.completion.chunk","created":1725454827,"model":"gpt-4-0613","system_fingerprint":null,"choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"tool_calls"}]} + +data: [DONE] \ No newline at end of file