Skip to content

Commit ca62af0

Browse files
refactor: eliminate code duplication and improve test quality in Ollama fix
- Extract duplicated Ollama tool summary logic into _generate_ollama_tool_summary() helper method - Replace magic number 10 with OLLAMA_MIN_RESPONSE_LENGTH constant - Fix test_tool_summary_generation() to call production code instead of reimplementing logic - Add comprehensive test scenarios for different response types and provider types - Maintain backward compatibility and fix functionality Co-authored-by: Mervin Praison <[email protected]>
1 parent ca8e8a9 commit ca62af0

File tree

2 files changed

+86
-46
lines changed

2 files changed

+86
-46
lines changed

src/praisonai-agents/praisonaiagents/llm/llm.py

Lines changed: 46 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,42 @@ def _is_ollama_provider(self) -> bool:
302302

303303
return False
304304

305+
def _generate_ollama_tool_summary(self, tool_results: List[Any], response_text: str) -> Optional[str]:
306+
"""
307+
Generate a summary from tool results for Ollama to prevent infinite loops.
308+
309+
This prevents infinite loops where Ollama provides an empty response after a
310+
tool call, expecting the user to prompt for a summary.
311+
312+
Args:
313+
tool_results: The list of results from tool execution.
314+
response_text: The text response from the LLM.
315+
316+
Returns:
317+
A summary string if conditions are met, otherwise None.
318+
"""
319+
# Constant for minimal response length check
320+
OLLAMA_MIN_RESPONSE_LENGTH = 10
321+
322+
# Only generate summary for Ollama with tool results
323+
if not (self._is_ollama_provider() and tool_results):
324+
return None
325+
326+
# If response is substantial, no summary needed
327+
if response_text and len(response_text.strip()) > OLLAMA_MIN_RESPONSE_LENGTH:
328+
return None
329+
330+
# Build tool summary efficiently
331+
summary_lines = ["Based on the tool execution results:"]
332+
for i, result in enumerate(tool_results):
333+
if isinstance(result, dict) and 'result' in result:
334+
function_name = result.get('function_name', 'Tool')
335+
summary_lines.append(f"- {function_name}: {result['result']}")
336+
else:
337+
summary_lines.append(f"- Tool {i+1}: {result}")
338+
339+
return "\n".join(summary_lines)
340+
305341
def _format_ollama_tool_result_message(self, function_name: str, tool_result: Any) -> Dict[str, str]:
306342
"""
307343
Format tool result message for Ollama provider.
@@ -1072,21 +1108,11 @@ def get_response(
10721108
final_response_text = response_text.strip()
10731109
break
10741110

1075-
# Special handling for Ollama: if we have tool results but empty/minimal response,
1076-
# generate a summary based on tool results to prevent infinite loops
1077-
if self._is_ollama_provider() and tool_results and len(tool_results) > 0:
1078-
# Create a summary of tool results for Ollama
1079-
tool_summary = "Based on the tool execution results:\n"
1080-
for i, result in enumerate(tool_results):
1081-
if isinstance(result, dict) and 'result' in result:
1082-
tool_summary += f"- {result.get('function_name', 'Tool')}: {result['result']}\n"
1083-
else:
1084-
tool_summary += f"- Tool {i+1}: {result}\n"
1085-
1086-
# If response is empty or minimal, use tool summary as final answer
1087-
if not response_text or len(response_text.strip()) <= 10:
1088-
final_response_text = tool_summary.strip()
1089-
break
1111+
# Special handling for Ollama to prevent infinite loops
1112+
tool_summary = self._generate_ollama_tool_summary(tool_results, response_text)
1113+
if tool_summary:
1114+
final_response_text = tool_summary
1115+
break
10901116

10911117
# Otherwise, continue the loop to check if more tools are needed
10921118
iteration_count += 1
@@ -1831,21 +1857,11 @@ async def get_response_async(
18311857
final_response_text = response_text.strip()
18321858
break
18331859

1834-
# Special handling for Ollama: if we have tool results but empty/minimal response,
1835-
# generate a summary based on tool results to prevent infinite loops
1836-
if self._is_ollama_provider() and tool_results and len(tool_results) > 0:
1837-
# Create a summary of tool results for Ollama
1838-
tool_summary = "Based on the tool execution results:\n"
1839-
for i, result in enumerate(tool_results):
1840-
if isinstance(result, dict) and 'result' in result:
1841-
tool_summary += f"- {result.get('function_name', 'Tool')}: {result['result']}\n"
1842-
else:
1843-
tool_summary += f"- Tool {i+1}: {result}\n"
1844-
1845-
# If response is empty or minimal, use tool summary as final answer
1846-
if not response_text or len(response_text.strip()) <= 10:
1847-
final_response_text = tool_summary.strip()
1848-
break
1860+
# Special handling for Ollama to prevent infinite loops
1861+
tool_summary = self._generate_ollama_tool_summary(tool_results, response_text)
1862+
if tool_summary:
1863+
final_response_text = tool_summary
1864+
break
18491865

18501866
# Continue the loop to check if more tools are needed
18511867
iteration_count += 1

test_ollama_fix.py

Lines changed: 40 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -57,34 +57,58 @@ def test_ollama_provider_detection():
5757
return False
5858

5959
def test_tool_summary_generation():
60-
"""Test that tool results summary generation works correctly."""
60+
"""Test that tool results summary generation works correctly by calling production code."""
6161
try:
62+
from praisonaiagents.llm.llm import LLM
63+
64+
# Create an Ollama LLM instance
65+
ollama_llm = LLM(model="ollama/test")
66+
6267
# Mock tool results like what would be generated
6368
tool_results = [
64-
"The stock price of Google is 100",
69+
"The stock price of Google is 100",
6570
200
6671
]
6772

68-
# Simulate the summary generation logic
69-
tool_summary = "Based on the tool execution results:\n"
70-
for i, result in enumerate(tool_results):
71-
if isinstance(result, dict) and 'result' in result:
72-
tool_summary += f"- {result.get('function_name', 'Tool')}: {result['result']}\n"
73-
else:
74-
tool_summary += f"- Tool {i+1}: {result}\n"
75-
73+
# Test with empty response (should generate summary)
74+
summary = ollama_llm._generate_ollama_tool_summary(tool_results, "")
7675
expected_summary = "Based on the tool execution results:\n- Tool 1: The stock price of Google is 100\n- Tool 2: 200"
7776

78-
if tool_summary.strip() == expected_summary:
79-
print("✅ Tool summary generation works correctly")
80-
print(f"Generated summary: {repr(tool_summary.strip())}")
81-
return True
77+
if summary == expected_summary:
78+
print("✅ Tool summary generation (empty response) works correctly")
8279
else:
83-
print("❌ Tool summary generation failed")
80+
print("❌ Tool summary generation (empty response) failed")
8481
print(f"Expected: {repr(expected_summary)}")
85-
print(f"Got: {repr(tool_summary.strip())}")
82+
print(f"Got: {repr(summary)}")
83+
return False
84+
85+
# Test with minimal response (should generate summary)
86+
summary_minimal = ollama_llm._generate_ollama_tool_summary(tool_results, "ok")
87+
if summary_minimal == expected_summary:
88+
print("✅ Tool summary generation (minimal response) works correctly")
89+
else:
90+
print("❌ Tool summary generation (minimal response) failed")
91+
return False
92+
93+
# Test with substantial response (should NOT generate summary)
94+
summary_substantial = ollama_llm._generate_ollama_tool_summary(tool_results, "This is a detailed response with more than 10 characters")
95+
if summary_substantial is None:
96+
print("✅ Tool summary generation correctly skips substantial responses")
97+
else:
98+
print("❌ Tool summary generation incorrectly generated summary for substantial response")
99+
return False
100+
101+
# Test with non-Ollama model (should NOT generate summary)
102+
non_ollama_llm = LLM(model="gpt-4o-mini")
103+
summary_non_ollama = non_ollama_llm._generate_ollama_tool_summary(tool_results, "")
104+
if summary_non_ollama is None:
105+
print("✅ Tool summary generation correctly skips non-Ollama models")
106+
else:
107+
print("❌ Tool summary generation incorrectly generated summary for non-Ollama model")
86108
return False
87109

110+
return True
111+
88112
except Exception as e:
89113
print(f"❌ Tool summary generation test failed: {e}")
90114
return False

0 commit comments

Comments
 (0)