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

# Integration Best Practices

> Production-ready patterns for integrating Netra into your AI applications. Covers initialization, instrumentation strategy, context tracking, cost optimization, and common pitfalls.

# Best Practices for Integrating Netra

> Follow these proven patterns to get the most out of Netra's observability, evaluation, and simulation capabilities — from first setup through production at scale.

Getting Netra running takes minutes. Getting it running *well* takes intention. This guide distills the patterns that high-performing teams follow when integrating Netra into production AI systems.

***

## Initialization

### Initialize Early, Initialize Once

`Netra.init()` must be the **first thing** your application does — before importing or using any LLM provider, framework, or vector database client. Netra patches supported libraries at initialization time; if a library is imported first, its calls won't be captured.

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    import os
    from netra import Netra

    # ✅ Initialize BEFORE importing providers
    Netra.init(
        app_name="my-ai-app",
        environment="production",
        headers=f"x-api-key={os.getenv('NETRA_API_KEY')}",
        trace_content=True,
    )

    # Now import and use providers
    from openai import OpenAI
    client = OpenAI()
    ```
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={null}
    import { Netra } from "netra-sdk";

    // ✅ Always await — init is async in TypeScript
    await Netra.init({
      appName: "my-ai-app",
      environment: "production",
      headers: `x-api-key=${process.env.NETRA_API_KEY}`,
      traceContent: true,
    });

    // Now import and use providers
    import OpenAI from "openai";
    const client = new OpenAI();
    ```
  </Tab>
</Tabs>

<Warning>
  Never call `Netra.init()` more than once. Multiple initializations can lead to duplicate spans and unexpected behavior.
</Warning>

### Always Shut Down Gracefully

Netra batches spans before exporting. If your application exits without calling `shutdown()`, pending spans may be lost — especially in short-lived processes like serverless functions, scripts, or CLI tools.

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    try:
        # Your application logic
        run_pipeline()
    finally:
        Netra.shutdown()
    ```
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={null}
    try {
      // Your application logic
      await runPipeline();
    } finally {
      await Netra.shutdown();
    }
    ```
  </Tab>
</Tabs>

<Tip>
  For long-running servers (FastAPI, Express, NestJS), hook `shutdown()` into your framework's graceful shutdown lifecycle — e.g., `@app.on_event("shutdown")` in FastAPI or `process.on("SIGTERM")` in Node.js.
</Tip>

### Use Environment Variables for Credentials

Avoid hardcoding API keys in source code. Use environment variables and let the SDK resolve them automatically:

```bash theme={null}
# US Region
export NETRA_API_KEY="your-api-key-here"
export NETRA_OTLP_ENDPOINT="https://api.getnetra.ai/telemetry"
```

```bash theme={null}
# EU Region
export NETRA_API_KEY="your-api-key-here"
export NETRA_OTLP_ENDPOINT="https://api.eu.getnetra.ai/telemetry"
```

The SDK picks up `NETRA_API_KEY` and `NETRA_OTLP_ENDPOINT` automatically, keeping credentials out of your codebase.

***

## Instrumentation Strategy

### Follow the Three-Layer Approach

Adopt instrumentation incrementally. Each layer adds visibility without requiring you to rewrite existing code:

| Layer | Method                   | When to Use                                                                             | Effort            |
| ----- | ------------------------ | --------------------------------------------------------------------------------------- | ----------------- |
| 1     | **Auto-instrumentation** | Start here — captures all LLM calls, retrieval, and framework activity automatically    | Zero code changes |
| 2     | **Decorators**           | Add semantic meaning to your business logic — `@workflow`, `@agent`, `@task`            | Minimal           |
| 3     | **Manual spans**         | Fine-grained control over span boundaries, custom metadata, and non-standard operations | Full control      |

