특정 프로젝트로 트레이스 기록하기

특정 프로젝트로 트레이스 기록하기

환경 변수나 SDK를 사용해 LangSmith 트레이스를 기본 프로젝트 대신 이름이 지정된 프로젝트로 라우팅하는 방법을 알려드릴게요.

이 페이지는 LangSmith가 트레이스를 보내는 위치를 제어하는 방법을 다룹니다:

목적지 프로젝트를 정적으로 설정하기

LangSmith는 트레이스를 그룹화하기 위해 프로젝트 개념을 사용합니다. 지정하지 않으면 프로젝트는 default로 설정됩니다.

LANGSMITH_PROJECT 환경 변수를 설정해 전체 애플리케이션 실행에 대한 사용자 지정 프로젝트 이름을 구성할 수 있습니다. 애플리케이션을 실행하기 전에 설정하세요:

export LANGSMITH_PROJECT=my-custom-project
`LANGSMITH_PROJECT` 플래그는 JS SDK 0.2.16 이상에서만 지원됩니다. 더 오래된 버전을 사용한다면 대신 `LANGCHAIN_PROJECT`를 사용하세요.

지정한 프로젝트가 존재하지 않으면 첫 번째 트레이스가 수집될 때 LangSmith가 자동으로 프로젝트를 생성합니다.

목적지 프로젝트를 동적으로 설정하기

코드를 트레이싱용으로 주석 처리하는 방식에 따라 프로그램 런타임에 프로젝트 이름을 다양한 방법으로 설정할 수도 있습니다. 같은 애플리케이션에서 서로 다른 프로젝트로 트레이스를 기록하고 싶을 때 유용합니다:

  • 데코레이션 또는 구성 시점에 프로젝트 이름을 전달합니다.
  • 개별 호출마다 재정의합니다.
  • 런을 직접 구성할 때 설정합니다.
아래 방법 중 하나를 사용해 프로젝트 이름을 동적으로 설정하면 `LANGSMITH_PROJECT` 환경 변수로 설정한 프로젝트 이름을 재정의합니다. ```python Python import openai from langsmith import traceable from langsmith.run_trees import RunTree

client = openai.Client() messages = [ {"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": "Hello!"} ]

Use the @traceable decorator with the 'project_name' parameter to log traces to LangSmith

Ensure that the LANGSMITH_TRACING environment variables is set for @traceable to work

@traceable( run_type="llm", name="OpenAI Call Decorator", project_name="My Project" ) def call_openai( messages: list[dict], model: str = "gpt-5.4-mini" ) -> str: return client.chat.completions.create( model=model, messages=messages, ).choices[0].message.content

Call the decorated function

call_openai(messages)

You can also specify the Project via the project_name parameter

This will override the project_name specified in the @traceable decorator

call_openai( messages, langsmith_extra={"project_name": "My Overridden Project"}, )

The wrapped OpenAI client accepts all the same langsmith_extra parameters

as @traceable decorated functions, and logs traces to LangSmith automatically.

Ensure that the LANGSMITH_TRACING environment variables is set for the wrapper to work.

from langsmith import wrappers wrapped_client = wrappers.wrap_openai(client) wrapped_client.chat.completions.create( model="gpt-5.4-mini", messages=messages, langsmith_extra={"project_name": "My Project"}, )

Alternatively, create a RunTree object

You can set the project name using the project_name parameter

rt = RunTree( run_type="llm", name="OpenAI Call RunTree", inputs={"messages": messages}, project_name="My Project" ) chat_completion = client.chat.completions.create( model="gpt-5.4-mini", messages=messages, )

End and submit the run

rt.end(outputs=chat_completion) rt.post()


```typescript TypeScript
import OpenAI from "openai";
import { traceable } from "langsmith/traceable";
import { wrapOpenAI } from "langsmith/wrappers";
import { RunTree} from "langsmith";

const client = new OpenAI();
const messages = [
  {role: "system", content: "You are a helpful assistant."},
  {role: "user", content: "Hello!"}
];

const traceableCallOpenAI = traceable(async (messages: {role: string, content: string}[], model: string) => {
  const completion = await client.chat.completions.create({
      model: model,
      messages: messages,
  });
  return completion.choices[0].message.content;
},{
  run_type: "llm",
  name: "OpenAI Call Traceable",
  project_name: "My Project"
});

// Call the traceable function
await traceableCallOpenAI(messages, "gpt-5.4-mini");

