# SDK reference

> Sessions, named steps, workers, redaction and every option of the Python and Node SDKs.

The SDK records every model call your process makes through a supported client, streamed or not, sync or async, and sends it in the background. It is open source under Apache-2.0: [read the source](https://github.com/GeneralContext/sdk). New here? Start with the [Quickstart](https://generalcontextlabs.com/docs/quickstart).

## Supported providers

| Provider | Python | Node.js |
| --- | --- | --- |
| OpenAI, including Azure OpenAI and any OpenAI-compatible base URL | `openai` (chat completions and responses) | `openai` |
| Anthropic, including Bedrock, Vertex and beta clients | `anthropic` | `@anthropic-ai/sdk` |
| Google Gemini | `google-genai` | `@google/genai` |
| AWS Bedrock Converse | `boto3` `bedrock-runtime` (`converse`, `converse_stream`) | `@aws-sdk/client-bedrock-runtime` (`ConverseCommand`, `ConverseStreamCommand`) |
| OpenAI Realtime (voice) | `client.realtime.connect()`, sync and async (`openai[realtime]`) | `OpenAIRealtimeWS` and `OpenAIRealtimeWebSocket` |
| Anything else | Send [OpenTelemetry](https://generalcontextlabs.com/docs/opentelemetry) | Send [OpenTelemetry](https://generalcontextlabs.com/docs/opentelemetry) |

Streams reach your code unchanged and are recorded once, as one assembled reply. Bedrock images and documents are sent as `{"omitted": "bytes"}`. Providers that are not installed are skipped.

## Voice agents

A phone call is one conversation, like a chat. What we record is what was said: the caller's words, the agent's spoken replies, every tool call, and how the call went. Audio itself is never sent, by any of the SDKs: inline audio becomes a marker such as `[omitted 48211 bytes sha256:...]`.

- **OpenAI Realtime.** Open the connection inside a session, with the id of the call, and the whole call is recorded as it happens: each reply the agent speaks, what the caller said before it, and tool calls and their results. Turn on input audio transcription in the session, or the caller's side is missing.
- **Audio models over chat completions** (`gpt-audio` and similar). Recorded like any other call. A spoken reply shows its transcript.
- **A voice platform or your own speech-to-text and text-to-speech.** The model in the middle is an ordinary client call, so it is recorded already. Pass the call's id as the session so every turn of one call lands together.

On a call, each spoken reply also shows how long the caller waited (from when they stopped speaking to the agent's first word) and whether they cut in. The conversation list calls out replies slower than 2 seconds and calls where the caller cut in.

Python:

```python
from openai import AsyncOpenAI
import generalcontext

generalcontext.initialize()
client = AsyncOpenAI()

with generalcontext.session(call.sid, channel="voice"):
    async with client.realtime.connect(model="gpt-realtime") as connection:
        await connection.session.update(session={
            "type": "realtime",
            "audio": {"input": {"transcription": {"model": "gpt-live-transcribe"}}},
        })
        await bridge(call, connection)  # your audio loop, unchanged
```

Node.js:

```ts
import OpenAI from 'openai';
import { OpenAIRealtimeWS } from 'openai/realtime/ws';
import { initialize, withSession } from 'generalcontext';

initialize({ instrumentModules: { openAI: OpenAI } });

await withSession({ sessionId: call.sid, channel: 'voice' }, async () => {
  const rt = await OpenAIRealtimeWS.create(new OpenAI(), { model: 'gpt-realtime' });
  rt.send({ type: 'session.update', session: { type: 'realtime', audio: { input: { transcription: { model: 'gpt-live-transcribe' } } } } });
  bridge(call, rt);  // your audio loop, unchanged
});
```

## Supported versions

|  | Python | Node.js |
| --- | --- | --- |
| Runtime | 3.9 to 3.13 | 18 to 24 |
| OpenAI | `openai` 1.0.0 and later | `openai` 4.0.0 and later |
| Anthropic | `anthropic` 0.20.0 and later | `@anthropic-ai/sdk` 0.20.0 and later |
| Google Gemini | `google-genai` 1.0.0 and later | `@google/genai` 1.0.0 and later |
| AWS Bedrock Converse | `boto3` 1.34.116 and later | `@aws-sdk/client-bedrock-runtime` 3.587.0 and later |

Each release is tested from these minimums to the latest versions, and the results are in [stress/compat/RESULTS.md](https://github.com/GeneralContext/sdk/blob/main/stress/compat/RESULTS.md). A call style a release does not have yet, such as `responses` in openai 1.0, is simply absent. Each provider package's own Node minimum still applies.

## Node.js setup

Pass the provider modules you use in `instrumentModules`. It is required whenever a bundler is involved, because a bundle can carry its own copy of the client that detection never sees.

```ts
import OpenAI from 'openai';
import Anthropic from '@anthropic-ai/sdk';
import * as googleGenAI from '@google/genai';
import * as bedrockRuntime from '@aws-sdk/client-bedrock-runtime';
import { initialize } from 'generalcontext';

initialize({ instrumentModules: { openAI: OpenAI, anthropic: Anthropic, googleGenAI, bedrockRuntime } });
```

### Next.js

Initialize in `instrumentation.ts` at the project root, keep the provider packages out of the bundle so routes use the patched copy, and use the Node.js runtime. The Edge runtime is not supported.

instrumentation.ts:

```ts
export async function register() {
  if (process.env.NEXT_RUNTIME === 'nodejs') {
    const { initialize } = await import('generalcontext');
    const { OpenAI } = await import('openai');
    const anthropic = await import('@anthropic-ai/sdk');
    initialize({ appName: 'my-agent', instrumentModules: { openAI: OpenAI, anthropic } });
  }
}
```

next.config.ts:

```ts
export default { serverExternalPackages: ['openai', '@anthropic-ai/sdk'] };
```

### Proxies

Node's built-in `fetch` ignores `HTTPS_PROXY`. On Node 24 and later, set `NODE_USE_ENV_PROXY=1`. On older versions, install a global dispatcher, for example undici's `setGlobalDispatcher(new EnvHttpProxyAgent())`. If the proxy inspects TLS, point `NODE_EXTRA_CA_CERTS` at its CA certificate. Python honors `HTTPS_PROXY` and `SSL_CERT_FILE` on its own.

## Sessions

Calls inside a session are grouped into one conversation, with the id you pass. Sessions follow async work, threads (Python) and promises (Node) in the same process.

Python:

```python
with generalcontext.session(ticket.id, channel="email", user_id=customer.id, product="billing", account=company.id):
    handle(ticket)
```

Node.js:

```ts
await withSession({ sessionId: ticket.id, channel: 'email', userId: customer.id, product: 'billing', account: company.id }, () => handle(ticket));
```

`channel` and `product` are free-form labels you can filter by in the console. Without a session, calls are grouped by their first message, which every later call in the same conversation repeats.

### Accounts

If your agent works for several companies of its own, as a platform used by many businesses does, pass `account` with the id you already use for that company. You can then filter **Traces** by account. It is optional, up to 120 characters, and trimmed. With OpenTelemetry, set `generalcontext.account` on the span or resource instead.

## Name the steps

Name a step to see which part of your agent each call came from. The conversation page shows the main thread as the timeline, and lists every model call under **Every model call**, with side calls such as a classifier or a summary marked and labeled by their step. (These are steps inside your agent, not benchmark [tasks](https://generalcontextlabs.com/docs/tasks).)

Python:

```python
with generalcontext.task("triage"):
    category = classify(message)
```

Node.js:

```ts
import { task } from 'generalcontext';

const category = await task('triage', () => classify(message));
```

## Serverless and background jobs

Calls are sent in batches in the background. In a serverless function, queue worker or script, flush before returning.

Python:

```python
generalcontext.flush()  # waits up to 10 seconds; True when everything was sent
```

Node.js:

```ts
import { flush } from 'generalcontext';

await flush();  // waits up to 10 seconds (flush(ms) to change); true when everything was sent
```

When work moves to another process (a queue, a cron job), pass the conversation id along with the job and open the same session there. Calls from both processes land in one conversation.

## Redaction and filtering

With `redact=True` (`redact: true` in Node), phone numbers, emails, card numbers and SSNs are replaced before anything leaves your process. Each becomes a stable placeholder such as `<email:3f9a1c>` keyed by `GENERALCONTEXT_REDACT_SALT`, so the same value reads the same across a conversation without being sent. Redaction also covers outcomes and events.

Credentials are always masked, with or without `redact`: API keys from OpenAI, Anthropic, Google, AWS, Stripe, GitHub, Slack and General Context, bearer tokens, JWTs and private keys become `<openai-key>`, `<jwt>` and so on before anything is sent. Per-request headers and query parameters (`extra_headers`, `extra_query`, Gemini's `http_options`, Node's per-request `headers`) are never recorded.

For anything else, `before_send` (`beforeSend`) receives each call before it is sent. Return it, changed or not, or return nothing to drop it.

Python:

```python
def before_send(call):
    call["request"].pop("metadata", None)
    return call

generalcontext.initialize(redact=True, before_send=before_send)
```

Node.js:

```ts
initialize({
  redact: true,
  beforeSend: call => (call.task === 'internal-eval' ? null : call),
});
```

## One client only

To record a single client instead of every client in the process, wrap it. Prefer `initialize()` unless you need this.

Python:

```python
client = generalcontext.wrap(OpenAI())
```

Node.js:

```ts
import { wrap } from 'generalcontext';

const client = wrap(new OpenAI());
```

## What is sent

For each model call: the request (messages, tools, model and settings), the response, timing, token usage, any error, and your session and task tags. Nothing else from your process.

## If something goes wrong

- Your model calls are never slowed or changed. If we are unreachable, calls are dropped and a warning is logged at most once a minute.
- Memory is bounded: at most 5,000 calls or 64 MB are held. A single call over 4 MB is dropped.
- Safe with forking servers (gunicorn, Celery). On exit, it flushes for up to 2 seconds. Before `os._exit()`, which skips that, call `flush()` yourself.
- `GENERALCONTEXT_DISABLED=1` turns recording off without a code change.

## Configuration

| Option (Python / Node.js) | Environment variable | Default |
| --- | --- | --- |
| `api_key` / `apiKey` | `GENERALCONTEXT_API_KEY` | required |
| `base_url` / `baseUrl` | `GENERALCONTEXT_BASE_URL` | General Context |
| `app_name` / `appName` |  | none |
| `redact` / `redact` | `GENERALCONTEXT_REDACT_SALT` | off |
| `before_send` / `beforeSend` |  | none |
| `instrument_http` / `instrumentFetch` |  | off |
| `instrumentModules` (Node.js) |  | detected |
| `disable_batch` / `disableBatch` |  | off: calls are sent in batches |
|  | `GENERALCONTEXT_DISABLED=1` | recording on |

`instrument_http` / `instrumentFetch` also records raw HTTP calls to OpenAI-compatible `/chat/completions` endpoints made without the OpenAI client: `httpx` and `requests` in Python, `fetch` in Node.js.

## Security

- Sends only over HTTPS to the configured host and never follows redirects, so your key cannot be forwarded.
- No runtime dependencies. Releases are built in CI with signed provenance. Details: [SECURITY.md](https://github.com/GeneralContext/sdk/blob/main/SECURITY.md).
