스레드 평가
스레드 평가 (Evaluate Threads)
전체 스레드(thread)를 평가해 멀티턴 대화에 대한 평가를 실행해볼게요. 스레드 평가는 개별 트레이스나 스팬을 따로 평가하는 대신, 전체 멀티턴 대화를 단일 단위로 평가해요. 대화 품질이 전체 컨텍스트에 달려 있는 대화형 AI 앱에게 필수적이에요 — 어시스턴트가 주제를 벗어나지 않았는지, 세 턴 전에 유저가 말한 것을 기억했는지, 결국 문제를 해결했는지 같은 걸요.
출처: 문서
본문
개요
스레드 평가는 개별 트레이스나 스팬을 따로 평가하는 대신, 전체 멀티턴 대화를 단일 단위로 평가하게 해줘요. 이는 품질이 대화의 전체 컨텍스트에 달려 있는 대화형 AI 앱에 필수적이에요 — 어시스턴트가 주제를 유지했는지, 세 턴 전에 유저가 말한 것을 기억했는지, 마침내 문제를 해결했는지 같은 걸 판단하죠.
트레이스·스팬 평가처럼 스레드 평가도 Confident AI에서 **서버 측(server-side)**으로 실행돼요. 애플리케이션에서는 confident-trace를 사용해 각 요청의 트레이스를 공유 thread ID로 스레드에 묶고, 트레이스의 인풋과 아웃풋을 설정해 Confident AI가 대화를 재구성하게 해요. 그런 다음 Evaluation Rule로 자동으로, 또는 DeepEval의 evaluate_thread 함수로 수동으로 평가를 트리거할 수 있어요.
개별 트레이스와 스팬 평가는 Evaluate Traces & Spans를 참고하세요.
동작 방식
스레드 평가는 다음 단계를 따르는 데:
- Confident AI에서 실행할 대화형 메트릭으로 멀티턴 메트릭 컬렉션을 만들어요.
- 앱이 공유 thread ID로 트레이스를 만들고, 각 트레이스에
input과output을 설정해 대화 턴을 나타내요. - 대화가 완료되면 Thread Evaluation Rule로, 또는 DeepEval의
evaluate_thread함수를 호출해 평가를 트리거해요. - Confident AI가 트레이스 I/O 값에서 대화형 테스트 케이스를 구성해요 — 각 트레이스의
input은 유저 턴이, 각output은 어시스턴트 턴이 돼요. - 멀티턴 메트릭이 전체 대화에 대해 실행되고, 결과가 대시보드의 스레드에 나타나요.
스레드 평가에는 멀티턴 메트릭 컬렉션만 동작해요. 싱글턴 컬렉션을 사용하면 결과가 나오지 않아요.
sequenceDiagram
participant App as Your App
participant SDK as confident-trace
participant CAI as Confident AI
loop Each conversation turn
App->>SDK: Enter span / turn()
App->>SDK: update_trace(thread_id, input, output)
SDK->>CAI: Export trace
end
CAI->>CAI: Thread idle for the rule's time limit
CAI->>CAI: Collect all traces in thread
CAI->>CAI: Build conversational test case from trace I/O
CAI->>CAI: Run multi-turn metrics
CAI->>CAI: Store results on thread
유휴 시간 제한(idle time limit)의 기본값은 300초예요. 유저가 대화 중간에 그보다 길게 멈추는 경우가 일반적이라면 올리세요 — 그렇지 않으면 하나의 대화가 여러 조각으로 평가될 수 있어요. 또한 Overwrite Evaluations를 켜면 각 유휴 주기마다 이전 결과를 덧붙이는 대신 교체해요. 자세한 내용은 rule fields를 참고하세요.
스레드 평가의 차이점
| 트레이스 & 스팬 평가 | 스레드 평가 | |
|---|---|---|
| 범위 (Scope) | 단일 요청/응답 | 전체 멀티턴 대화 |
| 메트릭 컬렉션 | 싱글턴 메트릭 | 멀티턴 메트릭 |
| 실행 시점 | 수집 시, 트레이스/스팬별 | 완료 시 명시적으로, 또는 유휴 기간 후 자동으로 |
| 데이터 소스 | 스팬/트레이스에 설정한 테스트 케이스 파라미터 | 트레이스 input/output 값이 대화 턴이 됨 |
핵심 차이는 스레드 평가를 위해 별도의 테스트 케이스를 설정하지 않는다는 점이에요 — 대신 Confident AI가 트레이스 I/O에서 대화를 자동으로 구성해요:
- 트레이스
input→ 유저 메시지 - 트레이스
output→ 어시스턴트 메시지
이것이 바로 트레이스 I/O 설정을 올바르게 하는 것이 스레드 평가에 중요한 이유예요. 원시 유저 텍스트와 최종 어시스턴트 응답으로 설정하세요 — 내부 프롬프트 템플릿이나 JSON 블롭이 아니라요. 대화형 메트릭이 이것을 대화 내용으로 읽기 때문이에요.
스레드의 어떤 트레이스에도
input과/또는output을 설정하지 않으면 Confident AI는 평가할 턴이 없게 되고, 평가가 결과를 만들어내지 못해요.
스레드 평가 (Evaluate a Thread)
confident-trace로 각 턴을 계측해요. 진입 스팬 안에서 update_trace / updateTrace로 thread ID와 트레이스 I/O를 설정해요:
Python
from openai import OpenAI
from confident_trace import init, span, update_trace, shutdown
init()
client = OpenAI()
your_thread_id = "your-thread-id"
def llm_app(query: str):
with span("llm_app", type="agent"):
res = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": query}]
).choices[0].message.content
update_trace(thread_id=your_thread_id, input=query, output=res)
return res
try:
llm_app("What's the weather in SF?")
llm_app("What about tomorrow?")
finally:
shutdown()
TypeScript
import OpenAI from "openai";
import { init, withSpan, updateTrace } from "confident-trace";
const runtime = init();
const openai = new OpenAI();
const yourThreadId = "your-thread-id";
const llmApp = async (query: string) => {
return withSpan({ name: "llm_app", type: "agent" }, async () => {
const res = await openai.chat.completions.create({
model: "gpt-4o",
messages: [{ role: "user", content: query }],
});
const data = res.choices[0].message.content ?? "";
updateTrace({ threadId: yourThreadId, input: query, output: data });
return data;
});
};
try {
await llmApp("What's the weather in SF?");
await llmApp("What about tomorrow?");
} finally {
await runtime.shutdown();
}
Thread Evaluation Rule이 활성화돼 있으면, 두 번째 트레이스가 규칙의 시간 제한 동안 유휴 상태가 된 후 Confident AI가 두 턴 대화를 평가하고 결과가 Observatory의 스레드에 나타나요.
앱이 자연스럽게 "한 턴 = 한 함수" 형태라면,
turn()헬퍼가 thread ID(그리고 선택적으로 turn ID, user ID, customer ID)가 이미 설정된 새 트레이스를 시작해요. 그래서update_trace는 인풋과 아웃풋에만 쓰면 돼요:from confident_trace import turn, update_trace with turn("chat-turn", thread_id="chat-42"): res = generate(query) update_trace(input=query, output=res)각 SDK의 전체
turn()API와 스레드 자체에 태그·메타데이터를 붙이는 방법은 threads를 참고하세요.
코드에서 평가 트리거
confident-trace는 스레드의 트레이스를 만들고 내보내는 역할을 하며, 평가 함수는 포함하지 않아요. 대화가 끝날 때 평가를 명시적으로 트리거하려면 DeepEval의 evaluate_thread를 사용해 같은 thread ID와 멀티턴 메트릭 컬렉션 이름을 전달해요:
from deepeval.tracing import evaluate_thread
# Run each conversation turn using the confident-trace instrumentation above.
try:
llm_app("What's the weather in SF?")
llm_app("What about tomorrow?")
finally:
shutdown() # Flush the traces before requesting the evaluation.
evaluate_thread(
thread_id=your_thread_id,
metric_collection="My Multi-Turn Collection",
)
evaluate_thread는 대화의 모든 트레이스가 내보내진 후에만 호출하세요. Python에는 비동기 버전인 a_evaluate_thread 함수도 있어요.
이미 끝난 대화라면 Observatory에서 평가를 시작하거나 Confident AI MCP server가 노출하는 evaluate_thread 도구를 사용할 수도 있어요.
턴 컨텍스트 추가 (Add Turn Context)
각 턴에 호출된 툴과 리트리벌 컨텍스트를 선택적으로 풍부하게 추가할 수 있어요. 이는 멀티턴 메트릭에 각 응답이 어떻게 생성됐는지에 대한 추가 컨텍스트를 줘요 — 예를 들어 어시스턴트가 답하기 전에 실제로 무언가를 찾아봤는지 같은 걸요.
트레이스 파라미터가 테스트 케이스 파라미터에 어떻게 매핑되는지에 대한 자세한 내용은 여기를 클릭하세요.
Python
from confident_trace import span, update_trace
def llm_app(query: str):
with span("llm_app", type="agent"):
chunks = retrieve(query)
results = web_search(query)
res = generate(query, chunks, results)
update_trace(
thread_id="your-thread-id",
input=query,
output=res,
retrieval_context=[chunk.text for chunk in chunks],
tools_called=[{"name": "WebSearch", "input": {"query": query}, "output": results}],
)
return res
TypeScript
import { withSpan, updateTrace } from "confident-trace";
const llmApp = async (query: string) => {
return withSpan({ name: "llm_app", type: "agent" }, async () => {
const chunks = await retrieve(query);
const results = await webSearch(query);
const res = await generate(query, chunks, results);
updateTrace({
threadId: "your-thread-id",
input: query,
output: res,
retrievalContext: chunks.map((c) => c.text),
toolsCalled: [{ name: "WebSearch", input: { query }, output: results }],
});
return res;
});
};
툴 콜은 name과 선택적 input / output을 가진 일반 JSON 객체예요 — import가 필요 없어요.
예시 (Examples)
빠른 퀴즈: 아래 코드에서 Thread Evaluation Rule이 활성화돼 있다면, Confident AI가 스레드를 성공적으로 평가할까요?
Python
from confident_trace import span, update_span, update_trace
your_thread_id = "your-thread-id"
def llm_app(query: str):
with span("llm_app", type="agent"):
res = generate(query)
update_span(input=query, output=res)
update_trace(thread_id=your_thread_id)
return res
llm_app("Hello")
llm_app("Can you help me with my order?")
TypeScript
import { withSpan, updateSpan, updateTrace } from "confident-trace";
const yourThreadId = "your-thread-id";
const llmApp = async (query: string) => {
return withSpan({ name: "llm_app", type: "agent" }, async () => {
const res = await generate(query);
updateSpan({ input: query, output: res });
updateTrace({ threadId: yourThreadId });
return res;
});
};
await llmApp("Hello");
await llmApp("Can you help me with my order?");
정답: 아니요 — 트레이스는 스레드로 올바르게 묶였지만, 트레이스에 input도 output도 설정되지 않았기 때문에 스레드 평가는 결과를 만들어내지 못해요. 그것들은 update_span으로 스팬에 설정됐는데, 이는 트레이스·스팬 평가가 읽는 방식이고 스레드 평가가 읽는 방식이 아니에요. 트레이스 레벨 I/O가 없으면 Confident AI가 평가할 대화 턴을 갖지 못해요.
스레드 평가는 트레이스
input과output만 읽어요.update_span/updateSpan으로 설정된 스팬 레벨 I/O는 스팬 평가와 스팬 상세 뷰에 사용되지만 대화 턴이 되지는 않아요. 위 예시를 고치려면input과output을update_trace/updateTrace호출로 옮기세요 —update_tracevsupdate_span을 참고하세요.
다음 단계
스레드 트레이스 (Thread Traces)
스레드를 만들고, I/O를 설정하고, turn()을 사용하며, 대화에 태그와 메타데이터를 붙이는 방법을 배워요.
평가 규칙 (Evaluation Rules)
스레드 평가를 구동하는 유휴 시간 제한, 필터, 메트릭 컬렉션을 구성해요.