Skip to content

Commit 69e6ace

Browse files
feat: consolidate tool alias mappings into single source of truth (fixes #1431)
- Create shared _tool_aliases.py with canonical TOOL_ALIAS_MAP - Remove duplicate TOOL_MAPPING from managed_agents.py - Remove duplicate TOOL_ALIAS_MAP from managed_local.py - Resolve mapping conflicts: grep->search_file, web_fetch->web_fetch, edit->apply_diff - Add comprehensive unit tests for alias consistency - Ensure backward compatibility for existing callers Co-authored-by: MervinPraison <MervinPraison@users.noreply.github.com>
1 parent 9463b1f commit 69e6ace

File tree

4 files changed

+148
-22
lines changed

4 files changed

+148
-22
lines changed
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
"""
2+
Tests for tool alias consistency across managed agent backends.
3+
4+
Ensures that the shared TOOL_ALIAS_MAP is properly imported and used
5+
consistently between managed_agents.py and managed_local.py to prevent
6+
contract drift and silent behavioral differences.
7+
"""
8+
9+
import pytest
10+
from unittest import TestCase
11+
12+
13+
class TestToolAliasConsistency(TestCase):
14+
"""Test suite for tool alias mapping consistency."""
15+
16+
def test_tool_alias_single_source_of_truth(self):
17+
"""Assert module-level identity - both modules use the same dict object."""
18+
# Import the shared mapping
19+
from praisonai.praisonai.integrations._tool_aliases import TOOL_ALIAS_MAP as shared_map
20+
21+
# Import the mapping as used in managed_agents.py
22+
from praisonai.praisonai.integrations.managed_agents import TOOL_ALIAS_MAP as agents_map
23+
24+
# Import the mapping as used in managed_local.py
25+
from praisonai.praisonai.integrations.managed_local import TOOL_ALIAS_MAP as local_map
26+
27+
# Assert all three are the same object (module-level identity)
28+
assert shared_map is agents_map, "managed_agents.py should import the shared TOOL_ALIAS_MAP"
29+
assert shared_map is local_map, "managed_local.py should import the shared TOOL_ALIAS_MAP"
30+
assert agents_map is local_map, "Both modules should use the same dict object"
31+
32+
def test_known_aliases_stable(self):
33+
"""Lock in the final mapping so future changes need deliberate test updates."""
34+
from praisonai.praisonai.integrations._tool_aliases import TOOL_ALIAS_MAP
35+
36+
# Expected stable mapping based on consolidation decisions
37+
expected_mapping = {
38+
"bash": "execute_command",
39+
"read": "read_file",
40+
"write": "write_file",
41+
"edit": "apply_diff",
42+
"glob": "list_files",
43+
"grep": "search_file",
44+
"web_fetch": "web_fetch",
45+
"search": "search_web",
46+
"web_search": "search_web",
47+
}
48+
49+
# Assert the mapping matches exactly
50+
assert TOOL_ALIAS_MAP == expected_mapping, (
51+
f"TOOL_ALIAS_MAP has changed from expected stable mapping.\n"
52+
f"Expected: {expected_mapping}\n"
53+
f"Actual: {TOOL_ALIAS_MAP}\n"
54+
f"If this change is intentional, update this test."
55+
)
56+
57+
def test_resolved_conflicts(self):
58+
"""Verify that previously conflicting mappings are resolved consistently."""
59+
from praisonai.praisonai.integrations._tool_aliases import TOOL_ALIAS_MAP
60+
61+
# Test conflict resolutions based on issue analysis:
62+
63+
# grep: chose 'search_file' over 'execute_command'
64+
# (matches PraisonAI grep_tool.py built-in)
65+
assert TOOL_ALIAS_MAP["grep"] == "search_file"
66+
67+
# web_fetch: chose 'web_fetch' over 'web_crawl'
68+
# (keeping original name as no web_crawl tool found)
69+
assert TOOL_ALIAS_MAP["web_fetch"] == "web_fetch"
70+
71+
# edit: chose 'apply_diff' over 'write_file'
72+
# (matches PraisonAI code/tools/apply_diff.py)
73+
assert TOOL_ALIAS_MAP["edit"] == "apply_diff"
74+
75+
def test_backward_compatibility(self):
76+
"""Verify that functions using the alias map maintain their signatures."""
77+
from praisonai.praisonai.integrations.managed_agents import map_managed_tools
78+
79+
# Test function signature and behavior is preserved
80+
test_tools = ["bash", "grep", "web_fetch", "unknown_tool"]
81+
result = map_managed_tools(test_tools)
82+
83+
expected = ["execute_command", "search_file", "web_fetch", "unknown_tool"]
84+
assert result == expected, f"map_managed_tools should map known tools and pass through unknown ones"
85+
86+
def test_no_duplicate_definitions(self):
87+
"""Ensure that TOOL_MAPPING and local TOOL_ALIAS_MAP definitions are removed."""
88+
import inspect
89+
90+
# Check managed_agents.py doesn't have TOOL_MAPPING anymore
91+
from praisonai.praisonai import integrations
92+
managed_agents_module = integrations.managed_agents
93+
94+
# TOOL_MAPPING should not exist as a module-level variable
95+
assert not hasattr(managed_agents_module, 'TOOL_MAPPING'), (
96+
"TOOL_MAPPING should be removed from managed_agents.py"
97+
)
98+
99+
# Check that managed_local.py source doesn't contain local definition
100+
import praisonai.praisonai.integrations.managed_local as managed_local_module
101+
source = inspect.getsource(managed_local_module)
102+
103+
# Should not have a local TOOL_ALIAS_MAP definition
104+
assert 'TOOL_ALIAS_MAP = {' not in source, (
105+
"Local TOOL_ALIAS_MAP definition should be removed from managed_local.py"
106+
)
107+
108+
# Should import from _tool_aliases
109+
assert 'from ._tool_aliases import TOOL_ALIAS_MAP' in source, (
110+
"managed_local.py should import TOOL_ALIAS_MAP from _tool_aliases"
111+
)
112+
113+
114+
if __name__ == "__main__":
115+
pytest.main([__file__])
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
"""
2+
Centralized tool alias mapping for managed agents.
3+
4+
This module provides the single source of truth for tool name translations
5+
between Anthropic managed agents and PraisonAI tools, eliminating duplication
6+
and preventing contract drift between different backends.
7+
8+
Decisions on conflicting mappings:
9+
- grep: Maps to 'search_file' (matches PraisonAI grep_tool.py built-in)
10+
- web_fetch: Maps to 'web_fetch' (keeping original name as no web_crawl tool found)
11+
- edit: Maps to 'apply_diff' (matches PraisonAI code/tools/apply_diff.py)
12+
13+
This mapping is used by both managed_agents.py and managed_local.py to ensure
14+
consistent tool translation across all managed agent backends.
15+
"""
16+
17+
from typing import Dict
18+
19+
#: Canonical tool alias mapping from Anthropic tool names to PraisonAI tool names
20+
TOOL_ALIAS_MAP: Dict[str, str] = {
21+
"bash": "execute_command",
22+
"read": "read_file",
23+
"write": "write_file",
24+
"edit": "apply_diff",
25+
"glob": "list_files",
26+
"grep": "search_file",
27+
"web_fetch": "web_fetch",
28+
"search": "search_web",
29+
"web_search": "search_web",
30+
}

src/praisonai/praisonai/integrations/managed_agents.py

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -639,21 +639,12 @@ def managed_session_id(self) -> Optional[str]:
639639
# ---------------------------------------------------------------------------
640640
# Tool mapping helpers
641641
# ---------------------------------------------------------------------------
642-
TOOL_MAPPING = {
643-
"bash": "execute_command",
644-
"read": "read_file",
645-
"write": "write_file",
646-
"edit": "apply_diff",
647-
"glob": "list_files",
648-
"grep": "search_file",
649-
"web_fetch": "web_fetch",
650-
"search": "search_web",
651-
}
642+
from ._tool_aliases import TOOL_ALIAS_MAP
652643

653644

654645
def map_managed_tools(managed_tools: List[str]) -> List[str]:
655646
"""Map managed agent tool names to PraisonAI tool names."""
656-
return [TOOL_MAPPING.get(tool, tool) for tool in managed_tools]
647+
return [TOOL_ALIAS_MAP.get(tool, tool) for tool in managed_tools]
657648

658649

659650
# ---------------------------------------------------------------------------

src/praisonai/praisonai/integrations/managed_local.py

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -50,17 +50,7 @@
5050
"search_web",
5151
]
5252

53-
TOOL_ALIAS_MAP = {
54-
"bash": "execute_command",
55-
"read": "read_file",
56-
"write": "write_file",
57-
"edit": "write_file",
58-
"glob": "list_files",
59-
"grep": "execute_command",
60-
"web_fetch": "web_crawl",
61-
"search": "search_web",
62-
"web_search": "search_web",
63-
}
53+
from ._tool_aliases import TOOL_ALIAS_MAP
6454

6555

6656
@dataclass

0 commit comments

Comments
 (0)