Skip to content

Commit b81d122

Browse files
committed
- Add support for enum tool params
- Add demo `arithmetic_calc.py`
1 parent 7b70f4b commit b81d122

3 files changed

Lines changed: 85 additions & 5 deletions

File tree

README.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ The following video, "Toolio in 10 minutes", is an easy way to learn about the p
2222
<img width="1268" alt="Toolio in 10 minutes still" src="https://github.com/user-attachments/assets/fc8dda94-326d-426e-a566-ac8ec60be31f">
2323
-->
2424

25-
`toolio_server` is a FastAPI program that you can use to host MLX-format LLMs for structured output query, for example, if you are on you can use the MLX format LLM model `mlx-community/Hermes-2-Theta-Llama-3-8B-4bit` as follows (from the cloned directory of this repository):
25+
`toolio_server` is a FastAPI program that you can use to host MLX-format LLMs for structured output query or function-calling. For example to host the MLX format LLM model `mlx-community/Hermes-2-Theta-Llama-3-8B-4bit` as follows (from the cloned directory of this repository):
2626

2727
# Installation and Setup
2828

@@ -33,7 +33,7 @@ toolio_server --model=mlx-community/Hermes-2-Theta-Llama-3-8B-4bit
3333

3434
This will download the model (a little over 4GB) to your local HuggingFace disk cache, and running it will take up about that much of your unified RAM.
3535

36-
For more on the MLX framework for ML workloads (including LLMs) on Apple Silicon, see the [MLX Notes](https://github.com/uogbuji/mlx-notes) article series. The "Day One" article provides all the context you need for using local LLMs through this project.
36+
For more on the MLX framework for ML workloads (including LLMs) on Apple Silicon, see the [MLX Notes](https://github.com/uogbuji/mlx-notes) article series. The "Day One" article provides all the context you need for using local LLMs with Toolio.
3737

3838
# cURLing the Toolio server
3939

@@ -91,7 +91,7 @@ toolio_request --apibase="http://localhost:8000" --prompt-file=/tmp/llmprompt.tx
9191

9292
## Tool calling
9393

94-
You can also run tool usage (function-calling) prompts, a key technique in LLM agent frameworks. A schema will automatically be generated from the tool specs
94+
You can run tool usage (function-calling) prompts, a key technique in LLM agent frameworks. A schema will automatically be generated from the tool specs, which themselves are based on [JSON Schema](https://json-schema.org/), according to OpenAI conventions.
9595

9696
```sh
9797
echo 'What'\''s the weather like in Boulder today?' > /tmp/llmprompt.txt
@@ -389,6 +389,10 @@ async def query_sq_root(tmm):
389389
asyncio.run(query_sq_root(toolio_mm))
390390
```
391391

392+
# More examples
393+
394+
See the `demo` directory.
395+
392396
# Credits
393397

394398
* otriscon's [llm-structured-output](https://github.com/otriscon/llm-structured-output/) is the foundation of this package

demo/arithmetic_calc.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
'''
2+
demo/arithmetic_calc.py
3+
4+
Based on an exaple from /llama-cpp-agent
5+
https://llama-cpp-agent.readthedocs.io/en/latest/parallel_function_calling/
6+
'''
7+
8+
import asyncio
9+
from enum import Enum, auto
10+
11+
from toolio.tool import tool, param
12+
from toolio.llm_helper import model_manager, extract_content
13+
14+
15+
class arithmetic_op(Enum):
16+
ADD = 'add'
17+
SUBTRACT = 'subtract'
18+
MULTIPLY = 'multiply'
19+
DIVIDE = 'divide'
20+
21+
22+
@tool('arithmetic_calc', params=[
23+
param('num1', float, 'Number on the left hand side of the calculation', True),
24+
param('num2', float, 'Number on the left hand side of the calculation', True),
25+
param('op', arithmetic_op, 'Arithmetic operation to make on the two numbers', True),
26+
])
27+
async def arithmetic_calc(num1=None, num2=None, op=None):
28+
'Very basic arithmetic calculator'
29+
match op:
30+
case arithmetic_op.ADD:
31+
result = num1 + num2
32+
case arithmetic_op.SUBTRACT:
33+
result = num1 - num2
34+
case arithmetic_op.MULTIPLY:
35+
result = num1 * num2
36+
case arithmetic_op.DIVIDE:
37+
result = num1 / num2
38+
case _:
39+
raise ValueError('Unknown operator') # Shouldn't happen
40+
return result
41+
42+
43+
# Had a problem using Hermes-2-Theta-Llama-3-8B-4bit 😬
44+
# MLX_MODEL_PATH = 'mlx-community/Hermes-2-Theta-Llama-3-8B-4bit'
45+
MLX_MODEL_PATH = 'mlx-community/Mistral-Nemo-Instruct-2407-4bit'
46+
47+
toolio_mm = model_manager(MLX_MODEL_PATH, tool_reg=[arithmetic_calc], trace=True)
48+
49+
# PROMPT = 'Solve the following calculations: 42 * 42, 24 * 24, 5 * 5, 89 * 75, 42 * 46, 69 * 85, 422 * 420, 753 * 321, 72 * 55, 240 * 204, 789 * 654, 123 * 321, 432 * 89, 564 * 321?' # noqa: E501
50+
PROMPT = 'Solve the following calculation: 4242 * 2424.2'
51+
async def async_main(tmm):
52+
msgs = [ {'role': 'user', 'content': PROMPT} ]
53+
async for chunk in extract_content(tmm.complete_with_tools(msgs)):
54+
print(chunk, end='')
55+
56+
asyncio.run(async_main(toolio_mm))

pylib/tool/schematics.py

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import functools
1010
import textwrap
1111
from dataclasses import dataclass
12+
from enum import Enum
1213

1314

1415
@dataclass
@@ -30,8 +31,15 @@ def tool_dec(func):
3031
required_list = []
3132
for p in params:
3233
# Translate type designation to JSON Schema, if need be
33-
typ = TYPES_LOOKUP.get(p.typ, p.typ)
34-
schema_params[p.name] = {'type': typ, 'description': p.desc}
34+
typ = TYPES_LOOKUP.get(p.typ)
35+
if typ:
36+
schema_params[p.name] = {'type': typ, 'description': p.desc}
37+
else:
38+
typ = complex_type(p.typ)
39+
if isinstance(typ, dict):
40+
schema_params[p.name] = {'description': p.desc, **typ}
41+
else:
42+
raise RuntimeError('Unable to determine param type')
3543
params_lookup[p.name] = p
3644
if p.required:
3745
required_list.append(p.name)
@@ -75,4 +83,16 @@ def tool_inner(*args, **kwargs):
7583
int: 'number',
7684
float: 'number',
7785
bool: 'boolean',
86+
'string': 'string',
87+
'number': 'number',
88+
'boolean': 'boolean',
7889
}
90+
91+
92+
def complex_type(typ):
93+
if issubclass(typ, Enum):
94+
# Worth being aware of typ._member_names_ & typ._member_map_
95+
return {'type': 'string', 'enum': [ e.value for e in typ ]}
96+
else:
97+
return None
98+

0 commit comments

Comments
 (0)