AI 기능을 만들 때 기획서에서 거의 늘 빠지는 화면이 있습니다. 틀렸을 때 보이는 화면입니다.
잘 됐을 때는 잘 그려져 옵니다. 사진을 올리면 알아서 분류되고, 문장을 넣으면 요약이 나옵니다. 그런데 분류가 틀리면 사용자가 뭘 보게 되는지, 요약이 이상하면 어떻게 되돌리는지는 대개 비어 있습니다.
보통 기능이라면 실패는 예외입니다. AI는 다릅니다. 정확도 95%면 스무 번에 한 번은 틀린다는 뜻이고, 매일 쓰는 사람은 한 달에 몇 번씩 그 한 번을 만납니다. 실패는 예외가 아니라 정상 동작의 일부입니다.
애매하게 판단한 걸 확실한 것처럼 보여주지 마세요
제일 자주 보는 실수입니다. 모델이 간신히 골라낸 값을 사용자가 직접 입력한 값과 똑같이 보여줍니다.
자동으로 채운 칸은 눈에 띄게 달라야 합니다.
<input
value={value}
onChange={onChange}
className={auto ? "border-dashed bg-paper-2" : "border-solid"}
/>
{auto && (
<p className="label mt-1 text-green-mid">
자동으로 채웠습니다 · 맞는지 봐주세요
</p>
)}
이 차이 하나로 사람들이 다르게 행동합니다. 확정된 값처럼 보이면 그냥 넘기고, 자동으로 채웠다고 알면 한 번 봅니다.
확신도를 숫자로 노출할 필요는 없습니다. 0.87 대신 "확인 필요" 라는 라벨 하나면 충분합니다.
되돌리는 버튼은 그 자리에 있어야 합니다
AI가 뭘 자동으로 처리했다면 되돌리는 방법도 같은 화면에 있어야 합니다. 설정 안쪽에 숨어 있으면 없는 거나 마찬가지입니다.
- 자동 분류된 항목 옆에서 바로 고칠 수 있게
- 자동으로 쓴 문장 옆에 원문 보기
- 한꺼번에 처리했으면 한꺼번에 되돌리기
되돌린 기록은 남겨두세요. 어떤 경우에 사람들이 자주 고치는지 보이고, 그게 다음에 뭘 개선할지 알려줍니다. 평가 데이터가 공짜로 쌓이는 셈입니다.
await logCorrection({
feature: "auto-categorize",
suggested: aiValue,
corrected: userValue,
confidence, // 확신도 낮은 구간에서 더 자주 고쳐지는지 본다
at: new Date(),
});
한 달쯤 지나 이 로그에서 확신도 구간별 수정 비율을 뽑아보면, 자동 처리 기준선을 감이 아니라 숫자로 정할 수 있습니다.
기다리는 시간을 감추지 마세요
LLM은 응답에 몇 초가 걸립니다. 그동안 화면이 멈춰 있으면 사람들은 고장 났다고 생각합니다.
글자가 만들어지는 대로 흘려보내면 체감이 확 달라집니다. 전체 시간은 똑같은데도 그렇습니다.
const stream = client.messages.stream({
model: "claude-opus-5",
max_tokens: 2000,
messages: [{ role: "user", content: question }],
});
stream.on("text", (delta) => {
setAnswer((prev) => prev + delta); // 오는 대로 붙인다
});
const final = await stream.finalMessage();
첫 글자가 1초 안에 뜨면 사람들은 기다립니다. 5초 동안 아무것도 없으면 새로고침합니다. 언제 끝나느냐보다 언제 시작하느냐가 체감을 정합니다.
단계가 여러 개면 지금 어디쯤인지 보여주세요.
문서 찾는 중 ▸ 내용 읽는 중 ▸ 답변 쓰는 중
30초 넘게 걸리는 일은 기다리게 하지 마세요
화면 앞에 붙잡아두면 대부분 나갑니다. 요청만 받아두고 끝나면 알려주는 방식으로 바꾸는 게 낫습니다.
급하지 않은 일이라면 배치로 돌리는 방법도 있습니다. 값이 절반이고, 어차피 아무도 기다리지 않습니다.
안 되면 안 된다고 말하게 하세요
모델이 답을 못 하는 상황은 반드시 옵니다. 이때 억지로 뭘 만들어내는 것보다 솔직한 편이 낫습니다.
"답을 찾지 못했습니다. 상담 연결하기"
그럴듯한 오답보다 이쪽이 신뢰를 덜 깎습니다. 한 번이라도 틀린 답에 걸리면 그다음부터 모든 답을 의심하게 되거든요.
응답이 왜 끊겼는지도 나눠서 처리해야 합니다.
| 상황 | 보여줄 것 |
|---|---|
| 길이 제한에 걸림 | "이어서 보기" 버튼 |
| 안전 정책상 거절 | 사유와 함께 사람 연결 |
| 네트워크·서버 오류 | 다시 시도 버튼 |
이 셋을 "오류가 발생했습니다" 하나로 뭉뚱그리면 사용자는 뭘 해야 할지 모릅니다. 응답에 이유가 들어 있으니 그대로 갈라주면 됩니다.
if (response.stop_reason === "max_tokens") {
showContinueButton();
} else if (response.stop_reason === "refusal") {
showHumanHandoff(response.stop_details?.category);
}
형식이 깨질 수 있다고 보고 만드세요
JSON을 달라고 하면 대부분 잘 줍니다. 대부분이라는 게 문제입니다. 앞에 설명을 붙이기도 하고, 코드 블록으로 감싸기도 하고, 따옴표 처리가 달라지기도 합니다.
문자열을 잘라 쓰지 말고 반드시 파싱하세요. 형식을 스키마로 못 박을 수 있으면 그게 제일 낫습니다.
const response = await client.messages.create({
model: "claude-opus-5",
max_tokens: 1024,
output_config: {
format: {
type: "json_schema",
schema: {
type: "object",
properties: {
category: { type: "string", enum: ["환불", "배송", "기타"] },
confidence: { type: "number" },
},
required: ["category", "confidence"],
additionalProperties: false,
},
},
},
messages: [{ role: "user", content: text }],
});
enum으로 값을 묶어두면 화면이 모르는 값을 받을 일이 없습니다. 이것만으로도 예외 처리가 절반으로 줍니다.
처음에는 사람을 남겨두세요
정확도가 쌓이기 전에 전부 자동으로 열면 사고가 사용자에게 바로 갑니다.
1단계 AI가 제안하고 사람이 확정
2단계 확신도 높은 것만 자동, 나머지는 사람
3단계 전부 자동, 이상한 것만 사람
1단계로 굴리는 동안 쌓인 수정 기록이 2단계 기준선을 정해줍니다. 이 순서를 건너뛰면 기준을 감으로 잡게 되고, 그 감이 틀리면 되돌리는 데 훨씬 큰 비용이 듭니다.
정리
기획서에 잘 된 화면 옆에 틀린 화면을 같이 그려두세요.
- 틀렸을 때 뭐가 보이는지 (자동 입력 표시, 확신도)
- 어떻게 되돌리는지 (같은 자리에서, 기록을 남기면서)
- 누구에게 넘어가는지 (사람 연결, 다시 시도, 이어보기)
이 셋만 정해두면 나머지 설계는 훨씬 수월합니다. 그리고 되돌린 기록이 쌓이면 다음 단계로 넘어갈 시점을 숫자로 판단할 수 있습니다.
