> ## Documentation Index
> Fetch the complete documentation index at: https://docs.inference.net/llms.txt
> Use this file to discover all available pages before exploring further.

# Span Handle API

> Reference for the typed span handle yielded by agentSpan, agent_span, manualSpan, and manual_span.

`agentSpan()` / `agent_span()` and `manualSpan()` / `manual_span()` yield a
handle to the active span. The handle exposes a typed surface for the
OpenInference attributes you typically want to set, plus an escape hatch to
the raw OTel span for anything outside that vocabulary. The same handle is
used by both helpers across both languages.

This page is the full reference for that handle. For when to call each method,
see the [Manual spans guide](/integrations/traces/manual-spans). For the wire
keys each method writes, see the
[Attributes reference](/integrations/traces/attributes).

## Shape

<CodeGroup>
  <Metadata text="integrations/traces/handle-api-shape-ts" />

  ```typescript TypeScript theme={"system"}
  interface AgentSpanHandle {
    setInput(value: unknown): void;
    setOutput(value: unknown): void;
    setModel(model: string): void;
    setTool(options: { name: string; callId?: string }): void;
    recordTokens(tokens: {
      prompt?: number;
      completion?: number;
      total?: number;
    }): void;
    recordUsage(usage: unknown): TokenUsage;
    setAttribute(key: string, value: unknown): void;
    setAttributes(attributes: Record<string, unknown>): void;
    raw: Span; // OTel @opentelemetry/api Span
  }
  ```

  <Metadata text="integrations/traces/handle-api-shape-python" />

  ```python Python theme={"system"}
  class AgentSpanHandle:
      span: Span  # OTel opentelemetry.trace.Span

      def set_input(self, value: Any) -> None: ...
      def set_output(self, value: Any) -> None: ...
      def set_model(self, model: str) -> None: ...
      def set_tool(self, *, name: str, call_id: str | None = None) -> None: ...
      def record_tokens(
          self,
          *,
          prompt: int | None = None,
          completion: int | None = None,
          total: int | None = None,
      ) -> None: ...
      def record_usage(self, usage: Any) -> None: ...
      def set_attribute(self, key: str, value: Any) -> None: ...
      def set_attributes(self, attributes: Mapping[str, Any]) -> None: ...
  ```
</CodeGroup>

## `setInput` / `set_input`

Records the input that started this unit of work.

* Strings are stored as-is on `input.value`.
* Non-strings are JSON-stringified.

If the input is structured, also set `input.mime_type = "application/json"`
so downstream viewers know to render it as JSON.

<CodeGroup>
  <Metadata text="integrations/traces/handle-api-set-input-ts" />

  ```typescript TypeScript theme={"system"}
  span.setInput("Summarize order ABC-123");
  // or
  span.setInput({ ticketId: "ticket_123", orderId: "ABC-123" });
  span.setAttribute(Attr.INPUT_MIME_TYPE, "application/json");
  ```

  <Metadata text="integrations/traces/handle-api-set-input-python" />

  ```python Python theme={"system"}
  span.set_input("Summarize order ABC-123")
  # or
  span.set_input({"ticket_id": "ticket_123", "order_id": "ABC-123"})
  span.set_attribute(Attr.INPUT_MIME_TYPE, "application/json")
  ```
</CodeGroup>

## `setOutput` / `set_output`

Records the final output of this unit of work. Same coercion rules as
`setInput`. Call it once near the end of the callback, after the work has
produced its result.

## `setModel` / `set_model`

Records the model name on `llm.model_name`. Useful on `AGENT` spans when the
agent picks a model dynamically and you want the model surfaced at the agent
level. The per-SDK patchers already set this on `LLM` child spans, so you
typically do not need it there.

```typescript theme={"system"}
span.setModel("claude-haiku-4-5");
```

## `recordTokens` / `record_tokens`

