@librechat/agents 3.3.10 → 3.3.12
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/graphs/Graph.cjs +248 -27
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/langfuseToolOutputTracing.cjs +228 -16
- package/dist/cjs/langfuseToolOutputTracing.cjs.map +1 -1
- package/dist/cjs/llm/bedrock/index.cjs +13 -2
- package/dist/cjs/llm/bedrock/index.cjs.map +1 -1
- package/dist/cjs/llm/init.cjs +1 -1
- package/dist/cjs/llm/invoke.cjs +160 -43
- package/dist/cjs/llm/invoke.cjs.map +1 -1
- package/dist/cjs/llm/openai/index.cjs +190 -13
- package/dist/cjs/llm/openai/index.cjs.map +1 -1
- package/dist/cjs/llm/streamLimits.cjs +723 -0
- package/dist/cjs/llm/streamLimits.cjs.map +1 -0
- package/dist/cjs/main.cjs +12 -3
- package/dist/cjs/messages/core.cjs +592 -27
- package/dist/cjs/messages/core.cjs.map +1 -1
- package/dist/cjs/run.cjs +7 -3
- package/dist/cjs/run.cjs.map +1 -1
- package/dist/cjs/session/AgentSession.cjs +4 -1
- package/dist/cjs/session/AgentSession.cjs.map +1 -1
- package/dist/cjs/stream.cjs +105 -14
- package/dist/cjs/stream.cjs.map +1 -1
- package/dist/cjs/summarization/node.cjs +157 -37
- package/dist/cjs/summarization/node.cjs.map +1 -1
- package/dist/cjs/tools/BashExecutor.cjs +3 -2
- package/dist/cjs/tools/BashExecutor.cjs.map +1 -1
- package/dist/cjs/tools/CodeExecutor.cjs +4 -3
- package/dist/cjs/tools/CodeExecutor.cjs.map +1 -1
- package/dist/cjs/tools/ProgrammaticToolCalling.cjs +5 -3
- package/dist/cjs/tools/ProgrammaticToolCalling.cjs.map +1 -1
- package/dist/cjs/tools/ToolNode.cjs +77 -4
- package/dist/cjs/tools/ToolNode.cjs.map +1 -1
- package/dist/cjs/tools/ToolSearch.cjs +3 -2
- package/dist/cjs/tools/ToolSearch.cjs.map +1 -1
- package/dist/cjs/tools/search/crw-scraper.cjs +7 -1
- package/dist/cjs/tools/search/crw-scraper.cjs.map +1 -1
- package/dist/cjs/tools/search/crw-search.cjs +3 -1
- package/dist/cjs/tools/search/crw-search.cjs.map +1 -1
- package/dist/cjs/tools/search/firecrawl.cjs +7 -1
- package/dist/cjs/tools/search/firecrawl.cjs.map +1 -1
- package/dist/cjs/tools/search/keenable-scraper.cjs +7 -1
- package/dist/cjs/tools/search/keenable-scraper.cjs.map +1 -1
- package/dist/cjs/tools/search/keenable-search.cjs +3 -1
- package/dist/cjs/tools/search/keenable-search.cjs.map +1 -1
- package/dist/cjs/tools/search/rerankers.cjs +26 -8
- package/dist/cjs/tools/search/rerankers.cjs.map +1 -1
- package/dist/cjs/tools/search/search.cjs +30 -10
- package/dist/cjs/tools/search/search.cjs.map +1 -1
- package/dist/cjs/tools/search/serper-scraper.cjs +7 -1
- package/dist/cjs/tools/search/serper-scraper.cjs.map +1 -1
- package/dist/cjs/tools/search/tavily-scraper.cjs +7 -1
- package/dist/cjs/tools/search/tavily-scraper.cjs.map +1 -1
- package/dist/cjs/tools/search/tavily-search.cjs +3 -1
- package/dist/cjs/tools/search/tavily-search.cjs.map +1 -1
- package/dist/cjs/tools/search/tool.cjs +17 -3
- package/dist/cjs/tools/search/tool.cjs.map +1 -1
- package/dist/cjs/tools/subagent/SubagentExecutor.cjs +54 -3
- package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
- package/dist/cjs/utils/index.cjs +2 -1
- package/dist/cjs/utils/misc.cjs +12 -0
- package/dist/cjs/utils/misc.cjs.map +1 -1
- package/dist/cjs/utils/proxy.cjs +63 -0
- package/dist/cjs/utils/proxy.cjs.map +1 -0
- package/dist/esm/graphs/Graph.mjs +247 -26
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/langfuseToolOutputTracing.mjs +228 -16
- package/dist/esm/langfuseToolOutputTracing.mjs.map +1 -1
- package/dist/esm/llm/bedrock/index.mjs +13 -2
- package/dist/esm/llm/bedrock/index.mjs.map +1 -1
- package/dist/esm/llm/init.mjs +1 -1
- package/dist/esm/llm/invoke.mjs +160 -43
- package/dist/esm/llm/invoke.mjs.map +1 -1
- package/dist/esm/llm/openai/index.mjs +192 -15
- package/dist/esm/llm/openai/index.mjs.map +1 -1
- package/dist/esm/llm/streamLimits.mjs +704 -0
- package/dist/esm/llm/streamLimits.mjs.map +1 -0
- package/dist/esm/main.mjs +8 -6
- package/dist/esm/messages/core.mjs +592 -28
- package/dist/esm/messages/core.mjs.map +1 -1
- package/dist/esm/run.mjs +7 -3
- package/dist/esm/run.mjs.map +1 -1
- package/dist/esm/session/AgentSession.mjs +4 -1
- package/dist/esm/session/AgentSession.mjs.map +1 -1
- package/dist/esm/stream.mjs +105 -14
- package/dist/esm/stream.mjs.map +1 -1
- package/dist/esm/summarization/node.mjs +157 -37
- package/dist/esm/summarization/node.mjs.map +1 -1
- package/dist/esm/tools/BashExecutor.mjs +3 -2
- package/dist/esm/tools/BashExecutor.mjs.map +1 -1
- package/dist/esm/tools/CodeExecutor.mjs +4 -3
- package/dist/esm/tools/CodeExecutor.mjs.map +1 -1
- package/dist/esm/tools/ProgrammaticToolCalling.mjs +5 -3
- package/dist/esm/tools/ProgrammaticToolCalling.mjs.map +1 -1
- package/dist/esm/tools/ToolNode.mjs +77 -4
- package/dist/esm/tools/ToolNode.mjs.map +1 -1
- package/dist/esm/tools/ToolSearch.mjs +3 -2
- package/dist/esm/tools/ToolSearch.mjs.map +1 -1
- package/dist/esm/tools/search/crw-scraper.mjs +7 -1
- package/dist/esm/tools/search/crw-scraper.mjs.map +1 -1
- package/dist/esm/tools/search/crw-search.mjs +3 -1
- package/dist/esm/tools/search/crw-search.mjs.map +1 -1
- package/dist/esm/tools/search/firecrawl.mjs +7 -1
- package/dist/esm/tools/search/firecrawl.mjs.map +1 -1
- package/dist/esm/tools/search/keenable-scraper.mjs +7 -1
- package/dist/esm/tools/search/keenable-scraper.mjs.map +1 -1
- package/dist/esm/tools/search/keenable-search.mjs +3 -1
- package/dist/esm/tools/search/keenable-search.mjs.map +1 -1
- package/dist/esm/tools/search/rerankers.mjs +26 -8
- package/dist/esm/tools/search/rerankers.mjs.map +1 -1
- package/dist/esm/tools/search/search.mjs +30 -10
- package/dist/esm/tools/search/search.mjs.map +1 -1
- package/dist/esm/tools/search/serper-scraper.mjs +7 -1
- package/dist/esm/tools/search/serper-scraper.mjs.map +1 -1
- package/dist/esm/tools/search/tavily-scraper.mjs +7 -1
- package/dist/esm/tools/search/tavily-scraper.mjs.map +1 -1
- package/dist/esm/tools/search/tavily-search.mjs +3 -1
- package/dist/esm/tools/search/tavily-search.mjs.map +1 -1
- package/dist/esm/tools/search/tool.mjs +17 -3
- package/dist/esm/tools/search/tool.mjs.map +1 -1
- package/dist/esm/tools/subagent/SubagentExecutor.mjs +54 -3
- package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
- package/dist/esm/utils/index.mjs +2 -1
- package/dist/esm/utils/misc.mjs +12 -1
- package/dist/esm/utils/misc.mjs.map +1 -1
- package/dist/esm/utils/proxy.mjs +62 -0
- package/dist/esm/utils/proxy.mjs.map +1 -0
- package/dist/types/graphs/Graph.d.ts +53 -1
- package/dist/types/index.d.ts +2 -0
- package/dist/types/langfuseToolOutputTracing.d.ts +1 -0
- package/dist/types/llm/invoke.d.ts +26 -5
- package/dist/types/llm/openai/index.d.ts +3 -0
- package/dist/types/llm/streamLimits.d.ts +314 -0
- package/dist/types/messages/core.d.ts +11 -6
- package/dist/types/run.d.ts +1 -0
- package/dist/types/summarization/node.d.ts +27 -2
- package/dist/types/tools/BashExecutor.d.ts +2 -2
- package/dist/types/tools/CodeExecutor.d.ts +3 -3
- package/dist/types/tools/ToolNode.d.ts +11 -1
- package/dist/types/tools/search/crw-scraper.d.ts +2 -0
- package/dist/types/tools/search/firecrawl.d.ts +2 -0
- package/dist/types/tools/search/keenable-scraper.d.ts +2 -0
- package/dist/types/tools/search/rerankers.d.ts +9 -5
- package/dist/types/tools/search/serper-scraper.d.ts +2 -0
- package/dist/types/tools/search/tavily-scraper.d.ts +2 -0
- package/dist/types/tools/search/types.d.ts +28 -33
- package/dist/types/tools/subagent/SubagentExecutor.d.ts +44 -0
- package/dist/types/types/graph.d.ts +7 -1
- package/dist/types/types/run.d.ts +46 -1
- package/dist/types/types/tools.d.ts +21 -0
- package/dist/types/utils/index.d.ts +1 -0
- package/dist/types/utils/misc.d.ts +7 -0
- package/dist/types/utils/proxy.d.ts +31 -0
- package/package.json +2 -1
- package/src/__tests__/stream.eagerArgsDivergence.test.ts +158 -0
- package/src/__tests__/stream.eagerEventExecution.test.ts +1 -0
- package/src/__tests__/stream.streamLimits.test.ts +1982 -0
- package/src/graphs/Graph.ts +302 -27
- package/src/graphs/__tests__/Graph.breakerLifecycle.test.ts +234 -0
- package/src/index.ts +11 -0
- package/src/langfuseToolOutputTracing.ts +410 -14
- package/src/llm/bedrock/index.ts +22 -2
- package/src/llm/custom-chat-models.smoke.test.ts +747 -0
- package/src/llm/invoke.streamLimits.test.ts +142 -0
- package/src/llm/invoke.test.ts +187 -1
- package/src/llm/invoke.ts +231 -43
- package/src/llm/openai/cacheWriteTokens.test.ts +112 -0
- package/src/llm/openai/index.ts +348 -31
- package/src/llm/openai/llm.spec.ts +107 -6
- package/src/llm/streamLimits.test.ts +450 -0
- package/src/llm/streamLimits.ts +1158 -0
- package/src/messages/core.ts +1290 -42
- package/src/messages/formatAgentMessages.test.ts +2623 -0
- package/src/run.ts +4 -0
- package/src/session/AgentSession.ts +5 -0
- package/src/specs/langfuse-tool-output-tracing.test.ts +887 -0
- package/src/specs/preemptSeal.test.ts +374 -5
- package/src/stream.ts +141 -6
- package/src/summarization/__tests__/node.test.ts +269 -0
- package/src/summarization/chunkHandler.test.ts +196 -0
- package/src/summarization/node.ts +203 -6
- package/src/tools/BashExecutor.ts +4 -3
- package/src/tools/CodeExecutor.ts +5 -4
- package/src/tools/ProgrammaticToolCalling.ts +7 -5
- package/src/tools/ToolNode.ts +109 -6
- package/src/tools/ToolSearch.ts +4 -3
- package/src/tools/__tests__/BashExecutor.test.ts +2 -2
- package/src/tools/__tests__/ProgrammaticToolCalling.test.ts +2 -4
- package/src/tools/__tests__/SubagentExecutor.test.ts +126 -0
- package/src/tools/__tests__/ToolNode.breakerSignal.test.ts +389 -0
- package/src/tools/__tests__/ToolNode.streamLimits.test.ts +69 -0
- package/src/tools/search/crw-scraper.ts +6 -0
- package/src/tools/search/crw-search.ts +6 -1
- package/src/tools/search/firecrawl.ts +6 -0
- package/src/tools/search/http-agent.test.ts +133 -0
- package/src/tools/search/keenable-scraper.ts +6 -0
- package/src/tools/search/keenable-search.ts +6 -1
- package/src/tools/search/rerankers.ts +36 -10
- package/src/tools/search/search.ts +29 -11
- package/src/tools/search/serper-scraper.ts +6 -0
- package/src/tools/search/tavily-scraper.ts +6 -0
- package/src/tools/search/tavily-search.ts +2 -0
- package/src/tools/search/tool.ts +16 -0
- package/src/tools/search/types.ts +31 -33
- package/src/tools/subagent/SubagentExecutor.ts +96 -3
- package/src/types/graph.ts +7 -0
- package/src/types/run.ts +49 -1
- package/src/types/tools.ts +21 -0
- package/src/utils/index.ts +1 -0
- package/src/utils/misc.ts +19 -0
- package/src/utils/proxy.test.ts +176 -0
- package/src/utils/proxy.ts +93 -0
package/src/llm/openai/index.ts
CHANGED
|
@@ -16,7 +16,10 @@ import {
|
|
|
16
16
|
import {
|
|
17
17
|
getEndpoint,
|
|
18
18
|
OpenAIClient,
|
|
19
|
+
wrapOpenAIClientError,
|
|
19
20
|
getHeadersWithUserAgent,
|
|
21
|
+
convertMessagesToResponsesInput,
|
|
22
|
+
convertResponsesDeltaToChatGenerationChunk,
|
|
20
23
|
ChatOpenAI as OriginalChatOpenAI,
|
|
21
24
|
ChatOpenAIResponses as OriginalChatOpenAIResponses,
|
|
22
25
|
ChatOpenAICompletions as OriginalChatOpenAICompletions,
|
|
@@ -34,9 +37,15 @@ import type { BindToolsInput } from '@langchain/core/language_models/chat_models
|
|
|
34
37
|
import type { ChatGeneration, ChatResult } from '@langchain/core/outputs';
|
|
35
38
|
import type { ChatXAIInput } from '@langchain/xai';
|
|
36
39
|
import type * as t from '@langchain/openai';
|
|
40
|
+
import type { ResponsesReplayPosition } from '@/messages/core';
|
|
37
41
|
import type { SeenScalarMetadata } from './streamMetadata';
|
|
38
42
|
import type { HeaderValue, HeadersLike } from './types';
|
|
39
43
|
import type { PromptCacheTtl } from '@/messages/cache';
|
|
44
|
+
import {
|
|
45
|
+
OPENAI_RESPONSES_REPLAY_POSITIONS_KEY,
|
|
46
|
+
projectOpenAIResponsesToolMessageContent,
|
|
47
|
+
projectToolStreamContentForProvider,
|
|
48
|
+
} from '@/messages/core';
|
|
40
49
|
import {
|
|
41
50
|
buildAnthropicCacheControl,
|
|
42
51
|
resolvePromptCacheTtl,
|
|
@@ -47,12 +56,8 @@ import {
|
|
|
47
56
|
STREAMED_TOOL_CALL_ADAPTER_METADATA_KEY,
|
|
48
57
|
OPENAI_CHAT_SEQUENTIAL_STREAMED_TOOL_CALL_ADAPTER,
|
|
49
58
|
} from '@/tools/streamedToolCallSeals';
|
|
50
|
-
import {
|
|
51
|
-
projectOpenAIResponsesToolMessageContent,
|
|
52
|
-
projectToolStreamContentForProvider,
|
|
53
|
-
} from '@/messages/core';
|
|
54
|
-
import { INTENT_ARG, isIntentLabelProperty } from '@/tools/intentArg';
|
|
55
59
|
import { isReasoningModel, _convertMessagesToOpenAIParams } from './utils';
|
|
60
|
+
import { INTENT_ARG, isIntentLabelProperty } from '@/tools/intentArg';
|
|
56
61
|
import { dropRepeatedScalarMetadata } from './streamMetadata';
|
|
57
62
|
|
|
58
63
|
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
|
@@ -224,6 +229,10 @@ type ResponsesRequest =
|
|
|
224
229
|
type ResponsesResult =
|
|
225
230
|
| AsyncIterable<OpenAIClient.Responses.ResponseStreamEvent>
|
|
226
231
|
| OpenAIClient.Responses.Response;
|
|
232
|
+
type ResponsesStreamChunkOptions = {
|
|
233
|
+
promptIndex?: number;
|
|
234
|
+
signal?: AbortSignal;
|
|
235
|
+
};
|
|
227
236
|
type CacheableChatPart = {
|
|
228
237
|
type: 'text' | 'image_url' | 'input_audio' | 'file' | 'refusal';
|
|
229
238
|
prompt_cache_breakpoint?: { mode: 'explicit' };
|
|
@@ -236,7 +245,15 @@ type CacheableResponsePart = (
|
|
|
236
245
|
) & {
|
|
237
246
|
prompt_cache_breakpoint?: { mode: 'explicit' };
|
|
238
247
|
};
|
|
239
|
-
|
|
248
|
+
// `Omit` before re-adding `input_tokens_details` as optional matters: the SDK's own
|
|
249
|
+
// `ResponseUsage` declares it required (true for OpenAI itself), so a plain intersection
|
|
250
|
+
// would keep it required in the merged type despite the `?:` here — masking, at the type
|
|
251
|
+
// level, that OpenAI-*compatible* servers (e.g. mlx_vlm.server) may omit it entirely.
|
|
252
|
+
// Mirrors `CompletionUsageWithCacheWrite`'s handling of the analogous Completions API field.
|
|
253
|
+
type ResponsesUsageWithCacheWrite = Omit<
|
|
254
|
+
OpenAIClient.Responses.ResponseUsage,
|
|
255
|
+
'input_tokens_details'
|
|
256
|
+
> & {
|
|
240
257
|
input_tokens_details?: OpenAIClient.Responses.ResponseUsage['input_tokens_details'] & {
|
|
241
258
|
cache_write_tokens?: number;
|
|
242
259
|
};
|
|
@@ -386,6 +403,10 @@ function isResponseMessage(
|
|
|
386
403
|
return item.type === 'message';
|
|
387
404
|
}
|
|
388
405
|
|
|
406
|
+
function isResponseInputRole(role: string): boolean {
|
|
407
|
+
return role === 'system' || role === 'developer' || role === 'user';
|
|
408
|
+
}
|
|
409
|
+
|
|
389
410
|
/** Only `input_text`/`input_image`/`input_file` accept a Responses breakpoint;
|
|
390
411
|
* `output_text`/`refusal` (replayed assistant blocks) are rejected with a 400. */
|
|
391
412
|
function isCacheableResponsePart(part: unknown): part is CacheableResponsePart {
|
|
@@ -459,11 +480,7 @@ export function addResponseCacheBreakpoints(
|
|
|
459
480
|
/** Only input roles take a Responses breakpoint. Assistant/tool turns
|
|
460
481
|
* carry output content (string or output_text) that the API rejects
|
|
461
482
|
* under an input marker, so they're never eligible. */
|
|
462
|
-
if (
|
|
463
|
-
item.role !== 'system' &&
|
|
464
|
-
item.role !== 'developer' &&
|
|
465
|
-
item.role !== 'user'
|
|
466
|
-
) {
|
|
483
|
+
if (!isResponseInputRole(item.role)) {
|
|
467
484
|
return false;
|
|
468
485
|
}
|
|
469
486
|
const content = item.content as
|
|
@@ -500,13 +517,13 @@ export function shouldIncludeEncryptedReasoning(
|
|
|
500
517
|
);
|
|
501
518
|
}
|
|
502
519
|
|
|
503
|
-
function getCacheWriteTokens(message: BaseMessage): number | undefined {
|
|
520
|
+
export function getCacheWriteTokens(message: BaseMessage): number | undefined {
|
|
504
521
|
const responseMetadata = message.response_metadata as {
|
|
505
522
|
usage?: ResponsesUsageWithCacheWrite;
|
|
506
523
|
metadata?: Record<string, string>;
|
|
507
524
|
};
|
|
508
525
|
const reported =
|
|
509
|
-
responseMetadata.usage?.input_tokens_details
|
|
526
|
+
responseMetadata.usage?.input_tokens_details?.cache_write_tokens;
|
|
510
527
|
if (reported != null) {
|
|
511
528
|
return reported;
|
|
512
529
|
}
|
|
@@ -518,7 +535,7 @@ function getCacheWriteTokens(message: BaseMessage): number | undefined {
|
|
|
518
535
|
return Number.isFinite(parsed) ? parsed : undefined;
|
|
519
536
|
}
|
|
520
537
|
|
|
521
|
-
function attachCacheWriteUsage(message: BaseMessage): void {
|
|
538
|
+
export function attachCacheWriteUsage(message: BaseMessage): void {
|
|
522
539
|
const cacheWriteTokens = getCacheWriteTokens(message);
|
|
523
540
|
if (
|
|
524
541
|
cacheWriteTokens == null ||
|
|
@@ -545,11 +562,11 @@ function attachCacheWriteUsage(message: BaseMessage): void {
|
|
|
545
562
|
};
|
|
546
563
|
}
|
|
547
564
|
|
|
548
|
-
function attachCacheWriteMetadata(
|
|
565
|
+
export function attachCacheWriteMetadata(
|
|
549
566
|
response: OpenAIClient.Responses.Response
|
|
550
567
|
): OpenAIClient.Responses.Response {
|
|
551
568
|
const usage = response.usage as ResponsesUsageWithCacheWrite | undefined;
|
|
552
|
-
const cacheWriteTokens = usage?.input_tokens_details
|
|
569
|
+
const cacheWriteTokens = usage?.input_tokens_details?.cache_write_tokens;
|
|
553
570
|
if (cacheWriteTokens == null) {
|
|
554
571
|
return response;
|
|
555
572
|
}
|
|
@@ -568,6 +585,278 @@ function isResponsesStream(
|
|
|
568
585
|
return Symbol.asyncIterator in result;
|
|
569
586
|
}
|
|
570
587
|
|
|
588
|
+
const RESPONSES_REPLAY_OUTPUT_ITEM_TYPES = new Set([
|
|
589
|
+
'local_shell_call_output',
|
|
590
|
+
'shell_call_output',
|
|
591
|
+
'apply_patch_call_output',
|
|
592
|
+
'program_output',
|
|
593
|
+
]);
|
|
594
|
+
|
|
595
|
+
function isResponsesReplayOutputItem(item: unknown): boolean {
|
|
596
|
+
return (
|
|
597
|
+
typeof item === 'object' &&
|
|
598
|
+
item != null &&
|
|
599
|
+
'type' in item &&
|
|
600
|
+
typeof item.type === 'string' &&
|
|
601
|
+
RESPONSES_REPLAY_OUTPUT_ITEM_TYPES.has(item.type)
|
|
602
|
+
);
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
/**
|
|
606
|
+
* LangChain's Responses converter places the authoritative terminal output in
|
|
607
|
+
* response_metadata.output. Its chunk merge has no way to delete provisional
|
|
608
|
+
* tool_outputs or replay-position sidecars, so remove those preemption-only
|
|
609
|
+
* captures once that terminal output arrives. An interrupted stream has no
|
|
610
|
+
* terminal chunk and keeps the captures for replay.
|
|
611
|
+
*/
|
|
612
|
+
class ResponsesReplayAIMessageChunk extends AIMessageChunk {
|
|
613
|
+
override get lc_id(): string[] {
|
|
614
|
+
return [...this.lc_namespace, AIMessageChunk.lc_name()];
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
override concat(chunk: AIMessageChunk): this {
|
|
618
|
+
const combined = super.concat(chunk);
|
|
619
|
+
if (!Array.isArray(chunk.response_metadata.output)) {
|
|
620
|
+
return combined;
|
|
621
|
+
}
|
|
622
|
+
delete combined.additional_kwargs[OPENAI_RESPONSES_REPLAY_POSITIONS_KEY];
|
|
623
|
+
const toolOutputs = combined.additional_kwargs.tool_outputs;
|
|
624
|
+
if (!Array.isArray(toolOutputs)) {
|
|
625
|
+
return combined;
|
|
626
|
+
}
|
|
627
|
+
const retainedToolOutputs = toolOutputs.filter(
|
|
628
|
+
(item) => !isResponsesReplayOutputItem(item)
|
|
629
|
+
);
|
|
630
|
+
if (retainedToolOutputs.length === toolOutputs.length) {
|
|
631
|
+
return combined;
|
|
632
|
+
}
|
|
633
|
+
if (retainedToolOutputs.length > 0) {
|
|
634
|
+
combined.additional_kwargs.tool_outputs = retainedToolOutputs;
|
|
635
|
+
} else {
|
|
636
|
+
delete combined.additional_kwargs.tool_outputs;
|
|
637
|
+
}
|
|
638
|
+
return combined;
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
function makeResponsesReplayAggregationSafe(
|
|
643
|
+
chunk: ChatGenerationChunk
|
|
644
|
+
): ChatGenerationChunk {
|
|
645
|
+
if (!AIMessageChunk.isInstance(chunk.message)) {
|
|
646
|
+
return chunk;
|
|
647
|
+
}
|
|
648
|
+
const message = chunk.message;
|
|
649
|
+
chunk.message = new ResponsesReplayAIMessageChunk({
|
|
650
|
+
id: message.id,
|
|
651
|
+
name: message.name,
|
|
652
|
+
content: message.content,
|
|
653
|
+
additional_kwargs: message.additional_kwargs,
|
|
654
|
+
response_metadata: message.response_metadata,
|
|
655
|
+
tool_calls: message.tool_calls,
|
|
656
|
+
invalid_tool_calls: message.invalid_tool_calls,
|
|
657
|
+
tool_call_chunks: message.tool_call_chunks,
|
|
658
|
+
usage_metadata: message.usage_metadata,
|
|
659
|
+
});
|
|
660
|
+
return chunk;
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
function remapResponsesTextBlockIndex(
|
|
664
|
+
chunk: ChatGenerationChunk,
|
|
665
|
+
event: OpenAIClient.Responses.ResponseStreamEvent,
|
|
666
|
+
textBlockIndices: Map<string, number>
|
|
667
|
+
): void {
|
|
668
|
+
const position = iife(() => {
|
|
669
|
+
if (
|
|
670
|
+
event.type === 'response.output_text.delta' ||
|
|
671
|
+
event.type === 'response.output_text.annotation.added'
|
|
672
|
+
) {
|
|
673
|
+
return {
|
|
674
|
+
contentIndex: event.content_index,
|
|
675
|
+
outputIndex: event.output_index,
|
|
676
|
+
};
|
|
677
|
+
}
|
|
678
|
+
if (
|
|
679
|
+
event.type === 'response.output_item.added' &&
|
|
680
|
+
event.item.type === 'message'
|
|
681
|
+
) {
|
|
682
|
+
return { contentIndex: 0, outputIndex: event.output_index };
|
|
683
|
+
}
|
|
684
|
+
return undefined;
|
|
685
|
+
});
|
|
686
|
+
if (position == null || !Array.isArray(chunk.message.content)) {
|
|
687
|
+
return;
|
|
688
|
+
}
|
|
689
|
+
const key = `${position.outputIndex}:${position.contentIndex}`;
|
|
690
|
+
let blockIndex = textBlockIndices.get(key);
|
|
691
|
+
if (blockIndex == null) {
|
|
692
|
+
blockIndex = textBlockIndices.size;
|
|
693
|
+
textBlockIndices.set(key, blockIndex);
|
|
694
|
+
}
|
|
695
|
+
const content = chunk.message.content.map((block) =>
|
|
696
|
+
typeof block === 'object' && block.type === 'text'
|
|
697
|
+
? { ...block, index: blockIndex }
|
|
698
|
+
: block
|
|
699
|
+
);
|
|
700
|
+
chunk.message.content = content;
|
|
701
|
+
chunk.message.lc_kwargs.content = content;
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
function convertDroppedResponsesReplayOutput(
|
|
705
|
+
event: OpenAIClient.Responses.ResponseStreamEvent
|
|
706
|
+
): ChatGenerationChunk | null {
|
|
707
|
+
if (event.type !== 'response.output_item.done') {
|
|
708
|
+
return null;
|
|
709
|
+
}
|
|
710
|
+
if (event.item.type === 'reasoning') {
|
|
711
|
+
// Added/summary events already stream id, type, and summary. Only merge
|
|
712
|
+
// terminal fields here so chunk concatenation does not duplicate summary.
|
|
713
|
+
return new ChatGenerationChunk({
|
|
714
|
+
text: '',
|
|
715
|
+
message: new AIMessageChunk({
|
|
716
|
+
content: [],
|
|
717
|
+
additional_kwargs: {
|
|
718
|
+
reasoning: {
|
|
719
|
+
status: event.item.status,
|
|
720
|
+
...(typeof event.item.encrypted_content === 'string'
|
|
721
|
+
? { encrypted_content: event.item.encrypted_content }
|
|
722
|
+
: {}),
|
|
723
|
+
},
|
|
724
|
+
},
|
|
725
|
+
response_metadata: { model_provider: 'openai' },
|
|
726
|
+
}),
|
|
727
|
+
});
|
|
728
|
+
}
|
|
729
|
+
if (!RESPONSES_REPLAY_OUTPUT_ITEM_TYPES.has(event.item.type)) {
|
|
730
|
+
return null;
|
|
731
|
+
}
|
|
732
|
+
return new ChatGenerationChunk({
|
|
733
|
+
text: '',
|
|
734
|
+
message: new AIMessageChunk({
|
|
735
|
+
content: [],
|
|
736
|
+
additional_kwargs: { tool_outputs: [event.item] },
|
|
737
|
+
response_metadata: { model_provider: 'openai' },
|
|
738
|
+
}),
|
|
739
|
+
});
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
function attachResponsesReplayPosition(
|
|
743
|
+
chunk: ChatGenerationChunk,
|
|
744
|
+
event: OpenAIClient.Responses.ResponseStreamEvent,
|
|
745
|
+
seenPositions: Set<string>
|
|
746
|
+
): void {
|
|
747
|
+
let position: ResponsesReplayPosition | undefined;
|
|
748
|
+
if (event.type === 'response.output_text.delta' && event.delta.length > 0) {
|
|
749
|
+
position = {
|
|
750
|
+
contentIndex: event.content_index,
|
|
751
|
+
itemId: event.item_id,
|
|
752
|
+
kind: 'text',
|
|
753
|
+
outputIndex: event.output_index,
|
|
754
|
+
};
|
|
755
|
+
} else if (
|
|
756
|
+
event.type === 'response.output_item.added' &&
|
|
757
|
+
event.item.type === 'message' &&
|
|
758
|
+
typeof event.item.id === 'string' &&
|
|
759
|
+
event.item.id.length > 0
|
|
760
|
+
) {
|
|
761
|
+
position = {
|
|
762
|
+
itemId: event.item.id,
|
|
763
|
+
kind: 'message',
|
|
764
|
+
outputIndex: event.output_index,
|
|
765
|
+
};
|
|
766
|
+
} else if (
|
|
767
|
+
event.type === 'response.output_item.added' &&
|
|
768
|
+
event.item.type === 'reasoning' &&
|
|
769
|
+
typeof event.item.id === 'string' &&
|
|
770
|
+
event.item.id.length > 0
|
|
771
|
+
) {
|
|
772
|
+
position = {
|
|
773
|
+
itemId: event.item.id,
|
|
774
|
+
kind: 'reasoning',
|
|
775
|
+
outputIndex: event.output_index,
|
|
776
|
+
};
|
|
777
|
+
} else if (
|
|
778
|
+
event.type === 'response.output_item.done' &&
|
|
779
|
+
(RESPONSES_REPLAY_OUTPUT_ITEM_TYPES.has(event.item.type) ||
|
|
780
|
+
Array.isArray(chunk.message.additional_kwargs.tool_outputs))
|
|
781
|
+
) {
|
|
782
|
+
let itemId: string | undefined;
|
|
783
|
+
if (typeof event.item.id === 'string' && event.item.id.length > 0) {
|
|
784
|
+
itemId = event.item.id;
|
|
785
|
+
} else if (
|
|
786
|
+
'call_id' in event.item &&
|
|
787
|
+
typeof event.item.call_id === 'string' &&
|
|
788
|
+
event.item.call_id.length > 0
|
|
789
|
+
) {
|
|
790
|
+
itemId = event.item.call_id;
|
|
791
|
+
}
|
|
792
|
+
if (itemId != null) {
|
|
793
|
+
position = {
|
|
794
|
+
itemId,
|
|
795
|
+
kind: 'output',
|
|
796
|
+
outputIndex: event.output_index,
|
|
797
|
+
};
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
if (position == null) {
|
|
801
|
+
return;
|
|
802
|
+
}
|
|
803
|
+
const positionKey = `${position.kind}:${position.itemId}:${position.outputIndex}:${position.contentIndex ?? ''}`;
|
|
804
|
+
if (seenPositions.has(positionKey)) {
|
|
805
|
+
return;
|
|
806
|
+
}
|
|
807
|
+
seenPositions.add(positionKey);
|
|
808
|
+
const existing = chunk.message.additional_kwargs[
|
|
809
|
+
OPENAI_RESPONSES_REPLAY_POSITIONS_KEY
|
|
810
|
+
] as unknown;
|
|
811
|
+
const additionalKwargs = {
|
|
812
|
+
...chunk.message.additional_kwargs,
|
|
813
|
+
[OPENAI_RESPONSES_REPLAY_POSITIONS_KEY]: [
|
|
814
|
+
...(Array.isArray(existing) ? existing : []),
|
|
815
|
+
position,
|
|
816
|
+
],
|
|
817
|
+
};
|
|
818
|
+
chunk.message.additional_kwargs = additionalKwargs;
|
|
819
|
+
chunk.message.lc_kwargs.additional_kwargs = additionalKwargs;
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
async function* convertLibreChatResponsesStream(
|
|
823
|
+
stream: AsyncIterable<OpenAIClient.Responses.ResponseStreamEvent>,
|
|
824
|
+
options: ResponsesStreamChunkOptions,
|
|
825
|
+
runManager?: CallbackManagerForLLMRun
|
|
826
|
+
): AsyncGenerator<ChatGenerationChunk> {
|
|
827
|
+
const seenReplayPositions = new Set<string>();
|
|
828
|
+
const responsesTextBlockIndices = new Map<string, number>();
|
|
829
|
+
try {
|
|
830
|
+
for await (const event of stream) {
|
|
831
|
+
options.signal?.throwIfAborted();
|
|
832
|
+
const convertedChunk =
|
|
833
|
+
convertResponsesDeltaToChatGenerationChunk(event) ??
|
|
834
|
+
convertDroppedResponsesReplayOutput(event);
|
|
835
|
+
if (convertedChunk == null) {
|
|
836
|
+
continue;
|
|
837
|
+
}
|
|
838
|
+
const chunk = makeResponsesReplayAggregationSafe(convertedChunk);
|
|
839
|
+
remapResponsesTextBlockIndex(chunk, event, responsesTextBlockIndices);
|
|
840
|
+
attachResponsesReplayPosition(chunk, event, seenReplayPositions);
|
|
841
|
+
attachCacheWriteUsage(chunk.message);
|
|
842
|
+
await runManager?.handleLLMNewToken(
|
|
843
|
+
chunk.text || '',
|
|
844
|
+
{
|
|
845
|
+
prompt: options.promptIndex ?? 0,
|
|
846
|
+
completion: 0,
|
|
847
|
+
},
|
|
848
|
+
undefined,
|
|
849
|
+
undefined,
|
|
850
|
+
undefined,
|
|
851
|
+
{ chunk }
|
|
852
|
+
);
|
|
853
|
+
yield chunk;
|
|
854
|
+
}
|
|
855
|
+
} catch (e) {
|
|
856
|
+
throw wrapOpenAIClientError(e);
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
|
|
571
860
|
function createUsageMetadata(
|
|
572
861
|
usage?: OpenAIClient.Completions.CompletionUsage
|
|
573
862
|
): UsageMetadata {
|
|
@@ -1754,14 +2043,20 @@ class LibreChatOpenAIResponses extends OriginalChatOpenAIResponses {
|
|
|
1754
2043
|
options: this['ParsedCallOptions'],
|
|
1755
2044
|
runManager?: CallbackManagerForLLMRun
|
|
1756
2045
|
): AsyncGenerator<ChatGenerationChunk> {
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
2046
|
+
const projectedMessages = projectOpenAIResponsesProviderMessages(messages);
|
|
2047
|
+
const stream = await this.completionWithRetry(
|
|
2048
|
+
{
|
|
2049
|
+
...this.invocationParams(options),
|
|
2050
|
+
input: convertMessagesToResponsesInput({
|
|
2051
|
+
messages: projectedMessages,
|
|
2052
|
+
zdrEnabled: this.zdrEnabled ?? false,
|
|
2053
|
+
model: this.model,
|
|
2054
|
+
}),
|
|
2055
|
+
stream: true,
|
|
2056
|
+
},
|
|
2057
|
+
options
|
|
2058
|
+
);
|
|
2059
|
+
yield* convertLibreChatResponsesStream(stream, options, runManager);
|
|
1765
2060
|
}
|
|
1766
2061
|
|
|
1767
2062
|
async *_streamChatModelEvents(
|
|
@@ -1988,7 +2283,11 @@ class LibreChatAzureOpenAIResponses extends OriginalAzureChatOpenAIResponses {
|
|
|
1988
2283
|
options: this['ParsedCallOptions'],
|
|
1989
2284
|
runManager?: CallbackManagerForLLMRun
|
|
1990
2285
|
): Promise<ChatResult> {
|
|
1991
|
-
const result = await super._generate(
|
|
2286
|
+
const result = await super._generate(
|
|
2287
|
+
projectOpenAIResponsesProviderMessages(messages),
|
|
2288
|
+
options,
|
|
2289
|
+
runManager
|
|
2290
|
+
);
|
|
1992
2291
|
for (const generation of result.generations) {
|
|
1993
2292
|
attachCacheWriteUsage(generation.message);
|
|
1994
2293
|
}
|
|
@@ -2000,14 +2299,32 @@ class LibreChatAzureOpenAIResponses extends OriginalAzureChatOpenAIResponses {
|
|
|
2000
2299
|
options: this['ParsedCallOptions'],
|
|
2001
2300
|
runManager?: CallbackManagerForLLMRun
|
|
2002
2301
|
): AsyncGenerator<ChatGenerationChunk> {
|
|
2003
|
-
|
|
2004
|
-
|
|
2302
|
+
const projectedMessages = projectOpenAIResponsesProviderMessages(messages);
|
|
2303
|
+
const stream = await this.completionWithRetry(
|
|
2304
|
+
{
|
|
2305
|
+
...this.invocationParams(options),
|
|
2306
|
+
input: convertMessagesToResponsesInput({
|
|
2307
|
+
messages: projectedMessages,
|
|
2308
|
+
zdrEnabled: this.zdrEnabled ?? false,
|
|
2309
|
+
model: this.model,
|
|
2310
|
+
}),
|
|
2311
|
+
stream: true,
|
|
2312
|
+
},
|
|
2313
|
+
options
|
|
2314
|
+
);
|
|
2315
|
+
yield* convertLibreChatResponsesStream(stream, options, runManager);
|
|
2316
|
+
}
|
|
2317
|
+
|
|
2318
|
+
async *_streamChatModelEvents(
|
|
2319
|
+
messages: BaseMessage[],
|
|
2320
|
+
options: this['ParsedCallOptions'],
|
|
2321
|
+
runManager?: CallbackManagerForLLMRun
|
|
2322
|
+
): AsyncGenerator<ChatModelStreamEvent> {
|
|
2323
|
+
yield* super._streamChatModelEvents(
|
|
2324
|
+
projectOpenAIResponsesProviderMessages(messages),
|
|
2005
2325
|
options,
|
|
2006
2326
|
runManager
|
|
2007
|
-
)
|
|
2008
|
-
attachCacheWriteUsage(chunk.message);
|
|
2009
|
-
yield chunk;
|
|
2010
|
-
}
|
|
2327
|
+
);
|
|
2011
2328
|
}
|
|
2012
2329
|
|
|
2013
2330
|
protected _getReasoningParams(
|
|
@@ -82,6 +82,17 @@ type CompletionsStreamDelegate = {
|
|
|
82
82
|
) => AsyncGenerator<ChatGenerationChunk>;
|
|
83
83
|
};
|
|
84
84
|
|
|
85
|
+
type ResponsesStreamDelegate = {
|
|
86
|
+
completionWithRetry: (
|
|
87
|
+
request: OpenAIClient.Responses.ResponseCreateParamsStreaming
|
|
88
|
+
) => Promise<AsyncIterable<OpenAIClient.Responses.ResponseStreamEvent>>;
|
|
89
|
+
_streamResponseChunks: (
|
|
90
|
+
messages: BaseMessage[],
|
|
91
|
+
options: Record<string, unknown>,
|
|
92
|
+
runManager?: CallbackManagerForLLMRun
|
|
93
|
+
) => AsyncGenerator<ChatGenerationChunk>;
|
|
94
|
+
};
|
|
95
|
+
|
|
85
96
|
type InvocationParamsDelegate = {
|
|
86
97
|
invocationParams: (
|
|
87
98
|
options: Record<string, unknown>,
|
|
@@ -109,6 +120,9 @@ function completionsDelegateOf(model: ChatOpenAI): CompletionsDelegate {
|
|
|
109
120
|
const completionsOf = <T>(model: ChatOpenAI): T =>
|
|
110
121
|
(model as unknown as { completions: T }).completions;
|
|
111
122
|
|
|
123
|
+
const responsesOf = <T>(model: ChatOpenAI): T =>
|
|
124
|
+
(model as unknown as { responses: T }).responses;
|
|
125
|
+
|
|
112
126
|
function newOpenAI(): ChatOpenAI {
|
|
113
127
|
return new ChatOpenAI({ model: 'gpt-4o-mini', apiKey: 'test-key' });
|
|
114
128
|
}
|
|
@@ -1080,6 +1094,90 @@ describe('ChatOpenAICompletions streaming usage_metadata callback', () => {
|
|
|
1080
1094
|
});
|
|
1081
1095
|
});
|
|
1082
1096
|
|
|
1097
|
+
describe('ChatOpenAIResponses streaming callback dispatch', () => {
|
|
1098
|
+
it('emits the callback before a consumer stops at the yielded chunk', async () => {
|
|
1099
|
+
const model = new ChatOpenAI({
|
|
1100
|
+
model: 'gpt-5',
|
|
1101
|
+
apiKey: 'test-key',
|
|
1102
|
+
useResponsesApi: true,
|
|
1103
|
+
});
|
|
1104
|
+
const responses = responsesOf<ResponsesStreamDelegate>(model);
|
|
1105
|
+
responses.completionWithRetry = async () =>
|
|
1106
|
+
(async function* () {
|
|
1107
|
+
yield {
|
|
1108
|
+
type: 'response.output_text.delta',
|
|
1109
|
+
sequence_number: 0,
|
|
1110
|
+
output_index: 0,
|
|
1111
|
+
content_index: 0,
|
|
1112
|
+
item_id: 'msg_callback',
|
|
1113
|
+
delta: 'visible chunk',
|
|
1114
|
+
logprobs: [],
|
|
1115
|
+
} as OpenAIClient.Responses.ResponseStreamEvent;
|
|
1116
|
+
})();
|
|
1117
|
+
|
|
1118
|
+
const events: string[] = [];
|
|
1119
|
+
const runManager = {
|
|
1120
|
+
handleLLMNewToken(token: string): void {
|
|
1121
|
+
events.push(`callback:${token}`);
|
|
1122
|
+
},
|
|
1123
|
+
} as unknown as CallbackManagerForLLMRun;
|
|
1124
|
+
|
|
1125
|
+
for await (const chunk of responses._streamResponseChunks(
|
|
1126
|
+
[new HumanMessage('test')],
|
|
1127
|
+
{},
|
|
1128
|
+
runManager
|
|
1129
|
+
)) {
|
|
1130
|
+
events.push(`yield:${chunk.text}`);
|
|
1131
|
+
break;
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
expect(events).toEqual(['callback:visible chunk', 'yield:visible chunk']);
|
|
1135
|
+
});
|
|
1136
|
+
|
|
1137
|
+
it('propagates an abort received with the next buffered event', async () => {
|
|
1138
|
+
const model = new ChatOpenAI({
|
|
1139
|
+
model: 'gpt-5',
|
|
1140
|
+
apiKey: 'test-key',
|
|
1141
|
+
useResponsesApi: true,
|
|
1142
|
+
});
|
|
1143
|
+
const responses = responsesOf<ResponsesStreamDelegate>(model);
|
|
1144
|
+
const controller = new AbortController();
|
|
1145
|
+
const abortReason = new Error('buffered Responses stream aborted');
|
|
1146
|
+
responses.completionWithRetry = async () =>
|
|
1147
|
+
(async function* () {
|
|
1148
|
+
yield {
|
|
1149
|
+
type: 'response.output_text.delta',
|
|
1150
|
+
sequence_number: 0,
|
|
1151
|
+
output_index: 0,
|
|
1152
|
+
content_index: 0,
|
|
1153
|
+
item_id: 'msg_abort',
|
|
1154
|
+
delta: 'partial',
|
|
1155
|
+
logprobs: [],
|
|
1156
|
+
} as OpenAIClient.Responses.ResponseStreamEvent;
|
|
1157
|
+
controller.abort(abortReason);
|
|
1158
|
+
yield {
|
|
1159
|
+
type: 'response.output_text.delta',
|
|
1160
|
+
sequence_number: 1,
|
|
1161
|
+
output_index: 0,
|
|
1162
|
+
content_index: 0,
|
|
1163
|
+
item_id: 'msg_abort',
|
|
1164
|
+
delta: ' must not complete normally',
|
|
1165
|
+
logprobs: [],
|
|
1166
|
+
} as OpenAIClient.Responses.ResponseStreamEvent;
|
|
1167
|
+
})();
|
|
1168
|
+
|
|
1169
|
+
const stream = responses._streamResponseChunks([new HumanMessage('test')], {
|
|
1170
|
+
signal: controller.signal,
|
|
1171
|
+
});
|
|
1172
|
+
|
|
1173
|
+
await expect(stream.next()).resolves.toMatchObject({
|
|
1174
|
+
done: false,
|
|
1175
|
+
value: { text: 'partial' },
|
|
1176
|
+
});
|
|
1177
|
+
await expect(stream.next()).rejects.toBe(abortReason);
|
|
1178
|
+
});
|
|
1179
|
+
});
|
|
1180
|
+
|
|
1083
1181
|
describe('ChatOpenAICompletions reasoning_content compatibility', () => {
|
|
1084
1182
|
it('should preserve reasoning_content on streamed assistant chunks', async () => {
|
|
1085
1183
|
const model = new ChatOpenAI({
|
|
@@ -1237,11 +1335,15 @@ describe('ChatOpenAICompletions strict tools for structured output', () => {
|
|
|
1237
1335
|
}) as unknown as {
|
|
1238
1336
|
tools?: { function: { parameters: { properties: object } } }[];
|
|
1239
1337
|
};
|
|
1240
|
-
return Object.keys(
|
|
1338
|
+
return Object.keys(
|
|
1339
|
+
params.tools?.[0]?.function.parameters.properties ?? {}
|
|
1340
|
+
);
|
|
1241
1341
|
}
|
|
1242
1342
|
|
|
1243
1343
|
it('drops the optional label when strict is auto-enabled (invalid otherwise)', () => {
|
|
1244
|
-
expect(
|
|
1344
|
+
expect(
|
|
1345
|
+
toolProperties({ response_format: jsonSchemaResponseFormat })
|
|
1346
|
+
).toEqual(['query']);
|
|
1245
1347
|
});
|
|
1246
1348
|
|
|
1247
1349
|
it('keeps the label on non-strict requests', () => {
|
|
@@ -1305,10 +1407,9 @@ describe('ChatOpenAICompletions strict tools for structured output', () => {
|
|
|
1305
1407
|
}) as unknown as {
|
|
1306
1408
|
tools?: { function: { parameters: { properties: object } } }[];
|
|
1307
1409
|
};
|
|
1308
|
-
expect(
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
]);
|
|
1410
|
+
expect(
|
|
1411
|
+
Object.keys(params.tools?.[0]?.function.parameters.properties ?? {})
|
|
1412
|
+
).toEqual(['intent', 'title']);
|
|
1312
1413
|
});
|
|
1313
1414
|
});
|
|
1314
1415
|
});
|