// Create and use a RunTree object
const rt = new RunTree({
  run_type: "llm",
  name: "OpenAI Call RunTree",
  inputs: { messages },
  project_name: "My Project"
});
await rt.postRun();

// Execute a chat completion and handle it within RunTree
rt.end({outputs: chatCompletion});
await rt.patchRun();
```typescript

```java Java
import com.langchain.smith.otel.OtelConfig;
import com.langchain.smith.otel.OtelSpanCreator;
import com.langchain.smith.otel.OtelTraceExporter;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.StatusCode;
import io.opentelemetry.api.trace.Tracer;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;

/**
 * Simple example: Send a single OpenTelemetry trace to LangSmith.
 *
 * Usage:
 *   export LANGSMITH_API_KEY=your_api_key
 *   export LANGSMITH_PROJECT=your_project_name  # Optional, defaults to "default"
 */
public class OtelLangSmithSimpleExample {
    public static void main(String[] args) throws Exception {
        // Get API key and project name
        String apiKey = System.getenv("LANGSMITH_API_KEY");
        if (apiKey == null || apiKey.isEmpty()) {
            System.err.println("ERROR: LANGSMITH_API_KEY environment variable is required!");
            return;
        }

        String projectName = System.getenv("LANGSMITH_PROJECT");
        if (projectName == null || projectName.isEmpty()) {
            projectName = "default";
        }

        // Configure exporter
        Map<String, String> headers = new HashMap<>();
        headers.put("x-api-key", apiKey);
        headers.put("Langsmith-Project", projectName);

        OtelConfig config = OtelConfig.builder()
                .enabled(true)
                .endpoint("https://api.smith.langchain.com/otel/v1/traces")
                .headers(headers)
                .timeout(Duration.ofSeconds(30))
                .serviceName("langsmith-java-simple")
                .build();

        OtelTraceExporter exporter = OtelTraceExporter.fromConfig(config);
        Tracer tracer = exporter.getTracer();

        // Create a simple span
        Span span = OtelSpanCreator.createLlmSpan(
                tracer, "simple.llm.call", "openai", "gpt-4", projectName, null);

        try {
            OtelSpanCreator.setInput(span, "Hello, world!");
            Thread.sleep(100); // Simulate processing
            OtelSpanCreator.setOutput(span, "Hello! How can I help you?");
            OtelSpanCreator.setTokenUsage(span, 5, 8);
            span.setStatus(StatusCode.OK);
        } finally {
            span.end();
        }

        // Flush and shutdown
        exporter.flush().join(5, java.util.concurrent.TimeUnit.SECONDS);
        exporter.shutdown().join(2, java.util.concurrent.TimeUnit.SECONDS);

        System.out.println("✓ Trace sent to LangSmith!");
    }
}
```java
</CodeGroup>

## 목적지 워크스페이스를 동적으로 설정하기

런타임 구성에 따라 트레이스를 서로 다른 LangSmith [워크스페이스](/langsmith/administration-overview#workspaces)로 동적으로 라우팅해야 한다면(예: 서로 다른 사용자나 테넌트를 별도 워크스페이스로 라우팅), 그 방법은 언어에 따라 다릅니다:

* **Python**: [`tracing_context`](/langsmith/annotate-code#use-the-trace-context-manager-python-only)와 함께 워크스페이스별 LangSmith 클라이언트를 사용합니다.
* **TypeScript**: [`traceable`](/langsmith/annotate-code#use-%40traceable-%2F-traceable)에 사용자 지정 클라이언트를 전달하거나, 콜백과 함께 `LangChainTracer`를 사용합니다.

이 접근 방식은 고객, 환경, 또는 팀별로 워크스페이스 수준에서 트레이스를 격리하려는 멀티테넌트 애플리케이션에 유용합니다. LangChain, OpenAI, `@traceable`로 데코레이션된 사용자 지정 함수를 포함한 모든 LangSmith 호환 트레이싱에서 작동합니다.

### 사전 요구사항

* 여러 워크스페이스에 접근할 수 있는 [LangSmith API 키](/langsmith/create-account-api-key).
* 각 대상 워크스페이스의 [워크스페이스 ID](/langsmith/set-up-hierarchy#set-up-a-workspace).

### 일반적인 크로스-워크스페이스 트레이싱

런타임 로직(예: 고객 ID, 테넌트, 또는 환경)에 따라 트레이스를 서로 다른 워크스페이스로 동적으로 라우팅하려는 일반 애플리케이션에 이 접근 방식을 사용합니다.

**핵심 구성 요소:**

1. 각 워크스페이스에 해당 `workspace_id`로 별도의 `Client` 인스턴스를 초기화합니다.
2. `tracing_context`(Python)를 사용하거나 워크스페이스별 `client`를 `traceable`(TypeScript)에 전달해 트레이스를 라우팅합니다.
3. 애플리케이션의 런타임 구성으로 워크스페이스 구성을 전달합니다.
4. 각 라우트별로 워크스페이스와 프로젝트 이름을 모두 재정의해 각 워크스페이스 내에서 트레이스를 더 잘 구성합니다.

<CodeGroup>
```python Python
import os
import contextlib
from langsmith import Client, traceable, tracing_context

