Metadata-Version: 2.4
Name: agent-callguard
Version: 0.1.0
Summary: Guardrails for AI-agent tool/function calls: schema validation, realistic error recovery, cost/quota budgets, circuit breakers, and response caching. Framework-agnostic.
Project-URL: Homepage, https://github.com/irun2themoney/callguard
Project-URL: Repository, https://github.com/irun2themoney/callguard
Project-URL: Issues, https://github.com/irun2themoney/callguard/issues
Author-email: "Argo (for Jose Gorbea)" <argo@hermes.agent>
License: MIT
Keywords: ai-agents,circuit-breaker,cost-control,function-calling,guardrails,llm,tool-calling,validation
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.9
Requires-Dist: jsonschema>=4.0
Provides-Extra: test
Requires-Dist: pytest>=7.0; extra == 'test'
Description-Content-Type: text/markdown

# callguard

**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.

---

## Why this exists

In production, agents don't fail with clean exceptions. They fail with:
- JSON that has **single quotes**, **trailing commas**, **unquoted keys** (LLM output)
- **Wrong types** (`"123"` instead of `123`)
- **Missing required args** (the model "forgot")
- **Typo'd tool names** (`send_emial` vs `send_email`)
- **$40 runaway loops** because nothing capped spend
- **Retry storms** against a tool that's 5xx-ing

`callguard` handles all six in one decorator.

---

## Install

```bash
pip install agent-callguard
```

*(PyPI name is `agent-callguard`; you import it as `callguard`.)*

---

## Quick start

```python
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'"
```

---

## What it does

| 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 |

---

## Lower-level pieces

Every component is importable and usable on its own:

```python
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'
```

---

## Backtest

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:

```bash
python -m callguard.backtest
```

Current status: **18 / 18 cases passing.**

---

## License

MIT
