mirror of
https://github.com/headroomlabs-ai/headroom.git
synced 2026-08-27 14:17:10 -04:00
refactor(proxy): extract tool name policy (#2008)
## Description
Extracts proxy tool-definition name parsing from
`headroom.proxy.helpers` into a focused policy module. The existing
private helper remains as a compatibility wrapper while memory and CCR
injection skip logic share the tested parser.
Closes #
## Type of Change
- [ ] Bug fix (non-breaking change that fixes an issue)
- [ ] New feature (non-breaking change that adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] Documentation update
- [ ] Performance improvement
- [x] Code refactoring (no functional changes)
## Changes Made
- Added `headroom.proxy.tool_name_policy.extract_tool_name` for
Anthropic custom tools, OpenAI function tools, and Anthropic native
memory tools.
- Updated `helpers._extract_tool_name` to delegate to the policy module
while keeping its existing import path intact.
- Added direct tests for name precedence, function-tool parsing,
native-tool fallback, invalid values, and wrapper compatibility.
## Testing
- [x] Unit tests pass (`pytest`)
- [x] Linting passes (`ruff check .`)
- [x] Type checking passes (`mypy headroom`)
- [x] New tests added for new functionality
- [ ] Manual testing performed
### Test Output
```text
python -m pytest tests/test_tool_name_policy.py tests/test_memory_tool_session_sticky.py tests/test_ccr_tool_always_on.py tests/test_issue_728_empty_tools_injection.py tests/test_proxy/test_ccr_frozen_prefix_coupling.py
60 passed in 0.90s
python -m ruff check .
All checks passed!
python -m ruff format --check .
1069 files already formatted
python -m mypy headroom --ignore-missing-imports
Success: no issues found in 410 source files
gitleaks protect --staged --no-banner --redact
no leaks found
```
## Real Behavior Proof
- Environment: Windows, Python 3.13.13, clean worktree from
`headroomlabs/main` at `d2170b19`.
- Exact command / steps: Ran targeted tool-name policy tests plus
memory/CCR injection regression tests, ruff, ruff-format, mypy, and
staged gitleaks scan.
- Observed result: All targeted tests and local gates passed; staged
secret scan found no leaks.
- Not tested: Full Docker/native wrapper CI locally; covered by
repository CI.
## Review Readiness
- [x] I have performed a self-review
- [x] This PR is ready for human review
## Checklist
- [x] My code follows the project's style guidelines
- [x] I have performed a self-review of my code
- [x] I have commented my code, particularly in hard-to-understand areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] I have updated the CHANGELOG.md if applicable
## Screenshots (if applicable)
N/A.
## Additional Notes
Documentation and changelog updates are not applicable for this internal
refactor. The push reported existing default-branch Dependabot
vulnerabilities; this PR's staged gitleaks scan passed and CI security
checks are expected to validate the branch.
---------
Co-authored-by: Tejas Chopra <chopratejas@gmail.com>
This commit is contained in:
parent
7c9a032f50
commit
10001755e8
3 changed files with 67 additions and 13 deletions
|
|
@ -61,6 +61,7 @@ from headroom.proxy.tool_injection_config import (
|
|||
get_tool_tracker_max_sessions as _get_tool_tracker_max_sessions,
|
||||
)
|
||||
from headroom.proxy.tool_injection_tracker import SessionToolTracker as _SessionToolTracker
|
||||
from headroom.proxy.tool_name_policy import extract_tool_name
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import httpx
|
||||
|
|
@ -1965,19 +1966,7 @@ def _extract_tool_name(tool_definition: dict[str, Any]) -> str | None:
|
|||
* Anthropic native: ``{"type": "memory_20250818", "name": "memory"}``
|
||||
* OpenAI function: ``{"type": "function", "function": {"name": "memory_save", ...}}``
|
||||
"""
|
||||
name = tool_definition.get("name")
|
||||
if isinstance(name, str) and name:
|
||||
return name
|
||||
fn = tool_definition.get("function")
|
||||
if isinstance(fn, dict):
|
||||
fn_name = fn.get("name")
|
||||
if isinstance(fn_name, str) and fn_name:
|
||||
return fn_name
|
||||
# Native memory tool with no explicit name uses ``type`` as its identifier.
|
||||
type_val = tool_definition.get("type")
|
||||
if isinstance(type_val, str) and type_val:
|
||||
return type_val
|
||||
return None
|
||||
return extract_tool_name(tool_definition)
|
||||
|
||||
|
||||
def apply_session_sticky_memory_tools(
|
||||
|
|
|
|||
22
headroom/proxy/tool_name_policy.py
Normal file
22
headroom/proxy/tool_name_policy.py
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
"""Tool-definition name extraction policy used by proxy injection helpers."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
|
||||
def extract_tool_name(tool_definition: dict[str, Any]) -> str | None:
|
||||
"""Extract a stable tool name from a tool definition."""
|
||||
|
||||
name = tool_definition.get("name")
|
||||
if isinstance(name, str) and name:
|
||||
return name
|
||||
function_definition = tool_definition.get("function")
|
||||
if isinstance(function_definition, dict):
|
||||
function_name = function_definition.get("name")
|
||||
if isinstance(function_name, str) and function_name:
|
||||
return function_name
|
||||
tool_type = tool_definition.get("type")
|
||||
if isinstance(tool_type, str) and tool_type:
|
||||
return tool_type
|
||||
return None
|
||||
43
tests/test_tool_name_policy.py
Normal file
43
tests/test_tool_name_policy.py
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from headroom.proxy.helpers import _extract_tool_name
|
||||
from headroom.proxy.tool_name_policy import extract_tool_name
|
||||
|
||||
|
||||
def test_extracts_anthropic_custom_tool_name() -> None:
|
||||
assert extract_tool_name({"name": "memory_save"}) == "memory_save"
|
||||
|
||||
|
||||
def test_extracts_openai_function_tool_name() -> None:
|
||||
assert (
|
||||
extract_tool_name({"type": "function", "function": {"name": "memory_search"}})
|
||||
== "memory_search"
|
||||
)
|
||||
|
||||
|
||||
def test_extracts_native_tool_type_when_name_absent() -> None:
|
||||
assert extract_tool_name({"type": "memory_20250818"}) == "memory_20250818"
|
||||
|
||||
|
||||
def test_prefers_explicit_name_over_function_and_type() -> None:
|
||||
assert (
|
||||
extract_tool_name(
|
||||
{
|
||||
"name": "headroom_retrieve",
|
||||
"type": "function",
|
||||
"function": {"name": "memory_save"},
|
||||
}
|
||||
)
|
||||
== "headroom_retrieve"
|
||||
)
|
||||
|
||||
|
||||
def test_ignores_empty_or_non_string_names() -> None:
|
||||
assert extract_tool_name({"name": "", "function": {"name": ""}, "type": ""}) is None
|
||||
assert extract_tool_name({"name": 123, "function": {"name": 456}, "type": []}) is None
|
||||
|
||||
|
||||
def test_helpers_private_wrapper_keeps_existing_import_path() -> None:
|
||||
tool_definition = {"function": {"name": "memory_update"}}
|
||||
|
||||
assert _extract_tool_name(tool_definition) == extract_tool_name(tool_definition)
|
||||
Loading…
Add table
Add a link
Reference in a new issue