# LangChain

> Capture traces from LangChain / LangGraph via LangSmith's OTel mode

## Language support

| Setup option | Python | TypeScript |
| - | :-: | :-: |
| Voxmith skill | ✓ | ✓ |
| Voxmith SDK | ✓ | ✓ |
| OpenTelemetry through LangSmith | ✓ | ✓ |

## Choose a setup method

| Method | Use it when |
| - | - |
| **Voxmith skill** | You want your coding agent to inspect the app, make the change, and verify it. |
| **Voxmith SDK** | You want explicit control over the interaction boundaries and fields. |
| **OpenTelemetry** | The framework already emits useful spans or you operate an OTel pipeline. |

Start with one method for a call path. Combining SDK tracking with framework
OpenTelemetry on the same call can create duplicate interactions.

## Recommended: Voxmith skill

<Note>
  **Recommended.** Choose this path when you want your coding agent to inspect the
  project, select a supported transport, make the smallest instrumentation change,
  send a test event, and verify that it reached Voxmith.
</Note>

Install the skill once in your coding-agent environment:

```bash theme={null}
npx skills add Voxmith/skills --skill voxmith
```

Run the framework-specific prompt below from the application root. Review the
resulting diff before deploying it.

```text theme={null}
Use the voxmith skill to add Voxmith analytics to this LangChain or LangGraph application.
Org ID: your-org-id
Instrument the real chain, graph, or agent invocation and verify one fresh interaction.
```

## Manual setup: Voxmith SDK

<CodeGroup>
  ```bash Python theme={null}
  pip install voxmith
  ```

  ```bash TypeScript theme={null}
  npm install voxmithai
  ```
</CodeGroup>

<CodeGroup>
  ```python Python theme={null}
  import voxmith

  voxmith.init("your-org-id")
  interaction = voxmith.begin(user_id="u-42", agent_name="langchain-support", input=prompt)
  try:
      result = chain.invoke({"input": prompt})
      interaction.end(output=str(result))
  except Exception as exc:
      interaction.end(output=str(exc), success=False)
      raise
  finally:
      voxmith.shutdown()
  ```

  ```typescript TypeScript theme={null}
  import * as voxmith from 'voxmithai';

  voxmith.init('your-org-id');
  const interaction = voxmith.begin({ userId: 'u-42', agentName: 'langchain-support', input: prompt });
  try {
    const result = await chain.invoke({ input: prompt });
    interaction.end(JSON.stringify(result));
  } catch (error) {
    interaction.end(String(error), false);
    throw error;
  } finally {
    await voxmith.shutdown();
  }
  ```
</CodeGroup>

## Manual setup: OpenTelemetry

LangChain emits OTel through LangSmith. Register the OTel exporter pointing at Voxmith before building chains or agents, enable LangSmith's OTel mode, then pass `session_id` / `user_id` per call.

### 1. Install

**Already have LangSmith and an OTLP exporter installed?** Skip.

**No setup yet?**

<CodeGroup>
  ```bash Python theme={null}
  pip install "langsmith[otel]" langchain \
              opentelemetry-sdk opentelemetry-exporter-otlp-proto-http
  ```

  ```bash TypeScript theme={null}
  npm install langsmith @opentelemetry/api @opentelemetry/context-async-hooks \
              @opentelemetry/sdk-trace-base @opentelemetry/exporter-trace-otlp-proto
  ```
</CodeGroup>

### 2. Wire LangSmith OTel pointing at Voxmith

The provider must be registered before importing or constructing LangChain objects.

**Already have an OTel TracerProvider?** Append Voxmith as an additional span processor:

```python Python theme={null}
import os
from opentelemetry import trace
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter

trace.get_tracer_provider().add_span_processor(
    BatchSpanProcessor(
        OTLPSpanExporter(
            endpoint="https://otel.voxmith.com/v1/traces",
            headers={"X-Voxmith-Org-ID": os.environ["VOXMITH_ORG_ID"]},
        )
    )
)

os.environ["LANGSMITH_OTEL_ENABLED"] = "true"
os.environ["LANGSMITH_TRACING"] = "true"
os.environ["LANGSMITH_OTEL_ONLY"] = "true"
```

**No OTel yet?** Full setup:

