A local tool-calling assistant¶
An assistant that answers questions about your project by calling Python functions you hand it — running entirely against a local Ollama model, so nothing leaves your machine and no API key is involved. The same program points at any hosted provider by changing the target string.
"""assistant.py — `python assistant.py "what does pyproject.toml declare?"`"""
import sys
from pathlib import Path
import anyinfer as ai
@ai.tool
def read_file(path: str) -> str:
"""Read a file from the current project directory."""
return Path(path).read_text(encoding="utf-8")
@ai.tool
def list_files(pattern: str = "*") -> str:
"""List project files matching a glob pattern."""
return "\n".join(str(p) for p in Path.cwd().glob(pattern))
client = ai.Client([ai.ProviderSettings.of("ollama")])
result = client.run_tools(
sys.argv[1],
tools=[read_file, list_files],
target="ollama:qwen3:8b",
)
print(result.text)
The model decides when to call read_file or list_files; AnyInfer runs the function,
feeds the result back, and loops until the model produces a final answer (bounded — a
runaway loop raises ToolLoopError rather than spinning).
Streaming the same conversation¶
For interactive use, stream tokens as they arrive instead of waiting for the full answer. Streaming and non-streaming are the same primitive — a typed event stream — so nothing else about the program changes:
with client.stream("Explain this project's layout", target="ollama:qwen3:8b") as stream:
for event in stream:
if isinstance(event, ai.TextDelta):
print(event.text, end="", flush=True)
result = stream.result # usage, timing, and the attempt trail, same as generate()
print(f"\n\n[{result.usage.output_tokens} tokens, "
f"first token in {result.timing.first_token_ms} ms]")
What to notice¶
@ai.toolderives the wire schema from the signature — name, docstring, and type hints become the provider-facing tool spec.read_file.specis inspectable if you want to see exactly what the model is told. See the tool loop.- The loop lives in the core, not your code.
run_toolshandles the call → execute → feed-back cycle identically on every provider that supports tools (the conformance matrix says which do). - Local is not a second-class citizen. If
qwen3:8bis not pulled yet, or you would rather have AnyInfer supervise allama-serverfor you, the local inference guide covers the end-to-end path — including letting the library pick a model tier that fits your hardware.
Related guides: Run the tool loop · Stream to a terminal · Run a model locally, end to end