@librechat/agents 3.7.17 → 3.7.19
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 +5 -2
- package/dist/cjs/agents/AgentContext.cjs.map +1 -1
- package/dist/cjs/graphs/Graph.cjs +2 -1
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/langfuse.cjs +6 -2
- package/dist/cjs/langfuse.cjs.map +1 -1
- package/dist/cjs/llm/bedrock/utils/message_inputs.cjs +67 -20
- package/dist/cjs/llm/bedrock/utils/message_inputs.cjs.map +1 -1
- package/dist/cjs/llm/invoke.cjs +17 -2
- package/dist/cjs/llm/invoke.cjs.map +1 -1
- package/dist/cjs/llm/prepareProviderRequest.cjs +102 -1
- package/dist/cjs/llm/prepareProviderRequest.cjs.map +1 -1
- package/dist/cjs/tools/search/rerankers.cjs +8 -3
- package/dist/cjs/tools/search/rerankers.cjs.map +1 -1
- package/dist/cjs/tools/search/tool.cjs +2 -1
- package/dist/cjs/tools/search/tool.cjs.map +1 -1
- package/dist/esm/agents/AgentContext.mjs +5 -2
- package/dist/esm/agents/AgentContext.mjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +2 -1
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/langfuse.mjs +6 -2
- package/dist/esm/langfuse.mjs.map +1 -1
- package/dist/esm/llm/bedrock/utils/message_inputs.mjs +67 -20
- package/dist/esm/llm/bedrock/utils/message_inputs.mjs.map +1 -1
- package/dist/esm/llm/invoke.mjs +17 -2
- package/dist/esm/llm/invoke.mjs.map +1 -1
- package/dist/esm/llm/prepareProviderRequest.mjs +104 -3
- package/dist/esm/llm/prepareProviderRequest.mjs.map +1 -1
- package/dist/esm/tools/search/rerankers.mjs +8 -3
- package/dist/esm/tools/search/rerankers.mjs.map +1 -1
- package/dist/esm/tools/search/tool.mjs +2 -1
- package/dist/esm/tools/search/tool.mjs.map +1 -1
- package/dist/types/agents/AgentContext.d.ts +4 -1
- package/dist/types/langfuse.d.ts +5 -1
- package/dist/types/llm/prepareProviderRequest.d.ts +1 -1
- package/dist/types/tools/search/rerankers.d.ts +4 -1
- package/dist/types/tools/search/types.d.ts +1 -0
- package/dist/types/types/graph.d.ts +2 -0
- package/package.json +1 -1
- package/src/agents/AgentContext.ts +7 -0
- package/src/graphs/Graph.ts +2 -1
- package/src/langfuse.ts +12 -0
- package/src/llm/bedrock/utils/message_inputs.ts +86 -8
- package/src/llm/invoke.ts +23 -2
- package/src/llm/prepareProviderRequest.ts +257 -6
- package/src/tools/search/rerankers.ts +14 -1
- package/src/tools/search/tool.ts +3 -1
- package/src/tools/search/types.ts +1 -0
- package/src/types/graph.ts +2 -0
package/src/graphs/Graph.ts
CHANGED
|
@@ -151,6 +151,7 @@ import { shouldTraceToolNodeForLangfuse } from '@/langfuseToolOutputTracing';
|
|
|
151
151
|
import { createLocalCodingToolBundle } from '@/tools/local/LocalCodingTools';
|
|
152
152
|
import { SUBAGENT_REPLAY_CONTROLLER } from '@/tools/subagent/SubagentReplay';
|
|
153
153
|
import { applyGraphRuntimeConfig } from '@/graphs/applyGraphRuntimeConfig';
|
|
154
|
+
import { isFadingTier, isInformativeFadingTier } from '@/messages/fading';
|
|
154
155
|
import { createContextPressureMeter } from '@/llm/contextPressureMeter';
|
|
155
156
|
import { safeDispatchCustomEvent, emitAgentLog } from '@/utils/events';
|
|
156
157
|
import { prepareProviderRequest } from '@/llm/prepareProviderRequest';
|
|
@@ -163,7 +164,6 @@ import { initializeLangfuseTracing } from '@/instrumentation';
|
|
|
163
164
|
import { shouldTriggerSummarization } from '@/summarization';
|
|
164
165
|
import { isRunStepResumeState } from '@/tools/runStepResume';
|
|
165
166
|
import { resolveLocalToolsForBinding } from '@/tools/local';
|
|
166
|
-
import { isFadingTier, isInformativeFadingTier } from '@/messages/fading';
|
|
167
167
|
import { createSummarizeNode } from '@/summarization/node';
|
|
168
168
|
import { getTruncationStopReason } from '@/llm/truncation';
|
|
169
169
|
import { messagesStateReducer } from '@/messages/reducer';
|
|
@@ -3992,6 +3992,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
3992
3992
|
rootAgentName: this.agentContexts.get(this.defaultAgentId)?.name,
|
|
3993
3993
|
activeAgentId: agentId,
|
|
3994
3994
|
activeAgentName: agentContext.name,
|
|
3995
|
+
endpoint: agentContext.endpoint,
|
|
3995
3996
|
});
|
|
3996
3997
|
let langfuseHandler: CallbackEntry | undefined;
|
|
3997
3998
|
let invokeConfig = {
|
package/src/langfuse.ts
CHANGED
|
@@ -840,6 +840,10 @@ export function createLangfuseTraceMetadata({
|
|
|
840
840
|
rootAgentName,
|
|
841
841
|
activeAgentId,
|
|
842
842
|
activeAgentName,
|
|
843
|
+
endpoint,
|
|
844
|
+
model,
|
|
845
|
+
provider,
|
|
846
|
+
resolvedProvider,
|
|
843
847
|
}: {
|
|
844
848
|
messageId?: unknown;
|
|
845
849
|
parentMessageId?: unknown;
|
|
@@ -849,6 +853,10 @@ export function createLangfuseTraceMetadata({
|
|
|
849
853
|
rootAgentName?: unknown;
|
|
850
854
|
activeAgentId?: unknown;
|
|
851
855
|
activeAgentName?: unknown;
|
|
856
|
+
endpoint?: unknown;
|
|
857
|
+
model?: unknown;
|
|
858
|
+
provider?: unknown;
|
|
859
|
+
resolvedProvider?: unknown;
|
|
852
860
|
}): LangfuseTraceMetadata {
|
|
853
861
|
return createTraceMetadata({
|
|
854
862
|
messageId,
|
|
@@ -859,6 +867,10 @@ export function createLangfuseTraceMetadata({
|
|
|
859
867
|
rootAgentName,
|
|
860
868
|
activeAgentId,
|
|
861
869
|
activeAgentName,
|
|
870
|
+
endpoint,
|
|
871
|
+
model,
|
|
872
|
+
provider,
|
|
873
|
+
resolvedProvider,
|
|
862
874
|
});
|
|
863
875
|
}
|
|
864
876
|
|
|
@@ -58,6 +58,15 @@ const FOREIGN_SERVER_TOOL_TYPES = ['toolCall', 'toolResponse'];
|
|
|
58
58
|
* also has no tool calls, fall back to this placeholder text.
|
|
59
59
|
*/
|
|
60
60
|
const BEDROCK_EMPTY_TEXT_PLACEHOLDER = '_';
|
|
61
|
+
/**
|
|
62
|
+
* Whether a converted user message carries top-level text and document blocks, recorded
|
|
63
|
+
* during its single conversion pass and OR-ed through merging so finalization never
|
|
64
|
+
* rescans content.
|
|
65
|
+
*/
|
|
66
|
+
const userGroupShape = new WeakMap<
|
|
67
|
+
BedrockMessage,
|
|
68
|
+
{ hasText: boolean; hasDocument: boolean }
|
|
69
|
+
>();
|
|
61
70
|
|
|
62
71
|
/**
|
|
63
72
|
* Convert a LangChain reasoning block to a Bedrock reasoning block.
|
|
@@ -965,20 +974,72 @@ function convertFromV1ToChatBedrockConverseMessage(
|
|
|
965
974
|
function convertHumanMessageToConverseMessage(
|
|
966
975
|
msg: BaseMessage
|
|
967
976
|
): BedrockMessage {
|
|
968
|
-
const
|
|
969
|
-
|
|
970
|
-
|
|
977
|
+
const contentBlocks: BedrockContentBlock[] = [];
|
|
978
|
+
let hasText = false;
|
|
979
|
+
let hasDocument = false;
|
|
980
|
+
// Whitespace with nothing before it to fold into is held until the next text fragment,
|
|
981
|
+
// so `[' ', 'hello']` and `[image, ' ', 'hello']` keep their leading space instead of
|
|
982
|
+
// losing it; whitespace left over at the end has nothing to attach to and is dropped.
|
|
983
|
+
let pendingWhitespace = '';
|
|
984
|
+
const appendText = (text: string): void => {
|
|
985
|
+
const candidate = pendingWhitespace + text;
|
|
986
|
+
if (appendSerializableBedrockTextBlock(contentBlocks, candidate)) {
|
|
987
|
+
hasText = true;
|
|
988
|
+
pendingWhitespace = '';
|
|
989
|
+
return;
|
|
990
|
+
}
|
|
991
|
+
pendingWhitespace = candidate;
|
|
971
992
|
};
|
|
972
993
|
|
|
973
994
|
if (typeof msg.content === 'string') {
|
|
974
|
-
|
|
995
|
+
appendText(msg.content);
|
|
975
996
|
} else if (Array.isArray(msg.content)) {
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
997
|
+
for (const block of msg.content) {
|
|
998
|
+
if (
|
|
999
|
+
block.type === 'text' &&
|
|
1000
|
+
typeof (block as { text?: unknown }).text === 'string'
|
|
1001
|
+
) {
|
|
1002
|
+
appendText((block as { text: string }).text);
|
|
1003
|
+
continue;
|
|
1004
|
+
}
|
|
1005
|
+
const converted = convertLangChainContentBlockToConverseContentBlock({
|
|
1006
|
+
block,
|
|
1007
|
+
});
|
|
1008
|
+
if ('document' in converted) {
|
|
1009
|
+
hasDocument = true;
|
|
1010
|
+
}
|
|
1011
|
+
contentBlocks.push(converted);
|
|
1012
|
+
}
|
|
979
1013
|
}
|
|
980
1014
|
|
|
981
|
-
|
|
1015
|
+
const message: BedrockMessage = { role: 'user', content: contentBlocks };
|
|
1016
|
+
userGroupShape.set(message, { hasText, hasDocument });
|
|
1017
|
+
return message;
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
/**
|
|
1021
|
+
* Bedrock rejects an empty user message just as it rejects a blank text block
|
|
1022
|
+
* (`The text field in the ContentBlock object at messages.N.content.0 is blank`), and the
|
|
1023
|
+
* rejection fences the whole conversation on every retry. A promptless send — attachment, no
|
|
1024
|
+
* typed text — serializes to exactly that. Applied only once a user group is complete: an
|
|
1025
|
+
* empty human turn adjacent to a tool result merges into it below, and a per-message
|
|
1026
|
+
* placeholder would inject a synthetic `_` into an otherwise valid message.
|
|
1027
|
+
*/
|
|
1028
|
+
function finalizeUserMessage(message: BedrockMessage): void {
|
|
1029
|
+
if (message.role !== 'user') {
|
|
1030
|
+
return;
|
|
1031
|
+
}
|
|
1032
|
+
if (message.content == null || message.content.length === 0) {
|
|
1033
|
+
message.content = [{ text: BEDROCK_EMPTY_TEXT_PLACEHOLDER }];
|
|
1034
|
+
return;
|
|
1035
|
+
}
|
|
1036
|
+
// Converse also requires a text block in any message that carries a document block. A
|
|
1037
|
+
// promptless document upload arrives as blank text plus the document; the blank is
|
|
1038
|
+
// dropped during conversion, so supply the placeholder if no text survived.
|
|
1039
|
+
const shape = userGroupShape.get(message);
|
|
1040
|
+
if (shape != null && shape.hasDocument && !shape.hasText) {
|
|
1041
|
+
message.content.push({ text: BEDROCK_EMPTY_TEXT_PLACEHOLDER });
|
|
1042
|
+
}
|
|
982
1043
|
}
|
|
983
1044
|
|
|
984
1045
|
/**
|
|
@@ -1110,13 +1171,30 @@ export function convertToConverseMessages(messages: BaseMessage[]): {
|
|
|
1110
1171
|
const lastMessage = acc[acc.length - 1];
|
|
1111
1172
|
if (lastMessage.role === 'user' && curr.role === 'user') {
|
|
1112
1173
|
lastMessage.content = lastMessage.content?.concat(curr.content ?? []);
|
|
1174
|
+
const lastShape = userGroupShape.get(lastMessage);
|
|
1175
|
+
const nextShape = userGroupShape.get(curr);
|
|
1176
|
+
if (lastShape != null || nextShape != null) {
|
|
1177
|
+
userGroupShape.set(lastMessage, {
|
|
1178
|
+
hasText:
|
|
1179
|
+
(lastShape?.hasText ?? false) || (nextShape?.hasText ?? false),
|
|
1180
|
+
hasDocument:
|
|
1181
|
+
(lastShape?.hasDocument ?? false) ||
|
|
1182
|
+
(nextShape?.hasDocument ?? false),
|
|
1183
|
+
});
|
|
1184
|
+
}
|
|
1113
1185
|
} else {
|
|
1186
|
+
finalizeUserMessage(lastMessage);
|
|
1114
1187
|
acc.push(curr);
|
|
1115
1188
|
}
|
|
1116
1189
|
return acc;
|
|
1117
1190
|
},
|
|
1118
1191
|
[]
|
|
1119
1192
|
);
|
|
1193
|
+
if (combinedConverseMessages.length > 0) {
|
|
1194
|
+
finalizeUserMessage(
|
|
1195
|
+
combinedConverseMessages[combinedConverseMessages.length - 1]
|
|
1196
|
+
);
|
|
1197
|
+
}
|
|
1120
1198
|
|
|
1121
1199
|
return { converseMessages: combinedConverseMessages, converseSystem };
|
|
1122
1200
|
}
|
package/src/llm/invoke.ts
CHANGED
|
@@ -692,11 +692,32 @@ export async function attemptInvoke(
|
|
|
692
692
|
config?: RunnableConfig
|
|
693
693
|
): Promise<Partial<t.BaseGraphState>> {
|
|
694
694
|
const provider = resolveAttemptProvider(params);
|
|
695
|
-
const
|
|
695
|
+
const providerStampedConfig: RunnableConfig = {
|
|
696
696
|
...config,
|
|
697
697
|
metadata: {
|
|
698
698
|
...(config?.metadata ?? {}),
|
|
699
699
|
[Constants.INVOKED_PROVIDER]: provider,
|
|
700
|
+
provider,
|
|
701
|
+
resolvedProvider: provider,
|
|
702
|
+
},
|
|
703
|
+
};
|
|
704
|
+
const request = resolveAttemptRequest(params, providerStampedConfig);
|
|
705
|
+
const configuredModel = providerStampedConfig.metadata?.[
|
|
706
|
+
Constants.INVOKED_MODEL
|
|
707
|
+
];
|
|
708
|
+
const modelId =
|
|
709
|
+
request.modelId ??
|
|
710
|
+
(typeof configuredModel === 'string' ? configuredModel : undefined);
|
|
711
|
+
const stampedConfig: RunnableConfig = {
|
|
712
|
+
...providerStampedConfig,
|
|
713
|
+
metadata: {
|
|
714
|
+
...providerStampedConfig.metadata,
|
|
715
|
+
...(modelId == null
|
|
716
|
+
? {}
|
|
717
|
+
: {
|
|
718
|
+
[Constants.INVOKED_MODEL]: modelId,
|
|
719
|
+
model: modelId,
|
|
720
|
+
}),
|
|
700
721
|
/**
|
|
701
722
|
* One `attemptInvoke` call is one model attempt; primary, fallback,
|
|
702
723
|
* and retry attempts within a node otherwise share the same langgraph
|
|
@@ -727,7 +748,7 @@ export async function attemptInvoke(
|
|
|
727
748
|
try {
|
|
728
749
|
return await attemptInvokeBody(
|
|
729
750
|
{
|
|
730
|
-
request
|
|
751
|
+
request,
|
|
731
752
|
context: params.context,
|
|
732
753
|
onChunk: params.onChunk,
|
|
733
754
|
preemptAgentId: params.preemptAgentId,
|
|
@@ -1,5 +1,10 @@
|
|
|
1
|
+
import { Buffer } from 'node:buffer';
|
|
2
|
+
import type {
|
|
3
|
+
BaseMessage,
|
|
4
|
+
Data,
|
|
5
|
+
MessageContentComplex,
|
|
6
|
+
} from '@langchain/core/messages';
|
|
1
7
|
import type { RunnableConfig } from '@langchain/core/runnables';
|
|
2
|
-
import type { BaseMessage } from '@langchain/core/messages';
|
|
3
8
|
import type { ToolOutputReferenceRegistry } from '@/tools/toolOutputReferences';
|
|
4
9
|
import type * as t from '@/types';
|
|
5
10
|
import {
|
|
@@ -20,14 +25,60 @@ import {
|
|
|
20
25
|
import {
|
|
21
26
|
stripAnthropicCacheControl,
|
|
22
27
|
stripBedrockCacheControl,
|
|
28
|
+
cloneMessage,
|
|
23
29
|
} from '@/messages/cache';
|
|
30
|
+
import {
|
|
31
|
+
isAnthropicLike,
|
|
32
|
+
isGoogleLike,
|
|
33
|
+
isOpenAILike,
|
|
34
|
+
} from '@/utils/llm';
|
|
24
35
|
import { annotateMessagesForLLM } from '@/tools/toolOutputReferences';
|
|
25
36
|
import { providerRequiresStrictAlternation } from '@/llm/providers';
|
|
26
|
-
import { isAnthropicLike, isOpenAILike } from '@/utils/llm';
|
|
27
37
|
import { getProviderFamily } from '@/llm/providerRegistry';
|
|
28
38
|
import { Providers } from '@/common';
|
|
29
39
|
|
|
30
40
|
const preparedProviderRequestBrand = Symbol('PreparedProviderRequest');
|
|
41
|
+
const OMITTED_ATTACHMENT_TEXT =
|
|
42
|
+
'[Attachment omitted because its binary format is unsupported by this provider.]';
|
|
43
|
+
|
|
44
|
+
const BEDROCK_DOCUMENT_MIME_TYPES: Readonly<Partial<Record<string, string>>> = {
|
|
45
|
+
csv: 'text/csv',
|
|
46
|
+
doc: 'application/msword',
|
|
47
|
+
docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
48
|
+
html: 'text/html',
|
|
49
|
+
md: 'text/markdown',
|
|
50
|
+
pdf: 'application/pdf',
|
|
51
|
+
txt: 'text/plain',
|
|
52
|
+
xls: 'application/vnd.ms-excel',
|
|
53
|
+
xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
interface SerializedBuffer {
|
|
57
|
+
type: 'Buffer';
|
|
58
|
+
data: number[];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
interface BedrockDocumentBlock {
|
|
62
|
+
type: 'document';
|
|
63
|
+
document: {
|
|
64
|
+
name: string;
|
|
65
|
+
format: string;
|
|
66
|
+
source: {
|
|
67
|
+
bytes: Uint8Array | SerializedBuffer;
|
|
68
|
+
};
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
type StandardBase64FileBlock = Data.StandardFileBlock & {
|
|
73
|
+
source_type: 'base64';
|
|
74
|
+
mime_type: string;
|
|
75
|
+
data: string;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
type ProviderContentBlock =
|
|
79
|
+
| MessageContentComplex
|
|
80
|
+
| BedrockDocumentBlock
|
|
81
|
+
| StandardBase64FileBlock;
|
|
31
82
|
|
|
32
83
|
export type ProviderMessageProjectionMode =
|
|
33
84
|
| 'chat-messages'
|
|
@@ -159,16 +210,216 @@ interface ProjectMessagesForProviderParams {
|
|
|
159
210
|
callOptions?: unknown;
|
|
160
211
|
}
|
|
161
212
|
|
|
213
|
+
function isSerializedBuffer(value: object): value is SerializedBuffer {
|
|
214
|
+
return (
|
|
215
|
+
'type' in value &&
|
|
216
|
+
value.type === 'Buffer' &&
|
|
217
|
+
'data' in value &&
|
|
218
|
+
Array.isArray(value.data)
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function isBedrockDocumentBlock(
|
|
223
|
+
block: ProviderContentBlock
|
|
224
|
+
): block is BedrockDocumentBlock {
|
|
225
|
+
if (block.type !== 'document' || !('document' in block)) {
|
|
226
|
+
return false;
|
|
227
|
+
}
|
|
228
|
+
const document = block.document;
|
|
229
|
+
if (typeof document !== 'object' || document == null) {
|
|
230
|
+
return false;
|
|
231
|
+
}
|
|
232
|
+
if (
|
|
233
|
+
!('name' in document) ||
|
|
234
|
+
typeof document.name !== 'string' ||
|
|
235
|
+
!('format' in document) ||
|
|
236
|
+
typeof document.format !== 'string' ||
|
|
237
|
+
!('source' in document) ||
|
|
238
|
+
typeof document.source !== 'object' ||
|
|
239
|
+
document.source == null ||
|
|
240
|
+
!('bytes' in document.source)
|
|
241
|
+
) {
|
|
242
|
+
return false;
|
|
243
|
+
}
|
|
244
|
+
const bytes = document.source.bytes;
|
|
245
|
+
return (
|
|
246
|
+
bytes instanceof Uint8Array ||
|
|
247
|
+
(typeof bytes === 'object' && bytes != null && isSerializedBuffer(bytes))
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function toStandardFileBlock(
|
|
252
|
+
block: BedrockDocumentBlock
|
|
253
|
+
): StandardBase64FileBlock | undefined {
|
|
254
|
+
const mimeType = BEDROCK_DOCUMENT_MIME_TYPES[block.document.format];
|
|
255
|
+
if (mimeType == null) {
|
|
256
|
+
return undefined;
|
|
257
|
+
}
|
|
258
|
+
const source = block.document.source.bytes;
|
|
259
|
+
const bytes = source instanceof Uint8Array ? source : source.data;
|
|
260
|
+
return {
|
|
261
|
+
type: 'file',
|
|
262
|
+
source_type: 'base64',
|
|
263
|
+
mime_type: mimeType,
|
|
264
|
+
data: Buffer.from(bytes).toString('base64'),
|
|
265
|
+
metadata: { name: block.document.name },
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function isStandardFileBlock(
|
|
270
|
+
block: ProviderContentBlock
|
|
271
|
+
): block is StandardBase64FileBlock {
|
|
272
|
+
return (
|
|
273
|
+
block.type === 'file' &&
|
|
274
|
+
'source_type' in block &&
|
|
275
|
+
block.source_type === 'base64' &&
|
|
276
|
+
'mime_type' in block &&
|
|
277
|
+
typeof block.mime_type === 'string' &&
|
|
278
|
+
'data' in block &&
|
|
279
|
+
typeof block.data === 'string'
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function shouldRetainStandardFile(
|
|
284
|
+
provider: t.ProviderName,
|
|
285
|
+
mimeType: string
|
|
286
|
+
): boolean {
|
|
287
|
+
const normalizedMimeType = mimeType.split(';', 1)[0].trim().toLowerCase();
|
|
288
|
+
if (
|
|
289
|
+
provider === Providers.ANTHROPIC ||
|
|
290
|
+
getProviderFamily(provider) === 'anthropic'
|
|
291
|
+
) {
|
|
292
|
+
return (
|
|
293
|
+
normalizedMimeType === 'application/pdf' ||
|
|
294
|
+
normalizedMimeType === 'image/jpeg' ||
|
|
295
|
+
normalizedMimeType === 'image/png' ||
|
|
296
|
+
normalizedMimeType === 'image/gif' ||
|
|
297
|
+
normalizedMimeType === 'image/webp'
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
if (isOpenAILike(provider)) {
|
|
301
|
+
return normalizedMimeType === 'application/pdf';
|
|
302
|
+
}
|
|
303
|
+
return true;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function canProjectBedrockDocument(
|
|
307
|
+
provider: t.ProviderName,
|
|
308
|
+
mimeType: string
|
|
309
|
+
): boolean {
|
|
310
|
+
if (isGoogleLike(provider)) {
|
|
311
|
+
return true;
|
|
312
|
+
}
|
|
313
|
+
if (
|
|
314
|
+
provider === Providers.ANTHROPIC ||
|
|
315
|
+
getProviderFamily(provider) === 'anthropic' ||
|
|
316
|
+
isOpenAILike(provider)
|
|
317
|
+
) {
|
|
318
|
+
return mimeType === 'application/pdf';
|
|
319
|
+
}
|
|
320
|
+
return false;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function isBlankTextBlock(block: ProviderContentBlock): boolean {
|
|
324
|
+
return (
|
|
325
|
+
block.type === 'text' &&
|
|
326
|
+
'text' in block &&
|
|
327
|
+
typeof block.text === 'string' &&
|
|
328
|
+
block.text.trim() === ''
|
|
329
|
+
);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function copyUsableContentPrefix(
|
|
333
|
+
sourceContent: ProviderContentBlock[],
|
|
334
|
+
end: number
|
|
335
|
+
): ProviderContentBlock[] {
|
|
336
|
+
const content: ProviderContentBlock[] = [];
|
|
337
|
+
for (let index = 0; index < end; index++) {
|
|
338
|
+
const block = sourceContent[index];
|
|
339
|
+
if (!isBlankTextBlock(block)) {
|
|
340
|
+
content.push(block);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
return content;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/** Reprojects persisted provider-native attachments without changing history. */
|
|
347
|
+
function projectAttachmentsForProvider(
|
|
348
|
+
messages: BaseMessage[],
|
|
349
|
+
provider: t.ProviderName
|
|
350
|
+
): BaseMessage[] {
|
|
351
|
+
if (
|
|
352
|
+
provider === Providers.BEDROCK ||
|
|
353
|
+
getProviderFamily(provider) === 'bedrock'
|
|
354
|
+
) {
|
|
355
|
+
return messages;
|
|
356
|
+
}
|
|
357
|
+
let projected: BaseMessage[] | undefined;
|
|
358
|
+
for (let messageIndex = 0; messageIndex < messages.length; messageIndex++) {
|
|
359
|
+
const message = messages[messageIndex];
|
|
360
|
+
if (!Array.isArray(message.content)) {
|
|
361
|
+
continue;
|
|
362
|
+
}
|
|
363
|
+
const sourceContent = message.content as ProviderContentBlock[];
|
|
364
|
+
let content: ProviderContentBlock[] | undefined;
|
|
365
|
+
for (let blockIndex = 0; blockIndex < sourceContent.length; blockIndex++) {
|
|
366
|
+
const block = sourceContent[blockIndex];
|
|
367
|
+
if (isStandardFileBlock(block)) {
|
|
368
|
+
if (shouldRetainStandardFile(provider, block.mime_type)) {
|
|
369
|
+
content?.push(block);
|
|
370
|
+
continue;
|
|
371
|
+
}
|
|
372
|
+
content ??= copyUsableContentPrefix(sourceContent, blockIndex);
|
|
373
|
+
continue;
|
|
374
|
+
}
|
|
375
|
+
if (!isBedrockDocumentBlock(block)) {
|
|
376
|
+
if (content != null && !isBlankTextBlock(block)) {
|
|
377
|
+
content.push(block);
|
|
378
|
+
}
|
|
379
|
+
continue;
|
|
380
|
+
}
|
|
381
|
+
content ??= copyUsableContentPrefix(sourceContent, blockIndex);
|
|
382
|
+
const mimeType = BEDROCK_DOCUMENT_MIME_TYPES[block.document.format];
|
|
383
|
+
if (
|
|
384
|
+
mimeType == null ||
|
|
385
|
+
!canProjectBedrockDocument(provider, mimeType)
|
|
386
|
+
) {
|
|
387
|
+
continue;
|
|
388
|
+
}
|
|
389
|
+
const standardFile = toStandardFileBlock(block);
|
|
390
|
+
if (standardFile == null) {
|
|
391
|
+
continue;
|
|
392
|
+
}
|
|
393
|
+
content.push(standardFile);
|
|
394
|
+
}
|
|
395
|
+
if (content == null) {
|
|
396
|
+
continue;
|
|
397
|
+
}
|
|
398
|
+
if (content.length === 0) {
|
|
399
|
+
content.push({ type: 'text', text: OMITTED_ATTACHMENT_TEXT });
|
|
400
|
+
}
|
|
401
|
+
projected ??= [...messages];
|
|
402
|
+
projected[messageIndex] = cloneMessage(
|
|
403
|
+
message,
|
|
404
|
+
content as MessageContentComplex[]
|
|
405
|
+
);
|
|
406
|
+
}
|
|
407
|
+
return projected ?? messages;
|
|
408
|
+
}
|
|
409
|
+
|
|
162
410
|
function projectMessagesForProviderMode(
|
|
163
411
|
{ messages, provider, maxToolResultChars }: ProjectMessagesForProviderParams,
|
|
164
412
|
projectionMode: ProviderMessageProjectionMode
|
|
165
413
|
): BaseMessage[] {
|
|
166
414
|
const providerFamily = getProviderFamily(provider);
|
|
167
415
|
const nativeOpenAIResponses = projectionMode === 'openai-responses';
|
|
168
|
-
const providerInputMessages =
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
416
|
+
const providerInputMessages = projectAttachmentsForProvider(
|
|
417
|
+
projectToolStreamContentForProvider(
|
|
418
|
+
messages,
|
|
419
|
+
nativeOpenAIResponses ? 'native' : 'fallback',
|
|
420
|
+
maxToolResultChars
|
|
421
|
+
),
|
|
422
|
+
provider
|
|
172
423
|
);
|
|
173
424
|
if (nativeOpenAIResponses) {
|
|
174
425
|
return projectOpenAIResponsesToolMessageContent(
|
|
@@ -4,6 +4,7 @@ import { createDefaultLogger, formatErrorForLog } from './utils';
|
|
|
4
4
|
import { createSearchMetrics } from './metrics';
|
|
5
5
|
|
|
6
6
|
const DEFAULT_JINA_API_URL = 'https://api.jina.ai/v1/rerank';
|
|
7
|
+
const DEFAULT_COHERE_API_URL = 'https://api.cohere.com/v2/rerank';
|
|
7
8
|
|
|
8
9
|
/** Every other network call in the search pipeline is bounded (scrapers,
|
|
9
10
|
* search providers); rerank requests must be too, or a hung rerank API
|
|
@@ -15,6 +16,11 @@ const getDefaultJinaApiUrl = (): string =>
|
|
|
15
16
|
? process.env.JINA_API_URL
|
|
16
17
|
: DEFAULT_JINA_API_URL;
|
|
17
18
|
|
|
19
|
+
const getDefaultCohereApiUrl = (): string =>
|
|
20
|
+
process.env.COHERE_API_URL != null && process.env.COHERE_API_URL !== ''
|
|
21
|
+
? process.env.COHERE_API_URL
|
|
22
|
+
: DEFAULT_COHERE_API_URL;
|
|
23
|
+
|
|
18
24
|
export abstract class BaseReranker {
|
|
19
25
|
protected apiKey: string | undefined;
|
|
20
26
|
protected logger: t.Logger;
|
|
@@ -219,23 +225,27 @@ export class JinaReranker extends BaseReranker {
|
|
|
219
225
|
|
|
220
226
|
export class CohereReranker extends BaseReranker {
|
|
221
227
|
readonly provider = 'cohere';
|
|
228
|
+
private apiUrl: string;
|
|
222
229
|
private timeout: number;
|
|
223
230
|
private httpAgent?: t.HttpAgent;
|
|
224
231
|
private httpsAgent?: t.HttpsAgent;
|
|
225
232
|
|
|
226
233
|
constructor({
|
|
227
234
|
apiKey = process.env.COHERE_API_KEY,
|
|
235
|
+
apiUrl = getDefaultCohereApiUrl(),
|
|
228
236
|
timeout = DEFAULT_RERANKER_TIMEOUT,
|
|
229
237
|
logger,
|
|
230
238
|
httpAgent,
|
|
231
239
|
httpsAgent,
|
|
232
240
|
}: {
|
|
233
241
|
apiKey?: string;
|
|
242
|
+
apiUrl?: string;
|
|
234
243
|
timeout?: number;
|
|
235
244
|
logger?: t.Logger;
|
|
236
245
|
} & t.HttpAgentConfig) {
|
|
237
246
|
super(logger);
|
|
238
247
|
this.apiKey = apiKey;
|
|
248
|
+
this.apiUrl = apiUrl;
|
|
239
249
|
this.timeout = timeout;
|
|
240
250
|
this.httpAgent = httpAgent;
|
|
241
251
|
this.httpsAgent = httpsAgent;
|
|
@@ -263,7 +273,7 @@ export class CohereReranker extends BaseReranker {
|
|
|
263
273
|
};
|
|
264
274
|
|
|
265
275
|
const response = await axios.post<t.CohereRerankerResponse | undefined>(
|
|
266
|
-
|
|
276
|
+
this.apiUrl,
|
|
267
277
|
requestData,
|
|
268
278
|
{
|
|
269
279
|
headers: {
|
|
@@ -560,6 +570,7 @@ export const createReranker = (
|
|
|
560
570
|
jinaApiKey?: string;
|
|
561
571
|
jinaApiUrl?: string;
|
|
562
572
|
cohereApiKey?: string;
|
|
573
|
+
cohereApiUrl?: string;
|
|
563
574
|
ragApiUrl?: string;
|
|
564
575
|
ragApiTokenSupplier?: t.RagApiTokenSupplier;
|
|
565
576
|
ragApiProfile?: string;
|
|
@@ -572,6 +583,7 @@ export const createReranker = (
|
|
|
572
583
|
jinaApiKey,
|
|
573
584
|
jinaApiUrl,
|
|
574
585
|
cohereApiKey,
|
|
586
|
+
cohereApiUrl,
|
|
575
587
|
ragApiUrl,
|
|
576
588
|
ragApiTokenSupplier,
|
|
577
589
|
ragApiProfile,
|
|
@@ -597,6 +609,7 @@ export const createReranker = (
|
|
|
597
609
|
case 'cohere':
|
|
598
610
|
return new CohereReranker({
|
|
599
611
|
apiKey: cohereApiKey,
|
|
612
|
+
apiUrl: cohereApiUrl,
|
|
600
613
|
timeout: rerankerTimeout,
|
|
601
614
|
logger: defaultLogger,
|
|
602
615
|
httpAgent,
|
package/src/tools/search/tool.ts
CHANGED
|
@@ -20,8 +20,8 @@ import { createFirecrawlScraper } from './firecrawl';
|
|
|
20
20
|
import { INTENT_PROPERTY } from '@/tools/intentArg';
|
|
21
21
|
import { createCrwScraper } from './crw-scraper';
|
|
22
22
|
import { expandHighlights } from './highlights';
|
|
23
|
-
import { formatResultsForLLM } from './format';
|
|
24
23
|
import { createSearchMetrics } from './metrics';
|
|
24
|
+
import { formatResultsForLLM } from './format';
|
|
25
25
|
import { createDefaultLogger } from './utils';
|
|
26
26
|
import { createReranker } from './rerankers';
|
|
27
27
|
import { Constants } from '@/common';
|
|
@@ -514,6 +514,7 @@ export const createSearchTool = (
|
|
|
514
514
|
jinaApiKey,
|
|
515
515
|
jinaApiUrl,
|
|
516
516
|
cohereApiKey,
|
|
517
|
+
cohereApiUrl,
|
|
517
518
|
ragApiUrl,
|
|
518
519
|
ragApiTokenSupplier,
|
|
519
520
|
ragApiProfile,
|
|
@@ -637,6 +638,7 @@ export const createSearchTool = (
|
|
|
637
638
|
jinaApiKey,
|
|
638
639
|
jinaApiUrl,
|
|
639
640
|
cohereApiKey,
|
|
641
|
+
cohereApiUrl,
|
|
640
642
|
ragApiUrl,
|
|
641
643
|
ragApiTokenSupplier,
|
|
642
644
|
ragApiProfile,
|
|
@@ -519,6 +519,7 @@ export interface SearchToolConfig
|
|
|
519
519
|
jinaApiKey?: string;
|
|
520
520
|
jinaApiUrl?: string;
|
|
521
521
|
cohereApiKey?: string;
|
|
522
|
+
cohereApiUrl?: string;
|
|
522
523
|
/** Base URL of the public `rag_api` service (`RAG_API_URL` env fallback).
|
|
523
524
|
* Requests post to `${ragApiUrl}/v1/rerank`. */
|
|
524
525
|
ragApiUrl?: string;
|
package/src/types/graph.ts
CHANGED
|
@@ -853,6 +853,8 @@ export interface LangfuseConfig {
|
|
|
853
853
|
|
|
854
854
|
interface AgentInputFields {
|
|
855
855
|
agentId: string;
|
|
856
|
+
/** Logical endpoint selected by the host before provider resolution. */
|
|
857
|
+
endpoint?: string;
|
|
856
858
|
/**
|
|
857
859
|
* Partition key for transient code-session ids and file refs. Agents with
|
|
858
860
|
* the same key share those refs; different execution profiles/scopes must
|