LLM 앱 개발 실전 #4 프롬프트 엔지니어링 실무

7 분 소요

3편까지 호출하는 방법을 익혔습니다. 그런데 LLM 앱의 결과 품질은 코드보다 무엇을 어떻게 묻느냐에 더 크게 좌우됩니다. 같은 모델, 같은 코드라도 프롬프트를 어떻게 쓰느냐에 따라 답이 쓸 만해지기도 하고 엉뚱해지기도 합니다. 이번 글에서는 원하는 결과를 안정적으로 끌어내는 프롬프트 작성법을 정리합니다.

모호한 지시와 구체적인 지시 #

LLM은 빈칸을 알아서 채웁니다. 지시가 모호하면 모델이 세부 사항을 멋대로 정합니다. 길이도, 형식도, 말투도 그때그때 달라집니다. 반대로 구체적으로 적을수록 결과가 원하는 모습에 가까워집니다.

같은 요약 작업을 두 가지 방식으로 비교해 보겠습니다.

  • 모호한 지시: “이 글을 요약해줘.”
  • 구체적인 지시: “이 글을 세 개의 불릿으로 요약해줘. 각 불릿은 한 문장으로, 전문 용어는 풀어서 써줘.”

앞쪽은 길이도 형식도 모델 마음대로입니다. 뒤쪽은 불릿 세 개, 한 문장, 쉬운 표현이라는 틀이 정해져 있어 매번 비슷한 결과가 나옵니다. 프롬프트를 쓸 때는 항상 자문해 보면 좋습니다. “내가 머릿속에 그린 결과를, 모델이 이 문장만 보고 똑같이 떠올릴 수 있을까?”

specific_prompt.py
import anthropic

client = anthropic.Anthropic()

prompt = """다음 글을 세 개의 불릿으로 요약해줘.
각 불릿은 한 문장으로, 전문 용어는 풀어서 써줘.

(여기에 요약할 글)"""

response = client.messages.create(
    model="claude-sonnet-4-6",
    max_tokens=512,
    messages=[{"role": "user", "content": prompt}],
)

출력 형식을 지정하기 #

답을 코드에서 다시 쓰려면 형식이 일정해야 합니다. “긍정/부정/중립 중 하나로만 답해”, “쉼표로 구분된 목록으로 줘”, “마크다운 표로 정리해줘"처럼 출력 형식을 못박아 두면 그다음 처리가 쉬워집니다.

특히 분류 작업에서는 답을 한 단어로 좁히는 것이 중요합니다.

classify.py
response = client.messages.create(
    model="claude-sonnet-4-6",
    max_tokens=10,
    system="너는 리뷰 분류기야. 답은 반드시 긍정, 부정, 중립 중 하나의 단어로만 해. 다른 설명은 붙이지 마.",
    messages=[{"role": "user", "content": "생각보다 별로였어요."}],
)

이렇게 출력을 좁히면 결과를 그대로 if문이나 딕셔너리 키로 쓸 수 있습니다. 다만 자연어 지시는 어디까지나 부탁이라, 모델이 가끔 “부정적입니다” 같은 문장으로 답할 수도 있습니다. 형식을 진짜로 강제하는 방법은 다음 글인 #5(구조화된 출력)에서 다루겠습니다. 여기서는 “프롬프트로 형식을 좁힐 수 있다” 정도로 충분합니다.

예시로 보여주기 #

말로 설명하기 어려운 형식이나 스타일은 예시 한두 개가 설명 열 줄보다 낫습니다. 입력과 출력의 짝을 몇 개 보여주고 새 입력을 주면, 모델이 그 패턴을 따라갑니다. 이 방식을 퓨샷(few-shot)이라고 부릅니다.

messagesuserassistant를 번갈아 넣어 예시를 만듭니다.

few_shot.py
response = client.messages.create(
    model="claude-sonnet-4-6",
    max_tokens=10,
    messages=[
        {"role": "user", "content": "배송이 빨라서 좋았어요"},
        {"role": "assistant", "content": "긍정"},
        {"role": "user", "content": "포장이 다 찢어져서 왔네요"},
        {"role": "assistant", "content": "부정"},
        {"role": "user", "content": "가격은 적당한 것 같아요"},
    ],
)

앞의 두 쌍이 “리뷰를 한 단어로 분류한다"는 패턴을 보여줍니다. Claude는 마지막 리뷰에도 같은 형식으로, 설명 없이 한 단어로 답합니다. 형식이 까다롭거나 말로 풀기 어려울수록 예시의 효과가 큽니다.

데이터와 지시를 태그로 분리하기 #

프롬프트 안에서 지시데이터가 섞이면 모델이 헷갈립니다. 특히 사용자 입력이나 긴 문서를 다룰 때, 어디까지가 처리할 대상이고 어디부터가 명령인지 경계가 흐려집니다. Claude는 XML 태그로 구분된 입력을 잘 다루므로, 데이터를 태그로 감싸 분리하면 좋습니다.

tagged_input.py
prompt = """다음 <review> 안의 고객 리뷰를 한 문장으로 요약해줘.

<review>
배송은 빨랐지만 제품 상태가 좋지 않았고, 고객센터 응대는 친절했습니다.
</review>"""