# API key with access to multiple workspaces
api_key = os.getenv("LS_CROSS_WORKSPACE_KEY")

# Initialize clients for different workspaces
workspace_a_client = Client(
    api_key=api_key,
    api_url="https://api.smith.langchain.com",
    workspace_id="<YOUR_WORKSPACE_A_ID>"  # e.g., "abc123..."
)

workspace_b_client = Client(
    api_key=api_key,
    api_url="https://api.smith.langchain.com",
    workspace_id="<YOUR_WORKSPACE_B_ID>"  # e.g., "def456..."
)

# Example: Route based on customer ID
def get_workspace_client(customer_id: str):
    """Route to appropriate workspace based on customer."""
    if customer_id.startswith("premium_"):
        return workspace_a_client, "premium-customer-traces"
    else:
        return workspace_b_client, "standard-customer-traces"

@traceable
def process_request(data: dict, customer_id: str):
    """Process a customer request with workspace-specific tracing."""
    # Your business logic here
    return {"status": "success", "data": data}

# Use tracing_context to route to the appropriate workspace
def handle_customer_request(customer_id: str, request_data: dict):
    client, project_name = get_workspace_client(customer_id)

    # Everything within this context will be traced to the selected workspace
    with tracing_context(enabled=True, client=client, project_name=project_name):
        result = process_request(request_data, customer_id)

    return result

# Example usage
handle_customer_request("premium_user_123", {"query": "Hello"})
handle_customer_request("standard_user_456", {"query": "Hi"})
```python

```typescript TypeScript
import { Client } from "langsmith";
import { traceable } from "langsmith/traceable";

// API key with access to multiple workspaces
const apiKey = process.env.LS_CROSS_WORKSPACE_KEY;

// Initialize clients for different workspaces
const workspaceAClient = new Client({
  apiKey: ***
  apiUrl: "https://api.smith.langchain.com",
  workspaceId: "<YOUR_WORKSPACE_A_ID>", // e.g., "abc123..."
});

const workspaceBClient = new Client({
  apiKey: ***
  apiUrl: "https://api.smith.langchain.com",
  workspaceId: "<YOUR_WORKSPACE_B_ID>", // e.g., "def456..."
});

// Example: Route based on customer ID
function getWorkspaceClient(customerId: string): {
  client: Client;
  projectName: string;
} {
  if (customerId.startsWith("premium_")) {
    return {
      client: workspaceAClient,
      projectName: "premium-customer-traces",
    };
  } else {
    return {
      client: workspaceBClient,
      projectName: "standard-customer-traces",
    };
  }
}

// Route traces to the appropriate workspace by passing the client to traceable
async function handleCustomerRequest(
  customerId: string,
  requestData: Record<string, any>
) {
  const { client, projectName } = getWorkspaceClient(customerId);

  // Create a traceable function with the workspace-specific client
  const processRequest = traceable(
    async (data: Record<string, any>, customerId: string) => {
      // Your business logic here
      return { status: "success", data };
    },
    {
      name: "process_request",
      client,
      project_name: projectName,
    }
  );

  return await processRequest(requestData, customerId);
}

// Example usage
await handleCustomerRequest("premium_user_123", { query: "Hello" });
await handleCustomerRequest("standard_user_456", { query: "Hi" });
```typescript
</CodeGroup>

### LangSmith 배포의 기본 워크스페이스 재정의하기

[에이전트를 LangSmith에 배포](/langsmith/deployment)할 때 그래프 수명주기 컨텍스트 매니저를 사용해 트레이스가 전송되는 기본 워크스페이스를 재정의할 수 있습니다. `config` 매개변수를 통해 전달되는 런타임 구성에 따라 배포된 에이전트의 트레이스를 서로 다른 워크스페이스로 라우팅하려는 경우에 유용합니다.

<CodeGroup>
```python Python
import os
import contextlib
from typing_extensions import TypedDict
from langgraph.graph import StateGraph
from langgraph.graph.state import RunnableConfig
from langsmith import Client, tracing_context

