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

# Weave로 OpenTelemetry 트레이스 보내기

> 전용 엔드포인트를 통해 OpenTelemetry 호환 트레이스 데이터를 임포트합니다

<Warning>
  OTel span을 Weave의 새로운 Agents 뷰로 보내려면 [Weave의 새로운 OTel 네이티브 워크플로](/ko/products/wandb/weave/guides/tracking/trace-agents-otel)를 사용하는 것이 좋습니다. 이 가이드는 원시 OTLP 트레이스를 Weave의 표준 Traces 뷰로 수집하려는 경우에만 사용하세요.
</Warning>

Weave는 전용 엔드포인트를 통해 OpenTelemetry 호환 트레이스 데이터의 임포트를 지원합니다. 이 엔드포인트를 사용하면 OTLP(OpenTelemetry Protocol) 형식의 트레이스 데이터를 Weave 프로젝트로 직접 보낼 수 있습니다. 기존 OTel 기반 관측성 파이프라인은 그대로 유지하면서 OpenTelemetry 표준으로 애플리케이션을 계측하고, 그 트레이스를 다른 Weave 데이터와 함께 확인하려는 경우 이 인테그레이션을 사용하세요.

이 페이지에서는 엔드포인트 세부 정보, 인증, Python 및 TypeScript 엔드투엔드 예시, OpenTelemetry Collector를 통한 트레이스 전달 방법, 트레이스를 Weave 스레드로 구성하는 방법, 그리고 Weave가 수신 span에 적용하는 속성 매핑을 설명합니다.

<Note>
  이 엔드포인트의 데이터는 **Traces** 및 **Threads** 뷰에 표시됩니다. 멀티턴 에이전트의 OTel span을 **Agents** 뷰로 보내려면 agents 엔드포인트를 사용하세요. 자세한 내용은 [Agents 뷰로 OpenTelemetry span 보내기](/ko/products/wandb/weave/guides/tracking/trace-agents-otel)를 참조하세요.
</Note>

<h2 id="endpoint-details">
  엔드포인트 세부 정보
</h2>

* **경로**: `/otel/v1/traces`
* **Method**: `POST`
* **Content-Type**: `application/x-protobuf`
* **기본 URL**: OTel 트레이스 엔드포인트의 기본 URL은 W\&B 배포 유형에 따라 다릅니다.

- Multi-tenant Cloud: `https://trace.wandb.ai/otel/v1/traces`
- Dedicated Cloud 및 Self-Managed 인스턴스: `https://<your-subdomain>.wandb.io/traces/otel/v1/traces`

`<your-subdomain>`을 조직의 고유한 W\&B 도메인(예: `acme.wandb.io`)으로 바꾸세요.

<h2 id="authentication-and-routing">
  인증 및 라우팅
</h2>

Weave는 `wandb-api-key` 헤더로 요청을 인증하고, `TracerProvider`의 리소스 속성을 사용해 span을 올바른 entity와 프로젝트로 라우팅합니다. `wandb-api-key` 헤더에 W\&B API 키를 전달한 다음, `TracerProvider` 클래스에서 다음 키를 OpenTelemetry Resource 속성으로 지정하세요.

* `wandb.entity`: CoreWeave Forge 팀 또는 사용자 이름입니다.
* `wandb.project`: 트레이스를 보낼 프로젝트 이름입니다.

다음 예시는 인증 및 프로젝트 라우팅을 설정하는 방법을 보여 줍니다.

<Warning>
  아래의 `WEAVE_OTLP_ENDPOINT`는 OTel SDK의 `OTEL_EXPORTER_OTLP_ENDPOINT` 환경 변수가 아닌 로컬 변수이며, 전체 트레이스 경로(`.../otel/v1/traces`)로 설정되어 있습니다. 이 값은 익스포터의 `endpoint=`/`url:`에 명시적으로 전달되므로 SDK는 해당 URL로 그대로 요청을 보냅니다. 반대로 값을 실제 `OTEL_EXPORTER_OTLP_ENDPOINT` 환경 변수로 `export`하고 인수 없이 익스포터를 생성하면(예: `OTLPSpanExporter()`) SDK가 `/v1/traces`를 자동으로 덧붙입니다. 따라서 이 경우에는 해당 변수에 기본 URL(`.../otel`)을 지정해야 합니다. 그렇지 않으면 경로가 중복되어(`.../otel/v1/traces/v1/traces`) `404` 오류가 발생하고, span이 아무런 알림 없이 유실됩니다. `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT`도 마찬가지로 전체 트레이스 경로를 지정해야 합니다. SDK는 이 변수에도 /v1/traces를 덧붙이지 않기 때문입니다.
</Warning>

<CodeGroup>
  ```python Python lines {7,8} theme={"system"}
  import os
  from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
  from opentelemetry.sdk import trace as trace_sdk
  from opentelemetry.sdk.resources import Resource

  WANDB_BASE_URL = "https://trace.wandb.ai"
  ENTITY = "<your-team-name>"
  PROJECT = "<your-project-name>"

  WEAVE_OTLP_ENDPOINT = f"{WANDB_BASE_URL}/otel/v1/traces"

  # https://forge.coreweave.com/settings 에서 API 키를 생성하세요
  WANDB_API_KEY = os.environ["WANDB_API_KEY"]

  exporter = OTLPSpanExporter(
      endpoint=WEAVE_OTLP_ENDPOINT,
      headers={"wandb-api-key": WANDB_API_KEY},
  )

  tracer_provider = trace_sdk.TracerProvider(resource=Resource({
      "wandb.entity": ENTITY,
      "wandb.project": PROJECT,
  }))
  ```

  ```typescript TypeScript lines theme={"system"}
  import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node";
  import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-proto";
  import { Resource } from "@opentelemetry/resources";

  const WANDB_BASE_URL = "https://trace.wandb.ai";
  const ENTITY = "<your-team-name>";
  const PROJECT = "<your-project-name>";

  const WEAVE_OTLP_ENDPOINT = `${WANDB_BASE_URL}/otel/v1/traces`;

  // https://forge.coreweave.com/settings 에서 API 키를 생성하세요
  const WANDB_API_KEY = process.env.WANDB_API_KEY!;

  const exporter = new OTLPTraceExporter({
    url: WEAVE_OTLP_ENDPOINT,
    headers: { "wandb-api-key": WANDB_API_KEY },
  });

  const provider = new NodeTracerProvider({
    resource: new Resource({
      "wandb.entity": ENTITY,
      "wandb.project": PROJECT,
    }),
  });
  ```
</CodeGroup>

<h2 id="examples">
  예시
</h2>

다음 예시는 Python과 TypeScript를 사용해 OpenTelemetry 트레이스를 Weave로 전송하는 방법을 보여줍니다. 예시마다 접근 방식이 다르며, OpenInference 계측 라이브러리를 사용하는 방법, OpenLLMetry 계측을 사용하는 방법, 계측 패키지 없이 OpenTelemetry SDK를 직접 사용하는 방법을 각각 다룹니다.

아래 코드 샘플을 실행하기 전에 다음 필드를 설정하세요.

