GS리테일 AX 블로그

AI와 데이터로 고객의 생활 가치를 혁신하는 GS리테일 AX 이야기

Data&AI

[사내 지식 AI 만들기 ③-2부·대화] RAG Score와 Answer Readiness는 다른 질문이다

DarionKim 2026. 7. 10. 14:12

정확도 1.0, 그런데 남은 질문

1부에서 정확도 1.0을 냈다. 에이전트가 KB를 정확히 고르고, 권한 안에서, 근거를 재정렬해서 답했다. 그런데도 사용자는 여전히 나쁜 경험을 겪었다. 이유는 하나였다 — "검색이 맞았는가"와 "이 사람에게 답이 됐는가"는 다른 질문이기 때문이다.

이 구분을 실감 나게 보여주는 비교 대상이 하나 있다.

좋은 비교 대상, 그러나 다른 질문 — NotebookLM

여기서 자주 듣는 말이 있다. "이건 NotebookLM에서는 잘 나오는데요?" 틀린 말은 아니다. NotebookLM은 문서 몇 개를 넣고 요약·질문·근거 정리를 시키는 데 실제로 뛰어나다. 범위가 좁고, 문맥이 유지되고, 목적이 분명해서 잘 나온다.

문제는 비교 대상이 다르다는 데 있다. NotebookLM은 문서를 잘 읽는 도구다. 기업의 지식 AI는 그 위에 몇 가지를 더 얹어야 하는 운영체계다.

  • 같은 질문도 누가 물었는지에 따라 답이 달라진다 — 그 사람이 그 문서를 볼 권한이 있는지부터 확인해야 한다(2탄).
  • 최신 공지와 오래된 규정이 충돌하면, 어느 쪽을 우선할지도 판단해야 한다.
  • 답이 틀렸을 때, 누가·어떤 근거로·어떤 경로로 답했는지 나중에 재현할 수 있어야 한다.

이건 문서 QA가 아니라 운영이다. 그래서 NotebookLM은 경쟁 대상이 아니라 기준점으로 쓰는 게 맞았다. 같은 문서를 넣었을 때 NotebookLM은 답하는데 우리 검색은 못한다면 — 그건 검색·청킹·리랭킹(1부)·근거검증·프롬프트 중 어딘가가 부족하다는 신호다. 반대로 NotebookLM이 잘한다고 그 경험을 그대로 기업 시스템에 옮길 수 있는 것도 아니다. 권한이 있어야 하고, 감사가 있어야 하고, 실패가 드러나야 하고, 업무로 이어져야 한다 — 문서를 잘 읽는 것과 회사 안에서 그 답을 운영 가능하게 만드는 것은 다른 일이다.

RAG Score vs Answer Readiness

이제 이름을 붙여보자.

RAG Score        : 문서를 잘 읽었는가?         (검색·태깅·재정렬의 정확도)
Answer Readiness : 이 사람에게 답이 됐는가?     (맥락·권한·시점까지 반영한 실사용 준비도)

RAG Score는 도구의 품질이고, Answer Readiness는 운영체계의 자격이다. 1부는 전자의 이야기였다. 여기서부터는 후자다.

Query Expansion — 사람은 완전한 문장으로 안 묻는다

"그거 다시 설명해줘." "방금 그건 휴가에도 적용돼?" — 이런 말은 그 자체로는 검색할 수 없다. 이전에 뭘 물었는지, 뭘 가리키는지가 없으면 "그거"는 그냥 대명사다.

그래서 검색으로 넘기기 전에 질문을 한 번 다듬는다. 세 갈래로:

  • 문맥 보존 명확화 — "그거"가 뭘 가리키는지 앞선 대화에서 찾아 완전한 문장으로 바꾼다.
  • 핵심 키워드 추출 — 검색엔진이 더 잘 무는 형태로 다시 뽑는다.
  • 의미 보존 변형 — 같은 뜻을 다르게 표현한 질문 몇 개를 더 만들어서, 하나로 안 걸리는 것도 다른 표현으론 걸리게 한다.

여기서도 익숙한 구조가 나온다. 사전에 등록된 동의어·줄임말은 규칙 기반으로 항상 확장하고, 그걸로 부족하면 LLM 기반으로 의미를 더 풀어서 확장한다. 값싼 걸 먼저, 비싼 건 필요할 때만 — 1부 rerank에서 봤던 것과 같은 이중 구조다.

