Skip to content

feat: add prfefix to component tool names#1432

Open
akihikokuroda wants to merge 7 commits into
generative-computing:mainfrom
akihikokuroda:issue95-1
Open

feat: add prfefix to component tool names#1432
akihikokuroda wants to merge 7 commits into
generative-computing:mainfrom
akihikokuroda:issue95-1

Conversation

@akihikokuroda

@akihikokuroda akihikokuroda commented Jul 23, 2026

Copy link
Copy Markdown
Member

Pull Request

Issue

Fixes #95

Description

  1. Auto-prefixing of component tools (mellea/backends/tools.py):
  • Component tools are now automatically prefixed with component{N}. where N is the component index
  • Prevents naming collisions when multiple components define tools with identical names
  • Tracks name mappings and stores them in TemplateRepresentation for JSON schema generation
  1. JSON schema updates (mellea/backends/tools.py):
  • Modified convert_tools_to_json() to ensure function names in JSON schemas match the prefixed keys
  • The model now sees and requests the prefixed tool names (e.g., component0.search)
  1. New field in TemplateRepresentation (mellea/core/base.py):
  • Added tool_name_mapping field to track original → prefixed name mappings
  • Defaults to None, only populated during tool extraction
  1. Test updates:
  • Updated test_tool_calls.py to expect component0.to_markdown instead of to_markdown
  • Updated test_tool_helpers.py to verify that duplicate tool names across components are now preserved with prefixes (e.g., component0.tool1 and component1.tool1) rather than being overwritten

Testing

  • Tests added to the respective file if code was changed
  • New code has 100% coverage if code was added
  • Ensure existing tests and github automation passes (a maintainer will kick off the github automation when the rest of the PR is populated)

Attribution

  • AI coding assistants used

Adding a new component, requirement, sampling strategy, or tool?

If your PR adds or modifies one of the types below, check the matching box. A checklist of type-specific review items will be posted as a comment.

  • Component
  • Requirement
  • Sampling Strategy
  • Tool

NOTE: Please ensure you have an issue that has been acknowledged by a core contributor and routed you to open a pull request against this repository. Otherwise, please open an issue before continuing with this pull request.

@github-actions github-actions Bot added the enhancement New feature or request label Jul 23, 2026
@akihikokuroda
akihikokuroda marked this pull request as ready for review July 23, 2026 15:01
@akihikokuroda
akihikokuroda requested a review from a team as a code owner July 23, 2026 15:01
@jakelorocco

Copy link
Copy Markdown
Contributor

@akihikokuroda, we should create a full proposal for this. I don't know if we want to prefix component tools with an index. I think we need to ensure that performance isn't impacted. We also need to make sure that we can clearly link tools to any given component, which might require more testing / planning.

@ajbozarth

Copy link
Copy Markdown
Contributor

Small note on just an initial pass, I think this will have conflicts will #1430 adding tool_call_id

@akihikokuroda

Copy link
Copy Markdown
Member Author

@jakelorocco Here is a proposal.

Proposed Solution: Object ID-Based Prefixing

Design

Use Python object identity as the stable component identifier:

component_id = hex(id(component))[-8:]  # e.g., "a1b2c3d4"
prefixed_name = f"component_{component_id}.{original_tool_name}"

Properties

