Skip to content

Commit 88ae3a2

Browse files
fix: Add defensive validation and fix test import path
- Add validation for nested dictionary keys to prevent KeyError - Add JSON serialization validation to ensure tools are serializable - Fix import path in test_async_tool_formats.py - Improves robustness of tool formatting Co-authored-by: Mervin Praison <MervinPraison@users.noreply.github.com>
1 parent 74ef96b commit 88ae3a2

File tree

2 files changed

+22
-5
lines changed

2 files changed

+22
-5
lines changed

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

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -386,14 +386,22 @@ def _format_tools_for_litellm(self, tools: Optional[List[Any]]) -> Optional[List
386386
for tool in tools:
387387
# Check if the tool is already in OpenAI format (e.g. from MCP.to_openai_tool())
388388
if isinstance(tool, dict) and 'type' in tool and tool['type'] == 'function':
389-
logging.debug(f"Using pre-formatted OpenAI tool: {tool['function']['name']}")
390-
formatted_tools.append(tool)
389+
# Validate nested dictionary structure before accessing
390+
if 'function' in tool and isinstance(tool['function'], dict) and 'name' in tool['function']:
391+
logging.debug(f"Using pre-formatted OpenAI tool: {tool['function']['name']}")
392+
formatted_tools.append(tool)
393+
else:
394+
logging.debug(f"Skipping malformed OpenAI tool: missing function or name")
391395
# Handle lists of tools (e.g. from MCP.to_openai_tool())
392396
elif isinstance(tool, list):
393397
for subtool in tool:
394398
if isinstance(subtool, dict) and 'type' in subtool and subtool['type'] == 'function':
395-
logging.debug(f"Using pre-formatted OpenAI tool from list: {subtool['function']['name']}")
396-
formatted_tools.append(subtool)
399+
# Validate nested dictionary structure before accessing
400+
if 'function' in subtool and isinstance(subtool['function'], dict) and 'name' in subtool['function']:
401+
logging.debug(f"Using pre-formatted OpenAI tool from list: {subtool['function']['name']}")
402+
formatted_tools.append(subtool)
403+
else:
404+
logging.debug(f"Skipping malformed OpenAI tool in list: missing function or name")
397405
elif callable(tool):
398406
tool_def = self._generate_tool_definition(tool.__name__)
399407
if tool_def:
@@ -405,6 +413,15 @@ def _format_tools_for_litellm(self, tools: Optional[List[Any]]) -> Optional[List
405413
else:
406414
logging.debug(f"Skipping tool of unsupported type: {type(tool)}")
407415

416+
# Validate JSON serialization before returning
417+
if formatted_tools:
418+
try:
419+
import json
420+
json.dumps(formatted_tools) # Validate serialization
421+
except (TypeError, ValueError) as e:
422+
logging.error(f"Tools are not JSON serializable: {e}")
423+
return None
424+
408425
return formatted_tools if formatted_tools else None
409426

410427
def get_response(

src/praisonai/tests/unit/test_async_tool_formats.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from typing import Dict, List, Optional
1010

1111
# Add the source directory to path
12-
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'src/praisonai-agents'))
12+
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '../../../praisonai-agents'))
1313

1414
from praisonaiagents.llm import LLM
1515

0 commit comments

Comments
 (0)