Post

[LLM 앱 실전] Day 1: 프롬프트와 컨텍스트 - 모델을 제어하는 첫 번째 도구

[LLM 앱 실전] Day 1: 프롬프트와 컨텍스트 - 모델을 제어하는 첫 번째 도구

이 글은 AI(Claude)의 도움을 받아 작성하고, 작성자가 검토·편집했습니다.

서론: 모델 안에서 모델 위로

지난 시리즈(Transformer 원리)에서 LLM의 내부를 들여다봤다. 이번 시리즈는 그 위에서 실제 제품을 만드는 법이다. 모델을 직접 학습시키지 않고도, API 호출과 설계만으로 강력한 애플리케이션을 만들 수 있다. 그 출발점이자 가장 기본 도구가 프롬프트와 컨텍스트 관리다. 이 시리즈는 프롬프팅·RAG·도구 호출·에이전트·평가를 5일에 걸쳐 다룬다.

1. 프롬프트의 구조: 역할 분리

대부분의 챗 API는 메시지를 역할(role)별로 나눈다. 이 구조를 활용하는 것이 첫걸음이다.

1
2
3
system:  모델의 정체성·규칙·제약 (대화 내내 유지되는 지침)
user:    사용자의 실제 요청
assistant: 모델의 이전 응답 (대화 맥락)
1
2
3
4
5
6
messages = [
    {"role": "system", "content":
        "너는 한국어 기술 문서 요약 도우미다. "
        "항상 3개의 핵심 불릿으로 요약하고, 추측하지 않는다."},
    {"role": "user", "content": document_text},
]

system 프롬프트에 역할·형식·금지사항을 명확히 두면, 매 요청마다 반복 지시할 필요가 없다.

2. 명확하고 구체적인 지시

LLM은 모호함을 싫어한다. “잘 요약해줘”보다 측정 가능한 지시가 결과를 좌우한다.

1
2
3
4
5
6
7
8
9
나쁜 프롬프트:
  "이 코드를 리뷰해줘"

좋은 프롬프트:
  "이 Python 코드를 리뷰하라. 다음 순서로:
   1. 버그 (있으면 라인 번호와 함께)
   2. 보안 문제
   3. 성능 개선점
   각 항목은 한 문장으로. 문제가 없으면 '없음'이라고 명시하라."

원칙: 무엇을, 어떤 형식으로, 어떤 제약 하에 할지를 명시한다. 추측을 줄이고 일관성을 높인다.

3. Few-shot: 예시로 가르치기

지시만으로 부족하면 예시를 보여준다. 모델은 패턴을 즉석에서 모방한다(in-context learning, Transformer Day 2의 어텐션이 예시를 참조).

1
2
3
4
5
6
7
8
9
10
11
messages = [
    {"role": "system", "content": "고객 문의를 카테고리로 분류하라."},
    # Few-shot 예시
    {"role": "user", "content": "환불은 어떻게 받나요?"},
    {"role": "assistant", "content": "결제/환불"},
    {"role": "user", "content": "비밀번호를 잊어버렸어요"},
    {"role": "assistant", "content": "계정/로그인"},
    # 실제 요청
    {"role": "user", "content": "앱이 자꾸 종료돼요"},
]
# → 모델이 패턴을 따라 "기술지원" 형식으로 답할 가능성↑

zero-shot(예시 없음)으로 시작하고, 결과가 불안정하면 few-shot으로 올린다.

4. 사고 유도: Chain-of-Thought

복잡한 추론은 “단계적으로 생각하라”고 지시하면 정확도가 오른다. 모델이 중간 추론을 토큰으로 펼치며 스스로 계산하기 때문이다.

1
2
3
4
"답만 말하지 말고, 단계별로 推論한 뒤 마지막에 결론을 내려라."

복잡한 계산·논리 문제에서 효과적.
단, 토큰(=비용·지연)이 늘어남 → 단순 작업엔 불필요.

추론 과정을 사용자에게 숨기고 결론만 보여주려면, 출력을 구조화해(아래) 추론 부분을 분리한다.

5. 구조화 출력: 파싱 가능한 응답

애플리케이션은 자유 텍스트가 아니라 파싱 가능한 데이터가 필요하다. JSON 스키마를 강제한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 많은 API가 JSON 모드 / 스키마 강제를 지원
response = client.chat.completions.create(
    model="...",
    messages=messages,
    response_format={
        "type": "json_schema",
        "json_schema": {
            "name": "classification",
            "schema": {
                "type": "object",
                "properties": {
                    "category": {"type": "string",
                                 "enum": ["결제/환불", "계정/로그인", "기술지원"]},
                    "confidence": {"type": "number"},
                },
                "required": ["category", "confidence"],
            },
        },
    },
)
data = json.loads(response.choices[0].message.content)  # 안전하게 파싱

스키마 강제는 “JSON으로 답해줘”라고 부탁하는 것보다 훨씬 안정적이다. 모델이 형식을 벗어날 수 없다.

6. 컨텍스트 윈도우 관리

모델의 컨텍스트(입력+출력 토큰)에는 한계가 있다(Transformer Day 5의 KV 캐시 비용과 직결). 길어지면 비싸지고 느려지고, 결국 잘린다.

1
2
3
4
5
6
7
8
9
관리 전략:
  - 대화 기록 축약: 오래된 메시지를 요약해 압축
  - 슬라이딩 윈도우: 최근 N개 메시지만 유지
  - 관련성 필터: 현재 질문에 필요한 것만 컨텍스트에 (→ Day 2 RAG)

주의:
  - "Lost in the middle": 긴 컨텍스트의 중간 정보를 모델이 놓치는 경향
    → 중요한 정보는 앞·뒤에 배치
  - 토큰 = 비용 → 불필요한 컨텍스트는 줄인다

7. Day 1 체크리스트

  1. system/user/assistant 역할 분리로 지침과 요청을 구조화했다.
  2. 무엇을·어떤 형식으로·어떤 제약으로 할지 명확히 지시했다.
  3. zero-shot에서 시작해 필요 시 few-shot 예시로 보강하는 전략을 잡았다.
  4. Chain-of-Thought의 효과와 비용 트레이드오프를 이해했다.
  5. JSON 스키마 강제로 파싱 가능한 출력을 얻고 컨텍스트를 관리했다.

다음 편 예고

프롬프트만으로는 모델이 모르는 최신 정보·사내 문서에 답할 수 없다. Day 2에서는 RAG(검색 증강 생성) — 외부 지식을 검색해 컨텍스트에 주입하는 가장 중요한 LLM 앱 패턴을 다룬다.

This post is licensed under CC BY 4.0 by the author.