@librechat/agents 3.1.54 → 3.1.56
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.map +1 -1
- package/dist/cjs/common/enum.cjs.map +1 -1
- package/dist/cjs/events.cjs.map +1 -1
- package/dist/cjs/graphs/Graph.cjs +1 -1
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/graphs/MultiAgentGraph.cjs.map +1 -1
- package/dist/cjs/llm/anthropic/index.cjs.map +1 -1
- package/dist/cjs/llm/anthropic/types.cjs.map +1 -1
- package/dist/cjs/llm/anthropic/utils/message_inputs.cjs.map +1 -1
- package/dist/cjs/llm/anthropic/utils/message_outputs.cjs.map +1 -1
- package/dist/cjs/llm/anthropic/utils/tools.cjs.map +1 -1
- package/dist/cjs/llm/bedrock/index.cjs.map +1 -1
- package/dist/cjs/llm/bedrock/utils/message_inputs.cjs.map +1 -1
- package/dist/cjs/llm/bedrock/utils/message_outputs.cjs.map +1 -1
- package/dist/cjs/llm/fake.cjs.map +1 -1
- package/dist/cjs/llm/google/index.cjs.map +1 -1
- package/dist/cjs/llm/google/utils/common.cjs.map +1 -1
- package/dist/cjs/llm/openai/index.cjs +1 -1
- package/dist/cjs/llm/openai/index.cjs.map +1 -1
- package/dist/cjs/llm/openai/utils/index.cjs.map +1 -1
- package/dist/cjs/llm/openrouter/index.cjs.map +1 -1
- package/dist/cjs/llm/providers.cjs.map +1 -1
- package/dist/cjs/llm/text.cjs.map +1 -1
- package/dist/cjs/llm/vertexai/index.cjs +68 -4
- package/dist/cjs/llm/vertexai/index.cjs.map +1 -1
- package/dist/cjs/main.cjs +29 -28
- package/dist/cjs/main.cjs.map +1 -1
- package/dist/cjs/messages/cache.cjs.map +1 -1
- package/dist/cjs/messages/content.cjs.map +1 -1
- package/dist/cjs/messages/core.cjs.map +1 -1
- package/dist/cjs/messages/format.cjs +118 -32
- package/dist/cjs/messages/format.cjs.map +1 -1
- package/dist/cjs/messages/ids.cjs.map +1 -1
- package/dist/cjs/messages/prune.cjs.map +1 -1
- package/dist/cjs/messages/tools.cjs.map +1 -1
- package/dist/cjs/run.cjs +5 -2
- package/dist/cjs/run.cjs.map +1 -1
- package/dist/cjs/splitStream.cjs.map +1 -1
- package/dist/cjs/stream.cjs +9 -0
- package/dist/cjs/stream.cjs.map +1 -1
- package/dist/cjs/tools/Calculator.cjs.map +1 -1
- package/dist/cjs/tools/CodeExecutor.cjs.map +1 -1
- package/dist/cjs/tools/ProgrammaticToolCalling.cjs.map +1 -1
- package/dist/cjs/tools/ToolNode.cjs +1 -1
- package/dist/cjs/tools/ToolNode.cjs.map +1 -1
- package/dist/cjs/tools/ToolSearch.cjs.map +1 -1
- package/dist/cjs/tools/handlers.cjs.map +1 -1
- package/dist/cjs/tools/schema.cjs.map +1 -1
- package/dist/cjs/tools/search/content.cjs.map +1 -1
- package/dist/cjs/tools/search/firecrawl.cjs.map +1 -1
- package/dist/cjs/tools/search/format.cjs.map +1 -1
- package/dist/cjs/tools/search/highlights.cjs.map +1 -1
- package/dist/cjs/tools/search/rerankers.cjs.map +1 -1
- package/dist/cjs/tools/search/schema.cjs.map +1 -1
- package/dist/cjs/tools/search/search.cjs +1 -0
- package/dist/cjs/tools/search/search.cjs.map +1 -1
- package/dist/cjs/tools/search/serper-scraper.cjs.map +1 -1
- package/dist/cjs/tools/search/tool.cjs.map +1 -1
- package/dist/cjs/tools/search/utils.cjs.map +1 -1
- package/dist/cjs/utils/events.cjs.map +1 -1
- package/dist/cjs/utils/graph.cjs.map +1 -1
- package/dist/cjs/utils/handlers.cjs.map +1 -1
- package/dist/cjs/utils/llm.cjs.map +1 -1
- package/dist/cjs/utils/misc.cjs.map +1 -1
- package/dist/cjs/utils/run.cjs.map +1 -1
- package/dist/cjs/utils/schema.cjs.map +1 -1
- package/dist/cjs/utils/title.cjs.map +1 -1
- package/dist/cjs/utils/tokens.cjs +33 -45
- package/dist/cjs/utils/tokens.cjs.map +1 -1
- package/dist/esm/agents/AgentContext.mjs.map +1 -1
- package/dist/esm/common/enum.mjs.map +1 -1
- package/dist/esm/events.mjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +1 -1
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/graphs/MultiAgentGraph.mjs.map +1 -1
- package/dist/esm/llm/anthropic/index.mjs.map +1 -1
- package/dist/esm/llm/anthropic/types.mjs.map +1 -1
- package/dist/esm/llm/anthropic/utils/message_inputs.mjs.map +1 -1
- package/dist/esm/llm/anthropic/utils/message_outputs.mjs.map +1 -1
- package/dist/esm/llm/anthropic/utils/tools.mjs.map +1 -1
- package/dist/esm/llm/bedrock/index.mjs.map +1 -1
- package/dist/esm/llm/bedrock/utils/message_inputs.mjs.map +1 -1
- package/dist/esm/llm/bedrock/utils/message_outputs.mjs.map +1 -1
- package/dist/esm/llm/fake.mjs.map +1 -1
- package/dist/esm/llm/google/index.mjs.map +1 -1
- package/dist/esm/llm/google/utils/common.mjs.map +1 -1
- package/dist/esm/llm/openai/index.mjs +1 -1
- package/dist/esm/llm/openai/index.mjs.map +1 -1
- package/dist/esm/llm/openai/utils/index.mjs.map +1 -1
- package/dist/esm/llm/openrouter/index.mjs.map +1 -1
- package/dist/esm/llm/providers.mjs.map +1 -1
- package/dist/esm/llm/text.mjs.map +1 -1
- package/dist/esm/llm/vertexai/index.mjs +68 -4
- package/dist/esm/llm/vertexai/index.mjs.map +1 -1
- package/dist/esm/main.mjs +1 -1
- package/dist/esm/messages/cache.mjs.map +1 -1
- package/dist/esm/messages/content.mjs.map +1 -1
- package/dist/esm/messages/core.mjs +1 -1
- package/dist/esm/messages/core.mjs.map +1 -1
- package/dist/esm/messages/format.mjs +120 -34
- package/dist/esm/messages/format.mjs.map +1 -1
- package/dist/esm/messages/ids.mjs.map +1 -1
- package/dist/esm/messages/prune.mjs +1 -1
- package/dist/esm/messages/prune.mjs.map +1 -1
- package/dist/esm/messages/tools.mjs.map +1 -1
- package/dist/esm/run.mjs +5 -2
- package/dist/esm/run.mjs.map +1 -1
- package/dist/esm/splitStream.mjs.map +1 -1
- package/dist/esm/stream.mjs +10 -1
- package/dist/esm/stream.mjs.map +1 -1
- package/dist/esm/tools/Calculator.mjs.map +1 -1
- package/dist/esm/tools/CodeExecutor.mjs.map +1 -1
- package/dist/esm/tools/ProgrammaticToolCalling.mjs +1 -1
- package/dist/esm/tools/ProgrammaticToolCalling.mjs.map +1 -1
- package/dist/esm/tools/ToolNode.mjs +1 -1
- package/dist/esm/tools/ToolNode.mjs.map +1 -1
- package/dist/esm/tools/ToolSearch.mjs.map +1 -1
- package/dist/esm/tools/handlers.mjs +1 -1
- package/dist/esm/tools/handlers.mjs.map +1 -1
- package/dist/esm/tools/schema.mjs.map +1 -1
- package/dist/esm/tools/search/content.mjs.map +1 -1
- package/dist/esm/tools/search/firecrawl.mjs.map +1 -1
- package/dist/esm/tools/search/format.mjs.map +1 -1
- package/dist/esm/tools/search/highlights.mjs.map +1 -1
- package/dist/esm/tools/search/rerankers.mjs.map +1 -1
- package/dist/esm/tools/search/schema.mjs.map +1 -1
- package/dist/esm/tools/search/search.mjs +1 -0
- package/dist/esm/tools/search/search.mjs.map +1 -1
- package/dist/esm/tools/search/serper-scraper.mjs.map +1 -1
- package/dist/esm/tools/search/tool.mjs.map +1 -1
- package/dist/esm/tools/search/utils.mjs.map +1 -1
- package/dist/esm/utils/events.mjs.map +1 -1
- package/dist/esm/utils/graph.mjs.map +1 -1
- package/dist/esm/utils/handlers.mjs.map +1 -1
- package/dist/esm/utils/llm.mjs.map +1 -1
- package/dist/esm/utils/misc.mjs.map +1 -1
- package/dist/esm/utils/run.mjs.map +1 -1
- package/dist/esm/utils/schema.mjs.map +1 -1
- package/dist/esm/utils/title.mjs.map +1 -1
- package/dist/esm/utils/tokens.mjs +33 -46
- package/dist/esm/utils/tokens.mjs.map +1 -1
- package/dist/types/llm/vertexai/index.d.ts +1 -1
- package/dist/types/types/graph.d.ts +2 -0
- package/dist/types/types/stream.d.ts +2 -0
- package/dist/types/utils/tokens.d.ts +6 -18
- package/package.json +7 -3
- package/src/llm/vertexai/index.ts +99 -6
- package/src/llm/vertexai/llm.spec.ts +114 -0
- package/src/messages/ensureThinkingBlock.test.ts +502 -27
- package/src/messages/format.ts +155 -44
- package/src/run.ts +6 -2
- package/src/scripts/bedrock-cache-debug.ts +15 -15
- package/src/scripts/code_exec_multi_session.ts +8 -13
- package/src/scripts/image.ts +2 -1
- package/src/scripts/multi-agent-parallel-start.ts +3 -4
- package/src/scripts/multi-agent-sequence.ts +3 -4
- package/src/scripts/single-agent-metadata-test.ts +3 -6
- package/src/scripts/test-tool-before-handoff-role-order.ts +2 -3
- package/src/scripts/test-tools-before-handoff.ts +2 -3
- package/src/scripts/thinking-vertexai.ts +168 -0
- package/src/scripts/tools.ts +1 -7
- package/src/specs/token-memoization.test.ts +35 -34
- package/src/specs/tokens.test.ts +64 -0
- package/src/stream.ts +12 -0
- package/src/types/graph.ts +2 -0
- package/src/types/stream.ts +2 -0
- package/src/utils/tokens.ts +43 -54
package/src/messages/format.ts
CHANGED
|
@@ -6,7 +6,6 @@ import {
|
|
|
6
6
|
BaseMessage,
|
|
7
7
|
HumanMessage,
|
|
8
8
|
SystemMessage,
|
|
9
|
-
getBufferString,
|
|
10
9
|
} from '@langchain/core/messages';
|
|
11
10
|
import type { MessageContentImageUrl } from '@langchain/core/messages';
|
|
12
11
|
import type { ToolCall } from '@langchain/core/messages/tool';
|
|
@@ -998,6 +997,113 @@ export function shiftIndexTokenCountMap(
|
|
|
998
997
|
return shiftedMap;
|
|
999
998
|
}
|
|
1000
999
|
|
|
1000
|
+
/** Block types that contain binary image data and must be preserved structurally. */
|
|
1001
|
+
const IMAGE_BLOCK_TYPES = new Set(['image_url', 'image']);
|
|
1002
|
+
|
|
1003
|
+
/** Checks whether a BaseMessage is a tool-role message. */
|
|
1004
|
+
const isToolMessage = (m: BaseMessage): boolean =>
|
|
1005
|
+
m instanceof ToolMessage || ('role' in m && (m as any).role === 'tool');
|
|
1006
|
+
|
|
1007
|
+
/** Flushes accumulated text chunks into `parts` as a single text block. */
|
|
1008
|
+
function flushTextChunks(
|
|
1009
|
+
textChunks: string[],
|
|
1010
|
+
parts: MessageContentComplex[]
|
|
1011
|
+
): void {
|
|
1012
|
+
if (textChunks.length === 0) {
|
|
1013
|
+
return;
|
|
1014
|
+
}
|
|
1015
|
+
parts.push({
|
|
1016
|
+
type: ContentTypes.TEXT,
|
|
1017
|
+
text: textChunks.join('\n'),
|
|
1018
|
+
} as MessageContentComplex);
|
|
1019
|
+
textChunks.length = 0;
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
/**
|
|
1023
|
+
* Appends a single message's content to the running `textChunks` / `parts`
|
|
1024
|
+
* accumulators. Image blocks are shallow-copied into `parts` as-is so that
|
|
1025
|
+
* binary data (base64 images) never becomes text tokens. All other block
|
|
1026
|
+
* types are serialized to text — unrecognized types are JSON-serialized
|
|
1027
|
+
* rather than silently dropped.
|
|
1028
|
+
*
|
|
1029
|
+
* When `content` is an array containing tool_use blocks, `tool_calls` is NOT
|
|
1030
|
+
* additionally serialized (avoiding double output). `tool_calls` is used as
|
|
1031
|
+
* a fallback when `content` is a plain string or an array with no tool_use.
|
|
1032
|
+
*/
|
|
1033
|
+
function appendMessageContent(
|
|
1034
|
+
msg: BaseMessage,
|
|
1035
|
+
role: string,
|
|
1036
|
+
textChunks: string[],
|
|
1037
|
+
parts: MessageContentComplex[]
|
|
1038
|
+
): void {
|
|
1039
|
+
const { content } = msg;
|
|
1040
|
+
|
|
1041
|
+
if (typeof content === 'string') {
|
|
1042
|
+
if (content) {
|
|
1043
|
+
textChunks.push(`${role}: ${content}`);
|
|
1044
|
+
}
|
|
1045
|
+
appendToolCalls(msg, role, textChunks);
|
|
1046
|
+
return;
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
if (!Array.isArray(content)) {
|
|
1050
|
+
appendToolCalls(msg, role, textChunks);
|
|
1051
|
+
return;
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
let hasToolUseBlock = false;
|
|
1055
|
+
|
|
1056
|
+
for (const block of content as ExtendedMessageContent[]) {
|
|
1057
|
+
if (IMAGE_BLOCK_TYPES.has(block.type ?? '')) {
|
|
1058
|
+
flushTextChunks(textChunks, parts);
|
|
1059
|
+
parts.push({ ...block } as MessageContentComplex);
|
|
1060
|
+
continue;
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
if (block.type === 'tool_use') {
|
|
1064
|
+
hasToolUseBlock = true;
|
|
1065
|
+
textChunks.push(
|
|
1066
|
+
`${role}: [tool_use] ${String(block.name ?? '')} ${JSON.stringify(block.input ?? {})}`
|
|
1067
|
+
);
|
|
1068
|
+
continue;
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
const text = block.text ?? block.input;
|
|
1072
|
+
if (typeof text === 'string' && text) {
|
|
1073
|
+
textChunks.push(`${role}: ${text}`);
|
|
1074
|
+
continue;
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
// Fallback: serialize unrecognized block types to preserve context
|
|
1078
|
+
if (block.type != null && block.type !== '') {
|
|
1079
|
+
textChunks.push(`${role}: [${block.type}] ${JSON.stringify(block)}`);
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
// If content array had no tool_use blocks, fall back to tool_calls metadata
|
|
1084
|
+
// (handles edge case: empty content array with tool_calls populated)
|
|
1085
|
+
if (!hasToolUseBlock) {
|
|
1086
|
+
appendToolCalls(msg, role, textChunks);
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
function appendToolCalls(
|
|
1091
|
+
msg: BaseMessage,
|
|
1092
|
+
role: string,
|
|
1093
|
+
textChunks: string[]
|
|
1094
|
+
): void {
|
|
1095
|
+
if (role !== 'AI') {
|
|
1096
|
+
return;
|
|
1097
|
+
}
|
|
1098
|
+
const aiMsg = msg as AIMessage;
|
|
1099
|
+
if (!aiMsg.tool_calls || aiMsg.tool_calls.length === 0) {
|
|
1100
|
+
return;
|
|
1101
|
+
}
|
|
1102
|
+
for (const tc of aiMsg.tool_calls) {
|
|
1103
|
+
textChunks.push(`AI: [tool_call] ${tc.name}(${JSON.stringify(tc.args)})`);
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1001
1107
|
/**
|
|
1002
1108
|
* Ensures compatibility when switching from a non-thinking agent to a thinking-enabled agent.
|
|
1003
1109
|
* Converts AI messages with tool calls (that lack thinking/reasoning blocks) into buffer strings,
|
|
@@ -1021,19 +1127,27 @@ export function ensureThinkingBlockInMessages(
|
|
|
1021
1127
|
return messages;
|
|
1022
1128
|
}
|
|
1023
1129
|
|
|
1024
|
-
//
|
|
1025
|
-
//
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1130
|
+
// Find the last HumanMessage. Only the trailing sequence after it needs
|
|
1131
|
+
// validation — earlier messages are history already accepted by the provider.
|
|
1132
|
+
let lastHumanIndex = -1;
|
|
1133
|
+
for (let k = messages.length - 1; k >= 0; k--) {
|
|
1134
|
+
const m = messages[k];
|
|
1135
|
+
if (
|
|
1136
|
+
m instanceof HumanMessage ||
|
|
1137
|
+
('role' in m && (m as any).role === 'user')
|
|
1138
|
+
) {
|
|
1139
|
+
lastHumanIndex = k;
|
|
1140
|
+
break;
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
if (lastHumanIndex === messages.length - 1) {
|
|
1032
1145
|
return messages;
|
|
1033
1146
|
}
|
|
1034
1147
|
|
|
1035
|
-
const result: BaseMessage[] =
|
|
1036
|
-
|
|
1148
|
+
const result: BaseMessage[] =
|
|
1149
|
+
lastHumanIndex >= 0 ? messages.slice(0, lastHumanIndex + 1) : [];
|
|
1150
|
+
let i = lastHumanIndex + 1;
|
|
1037
1151
|
|
|
1038
1152
|
while (i < messages.length) {
|
|
1039
1153
|
const msg = messages[i];
|
|
@@ -1059,21 +1173,24 @@ export function ensureThinkingBlockInMessages(
|
|
|
1059
1173
|
let hasThinkingBlock = false;
|
|
1060
1174
|
|
|
1061
1175
|
if (contentIsArray && aiMsg.content.length > 0) {
|
|
1062
|
-
const
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1176
|
+
for (const c of aiMsg.content as ExtendedMessageContent[]) {
|
|
1177
|
+
if (typeof c !== 'object') {
|
|
1178
|
+
continue;
|
|
1179
|
+
}
|
|
1180
|
+
if (c.type === 'tool_use') {
|
|
1181
|
+
hasToolUse = true;
|
|
1182
|
+
} else if (
|
|
1183
|
+
c.type === ContentTypes.THINKING ||
|
|
1184
|
+
c.type === ContentTypes.REASONING_CONTENT ||
|
|
1185
|
+
c.type === ContentTypes.REASONING ||
|
|
1186
|
+
c.type === 'redacted_thinking'
|
|
1187
|
+
) {
|
|
1188
|
+
hasThinkingBlock = true;
|
|
1189
|
+
}
|
|
1190
|
+
if (hasToolUse && hasThinkingBlock) {
|
|
1191
|
+
break;
|
|
1192
|
+
}
|
|
1193
|
+
}
|
|
1077
1194
|
}
|
|
1078
1195
|
|
|
1079
1196
|
// Bedrock also stores reasoning in additional_kwargs (may not be in content array)
|
|
@@ -1100,28 +1217,22 @@ export function ensureThinkingBlockInMessages(
|
|
|
1100
1217
|
continue;
|
|
1101
1218
|
}
|
|
1102
1219
|
|
|
1103
|
-
//
|
|
1104
|
-
|
|
1105
|
-
|
|
1220
|
+
// Build structured content in a single pass over the AI + following
|
|
1221
|
+
// ToolMessages — preserves image blocks as-is to avoid serializing
|
|
1222
|
+
// binary data as text (which caused 174× token amplification).
|
|
1223
|
+
const parts: MessageContentComplex[] = [];
|
|
1224
|
+
const textChunks: string[] = ['[Previous agent context]'];
|
|
1225
|
+
|
|
1226
|
+
appendMessageContent(msg, 'AI', textChunks, parts);
|
|
1106
1227
|
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
while (j < messages.length && isToolMsg(messages[j])) {
|
|
1111
|
-
toolSequence.push(messages[j]);
|
|
1228
|
+
let j = i + 1;
|
|
1229
|
+
while (j < messages.length && isToolMessage(messages[j])) {
|
|
1230
|
+
appendMessageContent(messages[j], 'Tool', textChunks, parts);
|
|
1112
1231
|
j++;
|
|
1113
1232
|
}
|
|
1114
1233
|
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
const bufferString = getBufferString(toolSequence);
|
|
1118
|
-
result.push(
|
|
1119
|
-
new HumanMessage({
|
|
1120
|
-
content: `[Previous agent context]\n${bufferString}`,
|
|
1121
|
-
})
|
|
1122
|
-
);
|
|
1123
|
-
|
|
1124
|
-
// Skip the messages we've processed
|
|
1234
|
+
flushTextChunks(textChunks, parts);
|
|
1235
|
+
result.push(new HumanMessage({ content: parts }));
|
|
1125
1236
|
i = j;
|
|
1126
1237
|
} else {
|
|
1127
1238
|
// Keep the message as is
|
package/src/run.ts
CHANGED
|
@@ -16,9 +16,9 @@ import {
|
|
|
16
16
|
createCompletionTitleRunnable,
|
|
17
17
|
createTitleRunnable,
|
|
18
18
|
} from '@/utils/title';
|
|
19
|
+
import { createTokenCounter, encodingForModel } from '@/utils/tokens';
|
|
19
20
|
import { GraphEvents, Callback, TitleMethod } from '@/common';
|
|
20
21
|
import { MultiAgentGraph } from '@/graphs/MultiAgentGraph';
|
|
21
|
-
import { createTokenCounter } from '@/utils/tokens';
|
|
22
22
|
import { StandardGraph } from '@/graphs/Graph';
|
|
23
23
|
import { HandlerRegistry } from '@/events';
|
|
24
24
|
import { isOpenAILike } from '@/utils/llm';
|
|
@@ -166,7 +166,11 @@ export class Run<_T extends t.BaseGraphState> {
|
|
|
166
166
|
): Promise<Run<T>> {
|
|
167
167
|
/** Create tokenCounter if indexTokenCountMap is provided but tokenCounter is not */
|
|
168
168
|
if (config.indexTokenCountMap && !config.tokenCounter) {
|
|
169
|
-
|
|
169
|
+
const gc = config.graphConfig;
|
|
170
|
+
const clientOpts =
|
|
171
|
+
'agents' in gc ? gc.agents[0]?.clientOptions : gc.clientOptions;
|
|
172
|
+
const model = (clientOpts as { model?: string } | undefined)?.model ?? '';
|
|
173
|
+
config.tokenCounter = await createTokenCounter(encodingForModel(model));
|
|
170
174
|
}
|
|
171
175
|
return new Run<T>(config);
|
|
172
176
|
}
|
|
@@ -9,13 +9,13 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import { config } from 'dotenv';
|
|
11
11
|
config();
|
|
12
|
-
import { HumanMessage } from '@langchain/core/messages';
|
|
13
|
-
import type { AIMessageChunk } from '@langchain/core/messages';
|
|
14
12
|
import { concat } from '@langchain/core/utils/stream';
|
|
13
|
+
import { HumanMessage } from '@langchain/core/messages';
|
|
15
14
|
import {
|
|
16
|
-
ConverseStreamCommand,
|
|
17
15
|
BedrockRuntimeClient,
|
|
16
|
+
ConverseStreamCommand,
|
|
18
17
|
} from '@aws-sdk/client-bedrock-runtime';
|
|
18
|
+
import type { AIMessageChunk } from '@langchain/core/messages';
|
|
19
19
|
import { CustomChatBedrockConverse } from '@/llm/bedrock';
|
|
20
20
|
|
|
21
21
|
const region = process.env.BEDROCK_AWS_REGION ?? 'us-east-1';
|
|
@@ -62,12 +62,12 @@ async function rawSdkCall(): Promise<void> {
|
|
|
62
62
|
console.log('\nSpecific cache fields:');
|
|
63
63
|
console.log(
|
|
64
64
|
' cacheReadInputTokens:',
|
|
65
|
-
(event.metadata.usage as Record<string, unknown>)
|
|
65
|
+
(event.metadata.usage as unknown as Record<string, unknown>)
|
|
66
66
|
?.cacheReadInputTokens
|
|
67
67
|
);
|
|
68
68
|
console.log(
|
|
69
69
|
' cacheWriteInputTokens:',
|
|
70
|
-
(event.metadata.usage as Record<string, unknown>)
|
|
70
|
+
(event.metadata.usage as unknown as Record<string, unknown>)
|
|
71
71
|
?.cacheWriteInputTokens
|
|
72
72
|
);
|
|
73
73
|
}
|
|
@@ -98,12 +98,12 @@ async function rawSdkCall(): Promise<void> {
|
|
|
98
98
|
console.log('\nSpecific cache fields:');
|
|
99
99
|
console.log(
|
|
100
100
|
' cacheReadInputTokens:',
|
|
101
|
-
(event.metadata.usage as Record<string, unknown>)
|
|
101
|
+
(event.metadata.usage as unknown as Record<string, unknown>)
|
|
102
102
|
?.cacheReadInputTokens
|
|
103
103
|
);
|
|
104
104
|
console.log(
|
|
105
105
|
' cacheWriteInputTokens:',
|
|
106
|
-
(event.metadata.usage as Record<string, unknown>)
|
|
106
|
+
(event.metadata.usage as unknown as Record<string, unknown>)
|
|
107
107
|
?.cacheWriteInputTokens
|
|
108
108
|
);
|
|
109
109
|
}
|
|
@@ -177,17 +177,17 @@ async function wrapperStreamCallWithCachePoint(): Promise<void> {
|
|
|
177
177
|
const chunk = handleConverseStreamMetadata(event.metadata, {
|
|
178
178
|
streamUsage: true,
|
|
179
179
|
});
|
|
180
|
+
const msg = chunk.message as AIMessageChunk;
|
|
180
181
|
console.log(
|
|
181
182
|
'handleConverseStreamMetadata output usage_metadata:',
|
|
182
|
-
JSON.stringify(
|
|
183
|
+
JSON.stringify(msg.usage_metadata)
|
|
183
184
|
);
|
|
184
185
|
|
|
185
|
-
const hasDetails =
|
|
186
|
-
chunk.message.usage_metadata?.input_token_details != null;
|
|
186
|
+
const hasDetails = msg.usage_metadata?.input_token_details != null;
|
|
187
187
|
console.log(
|
|
188
188
|
`Has input_token_details: ${hasDetails}`,
|
|
189
189
|
hasDetails
|
|
190
|
-
? JSON.stringify(
|
|
190
|
+
? JSON.stringify(msg.usage_metadata!.input_token_details)
|
|
191
191
|
: '(MISSING - BUG!)'
|
|
192
192
|
);
|
|
193
193
|
}
|
|
@@ -216,17 +216,17 @@ async function wrapperStreamCallWithCachePoint(): Promise<void> {
|
|
|
216
216
|
const chunk = handleConverseStreamMetadata(event.metadata, {
|
|
217
217
|
streamUsage: true,
|
|
218
218
|
});
|
|
219
|
+
const msg = chunk.message as AIMessageChunk;
|
|
219
220
|
console.log(
|
|
220
221
|
'handleConverseStreamMetadata output usage_metadata:',
|
|
221
|
-
JSON.stringify(
|
|
222
|
+
JSON.stringify(msg.usage_metadata)
|
|
222
223
|
);
|
|
223
224
|
|
|
224
|
-
const hasDetails =
|
|
225
|
-
chunk.message.usage_metadata?.input_token_details != null;
|
|
225
|
+
const hasDetails = msg.usage_metadata?.input_token_details != null;
|
|
226
226
|
console.log(
|
|
227
227
|
`Has input_token_details: ${hasDetails}`,
|
|
228
228
|
hasDetails
|
|
229
|
-
? JSON.stringify(
|
|
229
|
+
? JSON.stringify(msg.usage_metadata!.input_token_details)
|
|
230
230
|
: '(MISSING - BUG!)'
|
|
231
231
|
);
|
|
232
232
|
}
|
|
@@ -44,8 +44,8 @@ function printSessionContext(run: Run<t.IState>, label: string): void {
|
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
console.log(` Latest session_id: ${session.session_id}`);
|
|
47
|
-
console.log(` Files tracked: ${session.files
|
|
48
|
-
for (const file of session.files) {
|
|
47
|
+
console.log(` Files tracked: ${session.files?.length ?? 0}`);
|
|
48
|
+
for (const file of session.files ?? []) {
|
|
49
49
|
console.log(` - ${file.name} (session: ${file.session_id})`);
|
|
50
50
|
}
|
|
51
51
|
}
|
|
@@ -199,25 +199,20 @@ Tell me what version it shows.
|
|
|
199
199
|
| undefined;
|
|
200
200
|
|
|
201
201
|
if (finalSession) {
|
|
202
|
-
const
|
|
203
|
-
|
|
204
|
-
);
|
|
205
|
-
console.log(`\nTotal files tracked: ${finalSession.files.length}`);
|
|
202
|
+
const files = finalSession.files ?? [];
|
|
203
|
+
const uniqueSessionIds = new Set(files.map((f) => f.session_id));
|
|
204
|
+
console.log(`\nTotal files tracked: ${files.length}`);
|
|
206
205
|
console.log(`Unique session_ids: ${uniqueSessionIds.size}`);
|
|
207
206
|
console.log('\nFiles:');
|
|
208
|
-
for (const file of
|
|
207
|
+
for (const file of files) {
|
|
209
208
|
console.log(
|
|
210
209
|
` - ${file.name} (session: ${file.session_id?.slice(0, 20)}...)`
|
|
211
210
|
);
|
|
212
211
|
}
|
|
213
212
|
|
|
214
213
|
// Verify expectations
|
|
215
|
-
const fileACount =
|
|
216
|
-
|
|
217
|
-
).length;
|
|
218
|
-
const fileBCount = finalSession.files.filter(
|
|
219
|
-
(f) => f.name === 'file_b.txt'
|
|
220
|
-
).length;
|
|
214
|
+
const fileACount = files.filter((f) => f.name === 'file_a.txt').length;
|
|
215
|
+
const fileBCount = files.filter((f) => f.name === 'file_b.txt').length;
|
|
221
216
|
|
|
222
217
|
console.log('\n✓ Checks:');
|
|
223
218
|
console.log(` file_a.txt count: ${fileACount} (expected: 1, latest wins)`);
|
package/src/scripts/image.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// src/scripts/
|
|
1
|
+
// src/scripts/image.ts
|
|
2
2
|
import { config } from 'dotenv';
|
|
3
3
|
config();
|
|
4
4
|
import { HumanMessage, AIMessage, BaseMessage } from '@langchain/core/messages';
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
ModelEndHandler,
|
|
11
11
|
createMetadataAggregator,
|
|
12
12
|
} from '@/events';
|
|
13
|
+
// @ts-expect-error — example module not in current codebase
|
|
13
14
|
import { fetchRandomImageTool, fetchRandomImageURL } from '@/tools/example';
|
|
14
15
|
import { getLLMConfig } from '@/utils/llmConfig';
|
|
15
16
|
import { getArgs } from '@/scripts/args';
|
|
@@ -25,8 +25,7 @@ async function testParallelFromStart() {
|
|
|
25
25
|
console.log('Testing Parallel From Start Multi-Agent System...\n');
|
|
26
26
|
|
|
27
27
|
// Set up content aggregator
|
|
28
|
-
const { contentParts, aggregateContent,
|
|
29
|
-
createContentAggregator();
|
|
28
|
+
const { contentParts, aggregateContent, stepMap } = createContentAggregator();
|
|
30
29
|
|
|
31
30
|
// Define two agents - both have NO incoming edges, so they run in parallel from the start
|
|
32
31
|
const agents: t.AgentInputs[] = [
|
|
@@ -253,8 +252,8 @@ async function testParallelFromStart() {
|
|
|
253
252
|
console.log('Final content parts:', contentParts.length, 'parts');
|
|
254
253
|
console.log('\n=== Content Parts (clean, no metadata) ===');
|
|
255
254
|
console.dir(contentParts, { depth: null });
|
|
256
|
-
console.log('\n===
|
|
257
|
-
console.dir(Object.fromEntries(
|
|
255
|
+
console.log('\n=== Step Map (separate from content) ===');
|
|
256
|
+
console.dir(Object.fromEntries(stepMap), { depth: null });
|
|
258
257
|
|
|
259
258
|
await sleep(3000);
|
|
260
259
|
} catch (error) {
|
|
@@ -22,8 +22,7 @@ async function testSequentialMultiAgent() {
|
|
|
22
22
|
console.log('Testing Sequential Multi-Agent System (A → B → C)...\n');
|
|
23
23
|
|
|
24
24
|
// Set up content aggregator
|
|
25
|
-
const { contentParts, aggregateContent,
|
|
26
|
-
createContentAggregator();
|
|
25
|
+
const { contentParts, aggregateContent, stepMap } = createContentAggregator();
|
|
27
26
|
|
|
28
27
|
// Define three simple agents
|
|
29
28
|
const agents: t.AgentInputs[] = [
|
|
@@ -198,8 +197,8 @@ async function testSequentialMultiAgent() {
|
|
|
198
197
|
console.log(`Total content parts: ${contentParts.length}`);
|
|
199
198
|
console.log('\n=== Content Parts (clean, no metadata) ===');
|
|
200
199
|
console.dir(contentParts, { depth: null });
|
|
201
|
-
console.log('\n===
|
|
202
|
-
console.dir(Object.fromEntries(
|
|
200
|
+
console.log('\n=== Step Map (separate from content) ===');
|
|
201
|
+
console.dir(Object.fromEntries(stepMap), { depth: null });
|
|
203
202
|
|
|
204
203
|
// Display the sequential responses
|
|
205
204
|
const aiMessages = conversationHistory.filter(
|
|
@@ -20,8 +20,7 @@ async function testSingleAgent() {
|
|
|
20
20
|
console.log('Testing Single Agent with Metadata Logging...\n');
|
|
21
21
|
|
|
22
22
|
// Set up content aggregator
|
|
23
|
-
const { contentParts, aggregateContent,
|
|
24
|
-
createContentAggregator();
|
|
23
|
+
const { contentParts, aggregateContent, stepMap } = createContentAggregator();
|
|
25
24
|
|
|
26
25
|
const startTime = Date.now();
|
|
27
26
|
|
|
@@ -183,10 +182,8 @@ async function testSingleAgent() {
|
|
|
183
182
|
console.log('Final content parts:', contentParts.length, 'parts');
|
|
184
183
|
console.log('\n=== Content Parts (clean, no metadata) ===');
|
|
185
184
|
console.dir(contentParts, { depth: null });
|
|
186
|
-
console.log(
|
|
187
|
-
|
|
188
|
-
);
|
|
189
|
-
console.dir(Object.fromEntries(contentMetadataMap), { depth: null });
|
|
185
|
+
console.log('\n=== Step Map (should be empty for single-agent) ===');
|
|
186
|
+
console.dir(Object.fromEntries(stepMap), { depth: null });
|
|
190
187
|
console.log('====================================\n');
|
|
191
188
|
|
|
192
189
|
await sleep(3000);
|
|
@@ -42,10 +42,9 @@ async function testToolBeforeHandoffRoleOrder(): Promise<void> {
|
|
|
42
42
|
let handoffOccurred = false;
|
|
43
43
|
|
|
44
44
|
const customHandlers = {
|
|
45
|
-
[GraphEvents.TOOL_END]: new ToolEndHandler(
|
|
45
|
+
[GraphEvents.TOOL_END]: new ToolEndHandler(async () => {
|
|
46
46
|
toolCallCount++;
|
|
47
|
-
console.log(`\n Tool completed
|
|
48
|
-
return true;
|
|
47
|
+
console.log(`\n Tool completed (total: ${toolCallCount})`);
|
|
49
48
|
}),
|
|
50
49
|
[GraphEvents.CHAT_MODEL_END]: new ModelEndHandler(),
|
|
51
50
|
[GraphEvents.CHAT_MODEL_STREAM]: new ChatModelStreamHandler(),
|
|
@@ -28,9 +28,8 @@ async function testToolsBeforeHandoff() {
|
|
|
28
28
|
|
|
29
29
|
// Create custom handlers
|
|
30
30
|
const customHandlers = {
|
|
31
|
-
[GraphEvents.TOOL_END]: new ToolEndHandler(
|
|
32
|
-
console.log(
|
|
33
|
-
return true;
|
|
31
|
+
[GraphEvents.TOOL_END]: new ToolEndHandler(async () => {
|
|
32
|
+
console.log('\n✅ Tool completed');
|
|
34
33
|
}),
|
|
35
34
|
[GraphEvents.CHAT_MODEL_END]: new ModelEndHandler(),
|
|
36
35
|
[GraphEvents.CHAT_MODEL_STREAM]: new ChatModelStreamHandler(),
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
// src/scripts/thinking-vertexai.ts
|
|
2
|
+
import { config } from 'dotenv';
|
|
3
|
+
config();
|
|
4
|
+
import { HumanMessage, BaseMessage } from '@langchain/core/messages';
|
|
5
|
+
import type { UsageMetadata } from '@langchain/core/messages';
|
|
6
|
+
import * as t from '@/types';
|
|
7
|
+
import { ChatModelStreamHandler, createContentAggregator } from '@/stream';
|
|
8
|
+
import { ToolEndHandler, ModelEndHandler } from '@/events';
|
|
9
|
+
import { GraphEvents, Providers } from '@/common';
|
|
10
|
+
import { getLLMConfig } from '@/utils/llmConfig';
|
|
11
|
+
import { getArgs } from '@/scripts/args';
|
|
12
|
+
import { Run } from '@/run';
|
|
13
|
+
|
|
14
|
+
const conversationHistory: BaseMessage[] = [];
|
|
15
|
+
let _contentParts: t.MessageContentComplex[] = [];
|
|
16
|
+
const collectedUsage: UsageMetadata[] = [];
|
|
17
|
+
|
|
18
|
+
async function testVertexAIThinking(): Promise<void> {
|
|
19
|
+
const { userName } = await getArgs();
|
|
20
|
+
const instructions = `You are a helpful AI assistant for ${userName}. When answering questions, be thorough in your reasoning.`;
|
|
21
|
+
const { contentParts, aggregateContent } = createContentAggregator();
|
|
22
|
+
_contentParts = contentParts as t.MessageContentComplex[];
|
|
23
|
+
|
|
24
|
+
// Set up event handlers
|
|
25
|
+
const customHandlers = {
|
|
26
|
+
[GraphEvents.TOOL_END]: new ToolEndHandler(),
|
|
27
|
+
[GraphEvents.CHAT_MODEL_END]: new ModelEndHandler(collectedUsage),
|
|
28
|
+
[GraphEvents.CHAT_MODEL_STREAM]: new ChatModelStreamHandler(),
|
|
29
|
+
[GraphEvents.ON_RUN_STEP_COMPLETED]: {
|
|
30
|
+
handle: (
|
|
31
|
+
event: GraphEvents.ON_RUN_STEP_COMPLETED,
|
|
32
|
+
data: t.StreamEventData
|
|
33
|
+
): void => {
|
|
34
|
+
console.log('====== ON_RUN_STEP_COMPLETED ======');
|
|
35
|
+
aggregateContent({
|
|
36
|
+
event,
|
|
37
|
+
data: data as unknown as { result: t.ToolEndEvent },
|
|
38
|
+
});
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
[GraphEvents.ON_RUN_STEP]: {
|
|
42
|
+
handle: (event: GraphEvents.ON_RUN_STEP, data: t.RunStep) => {
|
|
43
|
+
aggregateContent({ event, data });
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
[GraphEvents.ON_RUN_STEP_DELTA]: {
|
|
47
|
+
handle: (
|
|
48
|
+
event: GraphEvents.ON_RUN_STEP_DELTA,
|
|
49
|
+
data: t.RunStepDeltaEvent
|
|
50
|
+
) => {
|
|
51
|
+
aggregateContent({ event, data });
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
[GraphEvents.ON_MESSAGE_DELTA]: {
|
|
55
|
+
handle: (
|
|
56
|
+
event: GraphEvents.ON_MESSAGE_DELTA,
|
|
57
|
+
data: t.MessageDeltaEvent
|
|
58
|
+
) => {
|
|
59
|
+
aggregateContent({ event, data });
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
[GraphEvents.ON_REASONING_DELTA]: {
|
|
63
|
+
handle: (
|
|
64
|
+
event: GraphEvents.ON_REASONING_DELTA,
|
|
65
|
+
data: t.ReasoningDeltaEvent
|
|
66
|
+
) => {
|
|
67
|
+
console.log(
|
|
68
|
+
'[ON_REASONING_DELTA]',
|
|
69
|
+
JSON.stringify(data.delta.content?.[0]).slice(0, 100)
|
|
70
|
+
);
|
|
71
|
+
aggregateContent({ event, data });
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const baseLlmConfig = getLLMConfig(Providers.VERTEXAI);
|
|
77
|
+
|
|
78
|
+
const llmConfig = {
|
|
79
|
+
...baseLlmConfig,
|
|
80
|
+
model: 'gemini-3-flash-preview',
|
|
81
|
+
location: 'global',
|
|
82
|
+
streaming: true,
|
|
83
|
+
streamUsage: true,
|
|
84
|
+
thinkingConfig: {
|
|
85
|
+
thinkingLevel: 'HIGH',
|
|
86
|
+
includeThoughts: true,
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const run = await Run.create<t.IState>({
|
|
91
|
+
runId: 'test-vertexai-thinking-id',
|
|
92
|
+
graphConfig: {
|
|
93
|
+
instructions,
|
|
94
|
+
type: 'standard',
|
|
95
|
+
llmConfig,
|
|
96
|
+
},
|
|
97
|
+
returnContent: true,
|
|
98
|
+
skipCleanup: true,
|
|
99
|
+
customHandlers: customHandlers as t.RunConfig['customHandlers'],
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
const streamConfig = {
|
|
103
|
+
configurable: {
|
|
104
|
+
thread_id: 'vertexai-thinking-test-thread',
|
|
105
|
+
},
|
|
106
|
+
streamMode: 'values',
|
|
107
|
+
version: 'v2' as const,
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
// Test 1: Regular thinking mode
|
|
111
|
+
console.log('\n\nTest 1: Vertex AI thinking mode with thinkingLevel=HIGH');
|
|
112
|
+
const userMessage1 =
|
|
113
|
+
'How many r\'s are in the word "strawberry"? Think carefully.';
|
|
114
|
+
conversationHistory.push(new HumanMessage(userMessage1));
|
|
115
|
+
|
|
116
|
+
console.log('Running first query with Vertex AI thinking enabled...');
|
|
117
|
+
const firstInputs = { messages: [...conversationHistory] };
|
|
118
|
+
await run.processStream(firstInputs, streamConfig);
|
|
119
|
+
|
|
120
|
+
// Extract and display results
|
|
121
|
+
const finalMessages = run.getRunMessages();
|
|
122
|
+
console.log('\n\nFinal messages after Test 1:');
|
|
123
|
+
console.dir(finalMessages, { depth: null });
|
|
124
|
+
|
|
125
|
+
// Test 2: Multi-turn conversation
|
|
126
|
+
console.log(
|
|
127
|
+
'\n\nTest 2: Multi-turn conversation with Vertex AI thinking enabled'
|
|
128
|
+
);
|
|
129
|
+
const userMessage2 =
|
|
130
|
+
'Now count the number of letters in "Mississippi". Explain step by step.';
|
|
131
|
+
conversationHistory.push(new HumanMessage(userMessage2));
|
|
132
|
+
|
|
133
|
+
console.log('Running second query with Vertex AI thinking enabled...');
|
|
134
|
+
const secondInputs = { messages: [...conversationHistory] };
|
|
135
|
+
await run.processStream(secondInputs, streamConfig);
|
|
136
|
+
|
|
137
|
+
const finalMessages2 = run.getRunMessages();
|
|
138
|
+
console.log('\n\nVertex AI thinking feature test completed!');
|
|
139
|
+
console.dir(finalMessages2, { depth: null });
|
|
140
|
+
|
|
141
|
+
console.log('\n\nContent parts:');
|
|
142
|
+
console.dir(_contentParts, { depth: null });
|
|
143
|
+
|
|
144
|
+
console.log('\n\nCollected usage:');
|
|
145
|
+
console.dir(collectedUsage, { depth: null });
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
process.on('unhandledRejection', (reason, promise) => {
|
|
149
|
+
console.error('Unhandled Rejection at:', promise, 'reason:', reason);
|
|
150
|
+
console.log('Conversation history:');
|
|
151
|
+
console.dir(conversationHistory, { depth: null });
|
|
152
|
+
console.log('Content parts:');
|
|
153
|
+
console.dir(_contentParts, { depth: null });
|
|
154
|
+
process.exit(1);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
process.on('uncaughtException', (err) => {
|
|
158
|
+
console.error('Uncaught Exception:', err);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
testVertexAIThinking().catch((err) => {
|
|
162
|
+
console.error(err);
|
|
163
|
+
console.log('Conversation history:');
|
|
164
|
+
console.dir(conversationHistory, { depth: null });
|
|
165
|
+
console.log('Content parts:');
|
|
166
|
+
console.dir(_contentParts, { depth: null });
|
|
167
|
+
process.exit(1);
|
|
168
|
+
});
|