Trajectory 뷰 통합

Trajectory 뷰 통합

LangSmith Trajectory 뷰에서 렌더링되는 프레임워크와 SDK, 각각이 설정하는 메타데이터를 알려드릴게요.

Trajectory 뷰스레드트레이스궤적(trajectory)으로 렌더링합니다: 사용자 프롬프트, 모델 응답, 도구 호출, 도구 결과를 순서대로 표시합니다. Trajectory 뷰는 궤적을 렌더링하기 위해 두 가지 실행 메타데이터가 필요합니다:

  • 스레드 그룹핑: 각 실행의 thread_id는 LangSmith에 실행 집합이 같은 대화에 속한다고 알려줍니다.
  • 실행 분류: 턴의 최상위 실행에 있는 ls_agent_type: "root"는 그 실행을 메인 대화의 일부로 표시합니다. 서브에이전트로 표시된 실행은 스레드에서 서브에이전트 작업으로 나타나며, 미들웨어나 압축으로 표시된 실행은 현재 필터링됩니다.

대부분의 LangSmith 통합에서는 둘 다 자동으로 설정됩니다. 메타데이터를 수동으로 설정해야 할 때는 다음 예제가 체이닝 모드의 OpenAI Responses API와 커스텀 미들웨어·가드레일 태그를 다룹니다.

출처: 문서

본문

지원되는 통합

다음 통합은 thread_idls_agent_type을 모두 자동으로 설정합니다:

체이닝 모드의 OpenAI Responses API(previous_response_id)는 ls_agent_type을 자동으로 설정하지만 thread_id는 직접 설정해야 합니다. 자세한 내용은 체이닝이 있는 OpenAI Responses API 예제를 참고하세요.

전체 ls_agent_type 스키마와 공식 통합이 루트가 아닌 실행에 설정하는 다른 값(subagent, middleware, compaction)은 코딩 에이전트 메타데이터 계약을 참고하세요. 기본 스레드 그룹핑 메커니즘은 스레드 구성을 참고하세요.

체이닝이 있는 OpenAI Responses API

previous_response_id를 전달해 OpenAI Responses API에 대한 호출을 체이닝하면 OpenAI가 서버 측에 대화 상태를 저장하며, LangSmith 래퍼에는 호출을 스레드로 그룹화할 자연스러운 키가 없습니다. thread_id를 호출별로 또는 래퍼 초기화 시점에 직접 설정하세요.

참고: thread_id에는 UUID v7을 사용하세요. LangSmith SDK는 uuid7 헬퍼를 내보내며, UUID v7은 생성 시간순으로 정렬되므로 목록 뷰에서 스레드가 순서대로 유지됩니다.

호출별 메타데이터

각 호출에 thread_id를 설정합니다. 하나의 래핑된 클라이언트가 여러 스레드를 제공할 때 사용합니다(예: 프로세스당 클라이언트 하나, 동시 대화 여러 개).

import openai
from langsmith import uuid7
from langsmith.wrappers import wrap_openai

client = wrap_openai(openai.Client())
thread_id = str(uuid7())

res1 = client.responses.create(
    model="gpt-5.6",
    input="What is the capital of France?",
    store=True,
    langsmith_extra={"metadata": {"thread_id": thread_id}},
)

res2 = client.responses.create(
    model="gpt-5.6",
    input="And its population?",
    previous_response_id=resp1.id,
    store=True,
    langsmith_extra={"metadata": {"thread_id": thread_id}},
)
import OpenAI from "openai";
import { uuid7 } from "langsmith";
import { wrapOpenAI } from "langsmith/wrappers";

const client = wrapOpenAI(new OpenAI());
const threadId = uuid7();

const res1 = await client.responses.create({
  model: "gpt-5.6",
  input: "What is the capital of France?",
  metadata: { thread_id: threadId },
  store: true,
});

const res2 = await client.responses.create({
  model: "gpt-5.6",
  input: "And its population?",
  previous_response_id: res1.id,
  metadata: { thread_id: threadId },
  store: true,
});

초기화 시점 메타데이터

클라이언트를 래핑할 때 thread_id를 한 번 설정합니다. 이 래퍼를 통해 이루어지는 모든 호출은 동일한 스레드로 태그됩니다. 래핑된 클라이언트가 수명 동안 정확히 하나의 스레드만 제공할 때 사용합니다(예: 대화별 워커).

import openai
from langsmith import uuid7
from langsmith.wrappers import wrap_openai

thread_id = str(uuid7())

client = wrap_openai(
    openai.Client(),
    tracing_extra={"metadata": {"thread_id": thread_id}},
)

res1 = client.responses.create(
    model="gpt-5.6",
    input="What is the capital of France?",
    store=True,
)

