Skip to main content

ffwf-tau-llm

The provider layer of Tau, a programmable coding agent harness. tau_llm speaks to OpenAI-compatible chat endpoints and turns the response stream into typed events. It knows nothing about agents.

Tau began as a Python port of the TypeScript project pi-mono, which is still read as the reference implementation when porting or debugging; it now diverges from pi deliberately in several places.

What is in it

  • Message and tool typesUserMessage, AssistantMessage, ToolResultMessage, and the content blocks they carry (TextContent, ThinkingContent, ImageContent, ToolCall). All pydantic models.
  • Model — one endpoint's configuration. The usual fields (id, provider, base_url, context_window, max_tokens) plus the τ-specific ones: reasoning, thinking_level_map, reasoning_replay, grammar_dialect, extra_body, server_features.
  • A streaming clientstream_simple() returns an AssistantMessageEventStream you can iterate for deltas and await for the final message. complete_simple() is the non-streaming spelling of the same call.
  • Constrained decodingDecodeConstraints carries a grammar, a JSON Schema, or a list of choices; the grammar helpers build one.
  • AbortSignal — cooperative cancellation for an in-flight completion.

Why it is a separate package

The dependencies are pydantic and httpx, and that is the whole list. The openai SDK is deliberately not among them: τ talks to the /chat/completions wire format directly, which is what makes a local OpenAI-compatible server (vLLM, llama.cpp, Ollama) a first-class case rather than an afterthought.

Install it alone if you want the streaming client and the message types without an agent loop attached.

Install

pip install ffwf-tau-llm

Python 3.11 or newer. Note the ffwf- prefix: tau-llm on PyPI is an unrelated project.

Example

import asyncio
from tau_llm import Model, stream_simple

model = Model(
    id="gpt-4o",
    name="gpt-4o",
    api="openai-completions",
    provider="openai",
    base_url="https://api.openai.com/v1",
    context_window=128000,
    max_tokens=4096,
)


async def main():
    stream = await stream_simple(
        model,
        {"messages": [{"role": "user", "content": "Say hello."}]},
        {"api_key": "sk-..."},
    )
    async for event in stream:
        if event.type == "text_delta":
            print(event.delta, end="", flush=True)
    final = await stream.result()
    print("\n", final.usage)


asyncio.run(main())

Point base_url at http://localhost:8080/v1 and the same code runs against a local llama.cpp.

A missing API key raises, it does not improvise. stream_simple fails with No API key for provider: … rather than sending a fabricated one. That is the house rule throughout Tau: refuse loudly instead of producing a plausible wrong answer.

Streaming events

tau_llm.streaming defines what the stream yields: TextDeltaEvent, ThinkingDeltaEvent, ToolCallDeltaEvent, DoneEvent, ErrorEvent.

The terminal DoneEvent.final is the authoritative AssistantMessage — its ToolCall blocks carry the fully accumulated arguments. OpenAI streams tool call arguments as incremental fragments, one piece per chunk, which the provider concatenates; a consumer that reads any single delta as a complete payload will corrupt the JSON.

Providers

Providers are pooled, not registered. Provider (in tau_llm.providers.base) is an abstract interface with a single concrete implementation today: OpenAICompletionsProvider, covering the OpenAI Chat Completions API and OpenAI-compatible servers. There is no registry; stream_simple() resolves and caches provider instances itself, keyed on provider name, base URL, and a hash of the API key — so a second model on a different endpoint can never be served by the first model's provider.

Tools

Build a tool with define_tool(), which returns a validated ToolDefinition:

from tau_llm import define_tool

word_count = define_tool(
    name="word_count",
    label="Word count",
    description="Count the words in a string.",
    parameters={
        "type": "object",
        "properties": {"text": {"type": "string"}},
        "required": ["text"],
    },
    execute=lambda text: {"words": len(text.split())},
)

A single mapping may also be passed positionally — define_tool({...}). Passing both forms, or neither, is an error. define_tool raises rather than patching a malformed definition into a working one: label is required and is never derived from name, an unknown field is rejected instead of being dropped, execute must be callable, name must be usable on the wire ([A-Za-z0-9_-]{1,64}), and parameters must be a JSON Schema object schema with a properties key — a tool that takes no arguments writes {"type": "object", "properties": {}}.

Tool argument validation at call time is hand-rolled against that schema (validate_tool_arguments). It checks only top-level type and required, so a keyword like minLength is accepted and then silently unenforced.

define_tool is not the shape tau_agent_core's ExtensionAPI.register_tool() takes — that one is a plain dict whose execute has the five-argument extension signature. See docs/extensions.md.

Docs

  • docs/tau-llm.md — design notes for this package.
  • docs/TOOL-CALL-PIPELINE.md — how a tool call travels from HTTP bytes to a rendered widget.
  • docs/REASONING-VS-CONSTRAINED-DECODING.md — why τ disables thinking on constrained calls.

Repository: https://github.com/jmccardle/tau

The rest of Tau

Distribution Imports as What it adds
ffwf-tau-agent-core tau_agent_core the agent loop, tools, sessions, extensions
ffwf-tau-coding-agent tau_coding_agent the tau command and the Textual TUI
ffwf-tau-jmfts tau_jmfts a JMFTS-backed session store

MIT © Fight Fire with Fire Robotics, LLC

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

ffwf_tau_llm-0.9.2.tar.gz (113.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

ffwf_tau_llm-0.9.2-py3-none-any.whl (56.3 kB view details)

Uploaded Python 3

File details

Details for the file ffwf_tau_llm-0.9.2.tar.gz.

File metadata

  • Download URL: ffwf_tau_llm-0.9.2.tar.gz
  • Upload date:
  • Size: 113.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.11.2

File hashes

Hashes for ffwf_tau_llm-0.9.2.tar.gz
Algorithm Hash digest
SHA256 1456d3b29d53dc2b4161370e5f7fb9103b29ade02a1590ab678e703e2316a4bf
MD5 0996659e6544ce0e73e328f0b9bf52b6
BLAKE2b-256 8c51c982d845859223c27fdb3c1d7d38e31437f05c8ae04bcbf963d03d43d0c9

See more details on using hashes here.

File details

Details for the file ffwf_tau_llm-0.9.2-py3-none-any.whl.

File metadata

  • Download URL: ffwf_tau_llm-0.9.2-py3-none-any.whl
  • Upload date:
  • Size: 56.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.11.2

File hashes

Hashes for ffwf_tau_llm-0.9.2-py3-none-any.whl
Algorithm Hash digest
SHA256 02e4462eff7a761c64c46889f956ea5e49c0ad89ba066b576820691a3c42b8a0
MD5 f214e23c3589559527311f3b3051c90f
BLAKE2b-256 2cb981c700e4c51ec0ec53b5650ce73d2f3029149c3c3a79b3b7bedc7b0ab2f8

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page