# API key with access to multiple workspaces
api_key = os.getenv("LS_CROSS_WORKSPACE_KEY")

# Initialize clients for different workspaces
workspace_a_client = Client(
    api_key=api_key,
    api_url="https://api.smith.langchain.com",
    workspace_id="<YOUR_WORKSPACE_A_ID>"
)

workspace_b_client = Client(
    api_key=api_key,
    api_url="https://api.smith.langchain.com",
    workspace_id="<YOUR_WORKSPACE_B_ID>"
)

# Define configuration schema for workspace routing
class Configuration(TypedDict):
    workspace_id: str

# Define the graph state
class State(TypedDict):
    response: str

def greeting(state: State, config: RunnableConfig) -> State:
    """Generate a workspace-specific greeting."""
    workspace_id = config.get("configurable", {}).get("workspace_id", "workspace_a")

    if workspace_id == "workspace_a":
        response = "Hello from Workspace A!"
    elif workspace_id == "workspace_b":
        response = "Hello from Workspace B!"
    else:
        response = "Hello from the default workspace!"

    return {"response": response}

# Build the base graph
base_graph = (
    StateGraph(state_schema=State, config_schema=Configuration)
    .add_node("greeting", greeting)
    .set_entry_point("greeting")
    .set_finish_point("greeting")
    .compile()
)

@contextlib.asynccontextmanager
async def graph(config):
    """Dynamically route traces to different workspaces based on configuration."""
    # Extract workspace_id from the configuration
    workspace_id = config.get("configurable", {}).get("workspace_id", "workspace_a")

    # Route to the appropriate workspace
    if workspace_id == "workspace_a":
        client = workspace_a_client
        project_name = "production-traces"
    elif workspace_id == "workspace_b":
        client = workspace_b_client
        project_name = "development-traces"
    else:
        client = workspace_a_client
        project_name = "default-traces"

    # Apply the tracing context for the selected workspace
    with tracing_context(enabled=True, client=client, project_name=project_name):
        yield base_graph

# Usage: Invoke with different workspace configurations
# await graph({"configurable": {"workspace_id": "workspace_a"}})
# await graph({"configurable": {"workspace_id": "workspace_b"}})
```python

```typescript TypeScript
import { Client } from "langsmith";
import { LangChainTracer } from "@langchain/core/tracers/tracer_langchain";
import { StateGraph, Annotation } from "@langchain/langgraph";

// API key with access to multiple workspaces
const apiKey = process.env.LS_CROSS_WORKSPACE_KEY;

// Initialize clients for different workspaces
const workspaceAClient = new Client({
  apiKey: ***
  apiUrl: "https://api.smith.langchain.com",
  workspaceId: "<YOUR_WORKSPACE_A_ID>", // e.g., "abc123..."
});

const workspaceBClient = new Client({
  apiKey: ***
  apiUrl: "https://api.smith.langchain.com",
  workspaceId: "<YOUR_WORKSPACE_B_ID>", // e.g., "def456..."
});

// Define the graph state
const StateAnnotation = Annotation.Root({
  response: Annotation<string>(),
});

async function greeting(state: typeof StateAnnotation.State, config: any) {
  const workspaceId = config?.configurable?.workspace_id || "workspace_a";

  let response: string;
  if (workspaceId === "workspace_a") {
    response = "Hello from Workspace A!";
  } else if (workspaceId === "workspace_b") {
    response = "Hello from Workspace B!";
  } else {
    response = "Hello from the default workspace!";
  }

  return { response };
}

// Build the base graph
const baseGraph = new StateGraph(StateAnnotation)
  .addNode("greeting", greeting)
  .addEdge("__start__", "greeting")
  .addEdge("greeting", "__end__")
  .compile();

// Helper to get workspace-specific client and project
function getWorkspaceConfig(workspaceId: string): {
  client: Client;
  projectName: string;
} {
  if (workspaceId === "workspace_a") {
    return { client: workspaceAClient, projectName: "production-traces" };
  } else if (workspaceId === "workspace_b") {
    return { client: workspaceBClient, projectName: "development-traces" };
  }
  return { client: workspaceAClient, projectName: "default-traces" };
}

