Few-shot으로 답변 형식 개선하고 배포하기

LangChain Prompt에 Few-shot 예시를 넣어 답변 형식을 일관되게 만드는 방법과 Structured Output의 한계, Streamlit 배포 전 보안 점검을 설명합니다.

LangChain으로 사내 문서 RAG 챗봇 만들기 8/9

문서를 준비하는 단계부터 검색, 대화 화면, 배포와 평가까지 하나씩 연결하는 학습 기록

이전 글 — Streamlit으로 대화·기억·Streaming 구현하기

축제 안내 챗봇에게 운영 시간을 물었습니다. 첫 답은 한 문장, 다음 답은 긴 보고서, 세 번째 답은 출처 없는 목록이었습니다. 내용이 비슷해도 매번 모양이 달라지면 사용자는 중요한 정보를 찾기 어렵습니다.

“짧게 답해”라고 지시하는 것보다 원하는 답의 예시를 직접 보여주면 형식을 이해하는 데 도움이 될 수 있습니다. Few-shot Prompt는 모델에게 몇 개의 입력·출력 예시를 보여주는 방법이지, 새로운 사실을 가르치거나 정확성을 보장하는 학습은 아닙니다.

이번 글에서는 예시로 답변 형식을 잡고, 코드가 처리할 구조가 필요할 때 Structured Output을 구분해 사용합니다. 마지막으로 Streamlit 앱을 공개하기 전에 문서·API Key·로그 경계를 확인합니다.

이 글은 시리즈 순서에 맞춰 2026년 6월 25일에 배치했습니다. API와 배포 설명은 2026년 8월 16일 LangChain과 Streamlit 공식 문서를 기준으로 확인했습니다. 실제 앱을 배포하거나 외부 모델을 호출하지는 않았습니다.

Few-shot은 본보기를 보여주는 방법이다

학교 방송부 신입에게 “잘 써”라고 말하는 것보다 완성된 안내문 두 장을 보여주는 편이 빠릅니다. 제목, 시간, 장소와 출처가 어떤 순서로 나오는지 눈으로 볼 수 있기 때문입니다.

Few-shot Prompt도 같습니다. 모델에 Task 설명과 함께 몇 개의 예시를 넣습니다.

질문: 축제는 언제 시작해?
답변:
- 핵심: 오전 10시에 시작합니다.
- 근거: 축제 안내 1조

질문: 음식 판매는 언제 끝나?
답변:
- 핵심: 오후 6시에 끝납니다.
- 근거: 축제 안내 3조

새 질문이 들어오면 모델은 이 패턴을 참고해 답합니다. 예시가 모델 가중치에 영구 저장되는 것은 아니며 현재 요청의 Prompt에 포함됩니다.

좋은 예시는 무엇이 다를까?

예시는 많다고 좋은 것이 아닙니다. 다음 조건이 중요합니다.

  • 실제 사용자 질문과 비슷합니다.
  • 원하는 출력 형식을 정확히 지킵니다.
  • 정상 사례뿐 아니라 “근거 없음” 같은 경계 사례를 포함합니다.
  • 서로 충돌하는 규칙을 보여주지 않습니다.
  • 개인정보와 실제 비공개 문서를 넣지 않습니다.
  • Prompt 길이와 비용을 감당할 수 있습니다.

예시가 틀리면 모델이 틀린 패턴을 따라갈 수 있습니다. Prompt도 코드처럼 검토와 버전 관리가 필요합니다.

LangChain Prompt로 예시를 연결해보자

다음 코드는 이해를 위해 예시 문자열을 직접 구성합니다. 예시가 많아지면 FewShotPromptTemplate이나 별도 Dataset을 검토할 수 있습니다.

from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI

examples = """
질문: 축제는 언제 시작해?
답변:
- 핵심: 오전 10시에 시작합니다.
- 근거: 축제 안내 1조

질문: 분실물 보관 기간은?
답변:
- 핵심: 제공된 문서에서 확인할 수 없습니다.
- 근거: 없음
""".strip()

prompt = ChatPromptTemplate.from_messages([
    (
        "system",
        "제공된 Context만 사용하세요. 다음 예시와 같은 형식으로 "
        "핵심과 근거를 구분하세요.\n\n예시:\n{examples}\n\n"
        "Context:\n{context}",
    ),
    ("human", "질문: {question}"),
])