<Steps>
  <Step title="Start with auto-instrumentation">
    Enable Netra with default instruments. Every supported LLM call, vector DB query, and framework operation is captured automatically.
  </Step>

  <Step title="Add decorators for business context">
    Annotate your key functions with `@workflow`, `@agent`, and `@task` decorators to create meaningful span hierarchies that map to your application's domain logic.
  </Step>

  <Step title="Use manual spans for edge cases">
    Add manual spans only where you need custom boundaries, metadata, or lifecycle control that decorators don't cover.
  </Step>
</Steps>

### Be Selective with Instruments

Don't instrument everything. Noisy traces from HTTP clients, health checks, or internal utilities obscure the signals that matter.

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    from netra.instrumentation.instruments import InstrumentSet

    Netra.init(
        app_name="my-ai-app",
        # Only instrument what you need
        instruments={InstrumentSet.OPENAI, InstrumentSet.LANGCHAIN},
        # Or block noisy instruments
        block_instruments={InstrumentSet.HTTPX, InstrumentSet.REQUESTS},
        # Filter out noise
        blocked_spans=["health-check", "internal.*"],
    )
    ```
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={null}
    import { Netra, NetraInstruments } from "netra-sdk";

    await Netra.init({
      appName: "my-ai-app",
      // Only instrument what you need
      instruments: new Set([NetraInstruments.OPENAI, NetraInstruments.LANGCHAIN]),
      // Or block noisy instruments
      blockInstruments: new Set([NetraInstruments.HTTP, NetraInstruments.FETCH]),
      // Filter out noise
      blockedSpans: ["health-check", "internal.*"],
    });
    ```
  </Tab>
</Tabs>

<Info>
  The `blockedSpans` parameter supports wildcards — `"internal.*"` blocks all spans starting with `internal.`, and `"*.debug"` blocks spans ending with `.debug`.
</Info>

***

## Context Tracking

### Always Set User, Session, and Tenant Context

Context attributes are the foundation of effective filtering, grouping, and analytics in the Netra dashboard. Set them as early as possible in your request lifecycle.

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    from netra import Netra

    # Set at the start of each request
    Netra.set_user_id("user-456")
    Netra.set_session_id("session-789")
    Netra.set_tenant_id("tenant-123")

    # Add custom attributes for further segmentation
    Netra.set_custom_attributes("feature", "chat-v2")
    Netra.set_custom_attributes("model_version", "gpt-4o")
    ```
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={null}
    import { Netra } from "netra-sdk";

    // Set at the start of each request
    Netra.setUserId("user-456");
    Netra.setSessionId("session-789");
    Netra.setTenantId("tenant-123");

    // Add custom attributes for further segmentation
    Netra.setCustomAttributes("feature", "chat-v2");
    Netra.setCustomAttributes("model_version", "gpt-4o");
    ```
  </Tab>
</Tabs>

| Context           | Purpose                        | Dashboard Benefit                                   |
| ----------------- | ------------------------------ | --------------------------------------------------- |
| `userId`          | Identify the end user          | Per-user cost tracking, usage analytics             |
| `sessionId`       | Group multi-turn conversations | Session replay, conversation debugging              |
| `tenantId`        | Isolate SaaS customer data     | Multi-tenant cost allocation, per-tenant dashboards |
| Custom attributes | Any domain-specific metadata   | Custom filters, segmentation, and alerting          |

### Always Set Root Input and Output

Root input and output are the most important attributes on a trace. They let you see *what went in* and *what came out* at a glance — without expanding span trees.

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    from netra import Netra
    from netra.decorators import workflow

    @workflow(name="chat")
    def handle_chat(user_message: str) -> str:
        Netra.set_root_input(user_message)

        response = run_agent(user_message)

        Netra.set_root_output(response)
        return response
    ```
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={null}
    import { Netra } from "netra-sdk";

    async function handleChat(userMessage: string): Promise<string> {
      Netra.setRootInput(userMessage);

      const response = await runAgent(userMessage);

      Netra.setRootOutput(response);
      return response;
    }
    ```
  </Tab>
