Skip to main content

Open-source guardrails for AI agents — loop detection, budget enforcement, and quality verification.

Project description

reivo-guard

Open-source guardrails for AI agents — Python SDK.

A developer's autonomous agent ran up a $47,000 bill overnight. reivo-guard prevents this.

Install

pip install reivo-guard

# Optional integrations
pip install reivo-guard[litellm]    # LiteLLM callback
pip install reivo-guard[langchain]  # LangChain / LangGraph handler

Requirements: Python >= 3.9. Core library has zero dependencies.


Quick Start

Standalone Guard (any framework)

from reivo_guard import Guard

guard = Guard(budget_limit_usd=50.0, loop_threshold=3)

messages = [{"role": "user", "content": "Hello"}]

decision = guard.before(messages=messages)
if not decision.allowed:
    print(f"Blocked: {decision.reason}")
else:
    response = your_llm_call(messages)
    guard.after(cost_usd=0.003)
    # or estimate from tokens:
    # guard.after(model="gpt-4o", input_tokens=100, output_tokens=50)

LiteLLM (1 line)

import litellm
from reivo_guard import ReivoGuard

litellm.callbacks = [ReivoGuard(budget_limit_usd=50.0)]

response = litellm.completion(
    model="gpt-4o",
    messages=[{"role": "user", "content": "Hello"}],
)

LangChain / LangGraph

from langchain_openai import ChatOpenAI
from reivo_guard.langchain import ReivoCallbackHandler

handler = ReivoCallbackHandler(budget_limit_usd=10.0, default_model="gpt-4o")
llm = ChatOpenAI(model="gpt-4o", callbacks=[handler])
response = llm.invoke("What is 2+2?")

Works with LangGraph agents, chains, and any component that accepts callbacks.


API Reference

Guard class

Framework-agnostic guardrail with before/after pattern.

from reivo_guard import Guard

Constructor

Guard(
    budget_limit_usd: float | None = None,  # None = unlimited
    loop_window: int = 20,                   # recent requests to check
    loop_threshold: int = 3,                 # identical prompts to trigger (>= 2)
    raise_on_block: bool = False,            # raise exceptions instead of returning decision
)
Parameter Type Default Constraints
budget_limit_usd float | None None Must be > 0 or None
loop_window int 20 Must be >= 1
loop_threshold int 3 Must be >= 2
raise_on_block bool False

guard.before(messages=None, prompt_hash=None) → GuardDecision

Check budget and loop before an LLM call. Provide either messages (auto-hashed) or a pre-computed prompt_hash. If neither is given, only budget check runs.

@dataclass
class GuardDecision:
    allowed: bool
    reason: str | None = None
    budget_used_usd: float = 0.0
    budget_remaining_usd: float | None = None

When raise_on_block=True, raises BudgetExceeded or LoopDetected instead of returning a blocked decision.

guard.after(cost_usd=0.0, model=None, input_tokens=0, output_tokens=0)

Record cost after an LLM call. If cost_usd is 0 and token counts are provided, cost is estimated from the built-in pricing table.

Parameter Type Description
cost_usd float Direct cost. NaN/Inf/negative silently ignored
model str | None Model name for cost estimation
input_tokens int Input token count
output_tokens int Output token count

guard.stats → dict

{
    "total_requests": 42,
    "total_cost_usd": 3.14,
    "budget_used_usd": 3.14,
    "budget_limit_usd": 100.0,
    "budget_remaining_usd": 96.86,
    "blocked_requests": 0,
}

guard.reset()

Reset all state (counters, budget, loop history).


ReivoGuard class (LiteLLM)

LiteLLM callback. Internally uses Guard.

from reivo_guard import ReivoGuard

Constructor

ReivoGuard(
    budget_limit_usd: float | None = None,
    loop_window: int = 20,
    loop_threshold: int = 3,
    on_budget_exceeded: Callable[[float, float], None] | None = None,
    on_loop_detected: Callable[[int, int], None] | None = None,
)

When no callback is provided, raises BudgetExceeded / LoopDetected by default.

Custom callbacks

def on_budget(used, limit):
    print(f"Warning: ${used:.2f} / ${limit:.2f}")

def on_loop(count, window):
    slack.post(f"Loop: {count} repeats in {window} requests")

guard = ReivoGuard(
    budget_limit_usd=100.0,
    on_budget_exceeded=on_budget,
    on_loop_detected=on_loop,
)

Properties

Property Type Description
stats dict Same format as Guard.stats
total_requests int Total requests processed
total_cost_usd float Cumulative cost
blocked_requests int Requests blocked by guards

ReivoCallbackHandler class (LangChain)

LangChain BaseCallbackHandler. Works with LangChain, LangGraph, and any framework using the callback protocol.

from reivo_guard.langchain import ReivoCallbackHandler

Constructor