Records token usage from explicit counts. Use this when you have aggregate
counts across an agent run, or when you are wrapping an SDK Catalyst does not
patch and you want to attach the provider's reported counts.

Any field may be omitted; only the provided fields are written. If you want
`total` on the span, pass it explicitly — `recordTokens` / `record_tokens`
does not infer it from `prompt` and `completion`. (The `recordUsage` /
`record_usage` helper documented below does infer `total` when the provider's
usage payload omits it.)

<CodeGroup>
  <Metadata text="integrations/traces/handle-api-record-tokens-ts" />

  ```typescript TypeScript theme={"system"}
  span.recordTokens({ prompt: 820, completion: 160, total: 980 });
  ```

  <Metadata text="integrations/traces/handle-api-record-tokens-python" />

  ```python Python theme={"system"}
  span.record_tokens(prompt=820, completion=160)
  ```
</CodeGroup>

## `recordUsage` / `record_usage`

Records token usage from a provider-shaped `usage` object. The SDK normalizes
the common shapes so you do not have to: OpenAI Chat Completions
(`prompt_tokens` / `completion_tokens`), the newer Responses spelling
(`input_tokens` / `output_tokens`), Anthropic cache accounting fields
(`cache_creation_input_tokens` / `cache_read_input_tokens`), and OpenAI's
`prompt_tokens_details.cached_tokens` /
`completion_tokens_details.reasoning_tokens`. When `total` is missing from
the payload but `prompt` and `completion` are present, the helper infers it.
The TypeScript version returns the normalized `TokenUsage` so the caller can
read the numbers back without re-parsing.

<CodeGroup>
  <Metadata text="integrations/traces/handle-api-record-usage-ts" />

  ```typescript TypeScript theme={"system"}
  const response = await client.chat.completions.create({ /* ... */ });
  if (response.usage) {
    span.recordUsage(response.usage);
  }

  // Works with Anthropic responses too:
  const message = await anthropicClient.messages.create({ /* ... */ });
  span.recordUsage(message.usage);
  ```

  <Metadata text="integrations/traces/handle-api-record-usage-python" />

  ```python Python theme={"system"}
  response = client.chat.completions.create(...)
  if response.usage is not None:
      span.record_usage(response.usage)

  # Works with Anthropic responses too:
  message = anthropic_client.messages.create(...)
  span.record_usage(message.usage)
  ```
</CodeGroup>

<Note>
  For Anthropic responses, `recordUsage` / `record_usage` folds
  `cache_creation_input_tokens` and `cache_read_input_tokens` into the recorded
  prompt token count, then also records them as separate cache-write and
  cache-read attributes. The prompt count on the span therefore reflects the
  total tokens that hit the model, not just the uncached prompt.
</Note>

## `setTool` / `set_tool`

Records tool identity on a `TOOL` span: writes `tool.name` and, when supplied,
`tool_call.id`. The `manualSpan` / `manual_span` helper accepts the same
fields as options, so `setTool` is rarely needed when authoring a TOOL span
from scratch — use it when the tool identity is only available partway
through the callback body.

<CodeGroup>
  <Metadata text="integrations/traces/handle-api-set-tool-ts" />

  ```typescript TypeScript theme={"system"}
  await manualSpan(
    { spanName: "lookup_order.tool", spanKind: SpanKindValues.TOOL },
    async (span) => {
      span.setTool({ name: "lookup_order", callId: toolCallId });
      span.setInput(args);
      const result = await TOOLS.lookup_order(args);
      span.setOutput(result);
    },
  );
  ```

  <Metadata text="integrations/traces/handle-api-set-tool-python" />

  ```python Python theme={"system"}
  with manual_span(
      tracing.tracer,
      name="lookup_order.tool",
      span_kind=SpanKindValues.TOOL,
  ) as span:
      span.set_tool(name="lookup_order", call_id=tool_call_id)
      span.set_input(args)
      result = TOOLS["lookup_order"](**args)
      span.set_output(result)
  ```
</CodeGroup>

