🪆 refactor: Internalize Producer Event Handling into Agent Graph Context (#11816)
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
Docker Dev Images Build / build (Dockerfile, librechat-dev, node) (push) Waiting to run
Docker Dev Images Build / build (Dockerfile.multi, librechat-dev-api, api-build) (push) Waiting to run
Sync Locize Translations & Create Translation PR / Sync Translation Keys with Locize (push) Waiting to run
Sync Locize Translations & Create Translation PR / Create Translation PR on Version Published (push) Blocked by required conditions

* 🔧 refactor: Simplify Event Handling with Consumer Callbacks only

    Removed direct handling of tool calls from the ModelEndHandler and using ChatModelStreamHandler  outside of graph contexts, as are now managed within the graph execution context to maintain it as a producer of events, and the model end handler as a consumer. This change eliminates potential race conditions and streamlines the processing of model end events.

          /**
       * handleToolCalls is now called from within the graph execution context
       * (Graph.createCallModel, after attemptInvoke) rather than here in the
       * stream consumer. This eliminates the race condition where ToolNode
       * could read toolCallStepIds before this handler had populated it,
       * since the stream consumer and graph execution run concurrently.
       */

* 📦 chore: Update `@librechat/agents` to v3.1.50
This commit is contained in:
Danny Avila 2026-02-17 00:53:22 -05:00 committed by GitHub
parent 8dd814d9b7
commit e710a12bfb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 9 additions and 68 deletions

View file

@ -1,22 +1,13 @@
const { nanoid } = require('nanoid');
const { Constants } = require('@librechat/agents');
const { logger } = require('@librechat/data-schemas');
const { Constants, EnvVar, GraphEvents, ToolEndHandler } = require('@librechat/agents');
const { Tools, StepTypes, FileContext, ErrorTypes } = require('librechat-data-provider');
const {
sendEvent,
GenerationJobManager,
writeAttachmentEvent,
createToolExecuteHandler,
} = require('@librechat/api');
const { Tools, StepTypes, FileContext, ErrorTypes } = require('librechat-data-provider');
const {
EnvVar,
Providers,
GraphEvents,
getMessageId,
ToolEndHandler,
handleToolCalls,
ChatModelStreamHandler,
} = require('@librechat/agents');
const { processFileCitations } = require('~/server/services/Files/Citations');
const { processCodeOutput } = require('~/server/services/Files/Code/process');
const { loadAuthValues } = require('~/server/services/Tools/credentials');
@ -57,8 +48,6 @@ class ModelEndHandler {
let errorMessage;
try {
const agentContext = graph.getAgentContext(metadata);
const isGoogle = agentContext.provider === Providers.GOOGLE;
const streamingDisabled = !!agentContext.clientOptions?.disableStreaming;
if (data?.output?.additional_kwargs?.stop_reason === 'refusal') {
const info = { ...data.output.additional_kwargs };
errorMessage = JSON.stringify({
@ -73,21 +62,6 @@ class ModelEndHandler {
});
}
const toolCalls = data?.output?.tool_calls;
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) {
await handleToolCalls(toolCalls, metadata, graph);
}
const usage = data?.output?.usage_metadata;
if (!usage) {
return this.finalize(errorMessage);
@ -98,38 +72,6 @@ class ModelEndHandler {
}
this.collectedUsage.push(usage);
if (!streamingDisabled) {
return this.finalize(errorMessage);
}
if (!data.output.content) {
return this.finalize(errorMessage);
}
const stepKey = graph.getStepKey(metadata);
const message_id = getMessageId(stepKey, graph) ?? '';
if (message_id) {
await graph.dispatchRunStep(stepKey, {
type: StepTypes.MESSAGE_CREATION,
message_creation: {
message_id,
},
});
}
const stepId = graph.getStepIdByKey(stepKey);
const content = data.output.content;
if (typeof content === 'string') {
await graph.dispatchMessageDelta(stepId, {
content: [
{
type: 'text',
text: content,
},
],
});
} else if (content.every((c) => c.type?.startsWith('text'))) {
await graph.dispatchMessageDelta(stepId, {
content,
});
}
} catch (error) {
logger.error('Error handling model end event:', error);
return this.finalize(errorMessage);
@ -200,7 +142,6 @@ function getDefaultHandlers({
const handlers = {
[GraphEvents.CHAT_MODEL_END]: new ModelEndHandler(collectedUsage),
[GraphEvents.TOOL_END]: new ToolEndHandler(toolEndCallback, logger),
[GraphEvents.CHAT_MODEL_STREAM]: new ChatModelStreamHandler(),
[GraphEvents.ON_RUN_STEP]: {
/**
* Handle ON_RUN_STEP event.