메인 콘텐츠로 건너뛰기

개요

Weave는 전용 엔드포인트를 통해 OpenTelemetry와 호환되는 트레이스 데이터를 수집할 수 있도록 지원합니다. 이 엔드포인트를 사용하면 OTLP(OpenTelemetry Protocol) 형식의 트레이스 데이터를 Weave 프로젝트로 직접 전송할 수 있습니다.

엔드포인트 상세 정보

Path: /otel/v1/traces Method: POST Content-Type: application/x-protobuf Base URL: OTel trace 엔드포인트의 기본 URL은 W&B 배포 유형에 따라 달라집니다:
  • 멀티 테넌트 클라우드:
    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와 같습니다.

인증 및 라우팅

wandb-api-key 헤더에 W&B API 키를 전달한 다음, TracerProvider 클래스에서 다음 키를 OpenTelemetry Resource 속성으로 지정합니다:
  • wandb.entity: W&B 팀 또는 사용자 이름.
  • wandb.project: 트레이스를 전송할 프로젝트 이름.
아래 예시는 인증 및 프로젝트 라우팅을 구성하는 방법을 보여줍니다:
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>"

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

# Create an API key at https://wandb.ai/settings
WANDB_API_KEY = os.environ["WANDB_API_KEY"]

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

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

예시

다음 예시는 Python과 TypeScript를 사용해 OpenTelemetry 트레이스를 Weave로 전송하는 방법을 보여줍니다. 아래 코드 샘플을 실행하기 전에 다음 항목을 설정하세요:
  1. WANDB_API_KEY: User Settings에서 확인할 수 있습니다.
  2. Entity: 접근 권한이 있는 Entity 소속 프로젝트에만 트레이스를 기록할 수 있습니다. [https://wandb.ai/home]에서 W&B 대시보드를 열고, 왼쪽 사이드바의 Teams 필드를 확인해 자신의 Entity 이름을 찾을 수 있습니다.
  3. Project Name: 마음에 드는 이름을 선택하세요!
  4. OPENAI_API_KEY: OpenAI dashboard에서 발급받을 수 있습니다.

OpenInference 계측

이 예제는 OpenAI 계측을 사용하는 방법을 보여줍니다. 공식 리포지토리에서 더 많은 계측 옵션을 확인할 수 있습니다. 먼저 필요한 종속 패키지를 설치합니다:
pip install openai openinference-instrumentation-openai opentelemetry-exporter-otlp-proto-http
성능 권장사항: Weave로 트레이스를 전송할 때는 항상 SimpleSpanProcessor 대신 BatchSpanProcessor를 사용하세요. SimpleSpanProcessor는 스팬을 동기적으로 내보내기 때문에 다른 워크로드의 성능에 영향을 줄 수 있습니다. 이 예제에서는 BatchSpanProcessor를 사용하며, 스팬을 비동기적으로 효율적으로 배치 처리하므로 운영 환경에서 사용하는 것을 권장합니다.
다음 코드를 예를 들어 openinference_example.py라는 이름의 Python 파일에 붙여넣으세요:
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>"

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

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

exporter = OTLPSpanExporter(
    endpoint=OTEL_EXPORTER_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))

# 선택 사항: 스팬을 콘솔에 출력합니다.
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()
코드를 실행하세요:
python openinference_example.py

OpenLLMetry 계측

다음 예제에서는 OpenAI 계측을 사용하는 방법을 보여줍니다. 추가 예제는 OpenLLMetry 저장소에서 확인할 수 있습니다. 먼저 필요한 의존성을 설치합니다:
pip install openai opentelemetry-instrumentation-openai opentelemetry-exporter-otlp-proto-http
다음 코드를 openllmetry_example.py와 같은 Python 파일에 붙여넣으세요. 이 코드는 위의 코드와 동일하지만, OpenAIInstrumentoropeninference.instrumentation.openai가 아니라 opentelemetry.instrumentation.openai에서 임포트된다는 점만 다릅니다:
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>"

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

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

exporter = OTLPSpanExporter(
    endpoint=OTEL_EXPORTER_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))

# 선택 사항: 스팬을 콘솔에 출력합니다.
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()
코드를 실행하세요:
python openllmetry_example.py

인스트루멘테이션 없이 사용하기