</Tabs>

#### Streaming Responses

For streaming outputs, accumulate chunks and set root output after iteration completes:

<Tabs>
  <Tab title="Python — Raw Stream">
    ```python theme={null}
    # Wrap the raw LLM stream — output is auto-committed when iteration ends
    stream = client.chat.completions.create(model="gpt-4o", messages=messages, stream=True)
    stream = Netra.set_root_output_stream(stream)

    for chunk in stream:
        print(chunk)
    ```
  </Tab>

  <Tab title="Python — SSE/Generator">
    ```python theme={null}
    # For SSE generators, manually accumulate and set output
    @workflow(name="chat-stream")
    def generate():
        Netra.set_root_input(user_message)
        collected: list[str] = []

        for chunk in agent.run(message, stream=True):
            if chunk and chunk.content:
                collected.append(chunk.content)
                yield f"data: {chunk.content}\n\n"

        Netra.set_root_output("".join(collected))
        yield "data: [DONE]\n\n"
    ```
  </Tab>

  <Tab title="TypeScript — SSE/Express">
    ```typescript theme={null}
    app.post("/api/chat/stream", async (req, res) => {
      Netra.setRootInput(req.body.message);
      const collected: string[] = [];

      const stream = await agent.run(req.body.message, { stream: true });

      for await (const chunk of stream) {
        if (chunk.content) {
          collected.push(chunk.content);
          res.write(`data: ${chunk.content}\n\n`);
        }
      }

      Netra.setRootOutput(collected.join(""));
      res.write("data: [DONE]\n\n");
      res.end();
    });
    ```
  </Tab>
</Tabs>

<Tip>
  **Rule of thumb:** Use `set_root_output_stream` (Python) when you can wrap the raw LLM iterable before consuming it. Use manual accumulation + `set_root_output` / `setRootOutput` when the handler transforms or formats chunks before sending (SSE, WebSocket frames, custom protocols).
</Tip>

***

## Decorator Best Practices

### Map Decorators to Your Domain

Use decorators to create a span hierarchy that mirrors your application's architecture:

```
@workflow("order-fulfillment")
  └── @agent("order-agent")
        ├── @task("validate-order")
        ├── @task("check-inventory")
        └── @task("process-payment")
```

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    from netra.decorators import workflow, agent, task

    @workflow(name="order-fulfillment")
    def fulfill_order(order: dict):
        Netra.set_root_input(order)
        result = OrderAgent().orchestrate(order)
        Netra.set_root_output(result)
        return result

    @agent
    class OrderAgent:
        @task(name="validate-order")
        def validate(self, order: dict):
            ...

        @task(name="check-inventory")
        def check_stock(self, order: dict):
            ...

        def orchestrate(self, order: dict):
            self.validate(order)
            return self.check_stock(order)
    ```
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={null}
    import { workflow, agent, task } from "netra-sdk/decorators";

    @workflow({ name: "order-fulfillment" })
    class OrderWorkflow {
      async run(order: Order) {
        Netra.setRootInput(order);
        const result = await new OrderAgent().orchestrate(order);
        Netra.setRootOutput(result);
        return result;
      }
    }

    @agent({ name: "order-agent" })
    class OrderAgent {
      @task({ name: "validate-order" })
      async validate(order: Order) { ... }

      @task({ name: "check-inventory" })
      async checkStock(order: Order) { ... }

      async orchestrate(order: Order) {
        await this.validate(order);
        return this.checkStock(order);
      }
    }
    ```
  </Tab>
</Tabs>

<Note>
  **TypeScript users:** Decorators require `"experimentalDecorators": true` in your `tsconfig.json`.
</Note>

***

## Manual Span Best Practices

### Always End Spans

In TypeScript, manual spans **must** be ended explicitly. Use `try/finally` to guarantee cleanup, or prefer `startActiveSpan()` for automatic lifecycle management.

