Skip to content

Python SDK

weharness-tracing sends your agent’s traces to WeHarness. It is plain OpenTelemetry with the GenAI semantic conventions, sent over OTLP/HTTP with your ingest key.

Package weharness-tracing on PyPI
Version 0.1.2 (changelog)
Python 3.10 or later
Licence Apache-2.0
Depends on opentelemetry-api, opentelemetry-sdk and opentelemetry-exporter-otlp-proto-http, version 1.45.1
Import import weharness_tracing as wh
Terminal window
pip install weharness-tracing
import weharness_tracing as wh
wh.init(service="refund-agent") # the key comes from WEHARNESS_INGEST_KEY
with wh.agent_run("refunds"):
with wh.model_call("gemini-3.8-flash", provider="gcp.gemini") as call:
... # call your model
call.usage(input_tokens=120, output_tokens=40)
with wh.tool_call("lookup_order"):
... # run the tool
wh.shutdown() # send what is queued before the process exits
wh.init(key=None, service=None, version=None, revision=None, endpoint=None, capture_content=False) -> TracerProvider

Starts sending traces and returns the tracer provider spans go through.

Parameter Default What it does
key WEHARNESS_INGEST_KEY The ingest key. Without one, init() raises ValueError.
service OTEL_SERVICE_NAME, else unknown_service The resource’s service.name
version None The resource’s service.version
revision WEHARNESS_REVISION or a CI variable The resource’s vcs.ref.head.revision: the commit your application runs. See Environment variables.
endpoint WEHARNESS_OTLP_ENDPOINT, else https://api.weharness.io/otlp/v1/traces Where spans are sent
capture_content False Send prompts, outputs and tool payloads too. See Content capture.
  • An existing OpenTelemetry setup keeps working. When the application already registered an SDK TracerProvider, init() adds WeHarness’s exporter to it instead of replacing it, so spans from your other instrumentation are sent too. service, version and revision then come from your provider’s resource.
  • Batches. Spans go in background batches (up to 512 spans, every 2 seconds, at most 2,048 queued), with a 10-second request timeout. A slow or unreachable WeHarness never blocks your agent; spans over the queue limit are dropped.
with wh.agent_run(name, **attributes) as run:
...

One run of your agent: the root of a trace. The span is named invoke_agent <name>, with gen_ai.operation.name=invoke_agent and gen_ai.agent.name=<name>. Extra keyword arguments become span attributes.

with wh.model_call(model, provider=None, operation="chat", **attributes) as call:
call.usage(input_tokens=120, output_tokens=40)

A call to a model. The span is named <operation> <model>, of kind client, with gen_ai.operation.name, gen_ai.request.model and, when given, gen_ai.provider.name.

with wh.tool_call(name, call_id=None, **attributes) as call:
...

A tool your agent calls. The span is named execute_tool <name>, with gen_ai.operation.name=execute_tool, gen_ai.tool.name and, when given, gen_ai.tool.call.id. It also records where your code called it: code.file.path (relative to the working directory), code.line.number and code.function.name. A caller outside the working directory, or inside installed packages, gives its function name only. This is what Find in code opens.

agent_run, model_call and tool_call each yield a helper:

Method What it does
usage(input_tokens=None, output_tokens=None) Sets gen_ai.usage.input_tokens and gen_ai.usage.output_tokens
set(key, value) Sets any attribute on the span
span The OpenTelemetry span itself

An exception raised inside the with block is recorded on the span, and the span is marked as an error.

wh.shutdown(timeout_millis=10000)

Sends what is still queued. Call it before your process exits, for example at the end of a script or in your server’s shutdown hook.

By default the SDK removes content before spans leave your process: prompts, model outputs, system instructions, tool arguments and results, request and response bodies, database statements, message bodies and stack traces. wh.CONTENT lists the attribute names (and prefixes) it removes; it is the same list WeHarness’s gateway uses.

To send content, pass capture_content=True to init(), and make the ingest key with Keep prompts and outputs on. If the key does not keep content, the gateway removes it anyway. See Content capture.

The filter works on attribute names. It is not a secret detector: do not put secrets or personal data in span names, error messages or your own attributes.

Pass the provider init() returns to an OpenTelemetry instrumentation:

from openinference.instrumentation.langchain import LangChainInstrumentor
provider = wh.init(service="refund-agent")
LangChainInstrumentor().instrument(tracer_provider=provider)

See Framework recipes.