res2 = client.responses.create(
    model="gpt-5.6",
    input="And its population?",
    previous_response_id=resp1.id,
    store=True,
)
import OpenAI from "openai";
import { uuid7 } from "langsmith";
import { wrapOpenAI } from "langsmith/wrappers";

const threadId = uuid7();

const client = wrapOpenAI(new OpenAI(), { metadata: { thread_id: threadId } });

const res1 = await client.responses.create({
  model: "gpt-5.6",
  input: "What is the capital of France?",
  store: true,
});

const res2 = await client.responses.create({
  model: "gpt-5.6",
  input: "And its population?",
  previous_response_id: res1.id,
  store: true,
});

커스텀 미들웨어 또는 가드레일 숨기기

LLM 또는 도구 호출 주변에 직접 가드레일, 정책 확인, 미들웨어 함수를 작성할 때 @traceable로 래핑하고 메타데이터에 ls_agent_type: "middleware"를 설정하세요. Trajectory 뷰는 이 실행들을 메인 대화에서 필터링합니다.

from langsmith import traceable

@traceable(
    run_type="llm",
    metadata={"ls_agent_type": "middleware"},
)
def entry_guardrail(prompt: str) -> dict:
    # Your guardrail logic
    return {"decision": "allow"}
import { traceable } from "langsmith/traceable";

const entryGuardrail = traceable(
  async (prompt: string) => {
    // Your guardrail logic
    return { decision: "allow" };
  },
  { run_type: "llm", metadata: { ls_agent_type: "middleware" } },
);

Trajectory 뷰에서 실행 제외

실행 메타데이터에 LS_MESSAGE_VIEW_EXCLUDE를 설정하면 Trajectory 뷰가 해당 실행을 건너뜁니다. 키의 존재가 중요하며, True가 관례적 값입니다. 필터는 어떤 추출 전략이 트레이스를 보기 전에 실행되므로, 제외된 LLM 또는 도구 실행은 감지, 메시지 추출, 도구 호출 페어링에 절대 영향을 주지 않습니다.

LS_MESSAGE_VIEW_EXCLUDElangsmith(Python 및 JS)에서 내보내는 최상위 상수이며, 그 값은 문자열 "ls_message_view_exclude"입니다. 오타를 피하려면 상수를 선호하세요. 리터럴 문자열도 동작합니다.

대화 턴이 아닌 LLM 하위 span(분류 호출, 임베딩 조회, 안전 필터, 라우팅/가드레일 결정 등)에 사용하세요. LangSmith의 다른 곳에서는 계속 보이길 원하지만 대화 기록을 어지럽히고 싶지 않은 경우입니다.

Python:

1. @traceable 데코레이터에서: 전체 함수의 실행을 제외합니다.

from langsmith import LS_MESSAGE_VIEW_EXCLUDE, traceable

@traceable(run_type="llm", metadata={LS_MESSAGE_VIEW_EXCLUDE: True})
def classify_intent(query: str) -> str:
    # This LLM call is internal routing, not part of the chat
    return llm.predict(f"Classify the intent of: {query}")

2. trace 컨텍스트 매니저를 통해: 임시 span을 제외합니다.

from langsmith import LS_MESSAGE_VIEW_EXCLUDE, trace

with trace(
    "safety_check",
    run_type="llm",
    metadata={LS_MESSAGE_VIEW_EXCLUDE: True},
) as run:
    result = safety_model.score(text)
    run.end(outputs={"score": result})

3. 실행 중인 함수 내부에서: 실행이 패치되기 전 어느 시점에 현재 실행 트리에 키를 설정합니다.

from langsmith import LS_MESSAGE_VIEW_EXCLUDE, get_current_run_tree, traceable

@traceable(run_type="llm")
def maybe_internal(query: str) -> str:
    result = llm.predict(query)
    if _looks_like_routing(query):
        rt = get_current_run_tree()
        if rt is not None:
            rt.add_metadata({LS_MESSAGE_VIEW_EXCLUDE: True})
    return result

4. wrap_openai / wrap_anthropic 사용 시 호출별로: 래핑된 클라이언트 호출에 langsmith_extra를 전달합니다.

import openai
from langsmith import LS_MESSAGE_VIEW_EXCLUDE
from langsmith.wrappers import wrap_openai

client = wrap_openai(openai.Client())

resp = client.chat.completions.create(
    model="gpt-5.6",
    messages=[{"role": "user", "content": "Classify: ..."}],
    langsmith_extra={"metadata": {LS_MESSAGE_VIEW_EXCLUDE: True}},
)

5. LangChain RunnableConfig: 체인 또는 채팅 모델의 단일 호출을 제외합니다.

from langchain_openai import ChatOpenAI
from langsmith import LS_MESSAGE_VIEW_EXCLUDE

