@miller-tech/uap 1.15.11 → 1.15.12
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
CHANGED
|
@@ -1343,19 +1343,56 @@ def _last_user_has_tool_result(anthropic_body: dict) -> bool:
|
|
|
1343
1343
|
return False
|
|
1344
1344
|
|
|
1345
1345
|
|
|
1346
|
+
def _sanitize_tool_schema_for_llama(schema):
|
|
1347
|
+
"""Remove JSON Schema keywords that generate unsupported regex grammar.
|
|
1348
|
+
|
|
1349
|
+
llama.cpp's tool grammar generator can fail on regex-heavy schema fields
|
|
1350
|
+
such as "pattern" and "patternProperties" (for example "\\w").
|
|
1351
|
+
"""
|
|
1352
|
+
|
|
1353
|
+
removed = 0
|
|
1354
|
+
|
|
1355
|
+
def _walk(node):
|
|
1356
|
+
nonlocal removed
|
|
1357
|
+
if isinstance(node, dict):
|
|
1358
|
+
cleaned = {}
|
|
1359
|
+
for key, value in node.items():
|
|
1360
|
+
if key in {"pattern", "patternProperties"}:
|
|
1361
|
+
removed += 1
|
|
1362
|
+
continue
|
|
1363
|
+
cleaned[key] = _walk(value)
|
|
1364
|
+
return cleaned
|
|
1365
|
+
if isinstance(node, list):
|
|
1366
|
+
return [_walk(item) for item in node]
|
|
1367
|
+
return node
|
|
1368
|
+
|
|
1369
|
+
return _walk(schema), removed
|
|
1370
|
+
|
|
1371
|
+
|
|
1346
1372
|
def _convert_anthropic_tools_to_openai(anthropic_tools: list[dict]) -> list[dict]:
|
|
1347
1373
|
converted = []
|
|
1374
|
+
removed_pattern_fields = 0
|
|
1348
1375
|
for tool in anthropic_tools:
|
|
1376
|
+
input_schema, removed = _sanitize_tool_schema_for_llama(
|
|
1377
|
+
tool.get("input_schema", {})
|
|
1378
|
+
)
|
|
1379
|
+
removed_pattern_fields += removed
|
|
1349
1380
|
converted.append(
|
|
1350
1381
|
{
|
|
1351
1382
|
"type": "function",
|
|
1352
1383
|
"function": {
|
|
1353
1384
|
"name": tool.get("name", ""),
|
|
1354
1385
|
"description": tool.get("description", ""),
|
|
1355
|
-
"parameters":
|
|
1386
|
+
"parameters": input_schema,
|
|
1356
1387
|
},
|
|
1357
1388
|
}
|
|
1358
1389
|
)
|
|
1390
|
+
if removed_pattern_fields > 0:
|
|
1391
|
+
logger.warning(
|
|
1392
|
+
"TOOL SCHEMA SANITIZE: removed %d regex pattern fields from %d tools",
|
|
1393
|
+
removed_pattern_fields,
|
|
1394
|
+
len(anthropic_tools),
|
|
1395
|
+
)
|
|
1359
1396
|
return converted
|
|
1360
1397
|
|
|
1361
1398
|
|
|
@@ -100,6 +100,61 @@ class TestProxyConfigTuning(unittest.TestCase):
|
|
|
100
100
|
setattr(proxy, "PROXY_CONTEXT_PRUNE_TARGET_FRACTION", old_target)
|
|
101
101
|
|
|
102
102
|
|
|
103
|
+
class TestToolSchemaSanitization(unittest.TestCase):
|
|
104
|
+
def test_convert_tools_strips_pattern_fields(self):
|
|
105
|
+
anthropic_tools = [
|
|
106
|
+
{
|
|
107
|
+
"name": "Sample",
|
|
108
|
+
"description": "test",
|
|
109
|
+
"input_schema": {
|
|
110
|
+
"type": "object",
|
|
111
|
+
"properties": {
|
|
112
|
+
"id": {
|
|
113
|
+
"type": "string",
|
|
114
|
+
"pattern": "^[\\w-]+$",
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
"required": ["id"],
|
|
118
|
+
},
|
|
119
|
+
}
|
|
120
|
+
]
|
|
121
|
+
|
|
122
|
+
converted = proxy._convert_anthropic_tools_to_openai(anthropic_tools)
|
|
123
|
+
params = converted[0]["function"]["parameters"]
|
|
124
|
+
self.assertEqual(params["properties"]["id"]["type"], "string")
|
|
125
|
+
self.assertNotIn("pattern", params["properties"]["id"])
|
|
126
|
+
|
|
127
|
+
def test_convert_tools_strips_pattern_properties_fields(self):
|
|
128
|
+
anthropic_tools = [
|
|
129
|
+
{
|
|
130
|
+
"name": "Sample",
|
|
131
|
+
"description": "test",
|
|
132
|
+
"input_schema": {
|
|
133
|
+
"type": "object",
|
|
134
|
+
"patternProperties": {
|
|
135
|
+
"^x-": {"type": "string"},
|
|
136
|
+
},
|
|
137
|
+
"properties": {
|
|
138
|
+
"meta": {
|
|
139
|
+
"type": "object",
|
|
140
|
+
"properties": {
|
|
141
|
+
"tag": {
|
|
142
|
+
"type": "string",
|
|
143
|
+
"pattern": "^[a-z]+$",
|
|
144
|
+
}
|
|
145
|
+
},
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
},
|
|
149
|
+
}
|
|
150
|
+
]
|
|
151
|
+
|
|
152
|
+
converted = proxy._convert_anthropic_tools_to_openai(anthropic_tools)
|
|
153
|
+
params = converted[0]["function"]["parameters"]
|
|
154
|
+
self.assertNotIn("patternProperties", params)
|
|
155
|
+
self.assertNotIn("pattern", params["properties"]["meta"]["properties"]["tag"])
|
|
156
|
+
|
|
157
|
+
|
|
103
158
|
class TestStreamGuardedPathSelection(unittest.TestCase):
|
|
104
159
|
def test_required_tool_turn_uses_guarded_non_stream(self):
|
|
105
160
|
old_force = getattr(proxy, "PROXY_FORCE_NON_STREAM")
|