@@ -162,6 +162,108 @@ void testGenerateContentWithFunctionCall() {
162162 assertThat (functionCall .args ().get ()).containsEntry ("city" , "Paris" );
163163 }
164164
165+ @ Test
166+ @ DisplayName ("Should handle multiple function calls in LLM responses" )
167+ void testGenerateContentWithMultipleFunctionCall () {
168+ // Given
169+ // Create mock FunctionTools
170+ final FunctionTool weatherTool = mock (FunctionTool .class );
171+ when (weatherTool .name ()).thenReturn ("getWeather" );
172+ when (weatherTool .description ()).thenReturn ("Get weather for a city" );
173+
174+ final FunctionTool timeTool = mock (FunctionTool .class );
175+ when (timeTool .name ()).thenReturn ("getCurrentTime" );
176+ when (timeTool .description ()).thenReturn ("Get current time for a city" );
177+
178+ // Create mock FunctionDeclarations
179+ final FunctionDeclaration weatherDeclaration = mock (FunctionDeclaration .class );
180+ final FunctionDeclaration timeDeclaration = mock (FunctionDeclaration .class );
181+ when (weatherTool .declaration ()).thenReturn (Optional .of (weatherDeclaration ));
182+ when (timeTool .declaration ()).thenReturn (Optional .of (timeDeclaration ));
183+
184+ // Create mock Schemas
185+ final Schema weatherSchema = mock (Schema .class );
186+ final Schema timeSchema = mock (Schema .class );
187+ when (weatherDeclaration .parameters ()).thenReturn (Optional .of (weatherSchema ));
188+ when (timeDeclaration .parameters ()).thenReturn (Optional .of (timeSchema ));
189+
190+ // Create mock Types
191+ final Type weatherType = mock (Type .class );
192+ final Type timeType = mock (Type .class );
193+ when (weatherSchema .type ()).thenReturn (Optional .of (weatherType ));
194+ when (timeSchema .type ()).thenReturn (Optional .of (timeType ));
195+ when (weatherType .knownEnum ()).thenReturn (Type .Known .OBJECT );
196+ when (timeType .knownEnum ()).thenReturn (Type .Known .OBJECT );
197+
198+ // Create mock schema properties
199+ when (weatherSchema .properties ()).thenReturn (Optional .of (Map .of ("city" , weatherSchema )));
200+ when (timeSchema .properties ()).thenReturn (Optional .of (Map .of ("city" , timeSchema )));
201+ when (weatherSchema .required ()).thenReturn (Optional .of (List .of ("city" )));
202+ when (timeSchema .required ()).thenReturn (Optional .of (List .of ("city" )));
203+
204+ // Create LlmRequest
205+ final LlmRequest llmRequest =
206+ LlmRequest .builder ()
207+ .contents (
208+ List .of (
209+ Content .fromParts (
210+ Part .fromText ("What's the weather in Paris and the current time?" ))))
211+ .build ();
212+
213+ // Mock multiple tool execution requests in the AI response
214+ final ToolExecutionRequest weatherRequest =
215+ ToolExecutionRequest .builder ()
216+ .id ("123" )
217+ .name ("getWeather" )
218+ .arguments ("{\" city\" :\" Paris\" }" )
219+ .build ();
220+
221+ final ToolExecutionRequest timeRequest =
222+ ToolExecutionRequest .builder ()
223+ .id ("456" )
224+ .name ("getCurrentTime" )
225+ .arguments ("{\" city\" :\" Paris\" }" )
226+ .build ();
227+
228+ final AiMessage aiMessage =
229+ AiMessage .builder ()
230+ .text ("" )
231+ .toolExecutionRequests (List .of (weatherRequest , timeRequest ))
232+ .build ();
233+
234+ final ChatResponse chatResponse = mock (ChatResponse .class );
235+ when (chatResponse .aiMessage ()).thenReturn (aiMessage );
236+ when (chatModel .chat (any (ChatRequest .class ))).thenReturn (chatResponse );
237+
238+ // When
239+ final LlmResponse response = langChain4j .generateContent (llmRequest , false ).blockingFirst ();
240+
241+ // Then
242+ assertThat (response ).isNotNull ();
243+ assertThat (response .content ()).isPresent ();
244+ assertThat (response .content ().get ().parts ()).isPresent ();
245+
246+ final List <Part > parts = response .content ().get ().parts ().orElseThrow ();
247+ assertThat (parts ).hasSize (2 );
248+
249+ // Verify first function call (getWeather)
250+ assertThat (parts .get (0 ).functionCall ()).isPresent ();
251+ final FunctionCall weatherCall = parts .get (0 ).functionCall ().orElseThrow ();
252+ assertThat (weatherCall .name ()).isEqualTo (Optional .of ("getWeather" ));
253+ assertThat (weatherCall .args ()).isPresent ();
254+ assertThat (weatherCall .args ().get ()).containsEntry ("city" , "Paris" );
255+
256+ // Verify second function call (getCurrentTime)
257+ assertThat (parts .get (1 ).functionCall ()).isPresent ();
258+ final FunctionCall timeCall = parts .get (1 ).functionCall ().orElseThrow ();
259+ assertThat (timeCall .name ()).isEqualTo (Optional .of ("getCurrentTime" ));
260+ assertThat (timeCall .args ()).isPresent ();
261+ assertThat (timeCall .args ().get ()).containsEntry ("city" , "Paris" );
262+
263+ // Verify the ChatModel was called
264+ verify (chatModel ).chat (any (ChatRequest .class ));
265+ }
266+
165267 @ Test
166268 @ DisplayName ("Should handle streaming responses correctly" )
167269 void testGenerateContentWithStreamingChatModel () {
0 commit comments