<Tabs>
  <Tab title="Python — Context Manager (auto-closes)">
    ```python theme={null}
    from netra import Netra, SpanType

    with Netra.start_span("my-operation", as_type=SpanType.TOOL) as span:
        span.set_attribute("key", "value")
        result = do_work()
    # Span is automatically ended here
    ```
  </Tab>

  <Tab title="TypeScript — try/finally">
    ```typescript theme={null}
    import { Netra, SpanType } from "netra-sdk";

    const span = Netra.startSpan("my-operation", { asType: SpanType.TOOL });
    try {
      span.setAttribute("key", "value");
      const result = await doWork();
      span.setSuccess();
    } catch (error: any) {
      span.setError(error?.message || "unknown error");
      throw error;
    } finally {
      span.end(); // Always end in finally
    }
    ```
  </Tab>

  <Tab title="TypeScript — startActiveSpan (recommended)">
    ```typescript theme={null}
    import { Netra, SpanType } from "netra-sdk";

    const result = Netra.startActiveSpan(
      "my-operation",
      { asType: SpanType.TOOL },
      async (span) => {
        span.setAttribute("key", "value");
        return await doWork();
      },
    );
    // Span is automatically ended and errors are tracked
    ```
  </Tab>
</Tabs>

***

## Environment Configuration

### Use Separate Environments

Tag traces by environment to keep production data clean and make it easy to filter in the dashboard:

```python theme={null}
# Development
Netra.init(app_name="my-app", environment="development")

# Staging
Netra.init(app_name="my-app", environment="staging")

# Production
Netra.init(app_name="my-app", environment="production")
```

### Add Resource Attributes for Rich Metadata

Attach deployment metadata to every span using `resource_attributes` / `resourceAttributes`:

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    Netra.init(
        app_name="my-app",
        environment="production",
        resource_attributes={
            "service.version": os.getenv("APP_VERSION"),
            "deployment.region": os.getenv("AWS_REGION"),
            "team": "ml-platform",
        },
    )
    ```
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={null}
    await Netra.init({
      appName: "my-app",
      environment: "production",
      resourceAttributes: {
        "service.version": process.env.APP_VERSION,
        "deployment.region": process.env.AWS_REGION,
        team: "ml-platform",
      },
    });
    ```
  </Tab>
</Tabs>

***

## Privacy and Security

### Control Content Capture

In production, you may want to disable prompt and response content capture for privacy compliance:

```python theme={null}
Netra.init(
    app_name="my-app",
    trace_content=False,  # Disable prompt/response capture
)
```

<Warning>
  When `trace_content` is `False`, prompts and completions are **not** sent to Netra. You'll still get latency, token usage, cost, and span structure — but you won't be able to debug specific prompt/response pairs.
</Warning>

### Enable PII Scrubbing (Python)

For applications handling sensitive data, enable automatic PII detection and redaction:

```python theme={null}
Netra.init(
    app_name="my-app",
    enable_scrubbing=True,  # Auto-redact detected PII
)
```

<Info>
  PII scrubbing adds processing overhead. Only enable it when your application handles sensitive user data. Currently available in the Python SDK only.
</Info>

***

## Common Pitfalls

Avoid these frequently encountered mistakes when integrating Netra:

