Skip to main content

wrap()

The one integration point. Wrap the LLM client your agent already uses; wrap() returns the same client object, instrumented:
str
required
A stable name for this agent (for example "support-bot"). Everything Visceral shows you — traffic, stats, findings — is organized under it. Required.
str | None
Workspace API key. Falls back to VISCERAL_API_KEY. If no key is available, wrap() logs a warning and returns the client uninstrumented — your agent is never blocked on Visceral configuration.
str | None
Backend URL. Falls back to VISCERAL_BASE_URL, then https://api.visceralai.dev.
str | dict | None
Override provider auto-detection. Pass "openai", "anthropic", "google", or "custom" for a client Visceral doesn’t recognize (recorded metadata-only).
bool
default:"False"
Opt in to applying proven cache-layout optimizations to outbound requests. Off by default — observation only. See Optimization.

Supported providers

The provider is detected from the client type; sync and async clients are both supported. Under the hood Visceral binds an OpenInference instrumentor to a private tracer provider. It never touches the global OpenTelemetry provider, so it coexists cleanly with any APM or tracing you already run. Calling a provider through an OpenAI-compatible endpoint (or a client Visceral doesn’t recognize) still works with provider_hint="custom" — calls are recorded as metadata-only events.

Instrumenting a framework with instrument()

If your agent is built on a framework, you don’t have a single provider client to wrap — the framework drives the model for you. instrument() is the client-less counterpart to wrap(): name the framework and Visceral installs its instrumentor, capturing the agent’s structure (chains, LLM calls, tools, agents) so it flows through the same private, redacting pipeline.
bool
default:"False"
Opt in to the apply path, mirroring wrap(apply=True): the SDK fetches your workspace’s active optimization rules and arms the tool serve executor. Off by default (observation only). See Optimization.
dict | None
Per-tool attestations for tools the framework runs, for example {"rag_search": {"readonly": True, "replay_safe": True}}. Same vocabulary as @tool, plus version and freshness_seconds. Declared tools get the visceral.tool.* contract stamped on their tool spans and, with apply=True, become servable; undeclared tools stay observe-only. Attestation is never inferred.
Framework capture wins — don’t also wrap() the same traffic. A framework already records its own LLM calls, so co-installing a provider instrumentor would double-count them. When a framework is instrumented, provider capture is dropped; direct, non-framework provider calls in that process aren’t recorded. Instrument the framework or wrap the client, not both.
An unknown framework name raises ValueError (a typo should be loud). Every other failure path is fail-open, exactly as with wrap().

Annotating steps with node()

Give Visceral the shape of your agent by naming its steps. node() opens a span around a function — sync or async:
Named nodes show up in traces and help Visceral attribute cost and waste to the right part of your agent. Before wrap() runs, node() is a no-op.

Fail open

However you integrate, the guarantee is the same:
  • No API key configured → the client is returned uninstrumented, with a warning.
  • Backend unreachable → calls fall through to the provider unchanged; telemetry is dropped, never queued into your latency.
  • Anything in Visceral’s pipeline fails → your call proceeds as if Visceral weren’t there.
Instrumentation can never be the reason a call fails. See How it works.