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

# Custom integrations

> Don't see your framework in the list? Instrument it yourself with startSpan/start_span and get full tracing, cost tracking, and evaluation scores today — no official integration required.

<Note>
  Available for: **Python** and **TypeScript**.
</Note>

Zespan ships wrappers for the most common providers and frameworks, but you don't need one to get traces into your dashboard. If you're using a framework, library, or self-hosted model that isn't in the integrations list, you can instrument it yourself in a few lines of code — with the same trace tree, cost tracking, and evaluation scores as a built-in wrapper.

## Wrap a custom LLM call

Use `startSpan` (`start_span` in Python) to create a span around any call — a hypothetical in-house model client, in this example:

<CodeGroup>
  ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
  import { zespan, startSpan } from "@zespan/sdk";

  zespan.init({ apiKey: process.env.ZESPAN_API_KEY! });

  async function callCustomModel(prompt: string): Promise<string> {
    const { span } = startSpan({
      name: "custom-llm-call",
      provider: "custom",
      model: "my-custom-model",
    });

    try {
      const response = await myCustomClient.generate(prompt);

      await span.end({
        status: "success",
        input_tokens: response.usage.inputTokens,
        output_tokens: response.usage.outputTokens,
      });

      return response.text;
    } catch (err) {
      await span.end({ status: "error", error_message: String(err) });
      throw err; // Always re-throw — never swallow errors
    }
  }
  ```

  ```python Python theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
  import zespan

  zespan.init(api_key="zsp_...")

  def call_custom_model(prompt: str) -> str:
      span = zespan.start_span(
          name="custom-llm-call",
          provider="custom",
          model="my-custom-model",
      )

      try:
          response = my_custom_client.generate(prompt)

          span.end(
              status="success",
              input_tokens=response.usage.input_tokens,
              output_tokens=response.usage.output_tokens,
          )

          return response.text
      except Exception as err:
          span.end(status="error", error_message=str(err))
          raise  # Always re-raise — never swallow errors
  ```
</CodeGroup>

This span shows up in the trace tree, provider breakdown, and cost views exactly like a built-in wrapper's spans do. See [Manual spans](/sdk/manual-spans) for the full API — options, `span.setEvalScore()` for attaching evaluation scores, and the `run` helper for linking nested wrapped calls as children.

## Other building blocks

Two more primitives cover the rest of what a custom integration typically needs:

* **[Agent tracing](/sdk/agent-tracing)** — use `withAgent`/`with_agent` when your custom framework runs a multi-step agent loop (planning, tool calls, handoffs) rather than a single call.
* **[OpenTelemetry integration](/sdk/otel-integration)** — use the raw OTel API (`initOTel`/`init_otel`, `getTracer`/`get_tracer`) if you need direct control over the `TracerProvider`, or want to dual-export to your own OTel backend alongside Zespan.

Mix and match as needed — `startSpan` calls made inside a `withAgent` block automatically link to that agent's trace.
