Skip to content

Commit 043f896

Browse files
authored
fix(ai): fix streamed tool call parsing, tool call arguments in v5 (#6813)
* fix tool call arguments parsing for v5 * fix tool call streaming and re-enable test * update assertion * fix remaining tests
1 parent 944e92b commit 043f896

File tree

2 files changed

+15
-16
lines changed

2 files changed

+15
-16
lines changed

packages/dd-trace/src/llmobs/plugins/ai/index.js

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,9 @@ class VercelAILLMObsPlugin extends BaseLLMObsPlugin {
8282
* @param {string} toolDescription
8383
* @returns {string | undefined}
8484
*/
85-
findToolName (toolDescription) {
85+
findToolName (toolName, toolDescription) {
86+
if (Number.isNaN(Number.parseInt(toolName))) return toolName
87+
8688
for (const availableTool of this.#availableTools) {
8789
const description = availableTool.description
8890
if (description === toolDescription && availableTool.id) {
@@ -260,16 +262,17 @@ class VercelAILLMObsPlugin extends BaseLLMObsPlugin {
260262

261263
const formattedToolCalls = []
262264
for (const toolCall of outputMessageToolCalls) {
263-
const toolCallArgs = getJsonStringValue(toolCall.args, {})
265+
const toolArgs = toolCall.args ?? toolCall.input
266+
const toolCallArgs = typeof toolArgs === 'string' ? getJsonStringValue(toolArgs, {}) : toolArgs
264267
const toolDescription = toolsForModel?.find(tool => toolCall.toolName === tool.name)?.description
265-
const name = this.findToolName(toolDescription)
268+
const name = this.findToolName(toolCall.toolName, toolDescription)
266269
this.#toolCallIdsToName[toolCall.toolCallId] = name
267270

268271
formattedToolCalls.push({
269272
arguments: toolCallArgs,
270273
name,
271274
toolId: toolCall.toolCallId,
272-
type: 'function'
275+
type: toolCall.toolCallType ?? 'function'
273276
})
274277
}
275278

@@ -317,10 +320,10 @@ class VercelAILLMObsPlugin extends BaseLLMObsPlugin {
317320
finalContent += part.text ?? part.data
318321
} else if (type === 'tool-call') {
319322
const toolDescription = toolsForModel?.find(tool => part.toolName === tool.name)?.description
320-
const name = this.findToolName(toolDescription)
323+
const name = this.findToolName(part.toolName, toolDescription)
321324

322325
toolCalls.push({
323-
arguments: part.args,
326+
arguments: part.args ?? part.input,
324327
name,
325328
toolId: part.toolCallId,
326329
type: 'function'

packages/dd-trace/test/llmobs/plugins/ai/index.spec.js

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -346,8 +346,7 @@ describe('Plugin', () => {
346346
})
347347
})
348348

349-
// TODO(sabrenner): Fix this test for v5.0.0 - tool "input" instead of "arguments"
350-
it.skip('creates a span for a tool call', async () => { // eslint-disable-line mocha/no-pending-tests
349+
it('creates a span for a tool call', async () => {
351350
let tools
352351
let additionalOptions = {}
353352
const toolSchema = ai.jsonSchema({
@@ -504,8 +503,7 @@ describe('Plugin', () => {
504503
})
505504
})
506505

507-
// TODO(sabrenner): Fix this test for v5.0.0 - tool "input" instead of "arguments" & parsing, streaming
508-
it.skip('created a span for a tool call from a stream', async () => { // eslint-disable-line mocha/no-pending-tests
506+
it('created a span for a tool call from a stream', async () => {
509507
let tools
510508
let additionalOptions = {}
511509
const toolSchema = ai.jsonSchema({
@@ -629,15 +627,13 @@ describe('Plugin', () => {
629627
span: apmSpans[2],
630628
parentId: llmobsSpans[0].span_id,
631629
/**
632-
* MOCK_STRING used as the stream implementation for ai does not finish the initial llm spans
630+
* Before [email protected], the stream implementation did not finish the initial llm spans
633631
* first to associate the tool call id with the tool itself (by matching descriptions).
634632
*
635-
* Usually, this would mean the tool call name is 'toolCall'.
636-
*
637-
* However, because we used mocked responses, the second time this test is called, the tool call
638-
* will have the name 'weather' instead. We just assert that the name exists and is a string to simplify.
633+
* Usually, this would mean the tool call name is 'toolCall'. This is a limitation with the older library
634+
* versions. In v5+, this is resolved as the tool name is not its index in the tools array, but its actual name.
639635
*/
640-
name: MOCK_STRING,
636+
name: semifies(realVersion, NODE_MAJOR < 22 ? '<=4.0.2' : '<4.0.2') ? 'toolCall' : 'weather',
641637
spanKind: 'tool',
642638
inputValue: JSON.stringify({ location: 'Tokyo' }),
643639
outputValue: JSON.stringify({ location: 'Tokyo', temperature: 72 }),

0 commit comments

Comments
 (0)