A search budget cap stops an AI agent from burning through API credits in a single runaway loop. The pattern wraps your search function with a decorator that tracks cumulative spend and raises an exception when the limit is hit.
Prerequisites
- Python 3.9+
- Scavio API key
- requests library
Walkthrough
Step 1: Create the budget tracker class
A simple class holds the credit count and raises BudgetExceeded when the cap is hit.
class BudgetExceeded(Exception):
def __init__(self, used, cap):
super().__init__(f"Search budget exceeded: {used}/{cap} credits used")
self.used = used
self.cap = cap
class SearchBudget:
def __init__(self, cap_credits: float):
self.cap = cap_credits
self.used = 0.0
def charge(self, credits: float):
self.used += credits
if self.used > self.cap:
raise BudgetExceeded(self.used, self.cap)
def remaining(self) -> float:
return max(0, self.cap - self.used)Step 2: Build the budget-aware search decorator
Wrap the raw API call. Each request costs 1 credit by default; adjust if you use bulk endpoints.
import functools
import requests
def with_budget(budget: SearchBudget, credits_per_call: float = 1.0):
def decorator(fn):
@functools.wraps(fn)
def wrapper(*args, **kwargs):
budget.charge(credits_per_call)
return fn(*args, **kwargs)
return wrapper
return decoratorStep 3: Apply to your search function
Create a session budget and decorate the search call before passing it to your agent.
API_KEY = "your-scavio-api-key"
def _raw_search(query: str, **kwargs) -> dict:
payload = {"query": query, "num_results": kwargs.get("num_results", 10)}
if kwargs.get("platform"):
payload["platform"] = kwargs["platform"]
r = requests.post(
"https://api.scavio.dev/api/v1/search",
json=payload,
headers={"x-api-key": API_KEY},
timeout=15
)
r.raise_for_status()
return r.json()
# Cap at 20 credits per agent run (~$0.10)
budget = SearchBudget(cap_credits=20)
search = with_budget(budget)(_raw_search)
try:
results = search("python async best practices")
print(f"Credits remaining: {budget.remaining()}")
except BudgetExceeded as e:
print(f"Stopped: {e}")Step 4: Integrate with an agent loop
Pass the budget-capped search to your agent and catch BudgetExceeded to return partial results gracefully.
def run_research_agent(topic: str, max_credits: int = 15):
budget = SearchBudget(cap_credits=max_credits)
search = with_budget(budget)(_raw_search)
findings = []
subtopics = [topic, f"{topic} tutorial", f"{topic} examples", f"{topic} best practices"]
for subtopic in subtopics:
try:
data = search(subtopic)
findings.extend(data.get("organic_results", [])[:3])
print(f"Searched '{subtopic}' | {budget.remaining():.0f} credits left")
except BudgetExceeded as e:
print(f"Budget cap hit after {e.used} credits. Returning partial results.")
break
return findings
results = run_research_agent("vector databases", max_credits=5)
print(f"Got {len(results)} results")Python Example
import functools
import requests
class BudgetExceeded(Exception):
def __init__(self, used, cap):
super().__init__(f"Search budget exceeded: {used:.1f}/{cap} credits")
self.used = used
self.cap = cap
class SearchBudget:
def __init__(self, cap_credits: float):
self.cap = cap_credits
self.used = 0.0
def charge(self, credits: float = 1.0):
self.used += credits
if self.used > self.cap:
raise BudgetExceeded(self.used, self.cap)
def remaining(self) -> float:
return max(0.0, self.cap - self.used)
API_KEY = "your-scavio-api-key"
def _raw_search(query: str, platform: str = None, num_results: int = 10) -> dict:
payload = {"query": query, "num_results": num_results}
if platform:
payload["platform"] = platform
r = requests.post(
"https://api.scavio.dev/api/v1/search",
json=payload,
headers={"x-api-key": API_KEY},
timeout=15
)
r.raise_for_status()
return r.json()
def make_capped_search(cap_credits: float):
budget = SearchBudget(cap_credits)
def search(query: str, **kwargs) -> dict:
budget.charge(1.0)
result = _raw_search(query, **kwargs)
result["_budget"] = {"used": budget.used, "remaining": budget.remaining()}
return result
search.budget = budget
return search
if __name__ == "__main__":
search = make_capped_search(cap_credits=5)
queries = [
"best vector databases 2026",
"vector database comparison",
"pinecone vs weaviate vs chroma",
"vector db benchmarks",
"vector database pricing",
"vector db tutorial", # This should trigger BudgetExceeded
]
for q in queries:
try:
data = search(q)
top = data.get("organic_results", [{}])[0]
print(f"[{search.budget.used:.0f}/{search.budget.cap}] {top.get('title', 'no title')}")
except BudgetExceeded as e:
print(f"Stopped: {e}")
breakJavaScript Example
const API_KEY = 'your-scavio-api-key';
class BudgetExceeded extends Error {
constructor(used, cap) {
super(`Search budget exceeded: ${used}/${cap} credits`);
this.used = used;
this.cap = cap;
}
}
function makeCappedSearch(capCredits) {
let used = 0;
async function search(query, { platform, numResults = 10 } = {}) {
used += 1;
if (used > capCredits) throw new BudgetExceeded(used, capCredits);
const payload = { query, num_results: numResults };
if (platform) payload.platform = platform;
const res = await fetch('https://api.scavio.dev/api/v1/search', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'x-api-key': API_KEY },
body: JSON.stringify(payload)
});
if (!res.ok) throw new Error(`Search failed: ${res.status}`);
const data = await res.json();
data._budget = { used, remaining: Math.max(0, capCredits - used) };
return data;
}
search.getUsed = () => used;
return search;
}
const search = makeCappedSearch(5);
const queries = ['vector databases 2026', 'pinecone vs chroma', 'vector db pricing', 'weaviate tutorial', 'qdrant setup', 'overflow query'];
for (const q of queries) {
try {
const data = await search(q);
const top = data.organic_results?.[0];
console.log(`[${data._budget.used}/${5}] ${top?.title ?? 'no title'}`);
} catch (e) {
if (e instanceof BudgetExceeded) { console.log(`Stopped: ${e.message}`); break; }
throw e;
}
}Expected Output
[1/5] Top 10 Vector Databases in 2026
[2/5] Vector Database Comparison: Pinecone vs Chroma vs Weaviate
[3/5] Vector Database Pricing Guide 2026
[4/5] Weaviate Getting Started Tutorial
[5/5] Qdrant Setup and Configuration
Stopped: Search budget exceeded: 6/5 credits