@@ -3,9 +3,19 @@ import type { Endpoint } from "../endpoints";
3
3
import { env } from "$env/dynamic/private" ;
4
4
import type { TextGenerationStreamOutput } from "@huggingface/inference" ;
5
5
import { createImageProcessorOptionsValidator } from "../images" ;
6
- import { endpointMessagesToAnthropicMessages } from "./utils" ;
6
+ import { endpointMessagesToAnthropicMessages , addToolResults } from "./utils" ;
7
7
import { createDocumentProcessorOptionsValidator } from "../document" ;
8
+ import type {
9
+ Tool ,
10
+ ToolCall ,
11
+ ToolInput ,
12
+ ToolInputFile ,
13
+ ToolInputFixed ,
14
+ ToolInputOptional ,
15
+ } from "$lib/types/Tool" ;
16
+ import type Anthropic from "@anthropic-ai/sdk" ;
8
17
import type { MessageParam } from "@anthropic-ai/sdk/resources/messages.mjs" ;
18
+ import directlyAnswer from "$lib/server/tools/directlyAnswer" ;
9
19
10
20
export const endpointAnthropicParametersSchema = z . object ( {
11
21
weight : z . number ( ) . int ( ) . positive ( ) . default ( 1 ) ,
@@ -52,23 +62,41 @@ export async function endpointAnthropic(
52
62
defaultQuery,
53
63
} ) ;
54
64
55
- return async ( { messages, preprompt, generateSettings } ) => {
65
+ return async ( {
66
+ messages,
67
+ preprompt,
68
+ generateSettings,
69
+ conversationId,
70
+ tools = [ ] ,
71
+ toolResults = [ ] ,
72
+ } ) => {
56
73
let system = preprompt ;
57
74
if ( messages ?. [ 0 ] ?. from === "system" ) {
58
75
system = messages [ 0 ] . content ;
59
76
}
60
77
61
78
let tokenId = 0 ;
79
+ if ( tools . length === 0 && toolResults . length > 0 ) {
80
+ const toolNames = new Set ( toolResults . map ( ( tool ) => tool . call . name ) ) ;
81
+ tools = Array . from ( toolNames ) . map ( ( name ) => ( {
82
+ name,
83
+ description : "" ,
84
+ inputs : [ ] ,
85
+ } ) ) as unknown as Tool [ ] ;
86
+ }
62
87
63
88
const parameters = { ...model . parameters , ...generateSettings } ;
64
89
65
90
return ( async function * ( ) {
66
91
const stream = anthropic . messages . stream ( {
67
92
model : model . id ?? model . name ,
68
- messages : ( await endpointMessagesToAnthropicMessages (
69
- messages ,
70
- multimodal
71
- ) ) as MessageParam [ ] ,
93
+ tools : createAnthropicTools ( tools ) ,
94
+ tool_choice :
95
+ tools . length > 0 ? { type : "auto" , disable_parallel_tool_use : false } : undefined ,
96
+ messages : addToolResults (
97
+ await endpointMessagesToAnthropicMessages ( messages , multimodal , conversationId ) ,
98
+ toolResults
99
+ ) as MessageParam [ ] ,
72
100
max_tokens : parameters ?. max_new_tokens ,
73
101
temperature : parameters ?. temperature ,
74
102
top_p : parameters ?. top_p ,
@@ -79,21 +107,40 @@ export async function endpointAnthropic(
79
107
while ( true ) {
80
108
const result = await Promise . race ( [ stream . emitted ( "text" ) , stream . emitted ( "end" ) ] ) ;
81
109
82
- // Stream end
83
110
if ( result === undefined ) {
84
- yield {
85
- token : {
86
- id : tokenId ++ ,
87
- text : "" ,
88
- logprob : 0 ,
89
- special : true ,
90
- } ,
91
- generated_text : await stream . finalText ( ) ,
92
- details : null ,
93
- } satisfies TextGenerationStreamOutput ;
111
+ if ( "tool_use" === stream . receivedMessages [ 0 ] . stop_reason ) {
112
+ // this should really create a new "Assistant" message with the tool id in it.
113
+ const toolCalls : ToolCall [ ] = stream . receivedMessages [ 0 ] . content
114
+ . filter (
115
+ ( block ) : block is Anthropic . Messages . ContentBlock & { type : "tool_use" } =>
116
+ block . type === "tool_use"
117
+ )
118
+ . map ( ( block ) => ( {
119
+ name : block . name ,
120
+ parameters : block . input as Record < string , string | number | boolean > ,
121
+ id : block . id ,
122
+ } ) ) ;
123
+
124
+ yield {
125
+ token : { id : tokenId , text : "" , logprob : 0 , special : false , toolCalls } ,
126
+ generated_text : null ,
127
+ details : null ,
128
+ } ;
129
+ } else {
130
+ yield {
131
+ token : {
132
+ id : tokenId ++ ,
133
+ text : "" ,
134
+ logprob : 0 ,
135
+ special : true ,
136
+ } ,
137
+ generated_text : await stream . finalText ( ) ,
138
+ details : null ,
139
+ } satisfies TextGenerationStreamOutput ;
140
+ }
141
+
94
142
return ;
95
143
}
96
-
97
144
// Text delta
98
145
yield {
99
146
token : {
@@ -109,3 +156,66 @@ export async function endpointAnthropic(
109
156
} ) ( ) ;
110
157
} ;
111
158
}
159
+
160
+ function createAnthropicTools ( tools : Tool [ ] ) : Anthropic . Messages . Tool [ ] {
161
+ return tools
162
+ . filter ( ( tool ) => tool . name !== directlyAnswer . name )
163
+ . map ( ( tool ) => {
164
+ const properties = tool . inputs . reduce ( ( acc , input ) => {
165
+ acc [ input . name ] = convertToolInputToJSONSchema ( input ) ;
166
+ return acc ;
167
+ } , { } as Record < string , unknown > ) ;
168
+
169
+ const required = tool . inputs
170
+ . filter ( ( input ) => input . paramType === "required" )
171
+ . map ( ( input ) => input . name ) ;
172
+
173
+ return {
174
+ name : tool . name ,
175
+ description : tool . description ,
176
+ input_schema : {
177
+ type : "object" ,
178
+ properties,
179
+ required : required . length > 0 ? required : undefined ,
180
+ } ,
181
+ } ;
182
+ } ) ;
183
+ }
184
+
185
+ function convertToolInputToJSONSchema ( input : ToolInput ) : Record < string , unknown > {
186
+ const baseSchema : Record < string , unknown > = { } ;
187
+ if ( "description" in input ) {
188
+ baseSchema [ "description" ] = input . description || "" ;
189
+ }
190
+ switch ( input . paramType ) {
191
+ case "optional" :
192
+ baseSchema [ "default" ] = ( input as ToolInputOptional ) . default ;
193
+ break ;
194
+ case "fixed" :
195
+ baseSchema [ "const" ] = ( input as ToolInputFixed ) . value ;
196
+ break ;
197
+ }
198
+
199
+ if ( input . type === "file" ) {
200
+ baseSchema [ "type" ] = "string" ;
201
+ baseSchema [ "format" ] = "binary" ;
202
+ baseSchema [ "mimeTypes" ] = ( input as ToolInputFile ) . mimeTypes ;
203
+ } else {
204
+ switch ( input . type ) {
205
+ case "str" :
206
+ baseSchema [ "type" ] = "string" ;
207
+ break ;
208
+ case "int" :
209
+ baseSchema [ "type" ] = "integer" ;
210
+ break ;
211
+ case "float" :
212
+ baseSchema [ "type" ] = "number" ;
213
+ break ;
214
+ case "bool" :
215
+ baseSchema [ "type" ] = "boolean" ;
216
+ break ;
217
+ }
218
+ }
219
+
220
+ return baseSchema ;
221
+ }
0 commit comments