성능 권장 사항: Weave로 트레이스를 전송할 때는 항상 SimpleSpanProcessor 대신 BatchSpanProcessor를 사용하세요. SimpleSpanProcessor는 스팬을 동기식으로 내보내므로 다른 워크로드의 성능에 영향을 줄 수 있습니다. 이 예제들에서는 BatchSpanProcessor를 사용합니다. 이 프로세서는 스팬을 비동기적으로 효율적으로 일괄 처리하므로 프로덕션 환경에서 권장됩니다.
Python
TypeScript
다음 코드를 openinference_example.py와 같은 Python 파일에 붙여 넣으세요:
import osimport openaifrom opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporterfrom opentelemetry.sdk import trace as trace_sdkfrom opentelemetry.sdk.resources import Resourcefrom 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"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
이 예제의 TypeScript 구현은 Python 구현과 비교할 때 다음과 같은 핵심 차이가 있습니다:
계측을 등록하기 전에 OpenAI를 먼저 임포트해야 합니다(ESM 모듈에서는 이것이 필요합니다).
W&B의 엔드포인트는 protobuf만 허용하므로 HTTP exporter 대신 @opentelemetry/exporter-trace-otlp-proto(protobuf 형식)를 사용합니다.
BatchSpanProcessor는 비동기적으로 플러시되므로, 스팬이 확실히 플러시되도록 종료 전에 지연을 두고 provider.shutdown()을 명시적으로 호출해야 합니다.
다음 코드를 openinference_example.ts 같은 TypeScript 파일에 붙여 넣으세요:
// 중요: 계측이 패치할 수 있도록 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 OTEL_EXPORTER_OTLP_ENDPOINT = `${WANDB_BASE_URL}/otel/v1/traces`;// https://wandb.ai/settings 에서 API 키를 생성하세요const WANDB_API_KEY = process.env.WANDB_API_KEY!;const exporter = new OTLPTraceExporter({ url: OTEL_EXPORTER_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();// tracer provider에 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 osimport openaifrom opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporterfrom opentelemetry.sdk import trace as trace_sdkfrom opentelemetry.sdk.resources import Resourcefrom 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"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
다음 코드를 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 { 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 OTEL_EXPORTER_OTLP_ENDPOINT = `${WANDB_BASE_URL}/otel/v1/traces`;// https://wandb.ai/settings 에서 API 키를 생성하세요const WANDB_API_KEY = process.env.WANDB_API_KEY!;const exporter = new OTLPTraceExporter({ url: OTEL_EXPORTER_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), // 선택 사항: 스팬을 콘솔에 출력합니다. 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 osimport openaifrom opentelemetry import tracefrom opentelemetry.sdk import trace as trace_sdkfrom opentelemetry.sdk.resources import Resourcefrom 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"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
다음 코드를 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";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 OTEL_EXPORTER_OTLP_ENDPOINT = `${WANDB_BASE_URL}/otel/v1/traces`;// https://wandb.ai/settings 에서 API 키를 생성하세요.const WANDB_API_KEY = process.env.WANDB_API_KEY!;const exporter = new OTLPTraceExporter({ url: OTEL_EXPORTER_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), // 선택 사항: 스팬을 콘솔에 출력합니다. 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();
코드를 실행하세요:
npx ts-node opentelemetry_example.ts
스팬 속성 접두사 gen_ai와 openinference는 트레이스를 해석할 때 어떤 규약을 사용할지, 또는 규약을 사용하지 않을지를 확인하는 데 사용됩니다. 두 키 중 어느 것도 감지되지 않으면 모든 스팬 속성이 트레이스 뷰에 표시됩니다. 트레이스를 선택하면 측면 패널에서 전체 스팬을 볼 수 있습니다.
위 예시에서는 애플리케이션에서 Weave로 트레이스를 직접 전송합니다. 프로덕션 환경에서는 애플리케이션과 Weave 사이의 중간 계층으로 OpenTelemetry Collector를 사용할 수 있습니다. Collector는 앱에서 트레이스를 수신한 다음 하나 이상의 백엔드로 전달합니다.
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 action은 애플리케이션 코드에서 이러한 속성을 이미 설정하지 않은 경우에만 속성을 주입합니다.
네트워크 오버헤드를 줄이기 위해 exporter에 내장된 sending_queue를 배치 처리와 함께 활성화합니다.
collector 설정을 완료한 후, 다음 Docker command에서 API 및 entity 값을 업데이트한 뒤 실행합니다:
collector가 실행되면 OTEL_EXPORTER_OTLP_ENDPOINT 환경 변수를 설정해 애플리케이션이 해당 엔드포인트로 트레이스를 내보내도록 구성하세요. OTel SDK는 이 변수를 자동으로 읽으므로 익스포터에 엔드포인트를 전달할 필요가 없습니다.애플리케이션의 TracerProvider에서 wandb.entity 또는 wandb.project를 리소스 속성으로 설정하면 collector 설정에 정의된 기본값보다 우선합니다.
OpenAIInstrumentor는 OpenAI 호출을 자동으로 래핑하고 트레이스를 생성한 뒤 collector로 내보냅니다. collector는 인증과 Weave로의 라우팅을 처리합니다.스크립트를 실행한 후에는 Weave UI에서 트레이스를 확인할 수 있습니다.트레이스를 추가 백엔드로 전송하려면 exporter를 더 추가하고 service.pipelines.traces.exporters 목록에 포함하세요. 예를 들어 동일한 Collector 인스턴스에서 Weave와 Jaeger 둘 다로 내보낼 수 있습니다.
특정 스팬 속성을 추가해 OpenTelemetry 트레이스를 Weave 스레드로 정리한 다음, Weave의 Thread UI를 사용해 멀티턴 대화나 사용자 세션과 같은 관련 오퍼레이션을 분석할 수 있습니다.스레드 그룹화를 사용하려면 OTel 스팬에 다음 속성을 추가하세요:
wandb.thread_id: 스팬을 특정 스레드로 그룹화합니다
wandb.is_turn: 스팬을 대화 턴으로 표시합니다(thread view에서 행으로 표시됨)
다음 예제는 OTel 트레이스를 Weave 스레드로 정리하는 방법을 보여줍니다. wandb.thread_id를 사용해 관련 오퍼레이션을 그룹화하고, wandb.is_turn을 사용해 thread view에서 행으로 표시되는 상위 수준 오퍼레이션을 표시합니다.
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("오류: WANDB_API_KEY 환경 변수가 설정되지 않았습니다"); console.error("실행: 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 exporter = new OTLPTraceExporter({ url: OTEL_EXPORTER_OTLP_ENDPOINT, headers: { "wandb-api-key": WANDB_API_KEY },});// 스팬 processor로 tracer provider 초기화const provider = new NodeTracerProvider({ resource: new Resource({ "wandb.entity": ENTITY, "wandb.project": PROJECT, }), 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" # 이 스팬은 턴을 나타냅니다(스레드의 직접 하위 스팬). 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") # 중첩된 스팬으로 일부 작업 시뮬레이션 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()
function example_1_basic_thread_and_turn() { console.log("\n=== Example 1: Basic Thread and Turn ==="); // 스레드 컨텍스트 생성 const threadId = "thread_example_1"; // 이 스팬은 턴을 나타냅니다(스레드의 직접 하위 스팬). 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; // 중첩된 스팬으로 일부 작업 시뮬레이션 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();
하나의 스레드 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) # 중첩 스팬에는 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) # 중첩 스팬에는 명시적으로 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); // 중첩 스팬에는 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); // 중첩 스팬에는 명시적으로 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();
여러 단계로 깊게 중첩된 오퍼레이션을 트레이스하고 최외곽 스팬만 턴으로 표시
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" # 여러 단계로 중첩된 턴 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()
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();
스레드에 속하지만 턴은 아닌 백그라운드 오퍼레이션 추적
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 스팬 속성을 내부 데이터 모델에 자동으로 매핑합니다. 여러 속성 이름이 동일한 필드에 매핑될 경우, Weave는 이를 우선순위에 따라 적용하므로 동일한 트레이스 내에서 여러 프레임워크를 함께 사용할 수 있습니다.