성능 권장 사항: Weave에 trace를 보낼 때는 항상 SimpleSpanProcessor 대신 BatchSpanProcessor를 사용하세요. SimpleSpanProcessor는 span을 동기적으로 내보내므로 다른 워크로드의 성능에 영향을 줄 수 있습니다. 이 예시들은 span을 비동기적이고 효율적으로 배치 처리하므로 프로덕션에서 권장되는 BatchSpanProcessor를 사용합니다.
다음으로, 아래 코드를 openinference_example.py와 같은 파이썬 파일에 붙여넣습니다.
잘못된 코드 신고
복사
AI에게 묻기
import base64import openaifrom opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporterfrom opentelemetry.sdk import trace as trace_sdkfrom opentelemetry.sdk.trace.export import ConsoleSpanExporter, BatchSpanProcessorfrom openinference.instrumentation.openai import OpenAIInstrumentorOPENAI_API_KEY="YOUR_OPENAI_API_KEY"WANDB_BASE_URL = "https://trace.wandb.ai"PROJECT_ID = "<your-entity>/<your-project>"OTEL_EXPORTER_OTLP_ENDPOINT = f"{WANDB_BASE_URL}/otel/v1/traces"# https://wandb.ai/settings 에서 API 키를 생성하세요WANDB_API_KEY = "<your-wandb-api-key>"AUTH = base64.b64encode(f"api:{WANDB_API_KEY}".encode()).decode()OTEL_EXPORTER_OTLP_HEADERS = { "Authorization": f"Basic {AUTH}", "project_id": PROJECT_ID,}tracer_provider = trace_sdk.TracerProvider()# OTLP 엑스포터 구성exporter = OTLPSpanExporter( endpoint=OTEL_EXPORTER_OTLP_ENDPOINT, headers=OTEL_EXPORTER_OTLP_HEADERS,)# Tracer provider에 엑스포터 추가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()
다음으로, 아래 코드를 openllmetry_example.py와 같은 파이썬 파일에 붙여넣습니다. 이 코드는 OpenAIInstrumentor를 openinference.instrumentation.openai 대신 opentelemetry.instrumentation.openai에서 가져온다는 점을 제외하면 위와 동일합니다.
잘못된 코드 신고
복사
AI에게 묻기
import base64import openaifrom opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporterfrom opentelemetry.sdk import trace as trace_sdkfrom opentelemetry.sdk.trace.export import ConsoleSpanExporter, BatchSpanProcessorfrom opentelemetry.instrumentation.openai import OpenAIInstrumentorOPENAI_API_KEY="YOUR_OPENAI_API_KEY"WANDB_BASE_URL = "https://trace.wandb.ai"PROJECT_ID = "<your-entity>/<your-project>"OTEL_EXPORTER_OTLP_ENDPOINT = f"{WANDB_BASE_URL}/otel/v1/traces"# https://wandb.ai/settings 에서 API 키를 생성하세요WANDB_API_KEY = "<your-wandb-api-key>"AUTH = base64.b64encode(f"api:{WANDB_API_KEY}".encode()).decode()OTEL_EXPORTER_OTLP_HEADERS = { "Authorization": f"Basic {AUTH}", "project_id": PROJECT_ID,}tracer_provider = trace_sdk.TracerProvider()# OTLP 엑스포터 구성exporter = OTLPSpanExporter( endpoint=OTEL_EXPORTER_OTLP_ENDPOINT, headers=OTEL_EXPORTER_OTLP_HEADERS,)# Tracer provider에 엑스포터 추가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()
다음으로, 아래 코드를 opentelemetry_example.py와 같은 파이썬 파일에 붙여넣습니다.
잘못된 코드 신고
복사
AI에게 묻기
import jsonimport base64import openaifrom opentelemetry import tracefrom opentelemetry.sdk import trace as trace_sdkfrom opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporterfrom opentelemetry.sdk.trace.export import ConsoleSpanExporter, BatchSpanProcessorOPENAI_API_KEY = "YOUR_OPENAI_API_KEY"WANDB_BASE_URL = "https://trace.wandb.ai"PROJECT_ID = "<your-entity>/<your-project>"OTEL_EXPORTER_OTLP_ENDPOINT = f"{WANDB_BASE_URL}/otel/v1/traces"# https://wandb.ai/settings 에서 API 키를 생성하세요WANDB_API_KEY = "<your-wandb-api-key>"AUTH = base64.b64encode(f"api:{WANDB_API_KEY}".encode()).decode()OTEL_EXPORTER_OTLP_HEADERS = { "Authorization": f"Basic {AUTH}", "project_id": PROJECT_ID,}tracer_provider = trace_sdk.TracerProvider()# OTLP 엑스포터 구성exporter = OTLPSpanExporter( endpoint=OTEL_EXPORTER_OTLP_ENDPOINT, headers=OTEL_EXPORTER_OTLP_HEADERS,)# Tracer provider에 엑스포터 추가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__)tracer.start_span('name=standard-span')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()
설정 후 코드를 실행합니다:
잘못된 코드 신고
복사
AI에게 묻기
python opentelemetry_example.py
Span 속성 접두사인 gen_ai 및 openinference는 trace를 해석할 때 어떤 규칙을 사용할지 결정하는 데 사용됩니다. 어떤 키도 감지되지 않으면 모든 span 속성이 trace 뷰에 표시됩니다. trace를 선택하면 사이드 패널에서 전체 span을 확인할 수 있습니다.
특정 span 속성을 추가하여 OpenTelemetry trace를 Weave threads로 구성한 다음, Weave의 Thread UI를 사용하여 멀티-턴 대화 또는 사용자 세션과 같은 관련 작업을 분석할 수 있습니다.thread 그룹화를 활성화하려면 OTEL span에 다음 속성을 추가하세요:
wandb.thread_id: span을 특정 thread로 그룹화합니다.
wandb.is_turn: span을 대화 턴으로 표시합니다 (thread 뷰에서 행으로 나타납니다).
다음 코드는 OTEL trace를 Weave threads로 구성하는 여러 예시를 보여줍니다. 관련 작업을 그룹화하기 위해 wandb.thread_id를 사용하고, 상위 수준 작업을 thread 뷰의 행으로 보기 위해 wandb.is_turn을 사용합니다.
def example_1_basic_thread_and_turn(): """예시 1: 단일 턴을 가진 기본 thread""" print("\n=== 예시 1: 기본 Thread와 턴 ===") # thread 컨텍스트 생성 thread_id = "thread_example_1" # 이 span은 턴을 나타냄 (thread의 직계 자식) with tracer.start_as_current_span("process_user_message") as turn_span: # thread 속성 설정 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"Thread에서 턴 완료: {thread_id}")def main(): example_1_basic_thread_and_turn()if __name__ == "__main__": main()
하나의 thread ID를 공유하는 멀티 턴 대화 추적
잘못된 코드 신고
복사
AI에게 묻기
def example_2_multiple_turns(): """예시 2: 단일 thread 내의 여러 턴""" print("\n=== 예시 2: 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"Thread에서 턴 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) # 중첩 작업에 대해 명시적 False response2 = "Python excels at data science while JavaScript dominates web development." turn2_span.set_attribute("output.value", response2) print(f"Thread에서 턴 2 완료: {thread_id}")def main(): example_2_multiple_turns()if __name__ == "__main__": main()
깊게 중첩된 작업을 추적하고 가장 바깥쪽 span만 턴으로 표시
잘못된 코드 신고
복사
AI에게 묻기
def example_3_complex_nested_structure(): """예시 3: 여러 레벨의 복잡한 중첩 구조""" print("\n=== 예시 3: 복잡한 중첩 구조 ===") 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"Thread에서 복잡한 턴 완료: {thread_id}")def main(): example_3_complex_nested_structure()if __name__ == "__main__": main()
thread에 속하지만 턴이 아닌 백그라운드 작업 추적
잘못된 코드 신고
복사
AI에게 묻기
def example_4_non_turn_operations(): """예시 4: thread의 일부이지만 턴은 아닌 작업""" print("\n=== 예시 4: 턴이 아닌 Thread 작업 ===") thread_id = "thread_background_789" # thread의 일부이지만 턴은 아닌 백그라운드 작업 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"Thread 내 백그라운드 작업: {thread_id}") # 동일한 thread의 실제 턴 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"Thread에서 턴 완료: {thread_id}")def main(): example_4_non_turn_operations()if __name__ == "__main__": main()
이러한 trace를 전송한 후 Weave UI의 Threads 탭에서 확인할 수 있으며, 여기서 thread_id별로 그룹화되고 각 턴은 별개의 행으로 표시됩니다.
Weave는 다양한 인스트루먼테이션 프레임워크의 OpenTelemetry span 속성을 내부 데이터 모델로 자동 매핑합니다. 여러 속성 이름이 동일한 필드에 매핑될 경우 Weave는 우선순위에 따라 적용하여 동일한 trace 내에서 프레임워크가 공존할 수 있도록 합니다.