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

# AI Framework (example)

> Instrument the provider your framework calls, and every call the chain makes is captured.

AI frameworks like LangChain, LlamaIndex or Haystack call the underlying provider SDK (OpenAI, Anthropic, ...) so the OpenTelemetry instrumentation for that provider catches every call your chain makes, transparently. You don't need a framework-specific instrumentation.

The example below uses LangChain, but the same approach works with any framework that calls an instrumented provider.

This example also turns on `exportMode: 'immediate'`, suitable for short-lived runtimes (Lambda, edge functions, scripts) where you don't want to risk losing buffered spans on exit.

## Install

<CodeGroup>
  ```js Node.js theme={null}
  npm install weflayr langchain @langchain/openai @traceloop/instrumentation-openai
  ```

  ```python Python theme={null}
  pip install weflayr langchain langchain-openai opentelemetry-instrumentation-openai
  ```
</CodeGroup>

## Setup

<CodeGroup>
  ```js Node.js theme={null}
  const weflayr = require('weflayr');
  const { ChatOpenAI } = require('@langchain/openai');

  await weflayr.autoInstrument({
    aiSdks: 'openai',
    defaultTags: { app: 'rag-pipeline', env: 'production', version: '2.4.1' },
    exportMode: 'immediate',
  });

  const llm = new ChatOpenAI({
    model: 'gpt-5.1',
    apiKey: process.env.OPENAI_API_KEY,
  });
  ```

  ```python Python theme={null}
  import os
  import weflayr
  from langchain_openai import ChatOpenAI

  weflayr.auto_instrument(
      ai_sdks=weflayr.AiSdk.OPENAI,
      default_tags={"app": "rag-pipeline"},
      export_mode="immediate",
  )

  llm = ChatOpenAI(model="gpt-5.1", api_key=os.environ["OPENAI_API_KEY"])
  ```
</CodeGroup>

## Use

A simple RAG-style chain: retrieve context, then ask the LLM. Decorate the feature function with `propagateMetadata` / `propagate_metadata`: every instrumented call it makes (here the OpenAI call inside `.invoke(...)`) is stamped with the metadata, and each invocation gets a fresh correlation id.

<CodeGroup>
  ```js Node.js theme={null}
  const { ChatPromptTemplate } = require('@langchain/core/prompts');

  const prompt = ChatPromptTemplate.fromMessages([
    ['system', 'Answer using only the provided context.'],
    ['user', 'Context:\n{context}\n\nQuestion: {question}'],
  ]);
  const chain = prompt.pipe(llm);

  const answer = weflayr.propagateMetadata({
    featureName: 'rag-answer',
    customerName: 'Acme Corp',
    extraTags: { retrieval_strategy: 'hybrid', top_k: 5 },
  })((question, context) => chain.invoke({ question, context }));
  ```

  ```python Python theme={null}
  from langchain_core.prompts import ChatPromptTemplate

  prompt = ChatPromptTemplate.from_messages([
      ("system", "Answer using only the provided context."),
      ("user", "Context:\n{context}\n\nQuestion: {question}"),
  ])
  chain = prompt | llm


  @weflayr.propagate_metadata(
      feature_name="rag-answer",
      customer_name="Acme Corp",
      extra_tags={"retrieval_strategy": "hybrid", "top_k": 5},
  )
  def answer(question: str, context: str):
      return chain.invoke({"question": question, "context": context})
  ```
</CodeGroup>

The same pattern works for any LangChain construct that ultimately calls an instrumented provider: chains, agents, runnables, LCEL pipelines. If the chain makes several LLM calls (e.g. a tool-calling agent loop), every call inside the decorated function carries the same `customer_name` and `feature_name`, so the dashboard can correlate them as one logical request. The decorator fixes its metadata at decoration time; when a value varies per request (a per-call `customer_name`, say), use the inline form instead (`weflayr.propagateMetadata(options, fn)` in JS, or `with weflayr.propagate_metadata(...):` in Python).
