@librechat/agents 3.3.0 → 3.3.2
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 +116 -2
- package/dist/cjs/agents/AgentContext.cjs.map +1 -1
- package/dist/cjs/common/enum.cjs +2 -0
- package/dist/cjs/common/enum.cjs.map +1 -1
- package/dist/cjs/graphs/Graph.cjs +229 -48
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/graphs/MultiAgentGraph.cjs +102 -27
- package/dist/cjs/graphs/MultiAgentGraph.cjs.map +1 -1
- package/dist/cjs/llm/bedrock/index.cjs +11 -2
- package/dist/cjs/llm/bedrock/index.cjs.map +1 -1
- package/dist/cjs/llm/contextOverflowRecovery.cjs +132 -0
- package/dist/cjs/llm/contextOverflowRecovery.cjs.map +1 -0
- package/dist/cjs/llm/google/index.cjs +1 -1
- package/dist/cjs/llm/invoke.cjs +60 -3
- package/dist/cjs/llm/invoke.cjs.map +1 -1
- package/dist/cjs/main.cjs +20 -7
- package/dist/cjs/messages/prune.cjs +16 -5
- package/dist/cjs/messages/prune.cjs.map +1 -1
- package/dist/cjs/messages/recency.cjs +2 -0
- package/dist/cjs/messages/recency.cjs.map +1 -1
- package/dist/cjs/run.cjs +15 -2
- package/dist/cjs/run.cjs.map +1 -1
- package/dist/cjs/stream.cjs +205 -49
- package/dist/cjs/stream.cjs.map +1 -1
- package/dist/cjs/summarization/node.cjs +55 -7
- package/dist/cjs/summarization/node.cjs.map +1 -1
- package/dist/cjs/tools/BashExecutor.cjs +2 -2
- package/dist/cjs/tools/BashExecutor.cjs.map +1 -1
- package/dist/cjs/tools/BashProgrammaticToolCalling.cjs +2 -2
- package/dist/cjs/tools/BashProgrammaticToolCalling.cjs.map +1 -1
- package/dist/cjs/tools/CodeExecutor.cjs +69 -8
- package/dist/cjs/tools/CodeExecutor.cjs.map +1 -1
- package/dist/cjs/tools/ProgrammaticToolCalling.cjs +19 -15
- package/dist/cjs/tools/ProgrammaticToolCalling.cjs.map +1 -1
- package/dist/cjs/tools/ToolNode.cjs +32 -3
- package/dist/cjs/tools/ToolNode.cjs.map +1 -1
- package/dist/cjs/utils/errors.cjs +317 -52
- package/dist/cjs/utils/errors.cjs.map +1 -1
- package/dist/esm/agents/AgentContext.mjs +117 -3
- package/dist/esm/agents/AgentContext.mjs.map +1 -1
- package/dist/esm/common/enum.mjs +2 -0
- package/dist/esm/common/enum.mjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +227 -46
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/graphs/MultiAgentGraph.mjs +102 -27
- package/dist/esm/graphs/MultiAgentGraph.mjs.map +1 -1
- package/dist/esm/llm/bedrock/index.mjs +11 -2
- package/dist/esm/llm/bedrock/index.mjs.map +1 -1
- package/dist/esm/llm/contextOverflowRecovery.mjs +130 -0
- package/dist/esm/llm/contextOverflowRecovery.mjs.map +1 -0
- package/dist/esm/llm/google/index.mjs +1 -1
- package/dist/esm/llm/invoke.mjs +59 -4
- package/dist/esm/llm/invoke.mjs.map +1 -1
- package/dist/esm/main.mjs +9 -9
- package/dist/esm/messages/prune.mjs +14 -6
- package/dist/esm/messages/prune.mjs.map +1 -1
- package/dist/esm/messages/recency.mjs +2 -1
- package/dist/esm/messages/recency.mjs.map +1 -1
- package/dist/esm/run.mjs +15 -2
- package/dist/esm/run.mjs.map +1 -1
- package/dist/esm/stream.mjs +205 -49
- package/dist/esm/stream.mjs.map +1 -1
- package/dist/esm/summarization/node.mjs +55 -7
- package/dist/esm/summarization/node.mjs.map +1 -1
- package/dist/esm/tools/BashExecutor.mjs +3 -3
- package/dist/esm/tools/BashExecutor.mjs.map +1 -1
- package/dist/esm/tools/BashProgrammaticToolCalling.mjs +3 -3
- package/dist/esm/tools/BashProgrammaticToolCalling.mjs.map +1 -1
- package/dist/esm/tools/CodeExecutor.mjs +62 -9
- package/dist/esm/tools/CodeExecutor.mjs.map +1 -1
- package/dist/esm/tools/ProgrammaticToolCalling.mjs +20 -16
- package/dist/esm/tools/ProgrammaticToolCalling.mjs.map +1 -1
- package/dist/esm/tools/ToolNode.mjs +32 -3
- package/dist/esm/tools/ToolNode.mjs.map +1 -1
- package/dist/esm/utils/errors.mjs +317 -53
- package/dist/esm/utils/errors.mjs.map +1 -1
- package/dist/types/agents/AgentContext.d.ts +62 -3
- package/dist/types/common/enum.d.ts +2 -0
- package/dist/types/graphs/Graph.d.ts +16 -2
- package/dist/types/graphs/MultiAgentGraph.d.ts +1 -0
- package/dist/types/llm/contextOverflowRecovery.d.ts +85 -0
- package/dist/types/llm/invoke.d.ts +35 -6
- package/dist/types/messages/prune.d.ts +10 -2
- package/dist/types/messages/recency.d.ts +1 -0
- package/dist/types/run.d.ts +2 -0
- package/dist/types/tools/CodeExecutor.d.ts +14 -1
- package/dist/types/types/llm.d.ts +7 -4
- package/dist/types/types/stream.d.ts +5 -4
- package/dist/types/types/summarize.d.ts +22 -0
- package/dist/types/utils/__tests__/fixtures/contextOverflowSignatures.d.ts +40 -0
- package/dist/types/utils/errors.d.ts +65 -16
- package/dist/types/utils/redactSecrets.d.ts +3 -0
- package/package.json +10 -11
- package/src/agents/AgentContext.ts +188 -7
- package/src/agents/__tests__/AgentContext.overflow.test.ts +205 -0
- package/src/common/enum.ts +2 -0
- package/src/graphs/Graph.ts +389 -58
- package/src/graphs/MultiAgentGraph.ts +184 -46
- package/src/graphs/__tests__/Graph.contextOverflow.test.ts +631 -0
- package/src/llm/__tests__/contextOverflowRecovery.test.ts +401 -0
- package/src/llm/__tests__/fallbackOverflow.test.ts +287 -0
- package/src/llm/anthropic/llm.spec.ts +8 -0
- package/src/llm/bedrock/index.ts +25 -12
- package/src/llm/contextOverflowRecovery.ts +292 -0
- package/src/llm/invoke.ts +119 -4
- package/src/messages/prune.ts +24 -11
- package/src/messages/recency.ts +3 -1
- package/src/run.ts +24 -1
- package/src/scripts/context-overflow-probe.ts +997 -0
- package/src/specs/agent-handoffs.test.ts +903 -1
- package/src/specs/context-overflow-recovery.live.test.ts +213 -0
- package/src/splitStream.test.ts +882 -0
- package/src/stream.ts +315 -51
- package/src/summarization/__tests__/aggregator.test.ts +83 -0
- package/src/summarization/__tests__/node.test.ts +139 -0
- package/src/summarization/node.ts +99 -14
- package/src/tools/BashExecutor.ts +4 -2
- package/src/tools/BashProgrammaticToolCalling.ts +4 -7
- package/src/tools/CodeExecutor.ts +119 -8
- package/src/tools/ProgrammaticToolCalling.ts +29 -27
- package/src/tools/ToolNode.ts +50 -8
- package/src/tools/__tests__/CodeApiAuthHeaders.test.ts +297 -3
- package/src/types/llm.ts +8 -1
- package/src/types/stream.ts +5 -4
- package/src/types/summarize.ts +22 -0
- package/src/utils/__tests__/errors.test.ts +270 -0
- package/src/utils/__tests__/fixtures/contextOverflowSignatures.ts +336 -0
- package/src/utils/__tests__/redactSecrets.test.ts +56 -0
- package/src/utils/errors.ts +484 -66
- package/src/utils/redactSecrets.ts +61 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../../../../src/llm/bedrock/index.ts"],"sourcesContent":["/**\n * Optimized ChatBedrockConverse wrapper that fixes content block merging for\n * streaming responses and adds support for latest @langchain/aws features:\n *\n * - Application Inference Profiles (PR #9129)\n * - Service Tiers (Priority/Standard/Flex) (PR #9785) - requires AWS SDK 3.966.0+\n *\n * Bedrock's `@langchain/aws` library does not include an `index` property on content\n * blocks (unlike Anthropic/OpenAI), which causes LangChain's `_mergeLists` to append\n * each streaming chunk as a separate array entry instead of merging by index.\n *\n * This wrapper takes full ownership of the stream by directly interfacing with the\n * AWS SDK client (`this.client`) and using custom handlers from `./utils/` that\n * include `contentBlockIndex` in response_metadata for every delta type. It then\n * promotes `contentBlockIndex` to an `index` property on each content block\n * (mirroring Anthropic's pattern) and strips it from metadata to avoid\n * `_mergeDicts` conflicts.\n *\n * When multiple content block types are present (e.g. reasoning + text), text deltas\n * are promoted from strings to array form with `index` so they merge correctly once\n * the accumulated content is already an array.\n */\n\nimport { ChatBedrockConverse } from '@langchain/aws';\nimport { AIMessageChunk } from '@langchain/core/messages';\nimport { ChatGenerationChunk, ChatResult } from '@langchain/core/outputs';\nimport {\n ConverseStreamCommand,\n type GuardrailConfiguration,\n type GuardrailStreamConfiguration,\n} from '@aws-sdk/client-bedrock-runtime';\nimport type { CallbackManagerForLLMRun } from '@langchain/core/callbacks/manager';\nimport type { BaseMessage, ResponseMetadata } from '@langchain/core/messages';\nimport type { ChatBedrockConverseInput } from '@langchain/aws';\nimport {\n convertToConverseMessages,\n createConverseToolUseStopChunk,\n handleConverseStreamContentBlockStart,\n handleConverseStreamContentBlockDelta,\n handleConverseStreamMetadata,\n} from './utils';\nimport type { ContentBlockDeltaEvent } from './types';\nimport {\n resolveBedrockPromptCacheTtl,\n supportsBedrockToolCache,\n type PromptCacheTtl,\n} from '@/messages/cache';\nimport { applyCachePointsToConversePayload } from './cachePoints';\nimport { insertBedrockToolCachePoint } from './toolCache';\n\n/**\n * Service tier type for Bedrock invocations.\n * Requires AWS SDK >= 3.966.0 to actually work.\n * @see https://docs.aws.amazon.com/bedrock/latest/userguide/service-tiers-inference.html\n */\nexport type ServiceTierType = 'priority' | 'default' | 'flex' | 'reserved';\n\nexport type CustomGuardrailConfiguration = GuardrailConfiguration &\n Pick<GuardrailStreamConfiguration, 'streamProcessingMode'>;\n\nconst MAX_STREAM_QUEUE_CHUNKS = 256;\nconst MAX_STREAM_QUEUE_TEXT_CHARS = 8192;\nconst STREAM_CHUNK_MIN_SIZE = 4;\nconst STREAM_BOUNDARIES = new Set([' ', '.', ',', '!', '?', ';', ':']);\n\ntype QueuedGenerationChunk = {\n chunk: ChatGenerationChunk;\n callbackChunk?: ChatGenerationChunk;\n callbackToken: string;\n smooth: boolean;\n textLength: number;\n};\n\nfunction findStreamChunkBoundary(text: string, minSize: number): number {\n if (minSize >= text.length) {\n return text.length;\n }\n\n for (let position = minSize; position < text.length; position++) {\n if (STREAM_BOUNDARIES.has(text[position])) {\n return position + 1;\n }\n }\n\n return text.length;\n}\n\nfunction splitStreamToken(text: string): string[] {\n const chunks: string[] = [];\n let currentIndex = 0;\n\n while (currentIndex < text.length) {\n const remainingText = text.slice(currentIndex);\n const chunkSize = findStreamChunkBoundary(\n remainingText,\n STREAM_CHUNK_MIN_SIZE\n );\n chunks.push(text.slice(currentIndex, currentIndex + chunkSize));\n currentIndex += chunkSize;\n }\n\n return chunks;\n}\n\nfunction getCadencedStreamDelay({\n targetDelay,\n lastVisibleContentAt,\n now,\n}: {\n targetDelay: number;\n lastVisibleContentAt?: number;\n now: number;\n}): number {\n if (targetDelay <= 0 || lastVisibleContentAt == null) {\n return 0;\n }\n return Math.max(0, targetDelay - (now - lastVisibleContentAt));\n}\n\nasync function waitForStreamDelay(\n delay: number,\n signal?: AbortSignal\n): Promise<void> {\n if (delay <= 0 || isSignalAborted(signal)) {\n return;\n }\n await new Promise<void>((resolve) => {\n const timeoutRef: { current?: ReturnType<typeof setTimeout> } = {};\n const onAbort = (): void => {\n if (timeoutRef.current) {\n clearTimeout(timeoutRef.current);\n }\n signal?.removeEventListener('abort', onAbort);\n resolve();\n };\n timeoutRef.current = setTimeout(() => {\n signal?.removeEventListener('abort', onAbort);\n resolve();\n }, delay);\n signal?.addEventListener('abort', onAbort, { once: true });\n if (isSignalAborted(signal)) {\n onAbort();\n }\n });\n}\n\nfunction isSignalAborted(signal?: AbortSignal): boolean {\n return signal?.aborted === true;\n}\n\n/**\n * Extended input interface with additional features:\n * - applicationInferenceProfile: Use an inference profile ARN instead of model ID\n * - serviceTier: Specify service tier (Priority, Standard, Flex, Reserved)\n */\nexport interface CustomChatBedrockConverseInput\n extends ChatBedrockConverseInput {\n /**\n * Enables Bedrock prompt cache checkpoints for message and tool prefixes.\n */\n promptCache?: boolean;\n\n /**\n * Prompt-cache checkpoint TTL. Defaults to `'1h'` (extended cache) when\n * `promptCache` is enabled; set `'5m'` for the legacy 5-minute behavior.\n * Bedrock models that don't support the 1-hour TTL downgrade to 5m\n * server-side (verified on Sonnet/Opus 4.6), so the default is safe to leave\n * on; use `'5m'` for any model that rejects it.\n */\n promptCacheTtl?: PromptCacheTtl;\n\n /**\n * Minimum delay in milliseconds between visible streamed content deltas.\n */\n _lc_stream_delay?: number;\n\n /**\n * Guardrail configuration for Converse and ConverseStream invocations.\n * `streamProcessingMode` is only used by ConverseStream.\n */\n guardrailConfig?: CustomGuardrailConfiguration;\n\n /**\n * Application Inference Profile ARN to use for the model.\n * For example, \"arn:aws:bedrock:eu-west-1:123456789102:application-inference-profile/fm16bt65tzgx\"\n * When provided, this ARN will be used for the actual inference calls instead of the model ID.\n * Must still provide `model` as normal modelId to benefit from all the metadata.\n * @see https://docs.aws.amazon.com/bedrock/latest/userguide/inference-profiles-create.html\n */\n applicationInferenceProfile?: string;\n\n /**\n * Service tier for model invocation.\n * Specifies the processing tier type used for serving the request.\n * Supported values are 'priority', 'default', 'flex', and 'reserved'.\n *\n * - 'priority': Prioritized processing for lower latency\n * - 'default': Standard processing tier\n * - 'flex': Flexible processing tier with lower cost\n * - 'reserved': Reserved capacity for consistent performance\n *\n * If not provided, AWS uses the default tier.\n * Note: Requires AWS SDK >= 3.966.0 to work.\n * @see https://docs.aws.amazon.com/bedrock/latest/userguide/service-tiers-inference.html\n */\n serviceTier?: ServiceTierType;\n}\n\n/**\n * Extended call options with serviceTier override support.\n */\nexport interface CustomChatBedrockConverseCallOptions {\n serviceTier?: ServiceTierType;\n guardrailConfig?: CustomGuardrailConfiguration;\n}\n\nexport class CustomChatBedrockConverse extends ChatBedrockConverse {\n _lc_stream_delay: number;\n\n /**\n * Whether to insert Bedrock prompt cache checkpoints when available.\n */\n promptCache?: boolean;\n\n /**\n * Prompt-cache checkpoint TTL (`'5m'` legacy or `'1h'` extended cache).\n */\n promptCacheTtl?: PromptCacheTtl;\n\n /**\n * Application Inference Profile ARN to use instead of model ID.\n */\n applicationInferenceProfile?: string;\n\n /**\n * Service tier for model invocation.\n */\n serviceTier?: ServiceTierType;\n\n /**\n * The configured model id, captured at construction so it survives the\n * temporary `this.model` swap to an application-inference-profile ARN during\n * generation. Used to gate the Bedrock tool cache point to Claude models\n * (see {@link supportsBedrockToolCache}).\n */\n private readonly cacheModelId: string;\n\n constructor(fields?: CustomChatBedrockConverseInput) {\n super(fields);\n this.promptCache = fields?.promptCache;\n this.promptCacheTtl = fields?.promptCacheTtl;\n this._lc_stream_delay = Math.max(0, fields?._lc_stream_delay ?? 0);\n this.applicationInferenceProfile = fields?.applicationInferenceProfile;\n this.serviceTier = fields?.serviceTier;\n // `super(fields)` initializes `this.model` to LangChain's default Claude\n // model when `fields.model` is omitted, so fall back to it rather than ''\n // (which would treat the default Claude model as tool-cache-unsupported).\n this.cacheModelId = fields?.model ?? this.model;\n }\n\n static lc_name(): string {\n return 'LibreChatBedrockConverse';\n }\n\n /**\n * Get the model ID to use for API calls.\n * Returns applicationInferenceProfile if set, otherwise returns this.model.\n */\n protected getModelId(): string {\n return this.applicationInferenceProfile ?? this.model;\n }\n\n /**\n * Override invocationParams to add serviceTier support.\n */\n override invocationParams(\n options?: this['ParsedCallOptions'] & CustomChatBedrockConverseCallOptions\n ): ReturnType<ChatBedrockConverse['invocationParams']> & {\n serviceTier?: { type: ServiceTierType };\n } {\n const baseParams = super.invocationParams(options);\n const toolConfig =\n this.promptCache === true && supportsBedrockToolCache(this.cacheModelId)\n ? insertBedrockToolCachePoint(\n baseParams.toolConfig,\n true,\n resolveBedrockPromptCacheTtl(this.promptCacheTtl, this.cacheModelId)\n )\n : baseParams.toolConfig;\n\n /** Service tier from options or fall back to class-level setting */\n const serviceTierType = options?.serviceTier ?? this.serviceTier;\n\n return {\n ...baseParams,\n toolConfig,\n serviceTier: serviceTierType ? { type: serviceTierType } : undefined,\n };\n }\n\n /**\n * Override _generateNonStreaming to use applicationInferenceProfile as modelId.\n * Uses the same model-swapping pattern as streaming for consistency.\n */\n override async _generateNonStreaming(\n messages: BaseMessage[],\n options: this['ParsedCallOptions'] & CustomChatBedrockConverseCallOptions,\n runManager?: CallbackManagerForLLMRun\n ): Promise<ChatResult> {\n const originalModel = this.model;\n if (\n this.applicationInferenceProfile != null &&\n this.applicationInferenceProfile !== ''\n ) {\n this.model = this.applicationInferenceProfile;\n }\n\n try {\n return await super._generateNonStreaming(messages, options, runManager);\n } finally {\n this.model = originalModel;\n }\n }\n\n /**\n * Own the stream end-to-end so we have direct access to every\n * `contentBlockDelta.contentBlockIndex` from the AWS SDK.\n *\n * This replaces the parent's implementation which strips contentBlockIndex\n * from text and reasoning deltas, making it impossible to merge correctly.\n */\n override async *_streamResponseChunks(\n messages: BaseMessage[],\n options: this['ParsedCallOptions'] & CustomChatBedrockConverseCallOptions,\n runManager?: CallbackManagerForLLMRun\n ): AsyncGenerator<ChatGenerationChunk> {\n const { converseMessages, converseSystem } =\n convertToConverseMessages(messages);\n const params = this.invocationParams(options);\n\n let { streamUsage } = this;\n if ((options as Record<string, unknown>).streamUsage !== undefined) {\n streamUsage = (options as Record<string, unknown>).streamUsage as boolean;\n }\n\n const modelId = this.getModelId();\n\n applyCachePointsToConversePayload({\n cacheControl: options.cache_control,\n system: converseSystem,\n messages: converseMessages,\n params,\n modelId,\n });\n\n const command = new ConverseStreamCommand({\n modelId,\n messages: converseMessages,\n system: converseSystem,\n ...(params as Record<string, unknown>),\n });\n\n const streamAbortController = new AbortController();\n const abortStream = (): void => streamAbortController.abort();\n options.signal?.addEventListener('abort', abortStream, { once: true });\n if (isSignalAborted(options.signal)) {\n abortStream();\n }\n\n try {\n const response = await this.client.send(command, {\n abortSignal: streamAbortController.signal,\n });\n\n const stream = response.stream;\n if (!stream) {\n return;\n }\n\n const seenBlockIndices = new Set<number>();\n const toolUseBlockIndices = new Set<number>();\n const queuedChunks: QueuedGenerationChunk[] = [];\n const producerState: { done: boolean; error?: unknown } = { done: false };\n let queuedChunkIndex = 0;\n let bufferedTextLength = 0;\n let consumerClosed = false;\n let notifyConsumer: (() => void) | undefined;\n let notifyProducer: (() => void) | undefined;\n let hasEmittedVisibleContent = false;\n let lastVisibleContentAt: number | undefined;\n\n /**\n * Guardrails can reject an already-streamed toolUse block at\n * `messageStop` (`guardrail_intervened`), after `contentBlockStop` has\n * passed. Only emit eager-execution seals when no guardrails are\n * configured, so a later intervention can't race an eagerly started tool.\n */\n const sealToolUseOnStop =\n options.guardrailConfig == null && this.guardrailConfig == null;\n\n const notifyConsumerForChunk = (): void => {\n notifyConsumer?.();\n notifyConsumer = undefined;\n };\n\n const notifyProducerForSpace = (): void => {\n notifyProducer?.();\n notifyProducer = undefined;\n };\n\n const hasQueuedChunks = (): boolean =>\n queuedChunkIndex < queuedChunks.length;\n\n const getQueuedChunkCount = (): number =>\n queuedChunks.length - queuedChunkIndex;\n\n const isQueueAtCapacity = (): boolean =>\n getQueuedChunkCount() >= MAX_STREAM_QUEUE_CHUNKS ||\n bufferedTextLength >= MAX_STREAM_QUEUE_TEXT_CHARS;\n\n const waitForNextChunk = async (): Promise<void> => {\n if (\n hasQueuedChunks() ||\n producerState.done ||\n producerState.error != null\n ) {\n return;\n }\n await new Promise<void>((resolve) => {\n notifyConsumer = resolve;\n });\n };\n\n const waitForQueueSpace = async (): Promise<void> => {\n while (\n isQueueAtCapacity() &&\n !consumerClosed &&\n !isSignalAborted(options.signal)\n ) {\n await new Promise<void>((resolve) => {\n const signal = options.signal;\n const onAbort = (): void => {\n signal?.removeEventListener('abort', onAbort);\n resolve();\n };\n const onSpace = (): void => {\n signal?.removeEventListener('abort', onAbort);\n resolve();\n };\n notifyProducer = onSpace;\n signal?.addEventListener('abort', onAbort, { once: true });\n if (isSignalAborted(signal)) {\n onAbort();\n }\n });\n }\n };\n\n const dequeue = (): QueuedGenerationChunk | undefined => {\n if (!hasQueuedChunks()) {\n return undefined;\n }\n const queuedChunk = queuedChunks[queuedChunkIndex];\n queuedChunkIndex++;\n if (\n queuedChunkIndex > 128 &&\n queuedChunkIndex * 2 >= queuedChunks.length\n ) {\n queuedChunks.splice(0, queuedChunkIndex);\n queuedChunkIndex = 0;\n }\n return queuedChunk;\n };\n\n const enqueue = async (\n queuedChunk: QueuedGenerationChunk\n ): Promise<void> => {\n await waitForQueueSpace();\n if (consumerClosed || isSignalAborted(options.signal)) {\n abortStream();\n throw new Error('AbortError: User aborted the request.');\n }\n queuedChunks.push(queuedChunk);\n if (queuedChunk.smooth) {\n bufferedTextLength += queuedChunk.textLength;\n }\n notifyConsumerForChunk();\n };\n\n const enqueueChunk = async ({\n chunk,\n callbackChunk,\n callbackToken = '',\n smooth = false,\n textLength = 0,\n }: {\n chunk: ChatGenerationChunk;\n callbackChunk?: ChatGenerationChunk;\n callbackToken?: string;\n smooth?: boolean;\n textLength?: number;\n }): Promise<void> => {\n await enqueue({\n chunk,\n callbackChunk,\n callbackToken,\n smooth,\n textLength: smooth ? textLength : 0,\n });\n };\n\n const enqueueDelta = async (\n contentBlockDelta: ContentBlockDeltaEvent\n ): Promise<void> => {\n const delta = contentBlockDelta.delta;\n if (delta == null) {\n throw new Error('No delta found in content block.');\n }\n\n const idx = contentBlockDelta.contentBlockIndex;\n if (idx != null) {\n seenBlockIndices.add(idx);\n }\n\n const text = delta.text;\n const reasoningContent = delta.reasoningContent;\n const reasoningText = reasoningContent?.text;\n const visibleText =\n typeof text === 'string'\n ? text\n : typeof reasoningText === 'string'\n ? reasoningText\n : '';\n const smooth = this._lc_stream_delay > 0 && visibleText !== '';\n const tokenChunks = smooth\n ? splitStreamToken(visibleText)\n : [visibleText];\n\n for (const token of tokenChunks) {\n let splitDelta = contentBlockDelta;\n if (typeof text === 'string') {\n splitDelta = {\n ...contentBlockDelta,\n delta: { text: token },\n };\n } else if (\n typeof reasoningText === 'string' &&\n reasoningContent != null\n ) {\n splitDelta = {\n ...contentBlockDelta,\n delta: {\n reasoningContent: {\n ...reasoningContent,\n text: token,\n },\n },\n };\n }\n\n const deltaChunk = handleConverseStreamContentBlockDelta(splitDelta);\n await enqueueChunk({\n chunk: this.enrichChunk(deltaChunk, seenBlockIndices),\n callbackChunk: deltaChunk,\n callbackToken: deltaChunk.text,\n smooth,\n textLength: token.length,\n });\n }\n };\n\n const producer = (async (): Promise<void> => {\n try {\n for await (const event of stream) {\n if (isSignalAborted(options.signal)) {\n abortStream();\n throw new Error('AbortError: User aborted the request.');\n }\n\n if (event.contentBlockStart != null) {\n const startChunk = handleConverseStreamContentBlockStart(\n event.contentBlockStart\n );\n if (startChunk != null) {\n const idx = event.contentBlockStart.contentBlockIndex;\n if (idx != null) {\n seenBlockIndices.add(idx);\n if (event.contentBlockStart.start?.toolUse != null) {\n toolUseBlockIndices.add(idx);\n }\n }\n await enqueueChunk({\n chunk: this.enrichChunk(startChunk, seenBlockIndices),\n callbackChunk: startChunk,\n callbackToken: startChunk.text,\n });\n }\n } else if (event.contentBlockDelta != null) {\n await enqueueDelta(event.contentBlockDelta);\n } else if (event.metadata != null) {\n await enqueueChunk({\n chunk: handleConverseStreamMetadata(event.metadata, {\n streamUsage,\n }),\n });\n } else if (event.contentBlockStop != null) {\n const stopIdx = event.contentBlockStop.contentBlockIndex;\n if (stopIdx != null) {\n seenBlockIndices.add(stopIdx);\n if (sealToolUseOnStop && toolUseBlockIndices.has(stopIdx)) {\n const sealChunk = createConverseToolUseStopChunk(stopIdx);\n await enqueueChunk({\n chunk: sealChunk,\n callbackChunk: sealChunk,\n callbackToken: sealChunk.text,\n });\n }\n }\n } else {\n await enqueueChunk({\n chunk: new ChatGenerationChunk({\n text: '',\n message: new AIMessageChunk({\n content: '',\n response_metadata: { ...event } as ResponseMetadata,\n }),\n }),\n });\n }\n }\n } catch (error) {\n producerState.error = error;\n } finally {\n producerState.done = true;\n notifyConsumerForChunk();\n }\n })();\n\n try {\n let keepStreaming = true;\n while (keepStreaming) {\n if (isSignalAborted(options.signal)) {\n abortStream();\n throw new Error('AbortError: User aborted the request.');\n }\n\n await waitForNextChunk();\n const queuedChunk = dequeue();\n\n if (!queuedChunk) {\n if (producerState.error != null) {\n throw producerState.error;\n }\n if (producerState.done) {\n keepStreaming = false;\n }\n continue;\n }\n\n if (queuedChunk.smooth) {\n bufferedTextLength = Math.max(\n 0,\n bufferedTextLength - queuedChunk.textLength\n );\n notifyProducerForSpace();\n await waitForStreamDelay(\n getCadencedStreamDelay({\n targetDelay: hasEmittedVisibleContent\n ? this._lc_stream_delay\n : 0,\n lastVisibleContentAt,\n now: Date.now(),\n }),\n options.signal\n );\n if (isSignalAborted(options.signal)) {\n abortStream();\n throw new Error('AbortError: User aborted the request.');\n }\n hasEmittedVisibleContent = true;\n lastVisibleContentAt = Date.now();\n } else {\n notifyProducerForSpace();\n }\n\n yield queuedChunk.chunk;\n\n if (queuedChunk.callbackChunk != null) {\n await runManager?.handleLLMNewToken(\n queuedChunk.callbackToken,\n undefined,\n undefined,\n undefined,\n undefined,\n { chunk: queuedChunk.callbackChunk }\n );\n }\n }\n } finally {\n consumerClosed = true;\n if (!producerState.done) {\n abortStream();\n notifyProducerForSpace();\n }\n await producer;\n }\n } finally {\n options.signal?.removeEventListener('abort', abortStream);\n if (!streamAbortController.signal.aborted) {\n streamAbortController.abort();\n }\n }\n }\n\n /**\n * Inject `index` on content blocks for proper merge behaviour, then strip\n * `contentBlockIndex` from response_metadata to prevent `_mergeDicts` conflicts.\n *\n * Text string content is promoted to array form only when the stream contains\n * multiple content block indices (e.g. reasoning at index 0, text at index 1),\n * ensuring text merges correctly with the already-array accumulated content.\n */\n private enrichChunk(\n chunk: ChatGenerationChunk,\n seenBlockIndices: Set<number>\n ): ChatGenerationChunk {\n const message = chunk.message;\n if (!(message instanceof AIMessageChunk)) {\n return chunk;\n }\n\n const metadata = message.response_metadata as Record<string, unknown>;\n const blockIndex = this.extractContentBlockIndex(metadata);\n const hasMetadataIndex = blockIndex != null;\n\n let content: AIMessageChunk['content'] = message.content;\n let contentModified = false;\n\n if (Array.isArray(content) && blockIndex != null) {\n content = content.map((block) =>\n typeof block === 'object' && !('index' in block)\n ? { ...block, index: blockIndex }\n : block\n );\n contentModified = true;\n } else if (\n typeof content === 'string' &&\n content !== '' &&\n blockIndex != null &&\n seenBlockIndices.size > 1\n ) {\n content = [{ type: 'text', text: content, index: blockIndex }];\n contentModified = true;\n }\n\n if (!contentModified && !hasMetadataIndex) {\n return chunk;\n }\n\n const cleanedMetadata = hasMetadataIndex\n ? (this.removeContentBlockIndex(metadata) as Record<string, unknown>)\n : metadata;\n\n return new ChatGenerationChunk({\n text: chunk.text,\n message: new AIMessageChunk({\n ...message,\n content,\n response_metadata: cleanedMetadata,\n }),\n generationInfo: chunk.generationInfo,\n });\n }\n\n /**\n * Extract `contentBlockIndex` from the top level of response_metadata.\n * Our custom handlers always place it at the top level.\n */\n private extractContentBlockIndex(\n metadata: Record<string, unknown>\n ): number | undefined {\n if (\n 'contentBlockIndex' in metadata &&\n typeof metadata.contentBlockIndex === 'number'\n ) {\n return metadata.contentBlockIndex;\n }\n return undefined;\n }\n\n private removeContentBlockIndex(obj: unknown): unknown {\n if (obj === null || obj === undefined) {\n return obj;\n }\n\n if (Array.isArray(obj)) {\n return obj.map((item) => this.removeContentBlockIndex(item));\n }\n\n if (typeof obj === 'object') {\n const cleaned: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(obj)) {\n if (key !== 'contentBlockIndex') {\n cleaned[key] = this.removeContentBlockIndex(value);\n }\n }\n return cleaned;\n }\n\n return obj;\n }\n}\n\nexport type { ChatBedrockConverseInput };\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../../../src/llm/bedrock/index.ts"],"sourcesContent":["/**\n * Optimized ChatBedrockConverse wrapper that fixes content block merging for\n * streaming responses and adds support for latest @langchain/aws features:\n *\n * - Application Inference Profiles (PR #9129)\n * - Service Tiers (Priority/Standard/Flex) (PR #9785) - requires AWS SDK 3.966.0+\n *\n * Bedrock's `@langchain/aws` library does not include an `index` property on content\n * blocks (unlike Anthropic/OpenAI), which causes LangChain's `_mergeLists` to append\n * each streaming chunk as a separate array entry instead of merging by index.\n *\n * This wrapper takes full ownership of the stream by directly interfacing with the\n * AWS SDK client (`this.client`) and using custom handlers from `./utils/` that\n * include `contentBlockIndex` in response_metadata for every delta type. It then\n * promotes `contentBlockIndex` to an `index` property on each content block\n * (mirroring Anthropic's pattern) and strips it from metadata to avoid\n * `_mergeDicts` conflicts.\n *\n * When multiple content block types are present (e.g. reasoning + text), text deltas\n * are promoted from strings to array form with `index` so they merge correctly once\n * the accumulated content is already an array.\n */\n\nimport { ChatBedrockConverse } from '@langchain/aws';\nimport { AIMessageChunk } from '@langchain/core/messages';\nimport { ChatGenerationChunk, ChatResult } from '@langchain/core/outputs';\nimport {\n ConverseStreamCommand,\n type ConverseStreamOutput,\n type GuardrailConfiguration,\n type GuardrailStreamConfiguration,\n} from '@aws-sdk/client-bedrock-runtime';\nimport type { CallbackManagerForLLMRun } from '@langchain/core/callbacks/manager';\nimport type { BaseMessage, ResponseMetadata } from '@langchain/core/messages';\nimport type { ChatBedrockConverseInput } from '@langchain/aws';\nimport type { ContentBlockDeltaEvent } from './types';\nimport {\n convertToConverseMessages,\n createConverseToolUseStopChunk,\n handleConverseStreamContentBlockStart,\n handleConverseStreamContentBlockDelta,\n handleConverseStreamMetadata,\n} from './utils';\nimport {\n resolveBedrockPromptCacheTtl,\n supportsBedrockToolCache,\n type PromptCacheTtl,\n} from '@/messages/cache';\nimport { applyCachePointsToConversePayload } from './cachePoints';\nimport { insertBedrockToolCachePoint } from './toolCache';\n\n/**\n * Service tier type for Bedrock invocations.\n * Requires AWS SDK >= 3.966.0 to actually work.\n * @see https://docs.aws.amazon.com/bedrock/latest/userguide/service-tiers-inference.html\n */\nexport type ServiceTierType = 'priority' | 'default' | 'flex' | 'reserved';\n\nexport type CustomGuardrailConfiguration = GuardrailConfiguration &\n Pick<GuardrailStreamConfiguration, 'streamProcessingMode'>;\n\nconst MAX_STREAM_QUEUE_CHUNKS = 256;\nconst MAX_STREAM_QUEUE_TEXT_CHARS = 8192;\nconst STREAM_CHUNK_MIN_SIZE = 4;\nconst STREAM_BOUNDARIES = new Set([' ', '.', ',', '!', '?', ';', ':']);\n\ntype QueuedGenerationChunk = {\n chunk: ChatGenerationChunk;\n callbackChunk?: ChatGenerationChunk;\n callbackToken: string;\n smooth: boolean;\n textLength: number;\n};\n\nfunction findStreamChunkBoundary(text: string, minSize: number): number {\n if (minSize >= text.length) {\n return text.length;\n }\n\n for (let position = minSize; position < text.length; position++) {\n if (STREAM_BOUNDARIES.has(text[position])) {\n return position + 1;\n }\n }\n\n return text.length;\n}\n\nfunction splitStreamToken(text: string): string[] {\n const chunks: string[] = [];\n let currentIndex = 0;\n\n while (currentIndex < text.length) {\n const remainingText = text.slice(currentIndex);\n const chunkSize = findStreamChunkBoundary(\n remainingText,\n STREAM_CHUNK_MIN_SIZE\n );\n chunks.push(text.slice(currentIndex, currentIndex + chunkSize));\n currentIndex += chunkSize;\n }\n\n return chunks;\n}\n\n/**\n * Resolves the text a delta contributes to the smoothing cadence, preferring a\n * text delta over a reasoning delta and ignoring non-string payloads.\n */\nfunction resolveVisibleText(text?: string, reasoningText?: string): string {\n if (typeof text === 'string') {\n return text;\n }\n\n if (typeof reasoningText === 'string') {\n return reasoningText;\n }\n\n return '';\n}\n\nfunction getCadencedStreamDelay({\n targetDelay,\n lastVisibleContentAt,\n now,\n}: {\n targetDelay: number;\n lastVisibleContentAt?: number;\n now: number;\n}): number {\n if (targetDelay <= 0 || lastVisibleContentAt == null) {\n return 0;\n }\n return Math.max(0, targetDelay - (now - lastVisibleContentAt));\n}\n\nasync function waitForStreamDelay(\n delay: number,\n signal?: AbortSignal\n): Promise<void> {\n if (delay <= 0 || isSignalAborted(signal)) {\n return;\n }\n await new Promise<void>((resolve) => {\n const timeoutRef: { current?: ReturnType<typeof setTimeout> } = {};\n const onAbort = (): void => {\n if (timeoutRef.current) {\n clearTimeout(timeoutRef.current);\n }\n signal?.removeEventListener('abort', onAbort);\n resolve();\n };\n timeoutRef.current = setTimeout(() => {\n signal?.removeEventListener('abort', onAbort);\n resolve();\n }, delay);\n signal?.addEventListener('abort', onAbort, { once: true });\n if (isSignalAborted(signal)) {\n onAbort();\n }\n });\n}\n\nfunction isSignalAborted(signal?: AbortSignal): boolean {\n return signal?.aborted === true;\n}\n\n/**\n * Extended input interface with additional features:\n * - applicationInferenceProfile: Use an inference profile ARN instead of model ID\n * - serviceTier: Specify service tier (Priority, Standard, Flex, Reserved)\n */\nexport interface CustomChatBedrockConverseInput\n extends ChatBedrockConverseInput {\n /**\n * Enables Bedrock prompt cache checkpoints for message and tool prefixes.\n */\n promptCache?: boolean;\n\n /**\n * Prompt-cache checkpoint TTL. Defaults to `'1h'` (extended cache) when\n * `promptCache` is enabled; set `'5m'` for the legacy 5-minute behavior.\n * Bedrock models that don't support the 1-hour TTL downgrade to 5m\n * server-side (verified on Sonnet/Opus 4.6), so the default is safe to leave\n * on; use `'5m'` for any model that rejects it.\n */\n promptCacheTtl?: PromptCacheTtl;\n\n /**\n * Minimum delay in milliseconds between visible streamed content deltas.\n */\n _lc_stream_delay?: number;\n\n /**\n * Guardrail configuration for Converse and ConverseStream invocations.\n * `streamProcessingMode` is only used by ConverseStream.\n */\n guardrailConfig?: CustomGuardrailConfiguration;\n\n /**\n * Application Inference Profile ARN to use for the model.\n * For example, \"arn:aws:bedrock:eu-west-1:123456789102:application-inference-profile/fm16bt65tzgx\"\n * When provided, this ARN will be used for the actual inference calls instead of the model ID.\n * Must still provide `model` as normal modelId to benefit from all the metadata.\n * @see https://docs.aws.amazon.com/bedrock/latest/userguide/inference-profiles-create.html\n */\n applicationInferenceProfile?: string;\n\n /**\n * Service tier for model invocation.\n * Specifies the processing tier type used for serving the request.\n * Supported values are 'priority', 'default', 'flex', and 'reserved'.\n *\n * - 'priority': Prioritized processing for lower latency\n * - 'default': Standard processing tier\n * - 'flex': Flexible processing tier with lower cost\n * - 'reserved': Reserved capacity for consistent performance\n *\n * If not provided, AWS uses the default tier.\n * Note: Requires AWS SDK >= 3.966.0 to work.\n * @see https://docs.aws.amazon.com/bedrock/latest/userguide/service-tiers-inference.html\n */\n serviceTier?: ServiceTierType;\n}\n\n/**\n * Extended call options with serviceTier override support.\n */\nexport interface CustomChatBedrockConverseCallOptions {\n serviceTier?: ServiceTierType;\n guardrailConfig?: CustomGuardrailConfiguration;\n}\n\nexport class CustomChatBedrockConverse extends ChatBedrockConverse {\n _lc_stream_delay: number;\n\n /**\n * Whether to insert Bedrock prompt cache checkpoints when available.\n */\n promptCache?: boolean;\n\n /**\n * Prompt-cache checkpoint TTL (`'5m'` legacy or `'1h'` extended cache).\n */\n promptCacheTtl?: PromptCacheTtl;\n\n /**\n * Application Inference Profile ARN to use instead of model ID.\n */\n applicationInferenceProfile?: string;\n\n /**\n * Service tier for model invocation.\n */\n serviceTier?: ServiceTierType;\n\n /**\n * The configured model id, captured at construction so it survives the\n * temporary `this.model` swap to an application-inference-profile ARN during\n * generation. Used to gate the Bedrock tool cache point to Claude models\n * (see {@link supportsBedrockToolCache}).\n */\n private readonly cacheModelId: string;\n\n constructor(fields?: CustomChatBedrockConverseInput) {\n super(fields);\n this.promptCache = fields?.promptCache;\n this.promptCacheTtl = fields?.promptCacheTtl;\n this._lc_stream_delay = Math.max(0, fields?._lc_stream_delay ?? 0);\n this.applicationInferenceProfile = fields?.applicationInferenceProfile;\n this.serviceTier = fields?.serviceTier;\n // `super(fields)` initializes `this.model` to LangChain's default Claude\n // model when `fields.model` is omitted, so fall back to it rather than ''\n // (which would treat the default Claude model as tool-cache-unsupported).\n this.cacheModelId = fields?.model ?? this.model;\n }\n\n static lc_name(): string {\n return 'LibreChatBedrockConverse';\n }\n\n /**\n * Get the model ID to use for API calls.\n * Returns applicationInferenceProfile if set, otherwise returns this.model.\n */\n protected getModelId(): string {\n return this.applicationInferenceProfile ?? this.model;\n }\n\n /**\n * Override invocationParams to add serviceTier support.\n */\n override invocationParams(\n options?: this['ParsedCallOptions'] & CustomChatBedrockConverseCallOptions\n ): ReturnType<ChatBedrockConverse['invocationParams']> & {\n serviceTier?: { type: ServiceTierType };\n } {\n const baseParams = super.invocationParams(options);\n const toolConfig =\n this.promptCache === true && supportsBedrockToolCache(this.cacheModelId)\n ? insertBedrockToolCachePoint(\n baseParams.toolConfig,\n true,\n resolveBedrockPromptCacheTtl(this.promptCacheTtl, this.cacheModelId)\n )\n : baseParams.toolConfig;\n\n /** Service tier from options or fall back to class-level setting */\n const serviceTierType = options?.serviceTier ?? this.serviceTier;\n\n return {\n ...baseParams,\n toolConfig,\n serviceTier: serviceTierType ? { type: serviceTierType } : undefined,\n };\n }\n\n /**\n * Override _generateNonStreaming to use applicationInferenceProfile as modelId.\n * Uses the same model-swapping pattern as streaming for consistency.\n */\n override async _generateNonStreaming(\n messages: BaseMessage[],\n options: this['ParsedCallOptions'] & CustomChatBedrockConverseCallOptions,\n runManager?: CallbackManagerForLLMRun\n ): Promise<ChatResult> {\n const originalModel = this.model;\n if (\n this.applicationInferenceProfile != null &&\n this.applicationInferenceProfile !== ''\n ) {\n this.model = this.applicationInferenceProfile;\n }\n\n try {\n return await super._generateNonStreaming(messages, options, runManager);\n } finally {\n this.model = originalModel;\n }\n }\n\n /**\n * Own the stream end-to-end so we have direct access to every\n * `contentBlockDelta.contentBlockIndex` from the AWS SDK.\n *\n * This replaces the parent's implementation which strips contentBlockIndex\n * from text and reasoning deltas, making it impossible to merge correctly.\n */\n override async *_streamResponseChunks(\n messages: BaseMessage[],\n options: this['ParsedCallOptions'] & CustomChatBedrockConverseCallOptions,\n runManager?: CallbackManagerForLLMRun\n ): AsyncGenerator<ChatGenerationChunk> {\n const { converseMessages, converseSystem } =\n convertToConverseMessages(messages);\n const params = this.invocationParams(options);\n\n let { streamUsage } = this;\n if ((options as Record<string, unknown>).streamUsage !== undefined) {\n streamUsage = (options as Record<string, unknown>).streamUsage as boolean;\n }\n\n const modelId = this.getModelId();\n\n applyCachePointsToConversePayload({\n cacheControl: options.cache_control,\n system: converseSystem,\n messages: converseMessages,\n params,\n modelId,\n });\n\n const command = new ConverseStreamCommand({\n modelId,\n messages: converseMessages,\n system: converseSystem,\n ...(params as Record<string, unknown>),\n });\n\n const streamAbortController = new AbortController();\n const abortStream = (): void => streamAbortController.abort();\n options.signal?.addEventListener('abort', abortStream, { once: true });\n if (isSignalAborted(options.signal)) {\n abortStream();\n }\n\n try {\n const response = await this.client.send(command, {\n abortSignal: streamAbortController.signal,\n });\n\n const stream: AsyncIterable<ConverseStreamOutput> | undefined =\n response.stream;\n if (!stream) {\n return;\n }\n\n const seenBlockIndices = new Set<number>();\n const toolUseBlockIndices = new Set<number>();\n const queuedChunks: QueuedGenerationChunk[] = [];\n const producerState: { done: boolean; error?: unknown } = { done: false };\n let queuedChunkIndex = 0;\n let bufferedTextLength = 0;\n let consumerClosed = false;\n let notifyConsumer: (() => void) | undefined;\n let notifyProducer: (() => void) | undefined;\n let hasEmittedVisibleContent = false;\n let lastVisibleContentAt: number | undefined;\n\n /**\n * Guardrails can reject an already-streamed toolUse block at\n * `messageStop` (`guardrail_intervened`), after `contentBlockStop` has\n * passed. Only emit eager-execution seals when no guardrails are\n * configured, so a later intervention can't race an eagerly started tool.\n */\n const sealToolUseOnStop =\n options.guardrailConfig == null && this.guardrailConfig == null;\n\n const notifyConsumerForChunk = (): void => {\n notifyConsumer?.();\n notifyConsumer = undefined;\n };\n\n const notifyProducerForSpace = (): void => {\n notifyProducer?.();\n notifyProducer = undefined;\n };\n\n const hasQueuedChunks = (): boolean =>\n queuedChunkIndex < queuedChunks.length;\n\n const getQueuedChunkCount = (): number =>\n queuedChunks.length - queuedChunkIndex;\n\n const isQueueAtCapacity = (): boolean =>\n getQueuedChunkCount() >= MAX_STREAM_QUEUE_CHUNKS ||\n bufferedTextLength >= MAX_STREAM_QUEUE_TEXT_CHARS;\n\n const waitForNextChunk = async (): Promise<void> => {\n if (\n hasQueuedChunks() ||\n producerState.done ||\n producerState.error != null\n ) {\n return;\n }\n await new Promise<void>((resolve) => {\n notifyConsumer = resolve;\n });\n };\n\n const waitForQueueSpace = async (): Promise<void> => {\n while (\n isQueueAtCapacity() &&\n !consumerClosed &&\n !isSignalAborted(options.signal)\n ) {\n await new Promise<void>((resolve) => {\n const signal = options.signal;\n const onAbort = (): void => {\n signal?.removeEventListener('abort', onAbort);\n resolve();\n };\n const onSpace = (): void => {\n signal?.removeEventListener('abort', onAbort);\n resolve();\n };\n notifyProducer = onSpace;\n signal?.addEventListener('abort', onAbort, { once: true });\n if (isSignalAborted(signal)) {\n onAbort();\n }\n });\n }\n };\n\n const dequeue = (): QueuedGenerationChunk | undefined => {\n if (!hasQueuedChunks()) {\n return undefined;\n }\n const queuedChunk = queuedChunks[queuedChunkIndex];\n queuedChunkIndex++;\n if (\n queuedChunkIndex > 128 &&\n queuedChunkIndex * 2 >= queuedChunks.length\n ) {\n queuedChunks.splice(0, queuedChunkIndex);\n queuedChunkIndex = 0;\n }\n return queuedChunk;\n };\n\n const enqueue = async (\n queuedChunk: QueuedGenerationChunk\n ): Promise<void> => {\n await waitForQueueSpace();\n if (consumerClosed || isSignalAborted(options.signal)) {\n abortStream();\n throw new Error('AbortError: User aborted the request.');\n }\n queuedChunks.push(queuedChunk);\n if (queuedChunk.smooth) {\n bufferedTextLength += queuedChunk.textLength;\n }\n notifyConsumerForChunk();\n };\n\n const enqueueChunk = async ({\n chunk,\n callbackChunk,\n callbackToken = '',\n smooth = false,\n textLength = 0,\n }: {\n chunk: ChatGenerationChunk;\n callbackChunk?: ChatGenerationChunk;\n callbackToken?: string;\n smooth?: boolean;\n textLength?: number;\n }): Promise<void> => {\n await enqueue({\n chunk,\n callbackChunk,\n callbackToken,\n smooth,\n textLength: smooth ? textLength : 0,\n });\n };\n\n const enqueueDelta = async (\n contentBlockDelta: ContentBlockDeltaEvent\n ): Promise<void> => {\n const delta = contentBlockDelta.delta;\n if (delta == null) {\n throw new Error('No delta found in content block.');\n }\n\n const idx = contentBlockDelta.contentBlockIndex;\n if (idx != null) {\n seenBlockIndices.add(idx);\n }\n\n const text = delta.text;\n const reasoningContent = delta.reasoningContent;\n const reasoningText = reasoningContent?.text;\n const visibleText = resolveVisibleText(text, reasoningText);\n const smooth = this._lc_stream_delay > 0 && visibleText !== '';\n const tokenChunks = smooth\n ? splitStreamToken(visibleText)\n : [visibleText];\n\n for (const token of tokenChunks) {\n let splitDelta = contentBlockDelta;\n if (typeof text === 'string') {\n splitDelta = {\n ...contentBlockDelta,\n delta: { text: token },\n };\n } else if (\n typeof reasoningText === 'string' &&\n reasoningContent != null\n ) {\n splitDelta = {\n ...contentBlockDelta,\n delta: {\n reasoningContent: {\n ...reasoningContent,\n text: token,\n },\n },\n };\n }\n\n const deltaChunk = handleConverseStreamContentBlockDelta(splitDelta);\n await enqueueChunk({\n chunk: this.enrichChunk(deltaChunk, seenBlockIndices),\n callbackChunk: deltaChunk,\n callbackToken: deltaChunk.text,\n smooth,\n textLength: token.length,\n });\n }\n };\n\n const producer = (async (): Promise<void> => {\n try {\n for await (const event of stream) {\n if (isSignalAborted(options.signal)) {\n abortStream();\n throw new Error('AbortError: User aborted the request.');\n }\n\n if (event.contentBlockStart != null) {\n const startChunk = handleConverseStreamContentBlockStart(\n event.contentBlockStart\n );\n if (startChunk != null) {\n const idx = event.contentBlockStart.contentBlockIndex;\n if (idx != null) {\n seenBlockIndices.add(idx);\n if (event.contentBlockStart.start?.toolUse != null) {\n toolUseBlockIndices.add(idx);\n }\n }\n await enqueueChunk({\n chunk: this.enrichChunk(startChunk, seenBlockIndices),\n callbackChunk: startChunk,\n callbackToken: startChunk.text,\n });\n }\n } else if (event.contentBlockDelta != null) {\n await enqueueDelta(event.contentBlockDelta);\n } else if (event.metadata != null) {\n await enqueueChunk({\n chunk: handleConverseStreamMetadata(event.metadata, {\n streamUsage,\n }),\n });\n } else if (event.contentBlockStop != null) {\n const stopIdx = event.contentBlockStop.contentBlockIndex;\n if (stopIdx != null) {\n seenBlockIndices.add(stopIdx);\n if (sealToolUseOnStop && toolUseBlockIndices.has(stopIdx)) {\n const sealChunk = createConverseToolUseStopChunk(stopIdx);\n await enqueueChunk({\n chunk: sealChunk,\n callbackChunk: sealChunk,\n callbackToken: sealChunk.text,\n });\n }\n }\n } else {\n await enqueueChunk({\n chunk: new ChatGenerationChunk({\n text: '',\n message: new AIMessageChunk({\n content: '',\n response_metadata: { ...event } as ResponseMetadata,\n }),\n }),\n });\n }\n }\n } catch (error) {\n producerState.error = error;\n } finally {\n producerState.done = true;\n notifyConsumerForChunk();\n }\n })();\n\n try {\n let keepStreaming = true;\n while (keepStreaming) {\n if (isSignalAborted(options.signal)) {\n abortStream();\n throw new Error('AbortError: User aborted the request.');\n }\n\n await waitForNextChunk();\n const queuedChunk = dequeue();\n\n if (!queuedChunk) {\n if (producerState.error != null) {\n throw producerState.error;\n }\n if (producerState.done) {\n keepStreaming = false;\n }\n continue;\n }\n\n if (queuedChunk.smooth) {\n bufferedTextLength = Math.max(\n 0,\n bufferedTextLength - queuedChunk.textLength\n );\n notifyProducerForSpace();\n await waitForStreamDelay(\n getCadencedStreamDelay({\n targetDelay: hasEmittedVisibleContent\n ? this._lc_stream_delay\n : 0,\n lastVisibleContentAt,\n now: Date.now(),\n }),\n options.signal\n );\n if (isSignalAborted(options.signal)) {\n abortStream();\n throw new Error('AbortError: User aborted the request.');\n }\n hasEmittedVisibleContent = true;\n lastVisibleContentAt = Date.now();\n } else {\n notifyProducerForSpace();\n }\n\n yield queuedChunk.chunk;\n\n if (queuedChunk.callbackChunk != null) {\n await runManager?.handleLLMNewToken(\n queuedChunk.callbackToken,\n undefined,\n undefined,\n undefined,\n undefined,\n { chunk: queuedChunk.callbackChunk }\n );\n }\n }\n } finally {\n consumerClosed = true;\n if (!producerState.done) {\n abortStream();\n notifyProducerForSpace();\n }\n await producer;\n }\n } finally {\n options.signal?.removeEventListener('abort', abortStream);\n if (!streamAbortController.signal.aborted) {\n streamAbortController.abort();\n }\n }\n }\n\n /**\n * Inject `index` on content blocks for proper merge behaviour, then strip\n * `contentBlockIndex` from response_metadata to prevent `_mergeDicts` conflicts.\n *\n * Text string content is promoted to array form only when the stream contains\n * multiple content block indices (e.g. reasoning at index 0, text at index 1),\n * ensuring text merges correctly with the already-array accumulated content.\n */\n private enrichChunk(\n chunk: ChatGenerationChunk,\n seenBlockIndices: Set<number>\n ): ChatGenerationChunk {\n const message = chunk.message;\n if (!(message instanceof AIMessageChunk)) {\n return chunk;\n }\n\n const metadata = message.response_metadata as Record<string, unknown>;\n const blockIndex = this.extractContentBlockIndex(metadata);\n const hasMetadataIndex = blockIndex != null;\n\n let content: AIMessageChunk['content'] = message.content;\n let contentModified = false;\n\n if (Array.isArray(content) && blockIndex != null) {\n content = content.map((block) =>\n typeof block === 'object' && !('index' in block)\n ? { ...block, index: blockIndex }\n : block\n );\n contentModified = true;\n } else if (\n typeof content === 'string' &&\n content !== '' &&\n blockIndex != null &&\n seenBlockIndices.size > 1\n ) {\n content = [{ type: 'text', text: content, index: blockIndex }];\n contentModified = true;\n }\n\n if (!contentModified && !hasMetadataIndex) {\n return chunk;\n }\n\n const cleanedMetadata = hasMetadataIndex\n ? (this.removeContentBlockIndex(metadata) as Record<string, unknown>)\n : metadata;\n\n return new ChatGenerationChunk({\n text: chunk.text,\n message: new AIMessageChunk({\n ...message,\n content,\n response_metadata: cleanedMetadata,\n }),\n generationInfo: chunk.generationInfo,\n });\n }\n\n /**\n * Extract `contentBlockIndex` from the top level of response_metadata.\n * Our custom handlers always place it at the top level.\n */\n private extractContentBlockIndex(\n metadata: Record<string, unknown>\n ): number | undefined {\n if (\n 'contentBlockIndex' in metadata &&\n typeof metadata.contentBlockIndex === 'number'\n ) {\n return metadata.contentBlockIndex;\n }\n return undefined;\n }\n\n private removeContentBlockIndex(obj: unknown): unknown {\n if (obj === null || obj === undefined) {\n return obj;\n }\n\n if (Array.isArray(obj)) {\n return obj.map((item) => this.removeContentBlockIndex(item));\n }\n\n if (typeof obj === 'object') {\n const cleaned: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(obj)) {\n if (key !== 'contentBlockIndex') {\n cleaned[key] = this.removeContentBlockIndex(value);\n }\n }\n return cleaned;\n }\n\n return obj;\n }\n}\n\nexport type { ChatBedrockConverseInput };\n"],"mappings":"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"}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { getContextOverflowInfo } from "../utils/errors.mjs";
|
|
2
|
+
//#region src/llm/contextOverflowRecovery.ts
|
|
3
|
+
/** Fraction of the previous budget used when the provider named no ceiling. */
|
|
4
|
+
const BLIND_SHRINK_RATIO = .7;
|
|
5
|
+
/** Slack left below a known ceiling so the retry is not sized to the edge. */
|
|
6
|
+
const CEILING_HEADROOM_RATIO = .95;
|
|
7
|
+
/**
|
|
8
|
+
* Fallback floor, used only when the instruction size is unknown. When it is
|
|
9
|
+
* known the floor is derived from it instead, so a genuinely small model — a
|
|
10
|
+
* 4k window, where a 95%-of-ceiling budget lands below this constant — can
|
|
11
|
+
* still recover.
|
|
12
|
+
*/
|
|
13
|
+
const MIN_RECOVERY_BUDGET_TOKENS = 4e3;
|
|
14
|
+
/**
|
|
15
|
+
* Room a corrected budget must leave above the instructions for the messages
|
|
16
|
+
* themselves. Without it, a budget that merely clears the system prompt and
|
|
17
|
+
* tool schemas is not a budget anything can be compacted into.
|
|
18
|
+
*/
|
|
19
|
+
const MIN_MESSAGE_HEADROOM_TOKENS = 2e3;
|
|
20
|
+
function isUsable(value) {
|
|
21
|
+
return value != null && Number.isFinite(value) && value > 0;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Converts a provider-space retry budget into the units consumed by a pruner
|
|
25
|
+
* calibrated for another provider while preserving the same raw-token limit.
|
|
26
|
+
*/
|
|
27
|
+
function translateRecoveryBudget(budgetTokens, sourceCalibrationRatio, targetCalibrationRatio) {
|
|
28
|
+
if (!isUsable(budgetTokens) || !isUsable(sourceCalibrationRatio) || !isUsable(targetCalibrationRatio)) return budgetTokens;
|
|
29
|
+
return Math.floor(budgetTokens * targetCalibrationRatio / sourceCalibrationRatio);
|
|
30
|
+
}
|
|
31
|
+
/** Applies the conservative shrink used when no provider-space ceiling is usable. */
|
|
32
|
+
function getBlindRecoveryBudget(maxContextTokens) {
|
|
33
|
+
return isUsable(maxContextTokens) ? Math.floor(maxContextTokens * BLIND_SHRINK_RATIO) : void 0;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* The completion allowance the provider counted against the same ceiling,
|
|
37
|
+
* when it reported both the total and the prompt portion. The retry budget
|
|
38
|
+
* governs the prompt only, so this has to come off the ceiling first —
|
|
39
|
+
* otherwise a large `maxTokens` keeps the request over the limit no matter
|
|
40
|
+
* how far the prompt is compacted.
|
|
41
|
+
*/
|
|
42
|
+
function reservedForCompletion(info, configuredCompletionTokens) {
|
|
43
|
+
if (isUsable(info.requestedTokens) && isUsable(info.promptTokens)) {
|
|
44
|
+
const difference = info.requestedTokens - info.promptTokens;
|
|
45
|
+
if (difference > 0) return difference;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* No breakdown on offer. Fall back to what the caller configured, because
|
|
49
|
+
* the provider still counts it: targeting the whole ceiling would leave the
|
|
50
|
+
* retry at `prompt + maxTokens` and over the limit however far the prompt
|
|
51
|
+
* is compacted.
|
|
52
|
+
*/
|
|
53
|
+
return isUsable(configuredCompletionTokens) ? configuredCompletionTokens : 0;
|
|
54
|
+
}
|
|
55
|
+
function resolveTargetBudget(info, maxContextTokens, estimatedPromptTokens, configuredCompletionTokens) {
|
|
56
|
+
if (isUsable(info.limitTokens)) {
|
|
57
|
+
const promptCeiling = info.limitTokens - reservedForCompletion(info, configuredCompletionTokens);
|
|
58
|
+
/**
|
|
59
|
+
* A completion allowance at or above the ceiling leaves nothing for the
|
|
60
|
+
* prompt: even an empty one plus the requested output overruns the limit.
|
|
61
|
+
* Compaction cannot fix that, so declining surfaces the real problem
|
|
62
|
+
* instead of burning the recovery budget on retries that must fail.
|
|
63
|
+
*/
|
|
64
|
+
if (promptCeiling <= 0) return null;
|
|
65
|
+
return promptCeiling * CEILING_HEADROOM_RATIO;
|
|
66
|
+
}
|
|
67
|
+
if (isUsable(estimatedPromptTokens)) return estimatedPromptTokens * BLIND_SHRINK_RATIO;
|
|
68
|
+
if (isUsable(maxContextTokens)) return maxContextTokens * BLIND_SHRINK_RATIO;
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Decides whether a failed model call is a recoverable context overflow and,
|
|
73
|
+
* if so, what budget the retry should be re-pruned against.
|
|
74
|
+
*
|
|
75
|
+
* Returns `null` when the error is something compaction cannot fix, or when
|
|
76
|
+
* the per-run recovery budget is spent — in both cases the caller should let
|
|
77
|
+
* its normal failure handling proceed.
|
|
78
|
+
*/
|
|
79
|
+
function planContextOverflowRecovery({ error, provider, maxContextTokens, estimatedPromptTokens, calibrationRatio, instructionTokens, canSummarize = false, configuredCompletionTokens, attemptsSoFar, maxAttempts = 2 }) {
|
|
80
|
+
if (attemptsSoFar >= maxAttempts) return null;
|
|
81
|
+
const info = getContextOverflowInfo(error, {
|
|
82
|
+
provider,
|
|
83
|
+
estimatedPromptTokens,
|
|
84
|
+
maxContextTokens
|
|
85
|
+
});
|
|
86
|
+
if (info == null) return null;
|
|
87
|
+
const currentCalibrationRatio = isUsable(calibrationRatio) ? calibrationRatio : 1;
|
|
88
|
+
const estimatedMessageTokens = isUsable(estimatedPromptTokens) && isUsable(instructionTokens) ? estimatedPromptTokens - instructionTokens : estimatedPromptTokens;
|
|
89
|
+
const observedMessageTokens = isUsable(info.promptTokens) && isUsable(instructionTokens) ? info.promptTokens - instructionTokens : info.promptTokens;
|
|
90
|
+
const observedCalibrationRatio = isUsable(observedMessageTokens) && isUsable(estimatedMessageTokens) ? observedMessageTokens / estimatedMessageTokens * currentCalibrationRatio : void 0;
|
|
91
|
+
const target = resolveTargetBudget(info, maxContextTokens, estimatedPromptTokens, configuredCompletionTokens);
|
|
92
|
+
if (target == null) return isUsable(info.limitTokens) || isUsable(maxContextTokens) || isUsable(estimatedPromptTokens) ? null : {
|
|
93
|
+
info,
|
|
94
|
+
observedCalibrationRatio,
|
|
95
|
+
budgetTokens: void 0
|
|
96
|
+
};
|
|
97
|
+
/**
|
|
98
|
+
* A retry that does not actually shrink the prompt would just reproduce the
|
|
99
|
+
* same rejection, so a ceiling that lands at or above the budget we already
|
|
100
|
+
* had is replaced by a blind shrink.
|
|
101
|
+
*/
|
|
102
|
+
const bounded = isUsable(maxContextTokens) && target >= maxContextTokens ? getBlindRecoveryBudget(maxContextTokens) ?? target : target;
|
|
103
|
+
const budgetTokens = Math.floor(bounded);
|
|
104
|
+
/**
|
|
105
|
+
* Below the floor there is no usable budget left: either nothing survives
|
|
106
|
+
* pruning, or the instructions alone fill the window, in which case the
|
|
107
|
+
* summarize node refuses to run and the detour would bounce between the
|
|
108
|
+
* agent and summarize nodes without ever shrinking the prompt. Declining
|
|
109
|
+
* lets the existing "instructions exceed context budget" guidance surface
|
|
110
|
+
* instead.
|
|
111
|
+
*/
|
|
112
|
+
let floorTokens = MIN_RECOVERY_BUDGET_TOKENS;
|
|
113
|
+
if (isUsable(instructionTokens)) floorTokens = instructionTokens + MIN_MESSAGE_HEADROOM_TOKENS;
|
|
114
|
+
else if (canSummarize) floorTokens = 1;
|
|
115
|
+
if (budgetTokens < floorTokens) return null;
|
|
116
|
+
/**
|
|
117
|
+
* Refuse to report a "recovery" that changes nothing: when the budget in
|
|
118
|
+
* force is already at or below the target, re-pruning cannot free space.
|
|
119
|
+
*/
|
|
120
|
+
if (isUsable(maxContextTokens) && budgetTokens >= maxContextTokens) return null;
|
|
121
|
+
return {
|
|
122
|
+
budgetTokens,
|
|
123
|
+
info,
|
|
124
|
+
observedCalibrationRatio
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
//#endregion
|
|
128
|
+
export { getBlindRecoveryBudget, planContextOverflowRecovery, translateRecoveryBudget };
|
|
129
|
+
|
|
130
|
+
//# sourceMappingURL=contextOverflowRecovery.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contextOverflowRecovery.mjs","names":[],"sources":["../../../src/llm/contextOverflowRecovery.ts"],"sourcesContent":["/**\n * Recovery policy for provider context-overflow rejections.\n *\n * Detection (`@/utils/errors`) answers \"was this an overflow, and what did\n * the provider disclose?\". This module answers the follow-up: \"what budget\n * should the retry target?\" — deliberately kept as pure functions so the\n * policy can be reasoned about and tested without a graph.\n *\n * On units: `maxContextTokens` is a **provider-space** budget. The pruner\n * converts it into its own raw estimate space by dividing by the\n * `calibrationRatio` it learns from reported usage. A provider-reported\n * ceiling is therefore applied verbatim — converting it here as well would\n * apply the same correction twice and prune toward roughly `limit / ratio²`,\n * silently discarding far more history than the overflow called for.\n *\n * `observedCalibrationRatio` is returned separately so the caller can seed\n * the pruner's conversion without folding the same correction into the\n * provider-space budget.\n */\nimport type { ContextOverflowInfo } from '@/utils/errors';\nimport type { Providers } from '@/common';\nimport { getContextOverflowInfo } from '@/utils/errors';\n\n/** Fraction of the previous budget used when the provider named no ceiling. */\nconst BLIND_SHRINK_RATIO = 0.7;\n\n/** Slack left below a known ceiling so the retry is not sized to the edge. */\nconst CEILING_HEADROOM_RATIO = 0.95;\n\n/**\n * Fallback floor, used only when the instruction size is unknown. When it is\n * known the floor is derived from it instead, so a genuinely small model — a\n * 4k window, where a 95%-of-ceiling budget lands below this constant — can\n * still recover.\n */\nconst MIN_RECOVERY_BUDGET_TOKENS = 4_000;\n\n/**\n * Room a corrected budget must leave above the instructions for the messages\n * themselves. Without it, a budget that merely clears the system prompt and\n * tool schemas is not a budget anything can be compacted into.\n */\nconst MIN_MESSAGE_HEADROOM_TOKENS = 2_000;\n\n/** Bound on forced-compaction retries per agent, per run. */\nexport const DEFAULT_MAX_OVERFLOW_RECOVERIES = 2;\n\nexport interface OverflowRecoveryPlan {\n /** Budget the retry should target, in provider token units when known. */\n budgetTokens?: number;\n /** What the provider disclosed. Carried through for logging. */\n info: ContextOverflowInfo;\n /**\n * Provider-reported message tokens divided by our own message estimate,\n * when both are known. Greater than 1 means we under-count relative to this\n * provider.\n *\n * Returned separately so the graph can seed the pruner's calibration;\n * applying it to this plan's budget as well would double-count. Fixed\n * instruction overhead is removed before deriving it from\n * `info.promptTokens`, never `info.requestedTokens`, since several providers\n * fold the completion allowance into the latter.\n */\n observedCalibrationRatio?: number;\n}\n\nexport interface OverflowRecoveryParams {\n error: unknown;\n provider: Providers;\n /** Budget in force when the rejected prompt was built. */\n maxContextTokens?: number;\n /** Our own estimate of the prompt we actually sent. */\n estimatedPromptTokens?: number;\n /** Provider/local calibration already applied to the prompt estimate. */\n calibrationRatio?: number;\n /**\n * System prompt plus tool schemas — the part of the budget compaction\n * cannot touch. A corrected budget at or below this leaves no room for\n * messages, and the summarize node refuses to run, so recovery is declined\n * rather than entered.\n */\n instructionTokens?: number;\n /** Whether a model-backed summary can compact messages without a pruner. */\n canSummarize?: boolean;\n /**\n * Completion allowance the caller configured. Providers count it against\n * the same ceiling, so it has to come off the top when the error itself did\n * not break the total down.\n */\n configuredCompletionTokens?: number;\n /** Recoveries already attempted for this agent in this run. */\n attemptsSoFar: number;\n maxAttempts?: number;\n}\n\nfunction isUsable(value: number | undefined): value is number {\n return value != null && Number.isFinite(value) && value > 0;\n}\n\n/**\n * Converts a provider-space retry budget into the units consumed by a pruner\n * calibrated for another provider while preserving the same raw-token limit.\n */\nexport function translateRecoveryBudget(\n budgetTokens: number | undefined,\n sourceCalibrationRatio: number | undefined,\n targetCalibrationRatio: number | undefined\n): number | undefined {\n if (\n !isUsable(budgetTokens) ||\n !isUsable(sourceCalibrationRatio) ||\n !isUsable(targetCalibrationRatio)\n ) {\n return budgetTokens;\n }\n return Math.floor(\n (budgetTokens * targetCalibrationRatio) / sourceCalibrationRatio\n );\n}\n\n/** Applies the conservative shrink used when no provider-space ceiling is usable. */\nexport function getBlindRecoveryBudget(\n maxContextTokens: number | undefined\n): number | undefined {\n return isUsable(maxContextTokens)\n ? Math.floor(maxContextTokens * BLIND_SHRINK_RATIO)\n : undefined;\n}\n\n/**\n * The completion allowance the provider counted against the same ceiling,\n * when it reported both the total and the prompt portion. The retry budget\n * governs the prompt only, so this has to come off the ceiling first —\n * otherwise a large `maxTokens` keeps the request over the limit no matter\n * how far the prompt is compacted.\n */\nfunction reservedForCompletion(\n info: ContextOverflowInfo,\n configuredCompletionTokens: number | undefined\n): number {\n if (isUsable(info.requestedTokens) && isUsable(info.promptTokens)) {\n const difference = info.requestedTokens - info.promptTokens;\n if (difference > 0) {\n return difference;\n }\n }\n /**\n * No breakdown on offer. Fall back to what the caller configured, because\n * the provider still counts it: targeting the whole ceiling would leave the\n * retry at `prompt + maxTokens` and over the limit however far the prompt\n * is compacted.\n */\n return isUsable(configuredCompletionTokens) ? configuredCompletionTokens : 0;\n}\n\nfunction resolveTargetBudget(\n info: ContextOverflowInfo,\n maxContextTokens: number | undefined,\n estimatedPromptTokens: number | undefined,\n configuredCompletionTokens: number | undefined\n): number | null {\n if (isUsable(info.limitTokens)) {\n const promptCeiling =\n info.limitTokens -\n reservedForCompletion(info, configuredCompletionTokens);\n /**\n * A completion allowance at or above the ceiling leaves nothing for the\n * prompt: even an empty one plus the requested output overruns the limit.\n * Compaction cannot fix that, so declining surfaces the real problem\n * instead of burning the recovery budget on retries that must fail.\n */\n if (promptCeiling <= 0) {\n return null;\n }\n\n return promptCeiling * CEILING_HEADROOM_RATIO;\n }\n if (isUsable(estimatedPromptTokens)) {\n return estimatedPromptTokens * BLIND_SHRINK_RATIO;\n }\n if (isUsable(maxContextTokens)) {\n return maxContextTokens * BLIND_SHRINK_RATIO;\n }\n return null;\n}\n\n/**\n * Decides whether a failed model call is a recoverable context overflow and,\n * if so, what budget the retry should be re-pruned against.\n *\n * Returns `null` when the error is something compaction cannot fix, or when\n * the per-run recovery budget is spent — in both cases the caller should let\n * its normal failure handling proceed.\n */\nexport function planContextOverflowRecovery({\n error,\n provider,\n maxContextTokens,\n estimatedPromptTokens,\n calibrationRatio,\n instructionTokens,\n canSummarize = false,\n configuredCompletionTokens,\n attemptsSoFar,\n maxAttempts = DEFAULT_MAX_OVERFLOW_RECOVERIES,\n}: OverflowRecoveryParams): OverflowRecoveryPlan | null {\n if (attemptsSoFar >= maxAttempts) {\n return null;\n }\n\n const info = getContextOverflowInfo(error, {\n provider,\n estimatedPromptTokens,\n maxContextTokens,\n });\n if (info == null) {\n return null;\n }\n\n const currentCalibrationRatio = isUsable(calibrationRatio)\n ? calibrationRatio\n : 1;\n const estimatedMessageTokens =\n isUsable(estimatedPromptTokens) && isUsable(instructionTokens)\n ? estimatedPromptTokens - instructionTokens\n : estimatedPromptTokens;\n const observedMessageTokens =\n isUsable(info.promptTokens) && isUsable(instructionTokens)\n ? info.promptTokens - instructionTokens\n : info.promptTokens;\n const observedCalibrationRatio =\n isUsable(observedMessageTokens) && isUsable(estimatedMessageTokens)\n ? (observedMessageTokens / estimatedMessageTokens) *\n currentCalibrationRatio\n : undefined;\n\n const target = resolveTargetBudget(\n info,\n maxContextTokens,\n estimatedPromptTokens,\n configuredCompletionTokens\n );\n if (target == null) {\n const hasNumericBasis =\n isUsable(info.limitTokens) ||\n isUsable(maxContextTokens) ||\n isUsable(estimatedPromptTokens);\n return hasNumericBasis\n ? null\n : { info, observedCalibrationRatio, budgetTokens: undefined };\n }\n\n /**\n * A retry that does not actually shrink the prompt would just reproduce the\n * same rejection, so a ceiling that lands at or above the budget we already\n * had is replaced by a blind shrink.\n */\n const bounded =\n isUsable(maxContextTokens) && target >= maxContextTokens\n ? (getBlindRecoveryBudget(maxContextTokens) ?? target)\n : target;\n\n const budgetTokens = Math.floor(bounded);\n\n /**\n * Below the floor there is no usable budget left: either nothing survives\n * pruning, or the instructions alone fill the window, in which case the\n * summarize node refuses to run and the detour would bounce between the\n * agent and summarize nodes without ever shrinking the prompt. Declining\n * lets the existing \"instructions exceed context budget\" guidance surface\n * instead.\n */\n let floorTokens = MIN_RECOVERY_BUDGET_TOKENS;\n if (isUsable(instructionTokens)) {\n floorTokens = instructionTokens + MIN_MESSAGE_HEADROOM_TOKENS;\n } else if (canSummarize) {\n floorTokens = 1;\n }\n if (budgetTokens < floorTokens) {\n return null;\n }\n\n /**\n * Refuse to report a \"recovery\" that changes nothing: when the budget in\n * force is already at or below the target, re-pruning cannot free space.\n */\n if (isUsable(maxContextTokens) && budgetTokens >= maxContextTokens) {\n return null;\n }\n\n return { budgetTokens, info, observedCalibrationRatio };\n}\n"],"mappings":";;;AAwBA,MAAM,qBAAqB;;AAG3B,MAAM,yBAAyB;;;;;;;AAQ/B,MAAM,6BAA6B;;;;;;AAOnC,MAAM,8BAA8B;AAqDpC,SAAS,SAAS,OAA4C;CAC5D,OAAO,SAAS,QAAQ,OAAO,SAAS,KAAK,KAAK,QAAQ;AAC5D;;;;;AAMA,SAAgB,wBACd,cACA,wBACA,wBACoB;CACpB,IACE,CAAC,SAAS,YAAY,KACtB,CAAC,SAAS,sBAAsB,KAChC,CAAC,SAAS,sBAAsB,GAEhC,OAAO;CAET,OAAO,KAAK,MACT,eAAe,yBAA0B,sBAC5C;AACF;;AAGA,SAAgB,uBACd,kBACoB;CACpB,OAAO,SAAS,gBAAgB,IAC5B,KAAK,MAAM,mBAAmB,kBAAkB,IAChD,KAAA;AACN;;;;;;;;AASA,SAAS,sBACP,MACA,4BACQ;CACR,IAAI,SAAS,KAAK,eAAe,KAAK,SAAS,KAAK,YAAY,GAAG;EACjE,MAAM,aAAa,KAAK,kBAAkB,KAAK;EAC/C,IAAI,aAAa,GACf,OAAO;CAEX;;;;;;;CAOA,OAAO,SAAS,0BAA0B,IAAI,6BAA6B;AAC7E;AAEA,SAAS,oBACP,MACA,kBACA,uBACA,4BACe;CACf,IAAI,SAAS,KAAK,WAAW,GAAG;EAC9B,MAAM,gBACJ,KAAK,cACL,sBAAsB,MAAM,0BAA0B;;;;;;;EAOxD,IAAI,iBAAiB,GACnB,OAAO;EAGT,OAAO,gBAAgB;CACzB;CACA,IAAI,SAAS,qBAAqB,GAChC,OAAO,wBAAwB;CAEjC,IAAI,SAAS,gBAAgB,GAC3B,OAAO,mBAAmB;CAE5B,OAAO;AACT;;;;;;;;;AAUA,SAAgB,4BAA4B,EAC1C,OACA,UACA,kBACA,uBACA,kBACA,mBACA,eAAe,OACf,4BACA,eACA,cAAA,KACsD;CACtD,IAAI,iBAAiB,aACnB,OAAO;CAGT,MAAM,OAAO,uBAAuB,OAAO;EACzC;EACA;EACA;CACF,CAAC;CACD,IAAI,QAAQ,MACV,OAAO;CAGT,MAAM,0BAA0B,SAAS,gBAAgB,IACrD,mBACA;CACJ,MAAM,yBACJ,SAAS,qBAAqB,KAAK,SAAS,iBAAiB,IACzD,wBAAwB,oBACxB;CACN,MAAM,wBACJ,SAAS,KAAK,YAAY,KAAK,SAAS,iBAAiB,IACrD,KAAK,eAAe,oBACpB,KAAK;CACX,MAAM,2BACJ,SAAS,qBAAqB,KAAK,SAAS,sBAAsB,IAC7D,wBAAwB,yBACzB,0BACA,KAAA;CAEN,MAAM,SAAS,oBACb,MACA,kBACA,uBACA,0BACF;CACA,IAAI,UAAU,MAKZ,OAHE,SAAS,KAAK,WAAW,KACzB,SAAS,gBAAgB,KACzB,SAAS,qBAAqB,IAE5B,OACA;EAAE;EAAM;EAA0B,cAAc,KAAA;CAAU;;;;;;CAQhE,MAAM,UACJ,SAAS,gBAAgB,KAAK,UAAU,mBACnC,uBAAuB,gBAAgB,KAAK,SAC7C;CAEN,MAAM,eAAe,KAAK,MAAM,OAAO;;;;;;;;;CAUvC,IAAI,cAAc;CAClB,IAAI,SAAS,iBAAiB,GAC5B,cAAc,oBAAoB;MAC7B,IAAI,cACT,cAAc;CAEhB,IAAI,eAAe,aACjB,OAAO;;;;;CAOT,IAAI,SAAS,gBAAgB,KAAK,gBAAgB,kBAChD,OAAO;CAGT,OAAO;EAAE;EAAc;EAAM;CAAyB;AACxD"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { convertBaseMessagesToContent, convertResponseContentToChatGenerationChunk, dropUnsupportedModelTurnPrefill, mapGenerateContentResultToChatResult } from "./utils/common.mjs";
|
|
2
2
|
import { AIMessageChunk } from "@langchain/core/messages";
|
|
3
3
|
import { ChatGenerationChunk } from "@langchain/core/outputs";
|
|
4
|
-
import { getEnvironmentVariable } from "@langchain/core/utils/env";
|
|
5
4
|
import { ChatGoogleGenerativeAI } from "@langchain/google-genai";
|
|
5
|
+
import { getEnvironmentVariable } from "@langchain/core/utils/env";
|
|
6
6
|
import { FunctionCallingMode, GoogleGenerativeAI } from "@google/generative-ai";
|
|
7
7
|
//#region src/llm/google/index.ts
|
|
8
8
|
var CustomChatGoogleGenerativeAI = class extends ChatGoogleGenerativeAI {
|
package/dist/esm/llm/invoke.mjs
CHANGED
|
@@ -4,6 +4,7 @@ import { modifyDeltaProperties } from "../messages/core.mjs";
|
|
|
4
4
|
import "../messages/index.mjs";
|
|
5
5
|
import { annotateMessagesForLLM } from "../tools/toolOutputReferences.mjs";
|
|
6
6
|
import { ChatModelStreamHandler } from "../stream.mjs";
|
|
7
|
+
import { getContextOverflowInfo } from "../utils/errors.mjs";
|
|
7
8
|
import { assertNotTruncatedToolCall } from "./truncation.mjs";
|
|
8
9
|
import { manualToolStreamProviders } from "./providers.mjs";
|
|
9
10
|
import { initializeModel } from "./init.mjs";
|
|
@@ -146,6 +147,22 @@ async function attemptInvoke({ model, messages, provider, context, onChunk }, co
|
|
|
146
147
|
assertNotTruncatedToolCall(finalMessage, provider);
|
|
147
148
|
return { messages: [finalMessage] };
|
|
148
149
|
}
|
|
150
|
+
const fallbackErrorContexts = /* @__PURE__ */ new WeakMap();
|
|
151
|
+
const fallbackOverflowCandidates = /* @__PURE__ */ new WeakMap();
|
|
152
|
+
function attachFallbackErrorContext(error, fallbackContext) {
|
|
153
|
+
if (typeof error !== "object" || error === null) return;
|
|
154
|
+
fallbackErrorContexts.set(error, fallbackContext);
|
|
155
|
+
}
|
|
156
|
+
/** Reads back the fallback attribution attached by `tryFallbackProviders`. */
|
|
157
|
+
function getFallbackErrorContext(error) {
|
|
158
|
+
if (typeof error !== "object" || error === null) return;
|
|
159
|
+
return fallbackErrorContexts.get(error);
|
|
160
|
+
}
|
|
161
|
+
/** Returns every fallback overflow retained from an exhausted provider chain. */
|
|
162
|
+
function getFallbackOverflowCandidates(error) {
|
|
163
|
+
if (typeof error !== "object" || error === null) return [];
|
|
164
|
+
return [...fallbackOverflowCandidates.get(error) ?? []];
|
|
165
|
+
}
|
|
149
166
|
/**
|
|
150
167
|
* Best-effort read of the configured model name from client options.
|
|
151
168
|
* Providers disagree on the key (`model` vs `modelName`).
|
|
@@ -157,10 +174,24 @@ function extractClientOptionsModel(clientOptions) {
|
|
|
157
174
|
}
|
|
158
175
|
/**
|
|
159
176
|
* Attempts each fallback provider in order until one succeeds.
|
|
160
|
-
*
|
|
177
|
+
*
|
|
178
|
+
* When every fallback fails, a context overflow among them is thrown in
|
|
179
|
+
* preference to whichever failure happened to come last. An overflow is the
|
|
180
|
+
* one failure the caller can act on — it compacts and retries — and losing it
|
|
181
|
+
* behind a later unrelated error would surface a dead end instead. Ordinary
|
|
182
|
+
* failures still throw last-error-wins.
|
|
161
183
|
*/
|
|
162
|
-
async function tryFallbackProviders({ fallbacks, tools, messages, config, primaryError, context, onChunk }) {
|
|
184
|
+
async function tryFallbackProviders({ fallbacks, tools, messages, config, primaryError, context, onChunk, overflowContext }) {
|
|
185
|
+
const isOverflow = (error, contextOverride = overflowContext) => getContextOverflowInfo(error, contextOverride) != null;
|
|
163
186
|
let lastError = primaryError;
|
|
187
|
+
/**
|
|
188
|
+
* Tracked apart from the primary's overflow. A caller reaching this
|
|
189
|
+
* function with an overflowing primary has already failed to recover from
|
|
190
|
+
* it, so a fallback overflow — which may sit against a different window and
|
|
191
|
+
* output allowance — is the more useful of the two to surface.
|
|
192
|
+
*/
|
|
193
|
+
const overflowCandidates = [];
|
|
194
|
+
const primaryOverflowError = isOverflow(primaryError) ? primaryError : void 0;
|
|
164
195
|
for (const fb of fallbacks) try {
|
|
165
196
|
const fbModel = initializeModel({
|
|
166
197
|
provider: fb.provider,
|
|
@@ -191,11 +222,35 @@ async function tryFallbackProviders({ fallbacks, tools, messages, config, primar
|
|
|
191
222
|
}, fbConfig);
|
|
192
223
|
} catch (e) {
|
|
193
224
|
lastError = e;
|
|
225
|
+
if (isOverflow(e, {
|
|
226
|
+
provider: fb.provider,
|
|
227
|
+
maxContextTokens: fb.maxContextTokens,
|
|
228
|
+
...overflowContext?.provider === fb.provider ? { estimatedPromptTokens: overflowContext.estimatedPromptTokens } : {}
|
|
229
|
+
})) {
|
|
230
|
+
const errorContext = {
|
|
231
|
+
provider: fb.provider,
|
|
232
|
+
clientOptions: fb.clientOptions,
|
|
233
|
+
maxContextTokens: fb.maxContextTokens
|
|
234
|
+
};
|
|
235
|
+
attachFallbackErrorContext(e, errorContext);
|
|
236
|
+
overflowCandidates.push({
|
|
237
|
+
error: e,
|
|
238
|
+
context: errorContext
|
|
239
|
+
});
|
|
240
|
+
}
|
|
194
241
|
continue;
|
|
195
242
|
}
|
|
196
|
-
|
|
243
|
+
/**
|
|
244
|
+
* Preference order: a fallback overflow, then the primary's overflow, then
|
|
245
|
+
* whichever failure came last. An overflow is the only one of the three a
|
|
246
|
+
* caller can act on, and the fallback's carries the client attribution that
|
|
247
|
+
* makes a correct retry budget possible.
|
|
248
|
+
*/
|
|
249
|
+
const preferred = overflowCandidates[0]?.error ?? primaryOverflowError ?? lastError;
|
|
250
|
+
if (overflowCandidates.length > 0 && typeof preferred === "object" && preferred !== null) fallbackOverflowCandidates.set(preferred, overflowCandidates);
|
|
251
|
+
if (preferred !== void 0) throw preferred;
|
|
197
252
|
}
|
|
198
253
|
//#endregion
|
|
199
|
-
export { attemptInvoke, tryFallbackProviders };
|
|
254
|
+
export { attemptInvoke, getFallbackErrorContext, getFallbackOverflowCandidates, tryFallbackProviders };
|
|
200
255
|
|
|
201
256
|
//# sourceMappingURL=invoke.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"invoke.mjs","names":[],"sources":["../../../src/llm/invoke.ts"],"sourcesContent":["import { concat } from '@langchain/core/utils/stream';\nimport { AIMessageChunk } from '@langchain/core/messages';\nimport type { RunnableConfig } from '@langchain/core/runnables';\nimport type { ToolCall } from '@langchain/core/messages/tool';\nimport type { BaseMessage } from '@langchain/core/messages';\nimport type { ToolOutputReferenceRegistry } from '@/tools/toolOutputReferences';\nimport type * as t from '@/types';\nimport { annotateMessagesForLLM } from '@/tools/toolOutputReferences';\nimport { assertNotTruncatedToolCall } from '@/llm/truncation';\nimport { Constants, GraphEvents, Providers } from '@/common';\nimport { manualToolStreamProviders } from '@/llm/providers';\nimport { modifyDeltaProperties } from '@/messages';\nimport { ChatModelStreamHandler } from '@/stream';\nimport { initializeModel } from '@/llm/init';\n\n/**\n * Context passed to `attemptInvoke`. Matches the subset of Graph that\n * `ChatModelStreamHandler.handle` needs *plus* the explicit\n * `getOrCreateToolOutputRegistry()` accessor that `attemptInvoke`\n * itself calls to pull the run-scoped tool-output registry off the\n * graph and project each relevant ToolMessage into a transient\n * annotated copy before the provider call.\n *\n * The intersection is intentional: `Parameters<...>[3]` resolves\n * indirectly through the stream handler's signature (which returns\n * `StandardGraph` and already exposes the accessor since #117), but\n * stating it explicitly here surfaces the contract at the call site —\n * a developer reading `attemptInvoke` doesn't have to chase the\n * upstream handler's parameter list to discover that\n * `context?.getOrCreateToolOutputRegistry()` is a real thing. Single\n * optional chain only — the method itself is required on the\n * `StandardGraph` branch of the intersection, so the second `?.` is\n * unnecessary at the call site.\n *\n * `NonNullable<...>` strips `undefined` from the upstream parameter\n * type so the intersection doesn't collapse to `never` on the\n * undefined branch; callers express optionality via `context?:\n * InvokeContext` on the function signature instead.\n *\n * Callers without a registry (e.g. summarization) simply pass no\n * `context` and the transform safely no-ops.\n */\nexport type InvokeContext = NonNullable<\n Parameters<ChatModelStreamHandler['handle']>[3]\n> & {\n getOrCreateToolOutputRegistry?(): ToolOutputReferenceRegistry | undefined;\n};\n\n/**\n * Per-chunk callback for custom stream processing.\n * When provided, replaces the default `ChatModelStreamHandler`.\n */\nexport type OnChunk = (chunk: AIMessageChunk) => void | Promise<void>;\n\nfunction getRegisteredDefaultChatStreamHandler(\n context?: InvokeContext\n): ChatModelStreamHandler | undefined {\n const handler = context?.handlerRegistry?.getHandler(\n GraphEvents.CHAT_MODEL_STREAM\n );\n return handler instanceof ChatModelStreamHandler ? handler : undefined;\n}\n\nfunction hasReasoningDetails(chunk: AIMessageChunk): boolean {\n const reasoningDetails = chunk.additional_kwargs.reasoning_details;\n return Array.isArray(reasoningDetails) && reasoningDetails.length > 0;\n}\n\nfunction removeOpenRouterFinalReasoningReplayContent({\n current,\n next,\n provider,\n}: {\n current?: AIMessageChunk;\n next: AIMessageChunk;\n provider: Providers;\n}): AIMessageChunk {\n const content = getOpenRouterFinalReasoningContent({\n current,\n next,\n provider,\n });\n if (content == null || content === next.content) {\n return next;\n }\n\n return new AIMessageChunk(\n Object.assign({}, next, {\n content,\n })\n );\n}\n\nfunction getOpenRouterFinalReasoningContent({\n current,\n next,\n provider,\n}: {\n current?: AIMessageChunk;\n next: AIMessageChunk;\n provider: Providers;\n}): string | undefined {\n if (\n provider !== Providers.OPENROUTER ||\n current == null ||\n !hasReasoningDetails(next) ||\n typeof current.content !== 'string' ||\n current.content === '' ||\n typeof next.content !== 'string' ||\n next.content === ''\n ) {\n return undefined;\n }\n if (!next.content.startsWith(current.content)) {\n return next.content;\n }\n return next.content.slice(current.content.length);\n}\n\nfunction removeReasoningDetails(\n additionalKwargs: AIMessageChunk['additional_kwargs']\n): AIMessageChunk['additional_kwargs'] {\n return Object.fromEntries(\n Object.entries(additionalKwargs).filter(\n ([key]) => key !== 'reasoning_details'\n )\n );\n}\n\nfunction getStreamHandlingChunk({\n current,\n next,\n provider,\n}: {\n current?: AIMessageChunk;\n next: AIMessageChunk;\n provider: Providers;\n}): AIMessageChunk | undefined {\n const content = getOpenRouterFinalReasoningContent({\n current,\n next,\n provider,\n });\n if (content == null) {\n return next;\n }\n if (content === '') {\n return undefined;\n }\n return new AIMessageChunk(\n Object.assign({}, next, {\n content,\n additional_kwargs: removeReasoningDetails(next.additional_kwargs),\n })\n );\n}\n\nfunction appendStreamChunk({\n current,\n next,\n provider,\n}: {\n current?: AIMessageChunk;\n next: AIMessageChunk;\n provider: Providers;\n}): AIMessageChunk {\n if (current == null) {\n return next;\n }\n return concat(\n current,\n removeOpenRouterFinalReasoningReplayContent({ current, next, provider })\n );\n}\n\n/**\n * Invokes a chat model with the given messages, handling both streaming and\n * non-streaming paths.\n *\n * By default, stream chunks are processed through a `ChatModelStreamHandler`\n * that dispatches run steps (MESSAGE_CREATION, TOOL_CALLS) for the graph.\n * Pass an `onChunk` callback to override this with custom chunk processing\n * (e.g. summarization delta events).\n */\nexport async function attemptInvoke(\n {\n model,\n messages,\n provider,\n context,\n onChunk,\n }: {\n model: t.ChatModel;\n messages: BaseMessage[];\n provider: Providers;\n context?: InvokeContext;\n onChunk?: OnChunk;\n },\n config?: RunnableConfig\n): Promise<Partial<t.BaseGraphState>> {\n /**\n * Pull the run-scoped tool output registry off the graph (when one\n * exists) and project ToolMessages carrying ref metadata into a\n * transient annotated copy. The original `messages` array stays\n * untouched so the graph state never sees `[ref: …]` / `_ref`\n * payload.\n */\n const registry = context?.getOrCreateToolOutputRegistry();\n const runId = config?.configurable?.run_id as string | undefined;\n const messagesForProvider = annotateMessagesForLLM(messages, registry, runId);\n\n /**\n * Stamp the provider that is ACTUALLY serving this invocation onto the\n * callback metadata. `attemptInvoke` is the single funnel for primary,\n * fallback, and summarization model calls, so consumers that need\n * provider attribution per call (the subagent usage-capture handler)\n * read this key instead of trusting static agent config — which is\n * wrong for fallback-served calls — or `ls_provider` — which derived\n * providers inherit from their base class.\n */\n config = {\n ...config,\n metadata: {\n ...(config?.metadata ?? {}),\n [Constants.INVOKED_PROVIDER]: provider,\n },\n };\n\n if (model.stream) {\n const stream = await model.stream(messagesForProvider, config);\n let finalChunk: AIMessageChunk | undefined;\n const registeredStreamHandler =\n getRegisteredDefaultChatStreamHandler(context);\n\n if (onChunk) {\n for await (const chunk of stream) {\n await onChunk(chunk);\n finalChunk = appendStreamChunk({\n current: finalChunk,\n next: chunk,\n provider,\n });\n }\n } else if (registeredStreamHandler == null) {\n const metadata = config.metadata as Record<string, unknown> | undefined;\n const streamHandler = new ChatModelStreamHandler();\n for await (const chunk of stream) {\n const handlingChunk = getStreamHandlingChunk({\n current: finalChunk,\n next: chunk,\n provider,\n });\n if (handlingChunk != null) {\n await streamHandler.handle(\n GraphEvents.CHAT_MODEL_STREAM,\n { chunk: handlingChunk },\n metadata,\n context\n );\n }\n finalChunk = appendStreamChunk({\n current: finalChunk,\n next: chunk,\n provider,\n });\n }\n } else {\n const metadata = config.metadata as Record<string, unknown> | undefined;\n for await (const chunk of stream) {\n const handlingChunk = getStreamHandlingChunk({\n current: finalChunk,\n next: chunk,\n provider,\n });\n if (handlingChunk != null && handlingChunk !== chunk) {\n await registeredStreamHandler.handle(\n GraphEvents.CHAT_MODEL_STREAM,\n { chunk: handlingChunk },\n metadata,\n context\n );\n }\n finalChunk = appendStreamChunk({\n current: finalChunk,\n next: chunk,\n provider,\n });\n }\n }\n\n if (manualToolStreamProviders.has(provider)) {\n finalChunk = modifyDeltaProperties(provider, finalChunk);\n }\n\n if ((finalChunk?.tool_calls?.length ?? 0) > 0) {\n finalChunk!.tool_calls = finalChunk!.tool_calls?.filter(\n (tool_call: ToolCall) => !!tool_call.name\n );\n }\n\n assertNotTruncatedToolCall(finalChunk, provider);\n return { messages: [finalChunk as AIMessageChunk] };\n }\n\n const finalMessage = await model.invoke(messagesForProvider, config);\n if ((finalMessage.tool_calls?.length ?? 0) > 0) {\n finalMessage.tool_calls = finalMessage.tool_calls?.filter(\n (tool_call: ToolCall) => !!tool_call.name\n );\n }\n assertNotTruncatedToolCall(finalMessage, provider);\n return { messages: [finalMessage] };\n}\n\n/**\n * Best-effort read of the configured model name from client options.\n * Providers disagree on the key (`model` vs `modelName`).\n */\nfunction extractClientOptionsModel(\n clientOptions: t.ClientOptions | undefined\n): string | undefined {\n const options = clientOptions as\n | { model?: unknown; modelName?: unknown }\n | undefined;\n if (typeof options?.model === 'string' && options.model !== '') {\n return options.model;\n }\n if (typeof options?.modelName === 'string' && options.modelName !== '') {\n return options.modelName;\n }\n return undefined;\n}\n\n/**\n * Attempts each fallback provider in order until one succeeds.\n * Throws the last error if all fallbacks fail.\n */\nexport async function tryFallbackProviders({\n fallbacks,\n tools,\n messages,\n config,\n primaryError,\n context,\n onChunk,\n}: {\n fallbacks: Array<{ provider: Providers; clientOptions?: t.ClientOptions }>;\n tools?: t.GraphTools;\n messages: BaseMessage[];\n config?: RunnableConfig;\n primaryError: unknown;\n context?: InvokeContext;\n onChunk?: OnChunk;\n}): Promise<Partial<t.BaseGraphState> | undefined> {\n let lastError: unknown = primaryError;\n for (const fb of fallbacks) {\n try {\n const fbModel = initializeModel({\n provider: fb.provider,\n clientOptions: fb.clientOptions,\n tools,\n });\n /**\n * Stamp the fallback's configured model onto callback metadata so\n * per-call attribution (subagent usage capture) doesn't fall back to\n * the PRIMARY config's model when the provider reports no\n * `ls_model_name`. The serving provider is stamped uniformly by\n * `attemptInvoke` (`INVOKED_PROVIDER`).\n */\n const fbModelName = extractClientOptionsModel(fb.clientOptions);\n const fbConfig: RunnableConfig | undefined =\n fbModelName == null\n ? config\n : {\n ...config,\n metadata: {\n ...(config?.metadata ?? {}),\n [Constants.INVOKED_MODEL]: fbModelName,\n },\n };\n const result = await attemptInvoke(\n {\n model: fbModel as t.ChatModel,\n messages,\n provider: fb.provider,\n context,\n onChunk,\n },\n fbConfig\n );\n return result;\n } catch (e) {\n lastError = e;\n continue;\n }\n }\n if (lastError !== undefined) {\n throw lastError;\n }\n return undefined;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"invoke.mjs","names":[],"sources":["../../../src/llm/invoke.ts"],"sourcesContent":["import { concat } from '@langchain/core/utils/stream';\nimport { AIMessageChunk } from '@langchain/core/messages';\nimport type { RunnableConfig } from '@langchain/core/runnables';\nimport type { ToolCall } from '@langchain/core/messages/tool';\nimport type { BaseMessage } from '@langchain/core/messages';\nimport type { ToolOutputReferenceRegistry } from '@/tools/toolOutputReferences';\nimport type { ContextOverflowContext } from '@/utils/errors';\nimport type * as t from '@/types';\nimport { annotateMessagesForLLM } from '@/tools/toolOutputReferences';\nimport { assertNotTruncatedToolCall } from '@/llm/truncation';\nimport { Constants, GraphEvents, Providers } from '@/common';\nimport { manualToolStreamProviders } from '@/llm/providers';\nimport { getContextOverflowInfo } from '@/utils/errors';\nimport { modifyDeltaProperties } from '@/messages';\nimport { ChatModelStreamHandler } from '@/stream';\nimport { initializeModel } from '@/llm/init';\n\n/**\n * Context passed to `attemptInvoke`. Matches the subset of Graph that\n * `ChatModelStreamHandler.handle` needs *plus* the explicit\n * `getOrCreateToolOutputRegistry()` accessor that `attemptInvoke`\n * itself calls to pull the run-scoped tool-output registry off the\n * graph and project each relevant ToolMessage into a transient\n * annotated copy before the provider call.\n *\n * The intersection is intentional: `Parameters<...>[3]` resolves\n * indirectly through the stream handler's signature (which returns\n * `StandardGraph` and already exposes the accessor since #117), but\n * stating it explicitly here surfaces the contract at the call site —\n * a developer reading `attemptInvoke` doesn't have to chase the\n * upstream handler's parameter list to discover that\n * `context?.getOrCreateToolOutputRegistry()` is a real thing. Single\n * optional chain only — the method itself is required on the\n * `StandardGraph` branch of the intersection, so the second `?.` is\n * unnecessary at the call site.\n *\n * `NonNullable<...>` strips `undefined` from the upstream parameter\n * type so the intersection doesn't collapse to `never` on the\n * undefined branch; callers express optionality via `context?:\n * InvokeContext` on the function signature instead.\n *\n * Callers without a registry (e.g. summarization) simply pass no\n * `context` and the transform safely no-ops.\n */\nexport type InvokeContext = NonNullable<\n Parameters<ChatModelStreamHandler['handle']>[3]\n> & {\n getOrCreateToolOutputRegistry?(): ToolOutputReferenceRegistry | undefined;\n};\n\n/**\n * Per-chunk callback for custom stream processing.\n * When provided, replaces the default `ChatModelStreamHandler`.\n */\nexport type OnChunk = (chunk: AIMessageChunk) => void | Promise<void>;\n\nfunction getRegisteredDefaultChatStreamHandler(\n context?: InvokeContext\n): ChatModelStreamHandler | undefined {\n const handler = context?.handlerRegistry?.getHandler(\n GraphEvents.CHAT_MODEL_STREAM\n );\n return handler instanceof ChatModelStreamHandler ? handler : undefined;\n}\n\nfunction hasReasoningDetails(chunk: AIMessageChunk): boolean {\n const reasoningDetails = chunk.additional_kwargs.reasoning_details;\n return Array.isArray(reasoningDetails) && reasoningDetails.length > 0;\n}\n\nfunction removeOpenRouterFinalReasoningReplayContent({\n current,\n next,\n provider,\n}: {\n current?: AIMessageChunk;\n next: AIMessageChunk;\n provider: Providers;\n}): AIMessageChunk {\n const content = getOpenRouterFinalReasoningContent({\n current,\n next,\n provider,\n });\n if (content == null || content === next.content) {\n return next;\n }\n\n return new AIMessageChunk(\n Object.assign({}, next, {\n content,\n })\n );\n}\n\nfunction getOpenRouterFinalReasoningContent({\n current,\n next,\n provider,\n}: {\n current?: AIMessageChunk;\n next: AIMessageChunk;\n provider: Providers;\n}): string | undefined {\n if (\n provider !== Providers.OPENROUTER ||\n current == null ||\n !hasReasoningDetails(next) ||\n typeof current.content !== 'string' ||\n current.content === '' ||\n typeof next.content !== 'string' ||\n next.content === ''\n ) {\n return undefined;\n }\n if (!next.content.startsWith(current.content)) {\n return next.content;\n }\n return next.content.slice(current.content.length);\n}\n\nfunction removeReasoningDetails(\n additionalKwargs: AIMessageChunk['additional_kwargs']\n): AIMessageChunk['additional_kwargs'] {\n return Object.fromEntries(\n Object.entries(additionalKwargs).filter(\n ([key]) => key !== 'reasoning_details'\n )\n );\n}\n\nfunction getStreamHandlingChunk({\n current,\n next,\n provider,\n}: {\n current?: AIMessageChunk;\n next: AIMessageChunk;\n provider: Providers;\n}): AIMessageChunk | undefined {\n const content = getOpenRouterFinalReasoningContent({\n current,\n next,\n provider,\n });\n if (content == null) {\n return next;\n }\n if (content === '') {\n return undefined;\n }\n return new AIMessageChunk(\n Object.assign({}, next, {\n content,\n additional_kwargs: removeReasoningDetails(next.additional_kwargs),\n })\n );\n}\n\nfunction appendStreamChunk({\n current,\n next,\n provider,\n}: {\n current?: AIMessageChunk;\n next: AIMessageChunk;\n provider: Providers;\n}): AIMessageChunk {\n if (current == null) {\n return next;\n }\n return concat(\n current,\n removeOpenRouterFinalReasoningReplayContent({ current, next, provider })\n );\n}\n\n/**\n * Invokes a chat model with the given messages, handling both streaming and\n * non-streaming paths.\n *\n * By default, stream chunks are processed through a `ChatModelStreamHandler`\n * that dispatches run steps (MESSAGE_CREATION, TOOL_CALLS) for the graph.\n * Pass an `onChunk` callback to override this with custom chunk processing\n * (e.g. summarization delta events).\n */\nexport async function attemptInvoke(\n {\n model,\n messages,\n provider,\n context,\n onChunk,\n }: {\n model: t.ChatModel;\n messages: BaseMessage[];\n provider: Providers;\n context?: InvokeContext;\n onChunk?: OnChunk;\n },\n config?: RunnableConfig\n): Promise<Partial<t.BaseGraphState>> {\n /**\n * Pull the run-scoped tool output registry off the graph (when one\n * exists) and project ToolMessages carrying ref metadata into a\n * transient annotated copy. The original `messages` array stays\n * untouched so the graph state never sees `[ref: …]` / `_ref`\n * payload.\n */\n const registry = context?.getOrCreateToolOutputRegistry();\n const runId = config?.configurable?.run_id as string | undefined;\n const messagesForProvider = annotateMessagesForLLM(messages, registry, runId);\n\n /**\n * Stamp the provider that is ACTUALLY serving this invocation onto the\n * callback metadata. `attemptInvoke` is the single funnel for primary,\n * fallback, and summarization model calls, so consumers that need\n * provider attribution per call (the subagent usage-capture handler)\n * read this key instead of trusting static agent config — which is\n * wrong for fallback-served calls — or `ls_provider` — which derived\n * providers inherit from their base class.\n */\n config = {\n ...config,\n metadata: {\n ...(config?.metadata ?? {}),\n [Constants.INVOKED_PROVIDER]: provider,\n },\n };\n\n if (model.stream) {\n const stream = await model.stream(messagesForProvider, config);\n let finalChunk: AIMessageChunk | undefined;\n const registeredStreamHandler =\n getRegisteredDefaultChatStreamHandler(context);\n\n if (onChunk) {\n for await (const chunk of stream) {\n await onChunk(chunk);\n finalChunk = appendStreamChunk({\n current: finalChunk,\n next: chunk,\n provider,\n });\n }\n } else if (registeredStreamHandler == null) {\n const metadata = config.metadata as Record<string, unknown> | undefined;\n const streamHandler = new ChatModelStreamHandler();\n for await (const chunk of stream) {\n const handlingChunk = getStreamHandlingChunk({\n current: finalChunk,\n next: chunk,\n provider,\n });\n if (handlingChunk != null) {\n await streamHandler.handle(\n GraphEvents.CHAT_MODEL_STREAM,\n { chunk: handlingChunk },\n metadata,\n context\n );\n }\n finalChunk = appendStreamChunk({\n current: finalChunk,\n next: chunk,\n provider,\n });\n }\n } else {\n const metadata = config.metadata as Record<string, unknown> | undefined;\n for await (const chunk of stream) {\n const handlingChunk = getStreamHandlingChunk({\n current: finalChunk,\n next: chunk,\n provider,\n });\n if (handlingChunk != null && handlingChunk !== chunk) {\n await registeredStreamHandler.handle(\n GraphEvents.CHAT_MODEL_STREAM,\n { chunk: handlingChunk },\n metadata,\n context\n );\n }\n finalChunk = appendStreamChunk({\n current: finalChunk,\n next: chunk,\n provider,\n });\n }\n }\n\n if (manualToolStreamProviders.has(provider)) {\n finalChunk = modifyDeltaProperties(provider, finalChunk);\n }\n\n if ((finalChunk?.tool_calls?.length ?? 0) > 0) {\n finalChunk!.tool_calls = finalChunk!.tool_calls?.filter(\n (tool_call: ToolCall) => !!tool_call.name\n );\n }\n\n assertNotTruncatedToolCall(finalChunk, provider);\n return { messages: [finalChunk as AIMessageChunk] };\n }\n\n const finalMessage = await model.invoke(messagesForProvider, config);\n if ((finalMessage.tool_calls?.length ?? 0) > 0) {\n finalMessage.tool_calls = finalMessage.tool_calls?.filter(\n (tool_call: ToolCall) => !!tool_call.name\n );\n }\n assertNotTruncatedToolCall(finalMessage, provider);\n return { messages: [finalMessage] };\n}\n\n/**\n * Identifies which fallback produced an error, so a caller planning a\n * recovery can reason about the client that actually failed rather than the\n * primary's configuration — their context windows and output allowances\n * differ, which is the whole reason a fallback exists.\n */\nexport interface FallbackErrorContext {\n provider: Providers;\n clientOptions?: t.ClientOptions;\n maxContextTokens?: number;\n}\n\nexport interface FallbackOverflowCandidate {\n error: unknown;\n context: FallbackErrorContext;\n}\n\nconst fallbackErrorContexts = new WeakMap<object, FallbackErrorContext>();\nconst fallbackOverflowCandidates = new WeakMap<\n object,\n FallbackOverflowCandidate[]\n>();\n\nfunction attachFallbackErrorContext(\n error: unknown,\n fallbackContext: FallbackErrorContext\n): void {\n if (typeof error !== 'object' || error === null) {\n return;\n }\n fallbackErrorContexts.set(error, fallbackContext);\n}\n\n/** Reads back the fallback attribution attached by `tryFallbackProviders`. */\nexport function getFallbackErrorContext(\n error: unknown\n): FallbackErrorContext | undefined {\n if (typeof error !== 'object' || error === null) {\n return undefined;\n }\n return fallbackErrorContexts.get(error);\n}\n\n/** Returns every fallback overflow retained from an exhausted provider chain. */\nexport function getFallbackOverflowCandidates(\n error: unknown\n): FallbackOverflowCandidate[] {\n if (typeof error !== 'object' || error === null) {\n return [];\n }\n return [...(fallbackOverflowCandidates.get(error) ?? [])];\n}\n\n/**\n * Best-effort read of the configured model name from client options.\n * Providers disagree on the key (`model` vs `modelName`).\n */\nfunction extractClientOptionsModel(\n clientOptions: t.ClientOptions | undefined\n): string | undefined {\n const options = clientOptions as\n | { model?: unknown; modelName?: unknown }\n | undefined;\n if (typeof options?.model === 'string' && options.model !== '') {\n return options.model;\n }\n if (typeof options?.modelName === 'string' && options.modelName !== '') {\n return options.modelName;\n }\n return undefined;\n}\n\n/**\n * Attempts each fallback provider in order until one succeeds.\n *\n * When every fallback fails, a context overflow among them is thrown in\n * preference to whichever failure happened to come last. An overflow is the\n * one failure the caller can act on — it compacts and retries — and losing it\n * behind a later unrelated error would surface a dead end instead. Ordinary\n * failures still throw last-error-wins.\n */\nexport async function tryFallbackProviders({\n fallbacks,\n tools,\n messages,\n config,\n primaryError,\n context,\n onChunk,\n overflowContext,\n}: {\n fallbacks: t.FallbackConfig[];\n tools?: t.GraphTools;\n messages: BaseMessage[];\n config?: RunnableConfig;\n primaryError: unknown;\n context?: InvokeContext;\n onChunk?: OnChunk;\n /**\n * Prompt-size corroboration for signatures that are not self-describing.\n * Vertex AI's overflow is a bare `400` with no reason, so without this a\n * fallback that overflows is indistinguishable from any other 400 and would\n * be dropped in favour of whichever failure came last.\n */\n overflowContext?: ContextOverflowContext;\n}): Promise<Partial<t.BaseGraphState> | undefined> {\n const isOverflow = (\n error: unknown,\n contextOverride = overflowContext\n ): boolean => getContextOverflowInfo(error, contextOverride) != null;\n let lastError: unknown = primaryError;\n /**\n * Tracked apart from the primary's overflow. A caller reaching this\n * function with an overflowing primary has already failed to recover from\n * it, so a fallback overflow — which may sit against a different window and\n * output allowance — is the more useful of the two to surface.\n */\n const overflowCandidates: FallbackOverflowCandidate[] = [];\n const primaryOverflowError: unknown = isOverflow(primaryError)\n ? primaryError\n : undefined;\n for (const fb of fallbacks) {\n try {\n const fbModel = initializeModel({\n provider: fb.provider,\n clientOptions: fb.clientOptions,\n tools,\n });\n /**\n * Stamp the fallback's configured model onto callback metadata so\n * per-call attribution (subagent usage capture) doesn't fall back to\n * the PRIMARY config's model when the provider reports no\n * `ls_model_name`. The serving provider is stamped uniformly by\n * `attemptInvoke` (`INVOKED_PROVIDER`).\n */\n const fbModelName = extractClientOptionsModel(fb.clientOptions);\n const fbConfig: RunnableConfig | undefined =\n fbModelName == null\n ? config\n : {\n ...config,\n metadata: {\n ...(config?.metadata ?? {}),\n [Constants.INVOKED_MODEL]: fbModelName,\n },\n };\n const result = await attemptInvoke(\n {\n model: fbModel as t.ChatModel,\n messages,\n provider: fb.provider,\n context,\n onChunk,\n },\n fbConfig\n );\n return result;\n } catch (e) {\n lastError = e;\n const fallbackOverflowContext: ContextOverflowContext = {\n provider: fb.provider,\n maxContextTokens: fb.maxContextTokens,\n ...(overflowContext?.provider === fb.provider\n ? {\n estimatedPromptTokens: overflowContext.estimatedPromptTokens,\n }\n : {}),\n };\n if (isOverflow(e, fallbackOverflowContext)) {\n const errorContext: FallbackErrorContext = {\n provider: fb.provider,\n clientOptions: fb.clientOptions,\n maxContextTokens: fb.maxContextTokens,\n };\n attachFallbackErrorContext(e, errorContext);\n overflowCandidates.push({ error: e, context: errorContext });\n }\n continue;\n }\n }\n /**\n * Preference order: a fallback overflow, then the primary's overflow, then\n * whichever failure came last. An overflow is the only one of the three a\n * caller can act on, and the fallback's carries the client attribution that\n * makes a correct retry budget possible.\n */\n const preferred =\n overflowCandidates[0]?.error ?? primaryOverflowError ?? lastError;\n if (\n overflowCandidates.length > 0 &&\n typeof preferred === 'object' &&\n preferred !== null\n ) {\n fallbackOverflowCandidates.set(preferred, overflowCandidates);\n }\n if (preferred !== undefined) {\n throw preferred;\n }\n return undefined;\n}\n"],"mappings":"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"}
|