Compare commits

...
Author SHA1 Message Date
wylabandClaude Sonnet 4.5 48ee7e1fd0 feat: capture Anthropic rate limit headers for quota-based model switching
- Writes rate_limits.json after every API call with weekly/5h utilization
- Writes api_headers.jsonl with raw headers for analysis
- Upgrades quota fallback model from Sonnet 4.5 to 4.6

Deploy to site-packages (gateway loads from there, not /app/):
  docker cp to /usr/local/lib/python3.12/site-packages/nanobot/providers/

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-18 20:18:39 +00:00
wylab 01d5ce9f1b Revert "feat: capture Anthropic rate limit headers for heartbeat throttling"
This reverts commit f58d967487.
2026-02-18 19:03:20 +00:00
wylab b6d96d926a Revert "fix: log rate limit header capture exceptions instead of silently ignoring"
This reverts commit 684233240b.
2026-02-18 19:03:20 +00:00
wylabandClaude Sonnet 4.5 684233240b fix: log rate limit header capture exceptions instead of silently ignoring
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-18 18:52:55 +00:00
nanobotandwylab f58d967487 feat: capture Anthropic rate limit headers for heartbeat throttling
Adds rate limit state capture to _make_request():
- Parses 7 Anthropic rate limit headers from every API response
- Writes to memory/rate_limits.json with utilization, reset times, binding limit
- Enables HEARTBEAT.md to throttle based on weekly_all_models budget

Part of rate limit monitoring implementation plan.
2026-02-18 18:38:10 +00:00
2 changed files with 34 additions and 1 deletions
+1 -1
View File
@@ -156,7 +156,7 @@ class AgentLoop:
# Default models # Default models
OPUS = "claude-opus-4-6" OPUS = "claude-opus-4-6"
SONNET = "claude-sonnet-4-5" SONNET = "claude-sonnet-4-6"
TOLERANCE = 1.17 # 17% overage triggers downgrade TOLERANCE = 1.17 # 17% overage triggers downgrade
# Read rate limits # Read rate limits
+33
View File
@@ -269,6 +269,39 @@ class AnthropicOAuthProvider(LLMProvider):
json=payload, json=payload,
) )
# Dump rate limit headers for analysis
try:
import datetime
import os
header_dump = {
"timestamp": datetime.datetime.utcnow().isoformat(),
"status_code": response.status_code,
"model": payload.get("model"),
"headers": dict(response.headers),
}
dump_path = "/root/.nanobot/workspace/api_headers.jsonl"
with open(dump_path, "a") as f:
f.write(json.dumps(header_dump) + "\n")
# Capture rate limit state for quota-based model switching
hdrs = response.headers
rate_limit_state = {
"updated_at": datetime.datetime.utcnow().isoformat(),
"model": payload.get("model"),
"weekly_all_models": float(hdrs["anthropic-ratelimit-unified-7d-utilization"]) if hdrs.get("anthropic-ratelimit-unified-7d-utilization") else None,
"weekly_sonnet": float(hdrs["anthropic-ratelimit-unified-7d_sonnet-utilization"]) if hdrs.get("anthropic-ratelimit-unified-7d_sonnet-utilization") else None,
"session_5h": float(hdrs["anthropic-ratelimit-unified-5h-utilization"]) if hdrs.get("anthropic-ratelimit-unified-5h-utilization") else None,
"weekly_reset": int(hdrs["anthropic-ratelimit-unified-7d-reset"]) if hdrs.get("anthropic-ratelimit-unified-7d-reset") else None,
"session_reset": int(hdrs["anthropic-ratelimit-unified-5h-reset"]) if hdrs.get("anthropic-ratelimit-unified-5h-reset") else None,
"binding_limit": hdrs.get("anthropic-ratelimit-unified-representative-claim"),
"sonnet_fallback": hdrs.get("anthropic-ratelimit-unified-fallback"),
}
state_path = "/root/.nanobot/workspace/memory/rate_limits.json"
os.makedirs(os.path.dirname(state_path), exist_ok=True)
with open(state_path, "w") as f:
json.dump(rate_limit_state, f, indent=2)
except Exception as e:
logger.warning("Rate limit header capture failed: {}", e)
if response.status_code != 200: if response.status_code != 200:
error_text = response.text error_text = response.text
raise Exception(f"Anthropic API error {response.status_code}: {error_text}") raise Exception(f"Anthropic API error {response.status_code}: {error_text}")