Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 61 additions & 13 deletions 01_funccall.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -353,7 +353,9 @@
" args = get_args(t)\n",
" if not args: return {'type': 'array', 'items': {}}\n",
" if args[-1] is Ellipsis: return {'type': 'array', 'items': _handle_type(args[0], defs)}\n",
" return {'type': 'array', 'prefixItems': [_handle_type(a, defs) for a in args]}\n",
" prefix = [_handle_type(a, defs) for a in args]\n",
" items = prefix[0] if all(p == prefix[0] for p in prefix) else {'anyOf': prefix}\n",
" return {'type': 'array', 'prefixItems': prefix, 'items': items, 'minItems': len(args), 'maxItems': len(args)}\n",
" if ot in (list, set):\n",
" args = get_args(t)\n",
" schema = {'type': 'array', 'items': _handle_type(args[0], defs) if args else {}}\n",
Expand Down Expand Up @@ -388,6 +390,14 @@
"_handle_type(int, None), _handle_type(Path, None)"
]
},
{
"cell_type": "markdown",
"id": "e669cbc1",
"metadata": {},
"source": [
"Fixed-length tuples (e.g. `tuple[int, str]`) are mapped to a JSON Schema array with `prefixItems` for per-position types, which is accepted by Anthropic. OpenAI/Gemini require `items` so this is added, along with `minItems`/`maxItems` to enforce the fixed length. If all positions share the same type, `items` is that type directly; otherwise it's an `anyOf` of the unique types."
]
},
{
"cell_type": "code",
"execution_count": null,
Expand All @@ -398,7 +408,11 @@
"data": {
"text/plain": [
"({'type': 'array', 'items': {}},\n",
" {'type': 'array', 'prefixItems': [{'type': 'string'}]},\n",
" {'type': 'array',\n",
" 'prefixItems': [{'type': 'string'}],\n",
" 'items': {'type': 'string'},\n",
" 'minItems': 1,\n",
" 'maxItems': 1},\n",
" {'type': 'array', 'items': {'type': 'string'}, 'uniqueItems': True})"
]
},
Expand Down Expand Up @@ -571,7 +585,7 @@
"source": [
"# Test primitive types in containers\n",
"test_eq(_handle_type(list[int], defs), {'type': 'array', 'items': {'type': 'integer'}})\n",
"test_eq(_handle_type(tuple[str], defs), {'type': 'array', 'prefixItems': [{'type': 'string'}]})\n",
"test_eq(_handle_type(tuple[str], defs), {'type': 'array', 'prefixItems': [{'type': 'string'}], 'items': {'type': 'string'}, 'minItems': 1, 'maxItems': 1})\n",
"test_eq(_handle_type(set[str], defs), dict(type='array', items={'type': 'string'}, uniqueItems=True))\n",
"test_eq(_handle_type(dict[str,bool], defs), {'type': 'object', 'additionalProperties': {'type': 'boolean'}})"
]
Expand Down Expand Up @@ -736,7 +750,10 @@
" 'properties': {'o': {'description': 'the o', 'type': 'object'},\n",
" 'q': {'description': '',\n",
" 'type': 'array',\n",
" 'prefixItems': [{'type': 'integer'}, {'type': 'string'}]},\n",
" 'prefixItems': [{'type': 'integer'}, {'type': 'string'}],\n",
" 'items': {'anyOf': [{'type': 'integer'}, {'type': 'string'}]},\n",
" 'minItems': 2,\n",
" 'maxItems': 2},\n",
" 'p': {'description': '',\n",
" 'default': 'a',\n",
" 'anyOf': [{'type': 'string'},\n",
Expand All @@ -756,7 +773,7 @@
"test_eq(s['name'], 'f')\n",
"inpp = s['input_schema']['properties']\n",
"test_eq(inpp['o'], {'type': 'object', 'description': 'the o'})\n",
"test_eq(inpp['q'], dict(type='array', description='', prefixItems=[{'type': 'integer'}, {'type': 'string'}]))\n",
"test_eq(inpp['q'], dict(type='array', description='', prefixItems=[{'type': 'integer'}, {'type': 'string'}], items=dict(anyOf=[{'type': 'integer'}, {'type': 'string'}]), minItems=2, maxItems=2))\n",
"test_eq(inpp['p'], dict(description='', default='a', anyOf=[{'type': 'string'}, {'type': 'array', 'items': {'type': 'string'}}]))\n",
"s"
]
Expand Down Expand Up @@ -1172,7 +1189,10 @@
" 'properties': {'opt_tup': {'description': '',\n",
" 'default': None,\n",
" 'anyOf': [{'type': 'array',\n",
" 'prefixItems': [{'type': 'integer'}, {'type': 'integer'}]},\n",
" 'prefixItems': [{'type': 'integer'}, {'type': 'integer'}],\n",
" 'items': {'type': 'integer'},\n",
" 'minItems': 2,\n",
" 'maxItems': 2},\n",
" {'type': 'string'},\n",
" {'type': 'integer'}]}}}}"
]
Expand Down Expand Up @@ -1215,7 +1235,10 @@
" 'properties': {'opt_tup': {'description': '',\n",
" 'default': None,\n",
" 'anyOf': [{'type': 'array',\n",
" 'prefixItems': [{'type': 'integer'}, {'type': 'integer'}]},\n",
" 'prefixItems': [{'type': 'integer'}, {'type': 'integer'}],\n",
" 'items': {'type': 'integer'},\n",
" 'minItems': 2,\n",
" 'maxItems': 2},\n",
" {'type': 'string'},\n",
" {'type': 'integer'}]}}}}"
]
Expand Down Expand Up @@ -1257,7 +1280,10 @@
" 'properties': {'opt_tup': {'description': '',\n",
" 'default': None,\n",
" 'anyOf': [{'type': 'array',\n",
" 'prefixItems': [{'type': 'integer'}, {'type': 'integer'}]},\n",
" 'prefixItems': [{'type': 'integer'}, {'type': 'integer'}],\n",
" 'items': {'type': 'integer'},\n",
" 'minItems': 2,\n",
" 'maxItems': 2},\n",
" {'type': 'null'}]}}}}"
]
},
Expand Down Expand Up @@ -1620,7 +1646,26 @@
"execution_count": null,
"id": "b2c66a73",
"metadata": {},
"outputs": [],
"outputs": [
{
"data": {
"text/plain": [
"{'name': '_cust_type',\n",
" 'description': 'Mandatory docstring',\n",
" 'input_schema': {'type': 'object',\n",
" 'properties': {'a': {'description': '',\n",
" 'anyOf': [{'type': 'string'},\n",
" {'type': 'array', 'items': {'type': 'string'}}]}},\n",
" 'required': ['a']}}"
]
},
"execution_count": null,
"metadata": {
"__type": "dict"
},
"output_type": "execute_result"
}
],
"source": [
"Cmd = str | list[str]\n",
"\n",
Expand Down Expand Up @@ -1856,14 +1901,14 @@
"output_type": "stream",
"text": [
"Traceback (most recent call last):\n",
" File \"/var/folders/51/b2_szf2945n072c0vj2cyty40000gn/T/ipykernel_38179/3422083997.py\", line 13, in python\n",
" File \"/var/folders/wm/y9k35r7n7q56mvx2wnndd0880000gp/T/ipykernel_61561/3422083997.py\", line 13, in python\n",
" try: return _run(code, glb, loc)\n",
" ^^^^^^^^^^^^^^^^^^^^\n",
" File \"/var/folders/51/b2_szf2945n072c0vj2cyty40000gn/T/ipykernel_38179/2387754473.py\", line 17, in _run\n",
" File \"/var/folders/wm/y9k35r7n7q56mvx2wnndd0880000gp/T/ipykernel_61561/2387754473.py\", line 17, in _run\n",
" try: exec(compiled_code, glb, loc)\n",
" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n",
" File \"<ast>\", line 1, in <module>\n",
" File \"/var/folders/51/b2_szf2945n072c0vj2cyty40000gn/T/ipykernel_38179/3422083997.py\", line 8, in handler\n",
" File \"/var/folders/wm/y9k35r7n7q56mvx2wnndd0880000gp/T/ipykernel_61561/3422083997.py\", line 8, in handler\n",
" def handler(*args): raise TimeoutError()\n",
" ^^^^^^^^^^^^^^^^^^^^\n",
"TimeoutError\n",
Expand Down Expand Up @@ -2771,7 +2816,10 @@
]
}
],
"metadata": {},
"metadata": {
"solveit_dialog_mode": "learning",
"solveit_ver": 2
},
"nbformat": 4,
"nbformat_minor": 5
}
4 changes: 3 additions & 1 deletion toolslm/funccall.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,9 @@ def _handle_type(t, defs):
args = get_args(t)
if not args: return {'type': 'array', 'items': {}}
if args[-1] is Ellipsis: return {'type': 'array', 'items': _handle_type(args[0], defs)}
return {'type': 'array', 'prefixItems': [_handle_type(a, defs) for a in args]}
prefix = [_handle_type(a, defs) for a in args]
items = prefix[0] if all(p == prefix[0] for p in prefix) else {'anyOf': prefix}
return {'type': 'array', 'prefixItems': prefix, 'items': items, 'minItems': len(args), 'maxItems': len(args)}
if ot in (list, set):
args = get_args(t)
schema = {'type': 'array', 'items': _handle_type(args[0], defs) if args else {}}
Expand Down