API reference
This page covers every name in hajer.__all__ for SDK version 0.2.0. Names not listed here, including every module whose name starts with an underscore, are private and may change. While the version is 0.x, a minor release may change the public API.
Two rules apply throughout:
- The SDK does not raise into your application code. A span that cannot be started, exported or flushed costs the span, never the call. The few exceptions are listed under Errors.
- Instrumentation changes nothing about the call. Wrapped methods return the provider's own values and raise the provider's own exceptions, unchanged.
import hajer
hajer.__version__ # "0.2.0"
Instrumentation
wrap
hajer.wrap(client: T, *, settings: HajerSettings | None = None) -> T
Instruments client in place and returns the same object. Every model call it makes is recorded and emitted as a model span. Recognises openai.OpenAI / AsyncOpenAI (and Azure variants), anthropic.Anthropic / AsyncAnthropic (and Bedrock and Vertex variants), google.genai.Client, the litellm module, and LangChain ChatOpenAI / ChatAnthropic chat models (instrumented at the provider client they hold). Calling it twice on the same client has no effect. A unittest.mock object is returned unchanged and records nothing. settings defaults to HajerSettings.from_env().
Raises UnsupportedClientError if the object has no surface the SDK recognises, and HajerConfigError if a HAJER_* variable is invalid. See Instrumenting clients.
instrument
hajer.instrument(*, settings: HajerSettings | None = None) -> Instrumentation
Patches the constructors of the openai and anthropic client classes so every client built afterwards is instrumented, and installs an import hook for those libraries if they are not imported yet. Returns an Instrumentation receipt; a second call returns the existing receipt. Does not cover google-genai or litellm. Raises HajerConfigError if a HAJER_* variable is invalid.
uninstrument
hajer.uninstrument() -> None
Restores the constructors instrument() patched, removes its import hook, and removes the HAJER_CAPTURE_HTTP transport patches. Does nothing if instrument() was not called. Clients already built stay instrumented.
attach
hajer.attach(*, settings: HajerSettings | None = None) -> Attachment
Attach mode: instruments every supported library already imported, and installs an import hook for those imported later, so every provider client the process builds records its calls. Does not read HAJER_ATTACH. Idempotent: a second call returns the standing attachment. Raises HajerConfigError if a HAJER_* variable is invalid. See Attach mode.
To attach conditionally, import hajer.autoattach instead. It calls attach() only when HAJER_ATTACH is on, and exposes the result as hajer.autoattach.ATTACHED (None when it did not attach).
detach
hajer.detach() -> None
Removes the import hook and stops instrumenting clients built afterwards. It does not un-patch anything: clients already instrumented keep recording and exporting. Idempotent.
attachment
hajer.attachment() -> Attachment | None
Returns the current Attachment, or None if the process is not attached.
CaptureTransport, AsyncCaptureTransport
hajer.CaptureTransport(transport: httpx.BaseTransport, *, settings: HajerSettings | None = None)
hajer.AsyncCaptureTransport(transport: httpx.AsyncBaseTransport, *, settings: HajerSettings | None = None)
httpx transports that wrap another transport and record model requests made through it: a JSON POST to a path ending in /chat/completions, /responses or /messages is recorded as a model call. Headers are never recorded. Raises HajerConfigError at construction if a HAJER_* variable is invalid.
import httpx
import hajer
http = httpx.Client(transport=hajer.CaptureTransport(httpx.HTTPTransport()))
Declared spans
workflow
hajer.workflow(workflow_id: str) -> Span
Declares a workflow span named workflow {workflow_id}, carrying hajer.workflow.id. Also enters hajer.scope(workflow=workflow_id). Use it as a decorator (sync or async), a with block or an async with block. See Workflows, components and tools.
component
hajer.component(component_id: str) -> Span
Declares a component span named component {component_id}, carrying hajer.component.id and the enclosing hajer.workflow.id. Same three forms as workflow.
tool
hajer.tool(tool_id: str, *, name: str | None = None, arguments: JsonObject | None = None) -> Span
Declares a tool-execution span named execute_tool {name or tool_id}, carrying hajer.tool.id, gen_ai.tool.name, gen_ai.operation.name = "execute_tool", the enclosing workflow and component ids and, with content capture on, gen_ai.tool.call.arguments (redacted and bounded). Same three forms as workflow.
The Span object these three return is not exported by name. An exception raised inside any of them propagates unchanged; the span records its class as error.type and an error status.
Conversation context
session
hajer.session(session_id: str) -> TraceContextScope
Sets session.id on every span opened inside. Equivalent to hajer.context(session=session_id).
user
hajer.user(user_id: str) -> TraceContextScope
Sets user.id on every span opened inside. Use an opaque id, never a name or email address. Equivalent to hajer.context(user=user_id).
context
hajer.context(
*,
session: str | None = None,
user: str | None = None,
tags: Sequence[str] = (),
metadata: Mapping[str, str | int | float | bool] | None = None,
) -> TraceContextScope
Sets session.id, user.id, hajer.tags and hajer.metadata.<key> on every span opened inside, including other OpenTelemetry instrumentation's spans. Nested declarations merge; tags accumulate. At most 16 tags, 32 metadata keys and 1024 characters per value; extra values are clipped. Raises TypeError if a metadata value is not a str, int, float or bool. See Sessions and context.
TraceContextScope
The object session, user and context return. Use it as a decorator on a sync or async function, as a with block, or as an async with block. You do not construct it directly.
Configuration and export
configure
hajer.configure(
settings: HajerSettings | None = None,
*,
tracer_provider: object | None = None,
policy: ClientRedactionPolicy | None = None,
) -> None
Sets the settings, tracer provider and redaction policy used for every span emitted afterwards. None means: read the environment, choose the provider automatically, and use the default redaction catalog. Each call replaces the previous configuration; configure() with no arguments resets it. If the SDK built its own provider for the previous configuration, that provider is shut down. See Exporting spans.
flush
hajer.flush(timeout_ms: int | None = None) -> bool
Waits up to timeout_ms (default HAJER_TRACE_FLUSH_TIMEOUT_MS, 2000) for spans emitted so far to be exported. Returns True if the tracer provider reports success, and False if there is no provider, the flush timed out, or it failed. Never raises. Call it before a short-lived process or serverless invocation ends. See Flushing.
HajerSettings
hajer.HajerSettings(**fields)
hajer.HajerSettings.from_env(env: Mapping[str, str] | None = None) -> HajerSettings
Every setting as a frozen Pydantic model. from_env() reads the HAJER_* variables (and OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_SERVICE_NAME as fallbacks) and raises HajerConfigError for an invalid boolean or number. The env argument lets you pass a mapping instead of os.environ. The inert property is True when nothing will be sent to Hajer. Field names are the variable names without HAJER_, in lower case. See Configuration.
Redaction
build_policy
hajer.build_policy(
*,
classes_off: frozenset[str] | Sequence[str] = (),
extra_rules: Sequence[tuple[str, str]] = (),
paths_exempt: Sequence[str] = (),
) -> ClientRedactionPolicy
Builds a redaction policy: turn off catalog categories, add (category, regex) rules, and exempt document paths. Raises re.error for a pattern that does not compile and ValueError for a path it cannot parse. Apply it with hajer.configure(policy=...). See Custom policies.
redact_document
hajer.redact_document(
document: JsonValue, *, policy: ClientRedactionPolicy
) -> tuple[JsonValue, tuple[RedactionEntry, ...]]
Returns a redacted copy of document and one RedactionEntry per category found at each path. Never raises; values it could not scan are replaced with [redacted:UNSCANNED].
ClientRedactionPolicy
A frozen dataclass returned by build_policy. Its fields classes_off, extra_rules and paths_exempt hold what you passed. Build it with build_policy rather than directly, so patterns are compiled and paths validated.
RedactionEntry
RedactionEntry(category: str, path: str, count: int)
One redaction: the category (for example EMAIL, CARD, FIELD, UNSCANNED), the path in the document, and the number of matches there.
Local call records
The SDK keeps the calls it records in memory, per task or per scope, independently of export. Use these to inspect calls in tests or in-process. Content in these records is as captured, not redacted.
scope
hajer.scope(*, workflow: str | None = None) -> ContextManager[Operation]
A with block that collects every model call made inside it, including calls from child tasks and threads that copy the context. workflow is a stable, non-secret name stored on each call as workflow_hint. Scopes nest; the inner one collects the calls made inside it. hajer.workflow opens a scope for you.
with hajer.scope(workflow="wf_support") as operation:
run_agent()
print(len(operation.calls), operation.dropped)
Operation
What scope yields. Properties:
calls -> tuple[WrappedCall, ...]: a snapshot of the calls recorded in the scope so far.dropped -> int: calls not kept becauseHAJER_WRAPPED_CALLS_MAX(default 32) was reached.
wrapped_calls
hajer.wrapped_calls() -> tuple[WrappedCall, ...]
The calls held by the innermost active scope, or, outside any scope, the calls recorded in the current task (at most HAJER_WRAPPED_CALLS_MAX).
wrapped_calls_dropped
hajer.wrapped_calls_dropped() -> int
How many calls were dropped in the same place because the limit was reached.
clear_wrapped_calls
hajer.clear_wrapped_calls() -> None
Forgets the calls held by the innermost active scope, or by the current task outside any scope.
WrappedCall
A dataclass describing one recorded model call. Commonly used fields:
| Field | Type | Meaning |
|---|---|---|
provider | str | openai, anthropic, google, the litellm upstream, or http for an HTTP span |
api | str | The surface called, for example chat.completions, responses, messages |
started_at | str | Start time, as an ISO 8601 UTC timestamp |
model | str | None | The model requested |
request_settings | JsonObject | Request settings you passed (temperature, max tokens and similar) |
declared_tools | tuple[str, ...] | Names of tools declared on the request |
message_count, message_roles | int, tuple[str, ...] | The shape of the input |
duration_ms | int | Wall-clock duration |
usage | dict[str, int] | Token counts: input_tokens, output_tokens, total_tokens, and cache counts when reported |
tool_calls | tuple[ToolCall, ...] | Tools the model asked to call |
tool_results | tuple[ToolResult, ...] | Tool results your code sent back |
response_id, finish_reason | str | None | From the response |
error, error_type, error_status | str | None, str | None, int | None | When the call raised |
streamed, stream_complete, stream_chunks | bool, bool | None, int | Streaming details |
content | JsonObject | None | Captured content, when content capture is on |
workflow_hint | str | None | The enclosing scope's workflow |
provider_host | str | None | The provider endpoint as host:port |
caller_frames | tuple[CallerFrame, ...] | Up to 8 application frames, innermost first, each with module, qualname, file (relative to the project root), line and file_digest (sha256: of the source file's bytes, or None). Empty with HAJER_CAPTURE_CALL_SITE=0. See Call-site capture. |
limitations | tuple[str, ...] | What the record could not observe |
retries | int | Always 0; retries inside the provider SDK are not visible |
ToolCall
ToolCall(id: str | None, name: str | None, arguments: JsonValue | None = None)
A tool call the model requested. arguments is set only with content capture on.
ToolResult
ToolResult(tool_use_id: str | None, is_error: bool | None = None, content: JsonValue | None = None)
A tool result your code sent back to the model. content is set only with content capture on.
Receipts
Instrumentation
Returned by instrument(). Records what was patched so uninstrument() can restore it. Its mode field is "wrap"; the rest is internal.
Attachment
Attachment(classes: tuple[str, ...], modules: tuple[str, ...], inert: bool)
Returned by attach() and attachment(). classes lists the patched client classes, modules the libraries found, and inert is True when no key is configured and nothing will be sent. describe() -> str returns a one-line summary.
Types
JsonValue, JsonObject
JsonValue is Pydantic's JsonValue: any value that survives a JSON round trip. JsonObject is dict[str, JsonValue]. Used in the signatures of tool, redact_document and the call records.
Errors
All SDK exceptions derive from HajerError, so except hajer.HajerError catches every one of them.
HajerError
The base class.
HajerConfigError
A HAJER_* variable is not a valid boolean or positive integer. Raised where settings are read from the environment (HajerSettings.from_env(), wrap, instrument, attach, CaptureTransport, import hajer.autoattach), never during a model call. Attributes: variable, expected, code ("HAJER_INVALID_CONFIGURATION"). The supplied value is withheld from the message and from value. A missing key is not a configuration error. See Invalid values.
UnsupportedClientError
wrap() was given an object with no surface it recognises. Attribute: client, the object passed. Raised so that a wrap line that would record nothing does not fail silently.
BodyOverBoundError
Used internally by hajer eval --upload when a payload is over its size limit, which reports it as a BODY_OVER_BOUND result. It does not reach application code. Attributes: size, limit. See Uploading results.