model = ChatOpenAI(model="<사용 가능한 모델 이름>")
chain = prompt | model

response = chain.invoke({
    "examples": examples,
    "context": "<Retriever가 반환한 문서>",
    "question": "음식 판매는 언제 끝나?",
})

이 코드는 공식 API를 바탕으로 한 학습 예제입니다. 이 Astro 저장소에서 실행하지 않았고 실제 응답을 측정하지 않았습니다.

예시는 답변을 어떻게 바꿀까?

지시에서 예시를 거쳐 일관된 출력으로

Few-shot 예시는 모델이 따라야 할 입력과 출력의 관계를 보여주지만 사실 검증은 별도 단계로 남습니다.

1업무 규칙Context만 사용하고 근거를 표시하도록 지시
2정상 예시답을 찾았을 때 핵심과 출처를 보여줌
3실패 예시근거가 없을 때 추측하지 않는 모양을 보여줌
4새 질문실제 사용자 질문과 검색 Context 입력
5형식 생성모델이 예시 패턴을 참고해 답변 생성
6결과 검증형식과 내용·출처를 서로 따로 확인

읽기 쉬운 답변 형식형식이 맞더라도 문서 해석과 사실이 맞는지는 별도로 검증해야 합니다.

현재 단계: 업무 규칙

학교 축제 안내 답변의 가상 Few-shot 흐름입니다. 특정 모델의 품질 개선을 측정한 결과가 아닙니다.

Few-shot이 해결하지 못하는 것

예시는 모델이 모르는 최신 규정을 새로 만들지 못합니다. RAG 검색에서 정답 문서가 누락됐다면 예쁜 형식으로 잘못 답할 수도 있습니다.

Temperature를 낮춰도 사실이 추가되지는 않습니다. 예시가 있다고 같은 문장이 매번 완전히 재현된다고 보장할 수도 없습니다.

다음은 따로 확인해야 합니다.

  • 검색된 문서가 최신이고 권한에 맞는가?
  • 답변의 핵심이 실제 Context에 있는가?
  • 표시한 source가 근거 문서와 일치하는가?
  • 금액·날짜·코드가 업무 규칙을 통과하는가?
  • 답을 찾지 못했을 때 안전하게 거절하는가?

문자열 형식보다 더 강한 구조가 필요하다면

후속 코드가 결과를 처리한다면 Markdown 목록보다 Structured Output, 즉 정해진 구조의 출력이 적합할 수 있습니다. Pydantic Model로 원하는 필드를 설명하고 Chat Model의 with_structured_output을 사용할 수 있습니다.

from pydantic import BaseModel, Field
from langchain_openai import ChatOpenAI

class FestivalAnswer(BaseModel):
    answer: str = Field(description="문서에 근거한 짧은 답변")
    source: str | None = Field(description="근거 문서의 source")
    found: bool = Field(description="Context에서 답을 찾았는지 여부")

model = ChatOpenAI(model="<Structured Output 지원 모델 이름>")
structured_model = model.with_structured_output(FestivalAnswer)

result = structured_model.invoke(
    "<질문과 검색 Context가 포함된 Message>"
)

지원 방식은 모델 제공자마다 다를 수 있습니다. Schema를 만족하는 객체가 나왔다고 내용이 사실이라는 뜻도 아닙니다.

형식 검증과 내용 검증을 분리합니다.

형식 검증 내용 검증
필수 필드가 있는가 답변이 Context에 있는가
자료형이 맞는가 source가 실제 문서인가
객체로 변환 가능한가 날짜·금액이 유효한가
허용된 Enum인가 사용자 권한에 맞는가

Schema는 답안지 칸을 정할 뿐 정답을 채점하지는 않습니다.

예시를 늘릴수록 좋아질까?

예시가 많으면 Prompt가 길어지고 입력 Token과 비용이 늘어납니다. 서로 비슷한 예시가 너무 많으면 중요한 규칙이 묻힐 수도 있습니다.

