@librechat/agents 2.4.52 → 2.4.54
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 +8 -7
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/llm/anthropic/index.cjs +8 -8
- 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 +15 -0
- package/dist/cjs/llm/anthropic/utils/message_inputs.cjs.map +1 -1
- package/dist/cjs/main.cjs +2 -0
- package/dist/cjs/main.cjs.map +1 -1
- package/dist/cjs/stream.cjs +8 -0
- package/dist/cjs/stream.cjs.map +1 -1
- package/dist/cjs/tools/ToolNode.cjs +8 -2
- package/dist/cjs/tools/ToolNode.cjs.map +1 -1
- package/dist/cjs/tools/handlers.cjs +107 -1
- package/dist/cjs/tools/handlers.cjs.map +1 -1
- package/dist/cjs/tools/search/anthropic.cjs +40 -0
- package/dist/cjs/tools/search/anthropic.cjs.map +1 -0
- package/dist/cjs/tools/search/search.cjs +2 -2
- package/dist/cjs/tools/search/search.cjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +8 -7
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/llm/anthropic/index.mjs +8 -8
- 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 +15 -0
- package/dist/esm/llm/anthropic/utils/message_inputs.mjs.map +1 -1
- package/dist/esm/main.mjs +1 -1
- package/dist/esm/stream.mjs +9 -1
- package/dist/esm/stream.mjs.map +1 -1
- package/dist/esm/tools/ToolNode.mjs +8 -2
- package/dist/esm/tools/ToolNode.mjs.map +1 -1
- package/dist/esm/tools/handlers.mjs +107 -3
- package/dist/esm/tools/handlers.mjs.map +1 -1
- package/dist/esm/tools/search/anthropic.mjs +37 -0
- package/dist/esm/tools/search/anthropic.mjs.map +1 -0
- package/dist/esm/tools/search/search.mjs +2 -2
- package/dist/esm/tools/search/search.mjs.map +1 -1
- package/dist/types/graphs/Graph.d.ts +3 -1
- package/dist/types/llm/anthropic/types.d.ts +2 -0
- package/dist/types/llm/anthropic/utils/message_inputs.d.ts +1 -1
- package/dist/types/llm/anthropic/utils/output_parsers.d.ts +3 -3
- package/dist/types/scripts/ant_web_search.d.ts +1 -0
- package/dist/types/tools/CodeExecutor.d.ts +2 -2
- package/dist/types/tools/ToolNode.d.ts +1 -1
- package/dist/types/tools/handlers.d.ts +11 -0
- package/dist/types/tools/search/anthropic.d.ts +16 -0
- package/dist/types/types/llm.d.ts +3 -2
- package/dist/types/types/stream.d.ts +9 -1
- package/package.json +5 -3
- package/src/graphs/Graph.ts +9 -7
- package/src/llm/anthropic/Jacob_Lee_Resume_2023.pdf +0 -0
- package/src/llm/anthropic/index.ts +7 -12
- package/src/llm/anthropic/llm.spec.ts +447 -115
- package/src/llm/anthropic/types.ts +16 -0
- package/src/llm/anthropic/utils/message_inputs.ts +17 -2
- package/src/llm/anthropic/utils/output_parsers.ts +4 -4
- package/src/scripts/ant_web_search.ts +158 -0
- package/src/stream.ts +16 -5
- package/src/tools/ToolNode.ts +17 -3
- package/src/tools/handlers.ts +170 -1
- package/src/tools/search/anthropic.ts +51 -0
- package/src/tools/search/search.ts +2 -1
- package/src/types/llm.ts +4 -2
- package/src/types/stream.ts +14 -0
|
@@ -49,6 +49,8 @@ export type AnthropicWebSearchToolResultBlockParam =
|
|
|
49
49
|
Anthropic.Messages.WebSearchToolResultBlockParam;
|
|
50
50
|
export type AnthropicWebSearchResultBlockParam =
|
|
51
51
|
Anthropic.Messages.WebSearchResultBlockParam;
|
|
52
|
+
export type AnthropicSearchResultBlockParam =
|
|
53
|
+
Anthropic.Beta.BetaSearchResultBlockParam;
|
|
52
54
|
|
|
53
55
|
// Union of all possible content block types including server tool use
|
|
54
56
|
export type AnthropicContentBlock =
|
|
@@ -63,6 +65,20 @@ export type AnthropicContentBlock =
|
|
|
63
65
|
| AnthropicWebSearchToolResultBlockParam
|
|
64
66
|
| AnthropicWebSearchResultBlockParam;
|
|
65
67
|
|
|
68
|
+
// Union of all possible content block types including server tool use
|
|
69
|
+
export type ChatAnthropicContentBlock =
|
|
70
|
+
| AnthropicTextBlockParam
|
|
71
|
+
| AnthropicImageBlockParam
|
|
72
|
+
| AnthropicToolUseBlockParam
|
|
73
|
+
| AnthropicToolResultBlockParam
|
|
74
|
+
| AnthropicDocumentBlockParam
|
|
75
|
+
| AnthropicThinkingBlockParam
|
|
76
|
+
| AnthropicRedactedThinkingBlockParam
|
|
77
|
+
| AnthropicServerToolUseBlockParam
|
|
78
|
+
| AnthropicWebSearchToolResultBlockParam
|
|
79
|
+
| AnthropicWebSearchResultBlockParam
|
|
80
|
+
| AnthropicSearchResultBlockParam;
|
|
81
|
+
|
|
66
82
|
export function isAnthropicImageBlockParam(
|
|
67
83
|
block: unknown
|
|
68
84
|
): block is AnthropicImageBlockParam {
|
|
@@ -25,7 +25,6 @@ import {
|
|
|
25
25
|
AnthropicImageBlockParam,
|
|
26
26
|
AnthropicMessageCreateParams,
|
|
27
27
|
AnthropicTextBlockParam,
|
|
28
|
-
AnthropicToolResponse,
|
|
29
28
|
AnthropicToolResultBlockParam,
|
|
30
29
|
AnthropicToolUseBlockParam,
|
|
31
30
|
AnthropicDocumentBlockParam,
|
|
@@ -34,7 +33,9 @@ import {
|
|
|
34
33
|
AnthropicServerToolUseBlockParam,
|
|
35
34
|
AnthropicWebSearchToolResultBlockParam,
|
|
36
35
|
isAnthropicImageBlockParam,
|
|
37
|
-
|
|
36
|
+
AnthropicSearchResultBlockParam,
|
|
37
|
+
AnthropicToolResponse,
|
|
38
|
+
} from '../types';
|
|
38
39
|
|
|
39
40
|
function _formatImage(imageUrl: string) {
|
|
40
41
|
const parsed = parseBase64DataUrl({ dataUrl: imageUrl });
|
|
@@ -407,6 +408,20 @@ function _formatContent(content: MessageContent) {
|
|
|
407
408
|
...(cacheControl ? { cache_control: cacheControl } : {}),
|
|
408
409
|
};
|
|
409
410
|
return block;
|
|
411
|
+
} else if (contentPart.type === 'search_result') {
|
|
412
|
+
const block: AnthropicSearchResultBlockParam = {
|
|
413
|
+
type: 'search_result' as const, // Explicitly setting the type as "search_result"
|
|
414
|
+
title: contentPart.title,
|
|
415
|
+
source: contentPart.source,
|
|
416
|
+
...('cache_control' in contentPart && contentPart.cache_control
|
|
417
|
+
? { cache_control: contentPart.cache_control }
|
|
418
|
+
: {}),
|
|
419
|
+
...('citations' in contentPart && contentPart.citations
|
|
420
|
+
? { citations: contentPart.citations }
|
|
421
|
+
: {}),
|
|
422
|
+
content: contentPart.content,
|
|
423
|
+
};
|
|
424
|
+
return block;
|
|
410
425
|
} else if (
|
|
411
426
|
textTypes.find((t) => t === contentPart.type) &&
|
|
412
427
|
'text' in contentPart
|
|
@@ -4,17 +4,17 @@ import {
|
|
|
4
4
|
BaseLLMOutputParser,
|
|
5
5
|
OutputParserException,
|
|
6
6
|
} from '@langchain/core/output_parsers';
|
|
7
|
-
import {
|
|
7
|
+
import { JsonOutputKeyToolsParserParamsInterop } from '@langchain/core/output_parsers/openai_tools';
|
|
8
|
+
import { ChatGeneration } from '@langchain/core/outputs';
|
|
9
|
+
import { ToolCall } from '@langchain/core/messages/tool';
|
|
8
10
|
import {
|
|
9
11
|
interopSafeParseAsync,
|
|
10
12
|
InteropZodType,
|
|
11
13
|
} from '@langchain/core/utils/types';
|
|
12
|
-
import { ChatGeneration } from '@langchain/core/outputs';
|
|
13
|
-
import { ToolCall } from '@langchain/core/messages/tool';
|
|
14
14
|
|
|
15
15
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
16
16
|
interface AnthropicToolsOutputParserParams<T extends Record<string, any>>
|
|
17
|
-
extends
|
|
17
|
+
extends JsonOutputKeyToolsParserParamsInterop<T> {}
|
|
18
18
|
|
|
19
19
|
export class AnthropicToolsOutputParser<
|
|
20
20
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/* eslint-disable no-console */
|
|
2
|
+
// src/scripts/cli.ts
|
|
3
|
+
import { config } from 'dotenv';
|
|
4
|
+
config();
|
|
5
|
+
import { HumanMessage, BaseMessage } from '@langchain/core/messages';
|
|
6
|
+
import type * as t from '@/types';
|
|
7
|
+
import { ChatModelStreamHandler, createContentAggregator } from '@/stream';
|
|
8
|
+
import { ToolEndHandler, ModelEndHandler } from '@/events';
|
|
9
|
+
|
|
10
|
+
import { getArgs } from '@/scripts/args';
|
|
11
|
+
import { Run } from '@/run';
|
|
12
|
+
import { GraphEvents, Callback, Providers } from '@/common';
|
|
13
|
+
import { getLLMConfig } from '@/utils/llmConfig';
|
|
14
|
+
|
|
15
|
+
const conversationHistory: BaseMessage[] = [];
|
|
16
|
+
let _contentParts: (t.MessageContentComplex | undefined)[] = [];
|
|
17
|
+
async function testStandardStreaming(): Promise<void> {
|
|
18
|
+
const { userName, location, currentDate } = await getArgs();
|
|
19
|
+
const { contentParts, aggregateContent } = createContentAggregator();
|
|
20
|
+
_contentParts = contentParts;
|
|
21
|
+
const customHandlers = {
|
|
22
|
+
[GraphEvents.TOOL_END]: new ToolEndHandler(),
|
|
23
|
+
[GraphEvents.CHAT_MODEL_END]: new ModelEndHandler(),
|
|
24
|
+
[GraphEvents.CHAT_MODEL_STREAM]: new ChatModelStreamHandler(),
|
|
25
|
+
[GraphEvents.ON_RUN_STEP_COMPLETED]: {
|
|
26
|
+
handle: (
|
|
27
|
+
event: GraphEvents.ON_RUN_STEP_COMPLETED,
|
|
28
|
+
data: t.StreamEventData
|
|
29
|
+
): void => {
|
|
30
|
+
console.log('====== ON_RUN_STEP_COMPLETED ======');
|
|
31
|
+
// console.dir(data, { depth: null });
|
|
32
|
+
aggregateContent({
|
|
33
|
+
event,
|
|
34
|
+
data: data as unknown as { result: t.ToolEndEvent },
|
|
35
|
+
});
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
[GraphEvents.ON_RUN_STEP]: {
|
|
39
|
+
handle: (
|
|
40
|
+
event: GraphEvents.ON_RUN_STEP,
|
|
41
|
+
data: t.StreamEventData
|
|
42
|
+
): void => {
|
|
43
|
+
console.log('====== ON_RUN_STEP ======');
|
|
44
|
+
console.dir(data, { depth: null });
|
|
45
|
+
aggregateContent({ event, data: data as t.RunStep });
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
[GraphEvents.ON_RUN_STEP_DELTA]: {
|
|
49
|
+
handle: (
|
|
50
|
+
event: GraphEvents.ON_RUN_STEP_DELTA,
|
|
51
|
+
data: t.StreamEventData
|
|
52
|
+
): void => {
|
|
53
|
+
console.log('====== ON_RUN_STEP_DELTA ======');
|
|
54
|
+
console.dir(data, { depth: null });
|
|
55
|
+
aggregateContent({ event, data: data as t.RunStepDeltaEvent });
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
[GraphEvents.ON_MESSAGE_DELTA]: {
|
|
59
|
+
handle: (
|
|
60
|
+
event: GraphEvents.ON_MESSAGE_DELTA,
|
|
61
|
+
data: t.StreamEventData
|
|
62
|
+
): void => {
|
|
63
|
+
console.log('====== ON_MESSAGE_DELTA ======');
|
|
64
|
+
console.dir(data, { depth: null });
|
|
65
|
+
aggregateContent({ event, data: data as t.MessageDeltaEvent });
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
[GraphEvents.TOOL_START]: {
|
|
69
|
+
handle: (
|
|
70
|
+
_event: string,
|
|
71
|
+
data: t.StreamEventData,
|
|
72
|
+
metadata?: Record<string, unknown>
|
|
73
|
+
): void => {
|
|
74
|
+
console.log('====== TOOL_START ======');
|
|
75
|
+
// console.dir(data, { depth: null });
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const llmConfig = getLLMConfig(
|
|
81
|
+
Providers.ANTHROPIC
|
|
82
|
+
) as t.AnthropicClientOptions & t.SharedLLMConfig;
|
|
83
|
+
llmConfig.model = 'claude-3-5-sonnet-latest';
|
|
84
|
+
|
|
85
|
+
const run = await Run.create<t.IState>({
|
|
86
|
+
runId: 'test-run-id',
|
|
87
|
+
graphConfig: {
|
|
88
|
+
type: 'standard',
|
|
89
|
+
llmConfig,
|
|
90
|
+
tools: [
|
|
91
|
+
{
|
|
92
|
+
type: 'web_search_20250305',
|
|
93
|
+
name: 'web_search',
|
|
94
|
+
max_uses: 5,
|
|
95
|
+
},
|
|
96
|
+
],
|
|
97
|
+
instructions: 'You are a friendly AI assistant.',
|
|
98
|
+
// additional_instructions: `Always address the user by their name. The user's name is ${userName} and they are located in ${location}.`,
|
|
99
|
+
},
|
|
100
|
+
returnContent: true,
|
|
101
|
+
customHandlers,
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
const config = {
|
|
105
|
+
configurable: {
|
|
106
|
+
provider: Providers.ANTHROPIC,
|
|
107
|
+
thread_id: 'conversation-num-1',
|
|
108
|
+
},
|
|
109
|
+
streamMode: 'values',
|
|
110
|
+
version: 'v2' as const,
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
console.log('Test 1: Search query (search tool test)');
|
|
114
|
+
|
|
115
|
+
// const userMessage = `
|
|
116
|
+
// Make a search for the weather in ${location} today, which is ${currentDate}.
|
|
117
|
+
// Before making the search, please let me know what you're about to do, then immediately start searching without hesitation.
|
|
118
|
+
// Make sure to always refer to me by name, which is ${userName}.
|
|
119
|
+
// After giving me a thorough summary, tell me a joke about the weather forecast we went over.
|
|
120
|
+
// `;
|
|
121
|
+
// const userMessage = 'Are massage guns good?';
|
|
122
|
+
// const userMessage = 'What is functional programming?';
|
|
123
|
+
const userMessage = "Get me today's trending news.";
|
|
124
|
+
// const userMessage = "search recent italy earthquake volcano activity";
|
|
125
|
+
// const userMessage =
|
|
126
|
+
// "use 'Trump' as the exact search query and tell me what you find.";
|
|
127
|
+
|
|
128
|
+
conversationHistory.push(new HumanMessage(userMessage));
|
|
129
|
+
|
|
130
|
+
const inputs = {
|
|
131
|
+
messages: conversationHistory,
|
|
132
|
+
};
|
|
133
|
+
const finalContentParts = await run.processStream(inputs, config);
|
|
134
|
+
const finalMessages = run.getRunMessages();
|
|
135
|
+
if (finalMessages) {
|
|
136
|
+
conversationHistory.push(...finalMessages);
|
|
137
|
+
console.dir(conversationHistory, { depth: null });
|
|
138
|
+
}
|
|
139
|
+
// console.dir(finalContentParts, { depth: null });
|
|
140
|
+
console.log('\n\n====================\n\n');
|
|
141
|
+
// console.dir(contentParts, { depth: null });
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
process.on('unhandledRejection', (reason, promise) => {
|
|
145
|
+
console.error('Unhandled Rejection at:', promise, 'reason:', reason);
|
|
146
|
+
console.log('Content Parts:');
|
|
147
|
+
console.dir(_contentParts, { depth: null });
|
|
148
|
+
process.exit(1);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
testStandardStreaming().catch((err) => {
|
|
152
|
+
console.error(err);
|
|
153
|
+
console.log('Conversation history:');
|
|
154
|
+
console.dir(conversationHistory, { depth: null });
|
|
155
|
+
console.log('Content Parts:');
|
|
156
|
+
console.dir(_contentParts, { depth: null });
|
|
157
|
+
process.exit(1);
|
|
158
|
+
});
|
package/src/stream.ts
CHANGED
|
@@ -1,17 +1,21 @@
|
|
|
1
1
|
// src/stream.ts
|
|
2
|
+
import type { ChatOpenAIReasoningSummary } from '@langchain/openai';
|
|
2
3
|
import type { AIMessageChunk } from '@langchain/core/messages';
|
|
3
4
|
import type { ToolCall } from '@langchain/core/messages/tool';
|
|
4
5
|
import type { Graph } from '@/graphs';
|
|
5
|
-
import type { ChatOpenAIReasoningSummary } from '@langchain/openai';
|
|
6
6
|
import type * as t from '@/types';
|
|
7
7
|
import {
|
|
8
|
-
|
|
8
|
+
ToolCallTypes,
|
|
9
9
|
ContentTypes,
|
|
10
10
|
GraphEvents,
|
|
11
|
-
|
|
11
|
+
StepTypes,
|
|
12
12
|
Providers,
|
|
13
13
|
} from '@/common';
|
|
14
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
handleServerToolResult,
|
|
16
|
+
handleToolCallChunks,
|
|
17
|
+
handleToolCalls,
|
|
18
|
+
} from '@/tools/handlers';
|
|
15
19
|
import { getMessageId } from '@/messages';
|
|
16
20
|
|
|
17
21
|
/**
|
|
@@ -132,8 +136,15 @@ export class ChatModelStreamHandler implements t.EventHandler {
|
|
|
132
136
|
reasoningKey: graph.reasoningKey,
|
|
133
137
|
provider: metadata?.provider as Providers,
|
|
134
138
|
});
|
|
139
|
+
const skipHandling = handleServerToolResult({
|
|
140
|
+
content,
|
|
141
|
+
metadata,
|
|
142
|
+
graph,
|
|
143
|
+
});
|
|
144
|
+
if (skipHandling) {
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
135
147
|
this.handleReasoning(chunk, graph, metadata?.provider as Providers);
|
|
136
|
-
|
|
137
148
|
let hasToolCalls = false;
|
|
138
149
|
if (
|
|
139
150
|
chunk.tool_calls &&
|
package/src/tools/ToolNode.ts
CHANGED
|
@@ -138,16 +138,30 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
138
138
|
}
|
|
139
139
|
}
|
|
140
140
|
|
|
141
|
+
function areToolCallsInvoked(
|
|
142
|
+
message: AIMessage,
|
|
143
|
+
invokedToolIds?: Set<string>
|
|
144
|
+
): boolean {
|
|
145
|
+
if (!invokedToolIds || invokedToolIds.size === 0) return false;
|
|
146
|
+
return (
|
|
147
|
+
message.tool_calls?.every(
|
|
148
|
+
(toolCall) => toolCall.id != null && invokedToolIds.has(toolCall.id)
|
|
149
|
+
) ?? false
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
|
|
141
153
|
export function toolsCondition(
|
|
142
|
-
state: BaseMessage[] | typeof MessagesAnnotation.State
|
|
154
|
+
state: BaseMessage[] | typeof MessagesAnnotation.State,
|
|
155
|
+
invokedToolIds?: Set<string>
|
|
143
156
|
): 'tools' | typeof END {
|
|
144
|
-
const message = Array.isArray(state)
|
|
157
|
+
const message: AIMessage = Array.isArray(state)
|
|
145
158
|
? state[state.length - 1]
|
|
146
159
|
: state.messages[state.messages.length - 1];
|
|
147
160
|
|
|
148
161
|
if (
|
|
149
162
|
'tool_calls' in message &&
|
|
150
|
-
(
|
|
163
|
+
(message.tool_calls?.length ?? 0) > 0 &&
|
|
164
|
+
!areToolCallsInvoked(message, invokedToolIds)
|
|
151
165
|
) {
|
|
152
166
|
return GraphNodeKeys.TOOLS;
|
|
153
167
|
} else {
|
package/src/tools/handlers.ts
CHANGED
|
@@ -1,10 +1,24 @@
|
|
|
1
1
|
/* eslint-disable no-console */
|
|
2
2
|
// src/tools/handlers.ts
|
|
3
3
|
import { nanoid } from 'nanoid';
|
|
4
|
+
import { ToolMessage } from '@langchain/core/messages';
|
|
5
|
+
import type { AnthropicWebSearchResultBlockParam } from '@/llm/anthropic/types';
|
|
4
6
|
import type { ToolCall, ToolCallChunk } from '@langchain/core/messages/tool';
|
|
5
7
|
import type { Graph } from '@/graphs';
|
|
6
8
|
import type * as t from '@/types';
|
|
7
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
ToolCallTypes,
|
|
11
|
+
ContentTypes,
|
|
12
|
+
GraphEvents,
|
|
13
|
+
StepTypes,
|
|
14
|
+
Providers,
|
|
15
|
+
Constants,
|
|
16
|
+
} from '@/common';
|
|
17
|
+
import {
|
|
18
|
+
coerceAnthropicSearchResults,
|
|
19
|
+
isAnthropicWebSearchResult,
|
|
20
|
+
} from '@/tools/search/anthropic';
|
|
21
|
+
import { formatResultsForLLM } from '@/tools/search/format';
|
|
8
22
|
import { getMessageId } from '@/messages';
|
|
9
23
|
|
|
10
24
|
export function handleToolCallChunks({
|
|
@@ -165,3 +179,158 @@ export const handleToolCalls = (
|
|
|
165
179
|
});
|
|
166
180
|
}
|
|
167
181
|
};
|
|
182
|
+
|
|
183
|
+
export const toolResultTypes = new Set([
|
|
184
|
+
// 'tool_use',
|
|
185
|
+
// 'server_tool_use',
|
|
186
|
+
// 'input_json_delta',
|
|
187
|
+
'tool_result',
|
|
188
|
+
'web_search_result',
|
|
189
|
+
'web_search_tool_result',
|
|
190
|
+
]);
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Handles the result of a server tool call; in other words, a provider's built-in tool.
|
|
194
|
+
* As of 2025-07-06, only Anthropic handles server tool calls with this pattern.
|
|
195
|
+
*/
|
|
196
|
+
export function handleServerToolResult({
|
|
197
|
+
content,
|
|
198
|
+
metadata,
|
|
199
|
+
graph,
|
|
200
|
+
}: {
|
|
201
|
+
content?: string | t.MessageContentComplex[];
|
|
202
|
+
metadata?: Record<string, unknown>;
|
|
203
|
+
graph: Graph;
|
|
204
|
+
}): boolean {
|
|
205
|
+
let skipHandling = false;
|
|
206
|
+
if (metadata?.provider !== Providers.ANTHROPIC) {
|
|
207
|
+
return skipHandling;
|
|
208
|
+
}
|
|
209
|
+
if (
|
|
210
|
+
typeof content === 'string' ||
|
|
211
|
+
content == null ||
|
|
212
|
+
content.length === 0 ||
|
|
213
|
+
(content.length === 1 &&
|
|
214
|
+
(content[0] as t.ToolResultContent).tool_use_id == null)
|
|
215
|
+
) {
|
|
216
|
+
return skipHandling;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
for (const contentPart of content) {
|
|
220
|
+
const toolUseId = (contentPart as t.ToolResultContent).tool_use_id;
|
|
221
|
+
if (toolUseId == null || toolUseId === '') {
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
const stepId = graph.toolCallStepIds.get(toolUseId);
|
|
225
|
+
if (stepId == null || stepId === '') {
|
|
226
|
+
console.warn(
|
|
227
|
+
`Tool use ID ${toolUseId} not found in graph, cannot dispatch tool result.`
|
|
228
|
+
);
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
const runStep = graph.getRunStep(stepId);
|
|
232
|
+
if (!runStep) {
|
|
233
|
+
console.warn(
|
|
234
|
+
`Run step for ${stepId} does not exist, cannot dispatch tool result.`
|
|
235
|
+
);
|
|
236
|
+
continue;
|
|
237
|
+
} else if (runStep.type !== StepTypes.TOOL_CALLS) {
|
|
238
|
+
console.warn(
|
|
239
|
+
`Run step for ${stepId} is not a tool call step, cannot dispatch tool result.`
|
|
240
|
+
);
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const toolCall =
|
|
245
|
+
runStep.stepDetails.type === StepTypes.TOOL_CALLS
|
|
246
|
+
? (runStep.stepDetails.tool_calls?.find(
|
|
247
|
+
(toolCall) => toolCall.id === toolUseId
|
|
248
|
+
) as ToolCall)
|
|
249
|
+
: undefined;
|
|
250
|
+
|
|
251
|
+
if (!toolCall) {
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (
|
|
256
|
+
contentPart.type === 'web_search_result' ||
|
|
257
|
+
contentPart.type === 'web_search_tool_result'
|
|
258
|
+
) {
|
|
259
|
+
handleAnthropicSearchResults({
|
|
260
|
+
contentPart: contentPart as t.ToolResultContent,
|
|
261
|
+
toolCall,
|
|
262
|
+
metadata,
|
|
263
|
+
graph,
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (!skipHandling) {
|
|
268
|
+
skipHandling = true;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return skipHandling;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function handleAnthropicSearchResults({
|
|
276
|
+
contentPart,
|
|
277
|
+
toolCall,
|
|
278
|
+
metadata,
|
|
279
|
+
graph,
|
|
280
|
+
}: {
|
|
281
|
+
contentPart: t.ToolResultContent;
|
|
282
|
+
toolCall: ToolCall;
|
|
283
|
+
metadata?: Record<string, unknown>;
|
|
284
|
+
graph: Graph;
|
|
285
|
+
}): void {
|
|
286
|
+
if (!Array.isArray(contentPart.content)) {
|
|
287
|
+
console.warn(
|
|
288
|
+
`Expected content to be an array, got ${typeof contentPart.content}`
|
|
289
|
+
);
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (!isAnthropicWebSearchResult(contentPart.content[0])) {
|
|
294
|
+
console.warn(
|
|
295
|
+
`Expected content to be an Anthropic web search result, got ${JSON.stringify(
|
|
296
|
+
contentPart.content
|
|
297
|
+
)}`
|
|
298
|
+
);
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const turn = graph.invokedToolIds?.size ?? 0;
|
|
303
|
+
const searchResultData = coerceAnthropicSearchResults({
|
|
304
|
+
turn,
|
|
305
|
+
results: contentPart.content as AnthropicWebSearchResultBlockParam[],
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
const name = toolCall.name;
|
|
309
|
+
const input = toolCall.args;
|
|
310
|
+
const artifact = {
|
|
311
|
+
[Constants.WEB_SEARCH]: searchResultData,
|
|
312
|
+
};
|
|
313
|
+
const { output: formattedOutput } = formatResultsForLLM(
|
|
314
|
+
turn,
|
|
315
|
+
searchResultData
|
|
316
|
+
);
|
|
317
|
+
const output = new ToolMessage({
|
|
318
|
+
name,
|
|
319
|
+
artifact,
|
|
320
|
+
content: formattedOutput,
|
|
321
|
+
tool_call_id: toolCall.id!,
|
|
322
|
+
});
|
|
323
|
+
const toolEndData: t.ToolEndData = {
|
|
324
|
+
input,
|
|
325
|
+
output,
|
|
326
|
+
};
|
|
327
|
+
graph.handlerRegistry
|
|
328
|
+
?.getHandler(GraphEvents.TOOL_END)
|
|
329
|
+
?.handle(GraphEvents.TOOL_END, toolEndData, metadata, graph);
|
|
330
|
+
|
|
331
|
+
if (graph.invokedToolIds == null) {
|
|
332
|
+
graph.invokedToolIds = new Set<string>();
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
graph.invokedToolIds.add(toolCall.id!);
|
|
336
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AnthropicTextBlockParam,
|
|
3
|
+
AnthropicWebSearchResultBlockParam,
|
|
4
|
+
} from '@/llm/anthropic/types';
|
|
5
|
+
import type { SearchResultData, ProcessedOrganic } from './types';
|
|
6
|
+
import { getAttribution } from './utils';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Coerces Anthropic web search results to the SearchResultData format
|
|
10
|
+
* @param results - Array of Anthropic web search results
|
|
11
|
+
* @param turn - The turn number to associate with these results
|
|
12
|
+
* @returns SearchResultData with minimal ProcessedOrganic items
|
|
13
|
+
*/
|
|
14
|
+
export function coerceAnthropicSearchResults({
|
|
15
|
+
results,
|
|
16
|
+
turn = 0,
|
|
17
|
+
}: {
|
|
18
|
+
results: (AnthropicTextBlockParam | AnthropicWebSearchResultBlockParam)[];
|
|
19
|
+
turn?: number;
|
|
20
|
+
}): SearchResultData {
|
|
21
|
+
const organic: ProcessedOrganic[] = results
|
|
22
|
+
.filter((result) => result.type === 'web_search_result')
|
|
23
|
+
.map((result, index) => ({
|
|
24
|
+
link: result.url,
|
|
25
|
+
position: index + 1,
|
|
26
|
+
title: result.title,
|
|
27
|
+
date: result.page_age ?? undefined,
|
|
28
|
+
attribution: getAttribution(result.url),
|
|
29
|
+
}));
|
|
30
|
+
|
|
31
|
+
return {
|
|
32
|
+
turn,
|
|
33
|
+
organic,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Helper function to check if an object is an Anthropic web search result
|
|
39
|
+
*/
|
|
40
|
+
export function isAnthropicWebSearchResult(
|
|
41
|
+
obj: unknown
|
|
42
|
+
): obj is AnthropicWebSearchResultBlockParam {
|
|
43
|
+
return (
|
|
44
|
+
typeof obj === 'object' &&
|
|
45
|
+
obj !== null &&
|
|
46
|
+
'type' in obj &&
|
|
47
|
+
obj.type === 'web_search_result' &&
|
|
48
|
+
'url' in obj &&
|
|
49
|
+
typeof (obj as Record<string, unknown>).url === 'string'
|
|
50
|
+
);
|
|
51
|
+
}
|
|
@@ -233,6 +233,7 @@ const createSearXNGAPI = (
|
|
|
233
233
|
const getSources = async ({
|
|
234
234
|
query,
|
|
235
235
|
numResults = 8,
|
|
236
|
+
safeSearch,
|
|
236
237
|
type,
|
|
237
238
|
}: t.GetSourcesParams): Promise<t.SearchResult> => {
|
|
238
239
|
if (!query.trim()) {
|
|
@@ -267,7 +268,7 @@ const createSearXNGAPI = (
|
|
|
267
268
|
pageno: 1,
|
|
268
269
|
categories: category,
|
|
269
270
|
language: 'all',
|
|
270
|
-
safesearch:
|
|
271
|
+
safesearch: safeSearch,
|
|
271
272
|
engines: 'google,bing,duckduckgo',
|
|
272
273
|
};
|
|
273
274
|
|
package/src/types/llm.ts
CHANGED
|
@@ -91,9 +91,11 @@ export type ClientOptions =
|
|
|
91
91
|
| DeepSeekClientOptions
|
|
92
92
|
| XAIClientOptions;
|
|
93
93
|
|
|
94
|
-
export type
|
|
94
|
+
export type SharedLLMConfig = {
|
|
95
95
|
provider: Providers;
|
|
96
|
-
}
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
export type LLMConfig = SharedLLMConfig & ClientOptions;
|
|
97
99
|
|
|
98
100
|
export type ProviderOptionsMap = {
|
|
99
101
|
[Providers.AZURE]: AzureClientOptions;
|
package/src/types/stream.ts
CHANGED
|
@@ -8,6 +8,7 @@ import type {
|
|
|
8
8
|
} from '@langchain/core/messages';
|
|
9
9
|
import type { ToolCall, ToolCallChunk } from '@langchain/core/messages/tool';
|
|
10
10
|
import type { LLMResult, Generation } from '@langchain/core/outputs';
|
|
11
|
+
import type { AnthropicContentBlock } from '@/llm/anthropic/types';
|
|
11
12
|
import type { ToolEndEvent } from '@/types/tools';
|
|
12
13
|
import { StepTypes, ContentTypes, GraphEvents } from '@/common/enum';
|
|
13
14
|
|
|
@@ -299,7 +300,20 @@ export type ToolCallContent = {
|
|
|
299
300
|
tool_call?: ToolCallPart;
|
|
300
301
|
};
|
|
301
302
|
|
|
303
|
+
export type ToolResultContent = {
|
|
304
|
+
content:
|
|
305
|
+
| string
|
|
306
|
+
| Record<string, unknown>
|
|
307
|
+
| Array<string | Record<string, unknown>>
|
|
308
|
+
| AnthropicContentBlock[];
|
|
309
|
+
type: 'tool_result' | 'web_search_result' | 'web_search_tool_result';
|
|
310
|
+
tool_use_id?: string;
|
|
311
|
+
input?: string | Record<string, unknown>;
|
|
312
|
+
index?: number;
|
|
313
|
+
};
|
|
314
|
+
|
|
302
315
|
export type MessageContentComplex = (
|
|
316
|
+
| ToolResultContent
|
|
303
317
|
| ThinkingContentText
|
|
304
318
|
| AgentUpdate
|
|
305
319
|
| ToolCallContent
|