<CodeGroup>
  ```python Python theme={null}
  import os
  from opentelemetry import trace
  from opentelemetry.sdk.trace import TracerProvider
  from opentelemetry.sdk.trace.export import BatchSpanProcessor
  from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter

  provider = TracerProvider()
  provider.add_span_processor(
      BatchSpanProcessor(
          OTLPSpanExporter(
              endpoint="https://otel.voxmith.com/v1/traces",
              headers={"X-Voxmith-Org-ID": os.environ["VOXMITH_ORG_ID"]},
          )
      )
  )
  trace.set_tracer_provider(provider)

  os.environ["LANGSMITH_OTEL_ENABLED"] = "true"
  os.environ["LANGSMITH_TRACING"] = "true"
  os.environ["LANGSMITH_OTEL_ONLY"] = "true"

  # Import LangChain AFTER the provider is registered.
  from langchain_openai import ChatOpenAI
  ```

  ```typescript TypeScript theme={null}
  import { trace, SpanStatusCode } from '@opentelemetry/api';

  process.env.LANGSMITH_TRACING = 'true';
  process.env.LANGCHAIN_TRACING_V2 = 'true';
  process.env.LANGSMITH_TRACING_MODE = 'otel';

  const { initializeOTEL } = await import('langsmith/experimental/otel/setup');

  initializeOTEL({
    exporterConfig: {
      url: 'https://otel.voxmith.com/v1/traces',
      headers: { 'X-Voxmith-Org-ID': process.env.VOXMITH_ORG_ID! },
    },
  });
  ```
</CodeGroup>

For TypeScript, `langsmith/experimental/vercel` is the Vercel AI SDK 7 adapter. LangChain JS/TS should use `langsmith/experimental/otel/setup`.

For LangChain JS/TS, keep one active OTel span around the actual invoke. The
LangSmith OTel translator attaches LangChain run data to that active span, and
the GenAI attributes below give Voxmith exact Chat View input/output text.

### 3. Pass user\_id / session\_id per call

<CodeGroup>
  ```python Python theme={null}
  result = agent.invoke(
      {"messages": [{"role": "user", "content": "Hello"}]},
      config={
          "metadata": {
              "session_id": "conv-abc123",
              "user_id": "user-42",
          },
      },
  )
  ```

  ```typescript TypeScript theme={null}
  const tracer = trace.getTracer('langchain-ts');
  const userMessage = 'Hello';

  const result = await tracer.startActiveSpan('langchain.invoke', {
    attributes: {
      'langsmith.traceable': 'true',
      'langsmith.metadata.session_id': 'conv-abc123',
      'langsmith.metadata.user_id': 'user-42',
      'gen_ai.operation.name': 'chat',
      'gen_ai.system': 'langchain',
      'gen_ai.input.messages': JSON.stringify([{ role: 'user', content: userMessage }]),
    },
  }, async (span) => {
    try {
      const result = await chain.invoke(
        { messages: [{ role: 'user', content: userMessage }] },
        {
          metadata: {
            session_id: 'conv-abc123',
            user_id: 'user-42',
          },
          tags: ['voxmith', 'langchain-ts'],
        },
      );
      const answer = typeof result.content === 'string' ? result.content : JSON.stringify(result);
      span.setAttribute('gen_ai.output.messages', JSON.stringify([{ role: 'assistant', content: answer }]));
      span.setStatus({ code: SpanStatusCode.OK });
      return result;
    } catch (error) {
      span.recordException(error as Error);
      span.setStatus({ code: SpanStatusCode.ERROR, message: error instanceof Error ? error.message : String(error) });
      throw error;
    } finally {
      span.end();
    }
  });
  ```
</CodeGroup>

LangChain prefixes metadata as `langsmith.metadata.*`: Voxmith reads `langsmith.metadata.session_id` and `langsmith.metadata.user_id` natively for user / session grouping.

For Chat View, keep the assistant answer in the normal LangChain output:
message content, `generations`, or a chain result wrapper such as `answer`,
`output`, or `result`. Voxmith stores the raw OTel payload, then collapses those
common output shapes to readable text for the dashboard.

### What appears in Voxmith

* **Conversations** grouped by `session_id`.
* **User-level analytics** grouped by `user_id`.
* **Events** for LangSmith OTel spans.

### Verify

Run one `agent.invoke`, then open **Events** and confirm `langsmith.metadata.session_id` and `langsmith.metadata.user_id` exist.

### Troubleshooting

* Register the `TracerProvider` before importing LangChain.
* Set `LANGSMITH_OTEL_ENABLED=true` and `LANGSMITH_TRACING=true`.
* Set `LANGSMITH_OTEL_ONLY=true` if you do not want to also ship to LangSmith.

### References

* [Enable OpenTelemetry export](https://docs.langchain.com/langsmith/trace-with-opentelemetry)
* [Add custom metadata](https://docs.langchain.com/langsmith/add-metadata-tags)

## Next steps

* [Conversations](/using-conversations): review the complete chain or graph execution.
* [Events](/using-events): inspect LangChain and LangGraph spans.
* [Intents](/using-intents): organize production conversations by what users wanted.
