AI gateways
How to add Caveman to Portkey without changing configs
Keep Portkey as your gateway and wrap one agent's OpenAI client with Caveman Middleware. Step by step setup, how to confirm it in Portkey logs, and rollback.
- 01Start the local runtime
- 02Wrap the Portkey client
- 03Find the request in Portkey logs
- 04Keep it or turn it off
This guide adds Caveman Middleware to an app that already sends its LLM calls through Portkey. Portkey stays the gateway: same base URL, same headers, same configs and guardrails. Caveman shortens large tool results in the outgoing copy of each request, so Portkey forwards fewer tokens. Try it on one agent first. For the reasoning, read the Portkey comparison.
What you need before you start
- An app that calls Portkey through the OpenAI SDK, the Anthropic SDK, or a framework such as the Vercel AI SDK, LangChain or Pydantic AI.
- Node.js 22.13 or newer for the
cavemanCLI. Python code needs Python 3.13 or newer. - One agent whose tools return big text, and a task with a known answer.
Step 1: Start the Caveman runtime
npm install -g @caveman-ai/cli && caveman setup --install
CAVEMAN_MODE=compress caveman startThe runtime listens on 127.0.0.1:8787 and keeps originals on local disk. If you also run Portkey's open-source gateway on this machine, it wants port 8787 too: start Caveman with --port 8788 and use that in the endpoint below. Confirm it is in compress mode:
curl -s http://127.0.0.1:8787/caveman/v1/middleware/capabilities | jq '{mode, recovery}'Step 2: Wrap the client you point at Portkey
In Python, install the adapter for the SDK you use. For OpenAI:
pip install caveman-sdk "caveman-middleware[openai]==0.1.0a1" portkey-aiBuild the OpenAI client exactly as Portkey documents it, then wrap it. with_caveman_openai_tools takes your tool definitions and the functions that run them, and adds caveman_retrieve so the model can ask for an original back.
import json, os
from openai import OpenAI
from portkey_ai import createHeaders
from caveman_cloud.middleware import MiddlewareRuntime, Scope
from caveman_middleware.openai import with_caveman_openai_tools
client = OpenAI(
api_key=os.environ["OPENAI_API_KEY"],
base_url="https://api.portkey.ai/v1",
default_headers=createHeaders({"apiKey": os.environ["PORTKEY_API_KEY"],
"provider": "@openai-prod"}),
)
mode = os.environ.get("CAVEMAN_CLIENT_MODE", "compress")
runtime = MiddlewareRuntime(endpoint="http://127.0.0.1:8787", mode=mode,
deadline_ms=500, on_report=print)
if mode != "off":
runtime.ready()
scope = Scope("support-agent", conversation_id)
loop = with_caveman_openai_tools(client, runtime=runtime, scope=scope,
protocol="openai-chat", tools=tools,
functions={"search_tickets": search_tickets})
for _ in range(8):
response = loop.client.chat.completions.create(
model="gpt-5.5", messages=messages, tools=loop.tools)
message = response.choices[0].message
messages.append(message.model_dump(exclude_none=True))
if not message.tool_calls:
break
for call in message.tool_calls:
result = loop.functions[call.function.name](json.loads(call.function.arguments))
messages.append({"role": "tool", "tool_call_id": call.id,
"content": result if isinstance(result, str) else json.dumps(result)})tools, messages and search_tickets are yours; use your own provider slug and model. Keep conversation_id the same for the whole conversation, since recovery handles only resolve under the scope that issued them. The adapter needs openai >=3.10 <4.
In TypeScript with the Vercel AI SDK, keep your Portkey provider and wrap the call options:
import { stepCountIs, streamText } from "ai";
import { createOpenAI } from "@ai-sdk/openai";
import { createMiddlewareRuntime } from "@caveman-ai/sdk/middleware";
import { withCaveman } from "@caveman-ai/middleware/ai-sdk";
const portkey = createOpenAI({
baseURL: "https://api.portkey.ai/v1",
headers: { "x-portkey-api-key": process.env.PORTKEY_API_KEY!, "x-portkey-provider": "@openai-prod" },
});
const mode = (process.env.CAVEMAN_CLIENT_MODE ?? "compress") as "off" | "record" | "compress";
const runtime = createMiddlewareRuntime({ endpoint: "http://127.0.0.1:8787", mode });
const scope = { namespace: "support", session_id: conversationId, branch_id: "main", cache_epoch: "0" };
if (mode !== "off") await runtime.ready();
const result = streamText({
...withCaveman({ model: portkey.chat("gpt-5.5"), tools }, { runtime, scope }),
messages,
stopWhen: stepCountIs(8),
});Other frameworks follow the same idea: configure the model to use Portkey as you do now, then add the Caveman adapter from the framework list.
How to check it worked
- The reports. In Python,
on_report=printlogs one decision per call. Look forappliedorreusedwith a transform likecaveman.engine.json.v1on calls that carry big tool output.unsupported_versionmeans youropenaipackage is outside the accepted range, and the call went out unchanged. - Portkey logs. Open the request in Portkey. The tool message should start with
[caveman: shortened; exact original via caveman_retrieve handle=cmw_…]. Sending anx-portkey-trace-idheader makes the test runs easy to find. - Tokens. Run the same task with
CAVEMAN_CLIENT_MODE=offand withCAVEMAN_CLIENT_MODE=compress, and compare input tokens in Portkey's analytics or inresponse.usage. Those counts come from the provider. - Quality. Check the answer, and count
caveman_retrievecalls. If the model keeps asking for originals, the saving shrinks.
Also re-run a guardrail you rely on with a known bad input, so you know it still fires on the shortened copy. The measurement guide covers repeats, caching and retries.
How to roll back
Set CAVEMAN_CLIENT_MODE=off and restart the app. The snippets pass it as mode on the runtime client and skip ready(), so the adapter sends every request to Portkey unchanged without touching the call site. To remove Caveman completely, call client instead of loop.client and pass your own tools again, or drop withCaveman(...) in TypeScript. Nothing in Portkey needs undoing.
Sources: Portkey request headers, Portkey gateway README, Caveman Python middleware, Caveman TypeScript middleware.
Try it on work you know
Keep your stack. Add the adapter.
Wrap the client or framework you already call, run one agent task you can check, and compare the tokens sent.
Open the middleware docs Explore comparisons ↗