인스트루멘테이션 패키지 대신 OTel을 직접 사용하려면 그렇게 할 수 있습니다. Span 속성은 https://opentelemetry.io/docs/specs/semconv/gen-ai/gen-ai-spans/에 설명된 OpenTelemetry 시맨틱 컨벤션에 따라 해석됩니다. 먼저, 필요한 의존성을 설치합니다:
pip install openai opentelemetry-sdk opentelemetry-api opentelemetry-exporter-otlp-proto-http
opentelemetry_example.py 같은 Python 파일에 다음 코드를 붙여넣으세요:`
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>"

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

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

# OTLP exporter 구성
exporter = OTLPSpanExporter(
    endpoint=OTEL_EXPORTER_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))

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

# tracer provider 설정
trace.set_tracer_provider(tracer_provider)

# 전역 tracer provider에서 tracer 생성
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()
코드를 실행하세요:
python opentelemetry_example.py
span 속성 접두사 gen_aiopeninference는 트레이스를 해석할 때 사용할 규약(있는 경우)을 결정하는 데 사용됩니다. 두 접두사 키가 모두 감지되지 않으면 모든 span 속성이 트레이스 뷰에 표시됩니다. 트레이스를 선택하면 사이드 패널에서 전체 span을 확인할 수 있습니다.

OpenTelemetry Collector 사용하기

위 예제들에서는 애플리케이션에서 Weave로 트레이스를 직접 내보냈습니다. 운영 환경에서는 애플리케이션과 Weave 사이의 중간 매개체로 OpenTelemetry Collector를 사용할 수 있습니다. Collector는 애플리케이션에서 트레이스를 수신한 뒤, 이를 하나 이상의 백엔드로 전달합니다.

콜렉터 설정

다음 예제에서는 다음 작업 방법을 보여줍니다:
  • OTLP 트레이스를 수신하고 배치 처리한 뒤 Weave로 전달하는 로컬 서버(콜렉터)를 배포하는 Docker 설정 파일을 준비합니다.
  • Docker를 사용해 로컬에서 콜렉터를 실행합니다.
  • Docker 컨테이너에서 실행 중인 콜렉터로 트레이스를 전달하는 간단한 OpenAI 호출을 보냅니다.
콜렉터를 사용하려면 먼저 collector-config.yaml 파일을 생성해 콜렉터가 OTLP 트레이스를 수신하고 이를 Weave로 내보내도록 설정합니다:
collector-config.yaml
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.entitywandb.project를 리소스 속성으로 설정하며, 값은 DEFAULT_WANDB_ENTITYDEFAULT_WANDB_PROJECT에서 읽어옵니다. insert 액션은 애플리케이션 코드가 이미 해당 값을 설정하지 않은 경우에만 이러한 속성을 주입합니다.
  • 네트워크 오버헤드를 줄이기 위해 배치 기능이 포함된 내장 sending_queue를 exporter에서 활성화합니다.
collector 설정을 마친 후, 아래 Docker 명령에서 API와 entity 값을 업데이트한 다음 실행합니다:
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
콜렉터가 실행 중이면 OTEL_EXPORTER_OTLP_ENDPOINT 환경 변수를 설정해 애플리케이션이 트레이스를 콜렉터로 내보내도록 구성합니다. OTel SDK는 이 변수를 자동으로 읽으므로 엔드포인트를 익스포터에 직접 전달할 필요가 없습니다. 애플리케이션의 TracerProvider에서 wandb.entity 또는 wandb.project를 리소스 속성으로 설정하면, 콜렉터 구성에서 정의한 기본값보다 해당 값들이 우선 적용됩니다.
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()
OpenAIInstrumentor는 OpenAI 호출을 자동으로 래핑해 트레이스를 생성하고, 이를 컬렉터로 내보냅니다. 컬렉터는 Weave로의 인증 및 라우팅을 처리합니다. 스크립트를 실행한 후 Weave UI에서 트레이스를 확인할 수 있습니다. 추가 백엔드로도 트레이스를 전송하려면 익스포터를 더 추가하고 이를 service.pipelines.traces.exporters 목록에 포함하면 됩니다. 예를 들어 동일한 Collector 인스턴스에서 Weave와 Jaeger 모두로 내보낼 수 있습니다.

OTel 트레이스를 스레드로 구성하기

특정 span 속성을 추가해 OpenTelemetry 트레이스를 Weave threads로 구성한 다음, Weave의 Thread UI를 사용해 멀티턴 대화나 사용자 세션과 같은 관련 작업을 분석합니다. 스레드 그룹화를 활성화하려면 OTel span에 다음 속성을 추가하세요:
  • wandb.thread_id: span을 특정 스레드로 그룹화
  • wandb.is_turn: span을 대화 턴으로 표시 (스레드 보기에서 한 행으로 표시됨)
다음 예제는 OTel 트레이스를 Weave threads로 구성하는 방법을 보여줍니다. wandb.thread_id를 사용해 관련 작업을 그룹화하고, wandb.is_turn을 사용해 스레드 보기에서 행으로 표시되는 상위 수준 작업을 표시합니다.
다음 설정을 사용해 아래 예제를 실행하세요:
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"]

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

exporter = OTLPSpanExporter(
    endpoint=OTEL_EXPORTER_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 provider에서 tracer 생성
tracer = trace.get_tracer(__name__)
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:
            # 이는 턴 내부의 중첩 호출이므로 is_turn은 False이거나 아예 설정하지 않아야 함
            nested_span.set_attribute("wandb.thread_id", thread_id)
            # 중첩 호출에는 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()
def example_2_multiple_turns():
    """예제 2: 하나의 스레드에서 여러 턴"""
    print("\n=== 예제 2: 스레드 내 여러 턴 ===")

    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"스레드에서 턴 1 완료: {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"스레드에서 턴 2 완료: {thread_id}")

def main():
    example_2_multiple_turns()

if __name__ == "__main__":
    main()
def example_3_complex_nested_structure():
    """Example 3: Complex nested structure with multiple levels"""
    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()
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()
이러한 트레이스를 전송한 후에는 Weave UI의 Threads 탭에서 확인할 수 있으며, thread_id별로 그룹화되고 각 턴은 개별 행으로 표시됩니다.

속성 매핑

Weave는 다양한 계측 프레임워크에서 생성된 OpenTelemetry span 속성을 내부 데이터 모델에 자동으로 매핑합니다. 여러 속성 이름이 동일한 필드에 매핑되는 경우 Weave는 우선순위에 따라 이를 적용하여, 여러 프레임워크가 동일한 트레이스에서 함께 동작할 수 있도록 합니다.

지원되는 프레임워크

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)
  • Langfuse: Langfuse 트레이싱 속성 (langfuse.startTime, langfuse.endTime)

속성 레퍼런스

속성 필드명W&B 매핑설명타입예시
ai.promptinputs사용자 프롬프트 텍스트나 메시지입니다.문자열, 리스트, 딕셔너리"여름에 대한 짧은 하이쿠를 작성하세요."
gen_ai.promptinputsAI 모델 프롬프트 또는 메시지 배열입니다.리스트, 딕셔너리, 문자열[{"role":"user","content":"abc"}]
input.valueinputs모델 호출에 사용되는 입력값입니다.문자열, 리스트, 딕셔너리{"text":"농담 하나 해 줘"}
mlflow.spanInputsinputs스팬 입력 데이터입니다.문자열, 리스트, 딕셔너리["prompt text"]
traceloop.entity.inputinputs엔터티 입력 데이터입니다.문자열, 리스트, 딕셔너리"이것을 프랑스어로 번역해 줘"
gcp.vertex.agent.tool_call_argsinputs툴 호출 인자입니다.딕셔너리{"args":{"query":"weather in SF"}}
gcp.vertex.agent.llm_requestinputsLLM 요청 페이로드입니다.딕셔너리{"contents":[{"role":"user","parts":[...]}]}
inputinputs일반 입력값입니다.문자열, 리스트, 딕셔너리"이 텍스트를 요약하세요"
inputsinputs일반 입력 배열입니다.리스트, 딕셔너리, 문자열["Summarize this text"]
ai.responseoutputs모델 응답 텍스트나 데이터입니다.문자열, 리스트, 딕셔너리"하이쿠가 여기 있습니다..."
gen_ai.completionoutputsAI 컴플리션 결과입니다.문자열, 리스트, 딕셔너리"완성된 텍스트"
output.valueoutputs모델에서 생성된 출력값입니다.문자열, 리스트, 딕셔너리{"text":"Answer text"}
mlflow.spanOutputsoutputs스팬 출력 데이터입니다.문자열, 리스트, 딕셔너리["answer"]
gen_ai.content.completionoutputs콘텐츠 컴플리션 결과입니다.문자열"답변 내용"
traceloop.entity.outputoutputs엔터티 출력 데이터입니다.문자열, 리스트, 딕셔너리"Answer text"
gcp.vertex.agent.tool_responseoutputs툴 실행 응답입니다.딕셔너리, 문자열{"toolResponse":"ok"}
gcp.vertex.agent.llm_responseoutputsLLM 응답 페이로드입니다.딕셔너리, 문자열{"candidates":[...]}
outputoutputs일반 출력값입니다.문자열(String), 리스트(list), 딕셔너리(dict)"답변 내용"
outputsoutputs일반 출력의 배열입니다.리스트(list), 딕셔너리(dict), 문자열(String)["Answer text"]
gen_ai.usage.input_tokensusage.input_tokens소비된 입력 토큰 수입니다.Int42
gen_ai.usage.prompt_tokensusage.prompt_tokens소비된 프롬프트 토큰 수입니다.Int30
llm.token_count.promptusage.prompt_tokens프롬프트 토큰 수입니다.Int30
ai.usage.promptTokensusage.prompt_tokens소비된 프롬프트 토큰 수입니다.Int30
gen_ai.usage.completion_tokensusage.completion_tokens생성된 컴플리션 토큰 수입니다.Int40
llm.token_count.completionusage.completion_tokens컴플리션 토큰 수입니다.Int40
ai.usage.completionTokensusage.completion_tokens생성된 컴플리션 토큰 수입니다.Int40
llm.usage.total_tokensusage.total_tokens요청에서 사용된 전체 토큰 수입니다.Int70
llm.token_count.totalusage.total_tokens전체 토큰 수입니다.Int70
gen_ai.systemattributes.system시스템 프롬프트 또는 지침입니다.String"당신은 도움이 되는 어시스턴트입니다."
llm.systemattributes.system시스템 프롬프트 또는 지침입니다.String"당신은 유용한 도우미입니다."
weave.span.kindattributes.kind스팬 유형 또는 범주입니다.String"llm"
traceloop.span.kindattributes.kind스팬 유형 또는 범주입니다.String"llm"
openinference.span.kindattributes.kind스팬 유형 또는 범주입니다.String"llm"
gen_ai.response.modelattributes.model모델 식별자입니다.String"gpt-4o"
llm.model_nameattributes.model모델 식별자입니다.String"gpt-4o-mini"
ai.model.idattributes.model모델 식별자입니다.String"gpt-4o"
llm.providerattributes.provider모델 제공자 이름입니다.String"openai"
ai.model.providerattributes.provider모델 제공자 이름입니다.String"openai"
gen_ai.requestattributes.model_parameters모델 생성 시 사용할 파라미터입니다.Dict{"temperature":0.7,"max_tokens":256}
llm.invocation_parametersattributes.model_parameters모델 호출 시 사용할 파라미터입니다.Dict{"temperature":0.2}
wandb.display_namedisplay_nameUI에 표시할 사용자 지정 이름입니다.String"사용자 메시지"
gcp.vertex.agent.session_idthread_id세션 또는 스레드 식별자입니다.String"thread_123"
wandb.thread_idthread_id대화 스레드 식별자입니다.String"thread_123"
wb_run_idwb_run_id연관된 W&B run 식별자입니다.String"abc123"
wandb.wb_run_idwb_run_id연관된 W&B run 식별자입니다.String"abc123"
gcp.vertex.agent.session_idis_turn이 스팬을 대화 턴으로 표시합니다.Booleantrue
wandb.is_turnis_turn이 스팬을 대화 턴으로 표시합니다.Booleantrue
langfuse.startTimestart_time (재정의)스팬 시작 타임스탬프를 수동으로 지정합니다.Timestamp (ISO8601/unix ns)"2024-01-01T12:00:00Z"
langfuse.endTimeend_time (override)스팬 종료 타임스탬프를 수동으로 지정합니다.Timestamp (ISO8601/unix ns)"2024-01-01T12:00:01Z"

제한 사항

  • Weave UI는 Chat view에서 OTel trace 도구 호출을 렌더링하는 기능을 지원하지 않습니다. 대신 원시 JSON 형태로 표시됩니다.