ReivoCallbackHandler(
    budget_limit_usd: float | None = None,
    loop_window: int = 20,
    loop_threshold: int = 3,
    raise_on_block: bool = True,
    default_model: str | None = None,  # for cost estimation
)

Cost estimation: Uses token counts from LLMResult.llm_output["token_usage"] or AIMessage.usage_metadata, combined with the model name, to estimate cost via the built-in pricing table.

Properties

Property Type Description
stats dict Same format as Guard.stats

estimate_cost(model, input_tokens, output_tokens) → float

Estimate cost in USD from token counts. Returns 0.0 for unknown models.

from reivo_guard import estimate_cost

cost = estimate_cost("gpt-4o", input_tokens=1000, output_tokens=500)
# → 0.0075

Supported models

Model Input ($/1M) Output ($/1M)
gpt-4o 2.50 10.00
gpt-4o-mini 0.15 0.60
gpt-4-turbo 10.00 30.00
gpt-4 30.00 60.00
gpt-3.5-turbo 0.50 1.50
o1 15.00 60.00
o1-mini 3.00 12.00
o3-mini 1.10 4.40
claude-3-5-sonnet-20241022 3.00 15.00
claude-3-5-haiku-20241022 0.80 4.00
claude-3-opus-20240229 15.00 75.00
claude-sonnet-4-20250514 3.00 15.00
claude-opus-4-20250514 15.00 75.00
gemini-1.5-pro 1.25 5.00
gemini-1.5-flash 0.075 0.30
gemini-2.0-flash 0.10 0.40

Model names with date suffixes (e.g., gpt-4o-mini-2024-07-18) are matched by longest prefix.


Exceptions

from reivo_guard import BudgetExceeded, LoopDetected

BudgetExceeded

Attribute Type Description
used float Amount spent in USD
limit float Budget limit in USD

LoopDetected

Attribute Type Description
match_count int Number of identical prompts found
window int Window size checked

Pure functions

from reivo_guard import detect_loop, check_budget, hash_messages

detect_loop(hashes, current_hash, threshold=3) → tuple[bool, int]

Check if current_hash appears >= threshold times in hashes + [current_hash].

detect_loop_by_cosine(previous_prompts, current_prompt, threshold=0.92, match_threshold=4) → CosineLoopResult

Detect loops using TF-IDF cosine similarity. Catches semantically similar prompts even when worded differently.

from reivo_guard import detect_loop_by_cosine

result = detect_loop_by_cosine(
    ["How do I sort a list?", "Sort a list please", "List sorting help"],
    "How to sort lists?",
    threshold=0.5,
    match_threshold=2,
)
if result.is_loop:
    print(f"Semantic loop: similarity={result.similarity:.3f}")
@dataclass
class CosineLoopResult:
    is_loop: bool
    match_count: int
    similarity: float | None = None

check_budget(used_usd, limit_usd) → tuple[bool, float | None]

Returns (exceeded: bool, remaining_usd: float | None). remaining is None if no limit set.

hash_messages(messages) → str

SHA-256 hex digest of JSON-serialized messages.


License

MIT

Project details


Download files

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

Source Distribution

reivo_guard-0.3.0.tar.gz (29.1 kB view details)

Uploaded Source

Built Distribution

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

reivo_guard-0.3.0-py3-none-any.whl (24.5 kB view details)

Uploaded Python 3

File details

Details for the file reivo_guard-0.3.0.tar.gz.

File metadata

  • Download URL: reivo_guard-0.3.0.tar.gz
  • Upload date:
  • Size: 29.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for reivo_guard-0.3.0.tar.gz
Algorithm Hash digest
SHA256 78247cc63ea36373100c883bf4decce1def91b77fd421f687bd3d8fc3b3b9080
MD5 59939fc9a5551a392cbe55b072744cfa
BLAKE2b-256 f17885e53cb2776410e3497caaae71004fde66509ef746aa7ca7c6cde4451e7f

See more details on using hashes here.

Provenance

The following attestation bundles were made for reivo_guard-0.3.0.tar.gz:

Publisher: publish-python.yml on tazsat0512/reivo-guard

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file reivo_guard-0.3.0-py3-none-any.whl.

File metadata

  • Download URL: reivo_guard-0.3.0-py3-none-any.whl
  • Upload date:
  • Size: 24.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for reivo_guard-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 9d552b28c3a422d45b548697948c2abe152d8736a4c5b4dfa13dc4784888c668
MD5 e6237e6c7b2cad367da9f3bc60b16b42
BLAKE2b-256 22b7a5a2e92da1e15d3e4d2e23ca7e750251cc0d466138bc6a8af05c471c2378

See more details on using hashes here.

Provenance

The following attestation bundles were made for reivo_guard-0.3.0-py3-none-any.whl:

Publisher: publish-python.yml on tazsat0512/reivo-guard

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

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