Observed since 20 Oct, 21:36 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
Guardrails for AI-agent tool/function calls: schema validation, realistic error recovery, cost/quota budgets, circuit breakers, and response caching. Framework-agnostic.
Guardrails for AI-agent tool / function calls. Framework-agnostic, zero heavy
dependencies (only jsonschema). Makes agent tool calls reliable and
cost-bounded — the two things every agent framework reinvents from
scratch and gets wrong.
In production, agents don't fail with clean exceptions. They fail with:
"123" instead of 123)send_emial vs send_email)callguard handles all six in one decorator.
pip install agent-callguard
(PyPI name is agent-callguard; you import it as callguard.)
from callguard import Guard
g = Guard(
budget=g.budget(max_cost=1.0, cost_per_call=0.002), # stop the $40 loop
breaker=g.breaker(failure_threshold=5, reset_after=30), # halt retry storms
cache=g.cache(ttl=120), # skip dup idempotent reads
)
@g.tool(schema={
"type": "object",
"properties": {
"to": {"type": "string"},
"subject": {"type": "string"},
"priority": {"type": "string", "enum": ["low", "normal", "high"]},
"retry": {"type": "integer"},
},
"required": ["to", "subject"],
})
def send_email(to, subject, body="", priority="normal", retry=0):
... # your real tool body
return f"sent:{to}"
# The LLM produced this MESSY blob — callguard fixes it automatically:
g.call("send_email", "{'to': 'a@b.com', 'subject': 'Hi', 'retry': '3'}")
# -> single quotes repaired, '3' coerced to int 3, tool executed OK
# Typo'd tool name? Auto-recovered:
g.call("send_emial", {"to": "a@b.com", "subject": "Hi"}) # redirects to send_email
# Missing required arg? Structured, actionable error (not a stack trace):
try:
g.call("send_email", {"to": "a@b.com"})
except ToolCallError as e:
print(e.errors[0].suggestion) # "provide 'subject'"
| Feature | Module | Behavior |
|---|---|---|
| JSON repair | errors.repair_json | single quotes, trailing commas, unquoted keys, True/False/None, code fences |
| Type coercion | schema.validate | "3"→3, "true"→True, "1.5"→1.5 before failing |
| Actionable validation | schema.validate | errors name the field + show the fix: "field 'x' expected number, got str" |
| Tool-name recovery | errors.suggest_tool | Levenshtein match redirects send_emial→send_email |
| Error classification | errors.classify | transient/retryable (timeout, 429, 5xx) vs hard (401, 404, auth) |
| Cost & quota budget | budget.Budget | raises before a hard cap; soft-warn callback at 80% |
| Circuit breaker | breaker.CircuitBreaker | opens after N consecutive failures, half-opens to probe recovery |
| Response cache | cache.Cache | short-TTL dedupe for idempotent reads |
Every component is importable and usable on its own:
from callguard import repair_json, validate, classify, suggest_tool, parse_args
from callguard import Budget, CircuitBreaker, Cache, tool, Guard
repair_json("{'a': 1,}") # '{"a": 1}'
classify("503 Service Unavailable") # CallError(kind='transient', recoverable=True)
suggest_tool("send_emial", ["send_email", "get_weather"]) # 'send_email'
The repo ships a regression corpus of real-world malformed agent calls
(callguard/backtest.py) — single-quote JSON, trailing commas, missing
args, enum typos, tool-name typos, transient vs hard errors, budget
enforcement, circuit-breaker trips. Run it:
python -m callguard.backtest
Current status: 18 / 18 cases passing.
MIT
Download counts and popularity will appear here as the collector receives them.
| Version | Published | Direct dependencies | Open version |
|---|---|---|---|
| 0.1.0 | 19 Jul 2026 | 2 |
This describes our collected data; it is not a security assessment.
Observed since 20 Oct, 21:36 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
package:eac6880e573daef3c6a7c629ce176b730bf9039c94e339f43e9549ece21b47ae