## `setAttribute` / `set_attribute` / `set_attributes`

Adds one or many domain-specific attributes with OTel-safe value normalization.
Strings, booleans, numbers, and homogeneous primitive arrays pass through
unchanged; mappings and heterogeneous arrays are JSON-stringified.

<CodeGroup>
  <Metadata text="integrations/traces/handle-api-set-attributes-ts" />

  ```typescript TypeScript theme={"system"}
  span.setAttribute("app.tenant_id", tenantId);
  span.setAttributes({
    "app.request_channel": "slack",
    "app.deploy_env": process.env.DEPLOY_ENV ?? "dev",
  });
  ```

  <Metadata text="integrations/traces/handle-api-set-attributes-python" />

  ```python Python theme={"system"}
  span.set_attribute("app.tenant_id", tenant_id)
  span.set_attributes({
      "app.request_channel": "slack",
      "app.deploy_env": os.environ.get("DEPLOY_ENV", "dev"),
  })
  ```
</CodeGroup>

## `raw` / `.span`

The underlying OTel span. Use this when you need behavior outside the typed
surface:

* Adding domain attributes.
* Recording span events (`span.raw.addEvent("rate_limit_hit", { retry_after: 30 })`).
* Recording an exception without ending the span yourself
  (`span.raw.recordException(err)`).
* Reading the span context to propagate to a background job
  (`span.raw.spanContext()`).

The escape hatch is `span.raw` in TypeScript and `span.span` in Python.

## Value Coercion Rules

| Input                           | Coerced to                                                                                        |
| ------------------------------- | ------------------------------------------------------------------------------------------------- |
| `string`                        | Stored verbatim.                                                                                  |
| `number`, `boolean`             | Stored as-is. OTel handles these as primitive attribute values.                                   |
| Plain object                    | `JSON.stringify(value)` (TS) / `json.dumps(value, default=str)` (Python).                         |
| Array of homogeneous primitives | Stored as an OTel attribute array.                                                                |
| Array containing mixed types    | JSON-stringified.                                                                                 |
| `Error` / `Exception`           | Not coerced. Use `span.raw.recordException(err)` instead, which produces an OTel exception event. |
| `bigint` (TS)                   | Cast yourself before passing — OTel does not accept `bigint`.                                     |

The `setInput` and `setOutput` methods apply these rules and also set the
matching MIME type attribute when the value is structured. The other helpers
write to one attribute key each and apply the rules to that value.

## Status Behavior

The handle does not expose `setStatus` directly. The wrapping context manager
sets status for you:

* Body returns normally → status `OK`.
* Body raises → exception is recorded as a span event, status is set to
  `ERROR` with the exception's message, then the exception re-raises.

If you need fine-grained status control mid-callback (for example, to mark a
partial-success state), set it on the raw span:

<Metadata text="integrations/traces/handle-api-status" />

```typescript TypeScript theme={"system"}
import { SpanStatusCode } from "@opentelemetry/api";

if (result.partial) {
  span.raw.setStatus({
    code: SpanStatusCode.ERROR,
    message: `partial result: ${result.missingFields.join(", ")}`,
  });
}
```

## Next Steps

<CardGroup cols={2}>
  <Card title="Manual spans guide" icon="pen-nib" href="/integrations/traces/manual-spans">
    Use the handle to author AGENT, TOOL, CHAIN, and RETRIEVER spans.
  </Card>

  <Card title="Attributes reference" icon="tags" href="/integrations/traces/attributes">
    All `Attr.*` constants and `SpanKindValues` values.
  </Card>

  <Card title="Production agent example" icon="kitchen-set" href="/integrations/traces/production-agent-example">
    A production-shaped agent with custom tool execution, end to end.
  </Card>

  <Card title="Troubleshooting" icon="wrench" href="/integrations/traces/troubleshooting">
    Debug missing spans, missing attributes, and unexpected status codes.
  </Card>
</CardGroup>
