Plain Python
Use the plain Python runtime when the work is deterministic application logic, a small adapter, or an existing callable that does not need an agent runtime.
from lllm.runtimes import as_tactic
def uppercase(text: str) -> str:
return text.upper()
tactic = as_tactic(uppercase, name="uppercase")
assert tactic.run("hello") == "HELLO"
CallableTactic infers the first non-context parameter annotation as the
input type and the return annotation as the output type. You can override both
when you need stricter public schemas.
Context-Aware Callables
If a callable accepts context, LLLM forwards the CallContext:
from lllm import CallContext
from lllm.runtimes import as_tactic
def label(text: str, *, context=None) -> dict[str, str | None]:
return {"text": text, "trace_id": context.trace_id if context else None}
tactic = as_tactic(label, name="label")
output = tactic.run("hello", context=CallContext(trace_id="trace-1"))
This is useful for logging, audit metadata, request correlation, and service adapters.
When To Prefer A Class
Subclass Tactic directly when the behavior has configuration, multiple call
paths, streaming, custom metadata, or custom endpoints.
from lllm import Tactic
class UppercaseTactic(Tactic[str, str]):
name = "uppercase"
input_type = str
output_type = str
def _run(self, input_value, *, context=None):
return input_value.upper()
Serving
Plain Python tactics use the same service adapter as agent-backed tactics:
from lllm.services import create_tactic_app
app = create_tactic_app(tactic)
The caller still sees /run, /stream when supported, and /info.
Package Shape
[tactics.uppercase]
entry = "demo.tactics:build_tactic"
input = "str"
output = "str"
runtime = "python"
Plain Python is often the best starting point for protocol-level tutorials, tests, package examples, and adapters around non-agent systems.