모든 예시를 항상 넣기보다 질문과 가까운 예시를 골라 넣는 Dynamic Few-shot을 검토할 수 있습니다. 하지만 예시 검색이 새 실패 지점이 됩니다.

작은 고정 예시로 시작하고 다음을 비교합니다.

  1. 예시가 없을 때
  2. 정상 예시만 있을 때
  3. 정상과 실패 예시가 함께 있을 때
  4. 형식 검증과 내용 검증 결과

평가 질문은 그대로 두고 Prompt 한 부분만 바꿔야 무엇이 효과를 냈는지 알 수 있습니다.

Streamlit Community Cloud에 올리기 전

Streamlit 공식 안내에서는 GitHub Repository의 Branch와 실행할 Python 파일을 선택해 Community Cloud에 배포할 수 있습니다. requirements.txt에는 앱이 실제로 사용하는 패키지를 적습니다.

festival-chatbot/
├─ streamlit_app.py
├─ rag.py
├─ requirements.txt
└─ data/

공개 저장소에 API Key를 커밋하지 않습니다. 배포 환경의 Secret 관리 기능을 사용하고 이미 노출된 Key는 삭제만 하지 말고 폐기·재발급해야 합니다.

또한 다음을 확인합니다.

  • 공개 앱이 비공개 문서를 포함하지 않는가?
  • 검색된 문서 조각이 외부 Model API로 전송되는가?
  • 사용자 Prompt와 답변이 어디에 기록되는가?
  • 업로드 파일의 크기·형식·보존 기간을 제한했는가?
  • 여러 사용자의 Session과 Vector Store 데이터가 섞이지 않는가?
  • API 사용량과 동시 요청을 제한할 수 있는가?

배포 버튼을 눌렀다고 운영 준비가 끝나는 것은 아닙니다. 공개 범위와 비용·오류·삭제 절차를 먼저 정해야 합니다.

배포 후에만 발견되는 문제

로컬에서는 한 사람만 질문하지만 공개 앱에는 여러 요청이 동시에 들어올 수 있습니다. 외부 API Rate Limit, Timeout과 사용량 증가가 생길 수 있습니다.

앱 재시작 뒤 Session State와 로컬 파일이 어떻게 되는지도 확인해야 합니다. 중요한 대화나 Index를 임시 실행 환경에만 의존하면 안 됩니다.

오류 메시지에 API Key, 내부 경로와 문서 원문이 노출되지 않도록 합니다. 사용자가 볼 메시지와 운영 로그를 구분합니다.

자주 생기는 오해

Few-shot은 Fine-tuning인가요?

아닙니다. 현재 Prompt에 예시를 포함하는 방법입니다. 모델 가중치를 다시 학습하지 않습니다.

예시를 넣으면 정확한 답을 보장하나요?

아닙니다. 형식과 Task 이해를 도울 수 있지만 검색되지 않은 사실을 만들지 못하며 환각 가능성도 남습니다.

JSON으로 나오면 믿어도 되나요?

아닙니다. JSON 문법과 Field 형식이 맞는 것과 내용이 사실인 것은 별개입니다.

Community Cloud에 올리면 비공개 앱이 되나요?

Repository와 앱 공유 설정에 따라 공개 범위가 달라질 수 있습니다. 배포 전에 현재 공식 정책과 앱 권한을 직접 확인해야 합니다.

세 줄 요약

  • Few-shot Prompt는 원하는 입력·출력 예시를 보여줘 답변 형식을 이해시키지만 모델을 재학습하거나 사실성을 보장하지 않습니다.
  • Structured Output은 결과를 코드가 처리하기 쉽게 만들지만 Schema 검증과 내용·업무 검증은 별개입니다.
  • Streamlit 배포 전에는 API Key·문서 전송·공개 범위·Session·비용과 오류 로그를 함께 점검해야 합니다.

다음 편 예고

이제 챗봇은 화면에서 대화하고 원하는 형식으로 답할 수 있습니다. 하지만 몇 번 잘 답한 것만으로 품질이 좋아졌다고 말할 수 있을까요?

마지막 9편 — LangSmith로 평가하고 AI Agent로 확장하기에서 답변을 반복해서 검증하고 행동하는 AI로 넘어가는 경계를 살펴봅니다.

공식 문서