llm = ChatOpenAI(model="gpt-5.6")
result = llm.invoke(
    "Classify this query",
    config={"metadata": {LS_MESSAGE_VIEW_EXCLUDE: True}},
)

TypeScript:

1. traceable 래퍼에서: 전체 함수의 실행을 제외합니다.

import { LS_MESSAGE_VIEW_EXCLUDE } from "langsmith";
import { traceable } from "langsmith/traceable";

const classifyIntent = traceable(
  async (query: string) => {
    return await llm.predict(`Classify the intent of: ${query}`);
  },
  {
    name: "classify_intent",
    run_type: "llm",
    metadata: { [LS_MESSAGE_VIEW_EXCLUDE]: true },
  },
);

2. 실행 중인 함수 내부에서: 현재 실행 트리를 변경합니다.

import { LS_MESSAGE_VIEW_EXCLUDE } from "langsmith";
import { traceable, getCurrentRunTree } from "langsmith/traceable";

const maybeInternal = traceable(
  async (query: string) => {
    const result = await llm.predict(query);
    if (looksLikeRouting(query)) {
      const rt = getCurrentRunTree();
      rt.extra = rt.extra ?? {};
      rt.extra.metadata = { ...rt.extra.metadata, [LS_MESSAGE_VIEW_EXCLUDE]: true };
    }
    return result;
  },
  { run_type: "llm" },
);

3. wrapOpenAI 사용 시 호출별로: 호출에 langsmithExtra를 전달합니다.

import { LS_MESSAGE_VIEW_EXCLUDE } from "langsmith";
import { wrapOpenAI } from "langsmith/wrappers";
import OpenAI from "openai";

const client = wrapOpenAI(new OpenAI());

const resp = await client.chat.completions.create(
  {
    model: "gpt-5.6",
    messages: [{ role: "user", content: "Classify: ..." }],
  },
  { langsmithExtra: { metadata: { [LS_MESSAGE_VIEW_EXCLUDE]: true } } },
);

4. Vercel AI SDK 미들웨어: wrapAISDKlsConfig.metadata를 통해 키를 전달합니다. 미들웨어는 이것을 내보내는 모든 LLM 실행에 병합합니다.

import * as ai from "ai";
import { LS_MESSAGE_VIEW_EXCLUDE } from "langsmith";
import { wrapAISDK } from "langsmith/experimental/vercel";

const { generateText } = wrapAISDK(ai, {
  metadata: { [LS_MESSAGE_VIEW_EXCLUDE]: true },
});

일부 호출만 제외하고 다른 것은 제외하지 않으려면 wrapAISDK로 정상 래핑하고 대신 AI SDK를 호출하는 부모 traceable 내부에서 getCurrentRunTree()를 변경하거나, createChild({ extra: { metadata: { [LS_MESSAGE_VIEW_EXCLUDE]: true } } })로 자식 RunTree를 사용하세요.

5. 수동 RunTree.createChild: 실행을 직접 구축할 때.

import { LS_MESSAGE_VIEW_EXCLUDE } from "langsmith";
import { RunTree } from "langsmith/run_trees";

const parent = new RunTree({ name: "agent", run_type: "chain" });
const child = parent.createChild({
  name: "safety_check",
  run_type: "llm",
  extra: { metadata: { [LS_MESSAGE_VIEW_EXCLUDE]: true } },
});

참고

  • 필터는 키의 존재를 확인하며 진실성을 확인하지 않습니다. {LS_MESSAGE_VIEW_EXCLUDE: false}도 실행을 제외합니다. 실행을 포함하려면 키를 완전히 생략하세요.
  • @traceable(Python) 또는 traceable(JS) 부모 안에서 실행되는 자식 실행은 공유 트레이싱 컨텍스트를 통해 제외를 상속합니다: Python의 _METADATA ContextVar와 JS의 AsyncLocalStorage. 자식의 데코레이터 시점 메타데이터는 상속된 값 위에 겹쳐집니다.
  • 제외된 실행은 일반 트레이스 뷰, 실행 탐색기, 메트릭에는 계속 나타납니다. Trajectory 뷰만 필터링합니다.

수동 계측

지원되는 통합의 래퍼 없이 트레이싱한다면(예: RunTree, REST API, 또는 프로바이더 SDK 주변의 커스텀 래퍼를 통해 실행을 보내는 경우) 각 LLM 실행의 메타데이터에 ls_message_format을 설정해 트레이스를 올바른 추출기로 라우팅하세요:

Trace shape Set on metadata
LangChain messages (constructor envelope) ls_message_format: "langchain"
OpenAI Chat Completions ls_message_format: "completions"
OpenAI Responses API ls_message_format: "responses"
Anthropic Messages API ls_message_format: "anthropic"

관련 자료

더 알아보기