// Invoke the graph with workspace-specific tracing
async function invokeWithWorkspaceTracing(
  workspaceId: string,
  input: typeof StateAnnotation.State
) {
  const { client, projectName } = getWorkspaceConfig(workspaceId);

  // Create a LangChainTracer with the workspace-specific client
  const tracer = new LangChainTracer({
    client,
    projectName,
  });

  // Invoke the graph with the tracer attached via callbacks
  // All traces will be routed to the selected workspace
  return await baseGraph.invoke(input, {
    configurable: { workspace_id: workspaceId },
    callbacks: [tracer],
  });
}

// Example usage
await invokeWithWorkspaceTracing("workspace_a", { response: "" });
await invokeWithWorkspaceTracing("workspace_b", { response: "" });
```typescript
</CodeGroup>

<Note>
크로스-워크스페이스 트레이싱으로 배포할 때 서비스 키 또는 PAT가 모든 대상 워크스페이스에 대한 권한을 갖고 있는지 확인하세요. 프로덕션 배포에는 멀티-워크스페이스 서비스 키를 사용할 것을 권장합니다. LangSmith 배포의 경우 배포가 생성한 기본 서비스 키를 재정의하려면 크로스-워크스페이스 접근을 가진 서비스 키를 환경 변수(예: `LS_CROSS_WORKSPACE_KEY`)에 추가해야 합니다.
</Note>

## 복제본으로 여러 목적지에 트레이스 쓰기

복제본을 사용하면 모든 트레이스를 여러 프로젝트나 워크스페이스에 **동시에** 보낼 수 있습니다. 각 트레이스가 하나의 목적지로 가는 동적 라우팅 패턴과 달리, 복제본은 트레이스를 모든 구성된 목적지에 병렬로 복제합니다.

복제본은 다음과 같은 경우에 유용합니다:

* 디버깅을 위해 프로덕션 트레이스를 스테이징 또는 개인 프로젝트로 미러링합니다.
* 애플리케이션 코드를 변경하지 않고 멀티테넌트 격리를 위해 여러 워크스페이스에 씁니다.
* 복제본별 메타데이터 재정의를 사용해 같은 서버의 다른 프로젝트에 트레이스를 보냅니다.

### 환경 변수로 복제본 구성하기

`LANGSMITH_RUNS_ENDPOINTS` 환경 변수를 JSON 값으로 설정합니다. 두 가지 형식이 지원됩니다:

* **객체 형식**: 각 엔드포인트 URL을 API 키에 매핑합니다:

```bash
export LANGSMITH_RUNS_ENDPOINTS='{
"https://api.smith.langchain.com": "ls__key_workspace_a",
"https://api.smith.langchain.com": "ls__key_workspace_b"
}'
  • 배열 형식: 복제본 객체 목록. 같은 URL을 가리키는 복제본이 여러 개 필요하거나 복제본별로 project_name을 설정하려는 경우 유용합니다:

    export LANGSMITH_RUNS_ENDPOINTS='[
    {"api_url": "https://api.smith.langchain.com", "api_key": "ls__key1", "project_name": "project-prod"},
    {"api_url": "https://api.smith.langchain.com", "api_key": "ls__key2", "project_name": "project-staging"}
    ]'
    
`LANGSMITH_RUNS_ENDPOINTS`를 `LANGSMITH_ENDPOINT`와 함께 사용할 수 없습니다. 둘 다 설정하면 LangSmith가 오류를 발생시킵니다. 엔드포인트를 구성하려면 하나만 사용하세요.

런타임에 복제본 구성하기

목적지가 요청이나 테넌트마다 달라지는 경우 코드에서 직접 복제본을 전달할 수도 있습니다.

```python Python from langsmith import traceable, tracing_context from langsmith.run_trees import WriteReplica, ApiKeyAuth

@traceable def my_pipeline(query: str) -> str: # Your application logic here return f"Answer to: {query}"

replicas = [ WriteReplica( api_url="https://api.smith.langchain.com", auth=ApiKeyAuth(api_key="ls__key_workspace_a"), project_name="project-prod", ), WriteReplica( api_url="https://api.smith.langchain.com", auth=ApiKeyAuth(api_key="ls__key_workspace_b"), project_name="project-staging", # Optionally override fields on the replicated run updates={"metadata": {"environment": "staging"}}, ), ]

with tracing_context(replicas=replicas): my_pipeline("What is LangSmith?")


```typescript TypeScript
import { traceable } from "langsmith/traceable";

