테스트 케이스를 트레이스에 연결하기
테스트 케이스를 트레이스에 연결하기 (Link Test Cases to Traces)
평가 테스트 케이스와 턴을 그 트레이스에 연결해 완전한 관측성(observability)을 확보하는 방법이에요.
출처: 문서
본문
개요 (Overview)
AI Connection을 통해 평가를 실행하면, Confident AI가 각 결과를 AI 앱이 만든 트레이스에 연결할 수 있어요. 이렇게 하면 완전한 관측성을 얻을 수 있어요. 평가 결과에서 바로 그 결과를 만든 정확한 트레이스로 점프할 수 있거든요.
트레이스 연결에는 두 가지 방식이 있어요.
- 단일 턴 평가: 테스트 케이스를 트레이스에 연결
- 멀티 턴 평가 및 레드팀 공격: 턴을 트레이스에 연결
둘 다 페이로드의 식별자(testCaseId 또는 turnId)를 트레이싱 설정에 전달하는 방식으로 동작해요. confident-trace에서는 계측된 호출이 시작되기 전에 trace_context / traceContext로 식별자를 제공하거나, 커스텀 스팬이 이미 트레이스를 시작했다면 update_trace / updateTrace를 사용해요.
테스트 케이스를 트레이스에 연결하기 (Linking Test Cases to Traces)
단일 턴 평가에서는 각 테스트 케이스를 해당 트레이스에 연결해 완전한 관측성을 확보할 수 있어요. 이를 위해 페이로드에 testCaseId를 포함시키고(기본 활성화) 트레이싱 설정에 전달하면 돼요.
sequenceDiagram
participant C as Confident AI
participant E as Your Endpoint
participant T as Tracing
C->>E: Ping AI Connection with testCaseId
E->>T: Create trace with testCaseId
E-->>C: Return actual_output
T-->>C: Send trace to Confident AI
Note over C: Trace linked to test case
페이로드 구성에 testCaseId를 포함하고 AI connection이 이를 받아들이도록 설정했는지 확인하세요.
{
"input": golden.input,
"testCaseId": testCaseId
}
testCaseId는 앱이 트레이스 작업을 시작하기 전에 이미 사용 가능하므로, trace_context / traceContext를 통해 전달해요. 이 컨텍스트는 트레이스나 추가 스팬을 만들지 않아요. 그 안의 자동 계측된 통합 호출이 만든 트레이스에 ID를 제공할 뿐이에요.
Python
각 예시는 FastAPI 요청 핸들러를 사용하고, 서버가 시작될 때 init()을 한 번 호출해요.
from fastapi import FastAPI
from pydantic import BaseModel
from langchain_openai import ChatOpenAI
from confident_trace import init, trace_context
init()
app = FastAPI()
model = ChatOpenAI(model="gpt-4o")
class GenerateRequest(BaseModel):
input: str
testCaseId: str
@app.post("/generate")
def generate(request: GenerateRequest):
with trace_context(test_case_id=request.testCaseId):
output = model.invoke(request.input).content
return {"output": output}
from fastapi import FastAPI
from pydantic import BaseModel
from langchain_openai import ChatOpenAI
from langgraph.prebuilt import create_react_agent
from confident_trace import init, trace_context
init()
app = FastAPI()
agent = create_react_agent(model=ChatOpenAI(model="gpt-4o"), tools=[])
class GenerateRequest(BaseModel):
input: str
testCaseId: str
@app.post("/generate")
def generate(request: GenerateRequest):
with trace_context(test_case_id=request.testCaseId):
result = agent.invoke({
"messages": [{"role": "user", "content": request.input}]
})
return {"output": result["messages"][-1].content}
from fastapi import FastAPI
from pydantic import BaseModel
from openai import OpenAI
from confident_trace import init, trace_context
init()
app = FastAPI()
client = OpenAI()
class GenerateRequest(BaseModel):
input: str
testCaseId: str
@app.post("/generate")
def generate(request: GenerateRequest):
with trace_context(test_case_id=request.testCaseId):
response = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": request.input}],
)
return {"output": response.choices[0].message.content}
from fastapi import FastAPI
from pydantic import BaseModel
from langchain_openai import ChatOpenAI
from openinference.instrumentation.langchain import LangChainInstrumentor
from confident_trace import init, trace_context
init(instrumentations=())
LangChainInstrumentor().instrument()
app = FastAPI()
model = ChatOpenAI(model="gpt-4o")
class GenerateRequest(BaseModel):
input: str
testCaseId: str
@app.post("/generate")
def generate(request: GenerateRequest):
with trace_context(test_case_id=request.testCaseId):
output = model.invoke(request.input).content
return {"output": output}
TypeScript
각 예시는 Express 요청 핸들러를 사용하고, 서버가 시작될 때 init()을 한 번 호출해요.
import express from "express";
import OpenAI from "openai";
import { init, traceContext } from "confident-trace";
init();
const app = express();
const client = new OpenAI();
app.use(express.json());
app.post("/generate", async (req, res) => {
const output = await traceContext(
{
testCaseId: req.body.testCaseId,
},
async () => {
const response = await client.chat.completions.create({
model: "gpt-4o",
messages: [{ role: "user", content: req.body.input }],
});
return response.choices[0].message.content;
},
);
res.json({ output });
});
app.listen(3000);
import express from "express";
import { generateText } from "ai";
import { openai } from "@ai-sdk/openai";
import { init, traceContext } from "confident-trace";
init();
const app = express();
app.use(express.json());
app.post("/generate", async (req, res) => {
const output = await traceContext(
{
testCaseId: req.body.testCaseId,
},
async () => {
const { text } = await generateText({
model: openai("gpt-4o"),
prompt: req.body.input,
});
return text;
},
);
res.json({ output });
});
app.listen(3000);
import express from "express";
import { registerInstrumentations } from "@opentelemetry/instrumentation";
import { OpenAIInstrumentation } from "@arizeai/openinference-instrumentation-openai";
import { init, traceContext } from "confident-trace";
init({ instrumentations: [] });
registerInstrumentations({
instrumentations: [new OpenAIInstrumentation()],
});
const app = express();
const { default: OpenAI } = await import("openai");
const client = new OpenAI();
app.use(express.json());
app.post("/generate", async (req, res) => {
const output = await traceContext(
{
testCaseId: req.body.testCaseId,
},
async () => {
const response = await client.chat.completions.create({
model: "gpt-4o",
messages: [{ role: "user", content: req.body.input }],
});
return response.choices[0].message.content;
},
);
res.json({ output });
});
app.listen(3000);
TypeScript 예시 중 하나를 preload와 함께 실행해요.
node --import tsx --import confident-trace/register src/index.ts
한 번 연결되면 평가 결과에서 각 테스트 케이스의 전체 트레이스를 바로 볼 수 있어요. 실패를 디버깅하고 모델 동작을 이해하기 쉬워져요.
커스텀 스팬이 이미 트레이스를 시작했다면
update_trace()/updateTrace()를 대신 사용해요. manage trace context를 참고하세요.
턴을 트레이스에 연결하기 (Linking Turns to Traces)
멀티 턴 평가와 멀티 턴 레드팀 공격에서는 Confident AI가 턴마다 엔드포인트를 한 번씩 호출해요. 각 턴에는 트레이싱 설정에 전달할 수 있는 고유한 turnId가 있어요. 이렇게 하면 각 턴의 트레이스를 대화의 특정 턴에 연결해, 평가 또는 어세스먼트 결과에서 턴별로 트레이스를 볼 수 있어요.
턴별 트레이스 연결은 AI connection의 페이로드에
testCaseId와turnId가 모두 포함된 경우에만 동작해요. 둘 다 기본 JSON 페이로드 템플릿에 있어요. 커스텀 페이로드만 이 변수들을 참조하는지 확인하면 돼요.turnId만 기록해서는 연결이 완성되지 않으니, 두 ID 모두 트레이싱 코드에 전달하세요.
sequenceDiagram
participant C as Confident AI
participant E as Your Endpoint
participant T as Tracing
Note over C,E: Turn 1
C->>E: { turnId, ...payload }
E->>T: Create trace with turnId
E-->>C: Return actual_output
Note over C,E: Turn 2
C->>E: { turnId, ...payload }
E->>T: Create trace with turnId
E-->>C: Return actual_output
T-->>C: Send traces to Confident AI
Note over C: Each turn linked to its trace
페이로드 구성에 turnId(testCaseId와 함께)를 포함해요.
{
"input": golden.input,
"testCaseId": testCaseId,
"turnId": turnId,
"state": state
}
그런 다음 두 ID를 각 요청에 대해 만들어진 트레이스에 제공해요.
Python
각 예시는 FastAPI 요청 핸들러를 사용하고, 서버가 시작될 때 init()을 한 번 호출해요.
from fastapi import FastAPI
from pydantic import BaseModel
from langchain_openai import ChatOpenAI
from confident_trace import init, trace_context
init()
app = FastAPI()
model = ChatOpenAI(model="gpt-4o")
class GenerateRequest(BaseModel):
input: str
testCaseId: str
turnId: str
@app.post("/generate")
def generate(request: GenerateRequest):
with trace_context(
test_case_id=request.testCaseId,
turn_id=request.turnId,
):
output = model.invoke(request.input).content
return {"output": output}
from fastapi import FastAPI
from pydantic import BaseModel
from langchain_openai import ChatOpenAI
from langgraph.prebuilt import create_react_agent
from confident_trace import init, trace_context
init()
app = FastAPI()
agent = create_react_agent(model=ChatOpenAI(model="gpt-4o"), tools=[])
class GenerateRequest(BaseModel):
input: str
testCaseId: str
turnId: str
@app.post("/generate")
def generate(request: GenerateRequest):
with trace_context(
test_case_id=request.testCaseId,
turn_id=request.turnId,
):
result = agent.invoke({
"messages": [{"role": "user", "content": request.input}]
})
return {"output": result["messages"][-1].content}
from fastapi import FastAPI
from pydantic import BaseModel
from openai import OpenAI
from confident_trace import init, trace_context
init()
app = FastAPI()
client = OpenAI()
class GenerateRequest(BaseModel):
input: str
testCaseId: str
turnId: str
@app.post("/generate")
def generate(request: GenerateRequest):
with trace_context(
test_case_id=request.testCaseId,
turn_id=request.turnId,
):
response = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": request.input}],
)
return {"output": response.choices[0].message.content}
from fastapi import FastAPI
from pydantic import BaseModel
from langchain_openai import ChatOpenAI
from openinference.instrumentation.langchain import LangChainInstrumentor
from confident_trace import init, trace_context
init(instrumentations=())
LangChainInstrumentor().instrument()
app = FastAPI()
model = ChatOpenAI(model="gpt-4o")
class GenerateRequest(BaseModel):
input: str
testCaseId: str
turnId: str
@app.post("/generate")
def generate(request: GenerateRequest):
with trace_context(
test_case_id=request.testCaseId,
turn_id=request.turnId,
):
output = model.invoke(request.input).content
return {"output": output}
TypeScript
각 예시는 Express 요청 핸들러를 사용하고, 서버가 시작될 때 init()을 한 번 호출해요.
import express from "express";
import OpenAI from "openai";
import { init, traceContext } from "confident-trace";
init();
const app = express();
const client = new OpenAI();
app.use(express.json());
app.post("/generate", async (req, res) => {
const output = await traceContext(
{
testCaseId: req.body.testCaseId,
turnId: req.body.turnId,
},
async () => {
const response = await client.chat.completions.create({
model: "gpt-4o",
messages: [{ role: "user", content: req.body.input }],
});
return response.choices[0].message.content;
},
);
res.json({ output });
});
app.listen(3000);
import express from "express";
import { generateText } from "ai";
import { openai } from "@ai-sdk/openai";
import { init, traceContext } from "confident-trace";
init();
const app = express();
app.use(express.json());
app.post("/generate", async (req, res) => {
const output = await traceContext(
{
testCaseId: req.body.testCaseId,
turnId: req.body.turnId,
},
async () => {
const { text } = await generateText({
model: openai("gpt-4o"),
prompt: req.body.input,
});
return text;
},
);
res.json({ output });
});
app.listen(3000);
import express from "express";
import { registerInstrumentations } from "@opentelemetry/instrumentation";
import { OpenAIInstrumentation } from "@arizeai/openinference-instrumentation-openai";
import { init, traceContext } from "confident-trace";
init({ instrumentations: [] });
registerInstrumentations({
instrumentations: [new OpenAIInstrumentation()],
});
const app = express();
const { default: OpenAI } = await import("openai");
const client = new OpenAI();
app.use(express.json());
app.post("/generate", async (req, res) => {
const output = await traceContext(
{
testCaseId: req.body.testCaseId,
turnId: req.body.turnId,
},
async () => {
const response = await client.chat.completions.create({
model: "gpt-4o",
messages: [{ role: "user", content: req.body.input }],
});
return response.choices[0].message.content;
},
);
res.json({ output });
});
app.listen(3000);
TypeScript 예시 중 하나를 preload와 함께 실행해요.
node --import tsx --import confident-trace/register src/index.ts
turnId가 연결되면 대화의 각 턴에 "View trace" 버튼이 생겨요. 평가 결과나 리스크 어세스먼트 사이드 드로어에서 클릭하면 해당 턴의 전체 트레이스를 볼 수 있어요.
턴을 트레이스에 연결하는 것과 트레이스를 thread로 그룹화하는 것은 별개예요.
turn_id는 트레이스가 어느 평가 턴에 속하는지,thread_id는 프로덕션 대화를 그룹화해요. 앱이 프로덕션에서도 실행된다면 같은 트레이스에 둘 다 설정할 수 있어요.
다음 단계 (Next Steps)
트레이스가 평가 결과에 연결되면 실패를 엔드투엔드로 디버깅할 수 있어요. 이제 관련 관측성·연결 기능을 살펴볼게요.
Multi-Turn State
멀티 턴 시뮬레이션 동안 턴을 가로질러 정보를 유지해요.
LLM Tracing
트레이싱이 AI 앱에서 어떻게 동작하는지 배워요.
Trace-Level Detections
레드팀 공격이 트레이스에 연결되면 어떤 스팬이 취약점을 유발했는지 확인해요.
AI Connections
testCaseId와 turnId를 담는 페이로드 템플릿을 구성해요.