@miller-tech/uap 1.84.0 → 1.84.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/policies/enforcers/__pycache__/_common.cpython-312.pyc +0 -0
- package/tools/agents/scripts/__pycache__/toolcall_path_normalizer.cpython-312.pyc +0 -0
- package/tools/agents/scripts/anthropic_proxy.py +25 -0
- package/tools/agents/tests/test_tool_convert_cache.py +49 -0
package/package.json
CHANGED
|
Binary file
|
|
Binary file
|
|
@@ -3351,7 +3351,28 @@ def anthropic_to_openai_response(anthropic_resp: dict) -> dict:
|
|
|
3351
3351
|
}
|
|
3352
3352
|
|
|
3353
3353
|
|
|
3354
|
+
# A3: the anthropic->openai tool conversion + schema sanitize walks every tool's
|
|
3355
|
+
# (often deeply nested) JSON schema. The tool set is IDENTICAL across every turn
|
|
3356
|
+
# of a session, so this recomputed the same result each turn (observed: ~1
|
|
3357
|
+
# SCHEMA SANITIZE log per turn). Cache by a stable hash of the tool definitions.
|
|
3358
|
+
# Downstream only READS the converted dicts and FILTERS the list (narrowing), so
|
|
3359
|
+
# returning the cached object directly is safe.
|
|
3360
|
+
_TOOL_CONVERT_CACHE: "OrderedDict[str, list]" = OrderedDict()
|
|
3361
|
+
_TOOL_CONVERT_CACHE_MAX = 32
|
|
3362
|
+
|
|
3363
|
+
|
|
3354
3364
|
def _convert_anthropic_tools_to_openai(anthropic_tools: list[dict]) -> list[dict]:
|
|
3365
|
+
cache_key = None
|
|
3366
|
+
try:
|
|
3367
|
+
cache_key = hashlib.sha1(
|
|
3368
|
+
json.dumps(anthropic_tools, sort_keys=True, default=str).encode("utf-8")
|
|
3369
|
+
).hexdigest()
|
|
3370
|
+
except Exception:
|
|
3371
|
+
cache_key = None
|
|
3372
|
+
if cache_key is not None and cache_key in _TOOL_CONVERT_CACHE:
|
|
3373
|
+
_TOOL_CONVERT_CACHE.move_to_end(cache_key)
|
|
3374
|
+
return _TOOL_CONVERT_CACHE[cache_key]
|
|
3375
|
+
|
|
3355
3376
|
converted = []
|
|
3356
3377
|
removed_pattern_fields = 0
|
|
3357
3378
|
for tool in anthropic_tools:
|
|
@@ -3375,6 +3396,10 @@ def _convert_anthropic_tools_to_openai(anthropic_tools: list[dict]) -> list[dict
|
|
|
3375
3396
|
removed_pattern_fields,
|
|
3376
3397
|
len(anthropic_tools),
|
|
3377
3398
|
)
|
|
3399
|
+
if cache_key is not None:
|
|
3400
|
+
_TOOL_CONVERT_CACHE[cache_key] = converted
|
|
3401
|
+
if len(_TOOL_CONVERT_CACHE) > _TOOL_CONVERT_CACHE_MAX:
|
|
3402
|
+
_TOOL_CONVERT_CACHE.popitem(last=False)
|
|
3378
3403
|
return converted
|
|
3379
3404
|
|
|
3380
3405
|
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""Tests for A3: per-session tool-conversion cache."""
|
|
2
|
+
import importlib.util
|
|
3
|
+
import unittest
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
proxy_path = Path(__file__).resolve().parents[3] / "tools" / "agents" / "scripts" / "anthropic_proxy.py"
|
|
7
|
+
spec = importlib.util.spec_from_file_location("anthropic_proxy", proxy_path)
|
|
8
|
+
ap = importlib.util.module_from_spec(spec)
|
|
9
|
+
spec.loader.exec_module(ap)
|
|
10
|
+
|
|
11
|
+
TOOLS = [
|
|
12
|
+
{"name": "Read", "description": "read", "input_schema": {"type": "object",
|
|
13
|
+
"properties": {"p": {"type": "string", "pattern": "^/.*"}}, "required": ["p"]}},
|
|
14
|
+
{"name": "Bash", "description": "run", "input_schema": {"type": "object",
|
|
15
|
+
"properties": {"cmd": {"type": "string"}}}},
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class ToolConvertCacheTest(unittest.TestCase):
|
|
20
|
+
def setUp(self):
|
|
21
|
+
ap._TOOL_CONVERT_CACHE.clear()
|
|
22
|
+
|
|
23
|
+
def test_correct_conversion_and_sanitize(self):
|
|
24
|
+
out = ap._convert_anthropic_tools_to_openai(TOOLS)
|
|
25
|
+
self.assertEqual(out[0]["function"]["name"], "Read")
|
|
26
|
+
# regex pattern field stripped by sanitize
|
|
27
|
+
self.assertNotIn("pattern", out[0]["function"]["parameters"]["properties"]["p"])
|
|
28
|
+
|
|
29
|
+
def test_second_call_is_cache_hit_same_object(self):
|
|
30
|
+
a = ap._convert_anthropic_tools_to_openai(TOOLS)
|
|
31
|
+
self.assertEqual(len(ap._TOOL_CONVERT_CACHE), 1)
|
|
32
|
+
b = ap._convert_anthropic_tools_to_openai([dict(t) for t in TOOLS]) # equal-by-value
|
|
33
|
+
self.assertIs(a, b, "identical tool set must return the cached object")
|
|
34
|
+
|
|
35
|
+
def test_different_tools_miss(self):
|
|
36
|
+
ap._convert_anthropic_tools_to_openai(TOOLS)
|
|
37
|
+
ap._convert_anthropic_tools_to_openai(TOOLS[:1])
|
|
38
|
+
self.assertEqual(len(ap._TOOL_CONVERT_CACHE), 2)
|
|
39
|
+
|
|
40
|
+
def test_cache_bounded(self):
|
|
41
|
+
for i in range(ap._TOOL_CONVERT_CACHE_MAX + 5):
|
|
42
|
+
ap._convert_anthropic_tools_to_openai(
|
|
43
|
+
[{"name": f"T{i}", "description": "", "input_schema": {"type": "object"}}]
|
|
44
|
+
)
|
|
45
|
+
self.assertLessEqual(len(ap._TOOL_CONVERT_CACHE), ap._TOOL_CONVERT_CACHE_MAX)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
if __name__ == "__main__":
|
|
49
|
+
unittest.main()
|