const myPipeline = traceable(
  async (query: string): Promise<string> => {
    // Your application logic here
    return `Answer to: ${query}`;
  },
  {
    name: "my_pipeline",
    replicas: [
      {
        apiUrl: "https://api.smith.langchain.com",
        apiKey: "ls__k..._a",
        projectName: "project-prod",
      },
      {
        apiUrl: "https://api.smith.langchain.com",
        apiKey: "ls__k..._b",
        projectName: "project-staging",
        // Optionally override fields on the replicated run
        updates: { metadata: { environment: "staging" } },
      },
    ],
  }
);

await myPipeline("What is LangSmith?");
```typescript
</CodeGroup>

`updates` 필드를 사용해 특정 복제본의 런에만 추가 필드(예: [메타데이터 또는 태그](/langsmith/ls-metadata-parameters))를 병합할 수도 있습니다. 기본 트레이스는 변경되지 않습니다. 복제본 오류는 치명적이지 않습니다. 복제본 엔드포인트를 사용할 수 없으면 LangSmith는 기본 트레이스에 영향을 주지 않고 오류를 기록합니다.

<Warning>
인증은 분산 트레이스에서 전파되지 않습니다. 트레이스가 여러 서비스를 걸쳐 있으면 LangSmith는 복제본 `project_name`과 `updates`를 다운스트림 서비스에 자동으로 전달하지만 API 키나 자격 증명은 전달하지 않습니다. 각 서비스는 복제본 목적지에 대한 자체 자격 증명을 구성해야 합니다.
</Warning>

### 같은 서버 내에서 복제하기 (프로젝트 전용 복제본)

모든 복제본이 같은 LangSmith 서버를 사용한다면 `api_url`과 `auth`를 생략하고 `project_name`만 지정하면 됩니다. SDK는 기본 클라이언트 자격 증명을 재사용합니다:

<CodeGroup>
```python Python
from langsmith import traceable, tracing_context
from langsmith.run_trees import WriteReplica

@traceable
def my_pipeline(query: str) -> str:
    return f"Answer to: {query}"

with tracing_context(
    replicas=[
        WriteReplica(project_name="project-prod"),
        WriteReplica(project_name="project-staging", updates={"metadata": {"env": "staging"}}),
    ]
):
    my_pipeline("What is LangSmith?")
```python

```typescript TypeScript
import { traceable } from "langsmith/traceable";

const myPipeline = traceable(
  async (query: string) => `Answer to: ${query}`,
  {
    name: "my_pipeline",
    replicas: [
      { projectName: "project-prod" },
      { projectName: "project-staging", updates: { metadata: { env: "staging" } } },
    ],
  }
);

await myPipeline("What is LangSmith?");
```typescript
</CodeGroup>

### 모든 복제본 인스턴스에 피드백 남기기

복제본을 사용하면 각 복제본이 모든 런의 복사본을 받습니다. 특정 복제본의 런에 피드백을 제출하려면 해당 복제본의 런 ID가 필요합니다. **Python SDK 0.10.8** 및 **JS SDK 0.8.5**부터 하나의 복제본을 **기본(primary)** 으로 지정하고 `compute_run_id_for_secondary_replica`를 사용해 다른 모든 복제본의 런 ID를 결정적으로 계산할 수 있습니다.

**기본(primary)** 복제본은 원래 런 ID를 그대로 유지합니다. 각 **보조(secondary)** 복제본은 원래 런 ID와 보조 복제본의 프로젝트 이름에서 파생된 결정적 런 ID를 받습니다. `compute_run_id_for_secondary_replica(original_run_id, project_name)`을 사용해 보조 런 ID를 계산하고 `create_feedback`를 호출할 때 전달합니다.

<CodeGroup>
```python Python
from langsmith import (
    Client,
    compute_run_id_for_secondary_replica,
    trace,
    tracing_context,
)

primary_client = Client(api_key="primary-key")
secondary_client = Client(api_key="secondary-key")

primary_project = "production"
secondary_project = "backup-project"

with tracing_context(
    replicas=[
        {
            "project_name": primary_project,
            "primary": True,
            "client": primary_client,
        },
        {
            "project_name": secondary_project,
            "primary": False,
            "client": secondary_client,
        },
    ]
):
    with trace("answer-question", inputs={"question": "Capital of France?"}) as run:
        run.outputs = {"answer": "Paris"}

