@librechat/agents 3.1.51 → 3.1.53
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 +43 -16
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/llm/google/index.cjs.map +1 -1
- package/dist/cjs/llm/openrouter/index.cjs +59 -5
- package/dist/cjs/llm/openrouter/index.cjs.map +1 -1
- package/dist/cjs/llm/vertexai/index.cjs +16 -2
- package/dist/cjs/llm/vertexai/index.cjs.map +1 -1
- package/dist/cjs/main.cjs +2 -0
- package/dist/cjs/main.cjs.map +1 -1
- package/dist/cjs/run.cjs +32 -2
- package/dist/cjs/run.cjs.map +1 -1
- package/dist/cjs/utils/run.cjs +3 -1
- package/dist/cjs/utils/run.cjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +43 -16
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/llm/google/index.mjs.map +1 -1
- package/dist/esm/llm/openrouter/index.mjs +59 -5
- package/dist/esm/llm/openrouter/index.mjs.map +1 -1
- package/dist/esm/llm/vertexai/index.mjs +16 -2
- package/dist/esm/llm/vertexai/index.mjs.map +1 -1
- package/dist/esm/main.mjs +1 -0
- package/dist/esm/main.mjs.map +1 -1
- package/dist/esm/run.mjs +32 -2
- package/dist/esm/run.mjs.map +1 -1
- package/dist/esm/utils/run.mjs +3 -1
- package/dist/esm/utils/run.mjs.map +1 -1
- package/dist/types/graphs/Graph.d.ts +7 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/llm/google/index.d.ts +2 -3
- package/dist/types/llm/openrouter/index.d.ts +21 -1
- package/dist/types/llm/vertexai/index.d.ts +2 -1
- package/dist/types/run.d.ts +1 -0
- package/dist/types/types/llm.d.ts +7 -2
- package/dist/types/types/run.d.ts +2 -0
- package/package.json +1 -1
- package/src/graphs/Graph.ts +49 -20
- package/src/index.ts +6 -0
- package/src/llm/google/index.ts +2 -3
- package/src/llm/openrouter/index.ts +117 -6
- package/src/llm/openrouter/reasoning.test.ts +207 -0
- package/src/llm/vertexai/index.ts +20 -3
- package/src/run.ts +40 -2
- package/src/scripts/ant_web_search.ts +1 -0
- package/src/scripts/ant_web_search_edge_case.ts +1 -0
- package/src/scripts/ant_web_search_error_edge_case.ts +1 -0
- package/src/scripts/bedrock-content-aggregation-test.ts +1 -0
- package/src/scripts/bedrock-parallel-tools-test.ts +1 -0
- package/src/scripts/caching.ts +1 -0
- package/src/scripts/code_exec.ts +1 -0
- package/src/scripts/code_exec_files.ts +1 -0
- package/src/scripts/code_exec_multi_session.ts +1 -0
- package/src/scripts/code_exec_ptc.ts +1 -0
- package/src/scripts/code_exec_session.ts +1 -0
- package/src/scripts/code_exec_simple.ts +1 -0
- package/src/scripts/content.ts +1 -0
- package/src/scripts/image.ts +1 -0
- package/src/scripts/memory.ts +16 -6
- package/src/scripts/multi-agent-chain.ts +1 -0
- package/src/scripts/multi-agent-conditional.ts +1 -0
- package/src/scripts/multi-agent-document-review-chain.ts +1 -0
- package/src/scripts/multi-agent-hybrid-flow.ts +1 -0
- package/src/scripts/multi-agent-parallel-start.ts +1 -0
- package/src/scripts/multi-agent-parallel.ts +1 -0
- package/src/scripts/multi-agent-sequence.ts +1 -0
- package/src/scripts/multi-agent-supervisor.ts +1 -0
- package/src/scripts/multi-agent-test.ts +1 -0
- package/src/scripts/parallel-asymmetric-tools-test.ts +1 -0
- package/src/scripts/parallel-full-metadata-test.ts +1 -0
- package/src/scripts/parallel-tools-test.ts +1 -0
- package/src/scripts/programmatic_exec_agent.ts +1 -0
- package/src/scripts/search.ts +1 -0
- package/src/scripts/sequential-full-metadata-test.ts +1 -0
- package/src/scripts/simple.ts +1 -0
- package/src/scripts/single-agent-metadata-test.ts +1 -0
- package/src/scripts/stream.ts +1 -0
- package/src/scripts/test-handoff-preamble.ts +1 -0
- package/src/scripts/test-handoff-steering.ts +3 -0
- package/src/scripts/test-multi-agent-list-handoff.ts +1 -0
- package/src/scripts/test-parallel-agent-labeling.ts +2 -0
- package/src/scripts/test-parallel-handoffs.ts +1 -0
- package/src/scripts/test-thinking-handoff-bedrock.ts +1 -0
- package/src/scripts/test-thinking-handoff.ts +1 -0
- package/src/scripts/test-thinking-to-thinking-handoff-bedrock.ts +1 -0
- package/src/scripts/test-tool-before-handoff-role-order.ts +1 -0
- package/src/scripts/test-tools-before-handoff.ts +1 -0
- package/src/scripts/thinking-bedrock.ts +1 -0
- package/src/scripts/thinking.ts +1 -0
- package/src/scripts/tools.ts +1 -0
- package/src/specs/agent-handoffs.test.ts +1 -0
- package/src/specs/anthropic.simple.test.ts +4 -0
- package/src/specs/azure.simple.test.ts +142 -3
- package/src/specs/cache.simple.test.ts +8 -0
- package/src/specs/custom-event-await.test.ts +2 -0
- package/src/specs/deepseek.simple.test.ts +3 -0
- package/src/specs/moonshot.simple.test.ts +5 -0
- package/src/specs/openai.simple.test.ts +3 -0
- package/src/specs/openrouter.simple.test.ts +164 -2
- package/src/specs/prune.test.ts +1 -0
- package/src/specs/reasoning.test.ts +1 -0
- package/src/specs/thinking-handoff.test.ts +1 -0
- package/src/specs/tool-error.test.ts +1 -0
- package/src/types/llm.ts +7 -2
- package/src/types/run.ts +2 -0
- package/src/utils/llmConfig.ts +3 -4
- package/src/utils/run.ts +4 -2
package/src/llm/google/index.ts
CHANGED
|
@@ -10,9 +10,8 @@ import type {
|
|
|
10
10
|
} from '@google/generative-ai';
|
|
11
11
|
import type { CallbackManagerForLLMRun } from '@langchain/core/callbacks/manager';
|
|
12
12
|
import type { BaseMessage, UsageMetadata } from '@langchain/core/messages';
|
|
13
|
-
import type { GeminiGenerationConfig } from '@langchain/google-common';
|
|
14
13
|
import type { GeminiApiUsageMetadata, InputTokenDetails } from './types';
|
|
15
|
-
import type { GoogleClientOptions } from '@/types';
|
|
14
|
+
import type { GoogleClientOptions, GoogleThinkingConfig } from '@/types';
|
|
16
15
|
import {
|
|
17
16
|
convertResponseContentToChatGenerationChunk,
|
|
18
17
|
convertBaseMessagesToContent,
|
|
@@ -20,7 +19,7 @@ import {
|
|
|
20
19
|
} from './utils/common';
|
|
21
20
|
|
|
22
21
|
export class CustomChatGoogleGenerativeAI extends ChatGoogleGenerativeAI {
|
|
23
|
-
thinkingConfig?:
|
|
22
|
+
thinkingConfig?: GoogleThinkingConfig;
|
|
24
23
|
|
|
25
24
|
/**
|
|
26
25
|
* Override to add gemini-3 model support for multimodal and function calling thought signatures
|
|
@@ -29,24 +29,135 @@ type OpenAIRoleEnum =
|
|
|
29
29
|
| 'function'
|
|
30
30
|
| 'tool';
|
|
31
31
|
|
|
32
|
-
export
|
|
32
|
+
export type OpenRouterReasoningEffort =
|
|
33
|
+
| 'xhigh'
|
|
34
|
+
| 'high'
|
|
35
|
+
| 'medium'
|
|
36
|
+
| 'low'
|
|
37
|
+
| 'minimal'
|
|
38
|
+
| 'none';
|
|
39
|
+
|
|
40
|
+
export interface OpenRouterReasoning {
|
|
41
|
+
effort?: OpenRouterReasoningEffort;
|
|
42
|
+
max_tokens?: number;
|
|
43
|
+
exclude?: boolean;
|
|
44
|
+
enabled?: boolean;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface ChatOpenRouterCallOptions
|
|
48
|
+
extends Omit<ChatOpenAICallOptions, 'reasoning'> {
|
|
49
|
+
/** @deprecated Use `reasoning` object instead */
|
|
33
50
|
include_reasoning?: boolean;
|
|
51
|
+
reasoning?: OpenRouterReasoning;
|
|
34
52
|
modelKwargs?: OpenAIChatInput['modelKwargs'];
|
|
35
53
|
}
|
|
54
|
+
|
|
55
|
+
/** invocationParams return type extended with OpenRouter reasoning */
|
|
56
|
+
export type OpenRouterInvocationParams = Omit<
|
|
57
|
+
OpenAIClient.Chat.ChatCompletionCreateParams,
|
|
58
|
+
'messages'
|
|
59
|
+
> & {
|
|
60
|
+
reasoning?: OpenRouterReasoning;
|
|
61
|
+
};
|
|
36
62
|
export class ChatOpenRouter extends ChatOpenAI {
|
|
63
|
+
private openRouterReasoning?: OpenRouterReasoning;
|
|
64
|
+
/** @deprecated Use `reasoning` object instead */
|
|
65
|
+
private includeReasoning?: boolean;
|
|
66
|
+
|
|
37
67
|
constructor(_fields: Partial<ChatOpenRouterCallOptions>) {
|
|
38
|
-
const {
|
|
68
|
+
const {
|
|
69
|
+
include_reasoning,
|
|
70
|
+
reasoning: openRouterReasoning,
|
|
71
|
+
modelKwargs = {},
|
|
72
|
+
...fields
|
|
73
|
+
} = _fields;
|
|
74
|
+
|
|
75
|
+
// Extract reasoning from modelKwargs if provided there (e.g., from LLMConfig)
|
|
76
|
+
const { reasoning: mkReasoning, ...restModelKwargs } = modelKwargs as {
|
|
77
|
+
reasoning?: OpenRouterReasoning;
|
|
78
|
+
} & Record<string, unknown>;
|
|
79
|
+
|
|
39
80
|
super({
|
|
40
81
|
...fields,
|
|
41
|
-
modelKwargs:
|
|
42
|
-
...modelKwargs,
|
|
43
|
-
include_reasoning,
|
|
44
|
-
},
|
|
82
|
+
modelKwargs: restModelKwargs,
|
|
45
83
|
});
|
|
84
|
+
|
|
85
|
+
// Merge reasoning config: modelKwargs.reasoning < constructor reasoning
|
|
86
|
+
if (mkReasoning != null || openRouterReasoning != null) {
|
|
87
|
+
this.openRouterReasoning = {
|
|
88
|
+
...mkReasoning,
|
|
89
|
+
...openRouterReasoning,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
this.includeReasoning = include_reasoning;
|
|
46
94
|
}
|
|
47
95
|
static lc_name(): 'LibreChatOpenRouter' {
|
|
48
96
|
return 'LibreChatOpenRouter';
|
|
49
97
|
}
|
|
98
|
+
|
|
99
|
+
// @ts-expect-error - OpenRouter reasoning extends OpenAI Reasoning with additional
|
|
100
|
+
// effort levels ('xhigh' | 'none' | 'minimal') not in ReasoningEffort.
|
|
101
|
+
// The parent's generic conditional return type cannot be widened in an override.
|
|
102
|
+
override invocationParams(
|
|
103
|
+
options?: this['ParsedCallOptions'],
|
|
104
|
+
extra?: { streaming?: boolean }
|
|
105
|
+
): OpenRouterInvocationParams {
|
|
106
|
+
type MutableParams = Omit<
|
|
107
|
+
OpenAIClient.Chat.ChatCompletionCreateParams,
|
|
108
|
+
'messages'
|
|
109
|
+
> & { reasoning_effort?: string; reasoning?: OpenRouterReasoning };
|
|
110
|
+
|
|
111
|
+
const params = super.invocationParams(options, extra) as MutableParams;
|
|
112
|
+
|
|
113
|
+
// Remove the OpenAI-native reasoning_effort that the parent sets;
|
|
114
|
+
// OpenRouter uses a `reasoning` object instead
|
|
115
|
+
delete params.reasoning_effort;
|
|
116
|
+
|
|
117
|
+
// Build the OpenRouter reasoning config
|
|
118
|
+
const reasoning = this.buildOpenRouterReasoning(options);
|
|
119
|
+
if (reasoning != null) {
|
|
120
|
+
params.reasoning = reasoning;
|
|
121
|
+
} else {
|
|
122
|
+
delete params.reasoning;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return params;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
private buildOpenRouterReasoning(
|
|
129
|
+
options?: this['ParsedCallOptions']
|
|
130
|
+
): OpenRouterReasoning | undefined {
|
|
131
|
+
let reasoning: OpenRouterReasoning | undefined;
|
|
132
|
+
|
|
133
|
+
// 1. Instance-level reasoning config (from constructor)
|
|
134
|
+
if (this.openRouterReasoning != null) {
|
|
135
|
+
reasoning = { ...this.openRouterReasoning };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// 2. LangChain-style reasoning params (from parent's `this.reasoning`)
|
|
139
|
+
const lcReasoning = this.getReasoningParams(options);
|
|
140
|
+
if (lcReasoning?.effort != null) {
|
|
141
|
+
reasoning = {
|
|
142
|
+
...reasoning,
|
|
143
|
+
effort: lcReasoning.effort as OpenRouterReasoningEffort,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// 3. Call-level reasoning override
|
|
148
|
+
const callReasoning = (options as ChatOpenRouterCallOptions | undefined)
|
|
149
|
+
?.reasoning;
|
|
150
|
+
if (callReasoning != null) {
|
|
151
|
+
reasoning = { ...reasoning, ...callReasoning };
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// 4. Legacy include_reasoning backward compatibility
|
|
155
|
+
if (reasoning == null && this.includeReasoning === true) {
|
|
156
|
+
reasoning = { enabled: true };
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return reasoning;
|
|
160
|
+
}
|
|
50
161
|
protected override _convertOpenAIDeltaToBaseMessageChunk(
|
|
51
162
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
52
163
|
delta: Record<string, any>,
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import { ChatOpenRouter } from './index';
|
|
2
|
+
import type { OpenRouterReasoning, ChatOpenRouterCallOptions } from './index';
|
|
3
|
+
import type { OpenAIChatInput } from '@langchain/openai';
|
|
4
|
+
|
|
5
|
+
type CreateRouterOptions = Partial<
|
|
6
|
+
ChatOpenRouterCallOptions & Pick<OpenAIChatInput, 'model' | 'apiKey'>
|
|
7
|
+
>;
|
|
8
|
+
|
|
9
|
+
function createRouter(overrides: CreateRouterOptions = {}): ChatOpenRouter {
|
|
10
|
+
return new ChatOpenRouter({
|
|
11
|
+
model: 'openrouter/test-model',
|
|
12
|
+
apiKey: 'test-key',
|
|
13
|
+
...overrides,
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
describe('ChatOpenRouter reasoning handling', () => {
|
|
18
|
+
// ---------------------------------------------------------------
|
|
19
|
+
// 1. Constructor reasoning config
|
|
20
|
+
// ---------------------------------------------------------------
|
|
21
|
+
describe('constructor reasoning config', () => {
|
|
22
|
+
it('stores reasoning when passed directly', () => {
|
|
23
|
+
const router = createRouter({ reasoning: { effort: 'high' } });
|
|
24
|
+
const params = router.invocationParams();
|
|
25
|
+
expect(params.reasoning).toEqual({ effort: 'high' });
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
// ---------------------------------------------------------------
|
|
30
|
+
// 2. modelKwargs reasoning extraction
|
|
31
|
+
// ---------------------------------------------------------------
|
|
32
|
+
describe('modelKwargs reasoning extraction', () => {
|
|
33
|
+
it('extracts reasoning from modelKwargs and places it into params.reasoning', () => {
|
|
34
|
+
const router = createRouter({
|
|
35
|
+
modelKwargs: { reasoning: { effort: 'medium' } },
|
|
36
|
+
});
|
|
37
|
+
const params = router.invocationParams();
|
|
38
|
+
expect(params.reasoning).toEqual({ effort: 'medium' });
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('does not leak reasoning into modelKwargs that reach the parent', () => {
|
|
42
|
+
const router = createRouter({
|
|
43
|
+
modelKwargs: {
|
|
44
|
+
reasoning: { effort: 'medium' },
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
const params = router.invocationParams();
|
|
48
|
+
// reasoning should be the structured OpenRouter object, not buried in modelKwargs
|
|
49
|
+
expect(params.reasoning).toEqual({ effort: 'medium' });
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// ---------------------------------------------------------------
|
|
54
|
+
// 3. Reasoning merge precedence
|
|
55
|
+
// ---------------------------------------------------------------
|
|
56
|
+
describe('reasoning merge precedence', () => {
|
|
57
|
+
it('constructor reasoning overrides modelKwargs.reasoning', () => {
|
|
58
|
+
const router = createRouter({
|
|
59
|
+
reasoning: { effort: 'high' },
|
|
60
|
+
modelKwargs: { reasoning: { effort: 'low' } },
|
|
61
|
+
});
|
|
62
|
+
const params = router.invocationParams();
|
|
63
|
+
expect(params.reasoning).toEqual({ effort: 'high' });
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('merges non-overlapping keys from modelKwargs.reasoning and constructor reasoning', () => {
|
|
67
|
+
const router = createRouter({
|
|
68
|
+
reasoning: { effort: 'high' },
|
|
69
|
+
modelKwargs: { reasoning: { max_tokens: 5000 } },
|
|
70
|
+
});
|
|
71
|
+
const params = router.invocationParams();
|
|
72
|
+
expect(params.reasoning).toEqual({ effort: 'high', max_tokens: 5000 });
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// ---------------------------------------------------------------
|
|
77
|
+
// 4. invocationParams output
|
|
78
|
+
// ---------------------------------------------------------------
|
|
79
|
+
describe('invocationParams output', () => {
|
|
80
|
+
it('includes reasoning object in params', () => {
|
|
81
|
+
const router = createRouter({ reasoning: { effort: 'high' } });
|
|
82
|
+
const params = router.invocationParams();
|
|
83
|
+
expect(params.reasoning).toBeDefined();
|
|
84
|
+
expect(params.reasoning).toEqual({ effort: 'high' });
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('does NOT include reasoning_effort in params', () => {
|
|
88
|
+
const router = createRouter({ reasoning: { effort: 'high' } });
|
|
89
|
+
const params = router.invocationParams();
|
|
90
|
+
expect(params.reasoning_effort).toBeUndefined();
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('does not include reasoning when none is configured', () => {
|
|
94
|
+
const router = createRouter();
|
|
95
|
+
const params = router.invocationParams();
|
|
96
|
+
expect(params.reasoning).toBeUndefined();
|
|
97
|
+
expect(params.reasoning_effort).toBeUndefined();
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
// ---------------------------------------------------------------
|
|
102
|
+
// 5. Legacy include_reasoning
|
|
103
|
+
// ---------------------------------------------------------------
|
|
104
|
+
describe('legacy include_reasoning', () => {
|
|
105
|
+
it('produces { enabled: true } when only include_reasoning is true', () => {
|
|
106
|
+
const router = createRouter({ include_reasoning: true });
|
|
107
|
+
const params = router.invocationParams();
|
|
108
|
+
expect(params.reasoning).toEqual({ enabled: true });
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('does not produce reasoning when include_reasoning is false', () => {
|
|
112
|
+
const router = createRouter({ include_reasoning: false });
|
|
113
|
+
const params = router.invocationParams();
|
|
114
|
+
expect(params.reasoning).toBeUndefined();
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
// ---------------------------------------------------------------
|
|
119
|
+
// 6. Legacy include_reasoning ignored when reasoning is provided
|
|
120
|
+
// ---------------------------------------------------------------
|
|
121
|
+
describe('legacy include_reasoning ignored when reasoning provided', () => {
|
|
122
|
+
it('reasoning wins over include_reasoning', () => {
|
|
123
|
+
const router = createRouter({
|
|
124
|
+
reasoning: { effort: 'medium' },
|
|
125
|
+
include_reasoning: true,
|
|
126
|
+
});
|
|
127
|
+
const params = router.invocationParams();
|
|
128
|
+
// Should use the structured reasoning, NOT fall back to { enabled: true }
|
|
129
|
+
expect(params.reasoning).toEqual({ effort: 'medium' });
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('reasoning from modelKwargs also wins over include_reasoning', () => {
|
|
133
|
+
const router = createRouter({
|
|
134
|
+
modelKwargs: { reasoning: { effort: 'low' } },
|
|
135
|
+
include_reasoning: true,
|
|
136
|
+
});
|
|
137
|
+
const params = router.invocationParams();
|
|
138
|
+
expect(params.reasoning).toEqual({ effort: 'low' });
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
// ---------------------------------------------------------------
|
|
143
|
+
// 7. Various effort levels (OpenRouter-specific)
|
|
144
|
+
// ---------------------------------------------------------------
|
|
145
|
+
describe('various effort levels', () => {
|
|
146
|
+
const efforts: Array<{
|
|
147
|
+
effort: OpenRouterReasoning['effort'];
|
|
148
|
+
}> = [
|
|
149
|
+
{ effort: 'xhigh' },
|
|
150
|
+
{ effort: 'none' },
|
|
151
|
+
{ effort: 'minimal' },
|
|
152
|
+
{ effort: 'high' },
|
|
153
|
+
{ effort: 'medium' },
|
|
154
|
+
{ effort: 'low' },
|
|
155
|
+
];
|
|
156
|
+
|
|
157
|
+
it.each(efforts)('supports effort level "$effort"', ({ effort }) => {
|
|
158
|
+
const router = createRouter({ reasoning: { effort } });
|
|
159
|
+
const params = router.invocationParams();
|
|
160
|
+
expect(params.reasoning).toEqual({ effort });
|
|
161
|
+
expect(params.reasoning_effort).toBeUndefined();
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
// ---------------------------------------------------------------
|
|
166
|
+
// 8. max_tokens reasoning
|
|
167
|
+
// ---------------------------------------------------------------
|
|
168
|
+
describe('max_tokens reasoning', () => {
|
|
169
|
+
it('passes max_tokens in reasoning object', () => {
|
|
170
|
+
const router = createRouter({
|
|
171
|
+
reasoning: { max_tokens: 8000 },
|
|
172
|
+
});
|
|
173
|
+
const params = router.invocationParams();
|
|
174
|
+
expect(params.reasoning).toEqual({ max_tokens: 8000 });
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it('combines max_tokens with effort', () => {
|
|
178
|
+
const router = createRouter({
|
|
179
|
+
reasoning: { effort: 'high', max_tokens: 8000 },
|
|
180
|
+
});
|
|
181
|
+
const params = router.invocationParams();
|
|
182
|
+
expect(params.reasoning).toEqual({ effort: 'high', max_tokens: 8000 });
|
|
183
|
+
expect(params.reasoning_effort).toBeUndefined();
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
// ---------------------------------------------------------------
|
|
188
|
+
// 9. exclude reasoning
|
|
189
|
+
// ---------------------------------------------------------------
|
|
190
|
+
describe('exclude reasoning', () => {
|
|
191
|
+
it('passes exclude flag in reasoning object', () => {
|
|
192
|
+
const router = createRouter({
|
|
193
|
+
reasoning: { effort: 'high', exclude: true },
|
|
194
|
+
});
|
|
195
|
+
const params = router.invocationParams();
|
|
196
|
+
expect(params.reasoning).toEqual({ effort: 'high', exclude: true });
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it('supports exclude without effort', () => {
|
|
200
|
+
const router = createRouter({
|
|
201
|
+
reasoning: { exclude: true },
|
|
202
|
+
});
|
|
203
|
+
const params = router.invocationParams();
|
|
204
|
+
expect(params.reasoning).toEqual({ exclude: true });
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
});
|
|
@@ -6,9 +6,11 @@ import type {
|
|
|
6
6
|
GoogleAbstractedClient,
|
|
7
7
|
} from '@langchain/google-common';
|
|
8
8
|
import type { BaseMessage } from '@langchain/core/messages';
|
|
9
|
-
import type { VertexAIClientOptions } from '@/types';
|
|
9
|
+
import type { GoogleThinkingConfig, VertexAIClientOptions } from '@/types';
|
|
10
10
|
|
|
11
11
|
class CustomChatConnection extends ChatConnection<VertexAIClientOptions> {
|
|
12
|
+
thinkingConfig?: GoogleThinkingConfig;
|
|
13
|
+
|
|
12
14
|
async formatData(
|
|
13
15
|
input: BaseMessage[],
|
|
14
16
|
parameters: GoogleAIModelRequestParams
|
|
@@ -26,6 +28,15 @@ class CustomChatConnection extends ChatConnection<VertexAIClientOptions> {
|
|
|
26
28
|
}
|
|
27
29
|
delete formattedData.generationConfig.thinkingConfig.thinkingBudget;
|
|
28
30
|
}
|
|
31
|
+
if (this.thinkingConfig?.thinkingLevel) {
|
|
32
|
+
formattedData.generationConfig ??= {};
|
|
33
|
+
(
|
|
34
|
+
formattedData.generationConfig as Record<string, unknown>
|
|
35
|
+
).thinkingConfig = {
|
|
36
|
+
...formattedData.generationConfig.thinkingConfig,
|
|
37
|
+
thinkingLevel: this.thinkingConfig.thinkingLevel,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
29
40
|
return formattedData;
|
|
30
41
|
}
|
|
31
42
|
}
|
|
@@ -315,6 +326,7 @@ class CustomChatConnection extends ChatConnection<VertexAIClientOptions> {
|
|
|
315
326
|
export class ChatVertexAI extends ChatGoogle {
|
|
316
327
|
lc_namespace = ['langchain', 'chat_models', 'vertexai'];
|
|
317
328
|
dynamicThinkingBudget = false;
|
|
329
|
+
thinkingConfig?: GoogleThinkingConfig;
|
|
318
330
|
|
|
319
331
|
static lc_name(): 'LibreChatVertexAI' {
|
|
320
332
|
return 'LibreChatVertexAI';
|
|
@@ -327,6 +339,7 @@ export class ChatVertexAI extends ChatGoogle {
|
|
|
327
339
|
platformType: 'gcp',
|
|
328
340
|
});
|
|
329
341
|
this.dynamicThinkingBudget = dynamicThinkingBudget;
|
|
342
|
+
this.thinkingConfig = fields?.thinkingConfig;
|
|
330
343
|
}
|
|
331
344
|
invocationParams(
|
|
332
345
|
options?: this['ParsedCallOptions'] | undefined
|
|
@@ -342,18 +355,22 @@ export class ChatVertexAI extends ChatGoogle {
|
|
|
342
355
|
fields: VertexAIClientOptions,
|
|
343
356
|
client: GoogleAbstractedClient
|
|
344
357
|
): void {
|
|
345
|
-
|
|
358
|
+
const connection = new CustomChatConnection(
|
|
346
359
|
{ ...fields, ...this },
|
|
347
360
|
this.caller,
|
|
348
361
|
client,
|
|
349
362
|
false
|
|
350
363
|
);
|
|
364
|
+
connection.thinkingConfig = this.thinkingConfig;
|
|
365
|
+
this.connection = connection;
|
|
351
366
|
|
|
352
|
-
|
|
367
|
+
const streamedConnection = new CustomChatConnection(
|
|
353
368
|
{ ...fields, ...this },
|
|
354
369
|
this.caller,
|
|
355
370
|
client,
|
|
356
371
|
true
|
|
357
372
|
);
|
|
373
|
+
streamedConnection.thinkingConfig = this.thinkingConfig;
|
|
374
|
+
this.streamedConnection = streamedConnection;
|
|
358
375
|
}
|
|
359
376
|
}
|
package/src/run.ts
CHANGED
|
@@ -45,6 +45,7 @@ export class Run<_T extends t.BaseGraphState> {
|
|
|
45
45
|
graphRunnable?: t.CompiledStateWorkflow;
|
|
46
46
|
Graph: StandardGraph | MultiAgentGraph | undefined;
|
|
47
47
|
returnContent: boolean = false;
|
|
48
|
+
private skipCleanup: boolean = false;
|
|
48
49
|
|
|
49
50
|
private constructor(config: Partial<t.RunConfig>) {
|
|
50
51
|
const runId = config.runId ?? '';
|
|
@@ -89,6 +90,7 @@ export class Run<_T extends t.BaseGraphState> {
|
|
|
89
90
|
}
|
|
90
91
|
|
|
91
92
|
this.returnContent = config.returnContent ?? false;
|
|
93
|
+
this.skipCleanup = config.skipCleanup ?? false;
|
|
92
94
|
}
|
|
93
95
|
|
|
94
96
|
private createLegacyGraph(
|
|
@@ -303,9 +305,45 @@ export class Run<_T extends t.BaseGraphState> {
|
|
|
303
305
|
}
|
|
304
306
|
}
|
|
305
307
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
+
/**
|
|
309
|
+
* Break the reference chain that keeps heavy data alive via
|
|
310
|
+
* LangGraph's internal `__pregel_scratchpad.currentTaskInput` →
|
|
311
|
+
* `@langchain/core` `RunTree.extra[lc:child_config]` →
|
|
312
|
+
* Node.js `AsyncLocalStorage` context captured by timers/promises.
|
|
313
|
+
*
|
|
314
|
+
* Without this, base64-encoded images/PDFs in message content remain
|
|
315
|
+
* reachable from lingering `Timeout` handles until GC runs.
|
|
316
|
+
*/
|
|
317
|
+
if (!this.skipCleanup) {
|
|
318
|
+
if (
|
|
319
|
+
(config.configurable as Record<string, unknown> | undefined) != null
|
|
320
|
+
) {
|
|
321
|
+
for (const key of Object.getOwnPropertySymbols(config.configurable)) {
|
|
322
|
+
const val = config.configurable[key as unknown as string] as
|
|
323
|
+
| Record<string, unknown>
|
|
324
|
+
| undefined;
|
|
325
|
+
if (
|
|
326
|
+
val != null &&
|
|
327
|
+
typeof val === 'object' &&
|
|
328
|
+
'currentTaskInput' in val
|
|
329
|
+
) {
|
|
330
|
+
(val as Record<string, unknown>).currentTaskInput = undefined;
|
|
331
|
+
}
|
|
332
|
+
delete config.configurable[key as unknown as string];
|
|
333
|
+
}
|
|
334
|
+
config.configurable = undefined;
|
|
335
|
+
}
|
|
336
|
+
config.callbacks = undefined;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const result = this.returnContent
|
|
340
|
+
? this.Graph.getContentParts()
|
|
341
|
+
: undefined;
|
|
342
|
+
|
|
343
|
+
if (!this.skipCleanup) {
|
|
344
|
+
this.Graph.clearHeavyState();
|
|
308
345
|
}
|
|
346
|
+
return result;
|
|
309
347
|
}
|
|
310
348
|
|
|
311
349
|
private createSystemCallback<K extends keyof t.ClientCallbacks>(
|
|
@@ -98,6 +98,7 @@ async function testStandardStreaming(): Promise<void> {
|
|
|
98
98
|
// additional_instructions: `Always address the user by their name. The user's name is ${userName} and they are located in ${location}.`,
|
|
99
99
|
},
|
|
100
100
|
returnContent: true,
|
|
101
|
+
skipCleanup: true,
|
|
101
102
|
customHandlers,
|
|
102
103
|
});
|
|
103
104
|
|
|
@@ -100,6 +100,7 @@ async function testStandardStreaming(): Promise<void> {
|
|
|
100
100
|
// additional_instructions: `Always address the user by their name. The user's name is ${userName} and they are located in ${location}.`,
|
|
101
101
|
},
|
|
102
102
|
returnContent: true,
|
|
103
|
+
skipCleanup: true,
|
|
103
104
|
customHandlers,
|
|
104
105
|
});
|
|
105
106
|
|
package/src/scripts/caching.ts
CHANGED
package/src/scripts/code_exec.ts
CHANGED
|
@@ -127,6 +127,7 @@ async function testCodeExecution(): Promise<void> {
|
|
|
127
127
|
additional_instructions: `The user's name is ${userName} and they are located in ${location}. The current date is ${currentDate}.`,
|
|
128
128
|
},
|
|
129
129
|
returnContent: true,
|
|
130
|
+
skipCleanup: true,
|
|
130
131
|
customHandlers,
|
|
131
132
|
});
|
|
132
133
|
|
package/src/scripts/content.ts
CHANGED
package/src/scripts/image.ts
CHANGED
package/src/scripts/memory.ts
CHANGED
|
@@ -17,19 +17,27 @@ const memoryKv: Record<string, string> = {};
|
|
|
17
17
|
const setMemory = tool(
|
|
18
18
|
async ({ key, value }) => {
|
|
19
19
|
if (!/^[a-z_]+$/.test(key)) {
|
|
20
|
-
throw new Error(
|
|
20
|
+
throw new Error(
|
|
21
|
+
'Key must only contain lowercase letters and underscores'
|
|
22
|
+
);
|
|
21
23
|
}
|
|
22
|
-
|
|
24
|
+
|
|
23
25
|
memoryKv[key] = value;
|
|
24
|
-
|
|
26
|
+
|
|
25
27
|
return { ok: true };
|
|
26
28
|
},
|
|
27
29
|
{
|
|
28
30
|
name: 'set_memory',
|
|
29
31
|
description: 'Saves important data about the user into memory.',
|
|
30
32
|
schema: z.object({
|
|
31
|
-
key: z
|
|
32
|
-
|
|
33
|
+
key: z
|
|
34
|
+
.string()
|
|
35
|
+
.describe(
|
|
36
|
+
'The key of the memory value. Always use lowercase and underscores, no other characters.'
|
|
37
|
+
),
|
|
38
|
+
value: z
|
|
39
|
+
.string()
|
|
40
|
+
.describe('Value can be anything represented as a string'),
|
|
33
41
|
}),
|
|
34
42
|
}
|
|
35
43
|
);
|
|
@@ -48,10 +56,12 @@ async function testStandardStreaming(): Promise<void> {
|
|
|
48
56
|
streaming: false,
|
|
49
57
|
},
|
|
50
58
|
tools: [setMemory],
|
|
51
|
-
instructions:
|
|
59
|
+
instructions:
|
|
60
|
+
'You can use the `set_memory` tool to save important data about the user into memory. If there is nothing to note about the user specifically, respond with `nothing`.',
|
|
52
61
|
toolEnd: true,
|
|
53
62
|
},
|
|
54
63
|
returnContent: true,
|
|
64
|
+
skipCleanup: true,
|
|
55
65
|
});
|
|
56
66
|
|
|
57
67
|
const config = {
|