Skip to main content

Interceptors SDK integration reference

The Interceptors SDK (aidial-interceptors-sdk) is a Python library for writing interceptors — the middleware DIAL Core runs over requests and responses travelling between a caller and a model or application, for PII redaction, guardrails, content filtering, and audit logging.

It turns an interceptor into an ordinary HTTP service that DIAL Core can call like any other deployment. You subclass a base class, override the callbacks for the parts of the request or response you want to change, and the SDK handles the rest: parsing the payload, calling onward to the next link in the chain, reassembling the response stream, and exposing the configuration endpoint.

Two things distinguish it from the DIAL SDK, which builds applications and adapters:

  • You modify traffic rather than produce it. An application implements a chat completion and owns the response. An interceptor sits in the middle, changes what passes through, and defers to whatever comes next.
  • You do not name what comes next. The SDK calls the downstream deployment through a reserved name that DIAL Core resolves, so an interceptor works the same whether it is first, last, or in the middle of a chain.

It depends on the DIAL SDK internally — it serves through DIALApp and reuses its request and response objects.

Packageaidial-interceptors-sdk
Sourcegithub.com/epam/ai-dial-interceptors-sdk

Installation​

pip install aidial-interceptors-sdk

How DIAL Core reaches your interceptor​

You register the service in DIAL Core configuration and assign it to a model or application — see Configure and assign interceptors. DIAL Core then calls it on every request to that deployment, passing two pieces of context:

What you readWhere it comes fromNotes
upstream_deployment_idX-DIAL-DEPLOYMENT-ID headerThe deployment this interceptor is assigned to. Available since DIAL Core 0.43.0.
configurationInterceptor configuration in DIAL Core configParsed into your Pydantic model, if you declare a schema. None when no schema is defined.

Your interceptor also receives request and response objects from the DIAL SDK, and a dial_client for calling onward.

How your interceptor calls the next deployment​

DialClient (dial_client.py) is how an interceptor reaches whatever comes next in the chain:

AttributePurpose
dial_urlDIAL Core server URL
clientOpenAI-compatible client for calling onward through DIAL Core
storageRead and write files on DIAL file storage

The important detail is the routing contract: client is configured with the reserved deployment name interceptor. DIAL Core resolves that name to the next interceptor in the chain, or to the target deployment if yours is the last one. You never name the downstream deployment yourself — that is what makes interceptors composable and order-independent from your code's point of view.

Overriding behaviour​

Both base classes are Pydantic models you subclass. Every callback has a pass-through default, so you override only what you change.

Chat completion interceptors​

Subclass ChatCompletionInterceptor (base.py). The callbacks most interceptors use:

MethodWhen it runsTypical use
on_request_messagePer message in the incoming requestRedact or rewrite user content
on_requestOnce, on the whole request dictInspect or reshape the full payload
on_response_messagePer message in the outgoing responseRestore redacted content, filter output
on_stream_start / on_stream_endAround the upstream response streamInject stages, flush buffers
on_stream_errorUpstream returned an errorConvert or suppress errors. Default re-raises.
call_upstreamsControls how the upstream is calledOverride for fan-out patterns

Request callbacks run bottom-up: message-level, then list-level, then the full request. Methods returning list[dict] | dict may add or remove elements — return a list to replace one element with several, or an empty list to drop it.

Beyond these, the SDK exposes finer-grained callbacks for attachments, stages, state, custom content, choices, finish reasons and usage, on both the request and response side, plus utilities such as reserve_stage_index, send_chunk, get_configuration and configuration_schema. Their signatures are defined in request_handler.py, request_message_handler.py and response_handler.py.

ChatCompletionNoOpInterceptor passes everything through unchanged — useful for testing the chain without side effects.

Embeddings interceptors​

Subclass EmbeddingsInterceptor (base.py). A smaller surface:

MethodDefault behaviour
modify_inputReturns the input string unchanged
modify_embeddingReturns the embedding unchanged
modify_requestCalls modify_input on each input in the request
modify_responseCalls modify_embedding on each embedding in the response

For simple transformations override modify_input and modify_embedding. For full control override modify_request and modify_response. EmbeddingsNoOpInterceptor is the pass-through equivalent.

Wiring a service​

Interceptor classes become DIAL deployments through two adapter functions — interceptor_to_chat_completion and interceptor_to_embeddings — which wrap your class into something a DIALApp can serve. Both take your class, the DIAL Core URL, and an async factory returning an httpx.AsyncClient.

from aidial_sdk import DIALApp
from aidial_sdk.telemetry.types import TelemetryConfig
from aidial_interceptors_sdk.chat_completion import (
interceptor_to_chat_completion,
)
from aidial_interceptors_sdk.embeddings.adapter import (
interceptor_to_embeddings,
)
from aidial_interceptors_sdk.utils._http_client import get_http_client

DIAL_URL = "http://dial-core:8080"


async def client_factory():
return get_http_client()


app = DIALApp(
dial_url=DIAL_URL,
telemetry_config=TelemetryConfig(),
add_healthcheck=True,
propagate_auth_headers=True,
allow_extra_request_fields=True,
)

app.add_chat_completion(
"my-interceptor",
interceptor_to_chat_completion(MyChatInterceptor, DIAL_URL, client_factory),
)

app.add_embeddings(
"my-emb-interceptor",
interceptor_to_embeddings(MyEmbInterceptor, DIAL_URL, client_factory),
)

Run it with uvicorn:

uvicorn app:app --host 0.0.0.0 --port 5000

This exposes the endpoints DIAL Core will call:

  • http://localhost:5000/openai/deployments/my-interceptor/chat/completions
  • http://localhost:5000/openai/deployments/my-emb-interceptor/embeddings

Environment variables​

VariableDefaultDescription
DIAL_URL(required)DIAL Core server URL
LOG_LEVELINFOApplication log level
DIAL_SDK_LOGWARNINGDIAL SDK internal log level
WEB_CONCURRENCY1Number of uvicorn workers

Further reading​