# Compute the secondary replica's run ID from the original run ID and project name
secondary_run_id = compute_run_id_for_secondary_replica(
    run.id,
    secondary_project,
)

# Each replica has its own project; resolve the corresponding project UUIDs
primary_session_id = primary_client.create_project(project_name=primary_project, upsert=True).id
secondary_session_id = secondary_client.create_project(project_name=secondary_project, upsert=True).id

# Submit feedback to the primary replica using the original run ID
primary_client.create_feedback(
    trace_id=run.id,
    key="user-rating",
    score=1,
    session_id=primary_session_id,
)

# Submit feedback to the secondary replica using the computed run ID
secondary_client.create_feedback(
    trace_id=secondary_run_id,
    key="user-rating",
    score=1,
    session_id=secondary_session_id,
)
```python

```typescript TypeScript
import { Client } from "langsmith";
import { traceable, getCurrentRunTree } from "langsmith/traceable";
import { computeRunIdForSecondaryReplica } from "langsmith";

const primaryClient = new Client({ apiKey: *** });
const secondaryClient = new Client({ apiKey: *** });

const primaryProject = "production";
const secondaryProject = "backup-project";

let primaryRunId: string | undefined;

const answerQuestion = traceable(
  async (question: string) => {
    primaryRunId = getCurrentRunTree()?.id;
    return { answer: "Paris" };
  },
  {
    name: "answer-question",
    client: primaryClient,
    replicas: [
      {
        projectName: primaryProject,
        primary: true,
        client: primaryClient,
      },
      {
        projectName: secondaryProject,
        primary: false,
        client: secondaryClient,
      },
    ],
  }
);

await answerQuestion("Capital of France?");

if (primaryRunId) {
  // Compute the secondary replica's run ID
  const secondaryRunId = computeRunIdForSecondaryReplica(
    primaryRunId,
    secondaryProject
  );

  // Each replica has its own project; resolve the corresponding project UUIDs
  const { id: primarySessionId } = await primaryClient.createProject({
    projectName: primaryProject,
    upsert: true,
  });
  const { id: secondarySessionId } = await secondaryClient.createProject({
    projectName: secondaryProject,
    upsert: true,
  });

  // Submit feedback to the primary replica using the original run ID
  await primaryClient.createFeedback({
    runId: primaryRunId,
    sessionId: primarySessionId,
    key: "user-rating",
    score: 1,
  });

  // Submit feedback to the secondary replica using the computed run ID
  await secondaryClient.createFeedback({
    runId: secondaryRunId,
    sessionId: secondarySessionId,
    key: "user-rating",
    score: 1,
  });
}
```typescript
</CodeGroup>

<Note>
`compute_run_id_for_secondary_replica` / `computeRunIdForSecondaryReplica` 헬퍼는 Python SDK 0.10.8 이상과 JS SDK 0.8.5 이상에서 사용할 수 있습니다. 더 이른 SDK 버전을 사용한다면 이 기능을 사용하려면 업그레이드해야 합니다.
</Note>

### LangSmith와 OpenTelemetry 목적지 사이에서 라우팅하기

재배포하거나 애플리케이션 로직을 수정하지 않고, 주어진 호출이 트레이스를 LangSmith로 보낼지 OpenTelemetry(OTel) 백엔드로 보낼지 아니면 둘 다로 보낼지 런타임에 결정할 수 있습니다. 환경이나 요청별로 관측성 백엔드 사이를 전환하려는 경우, 즉 런타임에 결정하려는 경우에 유용합니다.

`tracing_mode` 생성자 인자 또는 `LANGSMITH_TRACING_MODE` 환경 변수를 사용해 트레이싱 모드를 설정합니다. 둘 다 같은 값을 받으며, 명시적 `tracing_mode` 인자는 항상 환경 변수보다 우선합니다:

* **`"langsmith"` (기본값)**: 트레이스를 LangSmith에 기본 형식으로 보냅니다.
* **`"otel"`**: 트레이스를 OpenTelemetry 스팬으로 내보내 구성된 OTel 백엔드로 보냅니다.
* **`"hybrid"` (Python 전용)**: 단일 복제본에서 LangSmith와 OTel 백엔드 둘 다로 보냅니다.

<Note>
`Client`의 더 이상 사용되지 않는 `otel_enabled` 매개변수(Python 전용)를 사용한다면 `tracing_mode`로 마이그레이션하세요: `Client(otel_enabled=True)` → `Client(tracing_mode="hybrid")`. `otel_enabled` 매개변수는 다음 마이너 버전에서 제거됩니다.
</Note>

런타임에 원하는 모드를 적용하려면 구성된 `Client`를 복제본에 직접 전달합니다:

<CodeGroup>
```python Python
from langsmith import Client, traceable, tracing_context
from langsmith.run_trees import WriteReplica
from langsmith.wrappers import wrap_openai
import openai