<AccordionGroup>
  <Accordion title="Initializing after importing providers">
    **Problem:** LLM calls are not appearing in traces.

    **Cause:** `Netra.init()` was called *after* importing the provider library. Netra patches libraries at init time — if they're already imported, the patches don't apply.

    **Fix:** Move `Netra.init()` to the very top of your entrypoint, before any provider imports.
  </Accordion>

  <Accordion title="Forgetting to await init in TypeScript">
    **Problem:** Some or all spans are missing.

    **Cause:** `Netra.init()` in TypeScript is async. Without `await`, instrumentation may not be ready when LLM calls are made.

    **Fix:** Always `await Netra.init({ ... })`.
  </Accordion>

  <Accordion title="Missing root input and output">
    **Problem:** Traces appear in the dashboard but show no input/output at the top level.

    **Cause:** `set_root_input` / `set_root_output` were never called.

    **Fix:** Call them at the entry and exit points of your top-level workflow. This is the single most impactful practice for trace readability.
  </Accordion>

  <Accordion title="Not calling shutdown in short-lived processes">
    **Problem:** Traces from scripts, CLI tools, or serverless functions never appear.

    **Cause:** The process exits before batched spans are exported.

    **Fix:** Call `Netra.shutdown()` (or `await Netra.shutdown()` in TypeScript) before the process exits.
  </Accordion>

  <Accordion title="Mixing Python and TypeScript conventions">
    **Problem:** Init call fails or parameters are silently ignored.

    **Cause:** Using `snake_case` in TypeScript (`app_name`) or `camelCase` in Python (`appName`).

    **Fix:** Python uses `snake_case` (`app_name`, `trace_content`, `set_user_id`). TypeScript uses `camelCase` (`appName`, `traceContent`, `setUserId`). Never mix them.
  </Accordion>

  <Accordion title="Not ending manual spans in TypeScript">
    **Problem:** Spans appear as infinitely long or never close.

    **Cause:** `span.end()` was not called — typically because it wasn't in a `finally` block.

    **Fix:** Always call `span.end()` in a `finally` block, or use `Netra.startActiveSpan()` for automatic lifecycle management.
  </Accordion>
</AccordionGroup>

***

## Integration Checklist

Use this checklist to verify your Netra integration is production-ready:

<Steps>
  <Step title="SDK installed and up to date">
    Run `pip install --upgrade netra-sdk` or `npm update netra-sdk` to ensure you have the latest version.
  </Step>

  <Step title="Environment variables configured">
    `NETRA_API_KEY` and `NETRA_OTLP_ENDPOINT` are set and point to the correct data region.
  </Step>

  <Step title="Init called first">
    `Netra.init()` is the first SDK call in your entrypoint — before any provider imports or LLM usage.
  </Step>

  <Step title="Context is set per request">
    `userId`, `sessionId`, and `tenantId` are set at the start of each request for proper grouping.
  </Step>

  <Step title="Root input and output captured">
    `set_root_input` is called at the entry point, and `set_root_output` (or the streaming equivalent) is called before returning.
  </Step>

  <Step title="Environment tag is set">
    The `environment` parameter distinguishes `development`, `staging`, and `production` traces.
  </Step>

  <Step title="Noisy instruments filtered">
    `blocked_spans` and `block_instruments` exclude health checks, internal HTTP, and other non-AI noise.
  </Step>

  <Step title="Shutdown is called on exit">
    `Netra.shutdown()` is hooked into your application's graceful shutdown lifecycle.
  </Step>
</Steps>

***

## What's Next?

<CardGroup cols={2}>
  <Card title="Auto Instrumentation" icon="wand-magic-sparkles" href="/Observability/Traces/auto-instrumentation">
    See all supported libraries and frameworks
  </Card>

  <Card title="Decorators" icon="at" href="/Observability/Traces/decorators">
    Add semantic context with @workflow, @agent, @task
  </Card>

  <Card title="Manual Tracing" icon="code" href="/Observability/Traces/manual-tracing">
    Create custom spans for fine-grained control
  </Card>

  <Card title="Evaluations" icon="clipboard-check" href="/Evaluations/evaluation-overview">
    Measure and track AI quality with automated test suites
  </Card>

  <Card title="Simulations" icon="flask" href="/Simulations/Simulation-overview">
    Test agents with realistic multi-turn conversations
  </Card>

  <Card title="Alert Rules" icon="bell" href="/Alert-rules/Alert-rules">
    Get notified about cost spikes and performance regressions
  </Card>
</CardGroup>