세 번 넘어졌던 이야기

이거 켜는 데 세 번 넘어졌다.

첫 번째는 이름이었다. 코드는 한 이름의 환경변수만 봤는데, 운영자는 다른(그러나 더 흔히 쓰이던) 이름으로 설정을 넣었다. 둘 다 "쿼리 확장을 켜라"는 같은 뜻이었는데, 코드가 한쪽만 알아들어서 — dev에는 켜졌고 prod에는 조용히 안 켜져 있었다. 에러 한 번 없이, 그냥 확장이 안 됐을 뿐이었다.

두 번째는 기본값이었다. 이름을 통일한 다음에도 기본값이 꺼짐으로 남아 있었다. 로그를 뒤져서 잡았다 — 같은 기간 dev 로그엔 "쿼리 확장됨" 흔적이 여러 건 있었는데, prod 로그엔 한 건도 없었다. 품질 지표(주제 적합도)도 유독 낮게 나오던 원인 중 하나가 이거였다. 기본값을 켬으로 뒤집었다.

세 번째는 인터페이스였다. LLM 기반 확장을 붙이자 "지원하지 않는 클라이언트 타입"이라는 에러가 매 요청마다 조용히 찍히기 시작했다 — 확장을 시도했다가 실패하고 원래 쿼리로 되돌아가는 걸 반복하고 있었다. 새로 붙인 LLM 클라이언트가 기대하던 메서드 이름과 실제 메서드 이름이 달랐던 것 — 어댑터로 감싸서 맞췄다.

세 번 다 겉으로는 "왜 확장이 안 켜지지"라는 같은 증상이었다. 원인은 매번 달랐다 — 이름 불일치, 기본값 반전, 인터페이스 불일치. 셋이 서로 아무 관계가 없다는 게 오히려 배운 점이었다. 같은 증상이 여러 다른 원인을 가릴 수 있다는 것.

개인화 스레드 회수 — 출입증이 이제 기억한다

2탄 끝에서 열어둔 질문이 있었다. "출입증은 못 들어가는 문만 막아줄 뿐, 이 사람이 평소 자주 드나드는 문이 어딘지는 알려주지 않는다."

이제 그 답을 할 수 있다. 매 턴마다, 이 사람이 누구이고(조직·부서·직급) 무엇을 기억하고 있는지(장기 기억)를 한 번에 모아 하나의 맥락으로 만든다. 이 맥락이 두 곳에 쓰인다.

하나는 대화 이어가기다. "그거 다시 설명해줘" 같은 말은 그 자체로는 아무것도 아니다. 기억이 있어야 "그거"가 뭔지 안다. 장기 기억에서 방금 무슨 대화가 있었는지를 찾아, 그 말을 완전한 질문으로 다시 써서 검색에 넘긴다. (앞서 본 Query Expansion의 문맥 보존 명확화와 같은 문제를, 대화가 남긴 흔적 쪽에서 한 번 더 푸는 셈이다.)

다른 하나는 조직에 맞춘 우선순위다. 같은 질문도 이 사람이 어느 조직·부서에 있는지에 따라 무엇을 먼저 보여줄지가 달라진다. 접근 가능한 범위(2탄)는 그대로지만, 그 안에서 _이 사람에게 더 맞는 것_을 앞에 놓는다.

두 가지 다 최근에 실제로 켜졌다. "이 사람이 평소 자주 드나드는 문"에 대한 답은 결국, 문을 열어주는 것과는 별개로 — 그 사람을 기억하고, 그 기억을 대화와 라우팅 양쪽에 실제로 쓰는 것이었다.

Before/After — 뭐가 달라졌나

숫자로 자랑할 단계는 아직 아니다. 그런데 체감은 있다.

  • 같은 질문을 두 번 다른 말로 다시 물어야 했던 게, 한 번으로 줄었다 — 문맥 재작성과 쿼리 확장이 같이 자리를 잡은 덕분이다.
  • "그거 다시" 같은 되묻기가 끊기지 않고 이어진다 — 장기 기억이 무엇을 가리키는지 찾아준다.
  • 같은 권한 범위 안에서도, 이 사람 조직에 더 맞는 답이 앞으로 온다.

여전히 남은 것도 있다. 그리고 그건 다음 이야기 몫이다.

심화 — "그거"를 자립형 질문으로 바꾸는 프롬프트, 그리고 "Pod" 사고

