Observed since 20 Oct, 22:16 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
agentic-function
Type-safe LLM-powered functions. Write intelligent functions with decorators, not agents.
Turn LLM capabilities into ordinary Python functions with @agentic_function: describe the task in the docstring, constrain the output with a schema; the library handles prompt rendering, validation, retries, caching, tracing, and multi-backend execution.
pip install agentic-function
Optional provider SDKs:
pip install "agentic-function[openai]"
pip install "agentic-function[anthropic]"
pip install "agentic-function[openai,anthropic]"
Editable install from source:
pip install -e ".[dev,openai,anthropic]"
from agentic_function import agentic_function, AgenticResult, set_default_backend
from agentic_function.backends.mock_backend import MockBackend
from agentic_function.testing import mock_llm
set_default_backend(MockBackend())
mock_llm({"category": "positive", "confidence": 0.94, "reasoning": "..."})
@agentic_function(
output_schema={
"category": str,
"confidence": float,
"reasoning": str,
},
)
def classify_sentiment(text: str) -> AgenticResult:
"""Classify the sentiment of ``text``.
- ``category``: "positive" | "negative" | "neutral"
- ``confidence``: [0.0, 1.0]
- ``reasoning``: short explanation
"""
result = classify_sentiment("Amazing launch today!")
print(result.category, result.confidence, result.reasoning)
print(result.metrics.latency_ms, result.metrics.usage.prompt_tokens)
Runnable examples under examples/:
python examples/01_sentiment_classification.py
python examples/02_information_extraction.py
python examples/03_summarization.py
python examples/04_intent_routing.py
python examples/05_composition.py
python examples/06_real_minimax.py # requires an API key
Examples 01–05 use MockBackend and need no API key.
| Area | Notes |
|---|---|
| Decorator API | @agentic_function; call it like a normal function |
| Output schema | dict, pydantic BaseModel, or Literal[...] |
| Composition | Plain Python calls between functions |
| Tool export | as_openai_tool / as_anthropic_tool, FunctionRegistry |
| Backends | Mock, OpenAI, Anthropic, MiniMax, plus register_backend |
| Validation & retry | pydantic validation; retry on parse / validation failure |
| Cache | InMemoryCache / DiskCache / NullCache |
| Metrics & cost | CallMetrics on every result (latency, tokens, estimated USD, …) |
| Trace & budget | trace, BudgetTracker, Aggregator (incl. Prometheus text) |
| Diagnostics | diagnose / explain_failure / snapshot; debug= / AGENTIC_DEBUG |
| Testing helpers | mock_llm, mock_llm_table, freeze_time, capture_metrics |
| Async | .acall() primary path; sync __call__ available |
| Errors | ValidationError, RetryExhaustedError, BudgetExceededError, … |
Declare output_schema as a dict, a BaseModel, or a Literal[...] return annotation. The library injects the JSON schema into the prompt (or uses provider tool / json_schema mode), applies common coercions, and validates with pydantic. On failure it retries according to policy. Callers receive typed fields, not a raw string to parse.
@agentic_function(output_schema={"label": str, "score": float})
def classify(text: str) -> AgenticResult:
"""Classify sentiment of ``text``."""
topic = extract_topic(article)
summary = make_summary(article, topic.topic, topic.tone)
See examples/05_composition.py.
Export a function as OpenAI / Anthropic tool JSON for an external agent or custom tool loop:
from agentic_function import as_openai_tool, as_anthropic_tool, register, get_function
openai_tool = as_openai_tool(make_summary)
anthropic_tool = as_anthropic_tool(make_summary)
register(make_summary)
fn = get_function(make_summary.qualified_name)
Built-ins: MockBackend, OpenAIBackend, AnthropicBackend, and the MiniMax-CN preset minimax. Register custom backends with register_backend(...).
@agentic_function(backend="mock", output_schema={"label": str})
@agentic_function(backend="openai", model="gpt-4o-mini", output_schema={"label": str})
@agentic_function(backend="anthropic", model="claude-sonnet-4-20250514", output_schema={"label": str})
@agentic_function(backend="minimax", model="MiniMax-M3", output_schema={"label": str})
Point OpenAIBackend at any Chat Completions–compatible endpoint via base_url. No separate Ollama adapter is required when the server speaks the OpenAI protocol:
from agentic_function import OpenAIBackend, register_backend, set_default_backend
ollama = OpenAIBackend(
api_key="ollama", # required by the client; value often unused locally
base_url="http://localhost:11434/v1", # or OPENAI_BASE_URL
default_model="llama3.2",
)
register_backend("ollama", ollama)
set_default_backend(ollama)
# Equivalent env-based setup with the built-in "openai" backend:
# export OPENAI_API_KEY=ollama
# export OPENAI_BASE_URL=http://localhost:11434/v1
@agentic_function(backend="ollama", model="llama3.2", output_schema={"label": str})
def classify(text: str):
"""Classify sentiment of ``text`` as label: positive|negative|neutral."""
| Parameter | Purpose |
|---|---|
| docstring | Task description (system prompt body) |
few_shots | [(input, output), …] exemplars |
prompt_template / system_template | Custom {placeholder} templates |
include_schema_in_prompt | Whether to inject the schema |
description | Tool-export blurb (defaults to first docstring line) |
render_prompt(fn, args, kwargs) | Inspect the message list before calling |
out = await classify.acall("terrible")
Works as free functions, methods, and classmethods (descriptor protocol).
Every result includes CallMetrics:
result.metrics.latency_ms
result.metrics.usage.prompt_tokens
result.metrics.usage.completion_tokens
result.metrics.usage.total_tokens
result.metrics.cost_usd
result.metrics.cache_hit
result.metrics.attempts
result.metrics.retries
result.metrics.recovered
result.metrics.attempt_errors
result.metrics.timings
result.metrics.total_cost_usd
Tracing, budgets, and aggregation:
from agentic_function import (
trace,
Budget, BudgetTracker, install_budget_tracker,
Aggregator, install_default_aggregator,
)
with trace("nightly_eval") as ctx:
out = classify(sample.text)
ctx.span.set_attribute("sample.id", sample.id)
install_budget_tracker(BudgetTracker(budgets=[
Budget(metric="cost_usd", limit=5.0),
]))
agg = install_default_aggregator(Aggregator())
print(agg.summary())
print(agg.to_prometheus())
Diagnostics:
from agentic_function import diagnose, explain_failure, snapshot
print(diagnose(result).to_dict())
print(explain_failure(exc))
print(snapshot(result))
Cache keys cover (model, schema, few_shots, prompt_hash). Bound retries with RetryPolicy(max_retries=..., base_delay=..., max_delay=...).
examples/06_real_minimax.py runs against a live backend (API key required).
A successful call returns a value that matches output_schema. Transient failures are retried according to RetryPolicy; if every attempt fails, the call raises.
call → prompt → cache lookup
│ miss
▼
backend request
│
┌────────┴────────┐
│ OK │ BackendError / ParseError / ValidationError
▼ ▼
coerce + validate backoff retry
│ │
│ valid │ retries exhausted
▼ ▼
return RetryExhaustedError
| Exception | Meaning | Retried |
|---|---|---|
BackendError | Provider or transport failure | Yes |
ParseError | Response is not usable JSON | Yes |
ValidationError | JSON does not match the schema | Yes |
RetryExhaustedError | All attempts failed | No (terminal) |
BudgetExceededError | Configured budget exceeded | No |
TimeoutError | Request timed out | Per retry policy |
BackendError.status_code carries the HTTP status when the provider SDK exposes it (for example 429). error_category_of(exc) returns "rate_limit" for status 429, otherwise "backend" for other BackendErrors.
Library errors subclass AgenticFunctionError. On exhaustion, RetryExhaustedError exposes last_exception, metrics, and attempt_errors:
from agentic_function import BackendError, RetryExhaustedError, error_category_of
try:
result = classify(text)
except RetryExhaustedError as exc:
cause = exc.last_exception
status = getattr(cause, "status_code", None)
if error_category_of(exc) == "rate_limit" or status == 429:
# apply caller-side backoff / queueing
...
raise
Control retries with max_retries or retry_policy=RetryPolicy(...). Application code may catch failures and apply its own fallback; the library does not substitute default schema fields on failure.
Set provider keys via environment variables (for example OPENAI_API_KEY, ANTHROPIC_API_KEY, MINIMAX_CN_API_KEY) or pass them when constructing a backend. Do not place secrets in source files or pass them through configure().
from agentic_function.testing import mock_llm, mock_llm_table, freeze_time, capture_metrics
mock_llm({"label": "positive", "score": 0.95})
assert classify("amazing").label == "positive"
mock_llm_table([
{"label": "positive", "score": 0.9},
{"label": "negative", "score": 0.8},
])
with freeze_time():
classify("text")
with capture_metrics() as bag:
classify("a")
classify("b")
assert len(bag) == 2
You can also use MockBackend, or patch the openai / anthropic clients to assert request shaping (see tests/test_anthropic_backend.py).
pytest tests/ -m "not live_llm" # offline
pytest tests/ -m live_llm -v # live MiniMax (API key required)
pytest --cov=agentic_function
Live tests honor AGENTIC_LIVE_LLM_PACE and AGENTIC_LIVE_CALL_GAP (seconds) to space requests.
Isolation points:
| Concern | Approach |
|---|---|
| Schema | Test the pydantic model directly |
| Prompt | render_prompt(fn, args, kwargs) |
| Backend request | Patch the provider SDK |
| Backend response | Pass a synthetic LLMResponse |
| Retry | Raise synthetic retryable errors |
| Cache | Swap cache implementations |
| Tool export | Assert tool JSON shape |
| Metrics | capture_metrics / aggregator |
| Parameter | Default | Meaning |
|---|---|---|
model | global default | Model id for the backend |
output_schema | inferred from return annotation | dict / BaseModel / Literal[...] |
backend | set_default_backend(...) | Instance or registered name |
temperature, top_p, max_tokens, stop | global config | Sampling params |
max_retries | global config | Retries on parse / validation failure |
retry_policy | RetryPolicy(...) | Backoff and retryable exceptions |
cache | global default | Per-call cache override |
timeout | global config | Request timeout (seconds) |
include_schema_in_prompt | True | Inject JSON schema into the system message |
few_shots | [] | Exemplar pairs |
prompt_template / system_template | None | Custom templates |
description | first docstring line | Tool-export description |
debug | False / AGENTIC_DEBUG | Attach request/response snapshots |
executor | global default | Custom Executor |
Environment variables: AGENTIC_FUNCTION_MODEL, AGENTIC_FUNCTION_BACKEND, AGENTIC_FUNCTION_CACHE, AGENTIC_FUNCTION_CACHE_DIR, plus OPENAI_API_KEY, ANTHROPIC_API_KEY, MINIMAX_CN_API_KEY, etc.
from agentic_function import (
agentic_function, AgenticFunction, AgenticResult, DynamicResult,
SchemaSpec, resolve_schema, render_prompt,
LLMBackend, LLMResponse, StreamChunk,
MockBackend, OpenAIBackend,
register_backend, get_backend, get_default_backend, set_default_backend,
known_backends,
Executor, GlobalConfig, configure, global_config,
TraceContext, TraceSpan, TraceRecorder, trace, get_current_trace,
CallMetrics, TokenUsage, PhaseTimings,
RetryPolicy, default_retry_policy,
CacheBackend, InMemoryCache, DiskCache, NullCache,
get_default_executor, set_default_executor,
Budget, BudgetTracker, BudgetExceededError,
install_budget_tracker, get_default_budget_tracker,
Aggregator, FunctionStats,
install_default_aggregator, get_default_aggregator,
Diagnostic, diagnose, diagnose_metrics, explain_failure, snapshot,
FunctionRegistry, get_global_registry, register, get_function,
as_openai_tool, as_anthropic_tool,
testing,
AgenticFunctionError, BackendError, CacheError, CompositionError,
ConfigError, ParseError, RegistrationError, RetryExhaustedError,
SchemaError, TimeoutError_ as TimeoutError, ValidationError,
error_category_of,
)
AnthropicBackend and MiniMax live under agentic_function.backends and register as "anthropic" / "minimax".
@agentic_function(...)
│
▼
AgenticFunction (descriptor / call / await)
│ ExecutionRequest
▼
Executor
trace → cache → retry → schema → backend
│
├── BudgetTracker
├── Aggregator
└── as_*_tool / Registry
agentic-function/
├── agentic_function/
│ ├── core/
│ ├── backends/
│ ├── runtime/
│ ├── composition/
│ ├── validation/
│ ├── utils/
│ └── testing.py
├── tests/
└── examples/
Current release: 0.0.1.
In tree today
Literal schemasOpenAIBackend(base_url=...) (Ollama, vLLM, …)as_openai_tool / as_anthropic_tool)mock_llm test helpersstatus_code on BackendError (incl. rate-limit categorization)Next
See CONTRIBUTING.md.
Download counts and popularity will appear here as the collector receives them.
| Package / target | Requirement | Relationship |
|---|---|---|
pydanticEnvironment{
"extras": "",
"marker": null,
"requires_python": ">=3.10",
"evaluation": "NOT_EVALUATED"
} | pydantic>=2.5 | runtime |
typing-extensionsEnvironment{
"extras": "",
"marker": null,
"requires_python": ">=3.10",
"evaluation": "NOT_EVALUATED"
} | typing-extensions>=4.8 | runtime |
openaiEnvironment{
"extras": "",
"marker": "extra == \"openai\"",
"requires_python": ">=3.10",
"evaluation": "NOT_EVALUATED"
} | openai>=1.10; extra == "openai" | runtime |
anthropicEnvironment{
"extras": "",
"marker": "extra == \"anthropic\"",
"requires_python": ">=3.10",
"evaluation": "NOT_EVALUATED"
} | anthropic>=0.18; extra == "anthropic" | runtime |
pytestEnvironment{
"extras": "",
"marker": "extra == \"dev\"",
"requires_python": ">=3.10",
"evaluation": "NOT_EVALUATED"
} | pytest>=7.4; extra == "dev" | runtime |
pytest-asyncioEnvironment{
"extras": "",
"marker": "extra == \"dev\"",
"requires_python": ">=3.10",
"evaluation": "NOT_EVALUATED"
} | pytest-asyncio>=0.23; extra == "dev" | runtime |
pytest-covEnvironment{
"extras": "",
"marker": "extra == \"dev\"",
"requires_python": ">=3.10",
"evaluation": "NOT_EVALUATED"
} | pytest-cov>=4.1; extra == "dev" | runtime |
ruffEnvironment{
"extras": "",
"marker": "extra == \"dev\"",
"requires_python": ">=3.10",
"evaluation": "NOT_EVALUATED"
} | ruff>=0.1; extra == "dev" | runtime |
mypyEnvironment{
"extras": "",
"marker": "extra == \"dev\"",
"requires_python": ">=3.10",
"evaluation": "NOT_EVALUATED"
} | mypy>=1.7; extra == "dev" | runtime |
bdist_wheel · 79.4 KB · Python py3
sdist · 83.83 KB · Python source
Shared repository data · GitHub
Observed since 20 Oct, 22:16 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
package_version:ec2e536d617cf8f49cdc806b13ba35435d359083d8685db0b8430dcdf95a62bf