@librechat/agents 3.2.53 → 3.2.55
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/llm/anthropic/utils/message_inputs.cjs +1 -1
- package/dist/cjs/llm/anthropic/utils/message_inputs.cjs.map +1 -1
- package/dist/cjs/llm/invoke.cjs +3 -0
- package/dist/cjs/llm/invoke.cjs.map +1 -1
- package/dist/cjs/llm/truncation.cjs +110 -0
- package/dist/cjs/llm/truncation.cjs.map +1 -0
- package/dist/esm/llm/anthropic/utils/message_inputs.mjs +1 -1
- package/dist/esm/llm/anthropic/utils/message_inputs.mjs.map +1 -1
- package/dist/esm/llm/invoke.mjs +3 -0
- package/dist/esm/llm/invoke.mjs.map +1 -1
- package/dist/esm/llm/truncation.mjs +110 -0
- package/dist/esm/llm/truncation.mjs.map +1 -0
- package/dist/types/llm/truncation.d.ts +45 -0
- package/package.json +1 -1
- package/src/llm/anthropic/utils/cross-provider-reasoning.test.ts +66 -0
- package/src/llm/anthropic/utils/message_inputs.ts +9 -1
- package/src/llm/invoke.ts +3 -0
- package/src/llm/truncation.test.ts +242 -0
- package/src/llm/truncation.ts +178 -0
- package/src/specs/bedrock-truncation.live.test.ts +191 -0
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Live Bedrock tool-call truncation verification.
|
|
3
|
+
*
|
|
4
|
+
* Reproduces the production failure where a large tool argument (`content`)
|
|
5
|
+
* gets cut off by the max output token limit, leaving the handler with a
|
|
6
|
+
* well-formed-but-incomplete tool call and no truncation signal.
|
|
7
|
+
*
|
|
8
|
+
* Run with:
|
|
9
|
+
* RUN_BEDROCK_TRUNCATION_LIVE=1 npm test -- bedrock-truncation.live.test.ts --runInBand
|
|
10
|
+
*/
|
|
11
|
+
import { config as dotenvConfig } from 'dotenv';
|
|
12
|
+
dotenvConfig();
|
|
13
|
+
|
|
14
|
+
import { z } from 'zod';
|
|
15
|
+
import { HumanMessage } from '@langchain/core/messages';
|
|
16
|
+
import { DynamicStructuredTool } from '@langchain/core/tools';
|
|
17
|
+
import { describe, expect, it, jest } from '@jest/globals';
|
|
18
|
+
import type * as t from '@/types';
|
|
19
|
+
import { GraphEvents, Providers } from '@/common';
|
|
20
|
+
import { ChatModelStreamHandler } from '@/stream';
|
|
21
|
+
import { ToolEndHandler, ModelEndHandler } from '@/events';
|
|
22
|
+
import { Run } from '@/run';
|
|
23
|
+
|
|
24
|
+
const shouldRunLive =
|
|
25
|
+
process.env.RUN_BEDROCK_TRUNCATION_LIVE === '1' &&
|
|
26
|
+
(process.env.BEDROCK_AWS_ACCESS_KEY_ID ?? '') !== '' &&
|
|
27
|
+
(process.env.BEDROCK_AWS_SECRET_ACCESS_KEY ?? '') !== '';
|
|
28
|
+
|
|
29
|
+
const describeIfLive = shouldRunLive ? describe : describe.skip;
|
|
30
|
+
|
|
31
|
+
const REGION =
|
|
32
|
+
[
|
|
33
|
+
process.env.BEDROCK_AWS_REGION,
|
|
34
|
+
process.env.BEDROCK_AWS_DEFAULT_REGION,
|
|
35
|
+
process.env.AWS_DEFAULT_REGION,
|
|
36
|
+
].find(
|
|
37
|
+
(value): value is string => typeof value === 'string' && value !== ''
|
|
38
|
+
) ?? 'us-east-1';
|
|
39
|
+
const MODEL = 'us.anthropic.claude-sonnet-4-5-20250929-v1:0';
|
|
40
|
+
const MAX_TOKENS = Number(process.env.REPRO_MAX_TOKENS ?? 350);
|
|
41
|
+
|
|
42
|
+
const schema = z.object({
|
|
43
|
+
path: z.string().describe('Path to write.'),
|
|
44
|
+
content: z.string().describe('Complete file contents.'),
|
|
45
|
+
overwrite: z.boolean().optional(),
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
describeIfLive('Bedrock tool-call truncation (live)', () => {
|
|
49
|
+
jest.setTimeout(120_000);
|
|
50
|
+
|
|
51
|
+
it('fails fast with a truncation error instead of looping', async () => {
|
|
52
|
+
const received: Array<{ keys: string[]; contentLen: number | null }> = [];
|
|
53
|
+
const stepToolCalls: string[][] = [];
|
|
54
|
+
let invocations = 0;
|
|
55
|
+
|
|
56
|
+
const createFile = new DynamicStructuredTool({
|
|
57
|
+
name: 'create_file',
|
|
58
|
+
description: 'Create a new file. Requires path and content.',
|
|
59
|
+
schema,
|
|
60
|
+
func: async (input: z.infer<typeof schema>) => {
|
|
61
|
+
invocations += 1;
|
|
62
|
+
const hasContent =
|
|
63
|
+
typeof input.content === 'string' && input.content.length > 0;
|
|
64
|
+
received.push({
|
|
65
|
+
keys: Object.keys(input as Record<string, unknown>),
|
|
66
|
+
contentLen: hasContent ? input.content.length : null,
|
|
67
|
+
});
|
|
68
|
+
if (!hasContent) {
|
|
69
|
+
return 'Error: content is required\n Please fix your mistakes.';
|
|
70
|
+
}
|
|
71
|
+
return `Wrote ${input.content.length} bytes to ${input.path}`;
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
const llmConfig = {
|
|
76
|
+
provider: Providers.BEDROCK,
|
|
77
|
+
model: MODEL,
|
|
78
|
+
region: REGION,
|
|
79
|
+
credentials: {
|
|
80
|
+
accessKeyId: process.env.BEDROCK_AWS_ACCESS_KEY_ID!,
|
|
81
|
+
secretAccessKey: process.env.BEDROCK_AWS_SECRET_ACCESS_KEY!,
|
|
82
|
+
},
|
|
83
|
+
maxTokens: MAX_TOKENS,
|
|
84
|
+
streaming: true,
|
|
85
|
+
streamUsage: true,
|
|
86
|
+
} as t.LLMConfig;
|
|
87
|
+
|
|
88
|
+
const run = await Run.create<t.IState>({
|
|
89
|
+
runId: 'bedrock-truncation-live',
|
|
90
|
+
graphConfig: {
|
|
91
|
+
type: 'standard',
|
|
92
|
+
llmConfig,
|
|
93
|
+
tools: [createFile],
|
|
94
|
+
instructions: 'You are a ClickHouse assistant. Use tools when asked.',
|
|
95
|
+
maxContextTokens: 89000,
|
|
96
|
+
},
|
|
97
|
+
returnContent: true,
|
|
98
|
+
skipCleanup: true,
|
|
99
|
+
customHandlers: {
|
|
100
|
+
[GraphEvents.TOOL_END]: new ToolEndHandler(
|
|
101
|
+
async (toolEndData: t.ToolEndData) => {
|
|
102
|
+
const out = toolEndData.output as
|
|
103
|
+
| { content?: unknown; name?: string; status?: string }
|
|
104
|
+
| undefined;
|
|
105
|
+
const content =
|
|
106
|
+
typeof out?.content === 'string'
|
|
107
|
+
? out.content.slice(0, 120)
|
|
108
|
+
: JSON.stringify(out?.content).slice(0, 120);
|
|
109
|
+
// eslint-disable-next-line no-console
|
|
110
|
+
console.log(
|
|
111
|
+
`TOOL_END name=${out?.name} status=${out?.status} content=${content}`
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
),
|
|
115
|
+
[GraphEvents.CHAT_MODEL_END]: new ModelEndHandler(),
|
|
116
|
+
[GraphEvents.CHAT_MODEL_STREAM]: new ChatModelStreamHandler(),
|
|
117
|
+
[GraphEvents.ON_RUN_STEP]: {
|
|
118
|
+
handle: (_event: string, data: t.StreamEventData): void => {
|
|
119
|
+
const step = data as unknown as {
|
|
120
|
+
stepDetails?: {
|
|
121
|
+
type?: string;
|
|
122
|
+
tool_calls?: Array<{ name?: string; args?: unknown }>;
|
|
123
|
+
};
|
|
124
|
+
};
|
|
125
|
+
const tcs = step.stepDetails?.tool_calls;
|
|
126
|
+
if ((tcs?.length ?? 0) > 0 && tcs != null) {
|
|
127
|
+
for (const tc of tcs) {
|
|
128
|
+
if (tc.name === 'create_file') {
|
|
129
|
+
stepToolCalls.push(
|
|
130
|
+
typeof tc.args === 'string'
|
|
131
|
+
? ['<raw-string>']
|
|
132
|
+
: Object.keys((tc.args ?? {}) as Record<string, unknown>)
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
// eslint-disable-next-line no-console
|
|
137
|
+
console.log(
|
|
138
|
+
'ON_RUN_STEP tool_calls=',
|
|
139
|
+
JSON.stringify(
|
|
140
|
+
tcs.map((tc) => ({
|
|
141
|
+
name: tc.name,
|
|
142
|
+
args:
|
|
143
|
+
typeof tc.args === 'string'
|
|
144
|
+
? tc.args.slice(0, 80)
|
|
145
|
+
: Object.keys(
|
|
146
|
+
(tc.args ?? {}) as Record<string, unknown>
|
|
147
|
+
),
|
|
148
|
+
}))
|
|
149
|
+
)
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
const prompt =
|
|
158
|
+
'Call create_file once: path="skills/clickhouse-demo/SKILL.md", content=a complete ' +
|
|
159
|
+
'3000-word ClickHouse guide (schema design, MergeTree, partitioning, 10 example queries). ' +
|
|
160
|
+
'Put the entire guide in the content argument.';
|
|
161
|
+
|
|
162
|
+
let loopError: string | null = null;
|
|
163
|
+
try {
|
|
164
|
+
await run.processStream(
|
|
165
|
+
{ messages: [new HumanMessage(prompt)] },
|
|
166
|
+
{
|
|
167
|
+
configurable: { provider: Providers.BEDROCK, thread_id: 'trunc' },
|
|
168
|
+
version: 'v2',
|
|
169
|
+
recursionLimit: 8,
|
|
170
|
+
}
|
|
171
|
+
);
|
|
172
|
+
} catch (err) {
|
|
173
|
+
loopError = err instanceof Error ? err.message : String(err);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// eslint-disable-next-line no-console
|
|
177
|
+
console.log('LIVE RESULT', {
|
|
178
|
+
invocations,
|
|
179
|
+
received,
|
|
180
|
+
stepToolCalls,
|
|
181
|
+
loopError,
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
// The run fails fast with the actionable truncation error...
|
|
185
|
+
expect(loopError).toMatch(/truncated at the maximum output token limit/i);
|
|
186
|
+
// ...instead of looping until the recursion limit.
|
|
187
|
+
expect(loopError).not.toMatch(/Recursion limit/i);
|
|
188
|
+
// No truncated create_file call ever carried a usable `content` arg.
|
|
189
|
+
expect(stepToolCalls.every((keys) => !keys.includes('content'))).toBe(true);
|
|
190
|
+
});
|
|
191
|
+
});
|