🧰 fix: Unprocessed Tool Calls Edge Case (#10440)
Some checks are pending
Docker Dev Branch Images Build / build (Dockerfile, lc-dev, node) (push) Waiting to run
Docker Dev Branch Images Build / build (Dockerfile.multi, lc-dev-api, api-build) (push) Waiting to run

* chore: temp. remove @librechat/agents

* 🔧 chore: update @langchain/core to version 0.3.79

* chore: update dependencies for @langchain/core and add back latest @librechat/agents

* chore: update @librechat/agents to version 3.0.11

* fix: enhance error handling for uncaught exceptions due to abort errors

* fix: standardize warning message for uncatchable abort errors

* fix: improve tool call handling in ModelEndHandler for unprocessed edge case

* fix: prevent content type mismatch in message updates and preserve args in final updates

* chore: add debug logging for client disposal in disposeClient function
This commit is contained in:
Danny Avila 2025-11-10 17:12:06 -05:00 committed by GitHub
parent 09c309bc78
commit 06c060b983
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 910 additions and 197 deletions

View file

@ -43,11 +43,11 @@
"@google/generative-ai": "^0.24.0", "@google/generative-ai": "^0.24.0",
"@googleapis/youtube": "^20.0.0", "@googleapis/youtube": "^20.0.0",
"@keyv/redis": "^4.3.3", "@keyv/redis": "^4.3.3",
"@langchain/core": "^0.3.72", "@langchain/core": "^0.3.79",
"@langchain/google-genai": "^0.2.13", "@langchain/google-genai": "^0.2.13",
"@langchain/google-vertexai": "^0.2.13", "@langchain/google-vertexai": "^0.2.13",
"@langchain/textsplitters": "^0.1.0", "@langchain/textsplitters": "^0.1.0",
"@librechat/agents": "^3.0.5", "@librechat/agents": "^3.0.11",
"@librechat/api": "*", "@librechat/api": "*",
"@librechat/data-schemas": "*", "@librechat/data-schemas": "*",
"@microsoft/microsoft-graph-client": "^3.0.7", "@microsoft/microsoft-graph-client": "^3.0.7",

View file

@ -376,6 +376,8 @@ function disposeClient(client) {
client.options = null; client.options = null;
} catch { } catch {
// Ignore errors during disposal // Ignore errors during disposal
} finally {
logger.debug('[disposeClient] Client disposed');
} }
} }

View file

@ -42,11 +42,22 @@ class ModelEndHandler {
try { try {
const agentContext = graph.getAgentContext(metadata); const agentContext = graph.getAgentContext(metadata);
if ( const isGoogle = agentContext.provider === Providers.GOOGLE;
agentContext.provider === Providers.GOOGLE || const streamingDisabled = !!agentContext.clientOptions?.disableStreaming;
agentContext.clientOptions?.disableStreaming
) { const toolCalls = data?.output?.tool_calls;
handleToolCalls(data?.output?.tool_calls, metadata, graph); let hasUnprocessedToolCalls = false;
if (Array.isArray(toolCalls) && toolCalls.length > 0 && graph?.toolCallStepIds?.has) {
try {
hasUnprocessedToolCalls = toolCalls.some(
(tc) => tc?.id && !graph.toolCallStepIds.has(tc.id),
);
} catch {
hasUnprocessedToolCalls = false;
}
}
if (isGoogle || streamingDisabled || hasUnprocessedToolCalls) {
handleToolCalls(toolCalls, metadata, graph);
} }
const usage = data?.output?.usage_metadata; const usage = data?.output?.usage_metadata;
@ -59,7 +70,6 @@ class ModelEndHandler {
} }
this.collectedUsage.push(usage); this.collectedUsage.push(usage);
const streamingDisabled = !!agentContext.clientOptions?.disableStreaming;
if (!streamingDisabled) { if (!streamingDisabled) {
return; return;
} }

View file

@ -185,8 +185,8 @@ process.on('uncaughtException', (err) => {
logger.error('There was an uncaught error:', err); logger.error('There was an uncaught error:', err);
} }
if (err.message.includes('abort')) { if (err.message && err.message?.toLowerCase()?.includes('abort')) {
logger.warn('There was an uncatchable AbortController error.'); logger.warn('There was an uncatchable abort error.');
return; return;
} }

View file

@ -99,6 +99,12 @@ export default function useStepHandler({
if (!updatedContent[index]) { if (!updatedContent[index]) {
updatedContent[index] = { type: contentPart.type as AllContentTypes }; updatedContent[index] = { type: contentPart.type as AllContentTypes };
} }
/** Prevent overwriting an existing content part with a different type */
const existingType = (updatedContent[index]?.type as string | undefined) ?? '';
if (existingType && !contentType.startsWith(existingType)) {
console.warn('Content type mismatch');
return message;
}
if ( if (
contentType.startsWith(ContentTypes.TEXT) && contentType.startsWith(ContentTypes.TEXT) &&
@ -151,12 +157,16 @@ export default function useStepHandler({
const existingToolCall = existingContent?.tool_call; const existingToolCall = existingContent?.tool_call;
const toolCallArgs = (contentPart.tool_call as Agents.ToolCall).args; const toolCallArgs = (contentPart.tool_call as Agents.ToolCall).args;
/** When args are a valid object, they are likely already invoked */ /** When args are a valid object, they are likely already invoked */
const args = let args =
finalUpdate || finalUpdate ||
typeof existingToolCall?.args === 'object' || typeof existingToolCall?.args === 'object' ||
typeof toolCallArgs === 'object' typeof toolCallArgs === 'object'
? contentPart.tool_call.args ? contentPart.tool_call.args
: (existingToolCall?.args ?? '') + (toolCallArgs ?? ''); : (existingToolCall?.args ?? '') + (toolCallArgs ?? '');
/** Preserve previously streamed args when final update omits them */
if (finalUpdate && args == null && existingToolCall?.args != null) {
args = existingToolCall.args;
}
const id = getNonEmptyValue([contentPart.tool_call.id, existingToolCall?.id]) ?? ''; const id = getNonEmptyValue([contentPart.tool_call.id, existingToolCall?.id]) ?? '';
const name = getNonEmptyValue([contentPart.tool_call.name, existingToolCall?.name]) ?? ''; const name = getNonEmptyValue([contentPart.tool_call.name, existingToolCall?.name]) ?? '';

1059
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -82,8 +82,8 @@
"@azure/search-documents": "^12.0.0", "@azure/search-documents": "^12.0.0",
"@azure/storage-blob": "^12.27.0", "@azure/storage-blob": "^12.27.0",
"@keyv/redis": "^4.3.3", "@keyv/redis": "^4.3.3",
"@langchain/core": "^0.3.72", "@langchain/core": "^0.3.79",
"@librechat/agents": "^3.0.5", "@librechat/agents": "^3.0.11",
"@librechat/data-schemas": "*", "@librechat/data-schemas": "*",
"@modelcontextprotocol/sdk": "^1.17.1", "@modelcontextprotocol/sdk": "^1.17.1",
"axios": "^1.12.1", "axios": "^1.12.1",