Get Orchid
Back to Blog

No SDK Required. LLM Observability in Any Language with Two Headers

Orchid Team5 min read

Most LLM observability tools have a quiet requirement buried in their docs. To get full visibility, you need their SDK, and their SDK supports two or three languages. Building in Go? Java? Ruby? Elixir? You're either out of luck or stuck with a degraded integration.

Orchid takes a different approach. The proxy is entirely header-driven. If your language can make an HTTP request and set a header, it has a first-class Orchid integration. That covers every language, every framework, and yes, shell scripts.

The Whole Integration in Two Steps

Orchid records LLM traffic by sitting between your application and the provider as a proxy. The complete picture is in Record, Inspect, Replay. To use it from any language, you do exactly two things.

  1. Point your client at the proxy instead of the upstream API.
  2. Add a few X-Orchid-* headers to control recording.

Your provider API key stays in the Authorization header exactly as before. The proxy forwards it untouched to the upstream and never stores it.

Here are the headers that matter.

Header Purpose
X-Orchid-Session-Id Groups exchanges under a named session, like checkout-flow-test-1.
X-Orchid-Mode capture to record, replay to serve recorded responses offline, passthrough to do nothing.
X-Orchid-Proxy-Key Authenticates your app to the proxy, if the proxy has an API key set.
X-Orchid-Target-Url For non-LLM APIs, tells the proxy where the request was originally headed.

All X-Orchid-* headers are stripped before the request is stored, so they never appear in recorded payloads.

Start with curl

The fastest way to see the mechanism is a raw request. This captures an OpenAI call under a named session.

curl http://localhost:4320/v1/chat/completions \
  -H "Authorization: Bearer $OPENAI_API_KEY" \
  -H "Content-Type: application/json" \
  -H "X-Orchid-Proxy-Key: $ORCHID_PROXY_KEY" \
  -H "X-Orchid-Session-Id: manual-test-1" \
  -H "X-Orchid-Mode: capture" \
  -d '{"model": "gpt-4o", "messages": [{"role": "user", "content": "Hello!"}]}'

Now replay the same call offline. Identical request, one header changed.

curl http://localhost:4320/v1/chat/completions \
  -H "Authorization: Bearer $OPENAI_API_KEY" \
  -H "Content-Type: application/json" \
  -H "X-Orchid-Proxy-Key: $ORCHID_PROXY_KEY" \
  -H "X-Orchid-Session-Id: manual-test-1" \
  -H "X-Orchid-Mode: replay" \
  -d '{"model": "gpt-4o", "messages": [{"role": "user", "content": "Hello!"}]}'

In replay mode the proxy matches the request by a semantic hash of the prompt and serves the recorded response. No upstream call, no API cost. The full testing workflow built on this is covered in Zero-Cost AI Testing.

Go

Most Go LLM clients accept a base URL override. For the headers, a custom http.RoundTripper applies them globally, which is the idiomatic Go equivalent of what the Orchid SDKs do in Python and TypeScript.

type orchidTransport struct{ base http.RoundTripper }

func (t *orchidTransport) RoundTrip(req *http.Request) (*http.Response, error) {
	req.Header.Set("X-Orchid-Proxy-Key", os.Getenv("ORCHID_PROXY_KEY"))
	req.Header.Set("X-Orchid-Session-Id", os.Getenv("ORCHID_SESSION_ID"))
	req.Header.Set("X-Orchid-Mode", os.Getenv("ORCHID_MODE"))
	return t.base.RoundTrip(req)
}

client := openai.NewClient(
	option.WithBaseURL("http://localhost:4320/v1"),
	option.WithHTTPClient(&http.Client{Transport: &orchidTransport{base: http.DefaultTransport}}),
)

Because the mode comes from an environment variable, the same binary records in development and replays in CI with no code change.

Java

The official OpenAI Java SDK, along with Spring AI and LangChain4j, supports base URL and header configuration directly.

OpenAIClient client = OpenAIOkHttpClient.builder()
    .baseUrl("http://localhost:4320/v1")
    .putHeader("X-Orchid-Proxy-Key", System.getenv("ORCHID_PROXY_KEY"))
    .putHeader("X-Orchid-Session-Id", "my-session")
    .putHeader("X-Orchid-Mode", "capture")
    .apiKey(System.getenv("OPENAI_API_KEY"))
    .build();

Every Other Language

The recipe never changes.

  1. Set the client's base URL to the proxy. For OpenAI-compatible clients that's http://localhost:4320/v1.
  2. Attach the X-Orchid-* headers using whatever default-headers, interceptor, or middleware hook your HTTP stack provides. Nearly all of them have one.
  3. Keep your real provider key in Authorization as usual.

If your client offers no header hooks at all, the proxy's control API supports a global session override, so plain base URL redirection is enough on its own.

Not Just LLM Traffic

Agents call more than language models. They hit search APIs, vector stores, and internal services, and failures there are just as hard to debug. Orchid captures any HTTP API with one extra header. Send the request to the proxy with the original path and tell it where the request was headed.

GET http://localhost:4320/search?q=weather
X-Orchid-Target-Url: https://serpapi.com

Now your tool calls appear in the same session timeline as your LLM exchanges, and they replay in tests just like everything else.

The Payoff

Once your traffic flows through the proxy, the entire Orchid feature set is available regardless of your application language. The visualizer timeline, payload inspection, cost tracking, agent-driven debugging over MCP, and fixture export for CI replay all operate on the recording, not on your code.

For session export and import across environments, two control API calls complete the loop.

# Export a session as a JSON fixture
curl -H "X-Orchid-Api-Key: $ORCHID_API_KEY" \
  http://localhost:4321/v1/sessions/manual-test-1/export > fixture.json

# Import it elsewhere, for example in CI
curl -X POST -H "X-Orchid-Api-Key: $ORCHID_API_KEY" \
  -H "Content-Type: application/json" \
  --data @fixture.json \
  http://localhost:4321/v1/sessions/import

If you've been told observability requires adopting someone's SDK, try the two-header version instead. Spin up the proxy, redirect one request, and open the visualizer at http://localhost:4321. Get started at orchidtrace.xyz.