성능 권장 사항: Weave로 trace를 전송할 때는 항상 SimpleSpanProcessor 대신 BatchSpanProcessor를 사용하십시오. SimpleSpanProcessor는 span을 동기적으로 내보내므로 다른 워크로드의 성능에 영향을 줄 수 있습니다. 이 예제에서는 BatchSpanProcessor 사용 방법을 보여주며, 이는 span을 비동기적으로 효율적으로 배치 처리하므로 운영 환경에서 사용을 권장합니다.
Python
TypeScript
다음 코드를 openinference_example.py와 같은 Python 파일에 붙여넣습니다:
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 key를 생성하세요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 구성exporter = OTLPSpanExporter( endpoint=OTEL_EXPORTER_OTLP_ENDPOINT, headers=OTEL_EXPORTER_OTLP_HEADERS,)# tracer provider에 exporter 추가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
이 예제의 TypeScript 구현은 Python 구현과 비교했을 때 다음과 같은 주요 차이점이 있습니다:
ESM 모듈에서는 OpenAI를 계측을 등록하기 전에 먼저 import해야 합니다.
W&B의 엔드포인트는 protobuf만 허용하므로 HTTP exporter 대신 @opentelemetry/exporter-trace-otlp-proto(protobuf 형식)를 사용합니다.
BatchSpanProcessor가 비동기적으로 flush하므로 span이 모두 flush되도록 종료 전에 지연 시간을 두고 명시적으로 provider.shutdown()을 호출해야 합니다.
다음 코드를 openinference_example.ts와 같은 TypeScript 파일에 붙여넣으세요:
// 중요: 계측이 패치할 수 있도록 OpenAI를 먼저 임포트하세요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 { OpenAIInstrumentation, isPatched } from "@arizeai/openinference-instrumentation-openai";import { trace } from "@opentelemetry/api";const OPENAI_API_KEY = process.env.OPENAI_API_KEY;const WANDB_BASE_URL = "https://trace.wandb.ai";const PROJECT_ID = "dans-test-team/otel-test-python";const OTEL_EXPORTER_OTLP_ENDPOINT = `${WANDB_BASE_URL}/otel/v1/traces`;// https://wandb.ai/settings 에서 API key를 생성하세요const WANDB_API_KEY = process.env.WANDB_API_KEY!;const AUTH = Buffer.from(`api:${WANDB_API_KEY}`).toString("base64");const OTEL_EXPORTER_OTLP_HEADERS = { Authorization: `Basic ${AUTH}`, project_id: PROJECT_ID,};// OTLP 익스포터 구성const exporter = new OTLPTraceExporter({ url: OTEL_EXPORTER_OTLP_ENDPOINT, headers: OTEL_EXPORTER_OTLP_HEADERS,});const provider = new NodeTracerProvider({ 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가 패치되었나요?", isPatched()); const client = new OpenAI({ apiKey: OPENAI_API_KEY }); // 계측 테스트를 위해 먼저 비스트리밍 방식 사용 console.log("OpenAI API 호출 중..."); 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.choices[0]?.message?.content); console.log("스팬 플러시 대기 중...");}(async () => { await main(); // 스팬 플러시를 위한 대기 시간 부여 console.log("스팬 플러시를 위해 2초 대기 중..."); await new Promise(resolve => setTimeout(resolve, 2000)); await provider.shutdown(); // 종료 전 보류 중인 모든 스팬 플러시 console.log("종료 완료");})();
다음 코드를 openllmetry_example.py와 같은 Python 파일에 붙여넣습니다. 이 코드는 위의 코드와 동일하지만, OpenAIInstrumentor를 openinference.instrumentation.openai가 아니라 opentelemetry.instrumentation.openai에서 import 한다는 점만 다릅니다.
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 key를 생성하세요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 구성exporter = OTLPSpanExporter( endpoint=OTEL_EXPORTER_OTLP_ENDPOINT, headers=OTEL_EXPORTER_OTLP_HEADERS,)# tracer provider에 exporter 추가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()
코드를 실행하세요:
python openllmetry_example.py
다음 코드를 openllmetry_example.ts와 같은 TypeScript 파일에 붙여넣으세요. 이 코드는 Traceloop OpenAI 계측 패키지를 사용합니다:
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 { 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 PROJECT_ID = "dans-test-team/otel-test-python";const OTEL_EXPORTER_OTLP_ENDPOINT = `${WANDB_BASE_URL}/otel/v1/traces`;// https://wandb.ai/settings 에서 API key를 생성하세요const WANDB_API_KEY = process.env.WANDB_API_KEY!;const AUTH = Buffer.from(`api:${WANDB_API_KEY}`).toString("base64");const OTEL_EXPORTER_OTLP_HEADERS = { Authorization: `Basic ${AUTH}`, project_id: PROJECT_ID,};// OTLP 내보내기 도구 구성const exporter = new OTLPTraceExporter({ url: OTEL_EXPORTER_OTLP_ENDPOINT, headers: OTEL_EXPORTER_OTLP_HEADERS,});const provider = new NodeTracerProvider({ spanProcessors: [ new BatchSpanProcessor(exporter), // 선택 사항: 스팬을 콘솔에 출력합니다. 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(); // 스팬 플러시 대기 await new Promise(resolve => setTimeout(resolve, 2000)); await provider.shutdown(); // 종료 전 대기 중인 모든 스팬을 플러시합니다})();
opentelemetry_example.py와 같은 Python 파일에 다음 코드를 붙여넣으세요:
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 key를 생성하세요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.add_span_processor(BatchSpanProcessor(exporter))# 선택 사항: 스팬을 콘솔에 출력합니다.tracer_provider.add_span_processor(BatchSpanProcessor(ConsoleSpanExporter()))trace.set_tracer_provider(tracer_provider)# 전역 트레이서 프로바이더에서 트레이서를 생성합니다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()
코드를 실행하세요:
python opentelemetry_example.py
다음 코드를 opentelemetry_example.ts 같은 TypeScript 파일에 붙여넣으세요:
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";const OPENAI_API_KEY = "YOUR_OPENAI_API_KEY";const WANDB_BASE_URL = "https://trace.wandb.ai";const PROJECT_ID = "<your-entity>/<your-project>";const OTEL_EXPORTER_OTLP_ENDPOINT = `${WANDB_BASE_URL}/otel/v1/traces`;// https://wandb.ai/settings 에서 API key를 생성하세요const WANDB_API_KEY = "<your-wandb-api-key>";const AUTH = Buffer.from(`api:${WANDB_API_KEY}`).toString("base64");const OTEL_EXPORTER_OTLP_HEADERS = { Authorization: `Basic ${AUTH}`, project_id: PROJECT_ID,};const provider = new NodeTracerProvider();// OTLP exporter 구성const exporter = new OTLPTraceExporter({ url: OTEL_EXPORTER_OTLP_ENDPOINT, headers: OTEL_EXPORTER_OTLP_HEADERS,});// tracer provider에 exporter 추가provider.addSpanProcessor(new BatchSpanProcessor(exporter));// 선택 사항: span을 콘솔에 출력합니다.provider.addSpanProcessor(new BatchSpanProcessor(new ConsoleSpanExporter()));provider.register();// 전역 tracer provider에서 tracer를 생성합니다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();
코드를 실행하세요:
npx ts-node opentelemetry_example.ts
스팬 속성의 접두사인 gen_ai 및 openinference는 트레이스를 해석할 때, 적용할 규약이 있을 경우 어떤 규약을 사용할지 결정하는 데 사용됩니다. 두 접두사 모두 감지되지 않으면 모든 스팬 속성이 트레이스 뷰에 표시됩니다. 트레이스를 선택하면 전체 스팬을 사이드 패널에서 확인할 수 있습니다.
특정 span 속성을 추가하여 OpenTelemetry trace를 Weave 스레드로 구성한 다음, Weave의 Thread UI를 사용해 멀티턴 대화나 사용자 세션과 같이 연관된 작업을 분석합니다.다음 속성을 OTEL span에 추가하여 스레드 단위 그룹화를 활성화합니다:
wandb.thread_id: span을 특정 스레드로 그룹화합니다
wandb.is_turn: span을 대화 턴으로 표시합니다(스레드 뷰에서 하나의 행으로 나타납니다)
다음 예제는 OTEL trace를 Weave 스레드로 구성하는 방법을 보여줍니다. 여기서는 wandb.thread_id를 사용해 연관된 작업을 그룹화하고, wandb.is_turn을 사용해 스레드 뷰에서 행으로 나타나는 상위 수준 작업을 표시합니다.
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";// 구성 - 아래 값을 본인의 W&B 엔터티 및 프로젝트 이름으로 수정하세요const ENTITY = "dans-test-team";const PROJECT = "otel-test-typescript";const PROJECT_ID = `${ENTITY}/${PROJECT}`;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 OTEL_EXPORTER_OTLP_ENDPOINT = "https://trace.wandb.ai/otel/v1/traces";const AUTH = Buffer.from(`api:${WANDB_API_KEY}`).toString("base64");const OTEL_EXPORTER_OTLP_HEADERS = { Authorization: `Basic ${AUTH}`, project_id: PROJECT_ID,};// OTLP exporter 구성const exporter = new OTLPTraceExporter({ url: OTEL_EXPORTER_OTLP_ENDPOINT, headers: OTEL_EXPORTER_OTLP_HEADERS,});// span processor와 함께 tracer provider 초기화const provider = new NodeTracerProvider({ spanProcessors: [ new BatchSpanProcessor(exporter), new BatchSpanProcessor(new ConsoleSpanExporter()), ],});// tracer provider 등록provider.register();// 전역 tracer provider에서 tracer 생성const tracer = trace.getTracer("threads-examples");
기본 단일 턴 스레드 추적
Python
TypeScript
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()
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) => { // 이는 턴 내부의 중첩 호출이므로 is_turn은 false이거나 설정되지 않아야 합니다 nestedSpan.setAttribute("wandb.thread_id", threadId); // 중첩 호출에서는 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();
여러 턴으로 구성된 대화를 하나의 thread ID로 추적하기
Python
TypeScript
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 attribute를 설정하지 않거나 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()
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 attribute를 설정하지 않거나 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();
깊이 중첩된 operation을 추적하고 가장 바깥쪽 span만 turn으로 표시하기
Python
TypeScript
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" # 여러 단계로 중첩된 turn 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단계 중첩된 operation with tracer.start_as_current_span("code_analysis") as analysis_span: analysis_span.set_attribute("wandb.thread_id", thread_id) # 중첩된 operation에는 is_turn을 사용하지 않음 # 2단계 중첩된 operation 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단계 중첩된 operation 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단계 중첩된 operation 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()
function example_3_complex_nested_structure() { console.log("\n=== Example 3: Complex Nested Structure ==="); const threadId = "thread_complex_456"; // 여러 단계로 중첩된 turn 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단계 중첩된 operation tracer.startActiveSpan("code_analysis", (analysisSpan) => { analysisSpan.setAttribute("wandb.thread_id", threadId); // 중첩된 operation에는 is_turn을 사용하지 않음 // 2단계 중첩된 operation tracer.startActiveSpan("syntax_check", (syntaxSpan) => { syntaxSpan.setAttribute("wandb.thread_id", threadId); syntaxSpan.setAttribute("result", "No syntax errors found"); syntaxSpan.end(); }); // 또 다른 2단계 중첩된 operation tracer.startActiveSpan("performance_check", (perfSpan) => { perfSpan.setAttribute("wandb.thread_id", threadId); perfSpan.setAttribute("result", "Found 2 optimization opportunities"); perfSpan.end(); }); analysisSpan.end(); }); // 또 다른 1단계 중첩된 operation 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();
스레드에 속하지만 턴은 아닌 백그라운드 작업 추적
Python
TypeScript
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()
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();
이러한 트레이스를 전송한 후에는 Weave UI의 Threads 탭에서 확인할 수 있습니다. 여기에서 thread_id별로 그룹화되고, 각 턴은 별도의 행으로 표시됩니다.
Weave는 다양한 계측 프레임워크의 OpenTelemetry span 속성을 자동으로 내부 데이터 모델에 매핑합니다. 여러 속성 이름이 동일한 필드에 매핑되는 경우, Weave는 우선순위에 따라 적용하여 서로 다른 프레임워크가 동일한 트레이스에서 공존할 수 있도록 합니다.