@@ -323,8 +323,8 @@ def _generate_ollama_tool_summary(self, tool_results: List[Any], response_text:
323
323
if not (self ._is_ollama_provider () and tool_results ):
324
324
return None
325
325
326
- # If response is a final answer , no summary needed
327
- if self . _is_final_answer ( response_text , False , tool_results ) :
326
+ # If response is substantial , no summary needed
327
+ if response_text and len ( response_text . strip ()) > OLLAMA_MIN_RESPONSE_LENGTH :
328
328
return None
329
329
330
330
# Build tool summary efficiently
@@ -349,212 +349,6 @@ def _format_ollama_tool_result_message(self, function_name: str, tool_result: An
349
349
"content" : f"The { function_name } function returned: { tool_result_str } "
350
350
}
351
351
352
- def _analyze_tool_chain_context (self , tool_results : List [Any ], response_text : str ) -> dict :
353
- """
354
- Analyze tool execution context to understand the current state of the tool chain.
355
-
356
- Returns a context dictionary with confidence scores and patterns.
357
- """
358
- context = {
359
- 'tool_count' : len (tool_results ) if tool_results else 0 ,
360
- 'has_sequential_tools' : False ,
361
- 'has_final_computation' : False ,
362
- 'has_data_retrieval' : False ,
363
- 'confidence_score' : 0.0 ,
364
- 'tool_names' : [],
365
- 'tool_success_rate' : 1.0
366
- }
367
-
368
- if not tool_results :
369
- return context
370
-
371
- # Extract tool names and analyze patterns
372
- successful_tools = 0
373
- for result in tool_results :
374
- if isinstance (result , dict ) and 'function_name' in result :
375
- tool_name = result ['function_name' ].lower ()
376
- context ['tool_names' ].append (tool_name )
377
-
378
- # Check if tool execution was successful
379
- if 'error' not in result or not result .get ('error' ):
380
- successful_tools += 1
381
-
382
- # Analyze tool types for patterns
383
- if any (pattern in tool_name for pattern in ['get' , 'fetch' , 'search' , 'retrieve' , 'find' ]):
384
- context ['has_data_retrieval' ] = True
385
-
386
- if any (pattern in tool_name for pattern in ['calculate' , 'compute' , 'multiply' , 'add' , 'sum' , 'process' ]):
387
- context ['has_final_computation' ] = True
388
-
389
- # Calculate success rate
390
- if tool_results :
391
- context ['tool_success_rate' ] = successful_tools / len (tool_results )
392
-
393
- # Detect sequential tool usage patterns
394
- if len (set (context ['tool_names' ])) > 1 :
395
- context ['has_sequential_tools' ] = True
396
-
397
- # Calculate confidence score based on tool chain analysis
398
- confidence = 0.0
399
-
400
- # Sequential tools with final computation suggest completion
401
- if context ['has_sequential_tools' ] and context ['has_final_computation' ]:
402
- confidence += 0.4
403
-
404
- # Data retrieval followed by processing
405
- if context ['has_data_retrieval' ] and context ['has_final_computation' ]:
406
- confidence += 0.3
407
-
408
- # High success rate adds confidence
409
- confidence += context ['tool_success_rate' ] * 0.2
410
-
411
- # Multiple tools executed successfully
412
- if context ['tool_count' ] >= 2 and context ['tool_success_rate' ] > 0.8 :
413
- confidence += 0.1
414
-
415
- context ['confidence_score' ] = min (confidence , 1.0 )
416
- return context
417
-
418
- def _assess_response_quality (self , response_text : str , tool_results : List [Any ]) -> dict :
419
- """
420
- Assess the quality and completeness of a response based on content analysis.
421
-
422
- Returns quality metrics and confidence scores.
423
- """
424
- quality = {
425
- 'length' : len (response_text .strip ()) if response_text else 0 ,
426
- 'has_tool_references' : False ,
427
- 'has_conclusion_indicators' : False ,
428
- 'contains_results' : False ,
429
- 'quality_score' : 0.0
430
- }
431
-
432
- if not response_text :
433
- return quality
434
-
435
- response_lower = response_text .lower ().strip ()
436
-
437
- # Check for tool result integration
438
- if tool_results :
439
- tool_result_strings = [str (result ) for result in tool_results if result ]
440
- for tool_result in tool_result_strings :
441
- if tool_result and any (part in response_lower for part in str (tool_result ).lower ().split () if len (part ) > 3 ):
442
- quality ['has_tool_references' ] = True
443
- break
444
-
445
- # Check for conclusion indicators (dynamic pattern matching)
446
- conclusion_indicators = ['therefore' , 'so' , 'result' , 'answer' , 'conclusion' , 'final' , 'total' , 'summary' ]
447
- quality ['has_conclusion_indicators' ] = any (indicator in response_lower for indicator in conclusion_indicators )
448
-
449
- # Check if response contains actual results/data
450
- if any (char .isdigit () for char in response_text ) or '$' in response_text :
451
- quality ['contains_results' ] = True
452
-
453
- # Calculate quality score
454
- score = 0.0
455
-
456
- # Response length contributes to quality
457
- if quality ['length' ] > 20 :
458
- score += 0.2
459
- if quality ['length' ] > 50 :
460
- score += 0.1
461
- if quality ['length' ] > 100 :
462
- score += 0.1
463
-
464
- # Content quality indicators
465
- if quality ['has_tool_references' ]:
466
- score += 0.3
467
- if quality ['has_conclusion_indicators' ]:
468
- score += 0.2
469
- if quality ['contains_results' ]:
470
- score += 0.1
471
-
472
- quality ['quality_score' ] = min (score , 1.0 )
473
- return quality
474
-
475
- def _should_generate_tool_summary (self , tool_results : List [Any ], response_text : str , iteration_count : int ) -> bool :
476
- """
477
- Dynamically determine if a tool summary should be generated based on context analysis.
478
-
479
- This replaces the hardcoded iteration_count >= 5 check with intelligent analysis.
480
- """
481
- # Analyze tool execution context
482
- tool_context = self ._analyze_tool_chain_context (tool_results , response_text )
483
-
484
- # Assess response quality
485
- response_quality = self ._assess_response_quality (response_text , tool_results )
486
-
487
- # Decision logic based on dynamic analysis
488
- should_generate = False
489
-
490
- # High confidence that tool chain is complete
491
- if tool_context ['confidence_score' ] >= 0.7 :
492
- should_generate = True
493
-
494
- # Good tool chain with quality response
495
- elif tool_context ['confidence_score' ] >= 0.5 and response_quality ['quality_score' ] >= 0.6 :
496
- should_generate = True
497
-
498
- # Sequential tools with final computation and good response
499
- elif (tool_context ['has_sequential_tools' ] and
500
- tool_context ['has_final_computation' ] and
501
- response_quality ['quality_score' ] >= 0.4 ):
502
- should_generate = True
503
-
504
- # Safety fallback - prevent infinite loops (increased threshold)
505
- elif iteration_count >= 7 :
506
- should_generate = True
507
-
508
- return should_generate
509
-
510
- def _is_final_answer (self , response_text : str , has_tool_calls : bool , tool_results : List [Any ]) -> bool :
511
- """
512
- Determine if a response is a final answer or intermediate acknowledgment.
513
-
514
- This method provides intelligent differentiation using dynamic analysis
515
- instead of hardcoded patterns.
516
-
517
- Args:
518
- response_text: The text response from the LLM
519
- has_tool_calls: Whether the response contains tool calls
520
- tool_results: Results from executed tools
521
-
522
- Returns:
523
- True if this is a final answer, False if intermediate
524
- """
525
- if not response_text or not response_text .strip ():
526
- return False
527
-
528
- # If response contains tool calls, it's likely not a final answer
529
- if has_tool_calls :
530
- return False
531
-
532
- # For Ollama, use dynamic analysis instead of hardcoded patterns
533
- if self ._is_ollama_provider () and tool_results :
534
- # Analyze tool chain context
535
- tool_context = self ._analyze_tool_chain_context (tool_results , response_text )
536
-
537
- # Assess response quality
538
- response_quality = self ._assess_response_quality (response_text , tool_results )
539
-
540
- # Dynamic decision based on context and quality
541
- # If we have a complete tool chain with quality response, it's likely final
542
- if (tool_context ['confidence_score' ] >= 0.6 and
543
- response_quality ['quality_score' ] >= 0.5 ):
544
- return True
545
-
546
- # If response is very short and we have tool results, likely intermediate
547
- if response_quality ['length' ] < 20 :
548
- return False
549
-
550
- # If response doesn't reference tool results, likely intermediate
551
- if not response_quality ['has_tool_references' ] and response_quality ['length' ] < 80 :
552
- return False
553
-
554
- # For other providers, maintain existing behavior
555
- # Substantial content (>10 chars) is considered final
556
- return len (response_text .strip ()) > 10
557
-
558
352
def _process_stream_delta (self , delta , response_text : str , tool_calls : List [Dict ], formatted_tools : Optional [List ] = None ) -> tuple :
559
353
"""
560
354
Process a streaming delta chunk to extract content and tool calls.
@@ -1308,19 +1102,17 @@ def get_response(
1308
1102
continue
1309
1103
1310
1104
# Check if the LLM provided a final answer alongside the tool calls
1311
- # Use intelligent differentiation between intermediate and final responses
1312
- if self . _is_final_answer ( response_text , bool ( tool_calls ), tool_results ) :
1105
+ # If response_text contains substantive content, treat it as the final answer
1106
+ if response_text and response_text . strip () and len ( response_text . strip ()) > 10 :
1313
1107
# LLM provided a final answer after tool execution, don't continue
1314
1108
final_response_text = response_text .strip ()
1315
1109
break
1316
1110
1317
1111
# Special handling for Ollama to prevent infinite loops
1318
- # Use dynamic analysis instead of hardcoded iteration count
1319
- if self ._is_ollama_provider () and self ._should_generate_tool_summary (tool_results , response_text , iteration_count ):
1320
- tool_summary = self ._generate_ollama_tool_summary (tool_results , response_text )
1321
- if tool_summary :
1322
- final_response_text = tool_summary
1323
- break
1112
+ tool_summary = self ._generate_ollama_tool_summary (tool_results , response_text )
1113
+ if tool_summary :
1114
+ final_response_text = tool_summary
1115
+ break
1324
1116
1325
1117
# Otherwise, continue the loop to check if more tools are needed
1326
1118
iteration_count += 1
@@ -2059,19 +1851,17 @@ async def get_response_async(
2059
1851
stored_reasoning_content = reasoning_content
2060
1852
2061
1853
# Check if the LLM provided a final answer alongside the tool calls
2062
- # Use intelligent differentiation between intermediate and final responses
2063
- if self . _is_final_answer ( response_text , bool ( tool_calls ), tool_results ) :
1854
+ # If response_text contains substantive content, treat it as the final answer
1855
+ if response_text and response_text . strip () and len ( response_text . strip ()) > 10 :
2064
1856
# LLM provided a final answer after tool execution, don't continue
2065
1857
final_response_text = response_text .strip ()
2066
1858
break
2067
1859
2068
1860
# Special handling for Ollama to prevent infinite loops
2069
- # Use dynamic analysis instead of hardcoded iteration count
2070
- if self ._is_ollama_provider () and self ._should_generate_tool_summary (tool_results , response_text , iteration_count ):
2071
- tool_summary = self ._generate_ollama_tool_summary (tool_results , response_text )
2072
- if tool_summary :
2073
- final_response_text = tool_summary
2074
- break
1861
+ tool_summary = self ._generate_ollama_tool_summary (tool_results , response_text )
1862
+ if tool_summary :
1863
+ final_response_text = tool_summary
1864
+ break
2075
1865
2076
1866
# Continue the loop to check if more tools are needed
2077
1867
iteration_count += 1
0 commit comments