@@ -69,6 +69,8 @@ def __init__(self, model: str = DEFAULT_MODEL, host: str = DEFAULT_OLLAMA_HOST):
6969 self .show_tool_execution = True # By default, show tool execution displays
7070 # Metrics display settings
7171 self .show_metrics = False # By default, don't show metrics after each query
72+ # Agent mode settings
73+ self .loop_limit = 3 # Maximum follow-up tool loops per query
7274 self .default_configuration_status = False # Track if default configuration was loaded successfully
7375
7476 # Store server connection parameters for reloading
@@ -259,7 +261,8 @@ async def process_query(self, query: str) -> str:
259261 }
260262
261263 # Add thinking parameter if thinking mode is enabled and model supports it
262- if await self .supports_thinking_mode ():
264+ supports_thinking = await self .supports_thinking_mode ()
265+ if supports_thinking :
263266 chat_params ["think" ] = self .thinking_mode
264267
265268 # Initial Ollama API call with the query and available tools
@@ -286,9 +289,26 @@ async def process_query(self, query: str) -> str:
286289 # Update actual token count from metrics if available
287290 if metrics and metrics .get ('eval_count' ):
288291 self .actual_token_count += metrics ['eval_count' ]
289- # Check if there are any tool calls in the response
290- if len (tool_calls ) > 0 and self .tool_manager .get_enabled_tool_objects ():
291- for tool in tool_calls :
292+
293+ enabled_tools = self .tool_manager .get_enabled_tool_objects ()
294+
295+ loop_count = 0
296+ pending_tool_calls = tool_calls
297+
298+ # Keep looping while the model requests tools and we have capacity
299+ while pending_tool_calls and enabled_tools :
300+ if loop_count >= self .loop_limit :
301+ self .console .print (Panel (
302+ f"[yellow]Your current loop limit is set to [bold]{ self .loop_limit } [/bold] and has been reached. Skipping additional tool calls.[/yellow]\n "
303+ f"You will probably want to increase this limit if your model requires more tool interactions to complete tasks.\n "
304+ f"You can change the loop limit with the [bold cyan]loop-limit[/bold cyan] command." ,
305+ title = "[bold]Loop Limit Reached[/bold]" , border_style = "yellow" , expand = False
306+ ))
307+ break
308+
309+ loop_count += 1
310+
311+ for tool in pending_tool_calls :
292312 tool_name = tool .function .name
293313 tool_args = tool .function .arguments
294314
@@ -338,27 +358,39 @@ async def process_query(self, query: str) -> str:
338358 "model" : model ,
339359 "messages" : messages ,
340360 "stream" : True ,
361+ "tools" : available_tools ,
341362 "options" : model_options
342363 }
343364
344365 # Add thinking parameter if thinking mode is enabled and model supports it
345- if await self . supports_thinking_mode () :
366+ if supports_thinking :
346367 chat_params_followup ["think" ] = self .thinking_mode
347368
348369 stream = await self .ollama .chat (** chat_params_followup )
349370
350371 # Process the streaming response with thinking mode support
351- response_text , _ , followup_metrics = await self .streaming_manager .process_streaming_response (
372+ followup_response , pending_tool_calls , followup_metrics = await self .streaming_manager .process_streaming_response (
352373 stream ,
353374 thinking_mode = self .thinking_mode ,
354375 show_thinking = self .show_thinking ,
355376 show_metrics = self .show_metrics
356377 )
357378
379+ messages .append ({
380+ "role" : "assistant" ,
381+ "content" : followup_response ,
382+ "tool_calls" : pending_tool_calls
383+ })
384+
358385 # Update actual token count from followup metrics if available
359386 if followup_metrics and followup_metrics .get ('eval_count' ):
360387 self .actual_token_count += followup_metrics ['eval_count' ]
361388
389+ if followup_response :
390+ response_text = followup_response
391+
392+ enabled_tools = self .tool_manager .get_enabled_tool_objects ()
393+
362394 if not response_text :
363395 self .console .print ("[red]No content response received.[/red]" )
364396 response_text = ""
@@ -458,6 +490,10 @@ async def chat_loop(self):
458490 await self .toggle_show_thinking ()
459491 continue
460492
493+ if query .lower () in ['loop-limit' , 'll' ]:
494+ await self .set_loop_limit ()
495+ continue
496+
461497 if query .lower () in ['show-tool-execution' , 'ste' ]:
462498 self .toggle_show_tool_execution ()
463499 continue
@@ -565,6 +601,9 @@ def print_help(self):
565601 "• Type [bold]show-thinking[/bold] or [bold]st[/bold] to toggle thinking text visibility\n "
566602 "• Type [bold]show-metrics[/bold] or [bold]sm[/bold] to toggle performance metrics display\n \n "
567603
604+ "[bold cyan]Agent Mode:[/bold cyan] [bold magenta](New!)[/bold magenta]\n "
605+ "• Type [bold]loop-limit[/bold] or [bold]ll[/bold] to set the maximum tool loop iterations\n \n "
606+
568607 "[bold cyan]MCP Servers and Tools:[/bold cyan]\n "
569608 "• Type [bold]tools[/bold] or [bold]t[/bold] to configure tools\n "
570609 "• Type [bold]show-tool-execution[/bold] or [bold]ste[/bold] to toggle tool execution display\n "
@@ -671,6 +710,28 @@ def toggle_show_metrics(self):
671710 else :
672711 self .console .print ("[cyan]🔇 Performance metrics will be hidden for a cleaner output.[/cyan]" )
673712
713+ async def set_loop_limit (self ):
714+ """Configure the maximum number of follow-up tool loops per query."""
715+ user_input = await self .get_user_input (f"Loop limit (current: { self .loop_limit } )" )
716+
717+ if user_input is None :
718+ return
719+
720+ value = user_input .strip ()
721+
722+ if not value :
723+ self .console .print ("[yellow]Loop limit unchanged.[/yellow]" )
724+ return
725+
726+ try :
727+ new_limit = int (value )
728+ if new_limit < 1 :
729+ raise ValueError
730+ self .loop_limit = new_limit
731+ self .console .print (f"[green]🤖 Agent loop limit set to { self .loop_limit } ![/green]" )
732+ except ValueError :
733+ self .console .print ("[red]Invalid loop limit. Please enter a positive integer.[/red]" )
734+
674735 def clear_context (self ):
675736 """Clear conversation history and token count"""
676737 original_history_length = len (self .chat_history )
@@ -695,6 +756,7 @@ def display_context_stats(self):
695756 f"{ thinking_status } "
696757 f"Tool execution display: [{ 'green' if self .show_tool_execution else 'red' } ]{ 'Enabled' if self .show_tool_execution else 'Disabled' } [/{ 'green' if self .show_tool_execution else 'red' } ]\n "
697758 f"Performance metrics: [{ 'green' if self .show_metrics else 'red' } ]{ 'Enabled' if self .show_metrics else 'Disabled' } [/{ 'green' if self .show_metrics else 'red' } ]\n "
759+ f"Agent loop limit: [cyan]{ self .loop_limit } [/cyan]\n "
698760 f"Human-in-the-Loop confirmations: [{ 'green' if self .hil_manager .is_enabled () else 'red' } ]{ 'Enabled' if self .hil_manager .is_enabled () else 'Disabled' } [/{ 'green' if self .hil_manager .is_enabled () else 'red' } ]\n "
699761 f"Conversation entries: { history_count } \n "
700762 f"Total tokens generated: { self .actual_token_count :,} " ,
@@ -730,6 +792,9 @@ def save_configuration(self, config_name=None):
730792 "thinkingMode" : self .thinking_mode ,
731793 "showThinking" : self .show_thinking
732794 },
795+ "agentSettings" : {
796+ "loopLimit" : self .loop_limit
797+ },
733798 "modelConfig" : self .model_config_manager .get_config (),
734799 "displaySettings" : {
735800 "showToolExecution" : self .show_tool_execution ,
@@ -787,6 +852,14 @@ def load_configuration(self, config_name=None):
787852 if "showThinking" in config_data ["modelSettings" ]:
788853 self .show_thinking = config_data ["modelSettings" ]["showThinking" ]
789854
855+ if "agentSettings" in config_data :
856+ if "loopLimit" in config_data ["agentSettings" ]:
857+ try :
858+ loop_limit = int (config_data ["agentSettings" ]["loopLimit" ])
859+ self .loop_limit = max (1 , loop_limit )
860+ except (TypeError , ValueError ):
861+ pass
862+
790863 # Load model configuration if specified
791864 if "modelConfig" in config_data :
792865 self .model_config_manager .set_config (config_data ["modelConfig" ])
@@ -833,6 +906,17 @@ def reset_configuration(self):
833906 # Default show thinking to True if not specified
834907 self .show_thinking = True
835908
909+ if "agentSettings" in config_data :
910+ if "loopLimit" in config_data ["agentSettings" ]:
911+ try :
912+ self .loop_limit = max (1 , int (config_data ["agentSettings" ]["loopLimit" ]))
913+ except (TypeError , ValueError ):
914+ self .loop_limit = 3
915+ else :
916+ self .loop_limit = 3
917+ else :
918+ self .loop_limit = 3
919+
836920 # Reset display settings from the default configuration
837921 if "displaySettings" in config_data :
838922 if "showToolExecution" in config_data ["displaySettings" ]:
0 commit comments