이 절은 깊다. 대화 재작성과 쿼리 확장이 실제 코드에서 어떻게 도는지 궁금한 분을 위한 detail이다. 바쁘면 맺음으로 건너뛰어도 좋다.

① 코드 — "그거"를 푸는 실제 프롬프트 + 방어막

본문의 "문맥 보존 명확화"는 실제로 이런 프롬프트로 돈다.

def _build_prompt(query, history):
    return (
        "사용자의 모호한 후속 질문을, 대화 기록에서 대명사·지시어(그거, 저분, 위, 아까)를 "
        "풀어 자립형 한국어 검색 질의로 다시 써라. 하나의 간결한 질의로. 답하지 말 것. "
        "이미 자립형이면 그대로 반환.\n\n"
        f"[대화 기록]\n{history}\n\n[현재 질문] {query}\n\n[자립형 질문]"
    )

중요한 건 프롬프트 자체가 아니라 그 주위의 방어막이다. 재작성 결과는 (a) 비어있지 않고 (b) 원본 대비 터무니없이 길지 않고 (c) 프롬프트의 섹션 마커를 되돌려 뱉지 않을 때만 믿는다(_is_plausible_rewrite). 하나라도 실패하면(타임아웃·예외·이상한 출력) 조용히 옛 방식(직전 대화를 그냥 뒤에 이어 붙이기)으로 폴백하고, Datadog에 applied|timeout|error|rejected 태그를 남긴다. "LLM 한 스텝 + 하드 폴백 + 관측 트립와이어" — 이 기능 하나를 넘어 재사용되는 패턴이다.

② 우리가 처음엔 틀렸다 — "Pod"가 "Print On Demand"가 되다

쿼리 확장이 켜지자 새로운 실패가 나왔다. 쿠버네티스 "Pod"를 물은 사용자의 질의가, 같은 용어 사전에 있던 전혀 다른 도메인의 약어로 조용히 확장됐다(리테일 쪽 "Pod = Print On Demand"). 사전에 IT 약어와 리테일 약어가 같이 살았고, 확장 로직은 사용자가 어느 도메인을 뜻했는지 몰랐던 것.

여기서 배운 게 이 시리즈에서 드문 결이다 — 확장은 "못 찾던 걸 찾게" 할 뿐 아니라, "맞던 걸 틀리게" 만들 수도 있다. 본문의 세 번 넘어진 이야기(안 켜지는 문제)와는 정반대 방향의 실패다. 고친 방식: 질의에 이미 다른 IT·인프라 용어가 섞여 있으면, 알려진 교차-도메인 동음이의(pod, node, service, image, port, token, storage, disk)에 대해선 사전 확장을 건너뛴다 — 사전을 무조건 믿지 않고 문맥으로 가드.

그리고 하나 더 — 이 확장이 답변 품질을 실제로 올리는지 A/B로 재려 했는데, 정작 골든 질문 세트에 "짧은 도메인 약어 질의"가 너무 적어 통계적으로 판정할 수 없었다. 그때 결과를 우겨서 내는 대신 INSUFFICIENT_DATA로 정직하게 기록하고, 검정력이 낮다는 단서를 붙여 A/B 결론을 냈다. 기능의 버그가 아니라, 기능을 재는 방식의 결함을 먼저 공개한 사례다.

맺음 — 그리고 다음 질문

1부에서 라우팅을 풀었고, 2부에서 대화의 결을 채웠다. 태깅 정확도와 대화 능력, 둘 다 갖췄으니 이제 완성일까?

아직 하나가 남는다. 이 좋은 답을 어디서 보여줄 것인가. 우리는 하마터면 앱을 또 만들 뻔했다. 그 이야기는 4탄 — 모바일 앱을 또 만들 뻔했다에서.

(그리고 — 이 모든 걸 사람이 아니라 에이전트가 굴린다는 이야기, Claude나 Codex 같은 코딩 에이전트도 결국 같은 틀 안의 "에이전트"라는 이야기는 언젠가 따로.)


시리즈: 1탄 저장 · 2탄 권한 · 3탄-1부 대화 · 3탄-2부 대화 (이 글) · 4탄 채널 · 번외 수집


김헌기 Darion · AX본부 > AI데이터부문 > AI혁신지원팀

AI 및 공통 Tech 기반의 기술 표준화 업무를 수행하고 있습니다. 동료들과 함께 기술 문화 만들기와 낯선 기술자와의 인연의 시작에 관심이 많습니다.