Property Value
Uniqueness Guaranteed per object instance (Python guarantees)
Stability Stable for object lifetime (same object = same ID across turns)
Determinism Deterministic within a single generation
Serialization Non-serializable (acceptable; tools don't cross process boundaries)
Name length ~16 chars (component_a1b2c3d4.search) vs 8 chars for index

Example: Multi-Turn Scenario with ID-Based Prefixing

Turn 1: User provides [SearchAgent@0x7f123abc, RetrievalAgent@0x7f456def, FilterAgent@0x7f789ghi]

  IDs (last 8 hex chars):
    - SearchAgent: a1b2c3d4
    - RetrievalAgent: e5f6g7h8
    - FilterAgent: i9j0k1l2

  LLM sees tools:
    - component_a1b2c3d4.search
    - component_e5f6g7h8.retrieve
    - component_i9j0k1l2.filter

Turn 2: User reorders context [FilterAgent@0x7f789ghi, SearchAgent@0x7f123abc, RetrievalAgent@0x7f456def]

  Same objects, same IDs:
    - FilterAgent: i9j0k1l2 (unchanged)
    - SearchAgent: a1b2c3d4 (unchanged)
    - RetrievalAgent: e5f6g7h8 (unchanged)

  LLM sees tools:
    - component_i9j0k1l2.filter   ← SAME NAME 
    - component_a1b2c3d4.search   ← SAME NAME 
    - component_e5f6g7h8.retrieve ← SAME NAME 

STABLE: Tool names unchanged regardless of component order!

Advantages Over Index-Based

1. Multi-Turn Stability

  • Tool names remain constant across turns if components are reused
  • LLM can reference same tool by same name in follow-up interactions
  • No confusion from component reordering

2. Prevents Parallel Tool Collisions

3. Component Context Independence

  • Same component reused in different contexts gets same tool names
  • Tool identity follows component object, not context position
  • Enables composition scenarios where agents are mixed/reordered

4. Better Observability

  • Tool prefix is stable and traceable
  • Can correlate with component metadata (stored on TemplateRepresentation)
  • Trace spans show consistent component identity

5. Integrates with PR #1430 (Tool Tracing)

  • tool_call_id (provider ID) + component ID prefix = full observability chain

Performance Impact

Analysis

Operation Complexity Cost
Generate component ID O(1) hex(id(component))[-8:] ≈ 1µs
Prefix tool name O(1) String concatenation ≈ 0.1µs per tool
JSON schema update O(1) Name field update in dict copy
Tool lookup in dict O(1) Hash lookup unchanged

Example with 10 tools:

  • Index-based: ~1µs per tool = ~10µs total
  • ID-based: ~1.1µs per tool = ~11µs total
  • Difference: <1µs negligible

Token Cost

Name length comparison:

  • Index-based: component0.search (16 chars)
  • ID-based: component_a1b2c3d4.search (26 chars)
  • Difference: ~10 chars per tool

Example with 10 tools in JSON schema:

  • Index-based: ~160 chars
  • ID-based: ~260 chars
  • Token cost: ~0.05% increase (negligible for typical prompts)

Conclusion: Performance impact is negligible. No performance concerns.

  • Can link tool execution to component type and provider trace

About concern:

We also need to make sure that we can clearly link tools to any given component, which might require more testing / planning

Better Solution: Metadata on TemplateRepresentation

Instead of reverse mapping, store component metadata on TemplateRepresentation:

@dataclass                                                                                                                                                                                                
class TemplateRepresentation:                                                                                                                                                                             
    obj: Any                                                                                                                                                                                              
    args: dict[...]                                                                                                                                                                                       
    tools: dict[str, AbstractMelleaTool] | None = None                                                                                                                                                    
    tool_name_mapping: dict[str, str] | None = None                                                                                                                                                       
                                                                                                                                                                                                          
    # NEW: Component identity metadata                                                                                                                                                                    
    component_id: str | None = None  # e.g., "a1b2c3d4"                                                                                                                                                   
    component_type: str | None = None  # e.g., "SearchAgent"                                                                                                                                              
    component_description: str | None = None  # Optional                                                                                                                                                  

Why This is Better

Aspect Reverse Mapping Metadata on TR Winner
Storage Dict management Field on dataclass Metadata
Lifecycle Complex (when create/clear?) Simple (lives with TR) Metadata
Synchronization Must stay in sync with tools Atomic with tools Metadata
Serialization Can't serialize Can serialize Metadata
Observability Must access object Already available Metadata
API coupling Exposes component objects No coupling Metadata
Performance O(1) lookup (rarely used) O(1) access Neutral

Usage Example

# At tool registration time:                                                                                                                                                                              
if isinstance(action, Component):                                                                                                                                                                         
    tr = action.format_for_llm()                                                                                                                                                                          
    if isinstance(tr, TemplateRepresentation):                                                                                                                                                            
        tr.component_id = hex(id(action))[-8:]                                                                                                                                                            
        tr.component_type = type(action).__name__                                                                                                                                                         
        # tool_name_mapping already set                                                                                                                                                                   
        # All metadata in one place ✅                                                                                                                                                                    
                                                                                                                                                                                                          
# At trace time:                                                                                                                                                                                          
span.set_attribute("mellea.component.id", tr.component_id)                                                                                                                                                
span.set_attribute("mellea.component.type", tr.component_type)                                                                                                                                            
# Metadata available without reverse mapping ✅                                                                                                                                                           
                                                                                                                                                                                                          
# In error messages:                                                                                                                                                                                      
error = f"Tool {tool_name} failed (component: {tr.component_type})"                                                                                                                                       
# Clear attribution without reverse mapping ✅                                                                                                                                                            
                                                                                                                                                                                                          
# For debugging:                                                                                                                                                                                          
# User reads error message, sees component type, can correlate                                                                                                                                            
# No need to reverse-map tool name back to component ✅                                                                                                                                                   

@jakelorocco

Copy link
Copy Markdown
Contributor

@akihikokuroda, I think this is still a good idea but we probably need some evaluations to indicate that the model can properly track the difference between components. Especially since we don't print component ids when outputting them currently.

Signed-off-by: Akihiko Kuroda <akihikokuroda2020@gmail.com>
Signed-off-by: Akihiko Kuroda <akihikokuroda2020@gmail.com>
Signed-off-by: Akihiko Kuroda <akihikokuroda2020@gmail.com>
Signed-off-by: Akihiko Kuroda <akihikokuroda2020@gmail.com>
Signed-off-by: Akihiko Kuroda <akihikokuroda2020@gmail.com>
Signed-off-by: Akihiko Kuroda <akihikokuroda2020@gmail.com>
@akihikokuroda

Copy link
Copy Markdown
Member Author

@jakelorocco I added examples. docs/examples/components/pattern2_context_and_tools.py shows the component tools renaming, executing and telemetry output.

Signed-off-by: Akihiko Kuroda <akihikokuroda2020@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

allow tools to be renamed

3 participants