@@ -155,4 +155,272 @@ public String call(String toolInput) {
155
155
assertThatNoException ().isThrownBy (() -> managerWithCallback .executeToolCalls (prompt , chatResponse ));
156
156
}
157
157
158
+ @ Test
159
+ void shouldHandleMultipleToolCallsInSingleResponse () {
160
+ // Create mock tool callbacks
161
+ ToolCallback toolCallback1 = new ToolCallback () {
162
+ @ Override
163
+ public ToolDefinition getToolDefinition () {
164
+ return DefaultToolDefinition .builder ()
165
+ .name ("tool1" )
166
+ .description ("First tool" )
167
+ .inputSchema ("{\" type\" : \" object\" , \" properties\" : {\" param\" : {\" type\" : \" string\" }}}" )
168
+ .build ();
169
+ }
170
+
171
+ @ Override
172
+ public ToolMetadata getToolMetadata () {
173
+ return ToolMetadata .builder ().build ();
174
+ }
175
+
176
+ @ Override
177
+ public String call (String toolInput ) {
178
+ return "{\" result\" : \" tool1_success\" }" ;
179
+ }
180
+ };
181
+
182
+ ToolCallback toolCallback2 = new ToolCallback () {
183
+ @ Override
184
+ public ToolDefinition getToolDefinition () {
185
+ return DefaultToolDefinition .builder ()
186
+ .name ("tool2" )
187
+ .description ("Second tool" )
188
+ .inputSchema ("{\" type\" : \" object\" , \" properties\" : {\" value\" : {\" type\" : \" number\" }}}" )
189
+ .build ();
190
+ }
191
+
192
+ @ Override
193
+ public ToolMetadata getToolMetadata () {
194
+ return ToolMetadata .builder ().build ();
195
+ }
196
+
197
+ @ Override
198
+ public String call (String toolInput ) {
199
+ return "{\" result\" : \" tool2_success\" }" ;
200
+ }
201
+ };
202
+
203
+ // Create multiple ToolCalls
204
+ AssistantMessage .ToolCall toolCall1 = new AssistantMessage .ToolCall ("1" , "function" , "tool1" ,
205
+ "{\" param\" : \" test\" }" );
206
+ AssistantMessage .ToolCall toolCall2 = new AssistantMessage .ToolCall ("2" , "function" , "tool2" ,
207
+ "{\" value\" : 42}" );
208
+
209
+ // Create ChatResponse with multiple tool calls
210
+ AssistantMessage assistantMessage = AssistantMessage .builder ()
211
+ .content ("" )
212
+ .properties (Map .of ())
213
+ .toolCalls (List .of (toolCall1 , toolCall2 ))
214
+ .build ();
215
+ Generation generation = new Generation (assistantMessage );
216
+ ChatResponse chatResponse = new ChatResponse (List .of (generation ));
217
+
218
+ Prompt prompt = new Prompt (List .of (new UserMessage ("test multiple tools" )));
219
+
220
+ DefaultToolCallingManager manager = DefaultToolCallingManager .builder ()
221
+ .observationRegistry (ObservationRegistry .NOOP )
222
+ .toolCallbackResolver (toolName -> {
223
+ if ("tool1" .equals (toolName )) {
224
+ return toolCallback1 ;
225
+ }
226
+ if ("tool2" .equals (toolName )) {
227
+ return toolCallback2 ;
228
+ }
229
+ return null ;
230
+ })
231
+ .build ();
232
+
233
+ assertThatNoException ().isThrownBy (() -> manager .executeToolCalls (prompt , chatResponse ));
234
+ }
235
+
236
+ @ Test
237
+ void shouldHandleToolCallWithComplexJsonArguments () {
238
+ ToolCallback complexToolCallback = new ToolCallback () {
239
+ @ Override
240
+ public ToolDefinition getToolDefinition () {
241
+ return DefaultToolDefinition .builder ()
242
+ .name ("complexTool" )
243
+ .description ("A tool with complex JSON input" )
244
+ .inputSchema ("{\" type\" : \" object\" , \" properties\" : {\" nested\" : {\" type\" : \" object\" }}}" )
245
+ .build ();
246
+ }
247
+
248
+ @ Override
249
+ public ToolMetadata getToolMetadata () {
250
+ return ToolMetadata .builder ().build ();
251
+ }
252
+
253
+ @ Override
254
+ public String call (String toolInput ) {
255
+ assertThat (toolInput ).contains ("nested" );
256
+ assertThat (toolInput ).contains ("array" );
257
+ return "{\" result\" : \" processed\" }" ;
258
+ }
259
+ };
260
+
261
+ String complexJson = "{\" nested\" : {\" level1\" : {\" level2\" : \" value\" }}, \" array\" : [1, 2, 3]}" ;
262
+ AssistantMessage .ToolCall toolCall = new AssistantMessage .ToolCall ("1" , "function" , "complexTool" , complexJson );
263
+
264
+ AssistantMessage assistantMessage = AssistantMessage .builder ()
265
+ .content ("" )
266
+ .properties (Map .of ())
267
+ .toolCalls (List .of (toolCall ))
268
+ .build ();
269
+ Generation generation = new Generation (assistantMessage );
270
+ ChatResponse chatResponse = new ChatResponse (List .of (generation ));
271
+
272
+ Prompt prompt = new Prompt (List .of (new UserMessage ("test complex json" )));
273
+
274
+ DefaultToolCallingManager manager = DefaultToolCallingManager .builder ()
275
+ .observationRegistry (ObservationRegistry .NOOP )
276
+ .toolCallbackResolver (toolName -> "complexTool" .equals (toolName ) ? complexToolCallback : null )
277
+ .build ();
278
+
279
+ assertThatNoException ().isThrownBy (() -> manager .executeToolCalls (prompt , chatResponse ));
280
+ }
281
+
282
+ @ Test
283
+ void shouldHandleToolCallWithMalformedJson () {
284
+ ToolCallback toolCallback = new ToolCallback () {
285
+ @ Override
286
+ public ToolDefinition getToolDefinition () {
287
+ return DefaultToolDefinition .builder ()
288
+ .name ("testTool" )
289
+ .description ("Test tool" )
290
+ .inputSchema ("{}" )
291
+ .build ();
292
+ }
293
+
294
+ @ Override
295
+ public ToolMetadata getToolMetadata () {
296
+ return ToolMetadata .builder ().build ();
297
+ }
298
+
299
+ @ Override
300
+ public String call (String toolInput ) {
301
+ // Should still receive some input even if malformed
302
+ assertThat (toolInput ).isNotNull ();
303
+ return "{\" result\" : \" handled\" }" ;
304
+ }
305
+ };
306
+
307
+ // Malformed JSON as tool arguments
308
+ AssistantMessage .ToolCall toolCall = new AssistantMessage .ToolCall ("1" , "function" , "testTool" ,
309
+ "{invalid json}" );
310
+
311
+ AssistantMessage assistantMessage = AssistantMessage .builder ()
312
+ .content ("" )
313
+ .properties (Map .of ())
314
+ .toolCalls (List .of (toolCall ))
315
+ .build ();
316
+ Generation generation = new Generation (assistantMessage );
317
+ ChatResponse chatResponse = new ChatResponse (List .of (generation ));
318
+
319
+ Prompt prompt = new Prompt (List .of (new UserMessage ("test malformed json" )));
320
+
321
+ DefaultToolCallingManager manager = DefaultToolCallingManager .builder ()
322
+ .observationRegistry (ObservationRegistry .NOOP )
323
+ .toolCallbackResolver (toolName -> "testTool" .equals (toolName ) ? toolCallback : null )
324
+ .build ();
325
+
326
+ assertThatNoException ().isThrownBy (() -> manager .executeToolCalls (prompt , chatResponse ));
327
+ }
328
+
329
+ @ Test
330
+ void shouldHandleToolCallReturningNull () {
331
+ ToolCallback toolCallback = new ToolCallback () {
332
+ @ Override
333
+ public ToolDefinition getToolDefinition () {
334
+ return DefaultToolDefinition .builder ()
335
+ .name ("nullReturningTool" )
336
+ .description ("Tool that returns null" )
337
+ .inputSchema ("{}" )
338
+ .build ();
339
+ }
340
+
341
+ @ Override
342
+ public ToolMetadata getToolMetadata () {
343
+ return ToolMetadata .builder ().build ();
344
+ }
345
+
346
+ @ Override
347
+ public String call (String toolInput ) {
348
+ return null ; // Return null
349
+ }
350
+ };
351
+
352
+ AssistantMessage .ToolCall toolCall = new AssistantMessage .ToolCall ("1" , "function" , "nullReturningTool" , "{}" );
353
+
354
+ AssistantMessage assistantMessage = AssistantMessage .builder ()
355
+ .content ("" )
356
+ .properties (Map .of ())
357
+ .toolCalls (List .of (toolCall ))
358
+ .build ();
359
+ Generation generation = new Generation (assistantMessage );
360
+ ChatResponse chatResponse = new ChatResponse (List .of (generation ));
361
+
362
+ Prompt prompt = new Prompt (List .of (new UserMessage ("test null return" )));
363
+
364
+ DefaultToolCallingManager manager = DefaultToolCallingManager .builder ()
365
+ .observationRegistry (ObservationRegistry .NOOP )
366
+ .toolCallbackResolver (toolName -> "nullReturningTool" .equals (toolName ) ? toolCallback : null )
367
+ .build ();
368
+
369
+ assertThatNoException ().isThrownBy (() -> manager .executeToolCalls (prompt , chatResponse ));
370
+ }
371
+
372
+ @ Test
373
+ void shouldHandleMultipleGenerationsWithToolCalls () {
374
+ ToolCallback toolCallback = new ToolCallback () {
375
+ @ Override
376
+ public ToolDefinition getToolDefinition () {
377
+ return DefaultToolDefinition .builder ()
378
+ .name ("multiGenTool" )
379
+ .description ("Tool for multiple generations" )
380
+ .inputSchema ("{}" )
381
+ .build ();
382
+ }
383
+
384
+ @ Override
385
+ public ToolMetadata getToolMetadata () {
386
+ return ToolMetadata .builder ().build ();
387
+ }
388
+
389
+ @ Override
390
+ public String call (String toolInput ) {
391
+ return "{\" result\" : \" success\" }" ;
392
+ }
393
+ };
394
+
395
+ // Create multiple generations with tool calls
396
+ AssistantMessage .ToolCall toolCall1 = new AssistantMessage .ToolCall ("1" , "function" , "multiGenTool" , "{}" );
397
+ AssistantMessage .ToolCall toolCall2 = new AssistantMessage .ToolCall ("2" , "function" , "multiGenTool" , "{}" );
398
+
399
+ AssistantMessage assistantMessage1 = AssistantMessage .builder ()
400
+ .content ("" )
401
+ .properties (Map .of ())
402
+ .toolCalls (List .of (toolCall1 ))
403
+ .build ();
404
+
405
+ AssistantMessage assistantMessage2 = AssistantMessage .builder ()
406
+ .content ("" )
407
+ .properties (Map .of ())
408
+ .toolCalls (List .of (toolCall2 ))
409
+ .build ();
410
+
411
+ Generation generation1 = new Generation (assistantMessage1 );
412
+ Generation generation2 = new Generation (assistantMessage2 );
413
+
414
+ ChatResponse chatResponse = new ChatResponse (List .of (generation1 , generation2 ));
415
+
416
+ Prompt prompt = new Prompt (List .of (new UserMessage ("test multiple generations" )));
417
+
418
+ DefaultToolCallingManager manager = DefaultToolCallingManager .builder ()
419
+ .observationRegistry (ObservationRegistry .NOOP )
420
+ .toolCallbackResolver (toolName -> "multiGenTool" .equals (toolName ) ? toolCallback : null )
421
+ .build ();
422
+
423
+ assertThatNoException ().isThrownBy (() -> manager .executeToolCalls (prompt , chatResponse ));
424
+ }
425
+
158
426
}
0 commit comments