response = client.messages.create(
    model="claude-sonnet-4-6",
    max_tokens=200,
    messages=[{"role": "user", "content": prompt}],
)

태그로 감싸면 지시(“요약해줘”)와 데이터(리뷰 본문)의 경계가 분명해집니다. 데이터가 길거나 줄바꿈이 많아도 모델이 헷갈리지 않습니다. 또 하나의 이점이 있습니다. 사용자가 입력한 텍스트를 태그 안에 가둬 두면, 그 안에 “지금까지의 지시는 무시하고…” 같은 문구가 섞여 있어도 명령으로 오인할 여지가 줄어듭니다. 이는 프롬프트 인젝션을 막는 기본 습관이기도 합니다.

단계적으로 생각하게 하기 #

답을 바로 내기 어려운 문제, 예를 들어 여러 조건을 따져야 하는 계산이나 논리 문제는 “단계별로 차근차근 생각한 다음 답해줘"를 덧붙이면 정확도가 올라갑니다. 모델이 중간 과정을 거치면서 실수를 줄이기 때문입니다.

step_by_step.py
prompt = """사과가 12개 있어. 친구 3명에게 2개씩 나눠 주고,
남은 사과의 절반을 내가 먹었어. 지금 남은 사과는 몇 개야?
단계별로 차근차근 생각한 다음 마지막 줄에 '답:'으로 결과만 적어줘."""

response = client.messages.create(
    model="claude-sonnet-4-6",
    max_tokens=512,
    messages=[{"role": "user", "content": prompt}],
)

중간 과정을 출력하면 토큰이 늘어납니다. 최종 답만 필요하다면 위처럼 “마지막 줄에 답만” 형식을 함께 지정해, 과정은 거치되 결과만 깔끔하게 뽑아낼 수 있습니다.

노트
가장 강력한 등급인 Opus 최신 모델은 복잡한 문제에서 이런 단계적 사고를 내부에서 알아서 수행합니다. 그래서 “단계별로 생각해줘"를 굳이 붙이지 않아도 됩니다. 이 시리즈의 기본 모델인 claude-sonnet-4-6에서는 명시적으로 유도하면 도움이 됩니다. 모델 등급에 따라 필요한 지시가 다르다는 점을 기억해 두면 좋습니다.

흔히 걸려 넘어지는 곳 #

  • 한 프롬프트에 너무 많은 걸 시킨다 — 요약하고, 번역하고, 분류하고, 표로 정리하라고 한 번에 시키면 일부를 놓치기 쉽습니다. 작업을 쪼개 여러 번 호출하는 편이 정확합니다.
  • 부정 지시에 기댄다 — “장황하게 쓰지 마"보다 “두 문장 이내로 써"처럼 원하는 것을 직접 말하는 편이 잘 통합니다. 하지 말라는 말보다 하라는 말이 명확합니다.
  • 예시 형식이 들쭉날쭉하다 — 퓨샷 예시들의 형식이 서로 다르면 모델도 일관성을 잃습니다. 예시끼리 형식을 똑같이 맞춰야 합니다.

프롬프트는 다듬어 가는 것 #

좋은 프롬프트는 한 번에 완성되지 않습니다. 처음에는 간단히 써서 결과를 봅니다. 그런 다음 모델이 어디서 어긋나는지 확인하고, 그 부분을 보완하는 지시를 한 줄씩 더해 갑니다. 요약이 너무 길면 길이 제한을 붙이고, 전문 용어가 그대로 나오면 “풀어서 써줘"를 더하는 식입니다.

이 과정은 앞서 다룬 도구들과 잘 맞습니다. 같은 입력으로 여러 번 시험할 때는 temperature를 0에 가깝게 두어 결과를 안정시키면(2편) 프롬프트의 변화가 결과에 어떻게 반영되는지 비교하기 쉽습니다. 처음부터 완벽한 프롬프트를 쓰려 애쓰기보다, 빠르게 돌려 보고 실패를 한 가지씩 좁혀 가는 편이 결국 빠릅니다.

마무리 #

이번 글에서는 원하는 결과를 끌어내는 프롬프트 작성법을 정리했습니다.

  • 모호함을 줄이고 구체적으로 지시합니다. 길이, 형식, 말투까지 못박을수록 결과가 안정됩니다.
  • 출력 형식을 지정하면 결과를 코드에서 다시 쓰기 쉬워집니다.
  • 말로 어려운 형식은 예시(퓨샷)로 보여줍니다.
  • 데이터는 XML 태그로 감싸 지시와 분리합니다.
  • 어려운 추론은 단계적으로 생각하게 유도하되, 최종 답 형식도 함께 지정합니다.

프롬프트로 출력 형식을 좁힐 수는 있지만, “반드시 이 형식"을 100% 보장하지는 못합니다. 다음 글인 “LLM 앱 개발 실전 #5 구조화된 출력 받기"에서는 JSON 스키마로 출력 형식을 강제해서, 받은 결과를 곧바로 코드에 꽂아 쓰는 방법을 다루겠습니다.

X