@librechat/agents 3.1.37 → 3.1.38
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/agents/AgentContext.cjs +3 -0
- package/dist/cjs/agents/AgentContext.cjs.map +1 -1
- package/dist/cjs/graphs/Graph.cjs +1 -1
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/stream.cjs +2 -1
- package/dist/cjs/stream.cjs.map +1 -1
- package/dist/cjs/tools/handlers.cjs +25 -8
- package/dist/cjs/tools/handlers.cjs.map +1 -1
- package/dist/esm/agents/AgentContext.mjs +3 -0
- package/dist/esm/agents/AgentContext.mjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +1 -1
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/stream.mjs +2 -1
- package/dist/esm/stream.mjs.map +1 -1
- package/dist/esm/tools/handlers.mjs +25 -8
- package/dist/esm/tools/handlers.mjs.map +1 -1
- package/dist/types/agents/AgentContext.d.ts +2 -0
- package/dist/types/types/tools.d.ts +1 -0
- package/package.json +1 -1
- package/src/agents/AgentContext.ts +3 -0
- package/src/graphs/Graph.ts +1 -1
- package/src/scripts/bedrock-content-aggregation-test.ts +265 -0
- package/src/scripts/bedrock-parallel-tools-test.ts +203 -0
- package/src/scripts/tools.ts +3 -12
- package/src/stream.ts +2 -1
- package/src/tools/__tests__/ToolNode.session.test.ts +465 -0
- package/src/tools/__tests__/handlers.test.ts +994 -0
- package/src/tools/handlers.ts +32 -13
- package/src/types/tools.ts +1 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"handlers.mjs","sources":["../../../src/tools/handlers.ts"],"sourcesContent":["/* eslint-disable no-console */\n// src/tools/handlers.ts\nimport { nanoid } from 'nanoid';\nimport { ToolMessage } from '@langchain/core/messages';\nimport type { AnthropicWebSearchResultBlockParam } from '@/llm/anthropic/types';\nimport type { ToolCall, ToolCallChunk } from '@langchain/core/messages/tool';\nimport type { MultiAgentGraph, StandardGraph } from '@/graphs';\nimport type { AgentContext } from '@/agents/AgentContext';\nimport type * as t from '@/types';\nimport {\n ToolCallTypes,\n GraphEvents,\n StepTypes,\n Providers,\n Constants,\n} from '@/common';\nimport {\n coerceAnthropicSearchResults,\n isAnthropicWebSearchResult,\n} from '@/tools/search/anthropic';\nimport { formatResultsForLLM } from '@/tools/search/format';\nimport { getMessageId } from '@/messages';\n\nexport async function handleToolCallChunks({\n graph,\n stepKey,\n toolCallChunks,\n metadata,\n}: {\n graph: StandardGraph | MultiAgentGraph;\n stepKey: string;\n toolCallChunks: ToolCallChunk[];\n metadata?: Record<string, unknown>;\n}): Promise<void> {\n let prevStepId: string;\n let prevRunStep: t.RunStep | undefined;\n try {\n prevStepId = graph.getStepIdByKey(stepKey);\n prevRunStep = graph.getRunStep(prevStepId);\n } catch {\n /** Edge Case: If no previous step exists, create a new message creation step */\n const message_id = getMessageId(stepKey, graph, true) ?? '';\n prevStepId = await graph.dispatchRunStep(\n stepKey,\n {\n type: StepTypes.MESSAGE_CREATION,\n message_creation: {\n message_id,\n },\n },\n metadata\n );\n prevRunStep = graph.getRunStep(prevStepId);\n }\n\n const _stepId = graph.getStepIdByKey(stepKey);\n\n /** Edge Case: Tool Call Run Step or `tool_call_ids` never dispatched */\n const tool_calls: ToolCall[] | undefined =\n prevStepId && prevRunStep && prevRunStep.type === StepTypes.MESSAGE_CREATION\n ? []\n : undefined;\n\n /** Edge Case: `id` and `name` fields cannot be empty strings */\n for (const toolCallChunk of toolCallChunks) {\n if (toolCallChunk.name === '') {\n toolCallChunk.name = undefined;\n }\n if (toolCallChunk.id === '') {\n toolCallChunk.id = undefined;\n } else if (\n tool_calls != null &&\n toolCallChunk.id != null &&\n toolCallChunk.name != null\n ) {\n tool_calls.push({\n args: {},\n id: toolCallChunk.id,\n name: toolCallChunk.name,\n type: ToolCallTypes.TOOL_CALL,\n });\n }\n }\n\n let stepId: string = _stepId;\n const alreadyDispatched =\n prevRunStep?.type === StepTypes.MESSAGE_CREATION &&\n graph.messageStepHasToolCalls.has(prevStepId);\n\n if (prevRunStep?.type === StepTypes.TOOL_CALLS) {\n /**\n * If previous step is already a tool_calls step, use that step ID\n * This ensures tool call deltas are dispatched to the correct step\n */\n stepId = prevStepId;\n } else if (\n !alreadyDispatched &&\n prevRunStep?.type === StepTypes.MESSAGE_CREATION\n ) {\n /**\n * Create tool_calls step as soon as we receive the first tool call chunk\n * This ensures deltas are always associated with the correct step\n *\n * NOTE: We do NOT dispatch an empty text block here because:\n * - Empty text blocks cause providers (Anthropic, Bedrock) to reject messages\n * - The tool_calls themselves are sufficient for the step\n * - Empty content with tool_call_ids gets stored in conversation history\n * and causes \"messages must have non-empty content\" errors on replay\n */\n graph.messageStepHasToolCalls.set(prevStepId, true);\n stepId = await graph.dispatchRunStep(\n stepKey,\n {\n type: StepTypes.TOOL_CALLS,\n tool_calls: tool_calls ?? [],\n },\n metadata\n );\n }\n await graph.dispatchRunStepDelta(stepId, {\n type: StepTypes.TOOL_CALLS,\n tool_calls: toolCallChunks,\n });\n}\n\nexport const handleToolCalls = async (\n toolCalls?: ToolCall[],\n metadata?: Record<string, unknown>,\n graph?: StandardGraph | MultiAgentGraph\n): Promise<void> => {\n if (!graph || !metadata) {\n console.warn(`Graph or metadata not found in ${event} event`);\n return;\n }\n\n if (!toolCalls) {\n return;\n }\n\n if (toolCalls.length === 0) {\n return;\n }\n\n const stepKey = graph.getStepKey(metadata);\n\n for (const tool_call of toolCalls) {\n const toolCallId = tool_call.id ?? `toolu_${nanoid()}`;\n tool_call.id = toolCallId;\n if (!toolCallId || graph.toolCallStepIds.has(toolCallId)) {\n continue;\n }\n\n let prevStepId = '';\n let prevRunStep: t.RunStep | undefined;\n try {\n prevStepId = graph.getStepIdByKey(stepKey);\n prevRunStep = graph.getRunStep(prevStepId);\n } catch {\n // no previous step\n }\n\n /**\n * NOTE: We do NOT dispatch empty text blocks with tool_call_ids because:\n * - Empty text blocks cause providers (Anthropic, Bedrock) to reject messages\n * - They get stored in conversation history and cause errors on replay:\n * \"messages must have non-empty content\" (Anthropic)\n * \"The content field in the Message object is empty\" (Bedrock)\n * - The tool_calls themselves are sufficient\n */\n\n /* If the previous step exists and is a message creation */\n if (\n prevStepId &&\n prevRunStep &&\n prevRunStep.type === StepTypes.MESSAGE_CREATION\n ) {\n graph.messageStepHasToolCalls.set(prevStepId, true);\n /* If the previous step doesn't exist or is not a message creation */\n } else if (\n !prevRunStep ||\n prevRunStep.type !== StepTypes.MESSAGE_CREATION\n ) {\n const messageId = getMessageId(stepKey, graph, true) ?? '';\n const stepId = await graph.dispatchRunStep(\n stepKey,\n {\n type: StepTypes.MESSAGE_CREATION,\n message_creation: {\n message_id: messageId,\n },\n },\n metadata\n );\n graph.messageStepHasToolCalls.set(stepId, true);\n }\n\n await graph.dispatchRunStep(\n stepKey,\n {\n type: StepTypes.TOOL_CALLS,\n tool_calls: [tool_call],\n },\n metadata\n );\n }\n};\n\nexport const toolResultTypes = new Set([\n // 'tool_use',\n // 'server_tool_use',\n // 'input_json_delta',\n 'tool_result',\n 'web_search_result',\n 'web_search_tool_result',\n]);\n\n/**\n * Handles the result of a server tool call; in other words, a provider's built-in tool.\n * As of 2025-07-06, only Anthropic handles server tool calls with this pattern.\n */\nexport async function handleServerToolResult({\n graph,\n content,\n metadata,\n agentContext,\n}: {\n graph: StandardGraph | MultiAgentGraph;\n content?: string | t.MessageContentComplex[];\n metadata?: Record<string, unknown>;\n agentContext?: AgentContext;\n}): Promise<boolean> {\n let skipHandling = false;\n if (agentContext?.provider !== Providers.ANTHROPIC) {\n return skipHandling;\n }\n if (\n typeof content === 'string' ||\n content == null ||\n content.length === 0 ||\n (content.length === 1 &&\n (content[0] as t.ToolResultContent).tool_use_id == null)\n ) {\n return skipHandling;\n }\n\n for (const contentPart of content) {\n const toolUseId = (contentPart as t.ToolResultContent).tool_use_id;\n if (toolUseId == null || toolUseId === '') {\n continue;\n }\n const stepId = graph.toolCallStepIds.get(toolUseId);\n if (stepId == null || stepId === '') {\n console.warn(\n `Tool use ID ${toolUseId} not found in graph, cannot dispatch tool result.`\n );\n continue;\n }\n const runStep = graph.getRunStep(stepId);\n if (!runStep) {\n console.warn(\n `Run step for ${stepId} does not exist, cannot dispatch tool result.`\n );\n continue;\n } else if (runStep.type !== StepTypes.TOOL_CALLS) {\n console.warn(\n `Run step for ${stepId} is not a tool call step, cannot dispatch tool result.`\n );\n continue;\n }\n\n const toolCall =\n runStep.stepDetails.type === StepTypes.TOOL_CALLS\n ? (runStep.stepDetails.tool_calls?.find(\n (toolCall) => toolCall.id === toolUseId\n ) as ToolCall)\n : undefined;\n\n if (!toolCall) {\n continue;\n }\n\n if (\n contentPart.type === 'web_search_result' ||\n contentPart.type === 'web_search_tool_result'\n ) {\n await handleAnthropicSearchResults({\n contentPart: contentPart as t.ToolResultContent,\n toolCall,\n metadata,\n graph,\n });\n }\n\n if (!skipHandling) {\n skipHandling = true;\n }\n }\n\n return skipHandling;\n}\n\nasync function handleAnthropicSearchResults({\n contentPart,\n toolCall,\n metadata,\n graph,\n}: {\n contentPart: t.ToolResultContent;\n toolCall: Partial<ToolCall>;\n metadata?: Record<string, unknown>;\n graph: StandardGraph | MultiAgentGraph;\n}): Promise<void> {\n if (!Array.isArray(contentPart.content)) {\n console.warn(\n `Expected content to be an array, got ${typeof contentPart.content}`\n );\n return;\n }\n\n if (!isAnthropicWebSearchResult(contentPart.content[0])) {\n console.warn(\n `Expected content to be an Anthropic web search result, got ${JSON.stringify(\n contentPart.content\n )}`\n );\n return;\n }\n\n const turn = graph.invokedToolIds?.size ?? 0;\n const searchResultData = coerceAnthropicSearchResults({\n turn,\n results: contentPart.content as AnthropicWebSearchResultBlockParam[],\n });\n\n const name = toolCall.name;\n const input = toolCall.args ?? {};\n const artifact = {\n [Constants.WEB_SEARCH]: searchResultData,\n };\n const { output: formattedOutput } = formatResultsForLLM(\n turn,\n searchResultData\n );\n const output = new ToolMessage({\n name,\n artifact,\n content: formattedOutput,\n tool_call_id: toolCall.id!,\n });\n const toolEndData: t.ToolEndData = {\n input,\n output,\n };\n await graph.handlerRegistry\n ?.getHandler(GraphEvents.TOOL_END)\n ?.handle(GraphEvents.TOOL_END, toolEndData, metadata, graph);\n\n if (graph.invokedToolIds == null) {\n graph.invokedToolIds = new Set<string>();\n }\n\n graph.invokedToolIds.add(toolCall.id!);\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"handlers.mjs","sources":["../../../src/tools/handlers.ts"],"sourcesContent":["/* eslint-disable no-console */\n// src/tools/handlers.ts\nimport { nanoid } from 'nanoid';\nimport { ToolMessage } from '@langchain/core/messages';\nimport type { AnthropicWebSearchResultBlockParam } from '@/llm/anthropic/types';\nimport type { ToolCall, ToolCallChunk } from '@langchain/core/messages/tool';\nimport type { MultiAgentGraph, StandardGraph } from '@/graphs';\nimport type { AgentContext } from '@/agents/AgentContext';\nimport type * as t from '@/types';\nimport {\n ToolCallTypes,\n GraphEvents,\n StepTypes,\n Providers,\n Constants,\n} from '@/common';\nimport {\n coerceAnthropicSearchResults,\n isAnthropicWebSearchResult,\n} from '@/tools/search/anthropic';\nimport { formatResultsForLLM } from '@/tools/search/format';\nimport { getMessageId } from '@/messages';\n\nexport async function handleToolCallChunks({\n graph,\n stepKey,\n toolCallChunks,\n metadata,\n}: {\n graph: StandardGraph | MultiAgentGraph;\n stepKey: string;\n toolCallChunks: ToolCallChunk[];\n metadata?: Record<string, unknown>;\n}): Promise<void> {\n let prevStepId: string;\n let prevRunStep: t.RunStep | undefined;\n try {\n prevStepId = graph.getStepIdByKey(stepKey);\n prevRunStep = graph.getRunStep(prevStepId);\n } catch {\n /** Edge Case: If no previous step exists, create a new message creation step */\n const message_id = getMessageId(stepKey, graph, true) ?? '';\n prevStepId = await graph.dispatchRunStep(\n stepKey,\n {\n type: StepTypes.MESSAGE_CREATION,\n message_creation: {\n message_id,\n },\n },\n metadata\n );\n prevRunStep = graph.getRunStep(prevStepId);\n }\n\n const _stepId = graph.getStepIdByKey(stepKey);\n\n /** Edge Case: Tool Call Run Step or `tool_call_ids` never dispatched */\n const tool_calls: ToolCall[] | undefined =\n prevStepId && prevRunStep && prevRunStep.type === StepTypes.MESSAGE_CREATION\n ? []\n : undefined;\n\n /** Edge Case: `id` and `name` fields cannot be empty strings */\n for (const toolCallChunk of toolCallChunks) {\n if (toolCallChunk.name === '') {\n toolCallChunk.name = undefined;\n }\n if (toolCallChunk.id === '') {\n toolCallChunk.id = undefined;\n } else if (\n tool_calls != null &&\n toolCallChunk.id != null &&\n toolCallChunk.name != null\n ) {\n tool_calls.push({\n args: {},\n id: toolCallChunk.id,\n name: toolCallChunk.name,\n type: ToolCallTypes.TOOL_CALL,\n });\n }\n }\n\n let stepId: string = _stepId;\n const alreadyDispatched =\n prevRunStep?.type === StepTypes.MESSAGE_CREATION &&\n graph.messageStepHasToolCalls.has(prevStepId);\n\n if (prevRunStep?.type === StepTypes.TOOL_CALLS) {\n /**\n * If previous step is already a tool_calls step, use that step ID\n * This ensures tool call deltas are dispatched to the correct step\n */\n stepId = prevStepId;\n } else if (\n !alreadyDispatched &&\n prevRunStep?.type === StepTypes.MESSAGE_CREATION\n ) {\n /**\n * Create tool_calls step as soon as we receive the first tool call chunk\n * This ensures deltas are always associated with the correct step\n *\n * NOTE: We do NOT dispatch an empty text block here because:\n * - Empty text blocks cause providers (Anthropic, Bedrock) to reject messages\n * - The tool_calls themselves are sufficient for the step\n * - Empty content with tool_call_ids gets stored in conversation history\n * and causes \"messages must have non-empty content\" errors on replay\n */\n graph.messageStepHasToolCalls.set(prevStepId, true);\n stepId = await graph.dispatchRunStep(\n stepKey,\n {\n type: StepTypes.TOOL_CALLS,\n tool_calls: tool_calls ?? [],\n },\n metadata\n );\n }\n\n await graph.dispatchRunStepDelta(stepId, {\n type: StepTypes.TOOL_CALLS,\n tool_calls: toolCallChunks,\n });\n}\n\nexport const handleToolCalls = async (\n toolCalls?: ToolCall[],\n metadata?: Record<string, unknown>,\n graph?: StandardGraph | MultiAgentGraph\n): Promise<void> => {\n if (!graph || !metadata) {\n console.warn('Graph or metadata not found in `handleToolCalls`');\n return;\n }\n\n if (!toolCalls) {\n return;\n }\n\n if (toolCalls.length === 0) {\n return;\n }\n\n const stepKey = graph.getStepKey(metadata);\n\n /**\n * Track whether we've already reused an empty TOOL_CALLS step created by\n * handleToolCallChunks during streaming. Only reuse it once (for the first\n * tool call); subsequent parallel tool calls must create their own steps.\n */\n let reusedChunkStepId: string | undefined;\n\n for (const tool_call of toolCalls) {\n const toolCallId = tool_call.id ?? `toolu_${nanoid()}`;\n tool_call.id = toolCallId;\n if (!toolCallId || graph.toolCallStepIds.has(toolCallId)) {\n continue;\n }\n\n let prevStepId = '';\n let prevRunStep: t.RunStep | undefined;\n try {\n prevStepId = graph.getStepIdByKey(stepKey);\n prevRunStep = graph.getRunStep(prevStepId);\n } catch {\n // no previous step\n }\n\n /**\n * If the previous step is TOOL_CALLS (from handleToolCallChunks or a prior\n * iteration), either reuse it (if empty) or dispatch a new TOOL_CALLS step\n * directly — skip the intermediate MESSAGE_CREATION to avoid orphaned gaps.\n */\n if (prevRunStep?.type === StepTypes.TOOL_CALLS) {\n const details = prevRunStep.stepDetails as t.ToolCallsDetails;\n const isEmpty = !details.tool_calls || details.tool_calls.length === 0;\n if (isEmpty && prevStepId !== reusedChunkStepId) {\n graph.toolCallStepIds.set(toolCallId, prevStepId);\n reusedChunkStepId = prevStepId;\n continue;\n }\n await graph.dispatchRunStep(\n stepKey,\n { type: StepTypes.TOOL_CALLS, tool_calls: [tool_call] },\n metadata\n );\n continue;\n }\n\n /**\n * NOTE: We do NOT dispatch empty text blocks with tool_call_ids because:\n * - Empty text blocks cause providers (Anthropic, Bedrock) to reject messages\n * - They get stored in conversation history and cause errors on replay:\n * \"messages must have non-empty content\" (Anthropic)\n * \"The content field in the Message object is empty\" (Bedrock)\n * - The tool_calls themselves are sufficient\n */\n if (prevStepId && prevRunStep) {\n graph.messageStepHasToolCalls.set(prevStepId, true);\n } else if (!prevRunStep) {\n const messageId = getMessageId(stepKey, graph, true) ?? '';\n const stepId = await graph.dispatchRunStep(\n stepKey,\n {\n type: StepTypes.MESSAGE_CREATION,\n message_creation: {\n message_id: messageId,\n },\n },\n metadata\n );\n graph.messageStepHasToolCalls.set(stepId, true);\n }\n\n await graph.dispatchRunStep(\n stepKey,\n {\n type: StepTypes.TOOL_CALLS,\n tool_calls: [tool_call],\n },\n metadata\n );\n }\n};\n\nexport const toolResultTypes = new Set([\n // 'tool_use',\n // 'server_tool_use',\n // 'input_json_delta',\n 'tool_result',\n 'web_search_result',\n 'web_search_tool_result',\n]);\n\n/**\n * Handles the result of a server tool call; in other words, a provider's built-in tool.\n * As of 2025-07-06, only Anthropic handles server tool calls with this pattern.\n */\nexport async function handleServerToolResult({\n graph,\n content,\n metadata,\n agentContext,\n}: {\n graph: StandardGraph | MultiAgentGraph;\n content?: string | t.MessageContentComplex[];\n metadata?: Record<string, unknown>;\n agentContext?: AgentContext;\n}): Promise<boolean> {\n let skipHandling = false;\n if (agentContext?.provider !== Providers.ANTHROPIC) {\n return skipHandling;\n }\n if (\n typeof content === 'string' ||\n content == null ||\n content.length === 0 ||\n (content.length === 1 &&\n (content[0] as t.ToolResultContent).tool_use_id == null)\n ) {\n return skipHandling;\n }\n\n for (const contentPart of content) {\n const toolUseId = (contentPart as t.ToolResultContent).tool_use_id;\n if (toolUseId == null || toolUseId === '') {\n continue;\n }\n const stepId = graph.toolCallStepIds.get(toolUseId);\n if (stepId == null || stepId === '') {\n console.warn(\n `Tool use ID ${toolUseId} not found in graph, cannot dispatch tool result.`\n );\n continue;\n }\n const runStep = graph.getRunStep(stepId);\n if (!runStep) {\n console.warn(\n `Run step for ${stepId} does not exist, cannot dispatch tool result.`\n );\n continue;\n } else if (runStep.type !== StepTypes.TOOL_CALLS) {\n console.warn(\n `Run step for ${stepId} is not a tool call step, cannot dispatch tool result.`\n );\n continue;\n }\n\n const toolCall =\n runStep.stepDetails.type === StepTypes.TOOL_CALLS\n ? (runStep.stepDetails.tool_calls?.find(\n (toolCall) => toolCall.id === toolUseId\n ) as ToolCall)\n : undefined;\n\n if (!toolCall) {\n continue;\n }\n\n if (\n contentPart.type === 'web_search_result' ||\n contentPart.type === 'web_search_tool_result'\n ) {\n await handleAnthropicSearchResults({\n contentPart: contentPart as t.ToolResultContent,\n toolCall,\n metadata,\n graph,\n });\n }\n\n if (!skipHandling) {\n skipHandling = true;\n }\n }\n\n return skipHandling;\n}\n\nasync function handleAnthropicSearchResults({\n contentPart,\n toolCall,\n metadata,\n graph,\n}: {\n contentPart: t.ToolResultContent;\n toolCall: Partial<ToolCall>;\n metadata?: Record<string, unknown>;\n graph: StandardGraph | MultiAgentGraph;\n}): Promise<void> {\n if (!Array.isArray(contentPart.content)) {\n console.warn(\n `Expected content to be an array, got ${typeof contentPart.content}`\n );\n return;\n }\n\n if (!isAnthropicWebSearchResult(contentPart.content[0])) {\n console.warn(\n `Expected content to be an Anthropic web search result, got ${JSON.stringify(\n contentPart.content\n )}`\n );\n return;\n }\n\n const turn = graph.invokedToolIds?.size ?? 0;\n const searchResultData = coerceAnthropicSearchResults({\n turn,\n results: contentPart.content as AnthropicWebSearchResultBlockParam[],\n });\n\n const name = toolCall.name;\n const input = toolCall.args ?? {};\n const artifact = {\n [Constants.WEB_SEARCH]: searchResultData,\n };\n const { output: formattedOutput } = formatResultsForLLM(\n turn,\n searchResultData\n );\n const output = new ToolMessage({\n name,\n artifact,\n content: formattedOutput,\n tool_call_id: toolCall.id!,\n });\n const toolEndData: t.ToolEndData = {\n input,\n output,\n };\n await graph.handlerRegistry\n ?.getHandler(GraphEvents.TOOL_END)\n ?.handle(GraphEvents.TOOL_END, toolEndData, metadata, graph);\n\n if (graph.invokedToolIds == null) {\n graph.invokedToolIds = new Set<string>();\n }\n\n graph.invokedToolIds.add(toolCall.id!);\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -64,6 +64,8 @@ export declare class AgentContext {
|
|
|
64
64
|
lastToken?: string;
|
|
65
65
|
/** Token type switch state */
|
|
66
66
|
tokenTypeSwitch?: 'reasoning' | 'content';
|
|
67
|
+
/** Tracks how many reasoning→text transitions have occurred (ensures unique post-reasoning step keys) */
|
|
68
|
+
reasoningTransitionCount: number;
|
|
67
69
|
/** Current token type being processed */
|
|
68
70
|
currentTokenType: ContentTypes.TEXT | ContentTypes.THINK | 'think_and_text';
|
|
69
71
|
/** Whether tools should end the workflow */
|
package/package.json
CHANGED
|
@@ -142,6 +142,8 @@ export class AgentContext {
|
|
|
142
142
|
lastToken?: string;
|
|
143
143
|
/** Token type switch state */
|
|
144
144
|
tokenTypeSwitch?: 'reasoning' | 'content';
|
|
145
|
+
/** Tracks how many reasoning→text transitions have occurred (ensures unique post-reasoning step keys) */
|
|
146
|
+
reasoningTransitionCount = 0;
|
|
145
147
|
/** Current token type being processed */
|
|
146
148
|
currentTokenType: ContentTypes.TEXT | ContentTypes.THINK | 'think_and_text' =
|
|
147
149
|
ContentTypes.TEXT;
|
|
@@ -462,6 +464,7 @@ export class AgentContext {
|
|
|
462
464
|
this.pruneMessages = undefined;
|
|
463
465
|
this.lastStreamCall = undefined;
|
|
464
466
|
this.tokenTypeSwitch = undefined;
|
|
467
|
+
this.reasoningTransitionCount = 0;
|
|
465
468
|
this.currentTokenType = ContentTypes.TEXT;
|
|
466
469
|
this.discoveredToolNames.clear();
|
|
467
470
|
this.handoffContext = undefined;
|
package/src/graphs/Graph.ts
CHANGED
|
@@ -314,7 +314,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
314
314
|
) {
|
|
315
315
|
keyList.push('reasoning');
|
|
316
316
|
} else if (agentContext.tokenTypeSwitch === 'content') {
|
|
317
|
-
keyList.push(
|
|
317
|
+
keyList.push(`post-reasoning-${agentContext.reasoningTransitionCount}`);
|
|
318
318
|
}
|
|
319
319
|
|
|
320
320
|
if (this.invokedToolIds != null && this.invokedToolIds.size > 0) {
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
import { config } from 'dotenv';
|
|
2
|
+
config();
|
|
3
|
+
import { HumanMessage, BaseMessage } from '@langchain/core/messages';
|
|
4
|
+
import type { UsageMetadata } from '@langchain/core/messages';
|
|
5
|
+
import * as t from '@/types';
|
|
6
|
+
import { ChatModelStreamHandler, createContentAggregator } from '@/stream';
|
|
7
|
+
import { createCodeExecutionTool } from '@/tools/CodeExecutor';
|
|
8
|
+
import { ToolEndHandler, ModelEndHandler } from '@/events';
|
|
9
|
+
import { GraphEvents, ContentTypes, Providers } from '@/common';
|
|
10
|
+
import { getLLMConfig } from '@/utils/llmConfig';
|
|
11
|
+
import { Run } from '@/run';
|
|
12
|
+
|
|
13
|
+
const conversationHistory: BaseMessage[] = [];
|
|
14
|
+
let _contentParts: t.MessageContentComplex[] = [];
|
|
15
|
+
const collectedUsage: UsageMetadata[] = [];
|
|
16
|
+
|
|
17
|
+
async function testBedrockContentAggregation(): Promise<void> {
|
|
18
|
+
const instructions =
|
|
19
|
+
'You are a helpful AI assistant with coding capabilities. When answering questions, be thorough in your reasoning.';
|
|
20
|
+
const { contentParts, aggregateContent } = createContentAggregator();
|
|
21
|
+
_contentParts = contentParts as t.MessageContentComplex[];
|
|
22
|
+
|
|
23
|
+
const customHandlers = {
|
|
24
|
+
[GraphEvents.TOOL_END]: new ToolEndHandler(),
|
|
25
|
+
[GraphEvents.CHAT_MODEL_END]: new ModelEndHandler(collectedUsage),
|
|
26
|
+
[GraphEvents.CHAT_MODEL_STREAM]: new ChatModelStreamHandler(),
|
|
27
|
+
[GraphEvents.ON_RUN_STEP_COMPLETED]: {
|
|
28
|
+
handle: (
|
|
29
|
+
event: GraphEvents.ON_RUN_STEP_COMPLETED,
|
|
30
|
+
data: t.StreamEventData
|
|
31
|
+
): void => {
|
|
32
|
+
const result = (data as unknown as { result: t.ToolEndEvent }).result;
|
|
33
|
+
console.log(
|
|
34
|
+
`[ON_RUN_STEP_COMPLETED] stepId=${result.id} index=${result.index} type=${result.type} tool=${result.tool_call?.name ?? 'n/a'}`
|
|
35
|
+
);
|
|
36
|
+
aggregateContent({
|
|
37
|
+
event,
|
|
38
|
+
data: data as unknown as { result: t.ToolEndEvent },
|
|
39
|
+
});
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
[GraphEvents.ON_RUN_STEP]: {
|
|
43
|
+
handle: (event: GraphEvents.ON_RUN_STEP, data: t.RunStep) => {
|
|
44
|
+
const toolCalls =
|
|
45
|
+
data.stepDetails.type === 'tool_calls' && data.stepDetails.tool_calls
|
|
46
|
+
? (
|
|
47
|
+
data.stepDetails.tool_calls as Array<{
|
|
48
|
+
name?: string;
|
|
49
|
+
id?: string;
|
|
50
|
+
}>
|
|
51
|
+
)
|
|
52
|
+
.map((tc) => `${tc.name ?? '?'}(${tc.id ?? '?'})`)
|
|
53
|
+
.join(', ')
|
|
54
|
+
: 'none';
|
|
55
|
+
console.log(
|
|
56
|
+
`[ON_RUN_STEP] stepId=${data.id} index=${data.index} type=${data.type} stepIndex=${data.stepIndex} toolCalls=[${toolCalls}]`
|
|
57
|
+
);
|
|
58
|
+
aggregateContent({ event, data });
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
[GraphEvents.ON_RUN_STEP_DELTA]: {
|
|
62
|
+
handle: (
|
|
63
|
+
event: GraphEvents.ON_RUN_STEP_DELTA,
|
|
64
|
+
data: t.RunStepDeltaEvent
|
|
65
|
+
) => {
|
|
66
|
+
const tcNames =
|
|
67
|
+
data.delta.tool_calls
|
|
68
|
+
?.map(
|
|
69
|
+
(tc) =>
|
|
70
|
+
`${tc.name ?? '?'}(args=${(tc.args ?? '').substring(0, 30)}...)`
|
|
71
|
+
)
|
|
72
|
+
.join(', ') ?? 'none';
|
|
73
|
+
console.log(
|
|
74
|
+
`[ON_RUN_STEP_DELTA] stepId=${data.id} type=${data.delta.type} toolCalls=[${tcNames}]`
|
|
75
|
+
);
|
|
76
|
+
aggregateContent({ event, data });
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
[GraphEvents.ON_MESSAGE_DELTA]: {
|
|
80
|
+
handle: (
|
|
81
|
+
event: GraphEvents.ON_MESSAGE_DELTA,
|
|
82
|
+
data: t.MessageDeltaEvent
|
|
83
|
+
) => {
|
|
84
|
+
const preview = Array.isArray(data.delta.content)
|
|
85
|
+
? data.delta.content
|
|
86
|
+
.map(
|
|
87
|
+
(c) =>
|
|
88
|
+
`${c.type}:"${String((c as Record<string, unknown>).text ?? (c as Record<string, unknown>).think ?? '').substring(0, 40)}"`
|
|
89
|
+
)
|
|
90
|
+
.join(', ')
|
|
91
|
+
: String(data.delta.content).substring(0, 40);
|
|
92
|
+
console.log(
|
|
93
|
+
`[ON_MESSAGE_DELTA] stepId=${data.id} content=[${preview}]`
|
|
94
|
+
);
|
|
95
|
+
aggregateContent({ event, data });
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
[GraphEvents.ON_REASONING_DELTA]: {
|
|
99
|
+
handle: (
|
|
100
|
+
event: GraphEvents.ON_REASONING_DELTA,
|
|
101
|
+
data: t.ReasoningDeltaEvent
|
|
102
|
+
) => {
|
|
103
|
+
const preview = Array.isArray(data.delta.content)
|
|
104
|
+
? data.delta.content
|
|
105
|
+
.map(
|
|
106
|
+
(c) =>
|
|
107
|
+
`${c.type}:"${String((c as Record<string, unknown>).think ?? '').substring(0, 40)}"`
|
|
108
|
+
)
|
|
109
|
+
.join(', ')
|
|
110
|
+
: '?';
|
|
111
|
+
console.log(
|
|
112
|
+
`[ON_REASONING_DELTA] stepId=${data.id} content=[${preview}]`
|
|
113
|
+
);
|
|
114
|
+
aggregateContent({ event, data });
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const baseLlmConfig = getLLMConfig(Providers.BEDROCK);
|
|
120
|
+
|
|
121
|
+
const llmConfig = {
|
|
122
|
+
...baseLlmConfig,
|
|
123
|
+
model: 'global.anthropic.claude-opus-4-6-v1',
|
|
124
|
+
maxTokens: 16000,
|
|
125
|
+
additionalModelRequestFields: {
|
|
126
|
+
thinking: { type: 'enabled', budget_tokens: 10000 },
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const run = await Run.create<t.IState>({
|
|
131
|
+
runId: 'bedrock-content-aggregation-test',
|
|
132
|
+
graphConfig: {
|
|
133
|
+
instructions,
|
|
134
|
+
type: 'standard',
|
|
135
|
+
tools: [createCodeExecutionTool()],
|
|
136
|
+
llmConfig,
|
|
137
|
+
},
|
|
138
|
+
returnContent: true,
|
|
139
|
+
customHandlers: customHandlers as t.RunConfig['customHandlers'],
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
const streamConfig = {
|
|
143
|
+
configurable: {
|
|
144
|
+
thread_id: 'bedrock-content-aggregation-thread',
|
|
145
|
+
},
|
|
146
|
+
streamMode: 'values',
|
|
147
|
+
version: 'v2' as const,
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
const userMessage = `im testing edge cases with our code interpreter. i know we can persist files, but what happens when we put them in directories?`;
|
|
151
|
+
conversationHistory.push(new HumanMessage(userMessage));
|
|
152
|
+
|
|
153
|
+
console.log('Running Bedrock content aggregation test...\n');
|
|
154
|
+
console.log(`Prompt: "${userMessage}"\n`);
|
|
155
|
+
|
|
156
|
+
const inputs = { messages: [...conversationHistory] };
|
|
157
|
+
await run.processStream(inputs, streamConfig);
|
|
158
|
+
|
|
159
|
+
console.log('\n\n========== CONTENT PARTS ANALYSIS ==========\n');
|
|
160
|
+
|
|
161
|
+
let hasEmptyToolCall = false;
|
|
162
|
+
let hasReasoningOrderIssue = false;
|
|
163
|
+
|
|
164
|
+
for (let i = 0; i < _contentParts.length; i++) {
|
|
165
|
+
const part = _contentParts[i];
|
|
166
|
+
if (!part) {
|
|
167
|
+
console.log(` [${i}] undefined`);
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const partType = part.type;
|
|
172
|
+
if (partType === ContentTypes.TOOL_CALL) {
|
|
173
|
+
const tc = (part as t.ToolCallContent).tool_call;
|
|
174
|
+
if (!tc || !tc.name) {
|
|
175
|
+
hasEmptyToolCall = true;
|
|
176
|
+
console.log(` [${i}] TOOL_CALL *** EMPTY (no tool_call data) ***`);
|
|
177
|
+
} else {
|
|
178
|
+
const outputPreview = tc.output
|
|
179
|
+
? `output=${(tc.output as string).substring(0, 80)}...`
|
|
180
|
+
: 'no output';
|
|
181
|
+
console.log(` [${i}] TOOL_CALL name=${tc.name} ${outputPreview}`);
|
|
182
|
+
}
|
|
183
|
+
} else if (partType === ContentTypes.THINK) {
|
|
184
|
+
const think = (part as t.ReasoningContentText).think ?? '';
|
|
185
|
+
console.log(
|
|
186
|
+
` [${i}] THINK (${think.length} chars): "${think.substring(0, 80)}..."`
|
|
187
|
+
);
|
|
188
|
+
} else if (partType === ContentTypes.TEXT) {
|
|
189
|
+
const text = (part as t.MessageDeltaUpdate).text ?? '';
|
|
190
|
+
console.log(
|
|
191
|
+
` [${i}] TEXT (${text.length} chars): "${text.substring(0, 80)}..."`
|
|
192
|
+
);
|
|
193
|
+
} else {
|
|
194
|
+
console.log(` [${i}] ${partType}`);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Check reasoning ordering within a single invocation cycle.
|
|
200
|
+
* A tool_call resets the cycle — text before think across different
|
|
201
|
+
* invocations (e.g., text from invocation 2, think from invocation 3) is valid.
|
|
202
|
+
*/
|
|
203
|
+
let lastTextInCycle: number | null = null;
|
|
204
|
+
for (let i = 0; i < _contentParts.length; i++) {
|
|
205
|
+
const part = _contentParts[i];
|
|
206
|
+
if (!part) continue;
|
|
207
|
+
|
|
208
|
+
if (part.type === ContentTypes.TOOL_CALL) {
|
|
209
|
+
lastTextInCycle = null;
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (part.type === ContentTypes.TEXT) {
|
|
214
|
+
lastTextInCycle = i;
|
|
215
|
+
} else if (part.type === ContentTypes.THINK && lastTextInCycle !== null) {
|
|
216
|
+
const prevText = _contentParts[lastTextInCycle] as t.MessageDeltaUpdate;
|
|
217
|
+
const thinkContent = (part as t.ReasoningContentText).think ?? '';
|
|
218
|
+
if (
|
|
219
|
+
prevText?.text &&
|
|
220
|
+
prevText.text.trim().length > 5 &&
|
|
221
|
+
thinkContent.length > 0
|
|
222
|
+
) {
|
|
223
|
+
hasReasoningOrderIssue = true;
|
|
224
|
+
console.log(
|
|
225
|
+
`\n *** ORDERING ISSUE (same invocation): TEXT at [${lastTextInCycle}] appears before THINK at [${i}]`
|
|
226
|
+
);
|
|
227
|
+
console.log(
|
|
228
|
+
` Text ends with: "...${prevText.text.substring(prevText.text.length - 60)}"`
|
|
229
|
+
);
|
|
230
|
+
console.log(
|
|
231
|
+
` Think starts with: "${thinkContent.substring(0, 60)}..."`
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
console.log('\n========== SUMMARY ==========\n');
|
|
238
|
+
console.log(`Total content parts: ${_contentParts.filter(Boolean).length}`);
|
|
239
|
+
console.log(
|
|
240
|
+
`Empty tool_call parts: ${hasEmptyToolCall ? 'YES (BUG)' : 'No'}`
|
|
241
|
+
);
|
|
242
|
+
console.log(
|
|
243
|
+
`Reasoning order issues: ${hasReasoningOrderIssue ? 'YES (BUG)' : 'No'}`
|
|
244
|
+
);
|
|
245
|
+
console.log('\nFull contentParts dump:');
|
|
246
|
+
console.dir(_contentParts, { depth: null });
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
process.on('unhandledRejection', (reason, promise) => {
|
|
250
|
+
console.error('Unhandled Rejection at:', promise, 'reason:', reason);
|
|
251
|
+
console.log('Content parts:');
|
|
252
|
+
console.dir(_contentParts, { depth: null });
|
|
253
|
+
process.exit(1);
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
process.on('uncaughtException', (err) => {
|
|
257
|
+
console.error('Uncaught Exception:', err);
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
testBedrockContentAggregation().catch((err) => {
|
|
261
|
+
console.error(err);
|
|
262
|
+
console.log('Content parts:');
|
|
263
|
+
console.dir(_contentParts, { depth: null });
|
|
264
|
+
process.exit(1);
|
|
265
|
+
});
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import { config } from 'dotenv';
|
|
2
|
+
config();
|
|
3
|
+
import { HumanMessage, BaseMessage } from '@langchain/core/messages';
|
|
4
|
+
import type { UsageMetadata } from '@langchain/core/messages';
|
|
5
|
+
import type { StandardGraph } from '@/graphs';
|
|
6
|
+
import * as t from '@/types';
|
|
7
|
+
import { ChatModelStreamHandler, createContentAggregator } from '@/stream';
|
|
8
|
+
import { GraphEvents, ContentTypes, Providers } from '@/common';
|
|
9
|
+
import { ToolEndHandler, ModelEndHandler } from '@/events';
|
|
10
|
+
import { getLLMConfig } from '@/utils/llmConfig';
|
|
11
|
+
import { Calculator } from '@/tools/Calculator';
|
|
12
|
+
import { Run } from '@/run';
|
|
13
|
+
|
|
14
|
+
const conversationHistory: BaseMessage[] = [];
|
|
15
|
+
let _contentParts: t.MessageContentComplex[] = [];
|
|
16
|
+
const collectedUsage: UsageMetadata[] = [];
|
|
17
|
+
|
|
18
|
+
async function testParallelToolCalls(): Promise<void> {
|
|
19
|
+
const { contentParts, aggregateContent } = createContentAggregator();
|
|
20
|
+
_contentParts = contentParts as t.MessageContentComplex[];
|
|
21
|
+
|
|
22
|
+
const customHandlers = {
|
|
23
|
+
[GraphEvents.TOOL_END]: new ToolEndHandler(),
|
|
24
|
+
[GraphEvents.CHAT_MODEL_END]: new ModelEndHandler(collectedUsage),
|
|
25
|
+
[GraphEvents.CHAT_MODEL_STREAM]: {
|
|
26
|
+
handle: async (
|
|
27
|
+
event: string,
|
|
28
|
+
data: t.StreamEventData,
|
|
29
|
+
metadata?: Record<string, unknown>,
|
|
30
|
+
graph?: unknown
|
|
31
|
+
): Promise<void> => {
|
|
32
|
+
const chunk = data.chunk as Record<string, unknown> | undefined;
|
|
33
|
+
const tcc = chunk?.tool_call_chunks as
|
|
34
|
+
| Array<{ id?: string; name?: string; index?: number }>
|
|
35
|
+
| undefined;
|
|
36
|
+
if (tcc && tcc.length > 0) {
|
|
37
|
+
console.log(
|
|
38
|
+
`[CHAT_MODEL_STREAM] tool_call_chunks: ${JSON.stringify(tcc.map((c) => ({ id: c.id, name: c.name, index: c.index })))}`
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
const handler = new ChatModelStreamHandler();
|
|
42
|
+
return handler.handle(event, data, metadata, graph as StandardGraph);
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
[GraphEvents.ON_RUN_STEP_COMPLETED]: {
|
|
46
|
+
handle: (
|
|
47
|
+
event: GraphEvents.ON_RUN_STEP_COMPLETED,
|
|
48
|
+
data: t.StreamEventData
|
|
49
|
+
): void => {
|
|
50
|
+
const result = (data as unknown as { result: t.ToolEndEvent }).result;
|
|
51
|
+
console.log(
|
|
52
|
+
`[ON_RUN_STEP_COMPLETED] stepId=${result.id} index=${result.index} type=${result.type} tool=${result.tool_call?.name ?? 'n/a'}`
|
|
53
|
+
);
|
|
54
|
+
aggregateContent({
|
|
55
|
+
event,
|
|
56
|
+
data: data as unknown as { result: t.ToolEndEvent },
|
|
57
|
+
});
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
[GraphEvents.ON_RUN_STEP]: {
|
|
61
|
+
handle: (event: GraphEvents.ON_RUN_STEP, data: t.RunStep) => {
|
|
62
|
+
const toolCalls =
|
|
63
|
+
data.stepDetails.type === 'tool_calls' && data.stepDetails.tool_calls
|
|
64
|
+
? (
|
|
65
|
+
data.stepDetails.tool_calls as Array<{
|
|
66
|
+
name?: string;
|
|
67
|
+
id?: string;
|
|
68
|
+
}>
|
|
69
|
+
)
|
|
70
|
+
.map((tc) => `${tc.name ?? '?'}(${tc.id ?? '?'})`)
|
|
71
|
+
.join(', ')
|
|
72
|
+
: 'none';
|
|
73
|
+
console.log(
|
|
74
|
+
`[ON_RUN_STEP] stepId=${data.id} index=${data.index} type=${data.type} stepIndex=${data.stepIndex} toolCalls=[${toolCalls}]`
|
|
75
|
+
);
|
|
76
|
+
aggregateContent({ event, data });
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
[GraphEvents.ON_RUN_STEP_DELTA]: {
|
|
80
|
+
handle: (
|
|
81
|
+
event: GraphEvents.ON_RUN_STEP_DELTA,
|
|
82
|
+
data: t.RunStepDeltaEvent
|
|
83
|
+
) => {
|
|
84
|
+
aggregateContent({ event, data });
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
[GraphEvents.ON_MESSAGE_DELTA]: {
|
|
88
|
+
handle: (
|
|
89
|
+
event: GraphEvents.ON_MESSAGE_DELTA,
|
|
90
|
+
data: t.MessageDeltaEvent
|
|
91
|
+
) => {
|
|
92
|
+
aggregateContent({ event, data });
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
[GraphEvents.ON_REASONING_DELTA]: {
|
|
96
|
+
handle: (
|
|
97
|
+
event: GraphEvents.ON_REASONING_DELTA,
|
|
98
|
+
data: t.ReasoningDeltaEvent
|
|
99
|
+
) => {
|
|
100
|
+
aggregateContent({ event, data });
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const baseLlmConfig = getLLMConfig(Providers.BEDROCK);
|
|
106
|
+
|
|
107
|
+
const llmConfig = {
|
|
108
|
+
...baseLlmConfig,
|
|
109
|
+
model: 'global.anthropic.claude-opus-4-6-v1',
|
|
110
|
+
maxTokens: 16000,
|
|
111
|
+
additionalModelRequestFields: {
|
|
112
|
+
thinking: { type: 'enabled', budget_tokens: 10000 },
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const run = await Run.create<t.IState>({
|
|
117
|
+
runId: 'bedrock-parallel-tools-test',
|
|
118
|
+
graphConfig: {
|
|
119
|
+
instructions:
|
|
120
|
+
'You are a math assistant. When asked to calculate multiple things, use the calculator tool for ALL of them in parallel. Do NOT chain calculations sequentially.',
|
|
121
|
+
type: 'standard',
|
|
122
|
+
tools: [new Calculator()],
|
|
123
|
+
llmConfig,
|
|
124
|
+
},
|
|
125
|
+
returnContent: true,
|
|
126
|
+
customHandlers: customHandlers as t.RunConfig['customHandlers'],
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
const streamConfig = {
|
|
130
|
+
configurable: { thread_id: 'bedrock-parallel-tools-thread' },
|
|
131
|
+
streamMode: 'values',
|
|
132
|
+
version: 'v2' as const,
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
const userMessage =
|
|
136
|
+
'Calculate these 3 things at the same time using the calculator: 1) 123 * 456, 2) sqrt(144) + 7, 3) 2^10 - 24';
|
|
137
|
+
conversationHistory.push(new HumanMessage(userMessage));
|
|
138
|
+
|
|
139
|
+
console.log('Running Bedrock parallel tool calls test...\n');
|
|
140
|
+
console.log(`Prompt: "${userMessage}"\n`);
|
|
141
|
+
|
|
142
|
+
const inputs = { messages: [...conversationHistory] };
|
|
143
|
+
await run.processStream(inputs, streamConfig);
|
|
144
|
+
|
|
145
|
+
console.log('\n\n========== ANALYSIS ==========\n');
|
|
146
|
+
|
|
147
|
+
let toolCallCount = 0;
|
|
148
|
+
const toolCallNames: string[] = [];
|
|
149
|
+
let hasUndefined = false;
|
|
150
|
+
|
|
151
|
+
for (let i = 0; i < _contentParts.length; i++) {
|
|
152
|
+
const part = _contentParts[i];
|
|
153
|
+
if (!part) {
|
|
154
|
+
hasUndefined = true;
|
|
155
|
+
console.log(` [${i}] *** UNDEFINED ***`);
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
if (part.type === ContentTypes.TOOL_CALL) {
|
|
159
|
+
toolCallCount++;
|
|
160
|
+
const tc = (part as t.ToolCallContent).tool_call;
|
|
161
|
+
const hasData = tc && tc.name;
|
|
162
|
+
if (!hasData) {
|
|
163
|
+
console.log(` [${i}] TOOL_CALL *** EMPTY ***`);
|
|
164
|
+
} else {
|
|
165
|
+
toolCallNames.push(tc.name ?? '');
|
|
166
|
+
console.log(
|
|
167
|
+
` [${i}] TOOL_CALL name=${tc.name} id=${tc.id} output=${String(tc.output ?? '').substring(0, 40)}`
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
} else if (part.type === ContentTypes.THINK) {
|
|
171
|
+
const think = (part as t.ReasoningContentText).think ?? '';
|
|
172
|
+
console.log(` [${i}] THINK (${think.length} chars)`);
|
|
173
|
+
} else if (part.type === ContentTypes.TEXT) {
|
|
174
|
+
const text = (part as t.MessageDeltaUpdate).text ?? '';
|
|
175
|
+
console.log(
|
|
176
|
+
` [${i}] TEXT (${text.length} chars): "${text.substring(0, 80)}..."`
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
console.log('\n========== SUMMARY ==========\n');
|
|
182
|
+
console.log(`Total content parts: ${_contentParts.filter(Boolean).length}`);
|
|
183
|
+
console.log(`Tool calls found: ${toolCallCount}`);
|
|
184
|
+
console.log(`Tool call names: [${toolCallNames.join(', ')}]`);
|
|
185
|
+
console.log(`Undefined gaps: ${hasUndefined ? 'YES (BUG)' : 'No'}`);
|
|
186
|
+
console.log(
|
|
187
|
+
`Expected 3 tool calls: ${toolCallCount >= 3 ? 'PASS' : 'FAIL (only ' + toolCallCount + ')'}`
|
|
188
|
+
);
|
|
189
|
+
console.log('\nFull contentParts dump:');
|
|
190
|
+
console.dir(_contentParts, { depth: null });
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
process.on('unhandledRejection', (reason, promise) => {
|
|
194
|
+
console.error('Unhandled Rejection at:', promise, 'reason:', reason);
|
|
195
|
+
console.dir(_contentParts, { depth: null });
|
|
196
|
+
process.exit(1);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
testParallelToolCalls().catch((err) => {
|
|
200
|
+
console.error(err);
|
|
201
|
+
console.dir(_contentParts, { depth: null });
|
|
202
|
+
process.exit(1);
|
|
203
|
+
});
|
package/src/scripts/tools.ts
CHANGED
|
@@ -25,16 +25,7 @@ async function testStandardStreaming(): Promise<void> {
|
|
|
25
25
|
return true;
|
|
26
26
|
}
|
|
27
27
|
),
|
|
28
|
-
[GraphEvents.CHAT_MODEL_END]:
|
|
29
|
-
handle: (
|
|
30
|
-
_event: string,
|
|
31
|
-
_data: t.StreamEventData,
|
|
32
|
-
metadata?: Record<string, unknown>
|
|
33
|
-
): void => {
|
|
34
|
-
console.log('\n====== CHAT_MODEL_END METADATA ======');
|
|
35
|
-
console.dir(metadata, { depth: null });
|
|
36
|
-
},
|
|
37
|
-
},
|
|
28
|
+
[GraphEvents.CHAT_MODEL_END]: new ModelEndHandler(),
|
|
38
29
|
[GraphEvents.CHAT_MODEL_START]: {
|
|
39
30
|
handle: (
|
|
40
31
|
_event: string,
|
|
@@ -158,9 +149,9 @@ async function testStandardStreaming(): Promise<void> {
|
|
|
158
149
|
conversationHistory.push(...finalMessages);
|
|
159
150
|
console.dir(conversationHistory, { depth: null });
|
|
160
151
|
}
|
|
161
|
-
console.dir(finalContentParts, { depth: null });
|
|
152
|
+
// console.dir(finalContentParts, { depth: null });
|
|
162
153
|
console.log('\n\n====================\n\n');
|
|
163
|
-
|
|
154
|
+
console.dir(contentParts, { depth: null });
|
|
164
155
|
}
|
|
165
156
|
|
|
166
157
|
process.on('unhandledRejection', (reason, promise) => {
|
package/src/stream.ts
CHANGED
|
@@ -411,6 +411,7 @@ hasToolCallChunks: ${hasToolCallChunks}
|
|
|
411
411
|
) {
|
|
412
412
|
agentContext.currentTokenType = ContentTypes.TEXT;
|
|
413
413
|
agentContext.tokenTypeSwitch = 'content';
|
|
414
|
+
agentContext.reasoningTransitionCount++;
|
|
414
415
|
} else if (
|
|
415
416
|
chunk.content != null &&
|
|
416
417
|
typeof chunk.content === 'string' &&
|
|
@@ -465,7 +466,7 @@ export function createContentAggregator(): t.ContentAggregatorResult {
|
|
|
465
466
|
return;
|
|
466
467
|
}
|
|
467
468
|
|
|
468
|
-
if (!contentParts[index]) {
|
|
469
|
+
if (!contentParts[index] && partType !== ContentTypes.TOOL_CALL) {
|
|
469
470
|
contentParts[index] = { type: partType };
|
|
470
471
|
}
|
|
471
472
|
|