@carloscortezcloud/sayay-guard
v0.2.0
Published
AI agent cost guardrails. Budget enforcement per user/session. Block, degrade, or warn before LLM calls exceed limits.
Maintainers
Readme
What Is Sayay?
Sayay (Quechua: "to stop/detain") stops your AI costs from running away. Set per-user daily/monthly budgets or credit systems. Before every LLM call, Sayay decides: allow, warn, degrade, or block.
import { SayayGuard, MemoryStorage } from 'sayay';
const guard = new SayayGuard({
storage: new MemoryStorage(),
budget: { dailyUsd: 5.00, monthlyUsd: 50.00 },
onExceeded: 'block',
degradeToModel: 'meta-llama/llama-3.3-70b-instruct:free',
});
// Before LLM call:
const decision = await guard.check('user-123', 0.003);
if (decision.action === 'block') {
throw new Error(`Budget exceeded: ${decision.reason}`);
}
if (decision.action === 'degrade') {
// Use decision.suggestedModel instead of expensive model
}
// After LLM call:
await guard.record('user-123', 0.0025);Install
npm install github:breakingthecloud/sayayQuick Start
import { SayayGuard, MemoryStorage } from 'sayay';
const guard = new SayayGuard({
storage: new MemoryStorage(),
budget: { dailyUsd: 5.00, monthlyUsd: 50.00 },
onExceeded: 'block',
degradeToModel: 'meta-llama/llama-3.3-70b-instruct:free',
});
const decision = await guard.check('user-123', 0.003);
console.log(decision.action); // 'allow' | 'warn' | 'degrade' | 'block'Actions
| Action | What happens |
|--------|--------------|
| allow | Call proceeds normally |
| warn | Call proceeds, but threshold reached (log it) |
| degrade | Call proceeds with cheaper model (decision.suggestedModel) |
| block | Call rejected, return error to user |
Thresholds
0%────────80%──────95%──────100%
allow │ warn │degrade│ blockConfigurable via warnThreshold and degradeThreshold.
Credit-Based System
const guard = new SayayGuard({
storage: new MemoryStorage(),
budget: { credits: 50, creditsPerCall: 1 },
onExceeded: 'block',
warnThreshold: 80,
});
await guard.record('user-123', 0, 1);
const usage = await guard.getUsage('user-123');
console.log(`Credits used: ${usage.credits}/50`);Storage Adapters
Sayay needs a storage backend to track usage. Built-in: MemoryStorage (testing) and DynamoStorage (DynamoDB, optional AWS dependency).
For production, implement SayayStorage:
// Cloudflare KV example:
class KVStorage implements SayayStorage {
constructor(private kv: KVNamespace) {}
async get(key: string) { return parseFloat(await this.kv.get(key) || '0'); }
async increment(key: string, amount: number, ttl?: number) {
const current = await this.get(key);
const newVal = current + amount;
await this.kv.put(key, String(newVal), ttl ? { expirationTtl: ttl } : undefined);
return newVal;
}
async reset(key: string) { await this.kv.delete(key); }
}DynamoStorage (optional)
Real-time token/cost ledger per customer/session in DynamoDB — survives Lambda warm
starts and is the "Sayay = Cost Guardrail in Lambda + DynamoDB" pattern. Requires
@aws-sdk/lib-dynamodb + @aws-sdk/client-dynamodb (lazy-imported, so the package
keeps zero hard dependencies).
import { SayayGuard, DynamoStorage } from '@carloscortezcloud/sayay-guard';
// Table: partition key `pk` (S), attribute `value` (N), TTL on `ttl` (N)
const guard = new SayayGuard({
storage: new DynamoStorage({ tableName: 'sayay-ledger', region: 'us-east-1' }),
budget: { dailyUsd: 10 },
});Step Functions: TokenBudgetExceededException
Use checkOrThrow() to raise a native exception when the budget is exhausted.
In AWS Step Functions, matching ErrorEquals: ["TokenBudgetExceededException"]
in a Catch block instantly jumps to the error handler — stopping the workflow
before retries rack up more cost.
import { SayayGuard, MemoryStorage } from '@carloscortezcloud/sayay-guard';
const guard = new SayayGuard({ storage: new MemoryStorage(), budget: { dailyUsd: 10 } });
// Throws TokenBudgetExceededException on block; returns decision otherwise
const decision = await guard.checkOrThrow('user-42', 0.005);// ASL snippet
"Catch": [
{
"ErrorEquals": ["TokenBudgetExceededException"],
"Next": "HandleBudgetExceeded"
}
]TokenBudgetExceededException extends BudgetExceededError, so existing
instanceof BudgetExceededError checks keep working (backward compatible).
CloudWatch observability (optional)
Pass cloudWatch in config to emit a metric per decision. Requires
@aws-sdk/client-cloudwatch (lazy-imported). Emits Decision, RemainingBudget,
and UsagePercent metrics under the Sayay namespace (configurable).
const guard = new SayayGuard({
storage,
budget: { dailyUsd: 10 },
cloudWatch: { metricNamespace: 'MyApp', region: 'us-east-1' },
});Integration with Styrr
import { StyrRouter } from 'styrr';
import { SayayGuard, MemoryStorage } from 'sayay';
const guard = new SayayGuard({ storage: new MemoryStorage(), budget: { dailyUsd: 10 } });
const router = new StyrRouter({ apiKey: '...', models: [...] });
async function safeLLMCall(userId: string, prompt: string) {
const decision = await guard.check(userId, 0.005);
if (decision.action === 'block') throw new Error(decision.reason);
const result = await router.prompt(prompt);
await guard.record(userId, result.usage?.totalTokens || 0.003);
return result;
}Ecosystem
| Package | Role | npm |
|---------|------|-----|
| Sayay | Cost guardrails (this) | GitHub |
| Styrr | LLM router | styrr |
| Tinkuy | Agent framework | @carloscortezcloud/tinkuy-agent |
| Qhaway | Agent observability | @carloscortezcloud/qhaway |
| TideRAG | Edge RAG pipeline | @carloscortezcloud/tiderag |
License
Apache 2.0 — see LICENSE.