# Create clients with different tracing modes
ls_client = Client()                            # tracing_mode="langsmith" (default)
otel_client = Client(tracing_mode="otel")       # tracing_mode="otel"
hybrid_client = Client(tracing_mode="hybrid")   # tracing_mode="hybrid" (both)

openai_client = wrap_openai(openai.Client())

@traceable()
def joke():
    response = openai_client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[{"role": "user", "content": "Tell me a short joke."}],
    )
    return response.choices[0].message.content

# Mix tracing modes across replicas in a single invocation:
# one replica sends via LangSmith's native format, another as OTel spans.
with tracing_context(replicas=[
    WriteReplica(client=ls_client),    # tracing_mode="langsmith"
    WriteReplica(client=otel_client),  # tracing_mode="otel"
]):
    joke()

# Alternatively, a single hybrid replica sends to both simultaneously.
with tracing_context(replicas=[WriteReplica(client=hybrid_client)]):
    joke()

# Swap replica lists at runtime — e.g. based on a feature flag or environment.
def get_replicas(send_to_otel: bool):
    replicas = [WriteReplica(client=ls_client)]
    if send_to_otel:
        replicas.append(WriteReplica(client=otel_client))
    return replicas

with tracing_context(replicas=get_replicas(send_to_otel=True)):   # LangSmith + OTel
    joke()

with tracing_context(replicas=get_replicas(send_to_otel=False)):  # LangSmith only
    joke()
```python

```typescript TypeScript
import { Client } from "langsmith";
import { traceable } from "langsmith/traceable";
import { wrapOpenAI } from "langsmith/wrappers";
import OpenAI from "openai";

// Note: tracingMode: "otel" requires OTel SDK initialization
// (TracerProvider, SpanProcessor, etc.) before creating the client.
// See the OpenTelemetry integration guide for setup details.

// Create clients with different tracing modes
const lsClient = new Client();                           // tracingMode: "langsmith" (default)
const otelClient = new Client({ tracingMode: "otel" });  // tracingMode: "otel"

const openaiClient = wrapOpenAI(new OpenAI());

async function jokeImpl() {
  const response = await openaiClient.chat.completions.create({
    model: "gpt-4o-mini",
    messages: [{ role: "user", content: "Tell me a short joke." }],
  });
  return response.choices[0].message.content;
}

// Mix tracing modes across replicas in a single traceable call:
// the primary client sends via LangSmith, the replica sends as OTel spans.
const joke = traceable(jokeImpl, {
  name: "joke",
  client: lsClient,                    // tracingMode: "langsmith" (default)
  replicas: [{ client: otelClient }],  // tracingMode: "otel"
});
await joke();

// Build replicas dynamically for runtime switching — e.g. based on a feature flag.
function buildReplicas(sendToOtel: boolean) {
  return sendToOtel ? [{ client: otelClient }] : [];
}

const sendToOtel = process.env.ROUTE_TO_OTEL === "true";
const jokeDynamic = traceable(jokeImpl, {
  name: "joke",
  client: lsClient,
  replicas: buildReplicas(sendToOtel),
});
await jokeDynamic();
```typescript
</CodeGroup>

각 `Client`의 `tracing_mode`가 해당 복제본의 내보내기 경로를 결정합니다. Python에서는 `"hybrid"` 모드가 단일 복제본 내에서 두 목적지를 모두 처리합니다. TypeScript에는 `"hybrid"` 모드가 없으므로 "둘 다로 보내기" 경우는 각 클라이언트에 대해 두 개의 별도 복제본을 사용합니다. 각 복제본이 자체 클라이언트를 독립적으로 해석하므로 단일 `tracing_context` 내에서 모드를 혼합할 수도 있습니다. 예를 들어 하나의 복제본은 LangSmith로 보내는 동안 두 번째 복제본을 통해 같은 트레이스를 OTel 수집기로 전달할 수 있습니다.

***

> 출처: [문서](https://docs.langchain.com/langsmith/log-traces-to-project)

## 더 알아보기 (Learn more)

- [Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
- [Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/log-traces-to-project.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).