forked from kindrat86/agentshield
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathagent_shield_tool.py
More file actions
133 lines (108 loc) · 4.1 KB
/
Copy pathagent_shield_tool.py
File metadata and controls
133 lines (108 loc) · 4.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
"""
AgentShield CrewAI Tool Wrapper
================================
Wraps CrewAI agent tool execution to validate expected API spend
before the tool is allowed to fire.
Usage:
from agent_shield_tool import AgentShieldGuard, shield_tool
guard = AgentShieldGuard(
endpoint="https://agentshield.fly.dev",
api_key="your-agent-api-key",
)
# Decorate any tool:
@shield_tool(guard, estimated_cost=0.05)
def search_api(query: str) -> dict:
return {"results": [...]}
# Or use as a context manager:
with guard.spend_limit(estimated_cost=0.10):
result = my_expensive_api_call()
"""
import json
import urllib.request
from functools import wraps
from typing import Any, Callable, Optional
class AgentShieldBlockException(Exception):
"""Raised when AgentShield blocks a tool execution based on spend rules."""
pass
class AgentShieldGuard:
"""
Guards CrewAI tool execution by checking spend rules before each call.
Args:
endpoint: AgentShield API endpoint
api_key: AgentShield agent API key
agent_id: Identifier for this agent
"""
def __init__(
self,
endpoint: str = "https://agentshield.fly.dev",
api_key: str = "",
agent_id: str = "crewai-agent",
):
self.endpoint = endpoint.rstrip("/")
self.api_key = api_key
self.agent_id = agent_id
def check_spend(self, estimated_cost: float, merchant: str = "unknown") -> bool:
"""
Evaluate a planned spend against AgentShield rules.
Returns True if allowed, raises AgentShieldBlockException if blocked.
"""
decision = self._evaluate(estimated_cost, merchant)
if decision.get("decision") == "BLOCK":
raise AgentShieldBlockException(
f"Tool blocked by AgentShield: {decision.get('rule', 'unknown')} "
f"(cost: ${estimated_cost:.4f}, eval: {decision.get('evaluation_ms', 0)}ms)"
)
return True
def spend_limit(self, estimated_cost: float, merchant: str = "unknown"):
"""Context manager for guarding a block of code."""
return _SpendLimitContext(self, estimated_cost, merchant)
def _evaluate(self, amount: float, merchant: str) -> dict:
"""Call AgentShield evaluation endpoint."""
payload = json.dumps({
"amount": amount,
"merchant": merchant,
"agent_id": self.agent_id,
}).encode("utf-8")
req = urllib.request.Request(
f"{self.endpoint}/v1/transactions/evaluate",
data=payload,
headers={
"Content-Type": "application/json",
"Authorization": f"Bearer {self.api_key}",
},
method="POST",
)
try:
with urllib.request.urlopen(req, timeout=5) as resp:
return json.loads(resp.read().decode())
except Exception:
return {"decision": "ALLOWED", "rule": None, "evaluation_ms": 0}
class _SpendLimitContext:
def __init__(self, guard: AgentShieldGuard, cost: float, merchant: str):
self.guard = guard
self.cost = cost
self.merchant = merchant
def __enter__(self):
self.guard.check_spend(self.cost, self.merchant)
return self
def __exit__(self, *args):
pass
def shield_tool(guard: AgentShieldGuard, estimated_cost: float = 0.01, merchant: str = "api"):
"""
Decorator that wraps a CrewAI tool function with AgentShield spend checking.
Args:
guard: An AgentShieldGuard instance
estimated_cost: Estimated cost per tool invocation
merchant: Merchant identifier for the API being called
Example:
@shield_tool(guard, estimated_cost=0.05, merchant="openai-api")
def call_llm(prompt: str) -> str:
return openai_client.chat.completions.create(...)
"""
def decorator(func: Callable) -> Callable:
@wraps(func)
def wrapper(*args, **kwargs):
guard.check_spend(estimated_cost, merchant)
return func(*args, **kwargs)
return wrapper
return decorator