Skip to main content

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 because HAJER_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:

FieldTypeMeaning
providerstropenai, anthropic, google, the litellm upstream, or http for an HTTP span
apistrThe surface called, for example chat.completions, responses, messages
started_atstrStart time, as an ISO 8601 UTC timestamp
modelstr | NoneThe model requested
request_settingsJsonObjectRequest settings you passed (temperature, max tokens and similar)
declared_toolstuple[str, ...]Names of tools declared on the request
message_count, message_rolesint, tuple[str, ...]The shape of the input
duration_msintWall-clock duration
usagedict[str, int]Token counts: input_tokens, output_tokens, total_tokens, and cache counts when reported
tool_callstuple[ToolCall, ...]Tools the model asked to call
tool_resultstuple[ToolResult, ...]Tool results your code sent back
response_id, finish_reasonstr | NoneFrom the response
error, error_type, error_statusstr | None, str | None, int | NoneWhen the call raised
streamed, stream_complete, stream_chunksbool, bool | None, intStreaming details
contentJsonObject | NoneCaptured content, when content capture is on
workflow_hintstr | NoneThe enclosing scope's workflow
provider_hoststr | NoneThe provider endpoint as host:port
caller_framestuple[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.
limitationstuple[str, ...]What the record could not observe
retriesintAlways 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.