# funccall


<!-- WARNING: THIS FILE WAS AUTOGENERATED! DO NOT EDIT! -->

## Function calling

### Function to schema

Many LLMs do function calling (aka tool use) by taking advantage of JSON
schema.

We’ll use [docments](https://fastcore.fast.ai/docments.html) to make
getting JSON schema from Python functions as ergonomic as possible. Each
parameter (and the return value) should have a type, and a docments
comment with the description of what it is. Here’s an example:

``` python
def silly_sum(
    a:int, # First thing to sum
    b:int=1, # Second thing to sum
    c:list[int]=None, # A pointless argument
) -> int: # The sum of the inputs
    "Adds a + b."
    return a + b
```

This is what
[`docments`](https://fastcore.fast.ai/docments.html#docments) makes of
that:

``` python
d = docments(silly_sum, full=True)
d
```

``` python
{ 'a': { 'anno': <class 'int'>,
         'default': <class 'inspect._empty'>,
         'docment': 'First thing to sum'},
  'b': {'anno': <class 'int'>, 'default': 1, 'docment': 'Second thing to sum'},
  'c': {'anno': list[int], 'default': None, 'docment': 'A pointless argument'},
  'return': { 'anno': <class 'int'>,
              'default': <class 'inspect._empty'>,
              'docment': 'The sum of the inputs'}}
```

Note that this is an
[AttrDict](https://fastcore.fast.ai/basics.html#attrdict) so we can
treat it like an object, *or* a dict:

``` python
d.a.docment, d['a']['anno']
```

    ('First thing to sum', int)

This private function converts a key/value pair from the
[`docments`](https://fastcore.fast.ai/docments.html#docments) structure
into the `dict` that will be needed for the schema.

``` python
n,o = first(d.items())
print(n,'//', o)
_param(o)
```

    a // {'docment': 'First thing to sum', 'anno': <class 'int'>, 'default': <class 'inspect._empty'>}

    {'description': 'First thing to sum'}

``` python
n,o
```

    ('a',
     {'docment': 'First thing to sum', 'anno': int, 'default': inspect._empty})

``` python
d
```

``` python
{ 'a': { 'anno': <class 'int'>,
         'default': <class 'inspect._empty'>,
         'docment': 'First thing to sum'},
  'b': {'anno': <class 'int'>, 'default': 1, 'docment': 'Second thing to sum'},
  'c': {'anno': list[int], 'default': None, 'docment': 'A pointless argument'},
  'return': { 'anno': <class 'int'>,
              'default': <class 'inspect._empty'>,
              'docment': 'The sum of the inputs'}}
```

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/fastcore/blob/main/fastcore/funccall.py#L86"
target="_blank" style="float:right; font-size:smaller">source</a>

### union_schema

``` python
def union_schema(
    t, defs:NoneType=None
):
```

*Schema for union type `t`: its schema-representable members, unwrapped
if only one, `anyOf` otherwise*

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/fastcore/blob/main/fastcore/funccall.py#L52"
target="_blank" style="float:right; font-size:smaller">source</a>

### handle_type

``` python
def handle_type(
    t, defs
):
```

*Convert a type annotation to JSON Schema*

Not every Python type has a JSON Schema representation: bare `object`
and `Any` say nothing a model could act on, and a missing annotation is
treated the same way;
[`handle_type`](https://fastcore.fast.ai/funccall.html#handle_type)
raises `TypeError` for them all.
[`union_schema`](https://fastcore.fast.ai/funccall.html#union_schema)
builds a union’s schema from just its schema-representable members,
unwrapping when only one remains, so an annotation like `str|object`
(meaning “pass a string; other objects accepted at runtime”) collapses
to a plain string schema:

``` python
test_eq(union_schema(str|object), {'type': 'string'})
test_eq(union_schema(int|str), {'anyOf': [{'type': 'integer'}, {'type': 'string'}]})
test_fail(lambda: union_schema(object|Any), contains="No schema-representable member")
test_is(_schemable(object), None)
```

``` python
handle_type(int, None), handle_type(Path, None)
```

    ({'type': 'integer'}, {'type': 'string', 'format': 'Path'})

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.

``` python
# gemini expect `items` to be defined for arrays
handle_type(list, None), handle_type(tuple[str], None), handle_type(set[str], None)
```

    ({'type': 'array', 'items': {'type': 'string'}},
     {'type': 'array',
      'prefixItems': [{'type': 'string'}],
      'items': {'type': 'string'},
      'minItems': 1,
      'maxItems': 1},
     {'type': 'array', 'items': {'type': 'string'}, 'uniqueItems': True})

Tool parameters must be fully annotated: a missing annotation, or a type
with no schema representation (bare `object` or `Any`), raises rather
than producing a junk schema. Unions are filtered to just their
schema-representable members, so `str|object` means “a string, but any
object accepted at runtime”, and collapses to a plain string schema. A
union with no representable member raises:

``` python
test_fail(lambda: handle_type(object, None), contains="Can't make a schema")
test_eq(handle_type(str|object, None), {'type': 'string'})
test_eq(handle_type(int|str, None), {'anyOf': [{'type': 'integer'}, {'type': 'string'}]})
test_fail(lambda: handle_type(object|Any, None), contains="No schema-representable member")
```

``` python
handle_type(dict, None), handle_type(dict[str,str], None)
```

    ({'type': 'object'},
     {'type': 'object', 'additionalProperties': {'type': 'string'}})

``` python
def _example_new_unioin(opt_tup: str | None): pass

d = docments(_example_new_unioin, full=True)
anno1 = first(d.items())[1].anno
(anno1, get_origin(anno1), get_args(anno1))
```

    (str | None, types.UnionType, (str, NoneType))

``` python
def _example_old_union(opt_tup: Union[str, type(None)] =None): pass

d = docments(_example_old_union, full=True)
anno2 = first(d.items())[1].anno
(anno2, get_origin(anno2), get_args(anno2))
```

    (typing.Optional[str], typing.Union, (str, NoneType))

We treat non-specialised arrays as arrays of strs, since some LLMs
require a subtype:

``` python
test_eq(handle_type(list, {}), {'type': 'array', 'items': {'type': 'string'}})
test_eq(handle_type(set, {}), {'type': 'array', 'items': {'type': 'string'}, 'uniqueItems': True})
```

``` python
# Test primitive types
defs = {}
assert handle_type(int, defs) == {'type': 'integer'}
assert handle_type(str, defs) == {'type': 'string'}
assert handle_type(bool, defs) == {'type': 'boolean'}
assert handle_type(float, defs) == {'type': 'number'}

# Test custom class
class TestClass:
    def __init__(self, x: int, y: int): store_attr()

result = handle_type(TestClass, defs)
assert result == {'$ref': '#/$defs/TestClass'}
assert 'TestClass' in defs
assert defs['TestClass']['type'] == 'object'
assert 'properties' in defs['TestClass']

# tuple[int, ...] should produce array with items, not prefixItems
test_eq(handle_type(tuple[int, ...], {}), {'type': 'array', 'items': {'type': 'integer'}})
```

``` python
# Test primitive types in containers
test_eq(handle_type(list[int], defs), {'type': 'array', 'items': {'type': 'integer'}})
test_eq(handle_type(tuple[str], defs), {'type': 'array', 'prefixItems': [{'type': 'string'}], 'items': {'type': 'string'}, 'minItems': 1, 'maxItems': 1})
test_eq(handle_type(set[str], defs), dict(type='array', items={'type': 'string'}, uniqueItems=True))
test_eq(handle_type(dict[str,bool], defs), {'type': 'object', 'additionalProperties': {'type': 'boolean'}})
```

``` python
result = handle_type(list[TestClass], defs)
assert result == {'type': 'array', 'items': {'$ref': '#/$defs/TestClass'}}
assert 'TestClass' in defs

# Test complex nested structure
ComplexType = dict[str, list[TestClass]]
result = handle_type(dict[str, list[TestClass]], defs)
assert result == {'type': 'object', 'additionalProperties': {'type': 'array', 'items': {'$ref': '#/$defs/TestClass'}}}
```

``` python
# Test processing of a required integer property
props, req = {}, {}
class TestClass:
    "Test class"
    def __init__(
        self,
        x: int, # First thing
        y: list[float], # Second thing
        z: str = "default", # Third thing
    ): store_attr()

d = docments(TestClass, full=True)
_process_property('x', d.x, props, req, defs)
assert 'x' in props
assert props['x']['type'] == 'integer'
assert 'x' in req

# Test processing of a required list property
_process_property('y', d.y, props, req, defs)
assert 'y' in props
assert props['y']['type'] == 'array'
assert props['y']['items']['type'] == 'number'
assert 'y' in req

# Test processing of an optional string property with default
_process_property('z', d.z, props, req, defs)
assert 'z' in props
assert props['z']['type'] == 'string'
assert props['z']['default'] == "default"
assert 'z' not in req
```

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/fastcore/blob/main/fastcore/funccall.py#L121"
target="_blank" style="float:right; font-size:smaller">source</a>

### get_schema

``` python
def get_schema(
    f:Union, # Function to get schema for
    pname:str='input_schema', # Key name for parameters
    evalable:bool=False, # stringify defaults that can't be literal_eval'd?
    skip_hidden:bool=False, # skip parameters starting with '_'?
    name:NoneType=None, # Override function name (useful for dotted paths like 'obj.method')
)->dict: # {'name':..., 'description':..., pname:...}
```

*Generate JSON schema for a class, function, or method*

``` python
get_schema(get_schema)
```

    {'name': 'get_schema',
     'description': "Generate JSON schema for a class, function, or method\n\nReturns:\n- {'name':..., 'description':..., pname:...} (type: object)",
     'input_schema': {'type': 'object',
      'properties': {'f': {'description': 'Function to get schema for',
        'anyOf': [{'type': 'object'}, {'type': 'object'}]},
       'pname': {'description': 'Key name for parameters',
        'default': 'input_schema',
        'type': 'string'},
       'evalable': {'description': "stringify defaults that can't be literal_eval'd?",
        'default': False,
        'type': 'boolean'},
       'skip_hidden': {'description': "skip parameters starting with '_'?",
        'default': False,
        'type': 'boolean'},
       'name': {'description': "Override function name (useful for dotted paths like 'obj.method')",
        'default': None,
        'type': 'null'}},
      'required': ['f']}}

``` python
def f(
    o:dict, # the o
    q:tuple[int,str],
    p:str|list[str] = 'a',
): "dict function"
```

``` python
s = get_schema(f)
test_eq(s['name'], 'f')
inpp = s['input_schema']['properties']
test_eq(inpp['o'], {'type': 'object', 'description': 'the o'})
test_eq(inpp['q'], dict(type='array', description='', prefixItems=[{'type': 'integer'}, {'type': 'string'}], items=dict(anyOf=[{'type': 'integer'}, {'type': 'string'}]), minItems=2, maxItems=2))
test_eq(inpp['p'], dict(description='', default='a', anyOf=[{'type': 'string'}, {'type': 'array', 'items': {'type': 'string'}}]))
s
```

    {'name': 'f',
     'description': 'object function',
     'input_schema': {'type': 'object',
      'properties': {'o': {'description': 'the o', 'type': 'object'},
       'q': {'description': '',
        'type': 'array',
        'prefixItems': [{'type': 'integer'}, {'type': 'string'}],
        'items': {'anyOf': [{'type': 'integer'}, {'type': 'string'}]},
        'minItems': 2,
        'maxItems': 2},
       'p': {'description': '',
        'default': 'a',
        'anyOf': [{'type': 'string'},
         {'type': 'array', 'items': {'type': 'string'}}]}},
      'required': ['o', 'q']}}

``` python
def _noanno(x, y:int=0):
    "Docs"
    return x
test_fail(lambda: get_schema(_noanno), contains="Parameter 'x': Missing type annotation")
```

``` python
class ClassA:
    "I am a class"
    def f(self, a:int): # That is `a`
        "Do a thing"
        return 1
    def __call__(self, b:str): # That is `b`
        "Do another thing"
        return 2

ca = ClassA()
ca.f(2)
```

    1

``` python
get_schema(ca.f)
```

    {'name': 'f',
     'description': 'Do a thing',
     'input_schema': {'type': 'object',
      'properties': {'a': {'description': 'That is `a`', 'type': 'integer'}},
      'required': ['a']}}

``` python
get_schema(ca)
```

    {'name': '__call__',
     'description': 'Do another thing',
     'input_schema': {'type': 'object',
      'properties': {'b': {'description': 'That is `b`', 'type': 'string'}},
      'required': ['b']}}

### Usage examples

Putting this all together, we can now test getting a schema from
`silly_sum`. The tool use spec doesn’t support return annotations
directly, so we put that in the description instead.

``` python
s = get_schema(silly_sum)
desc = s.pop('description')
print(desc)
s
```

    Adds a + b.

    Returns:
    - The sum of the inputs (type: integer)

    {'name': 'silly_sum',
     'input_schema': {'type': 'object',
      'properties': {'a': {'description': 'First thing to sum', 'type': 'integer'},
       'b': {'description': 'Second thing to sum',
        'default': 1,
        'type': 'integer'},
       'c': {'description': 'A pointless argument',
        'default': None,
        'type': 'array',
        'items': {'type': 'integer'}}},
      'required': ['a']}}

This also works with string annotations, e.g:

``` python
def silly_test(
    a: 'int',  # quoted type hint
)->int:
    "Mandatory docstring"
    return a

get_schema(silly_test)
```

    {'name': 'silly_test',
     'description': 'Mandatory docstring\n\nReturns:\n- type: integer',
     'input_schema': {'type': 'object',
      'properties': {'a': {'description': 'quoted type hint', 'type': 'integer'}},
      'required': ['a']}}

This also works with instance methods:

``` python
class Dummy:
    def sums(
        self,
        a:int,  # First thing to sum
        b:int=1 # Second thing to sum
    ): # The sum of the inputs
        "Adds a + b."
        print(f"Finding the sum of {a} and {b}")
        return a + b

get_schema(Dummy.sums)
```

    {'name': 'sums',
     'description': 'Adds a + b.',
     'input_schema': {'type': 'object',
      'properties': {'a': {'description': 'First thing to sum', 'type': 'integer'},
       'b': {'description': 'Second thing to sum',
        'default': 1,
        'type': 'integer'}},
      'required': ['a']}}

[`get_schema`](https://fastcore.fast.ai/funccall.html#get_schema) also
handles more complicated structures such as nested classes. This is
useful for things like structured outputs.

``` python
class Turn:
    "Turn between two speakers"
    def __init__(
        self,
        speaker_a:str, # First speaker's message
        speaker_b:str,  # Second speaker's message
    ): store_attr()

class Conversation:
    "A conversation between two speakers"
    def __init__(
        self,
        turns:list[Turn], # Turns of the conversation
    ): store_attr()

get_schema(Conversation)
```

    {'name': 'Conversation',
     'description': 'A conversation between two speakers',
     'input_schema': {'type': 'object',
      'properties': {'turns': {'description': 'Turns of the conversation',
        'type': 'array',
        'items': {'$ref': '#/$defs/Turn'}}},
      'title': 'Conversation',
      'required': ['turns'],
      '$defs': {'Turn': {'type': 'object',
        'properties': {'speaker_a': {'description': "First speaker's message",
          'type': 'string'},
         'speaker_b': {'description': "Second speaker's message",
          'type': 'string'}},
        'title': 'Turn',
        'required': ['speaker_a', 'speaker_b']}}}}

``` python
class DictConversation:
    "A conversation between two speakers"
    def __init__(
        self,
        turns:dict[str,Turn], # dictionary of topics and the Turns of the conversation
    ): store_attr()

get_schema(DictConversation)
```

    {'name': 'DictConversation',
     'description': 'A conversation between two speakers',
     'input_schema': {'type': 'object',
      'properties': {'turns': {'description': 'dictionary of topics and the Turns of the conversation',
        'type': 'object',
        'additionalProperties': {'type': 'object'}}},
      'title': 'DictConversation',
      'required': ['turns']}}

``` python
class SetConversation:
    "A conversation between two speakers"
    def __init__(
        self,
        turns:set[Turn], # the unique Turns of the conversation
    ): store_attr()

get_schema(SetConversation)
```

    {'name': 'SetConversation',
     'description': 'A conversation between two speakers',
     'input_schema': {'type': 'object',
      'properties': {'turns': {'description': 'the unique Turns of the conversation',
        'type': 'array',
        'items': {'$ref': '#/$defs/Turn'},
        'uniqueItems': True}},
      'title': 'SetConversation',
      'required': ['turns'],
      '$defs': {'Turn': {'type': 'object',
        'properties': {'speaker_a': {'description': "First speaker's message",
          'type': 'string'},
         'speaker_b': {'description': "Second speaker's message",
          'type': 'string'}},
        'title': 'Turn',
        'required': ['speaker_a', 'speaker_b']}}}}

### Additional [`get_schema()`](https://fastcore.fast.ai/funccall.html#get_schema) Test Cases

Union types are represented in JSON Schema using `anyOf`.

``` python
IntPair = tuple[int, int]
def _union_test(opt_tup: Union[IntPair, str, int]=None):
    "Mandatory docstring"
    return ""
get_schema(_union_test)
```

    {'name': '_union_test',
     'description': 'Mandatory docstring',
     'input_schema': {'type': 'object',
      'properties': {'opt_tup': {'description': '',
        'default': None,
        'anyOf': [{'type': 'array',
          'prefixItems': [{'type': 'integer'}, {'type': 'integer'}],
          'items': {'type': 'integer'},
          'minItems': 2,
          'maxItems': 2},
         {'type': 'string'},
         {'type': 'integer'}]}}}}

The new (Python 3.10+) union syntax can also be used, producing an
equivalent schema.

``` python
def _new_union_test(opt_tup: IntPair | str | int =None):
    "Mandatory docstring"
    pass
get_schema(_new_union_test)
```

    {'name': '_new_union_test',
     'description': 'Mandatory docstring',
     'input_schema': {'type': 'object',
      'properties': {'opt_tup': {'description': '',
        'default': None,
        'anyOf': [{'type': 'array',
          'prefixItems': [{'type': 'integer'}, {'type': 'integer'}],
          'items': {'type': 'integer'},
          'minItems': 2,
          'maxItems': 2},
         {'type': 'string'},
         {'type': 'integer'}]}}}}

Optional is a special case of union types, limited to two types, one of
which is None (mapped to null in JSON schema):

``` python
def _optional_test(opt_tup: Optional[IntPair]=None):
    "Mandatory docstring"
    pass
get_schema(_optional_test)
```

    {'name': '_optional_test',
     'description': 'Mandatory docstring',
     'input_schema': {'type': 'object',
      'properties': {'opt_tup': {'description': '',
        'default': None,
        'anyOf': [{'type': 'array',
          'prefixItems': [{'type': 'integer'}, {'type': 'integer'}],
          'items': {'type': 'integer'},
          'minItems': 2,
          'maxItems': 2},
         {'type': 'null'}]}}}}

``` python
def _param_union_test(items: list[str] | None = None):
    "Test parameterized container in union"
    pass
get_schema(_param_union_test)
```

    {'name': '_param_union_test',
     'description': 'Test parameterized container in union',
     'input_schema': {'type': 'object',
      'properties': {'items': {'description': '',
        'default': None,
        'anyOf': [{'type': 'array', 'items': {'type': 'string'}},
         {'type': 'null'}]}}}}

Containers can also be used, both in their parameterized form
(`List[int]`) or as their unparameterized raw type (`List`). In the
latter case, the item type is mapped to `object` in JSON schema.

``` python
def _list_test(l: List[int]):
    "Mandatory docstring"
    pass
get_schema(_list_test)
```

    {'name': '_list_test',
     'description': 'Mandatory docstring',
     'input_schema': {'type': 'object',
      'properties': {'l': {'description': '',
        'type': 'array',
        'items': {'type': 'integer'}}},
      'required': ['l']}}

``` python
def _raw_list_test(l: List):
    "Mandatory docstring"
    pass
get_schema(_raw_list_test)
```

    {'name': '_raw_list_test',
     'description': 'Mandatory docstring',
     'input_schema': {'type': 'object',
      'properties': {'l': {'description': '',
        'type': 'array',
        'items': {'type': 'string'}}},
      'required': ['l']}}

The same applies to dictionary, which can similarly be parameterized
with key/value types or specified as a raw type.

``` python
def _dict_test(d: Dict[str, int]):
    "Mandatory docstring"
    pass
get_schema(_dict_test)
```

    {'name': '_dict_test',
     'description': 'Mandatory docstring',
     'input_schema': {'type': 'object',
      'properties': {'d': {'description': '',
        'type': 'object',
        'additionalProperties': {'type': 'integer'}}},
      'required': ['d']}}

``` python
def _raw_dict_test(d: Dict):
    "Mandatory docstring"
get_schema(_raw_dict_test)
```

    {'name': '_raw_dict_test',
     'description': 'Mandatory docstring',
     'input_schema': {'type': 'object',
      'properties': {'d': {'description': '', 'type': 'object'}},
      'required': ['d']}}

``` python
def _path_test(path: Path = Path('.')):
    "Mandatory docstring"
get_schema(_path_test)
```

    {'name': '_path_test',
     'description': 'Mandatory docstring',
     'input_schema': {'type': 'object',
      'properties': {'path': {'description': '',
        'default': Path('.'),
        'type': 'string',
        'format': 'Path'}}}}

Schemas that need to be converted using `ast.literal_eval` will fail
with non-primitive defaults:

``` python
test_fail(lambda: ast.literal_eval(str(get_schema(_path_test))), exc=ValueError)
```

Use `evalable` to have those defaults stringified:

``` python
def _path_test(path: Path = Path('.')):
    "Mandatory docstring"
get_schema(_path_test, evalable=True)
```

    {'name': '_path_test',
     'description': 'Mandatory docstring',
     'input_schema': {'type': 'object',
      'properties': {'path': {'description': '',
        'default': '.',
        'type': 'string',
        'format': 'Path'}}}}

Use `skip_hidden` to exclude parameters starting with `_` from the
schema:

``` python
def test_hidden(a: int, _internal: str = "x"):
    "Test func"
    pass

get_schema(test_hidden, skip_hidden=True)  # should exclude _internal
```

    {'name': 'test_hidden',
     'description': 'Test func',
     'input_schema': {'type': 'object',
      'properties': {'a': {'description': '', 'type': 'integer'}},
      'required': ['a']}}

``` python
get_schema(test_hidden)
```

    {'name': 'test_hidden',
     'description': 'Test func',
     'input_schema': {'type': 'object',
      'properties': {'a': {'description': '', 'type': 'integer'},
       '_internal': {'description': '', 'default': 'x', 'type': 'string'}},
      'required': ['a']}}

``` python
def _ret_list_test(a: int) -> list[str]:
    "Mandatory docstring"

s = get_schema(_ret_list_test)
assert 'type: array[string]' in s['description']
```

``` python
Cmd = str | list[str]

def _cust_type(a: Cmd): "Mandatory docstring"

s = get_schema(_cust_type)
s
```

    {'name': '_cust_type',
     'description': 'Mandatory docstring',
     'input_schema': {'type': 'object',
      'properties': {'a': {'description': '',
        'anyOf': [{'type': 'string'},
         {'type': 'array', 'items': {'type': 'string'}}]}},
      'required': ['a']}}

### Tool Calling

Many LLM API providers offer tool calling where an LLM can choose to
call a given tool. This is also helpful for structured outputs since the
response from the LLM is contrained to the required arguments of the
tool.

This section will be dedicated to helper functions for calling tools. We
don’t want to allow LLMs to call just any possible function (that would
be a security disaster!) so we create a namespace – that is, a
dictionary of allowable function names to call.

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/fastcore/blob/main/fastcore/funccall.py#L148"
target="_blank" style="float:right; font-size:smaller">source</a>

### mk_ns

``` python
def mk_ns(
    fs
):
```

*Call self as a function.*

``` python
def sums(a, b): return a + b
ns = mk_ns(sums)
ns
```

    {'sums': <function __main__.sums(a, b)>}

``` python
ns['sums'](1, 2)
```

    3

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/fastcore/blob/main/fastcore/funccall.py#L170"
target="_blank" style="float:right; font-size:smaller">source</a>

### coerce_inputs

``` python
def coerce_inputs(
    func, inputs
):
```

*Coerce inputs based on function type annotations, repairing common LLM
container mistakes*

``` python
ca = ClassA()
```

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/fastcore/blob/main/fastcore/funccall.py#L189"
target="_blank" style="float:right; font-size:smaller">source</a>

### resolve_nm

``` python
def resolve_nm(
    nm, ns
):
```

*Call self as a function.*

``` python
test_eq(resolve_nm('ca.f', globals()), ca.f)
```

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/fastcore/blob/main/fastcore/funccall.py#L194"
target="_blank" style="float:right; font-size:smaller">source</a>

### get_schema_nm

``` python
def get_schema_nm(
    nm:str, ns, dot2dash:bool=False, **kwargs
):
```

*Get schema for symbol `nm` in namespace `ns`, preserving the full
dotted name*

``` python
schema = get_schema_nm('ca.f', locals())
test_eq(schema['name'], 'ca.f')
schema
```

    {'name': 'ca.f',
     'description': 'Do a thing',
     'input_schema': {'type': 'object',
      'properties': {'a': {'description': 'That is `a`', 'type': 'integer'}},
      'required': ['a']}}

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/fastcore/blob/main/fastcore/funccall.py#L222"
target="_blank" style="float:right; font-size:smaller">source</a>

### call_func

``` python
def call_func(
    fc_name, fc_inputs, ns, raise_on_err:bool=True
):
```

*Call the function `fc_name` with the given `fc_inputs` using namespace
`ns`.*

Now when we an LLM responses with the tool to use and its inputs, we can
simply use the same namespace it was given to look up the tool and call
it.

``` python
call_func('sums', {'a': 1, 'b': 2}, ns=[sums])
```

    3

``` python
assert "unsupported operand type(s) for +: 'int' and 'str'" in call_func('sums', {'a': 1, 'b': '3'}, ns=ns, raise_on_err=False)
```

``` python
test_fail(call_func, args=['sums', {'a': 1, 'b': '3'}], kwargs={'ns': ns})
```

Types that can be constructed from a plain `str` can be used directly,
as long as they are in `custom_types` (which you can add to).

``` python
def path_test(
    a: Path,  # a type hint
    b: Path   # b type hint
):
    "Mandatory docstring"
    return a/b

test_eq(call_func('path_test', {'a': '/home', 'b': 'user'}, ns=[path_test]), Path('/home/user'))
```

``` python
test_eq(call_func('ca.f', {'a': 5}, ns=globals()), 1)
```

Sometimes LLMs accidentally use a str instead of a real dict/list, or
pass `{}` instead of a list. We coerce these:

``` python
def list_test(a:list[str], b:list[str]=None, c:str|list[str]='x'): return a,b,c
test_eq(call_func('list_test', {'a': '["x","y"]', 'b': None, 'c': 'z'}, ns=[list_test]), (['x','y'], None, 'z'))
test_eq(call_func('list_test', {'a': {}, 'b': 'foo'}, ns=[list_test]), ([], ['foo'], 'x'))
```

### Async function calling

``` python
async def asums(a, b): return a + b
ns = mk_ns(asums)
ns
```

    {'asums': <function __main__.asums(a, b)>}

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/fastcore/blob/main/fastcore/funccall.py#L234"
target="_blank" style="float:right; font-size:smaller">source</a>

### call_func_async

``` python
async def call_func_async(
    fc_name, fc_inputs, ns, raise_on_err:bool=True
):
```

*Awaits the function `fc_name` with the given `fc_inputs` using
namespace `ns`.*

Testing async
[`call_func_async`](https://fastcore.fast.ai/funccall.html#call_func_async)
both with sync and async functions.

``` python
test_eq(await call_func_async('asums', {'a': 1, 'b': 2}, ns=[asums]), 3)
```

``` python
test_eq(await call_func_async('sums', {'a': 1, 'b': 2}, ns=[sums]), 3)
```

``` python
r = await call_func_async('asums', {'a': 1, 'b': '2'}, ns=[asums], raise_on_err=False)
assert "unsupported operand type(s) for +: 'int' and 'str'" in r
```

``` python
ex = False
try: await call_func_async('asums', {'a': 1, 'b': '2'}, ns=[asums], raise_on_err=True)
except: ex = True
assert ex
```

``` python
class B:
    async def g(self, x:int): return x*2

b = B()
res = await call_func_async('b.g', {'x': 5}, ns=globals())
test_eq(res, 10)
```

``` python
async def list_test(a:list, b:list[str]=None, c:str|list[str]='x'): return a,b,c
test_eq(await call_func_async('list_test', {'a': '["x","y"]', 'b': None, 'c': 'z'}, ns=[list_test]), (['x','y'], None, 'z'))
```

## Schema to function

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/fastcore/blob/main/fastcore/funccall.py#L249"
target="_blank" style="float:right; font-size:smaller">source</a>

### mk_param

``` python
def mk_param(
    orig, props, req, pynm:NoneType=None
):
```

*Create a `Parameter` for `orig` with schema `props`*

``` python
tool = dict2obj(dict(
    description='Find real-…',
    inputSchema={
        '$schema': 'http://json-schema.org/draft-07/schema#',
        **dict(
            additionalProperties=False,
            properties=dict(
                language=dict(description='Filter by …', items={'type': 'string'}, type='array'),
                matchCase=dict(default=False, description='Whether th…', type='boolean'),
                path=dict(description='Filter by …', type='string'),
                query=dict(description='The litera…', type='string'),
                useRegexp=dict(default=False, description='Whether to…', type='boolean')),
            required=['query'],
            type='object')},
    name='searchGitHub'))
```

``` python
props, req = tool.inputSchema['properties'], tool.inputSchema['required']
list(props)
```

    ['language', 'matchCase', 'path', 'query', 'useRegexp']

``` python
props.matchCase
```

``` python
{'default': False, 'description': 'Whether th…', 'type': 'boolean'}
```

``` python
p = mk_param('query', props.query, req)
p, p.kind
```

    (<Parameter "query: str">, <_ParameterKind.POSITIONAL_OR_KEYWORD: 1>)

``` python
p = mk_param('language', props.language, req)
p, p.kind
```

    (<Parameter "language: list[str] = None">, <_ParameterKind.KEYWORD_ONLY: 3>)

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/fastcore/blob/main/fastcore/funccall.py#L262"
target="_blank" style="float:right; font-size:smaller">source</a>

### schema2sig

``` python
def schema2sig(
    tool
):
```

*Convert json schema `tool` to a `Signature`*

``` python
schema2sig(tool)
```

    <Signature (query: str, *, language: list[str] = None, matchCase: bool = False, path: str = None, useRegexp: bool = False)>

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/fastcore/blob/main/fastcore/funccall.py#L270"
target="_blank" style="float:right; font-size:smaller">source</a>

### mk_tool

``` python
def mk_tool(
    dispfn, tool
):
```

*Create a callable function from a JSON schema tool definition*

[`mk_tool`](https://fastcore.fast.ai/funccall.html#mk_tool) is the
inverse of
[`get_schema`](https://fastcore.fast.ai/funccall.html#get_schema) — it
creates a callable Python function from a JSON schema tool definition.
This is useful for MCP clients where tools are defined as schemas but
need to be called as regular Python functions.

The created function has a proper signature, docstring, and annotations,
so it works well with IDE autocomplete and introspection.

``` python
def dispatch_eg(name, **kwargs): return f"Called {name} with {kwargs}"

fn = mk_tool(dispatch_eg, tool)
fn('hello', path='src/')
```

    "Called searchGitHub with {'query': 'hello', 'path': 'src/'}"

``` python
tool_hy = dict2obj(dict(name='run', description='Run command', inputSchema=dict(type='object', properties={'cmd': {'type': 'string'}, 'approval-policy': {'type': 'string', 'default': 'never'}}, required=['cmd'])))

sig = schema2sig(tool_hy)
test_eq(list(sig.parameters), ['cmd', 'approval_policy'])
test_eq(sig.parameters['approval_policy'].default, 'never')
test_eq(sig.parameters['approval_policy'].kind, Parameter.KEYWORD_ONLY)
```

``` python
seen = []
def disp(name, **kwargs):
    seen.append((name, kwargs))
    return kwargs

fn = mk_tool(disp, tool_hy)
test_eq(fn('ls', approval_policy='never'), {'cmd': 'ls', 'approval-policy': 'never'})
test_eq(seen[-1], ('run', {'cmd': 'ls', 'approval-policy': 'never'}))
```

``` python
tool_req_hy = dict2obj(dict(name='run', description='Run command', inputSchema=dict(type='object', properties={'approval-policy': {'type': 'string'}, 'cmd': {'type': 'string'}}, required=['approval-policy', 'cmd'])))

seen = []
fn = mk_tool(disp, tool_req_hy)
fn('never', 'ls')
test_eq(seen[-1], ('run', {'approval-policy': 'never', 'cmd': 'ls'}))
```

``` python
tool_collision = dict2obj(dict(name='run', description='Run command', inputSchema=dict(type='object', properties={'approval-policy': {'type': 'string'}, 'approval_policy': {'type': 'string'}})))

test_fail(lambda: schema2sig(tool_collision), contains='collision')
```

A schema-derived function must be re-derivable:
[`get_schema`](https://fastcore.fast.ai/funccall.html#get_schema) on
[`mk_tool`](https://fastcore.fast.ai/funccall.html#mk_tool)’s output
reproduces the schema it was built from. This matters wherever a remote
tool re-enters a local toolchain, e.g. an MCP client’s bound tools
passed to a chat API. Parameter descriptions ride on the rebuilt
signature as `Annotated` metadata, which is how
[`docments`](https://fastcore.fast.ai/docments.html#docments) reads them
back.

``` python
rt_tool = dict2obj(dict(name='fahrenheit', description='Convert Celsius to Fahrenheit',
    inputSchema=dict(type='object', required=['celsius'],
        properties=dict(celsius=dict(description='Temperature to convert', type='number')))))
test_eq(get_schema(mk_tool(noop, rt_tool), pname='inputSchema'), dict(rt_tool))
```

``` python
test_eq(_py_nm(''), '_')
```
