

Anthropic’s $11.6 B, 7‑year deal with Akamai signals a major shift: AI workloads are moving to the edge. Below is a ready‑to‑run guide that shows how to call Anthropic’s Claude model from a service hosted on Akamai (EdgeWorkers or Akamai Cloud Computing) and how to production‑ize the integration.
<a name="step-1-prerequisites"></a>
| Item | Why you need it | How to obtain |
|---|---|---|
| Anthropic API key | Authenticates calls to Claude (v1/v2) | Sign up at https://console.anthropic.com/ → API Keys → create a key |
| Akamai account | Host the service (EdgeWorker or Akamai Cloud) | https://www.akamai.com/ → sign up → create a property or EdgeWorker |
| Python ≥ 3.9 (or Node ≥ 18) | Runtime for the sample code | python --version / node --version |
| Package managers | Install dependencies | pip (Python) & npm or yarn (JS/TS) |
| Git (optional) | Clone the repo if you prefer | git --version |
| dotenv (or similar) | Load secrets from .env safely | Included in the install steps |
Tip: Keep the Anthropic key out of source control. Add
.envto.gitignore.
<a name="step-2-installation-and-setup"></a>
# 1️⃣ Clone a starter folder (optional)
mkdir anthropic-akamai-demo && cd $_
# 2️⃣ Create a virtual environment
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
# 3️⃣ Install core packages
pip install --upgrade pip
pip install fastapi uvicorn[standard] anthropic python-dotenv
# 4️⃣ (Optional) Install dev tools
pip install black isort flake8 pytest
mkdir anthropic-akamai-demo && cd $_
# Initialize npm project
npm init -y
# Install dependencies
npm install express dotenv @anthropic-ai/sdk
# For TypeScript (optional but recommended)
npm install --save-dev typescript ts-node @types/node @types/express
# Initialize TS config (if using TS)
npx tsc --init --rootDir src --outDir dist --esModuleInterop --resolveJsonModule --lib es6,dom
Akamai EdgeWorker note: If you plan to deploy as an EdgeWorker, replace
expresswith the@akamai/edgeworkerruntime (the code below works unchanged because it only usesfetch). Thepackage.jsonwould then list@akamai/edgeworkeras a dependency instead ofexpress.
<a name="step-3-basic-implementation"></a>
The following snippets expose a single HTTP endpoint (/summarize) that:
{ "text": "<your‑text>" }.The prompt is deliberately simple so you can swap it for any other instruction (translation, classification, etc.).
File: app.py
"""
app.py – FastAPI wrapper around Anthropic's Claude.
Deployable to Akamai Cloud Computing (Linux VM/containers) or as a containerized EdgeWorker.
"""
import os
import logging
from fastapi import FastAPI, HTTPException, Request
from pydantic import BaseModel
from anthropic import Anthropic, AnthropicError
from dotenv import load_dotenv
# ----------------------------------------------------------------------
# Load environment variables from .env (if present)
# ----------------------------------------------------------------------
load_dotenv() # reads ANTHROPIC_API_KEY, etc.
# ----------------------------------------------------------------------
# Configure logging (structured JSON is preferred in prod)
# ----------------------------------------------------------------------
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s %(levelname)s %(name)s %(message)s",
)
logger = logging.getLogger("anthropic-akamai")
# ----------------------------------------------------------------------
# Initialize FastAPI + Anthropic client
# ----------------------------------------------------------------------
app = FastAPI(title="Anthropic‑Claude on Akamai")
anthropic = Anthropic(api_key=os.getenv("ANTHROPIC_API_KEY"))
# ----------------------------------------------------------------------
# Request / Response models
# ----------------------------------------------------------------------
class SummarizeRequest(BaseModel):
text: str
max_tokens: int = 256 # optional override
class SummarizeResponse(BaseModel):
summary: str
model: str
usage: dict # token usage returned by Anthropic
# ----------------------------------------------------------------------
# Helper: call Claude with retry & back‑off (simple exponential)
# ----------------------------------------------------------------------
import time
import random
def call_claude(prompt: str, max_tokens: int = 256) -> dict:
"""
Sends a prompt to Claude and returns the raw response dict.
Implements a tiny retry loop for transient errors (network, 5xx).
"""
max_attempts = 3
for attempt in range(1, max_attempts + 1):
try:
logger.info(f"Calling Claude (attempt {attempt})")
response = anthropic.completions.create(
model="claude-2", # or "claude-instant-1" for cheaper/faster
prompt=f"\n\nHuman: {prompt}\n\nAssistant:",
max_tokens_to_sample=max_tokens,
temperature=0.2, # low temp for deterministic summaries
stop_sequences=["\n\nHuman:"],
)
# The SDK returns an object; convert to dict for JSON serialization
return {
"completion": response.completion,
"model": response.model,
"usage": {
"prompt_tokens": response.usage.prompt_tokens,
"completion_tokens": response.usage.completion_tokens,
"total_tokens": response.usage.total_tokens,
},
}
except AnthropicError as exc:
# 4xx errors are usually client‑side (bad request, auth) → don't retry
if exc.status_code < 500:
logger.error(f"Client error from Anthropic: {exc}")
raise HTTPException(status_code=exc.status_code, detail=str(exc))
# 5xx or network → retry with back‑off
wait = (2 ** attempt) + random.uniform(0, 1)
logger.warning(
f"Transient error ({exc.status_code}): retrying in {wait:.1f}s"
)
time.sleep(wait)
# If we exhausted retries
logger.error("Failed to call Claude after retries")
raise HTTPException(
status_code=502,
detail="Unable to reach Anthropic service after several attempts",
)
# ----------------------------------------------------------------------
# Routes
# ----------------------------------------------------------------------
@app.post("/summarize", response_model=SummarizeResponse)
async def summarize(payload: SummarizeRequest):
"""
Expected JSON:
{ "text": "Long article …", "max_tokens": 200 }
Returns:
{ "summary": "...", "model": "claude-2", "usage": {...} }
"""
if not payload.text.strip():
raise HTTPException(status_code=400, detail="`text` field must not be empty")
# Construct a summarization prompt – feel free to customize
prompt = (
"You are a precise summarizer. Provide a concise summary (2‑3 sentences) "
"of the following passage, preserving key facts and tone.\n\n"
f"{payload.text}"
)
result = call_claude(prompt, max_tokens=payload.max_tokens)
return SummarizeResponse(
summary=result["completion"].strip(),
model=result["model"],
usage=result["usage"],
)
# ----------------------------------------------------------------------
# Healthcheck (useful for Akamai load‑balancer / Kubernetes probes)
# ----------------------------------------------------------------------
@app.get("/healthz")
async def healthz():
return {"status": "ok"}
Run locally
uvicorn app:app --host 0.0.0.0 --port 8080 --reload
Deploy to Akamai:
- Build a Dockerfile (see below) and push to your Akamai Cloud Container Registry.
- Create a Cloud Computing service pointing to the image, expose port 8080, and attach an Edge DNS property.
- For EdgeWorkers, you would replace the FastAPI server with a simple
fetchhandler (see JS version) and upload the worker bundle via Akamai Control Center.
Dockerfile (optional but recommended)
# ---- Base image ----
FROM python:3.12-slim
# ---- System deps ----
RUN apt-get update && apt-get install -y --no-install-recommends \
gcc libffi-dev && \
rm -rf /var/lib/apt/lists/*
# ---- Workdir ----
WORKDIR /app
# ---- Python deps ----
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# ---- App code ----
COPY app.py .
# ---- Runtime ----
EXPOSE 8080
ENV PYTHONUNBUFFERED=1
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8080"]
Create a requirements.txt:
fastapi
uvicorn[standard]
anthropic
python-dotenv
File: src/server.ts (if using TypeScript) or src/server.js for plain JS.
/**
* server.ts – Thin Express wrapper around Anthropic's Claude.
* Works on Akamai Cloud Computing (Node.js) or as an EdgeWorker
* (replace Express with the Akamai EdgeWorker fetch handler).
*/
import express, { Request, Response } from "express";
import dotenv from "dotenv";
import { Anthropic } from "@anthropic-ai/sdk";
dotenv.config(); // loads .env → ANTHROPIC_API_KEY
const app = express();
app.use(express.json()); // parse JSON bodies
// ----------------------------------------------------------------------
// Anthropic client
// ----------------------------------------------------------------------
const anthropic = new Anthropic({
apiKey: process.env.ANTHROPIC_API_KEY!, // will throw if missing
});
// ----------------------------------------------------------------------
// Helper: exponential back‑off wrapper
// ----------------------------------------------------------------------
async function callClaudeWithRetry(
prompt: string,
maxTokens: number = 256,
maxAttempts: number = 3
): Promise<{ completion: string; model: string; usage: any }> {
let attempt = 0;
while (true) {
attempt++;
try {
const resp = await anthropic.completions.create({
model: "claude-2",
prompt: `\n\nHuman: ${prompt}\n\nAssistant:`,
max_tokens_to_sample: maxTokens,
temperature: 0.2,
stop_sequences: ["\n\nHuman:"],
});
return {
completion: resp.completion,
model: resp.model,
usage: {
prompt_tokens: resp.usage.prompt_tokens,
completion_tokens: resp.usage.completion_tokens,
total_tokens: resp.usage.total_tokens,
},
};
} catch (err: any) {
// Anthropic SDK throws an AnthropicError with status & message
if (err.status && err.status < 500) {
// client error (4xx) – don't retry
throw err;
}
if (attempt >= maxAttempts) {
throw err;
}
const wait = Math.pow(2, attempt) + Math.random() * 1000; // ms
console.warn(
`[callClaudeWithRetry] Attempt ${attempt} failed (${err.message}). Retrying in ${wait}ms…`
);
await new Promise((res) => setTimeout(res, wait));
}
}
}
// ----------------------------------------------------------------------
// Route: POST /summarize
// ----------------------------------------------------------------------
interface SummarizeReq {
text: string;
max_tokens?: number;
}
interface SummarizeRes {
summary: string;
model: string;
usage: any;
}
app.post("/summarize", async (req: Request<{}, any, SummarizeReq>, res: Response<SummarizeRes>) => {
const { text, max_tokens = 256 } = req.body;
if (!text || !text.trim()) {
return res.status(400).json({ error: "`text` must be a non‑empty string" });
}
const prompt = `
You are a precise summarizer. Provide a concise summary (2‑3 sentences) of the following passage,
preserving key facts and tone.
${text}
`.trim();
try {
const { completion, model, usage } = await callClaudeWithRetry(prompt, max_tokens);
return res.json({
summary: completion.trim(),
model,
usage,
});
} catch (err: any) {
console.error("Anthropic error:", err);
const status = err.status ?? 502;
return res.status(status).json({ error: err.message ?? "Unknown error" });
}
});
// ----------------------------------------------------------------------
// Healthcheck
// ----------------------------------------------------------------------
app.get("/healthz", (_req, res) => res.json({ status: "ok" }));
// ----------------------------------------------------------------------
// Start server (only when run directly, not when imported)
// ----------------------------------------------------------------------
const PORT = process.env.PORT ?? 8080;
if (require.main === module) {
app.listen(PORT, () => {
console.log(`🚀 Server listening on http://0.0.0.0:${PORT}`);
});
}
export default app; // needed for testing / edgeworker adapters
Compile (if using TS)
npx tsc # outputs to ./dist/
node dist/server.js
Run locally (JS)
node src/server.js
Dockerfile (Node.js)
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
EXPOSE 8080
ENV NODE_ENV=production
CMD ["node", "dist/server.js"] # adjust if you kept JS source
EdgeWorker variant: Replace the Express app with the Akamai EdgeWorker skeleton:
import { createResponse } from "edgeworker";
export default async function handler(request) {
if (request.method !== "POST" || request.url.pathname !== "/summarize") {
return new Response("Not Found", { status: 404 });
}
const body = await request.json();
// reuse callClaudeWithRetry logic (fetch‑based) …
// return new Response(JSON.stringify(result), {
// headers: { "Content-Type": "application/json" },
// });
}
The core Claude‑calling logic (callClaudeWithRetry) stays the same; only the HTTP framework changes.
<a name="step-4-configuration"></a>
Create a .env file at the project root (never commit it).
# .env – keep this file out of version control
ANTHROPIC_API_KEY=sk-ant-api03-... # your Anthropic key
# Optional overrides
PORT=8080 # change if you need a different port
LOG_LEVEL=info # debug, info, warn, error
Loading the variables
python-dotenv (see load_dotenv() at top of app.py).dotenv.config() (see top of server.ts).Akamai‑specific settings
| Setting | Where to set | Example |
|---|---|---|
| EdgeWorker bundle | Akamai Control Center → Properties → EdgeWorker → Upload ZIP | edgeworker-bundle.zip containing server.js (or the compiled JS) |
| Container image registry | Akamai Cloud → Container Registry | registry.akamai.com/myorg/claude-summarizer:latest |
| Environment variables in Akamai Cloud | Service → Configuration → Env Vars | Add ANTHROPIC_API_KEY as a secret (masked) |
| TLS / Custom hostname | Property → Edge Hostnames | ai-summary.example.com (CNAME to your Akamai edge hostname) |
| Rate‑limit / quotas | Property → Rate‑Clipping or Cloud Computing → Autoscaling | Set max RPS, concurrency, etc. |
Security tip: Store the Anthropic key as a secret in Akamai’s vault (or Kubernetes secrets) and inject it at runtime; never bake it into the image.
<a name="step-5-common-patterns"></a>
Handles transient 5xx or network glitches without overwhelming the API.
Keep prompts in separate files or constants to ease A/B testing.
SUMMARY_PROMPT = """
You are a precise summarizer. Provide a concise summary (2‑3 sentences) of the following passage,
preserving key facts and tone.
{text}
"""
Anthropic returns usage. Log it and feed into a monitoring system (Datadog, Prometheus, Akamai Log Delivery).
logger.info(
"Claude call completed",
extra={
"model": result["model"],
"prompt_tokens": result["usage"]["prompt_tokens"],
"completion_tokens": result["usage"]["completion_tokens"],
"total_tokens": result["usage"]["total_tokens"],
},
)
Reject overly large payloads (e.g., > 10 KB) to avoid excessive token consumption and potential abuse.
if len(payload.text) > 10_000:
raise HTTPException(status_code=413, detail="Input too large")
If the same text is summarized often, cache the result (Redis, Akamai Edge KV, or in‑process LRU for low‑traffic services).
from functools import lru_cache
@lru_cache(maxsize=1024)
def cached_summarize(text: str, max_tokens: int) -> str:
# call Claude …
If Anthropic is unavailable, return a fallback (e.g., a simple extractive summary) or a 503 with a retry‑after header.
<a name="step-6-troubleshooting"></a>
| Symptom | Likely Cause | Fix |
|---|---|---|
401 Unauthorized from Anthropic | Missing or incorrect ANTHROPIC_API_KEY | Verify .env contains the key; ensure no extra spaces; check that the key is active in the Anthropic console. |
429 Too Many Requests | Exceeded Anthropic rate limit | Implement client‑side rate limiting (token bucket) or request a higher tier from Anthropic. |
502 Bad Gateway from your service | Upstream (Anthropic) timeout or network issue | Check retry logs; increase timeout; verify outbound internet access from Akamai compute (egress allowed). |
ModuleNotFoundError: anthropic | SDK not installed in the runtime | Re‑run pip install anthropic (Python) or npm install @anthropic-ai/sdk (Node) inside the container/VM. |
Error: Cannot find module 'dotenv' | .env not loaded or dotenv missing | npm install dotenv and ensure dotenv.config() runs before accessing process.env. |
EdgeWorker returns 500 Internal Server Error | Uncaught exception in worker | Add try/catch around the handler and log err.stack; enable EdgeWorker Log Delivery for details. |
| High latency (> 2 s) | Cold start of container or large prompt | Keep a minimum number of warm instances (Akamai Cloud → Autoscaling min‑instances); consider truncating input or using claude-instant-1 for faster response. |
| Unexpected token usage spikes | Prompt too long or model set to claude-2 with high max_tokens | Cap input length; lower max_tokens; switch to cheaper model for non‑critical tasks. |
Debugging tip: Enable verbose logging (LOG_LEVEL=debug) and forward logs to a centralized system (Akamai Log Delivery, Splunk, ELK). The logs will show the exact request/response payloads (strip the API key before sending to external systems).
<a name="step-7-production-checklist"></a>
| ✅ Item | Why it matters | How to verify |
|---|---|---|
| Secrets management | Prevent key leakage | Store ANTHROPIC_API_KEY in Akamai Secret Manager or Kubernetes secret; confirm echo $ANTHROPIC_API_KEY is not visible in image layers (docker history). |
| HTTPS everywhere | Protect data in transit | Ensure Akamai property enforces TLS 1.2+; test with curl -v https://ai-summary.example.com/healthz. |
| Input size limits | Avoid runaway token cost & DoS | Enforce max request body (e.g., client_max_body_size 64k in NGINX or Express limit: '64kb'). |
| Rate limiting & throttling | Protect upstream API and your bill | Use Akamai Rate‑Clipping or a token‑bucket middleware; return 429 with Retry-After. |
| Observability | Detect regressions & cost overruns | - Structured JSON logs (timestamp, level, request_id, model, usage). <br> - Metrics: request latency, token usage, error rates (Prometheus/Grafana or Akamai Metrics). |
| Health checks | Enable auto‑healing & load‑balancing | /healthz returns 200 within < 100 ms; configure Akamai health probes. |
| Graceful shutdown | Avoid dropping in‑flight requests | Listen for SIGTERM; stop accepting new connections, drain existing ones, then exit. |
| Dependency pinning | Prevent breaking changes | Use requirements.txt with exact versions (anthropic==0.15.0) and package-lock.json. |
| CI/CD pipeline | Catch bugs early | - Unit tests (mock Anthropic client). <br> - Linting (black/flake8, eslint). <br> - Build Docker image, push to Akamai registry, deploy via ArgoCD or Akamai Deploy. |
| Disaster recovery | Ensure continuity | Have a backup region (e.g., another Akamai cloud location) and a fallback to a self‑hosted model or a cached response. |
| Cost monitoring | Stay within budget | Set up alerts on Anthropic token usage (via their usage dashboard) and on Akamai compute spend. |
| Documentation & onboarding | Reduce friction for teammates | Keep a README.md with setup, env var list, and deployment steps. |
| Legal / compliance | Meet data‑privacy rules | Verify that sending user‑provided text to Anthropic complies with your data‑processing addendum (DPA) and any regional restrictions (e.g., GDPR). |
You now have:
Feel free to adapt the prompt, swap the model (claude-instant-1 for lower cost, claude-2 for higher quality), or plug the service into a larger AI‑powered pipeline (e.g., summarizing news articles before pushing them to an Akamai Edge KV cache).
Happy building, and enjoy the speed and scale that Anthropic + Akamai can bring to your AI workloads! 🚀
Source: TechCrunch AI
Follow ICARAX for more AI insights and tutorials.