* `WANDB_API_KEY`: [User Settings](https://forge.coreweave.com/settings)에서 조회할 수 있습니다.
* entity: 액세스 권한이 있는 팀/entity의 프로젝트에만 트레이스를 로깅할 수 있습니다. entity 이름을 확인하려면 [W\&B 대시보드](https://forge.coreweave.com/wandb)로 이동하여 왼쪽 사이드바의 **Teams** 필드를 확인하세요.
* 프로젝트 이름: 원하는 이름을 지정하세요.
* `OPENAI_API_KEY`: [OpenAI 대시보드](https://platform.openai.com/api-keys)에서 획득할 수 있습니다.

<h3 id="openinference-instrumentation">
  OpenInference 계측
</h3>

[OpenInference](https://github.com/Arize-ai/openinference)는 Arize AI에서 제공하는 오픈 소스 계측 라이브러리로, LLM Call을 OpenTelemetry span으로 캡처합니다. 이 예제에서는 OpenAI 계측을 사용하는 방법을 보여줍니다. 다른 계측은 [공식 저장소](https://github.com/Arize-ai/openinference)에서 확인할 수 있습니다.

먼저 필요한 의존성을 설치하세요.

<Tabs>
  <Tab title="Python">
    ```bash theme={"system"}
    pip install openai openinference-instrumentation-openai opentelemetry-exporter-otlp-proto-http
    ```
  </Tab>

  <Tab title="TypeScript">
    ```bash theme={"system"}
    npm install openai @opentelemetry/sdk-trace-node @opentelemetry/sdk-trace-base @opentelemetry/resources @opentelemetry/exporter-trace-otlp-proto @arizeai/openinference-instrumentation-openai @opentelemetry/api
    ```
  </Tab>
</Tabs>

<Warning>
  **성능 권장 사항**: Weave로 트레이스를 전송할 때는 항상 `SimpleSpanProcessor` 대신 `BatchSpanProcessor`를 사용하세요. `SimpleSpanProcessor`는 span을 동기적으로 내보내므로 다른 워크로드의 성능에 영향을 줄 수 있습니다. 이 예시에서는 `BatchSpanProcessor`를 사용합니다. `BatchSpanProcessor`는 span을 비동기적으로 효율적으로 일괄 처리하므로 프로덕션 환경에서 사용을 권장합니다.
</Warning>

<Tabs>
  <Tab title="Python">
    다음 코드를 `openinference_example.py` 같은 Python 파일에 붙여 넣으세요.

    ```python lines theme={"system"}
    import os
    import openai
    from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
    from opentelemetry.sdk import trace as trace_sdk
    from opentelemetry.sdk.resources import Resource
    from opentelemetry.sdk.trace.export import ConsoleSpanExporter, BatchSpanProcessor
    from openinference.instrumentation.openai import OpenAIInstrumentor

    OPENAI_API_KEY = "YOUR_OPENAI_API_KEY"
    WANDB_BASE_URL = "https://trace.wandb.ai"
    ENTITY = "<your-team-name>"
    PROJECT = "<your-project-name>"

    WEAVE_OTLP_ENDPOINT = f"{WANDB_BASE_URL}/otel/v1/traces"

    # https://forge.coreweave.com/settings 에서 API 키를 생성하세요.
    WANDB_API_KEY = os.environ["WANDB_API_KEY"]

    exporter = OTLPSpanExporter(
        endpoint=WEAVE_OTLP_ENDPOINT,
        headers={"wandb-api-key": WANDB_API_KEY},
    )

    tracer_provider = trace_sdk.TracerProvider(resource=Resource({
        "wandb.entity": ENTITY,
        "wandb.project": PROJECT,
    }))
    tracer_provider.add_span_processor(BatchSpanProcessor(exporter))

    # (선택) span을 콘솔에도 출력합니다.
    tracer_provider.add_span_processor(BatchSpanProcessor(ConsoleSpanExporter()))

    OpenAIInstrumentor().instrument(tracer_provider=tracer_provider)

    def main():
        client = openai.OpenAI(api_key=OPENAI_API_KEY)
        response = client.chat.completions.create(
            model="gpt-3.5-turbo",
            messages=[{"role": "user", "content": "Describe OTel in a single sentence."}],
            max_tokens=20,
            stream=True,
            stream_options={"include_usage": True},
        )
        for chunk in response:
            if chunk.choices and (content := chunk.choices[0].delta.content):
                print(content, end="")

    if __name__ == "__main__":
        main()
    ```

    코드를 실행하세요.

    ```bash theme={"system"}
    python openinference_example.py
    ```
  </Tab>

  <Tab title="TypeScript">
    이 예제의 TypeScript 구현은 Python 구현과 비교해 다음과 같은 주요 차이가 있습니다.

    * 계측을 등록하기 전에 OpenAI를 임포트해야 합니다(ESM 모듈의 요구 사항).
    * W\&B의 엔드포인트는 protobuf만 허용하므로, HTTP 익스포터 대신 `@opentelemetry/exporter-trace-otlp-proto`(protobuf 형식)를 사용합니다.
    * `BatchSpanProcessor`는 비동기로 플러시하므로, span이 모두 플러시되도록 종료 전에 지연 시간을 두고 `provider.shutdown()`을 명시적으로 호출해야 합니다.

    다음 코드를 `openinference_example.ts` 등의 TypeScript 파일에 붙여넣으세요.

    ```typescript lines {11,12} theme={"system"}
    // 중요: 계측이 OpenAI를 패치할 수 있도록 OpenAI를 가장 먼저 임포트하세요
    import OpenAI from "openai";
    import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node";
    import { BatchSpanProcessor } from "@opentelemetry/sdk-trace-base";
    import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-proto";
    import { resourceFromAttributes } from "@opentelemetry/resources";
    import { OpenAIInstrumentation, isPatched } from "@arizeai/openinference-instrumentation-openai";

    const OPENAI_API_KEY = process.env.OPENAI_API_KEY;
    const WANDB_BASE_URL = "https://trace.wandb.ai";
    const ENTITY = "<your-team-name>";
    const PROJECT = "<your-project-name>";

    const WEAVE_OTLP_ENDPOINT = `${WANDB_BASE_URL}/otel/v1/traces`;

    // https://forge.coreweave.com/settings 에서 API 키를 생성하세요
    const WANDB_API_KEY = process.env.WANDB_API_KEY!;

    const exporter = new OTLPTraceExporter({
      url: WEAVE_OTLP_ENDPOINT,
      headers: { "wandb-api-key": WANDB_API_KEY },
    });

    const provider = new NodeTracerProvider({
      resource: resourceFromAttributes({
        "wandb.entity": ENTITY,
        "wandb.project": PROJECT,
      }),
      spanProcessors: [
        new BatchSpanProcessor(exporter)
      ],
    });

    provider.register();

    // 트레이서 프로바이더에 OpenAI 계측을 등록합니다
    const openAIInstrumentation = new OpenAIInstrumentation();
    openAIInstrumentation.setTracerProvider(provider);

    // ESM을 사용하므로 OpenAI를 수동으로 계측합니다
    openAIInstrumentation.manuallyInstrument(OpenAI);

    async function main() {
      console.log("OpenAI is patched?", isPatched());

      const client = new OpenAI({ apiKey: OPENAI_API_KEY });

      console.log("Making OpenAI API call...");
      const response = await client.chat.completions.create({
        model: "gpt-3.5-turbo",
        messages: [{ role: "user", content: "Describe OTel in a single sentence." }],
        max_tokens: 50,
      });

      console.log("Response:", response.choices[0]?.message?.content);
      console.log("Waiting for spans to flush...");
    }

    (async () => {
      await main();

      // span이 플러시될 때까지 잠시 기다립니다
      console.log("Waiting 2 seconds for spans to flush...");
      await new Promise(resolve => setTimeout(resolve, 2000));

      await provider.shutdown(); // 종료하기 전에 대기 중인 모든 span을 플러시합니다
      console.log("Shutdown complete");
    })();
    ```

    코드를 실행하세요.

    ```bash theme={"system"}
    npx ts-node openinference_example.ts
    ```
  </Tab>
</Tabs>

<h3 id="openllmetry-instrumentation">
  OpenLLMetry 계측
</h3>

[OpenLLMetry](https://github.com/traceloop/openllmetry)는 Traceloop의 오픈 소스 관측성 라이브러리로, 널리 사용되는 LLM 공급자와 프레임워크에 OpenTelemetry 계측을 제공합니다. 다음 예시에서는 OpenLLMetry의 OpenAI 계측을 사용하는 방법을 보여줍니다. 더 많은 예시는 [OpenLLMetry 저장소](https://github.com/traceloop/openllmetry)에서 확인할 수 있습니다.

먼저 필요한 의존성을 설치하세요.

<Tabs>
  <Tab title="Python">
    ```bash theme={"system"}
    pip install openai opentelemetry-instrumentation-openai opentelemetry-exporter-otlp-proto-http
    ```
  </Tab>

  <Tab title="TypeScript">
    ```bash theme={"system"}
    npm install openai @traceloop/instrumentation-openai @opentelemetry/sdk-trace-node @opentelemetry/resources @opentelemetry/exporter-trace-otlp-http
    ```
  </Tab>
</Tabs>

<Tabs>
  <Tab title="Python">
    다음 코드를 `openllmetry_example.py` 같은 Python 파일에 붙여 넣으세요. 앞의 예시와 동일한 코드이지만, `OpenAIInstrumentor`를 `openinference.instrumentation.openai`가 아닌 `opentelemetry.instrumentation.openai`에서 임포트한다는 점만 다릅니다.

    ```python lines theme={"system"}
    import os
    import openai
    from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
    from opentelemetry.sdk import trace as trace_sdk
    from opentelemetry.sdk.resources import Resource
    from opentelemetry.sdk.trace.export import ConsoleSpanExporter, BatchSpanProcessor
    from opentelemetry.instrumentation.openai import OpenAIInstrumentor

    OPENAI_API_KEY = "YOUR_OPENAI_API_KEY"
    WANDB_BASE_URL = "https://trace.wandb.ai"
    ENTITY = "<your-team-name>"
    PROJECT = "<your-project-name>"

    WEAVE_OTLP_ENDPOINT = f"{WANDB_BASE_URL}/otel/v1/traces"

    # https://forge.coreweave.com/settings 에서 API 키를 생성하세요
    WANDB_API_KEY = os.environ["WANDB_API_KEY"]

    exporter = OTLPSpanExporter(
        endpoint=WEAVE_OTLP_ENDPOINT,
        headers={"wandb-api-key": WANDB_API_KEY},
    )

    tracer_provider = trace_sdk.TracerProvider(resource=Resource({
        "wandb.entity": ENTITY,
        "wandb.project": PROJECT,
    }))
    tracer_provider.add_span_processor(BatchSpanProcessor(exporter))

    # (선택) span을 콘솔에도 출력합니다.
    tracer_provider.add_span_processor(BatchSpanProcessor(ConsoleSpanExporter()))

    OpenAIInstrumentor().instrument(tracer_provider=tracer_provider)

    def main():
        client = openai.OpenAI(api_key=OPENAI_API_KEY)
        response = client.chat.completions.create(
            model="gpt-3.5-turbo",
            messages=[{"role": "user", "content": "Describe OTel in a single sentence."}],
            max_tokens=20,
            stream=True,
            stream_options={"include_usage": True},
        )
        for chunk in response:
            if chunk.choices and (content := chunk.choices[0].delta.content):
                print(content, end="")

    if __name__ == "__main__":
        main()
    ```

    코드를 실행하세요.

    ```bash theme={"system"}
    python openllmetry_example.py
    ```
  </Tab>

  <Tab title="TypeScript">
    다음 코드를 `openllmetry_example.ts` 같은 TypeScript 파일에 붙여 넣으세요. 이 코드는 Traceloop OpenAI 계측 패키지를 사용합니다.

    ```typescript lines theme={"system"}
    import OpenAI from "openai";
    import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node";
    import { BatchSpanProcessor, ConsoleSpanExporter } from "@opentelemetry/sdk-trace-base";
    import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-proto";
    import { Resource } from "@opentelemetry/resources";
    import { OpenAIInstrumentation } from "@traceloop/instrumentation-openai";
    import { registerInstrumentations } from "@opentelemetry/instrumentation";

    const OPENAI_API_KEY = process.env.OPENAI_API_KEY;
    const WANDB_BASE_URL = "https://trace.wandb.ai";
    const ENTITY = "<your-team-name>";
    const PROJECT = "<your-project-name>";

    const WEAVE_OTLP_ENDPOINT = `${WANDB_BASE_URL}/otel/v1/traces`;

    // https://forge.coreweave.com/settings 에서 API 키를 생성하세요
    const WANDB_API_KEY = process.env.WANDB_API_KEY!;

    const exporter = new OTLPTraceExporter({
      url: WEAVE_OTLP_ENDPOINT,
      headers: { "wandb-api-key": WANDB_API_KEY },
    });

    const provider = new NodeTracerProvider({
      resource: new Resource({
        "wandb.entity": ENTITY,
        "wandb.project": PROJECT,
      }),
      spanProcessors: [
        new BatchSpanProcessor(exporter),
        // 선택 사항: span을 콘솔에 출력합니다.
        new BatchSpanProcessor(new ConsoleSpanExporter()),
      ],
    });

    provider.register();

    // 트레이서 프로바이더에 OpenAI 계측을 등록합니다
    const openAIInstrumentation = new OpenAIInstrumentation();
    registerInstrumentations({
      tracerProvider: provider,
      instrumentations: [openAIInstrumentation],
    });

    // ESM을 사용하므로 OpenAI를 수동으로 계측합니다
    openAIInstrumentation.manuallyInstrument(OpenAI);

    async function main() {
      const client = new OpenAI({ apiKey: OPENAI_API_KEY });
      const stream = await client.chat.completions.create({
        model: "gpt-3.5-turbo",
        messages: [{ role: "user", content: "Describe OTel in a single sentence." }],
        max_tokens: 20,
        stream: true,
      });

      for await (const chunk of stream) {
        const content = chunk.choices[0]?.delta?.content;
        if (content) {
          process.stdout.write(content);
        }
      }
      console.log(); // 스트리밍 후 줄바꿈
    }

    (async () => {
      await main();

      // span이 플러시될 때까지 잠시 대기합니다
      await new Promise(resolve => setTimeout(resolve, 2000));

      await provider.shutdown(); // 종료 전에 대기 중인 모든 span을 플러시합니다
    })();
    ```

    코드를 실행하세요.

    ```bash theme={"system"}
    npx ts-node openllmetry_example.ts
    ```
  </Tab>
</Tabs>

<h3 id="without-instrumentation">
  계측 없이 사용하기
</h3>

계측 패키지 대신 OTel을 직접 사용할 수도 있습니다. 이 방식을 사용하면 각 span에 어떤 속성을 설정할지 전체적으로 제어할 수 있습니다. Weave는 [https://opentelemetry.io/docs/specs/semconv/gen-ai/gen-ai-spans/](https://opentelemetry.io/docs/specs/semconv/gen-ai/gen-ai-spans/)에 설명된 OpenTelemetry 시맨틱 규칙에 따라 span 속성을 파싱합니다.

먼저 필요한 의존성을 설치하세요.

<Tabs>
  <Tab title="Python">
    ```bash theme={"system"}
    pip install openai opentelemetry-sdk opentelemetry-api opentelemetry-exporter-otlp-proto-http
    ```
  </Tab>

  <Tab title="TypeScript">
    ```bash theme={"system"}
    npm install openai @opentelemetry/api @opentelemetry/sdk-trace-node @opentelemetry/resources @opentelemetry/exporter-trace-otlp-http
    ```
  </Tab>
</Tabs>

<Tabs>
  <Tab title="Python">
    다음 코드를 `opentelemetry_example.py` 같은 Python 파일에 붙여 넣으세요.

    ```python lines theme={"system"}
    import json
    import os
    import openai
    from opentelemetry import trace
    from opentelemetry.sdk import trace as trace_sdk
    from opentelemetry.sdk.resources import Resource
    from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
    from opentelemetry.sdk.trace.export import ConsoleSpanExporter, BatchSpanProcessor

    OPENAI_API_KEY = "YOUR_OPENAI_API_KEY"
    WANDB_BASE_URL = "https://trace.wandb.ai"
    ENTITY = "<your-team-name>"
    PROJECT = "<your-project-name>"

    WEAVE_OTLP_ENDPOINT = f"{WANDB_BASE_URL}/otel/v1/traces"

    # https://forge.coreweave.com/settings 에서 API 키를 생성하세요
    WANDB_API_KEY = os.environ["WANDB_API_KEY"]

    # OTLP 익스포터 설정
    exporter = OTLPSpanExporter(
        endpoint=WEAVE_OTLP_ENDPOINT,
        headers={"wandb-api-key": WANDB_API_KEY},
    )

    tracer_provider = trace_sdk.TracerProvider(resource=Resource({
        "wandb.entity": ENTITY,
        "wandb.project": PROJECT,
    }))
    tracer_provider.add_span_processor(BatchSpanProcessor(exporter))

    # (선택 사항) span을 콘솔에도 출력합니다.
    tracer_provider.add_span_processor(BatchSpanProcessor(ConsoleSpanExporter()))

    # 트레이서 프로바이더 설정
    trace.set_tracer_provider(tracer_provider)

    # 전역 트레이서 프로바이더로부터 트레이서 생성
    tracer = trace.get_tracer(__name__)

    def my_function():
        with tracer.start_as_current_span("outer_span") as outer_span:
            client = openai.OpenAI()
            input_messages = [{"role": "user", "content": "Describe OTel in a single sentence."}]
            outer_span.set_attribute("input.value", json.dumps(input_messages))
            outer_span.set_attribute("gen_ai.system", "openai")
            response = client.chat.completions.create(
                model="gpt-3.5-turbo",
                messages=input_messages,
                max_tokens=20,
                stream=True,
                stream_options={"include_usage": True},
            )
            out = ""
            for chunk in response:
                if chunk.choices and (content := chunk.choices[0].delta.content):
                    out += content
            outer_span.set_attribute("output.value", json.dumps({"content": out}))

    if __name__ == "__main__":
        my_function()
    ```

    코드를 실행하세요.

    ```bash theme={"system"}
    python opentelemetry_example.py
    ```
  </Tab>

  <Tab title="TypeScript">
    다음 코드를 `opentelemetry_example.ts`와 같은 TypeScript 파일에 붙여 넣으세요.

    ```typescript lines theme={"system"}
    import OpenAI from "openai";
    import { trace } from "@opentelemetry/api";
    import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node";
    import { BatchSpanProcessor, ConsoleSpanExporter } from "@opentelemetry/sdk-trace-base";
    import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
    import { Resource } from "@opentelemetry/resources";

    const OPENAI_API_KEY = "YOUR_OPENAI_API_KEY";
    const WANDB_BASE_URL = "https://trace.wandb.ai";
    const ENTITY = "<your-team-name>";
    const PROJECT = "<your-project-name>";

    const WEAVE_OTLP_ENDPOINT = `${WANDB_BASE_URL}/otel/v1/traces`;

    // https://forge.coreweave.com/settings 에서 API 키를 생성하세요
    const WANDB_API_KEY = process.env.WANDB_API_KEY!;

    const exporter = new OTLPTraceExporter({
      url: WEAVE_OTLP_ENDPOINT,
      headers: { "wandb-api-key": WANDB_API_KEY },
    });

    const provider = new NodeTracerProvider({
      resource: new Resource({
        "wandb.entity": ENTITY,
        "wandb.project": PROJECT,
      }),
      spanProcessors: [
        new BatchSpanProcessor(exporter),
        // (선택) span을 콘솔에 출력합니다.
        new BatchSpanProcessor(new ConsoleSpanExporter()),
      ],
    });

    provider.register();

    // 전역 트레이서 프로바이더에서 트레이서를 생성합니다
    const tracer = trace.getTracer("my-app");

    async function myFunction() {
      const span = tracer.startSpan("outer_span");

      try {
        const client = new OpenAI({ apiKey: OPENAI_API_KEY });
        const inputMessages = [
          { role: "user" as const, content: "Describe OTel in a single sentence." },
        ];

        // 이 값은 사이드 패널에만 표시됩니다
        span.setAttribute("input.value", JSON.stringify(inputMessages));
        
        // 이 값은 시맨틱 규칙을 따르므로 대시보드에 표시됩니다
        span.setAttribute("gen_ai.system", "openai");

        const stream = await client.chat.completions.create({
          model: "gpt-3.5-turbo",
          messages: inputMessages,
          max_tokens: 20,
          stream: true,
        });

        let output = "";
        for await (const chunk of stream) {
          const content = chunk.choices[0]?.delta?.content;
          if (content) {
            output += content;
          }
          }

        // 이 값은 사이드 패널에만 표시됩니다
        span.setAttribute("output.value", JSON.stringify({ content: output }));
      } finally {
        span.end();
      }
    }

    myFunction();
    ```

    코드를 실행하세요.

    ```bash theme={"system"}
    npx ts-node opentelemetry_example.ts
    ```
  </Tab>
</Tabs>

Weave는 span 속성 접두사 `gen_ai`와 `openinference`를 기준으로, 트레이스를 해석할 때 적용할 규칙이 있는지, 있다면 어떤 규칙을 적용할지 확인합니다. 두 키가 모두 감지되지 않으면 트레이스 뷰에 모든 span 속성이 표시됩니다. 트레이스를 선택하면 측면 패널에서 전체 span을 확인할 수 있습니다.

<h2 id="use-an-opentelemetry-collector">
  OpenTelemetry Collector 사용하기
</h2>

앞의 예시에서는 애플리케이션에서 Weave로 트레이스를 직접 내보냈습니다. 프로덕션 환경에서는 애플리케이션과 Weave 사이에 [OpenTelemetry Collector](https://opentelemetry.io/docs/collector/)를 중개자로 둘 수 있습니다. collector는 앱에서 트레이스를 받아 하나 이상의 백엔드로 전달합니다. 이 패턴을 사용하면 인증, 배치 처리, 라우팅 로직을 애플리케이션 코드 밖에서 한곳에 모아 관리할 수 있으며, 하나의 파이프라인에서 여러 관측성 백엔드로 트레이스를 분산 전송할 수 있습니다.

<h3 id="set-up-a-collector">
  collector 설정
</h3>

이 섹션에서는 Docker에서 로컬 OpenTelemetry Collector를 실행하고, 애플리케이션이 이 collector로 트레이스를 전송하도록 설정하는 방법을 안내합니다. 다음 예시에서는 아래 작업을 수행하는 방법을 보여 줍니다.

* OTLP 트레이스를 수신해 배치로 묶은 뒤 Weave로 전달하는 로컬 서버(collector)를 배포하도록 Docker 설정 파일을 작성합니다.
* Docker를 사용해 collector를 로컬에서 실행합니다.
* OpenAI에 기본 Call을 보내고, 도커 컨테이너에서 실행 중인 collector로 트레이스를 전달합니다.

collector를 사용하려면 먼저 `collector-config.yaml` 파일을 생성하세요. 이 파일은 collector가 OTLP 트레이스를 수신해 Weave로 내보내도록 설정합니다.

```yaml lines {23,26} collector-config.yaml title="collector-config.yaml"  theme={"system"}
receivers:
  otlp:
    protocols:
      http:
        endpoint: 0.0.0.0:4318

exporters:
  otlphttp/weave:
    endpoint: ${env:WANDB_OTLP_ENDPOINT}
    headers:
      wandb-api-key: ${env:WANDB_API_KEY}
    sending_queue:
      batch:

processors:
  resource:
    attributes:
      - key: wandb.entity # 리소스 속성 필드
        value: ${env:DEFAULT_WANDB_ENTITY}  # 주입할 값
        action: insert # 값이 없는 경우에만 주입
      - key: wandb.project
        value: ${env:DEFAULT_WANDB_PROJECT}
        action: insert 

service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [resource]
      exporters: [otlphttp/weave]
```

이 설정 파일은 다음을 수행합니다.

* 포트 `4318`(HTTP)에서 OTLP 트레이스를 수신합니다.
* `wandb-api-key` 헤더를 사용하여 Weave의 OTLP 엔드포인트로 트레이스를 내보냅니다. 엔드포인트 URL은 `WANDB_OTLP_ENDPOINT`에서, API 키는 `WANDB_API_KEY`에서 읽어옵니다.
* `resource` 프로세서를 사용하여 `wandb.entity`와 `wandb.project`를 리소스 속성으로 설정하며, 값은 `DEFAULT_WANDB_ENTITY`와 `DEFAULT_WANDB_PROJECT`에서 읽어옵니다. `insert` 액션은 애플리케이션 코드에서 이 속성들을 아직 설정하지 않은 경우에만 주입합니다.
* 익스포터에 기본 제공되는 `sending_queue`와 배치 처리를 활성화하여 네트워크 오버헤드를 줄입니다.

collector 설정을 마쳤으면 다음 Docker 명령에서 API 키와 entity 값을 수정한 후 실행하세요.

```bash lines {3,5} theme={"system"}
docker run \
  -v ./config.yaml:/etc/otelcol-contrib/config.yaml \
  -e WANDB_API_KEY="<your-wandb-api-key>" \
  -e WANDB_OTLP_ENDPOINT="https://trace.wandb.ai/otel" \
  -e DEFAULT_WANDB_ENTITY="<your-team-name>" \
  -e DEFAULT_WANDB_PROJECT="YOUR_PROJECT" \
  -p 4318:4318 \
  otel/opentelemetry-collector-contrib:latest
```

collector가 실행되면 `OTEL_EXPORTER_OTLP_ENDPOINT` 환경 변수를 설정하여 애플리케이션이 collector로 트레이스를 내보내도록 구성하세요. OTel SDK가 이 변수를 자동으로 읽으므로 익스포터에 엔드포인트를 따로 전달할 필요가 없습니다.

애플리케이션의 `TracerProvider`에서 `wandb.entity` 또는 `wandb.project`를 리소스 속성으로 설정하면, 해당 값이 collector 설정에 정의된 기본값보다 우선 적용됩니다.

<CodeGroup>
  ```python Python lines theme={"system"}
  import os
  import openai
  from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
  from opentelemetry.sdk import trace as trace_sdk
  from opentelemetry.sdk.trace.export import BatchSpanProcessor
  from openinference.instrumentation.openai import OpenAIInstrumentor

  os.environ["OTEL_EXPORTER_OTLP_ENDPOINT"] = "http://localhost:4318"

  OPENAI_API_KEY = "YOUR_OPENAI_API_KEY"

  tracer_provider = trace_sdk.TracerProvider()
  tracer_provider.add_span_processor(BatchSpanProcessor(OTLPSpanExporter()))

  OpenAIInstrumentor().instrument(tracer_provider=tracer_provider)

  def main():
      client = openai.OpenAI(api_key=OPENAI_API_KEY)
      response = client.chat.completions.create(
          model="gpt-3.5-turbo",
          messages=[{"role": "user", "content": "Describe OTel in a single sentence."}],
          max_tokens=20,
      )
      print(response.choices[0].message.content)

  if __name__ == "__main__":
      main()
  ```

  ```typescript TypeScript lines theme={"system"}
  import OpenAI from "openai";
  import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node";
  import { BatchSpanProcessor } from "@opentelemetry/sdk-trace-base";
  import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-proto";
  import { OpenAIInstrumentation, isPatched } from "@arizeai/openinference-instrumentation-openai";

  process.env.OTEL_EXPORTER_OTLP_ENDPOINT = "http://localhost:4318";

  const OPENAI_API_KEY = process.env.OPENAI_API_KEY;

  const provider = new NodeTracerProvider({
    spanProcessors: [new BatchSpanProcessor(new OTLPTraceExporter())],
  });

  provider.register();

  const openAIInstrumentation = new OpenAIInstrumentation();
  openAIInstrumentation.setTracerProvider(provider);
  openAIInstrumentation.manuallyInstrument(OpenAI);

  async function main() {
    console.log("OpenAI is patched?", isPatched());

    const client = new OpenAI({ apiKey: OPENAI_API_KEY });
    const response = await client.chat.completions.create({
      model: "gpt-3.5-turbo",
      messages: [{ role: "user", content: "Describe OTel in a single sentence." }],
      max_tokens: 20,
    });

    console.log("Response:", response.choices[0]?.message?.content);
  }

  (async () => {
    await main();
    await new Promise(resolve => setTimeout(resolve, 2000));
    await provider.shutdown();
  })();
  ```
</CodeGroup>

`OpenAIInstrumentor`는 OpenAI Call을 래핑하여 트레이스를 생성하고, 이를 collector로 내보냅니다. 인증 및 Weave로의 라우팅은 collector가 처리합니다.

스크립트를 실행하면 Weights & Biases UI에서 [트레이스를 확인](/ko/products/wandb/weave/guides/tracking/trace-tree)할 수 있습니다.

트레이스를 다른 백엔드로도 보내려면 익스포터를 추가하고 `service.pipelines.traces.exporters` 목록에 포함하세요. 예를 들어, 하나의 Collector 인스턴스에서 Weave와 Jaeger로 동시에 내보낼 수 있습니다.

<h2 id="organize-otel-traces-into-threads">
  OTel 트레이스를 스레드로 구성하기
</h2>

[Weave 스레드](/ko/products/wandb/weave/guides/tracking/threads)를 사용하면 관련 트레이스를 그룹화하여 멀티턴 대화나 사용자 세션을 하나의 단위로 분석할 수 있습니다. 특정 span 속성을 추가해 OpenTelemetry 트레이스를 스레드로 구성한 다음, Weave의 Thread UI에서 멀티턴 대화나 사용자 세션 같은 관련 오퍼레이션을 분석하세요.

스레드 그룹화를 사용하려면 OTel span에 다음 속성을 추가하세요.

* `wandb.thread_id`: span을 특정 스레드로 그룹화합니다.
* `wandb.is_turn`: span을 대화 턴으로 표시합니다(스레드 뷰에 행으로 나타남).

다음 예시에서는 OTel 트레이스를 Weave 스레드로 구성하는 방법을 보여 줍니다. `wandb.thread_id`로 관련 오퍼레이션을 그룹화하고, `wandb.is_turn`으로 스레드 뷰에 행으로 나타나는 상위 수준 오퍼레이션을 표시합니다.

<Accordion title="초기 설정">
  예시를 실행하려면 다음 설정을 사용하세요.

  <Tabs>
    <Tab title="Python">
      ```python lines theme={"system"}
      import json
      import os
      from opentelemetry import trace
      from opentelemetry.sdk import trace as trace_sdk
      from opentelemetry.sdk.resources import Resource
      from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
      from opentelemetry.sdk.trace.export import ConsoleSpanExporter, BatchSpanProcessor

      # 설정
      ENTITY = "<your-team-name>"
      PROJECT = "<your-project-name>"
      WANDB_API_KEY = os.environ["WANDB_API_KEY"]

      WEAVE_OTLP_ENDPOINT = "https://trace.wandb.ai/otel/v1/traces"

      exporter = OTLPSpanExporter(
          endpoint=WEAVE_OTLP_ENDPOINT,
          headers={"wandb-api-key": WANDB_API_KEY},
      )

      tracer_provider = trace_sdk.TracerProvider(resource=Resource({
          "wandb.entity": ENTITY,
          "wandb.project": PROJECT,
      }))
      tracer_provider.add_span_processor(BatchSpanProcessor(exporter))

      # 선택 사항: span을 콘솔에 출력
      tracer_provider.add_span_processor(BatchSpanProcessor(ConsoleSpanExporter()))

      trace.set_tracer_provider(tracer_provider)

      # 전역 트레이서 프로바이더에서 트레이서 생성
      tracer = trace.get_tracer(__name__)
      ```
    </Tab>

    <Tab title="TypeScript">
      ```typescript lines theme={"system"}
      import { trace, context } from "@opentelemetry/api";
      import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node";
      import {
        BatchSpanProcessor,
        ConsoleSpanExporter,
      } from "@opentelemetry/sdk-trace-base";
      import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-proto";
      import { Resource } from "@opentelemetry/resources";

      // 설정
      const ENTITY = "<your-team-name>";
      const PROJECT = "<your-project-name>";
      const WANDB_API_KEY = process.env.WANDB_API_KEY;

      if (!WANDB_API_KEY) {
        console.error("Error: WANDB_API_KEY environment variable is not set");
        console.error("Run: export WANDB_API_KEY=your_api_key_here");
        process.exit(1);
      }

      // OTel 설정
      const WEAVE_OTLP_ENDPOINT = "https://trace.wandb.ai/otel/v1/traces";

      const exporter = new OTLPTraceExporter({
        url: WEAVE_OTLP_ENDPOINT,
        headers: { "wandb-api-key": WANDB_API_KEY },
      });

      // span 프로세서로 트레이서 프로바이더 초기화
      const provider = new NodeTracerProvider({
        resource: new Resource({
          "wandb.entity": ENTITY,
          "wandb.project": PROJECT,
        }),
        spanProcessors: [
          new BatchSpanProcessor(exporter),
          new BatchSpanProcessor(new ConsoleSpanExporter()),
        ],
      });

      // 트레이서 프로바이더 등록
      provider.register();

      // 전역 트레이서 프로바이더에서 트레이서 생성
      const tracer = trace.getTracer("threads-examples");
      ```
    </Tab>
  </Tabs>
</Accordion>

<Accordion title="기본 단일 턴 스레드 트레이스하기">
  <Tabs>
    <Tab title="Python">
      ```python lines theme={"system"}
      def example_1_basic_thread_and_turn():
          """Example 1: Basic thread with a single turn"""
          print("\n=== Example 1: Basic Thread and Turn ===")

          # 스레드 컨텍스트 생성
          thread_id = "thread_example_1"

          # 이 span은 턴을 나타냄(스레드의 직계 자식)
          with tracer.start_as_current_span("process_user_message") as turn_span:
              # 스레드 속성 설정
              turn_span.set_attribute("wandb.thread_id", thread_id)
              turn_span.set_attribute("wandb.is_turn", True)

              # 예시 속성 추가
              turn_span.set_attribute("input.value", "Hello, help me with setup")

              # 중첩 span으로 작업 시뮬레이션
              with tracer.start_as_current_span("generate_response") as nested_span:
                  # 턴 내부의 중첩 Call이므로 is_turn은 false로 설정하거나 설정하지 않아야 함
                  nested_span.set_attribute("wandb.thread_id", thread_id)
                  # 중첩 Call에서는 wandb.is_turn을 설정하지 않거나 False로 설정

                  response = "I'll help you get started with the setup process."
                  nested_span.set_attribute("output.value", response)

              turn_span.set_attribute("output.value", response)
              print(f"Turn completed in thread: {thread_id}")

      def main():
          example_1_basic_thread_and_turn()

      if __name__ == "__main__":
          main()
      ```
    </Tab>

    <Tab title="TypeScript">
      ```typescript twoslash lines theme={"system"}
      // @noErrors
      function example_1_basic_thread_and_turn() {
        console.log("\n=== Example 1: Basic Thread and Turn ===");

        // 스레드 컨텍스트 생성
        const threadId = "thread_example_1";

        // 이 span은 턴을 나타냄(스레드의 직계 자식)
        tracer.startActiveSpan("process_user_message", (turnSpan) => {
          // 스레드 속성 설정
          turnSpan.setAttribute("wandb.thread_id", threadId);
          turnSpan.setAttribute("wandb.is_turn", true);

          // 예시 속성 추가
          turnSpan.setAttribute("input.value", "Hello, help me with setup");

          let response: string;
          
          // 중첩 span으로 작업 시뮬레이션
          tracer.startActiveSpan("generate_response", (nestedSpan) => {
            // 턴 내부의 중첩 Call이므로 is_turn은 false로 설정하거나 설정하지 않아야 함
            nestedSpan.setAttribute("wandb.thread_id", threadId);
            // 중첩 Call에서는 wandb.is_turn을 설정하지 않거나 false로 설정

            response = "I'll help you get started with the setup process.";
            nestedSpan.setAttribute("output.value", response);
            nestedSpan.end();
          });
          
          turnSpan.setAttribute("output.value", response!);
          console.log(`Turn completed in thread: ${threadId}`);
          turnSpan.end();
        });
      }

      function main() {
        example_1_basic_thread_and_turn();
      }

      main();
      ```
    </Tab>
  </Tabs>
</Accordion>

<Accordion title="하나의 스레드 ID를 공유하는 멀티턴 대화 트레이스하기">
  <Tabs>
    <Tab title="Python">
      ```python lines theme={"system"}
      def example_2_multiple_turns():
          """Example 2: Multiple turns in a single thread"""
          print("\n=== Example 2: Multiple Turns in Thread ===")

          thread_id = "thread_conversation_123"

          # 턴 1
          with tracer.start_as_current_span("process_message_turn1") as turn1_span:
              turn1_span.set_attribute("wandb.thread_id", thread_id)
              turn1_span.set_attribute("wandb.is_turn", True)
              turn1_span.set_attribute("input.value", "What programming languages do you recommend?")

              # 중첩된 오퍼레이션
              with tracer.start_as_current_span("analyze_query") as analyze_span:
                  analyze_span.set_attribute("wandb.thread_id", thread_id)
                  # 중첩된 span에는 is_turn 속성을 지정하지 않거나 False로 설정

              response1 = "I recommend Python for beginners and JavaScript for web development."
              turn1_span.set_attribute("output.value", response1)
              print(f"Turn 1 completed in thread: {thread_id}")

          # 턴 2
          with tracer.start_as_current_span("process_message_turn2") as turn2_span:
              turn2_span.set_attribute("wandb.thread_id", thread_id)
              turn2_span.set_attribute("wandb.is_turn", True)
              turn2_span.set_attribute("input.value", "Can you explain Python vs JavaScript?")

              # 중첩된 오퍼레이션
              with tracer.start_as_current_span("comparison_analysis") as compare_span:
                  compare_span.set_attribute("wandb.thread_id", thread_id)
                  compare_span.set_attribute("wandb.is_turn", False)  # 중첩된 span이므로 명시적으로 False로 설정

              response2 = "Python excels at data science while JavaScript dominates web development."
              turn2_span.set_attribute("output.value", response2)
              print(f"Turn 2 completed in thread: {thread_id}")

      def main():
          example_2_multiple_turns()

      if __name__ == "__main__":
          main()
      ```
    </Tab>

    <Tab title="TypeScript">
      ```typescript twoslash lines theme={"system"}
      // @noErrors
      function example_2_multiple_turns() {
        console.log("\n=== Example 2: Multiple Turns in Thread ===");

        const threadId = "thread_conversation_123";

        // 턴 1
        tracer.startActiveSpan("process_message_turn1", (turn1Span) => {
          turn1Span.setAttribute("wandb.thread_id", threadId);
          turn1Span.setAttribute("wandb.is_turn", true);
          turn1Span.setAttribute(
            "input.value",
            "What programming languages do you recommend?"
          );

          // 중첩된 오퍼레이션
          tracer.startActiveSpan("analyze_query", (analyzeSpan) => {
            analyzeSpan.setAttribute("wandb.thread_id", threadId);
            // 중첩된 span에는 is_turn 속성을 지정하지 않거나 false로 설정
            analyzeSpan.end();
          });

          const response1 =
            "I recommend Python for beginners and JavaScript for web development.";
          turn1Span.setAttribute("output.value", response1);
          console.log(`Turn 1 completed in thread: ${threadId}`);
          turn1Span.end();
        });

        // 턴 2
        tracer.startActiveSpan("process_message_turn2", (turn2Span) => {
          turn2Span.setAttribute("wandb.thread_id", threadId);
          turn2Span.setAttribute("wandb.is_turn", true);
          turn2Span.setAttribute("input.value", "Can you explain Python vs JavaScript?");

          // 중첩된 오퍼레이션
          tracer.startActiveSpan("comparison_analysis", (compareSpan) => {
            compareSpan.setAttribute("wandb.thread_id", threadId);
            compareSpan.setAttribute("wandb.is_turn", false); // 중첩된 span이므로 명시적으로 false로 설정
            compareSpan.end();
          });

          const response2 =
            "Python excels at data science while JavaScript dominates web development.";
          turn2Span.setAttribute("output.value", response2);
          console.log(`Turn 2 completed in thread: ${threadId}`);
          turn2Span.end();
        });
      }

      function main() {
        example_2_multiple_turns();
      }

      main();
      ```
    </Tab>
  </Tabs>
</Accordion>

<Accordion title="깊게 중첩된 오퍼레이션을 트레이스하고 가장 바깥쪽 span만 턴으로 표시하기">
  <Tabs>
    <Tab title="Python">
      ```python lines theme={"system"}
      def example_3_complex_nested_structure():
          """예시 3: 여러 단계로 구성된 복잡한 중첩 구조"""
          print("\n=== Example 3: Complex Nested Structure ===")

          thread_id = "thread_complex_456"

          # 여러 단계로 중첩된 턴
          with tracer.start_as_current_span("handle_complex_request") as turn_span:
              turn_span.set_attribute("wandb.thread_id", thread_id)
              turn_span.set_attribute("wandb.is_turn", True)
              turn_span.set_attribute("input.value", "Analyze this code and suggest improvements")

              # 1단계 중첩 오퍼레이션
              with tracer.start_as_current_span("code_analysis") as analysis_span:
                  analysis_span.set_attribute("wandb.thread_id", thread_id)
                  # 중첩 오퍼레이션에는 is_turn을 설정하지 않음

                  # 2단계 중첩 오퍼레이션
                  with tracer.start_as_current_span("syntax_check") as syntax_span:
                      syntax_span.set_attribute("wandb.thread_id", thread_id)
                      syntax_span.set_attribute("result", "No syntax errors found")

                  # 또 다른 2단계 중첩 오퍼레이션
                  with tracer.start_as_current_span("performance_check") as perf_span:
                      perf_span.set_attribute("wandb.thread_id", thread_id)
                      perf_span.set_attribute("result", "Found 2 optimization opportunities")

              # 또 다른 1단계 중첩 오퍼레이션
              with tracer.start_as_current_span("generate_suggestions") as suggest_span:
                  suggest_span.set_attribute("wandb.thread_id", thread_id)
                  suggestions = ["Use list comprehension", "Consider caching results"]
                  suggest_span.set_attribute("suggestions", json.dumps(suggestions))

              turn_span.set_attribute("output.value", "Analysis complete with 2 improvement suggestions")
              print(f"Complex turn completed in thread: {thread_id}")

      def main():
          example_3_complex_nested_structure()

      if __name__ == "__main__":
          main()
      ```
    </Tab>

    <Tab title="TypeScript">
      ```typescript twoslash lines theme={"system"}
      // @noErrors
      function example_3_complex_nested_structure() {
        console.log("\n=== Example 3: Complex Nested Structure ===");

        const threadId = "thread_complex_456";

        // 여러 단계로 중첩된 턴
        tracer.startActiveSpan("handle_complex_request", (turnSpan) => {
          turnSpan.setAttribute("wandb.thread_id", threadId);
          turnSpan.setAttribute("wandb.is_turn", true);
          turnSpan.setAttribute(
            "input.value",
            "Analyze this code and suggest improvements"
          );

          // 1단계 중첩 오퍼레이션
          tracer.startActiveSpan("code_analysis", (analysisSpan) => {
            analysisSpan.setAttribute("wandb.thread_id", threadId);
            // 중첩 오퍼레이션에는 is_turn을 설정하지 않음

            // 2단계 중첩 오퍼레이션
            tracer.startActiveSpan("syntax_check", (syntaxSpan) => {
              syntaxSpan.setAttribute("wandb.thread_id", threadId);
              syntaxSpan.setAttribute("result", "No syntax errors found");
              syntaxSpan.end();
            });

            // 또 다른 2단계 중첩 오퍼레이션
            tracer.startActiveSpan("performance_check", (perfSpan) => {
              perfSpan.setAttribute("wandb.thread_id", threadId);
              perfSpan.setAttribute("result", "Found 2 optimization opportunities");
              perfSpan.end();
            });

            analysisSpan.end();
          });

          // 또 다른 1단계 중첩 오퍼레이션
          tracer.startActiveSpan("generate_suggestions", (suggestSpan) => {
            suggestSpan.setAttribute("wandb.thread_id", threadId);
            const suggestions = ["Use list comprehension", "Consider caching results"];
            suggestSpan.setAttribute("suggestions", JSON.stringify(suggestions));
            suggestSpan.end();
          });

          turnSpan.setAttribute(
            "output.value",
            "Analysis complete with 2 improvement suggestions"
          );
          console.log(`Complex turn completed in thread: ${threadId}`);
          turnSpan.end();
        });
      }

      function main() {
        example_3_complex_nested_structure();
      }

      main();
      ```
    </Tab>
  </Tabs>
</Accordion>

<Accordion title="스레드에 속하지만 턴이 아닌 백그라운드 오퍼레이션 트레이스하기">
  <Tabs>
    <Tab title="Python">
      ```python lines theme={"system"}
      def example_4_non_turn_operations():
          """Example 4: Operations that are part of a thread but not turns"""
          print("\n=== Example 4: Non-Turn Thread Operations ===")

          thread_id = "thread_background_789"

          # 스레드에 속하지만 턴은 아닌 백그라운드 오퍼레이션
          with tracer.start_as_current_span("background_indexing") as bg_span:
              bg_span.set_attribute("wandb.thread_id", thread_id)
              # wandb.is_turn이 설정되지 않았거나 false이면 턴이 아님
              bg_span.set_attribute("wandb.is_turn", False)
              bg_span.set_attribute("operation", "Indexing conversation history")
              print(f"Background operation in thread: {thread_id}")

          # 같은 스레드의 실제 턴
          with tracer.start_as_current_span("user_query") as turn_span:
              turn_span.set_attribute("wandb.thread_id", thread_id)
              turn_span.set_attribute("wandb.is_turn", True)
              turn_span.set_attribute("input.value", "Search my previous conversations")
              turn_span.set_attribute("output.value", "Found 5 relevant conversations")
              print(f"Turn completed in thread: {thread_id}")

      def main():
          example_4_non_turn_operations()

      if __name__ == "__main__":
          main()
      ```
    </Tab>

    <Tab title="TypeScript">
      ```typescript twoslash lines theme={"system"}
      // @noErrors
      function example_4_non_turn_operations() {
        console.log("\n=== Example 4: Non-Turn Thread Operations ===");

        const threadId = "thread_background_789";

        // 스레드에 속하지만 턴은 아닌 백그라운드 오퍼레이션
        tracer.startActiveSpan("background_indexing", (bgSpan) => {
          bgSpan.setAttribute("wandb.thread_id", threadId);
          // wandb.is_turn이 설정되지 않았거나 false이면 턴이 아님
          bgSpan.setAttribute("wandb.is_turn", false);
          bgSpan.setAttribute("operation", "Indexing conversation history");
          console.log(`Background operation in thread: ${threadId}`);
          bgSpan.end();
        });

        // 같은 스레드의 실제 턴
        tracer.startActiveSpan("user_query", (turnSpan) => {
          turnSpan.setAttribute("wandb.thread_id", threadId);
          turnSpan.setAttribute("wandb.is_turn", true);
          turnSpan.setAttribute("input.value", "Search my previous conversations");
          turnSpan.setAttribute("output.value", "Found 5 relevant conversations");
          console.log(`Turn completed in thread: ${threadId}`);
          turnSpan.end();
        });
      }

      function main() {
        example_4_non_turn_operations();
      }

      main();
      ```
    </Tab>
  </Tabs>
</Accordion>

트레이스를 전송하면 Weights & Biases UI의 **Threads** 탭에서 확인할 수 있습니다. 트레이스는 `thread_id`별로 그룹화되며, 각 턴은 별도의 행으로 표시됩니다.

<h2 id="attribute-mappings">
  속성 매핑
</h2>

Weave는 다양한 계측 프레임워크의 OpenTelemetry span 속성을 내부 데이터 모델에 매핑합니다. 따라서 기존 계측에서 사용하던 속성의 이름을 바꾸거나 변환하지 않아도 Weave에서 풍부한 뷰를 확인할 수 있습니다. 여러 속성 이름이 같은 필드에 매핑되면 Weave는 우선순위에 따라 적용하므로, 여러 프레임워크를 같은 트레이스 안에서 함께 사용할 수 있습니다.

<h3 id="supported-frameworks">
  지원되는 프레임워크
</h3>

Weave는 다음 관측성 프레임워크 및 SDK의 속성 규칙을 지원합니다.

* **OpenTelemetry GenAI**: 생성형 AI용 표준 시맨틱 규칙(`gen_ai.*`)
* **OpenInference**: Arize AI의 계측 라이브러리(`input.value`, `output.value`, `llm.*`, `openinference.*`)
* **Vercel AI SDK**: Vercel AI SDK 속성(`ai.prompt`, `ai.response`, `ai.model.*`, `ai.usage.*`)
* **MLflow**: MLflow 추적 속성(`mlflow.spanInputs`, `mlflow.spanOutputs`)
* **Traceloop**: OpenLLMetry 계측(`traceloop.entity.*`, `traceloop.span.kind`)
* **Google Vertex AI**: Vertex AI 에이전트 속성(`gcp.vertex.agent.*`)
* **OpenLit**: OpenLit 관측성 속성(`gen_ai.content.completion`)
* **Logfire / Pydantic AI**: Logfire의 Pydantic AI 계측(`gen_ai.input.messages`, `gen_ai.output.messages`, `pydantic_ai.all_messages`, `final_result`)
* **Langfuse**: Langfuse 트레이싱 속성(`langfuse.startTime`, `langfuse.endTime`)

<h3 id="attribute-reference">
  속성 레퍼런스
</h3>

| 속성 필드 이름 | W\&B 매핑 | 설명 | 유형 | 예시 |
| :- | :- | :- | :- | :- |
| `ai.prompt` | `inputs` | 사용자 프롬프트 텍스트 또는 메시지입니다. | String, list, dict | `"Write a short haiku about summer."` |
| `gen_ai.prompt` | `inputs` | AI 모델 프롬프트 또는 메시지 배열입니다. | List, dict, string | `[{"role":"user","content":"abc"}]` |
| `input.value` | `inputs` | 모델 호출의 입력 값입니다. | String, list, dict | `{"text":"Tell a joke"}` |
| `mlflow.spanInputs` | `inputs` | span 입력 데이터입니다. | String, list, dict | `["prompt text"]` |
| `gen_ai.input.messages` | `inputs` | Logfire가 내보낸 입력 메시지입니다. | List, dict, string | `[{"role":"user","parts":[...]}]` |
| `pydantic_ai.all_messages` | `inputs` | Pydantic AI 에이전트 run의 전체 메시지입니다. | List | `[{"role":"user","content":"abc"}]` |
| `traceloop.entity.input` | `inputs` | entity 입력 데이터입니다. | String, list, dict | `"Translate this to French"` |
| `gcp.vertex.agent.tool_call_args` | `inputs` | 도구 Call 인수입니다. | Dict | `{"args":{"query":"weather in SF"}}` |
| `gcp.vertex.agent.llm_request` | `inputs` | LLM 요청 페이로드입니다. | Dict | `{"contents":[{"role":"user","parts":[...]}]}` |
| `input` | `inputs` | 일반 입력 값입니다. | String, list, dict | `"Summarize this text"` |
| `inputs` | `inputs` | 일반 입력 배열입니다. | List, dict, string | `["Summarize this text"]` |
| `ai.response` | `outputs` | 모델 응답 텍스트 또는 데이터입니다. | String, list, dict | `"Here is a haiku..."` |
| `gen_ai.completion` | `outputs` | AI 완성 결과입니다. | String, list, dict | `"Completion text"` |
| `output.value` | `outputs` | 모델의 출력 값입니다. | String, list, dict | `{"text":"Answer text"}` |
| `gen_ai.output.messages` | `outputs` | Logfire가 내보낸 출력 메시지입니다. | List, dict, string | `[{"role":"assistant","parts":[...]}]` |
| `mlflow.spanOutputs` | `outputs` | span 출력 데이터입니다. | String, list, dict | `["answer"]` |
| `final_result` | `outputs` | Pydantic AI 에이전트 run의 최종 출력입니다. | String, dict | `"Answer text"` |
| `gen_ai.content.completion` | `outputs` | 콘텐츠 완성 결과입니다. | String | `"Answer text"` |
| `traceloop.entity.output` | `outputs` | entity 출력 데이터입니다. | String, list, dict | `"Answer text"` |
| `gcp.vertex.agent.tool_response` | `outputs` | 도구 실행 응답입니다. | Dict, string | `{"toolResponse":"ok"}` |
| `gcp.vertex.agent.llm_response` | `outputs` | LLM 응답 페이로드입니다. | Dict, string | `{"candidates":[...]}` |
| `output` | `outputs` | 일반 출력 값입니다. | String, list, dict | `"Answer text"` |
| `outputs` | `outputs` | 일반 출력 배열입니다. | List, dict, string | `["Answer text"]` |
| `gen_ai.usage.input_tokens` | `usage.input_tokens` | 사용된 입력 토큰 수입니다. | Int | `42` |
| `gen_ai.usage.prompt_tokens` | `usage.prompt_tokens` | 사용된 프롬프트 토큰 수입니다. | Int | `30` |
| `llm.token_count.prompt` | `usage.prompt_tokens` | 프롬프트 토큰 수입니다. | Int | `30` |
| `ai.usage.promptTokens` | `usage.prompt_tokens` | 사용된 프롬프트 토큰 수입니다. | Int | `30` |
| `gen_ai.usage.completion_tokens` | `usage.completion_tokens` | 생성된 완성 토큰 수입니다. | Int | `40` |
| `llm.token_count.completion` | `usage.completion_tokens` | 완성 토큰 수입니다. | Int | `40` |
| `ai.usage.completionTokens` | `usage.completion_tokens` | 생성된 완성 토큰 수입니다. | Int | `40` |
| `llm.usage.total_tokens` | `usage.total_tokens` | 요청에 사용된 총 토큰 수입니다. | Int | `70` |
| `llm.token_count.total` | `usage.total_tokens` | 총 토큰 수입니다. | Int | `70` |
| `gen_ai.system` | `attributes.system` | system 프롬프트 또는 지침입니다. | String | `"You are a helpful assistant."` |
| `llm.system` | `attributes.system` | system 프롬프트 또는 지침입니다. | String | `"You are a helpful assistant."` |
| `gen_ai.system_instructions` | `attributes.system` | system 프롬프트 또는 지침입니다. | String | `"You are a helpful assistant."` |
| `weave.span.kind` | `attributes.kind` | span 유형 또는 범주입니다. | String | `"llm"` |
| `traceloop.span.kind` | `attributes.kind` | span 유형 또는 범주입니다. | String | `"llm"` |
| `openinference.span.kind` | `attributes.kind` | span 유형 또는 범주입니다. | String | `"llm"` |
| `gen_ai.response.model` | `attributes.model` | 모델 식별자입니다. | String | `"gpt-4o"` |
| `llm.model_name` | `attributes.model` | 모델 식별자입니다. | String | `"gpt-4o-mini"` |
| `ai.model.id` | `attributes.model` | 모델 식별자입니다. | String | `"gpt-4o"` |
| `llm.provider` | `attributes.provider` | 모델 공급자 이름입니다. | String | `"openai"` |
| `ai.model.provider` | `attributes.provider` | 모델 공급자 이름입니다. | String | `"openai"` |
| `gen_ai.request` | `attributes.model_parameters` | 모델 생성 매개변수입니다. | Dict | `{"temperature":0.7,"max_tokens":256}` |
| `llm.invocation_parameters` | `attributes.model_parameters` | 모델 호출 매개변수입니다. | Dict | `{"temperature":0.2}` |
| `wandb.display_name` | `display_name` | UI에 표시할 맞춤형 표시 이름입니다. | String | `"User Message"` |
| `gcp.vertex.agent.session_id` | `thread_id` | 세션 또는 스레드 식별자입니다. | String | `"thread_123"` |
| `wandb.thread_id` | `thread_id` | 대화에 사용되는 스레드 식별자입니다. | String | `"thread_123"` |
| `wb_run_id` | `wb_run_id` | 연결된 W\&B run의 식별자입니다. | String | `"abc123"` |
| `wandb.wb_run_id` | `wb_run_id` | 연결된 W\&B run의 식별자입니다. | String | `"abc123"` |
| `gcp.vertex.agent.session_id` | `is_turn` | span을 대화 턴으로 지정합니다. | Boolean | `true` |
| `wandb.is_turn` | `is_turn` | span을 대화 턴으로 지정합니다. | Boolean | `true` |
| `langfuse.startTime` | `start_time` (override) | span 시작 타임스탬프를 재정의합니다. | Timestamp (ISO8601/unix ns) | `"2024-01-01T12:00:00Z"` |
| `langfuse.endTime` | `end_time` (override) | span 종료 타임스탬프를 재정의합니다. | Timestamp (ISO8601/unix ns) | `"2024-01-01T12:00:01Z"` |

<h2 id="limitations">
  제한 사항
</h2>

Weights & Biases UI는 Chat 뷰에서 OTel 트레이스의 도구 Call 렌더링을 지원하지 않습니다. 이러한 도구 Call은 원시 JSON 형태로 표시됩니다.


## Related topics

- [Koog](/ko/products/wandb/weave/guides/integrations/koog.md)
