@@ -162,6 +162,102 @@ 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 = LlmRequest .builder ()
206+ .contents (List .of (Content .fromParts (Part .fromText ("What's the weather in Paris and the current time?" ))))
207+ .build ();
208+
209+ // Mock multiple tool execution requests in the AI response
210+ final ToolExecutionRequest weatherRequest = ToolExecutionRequest .builder ()
211+ .id ("123" )
212+ .name ("getWeather" )
213+ .arguments ("{\" city\" :\" Paris\" }" )
214+ .build ();
215+
216+ final ToolExecutionRequest timeRequest = ToolExecutionRequest .builder ()
217+ .id ("456" )
218+ .name ("getCurrentTime" )
219+ .arguments ("{\" city\" :\" Paris\" }" )
220+ .build ();
221+
222+ final AiMessage aiMessage = AiMessage .builder ()
223+ .text ("" )
224+ .toolExecutionRequests (List .of (weatherRequest , timeRequest ))
225+ .build ();
226+
227+ final ChatResponse chatResponse = mock (ChatResponse .class );
228+ when (chatResponse .aiMessage ()).thenReturn (aiMessage );
229+ when (chatModel .chat (any (ChatRequest .class ))).thenReturn (chatResponse );
230+
231+ // When
232+ final LlmResponse response = langChain4j .generateContent (llmRequest , false ).blockingFirst ();
233+
234+ // Then
235+ assertThat (response ).isNotNull ();
236+ assertThat (response .content ()).isPresent ();
237+ assertThat (response .content ().get ().parts ()).isPresent ();
238+
239+ final List <Part > parts = response .content ().get ().parts ().orElseThrow ();
240+ assertThat (parts ).hasSize (2 );
241+
242+ // Verify first function call (getWeather)
243+ assertThat (parts .get (0 ).functionCall ()).isPresent ();
244+ final FunctionCall weatherCall = parts .get (0 ).functionCall ().orElseThrow ();
245+ assertThat (weatherCall .name ()).isEqualTo (Optional .of ("getWeather" ));
246+ assertThat (weatherCall .args ()).isPresent ();
247+ assertThat (weatherCall .args ().get ()).containsEntry ("city" , "Paris" );
248+
249+ // Verify second function call (getCurrentTime)
250+ assertThat (parts .get (1 ).functionCall ()).isPresent ();
251+ final FunctionCall timeCall = parts .get (1 ).functionCall ().orElseThrow ();
252+ assertThat (timeCall .name ()).isEqualTo (Optional .of ("getCurrentTime" ));
253+ assertThat (timeCall .args ()).isPresent ();
254+ assertThat (timeCall .args ().get ()).containsEntry ("city" , "Paris" );
255+
256+ // Verify the ChatModel was called
257+ verify (chatModel ).chat (any (ChatRequest .class ));
258+ }
259+
260+
165261 @ Test
166262 @ DisplayName ("Should handle streaming responses correctly" )
167263 void testGenerateContentWithStreamingChatModel () {
0 commit comments