본문 바로가기
AI/Prompt Engineering

2-3. 프롬프트 엔지니어링으로 의료급여 본인부담률 추론하기

by jys275 2026. 3. 28.

이번 글에서는 과연 복잡한 다중 조건이 얽힌 '의료급여 본인부담률 표'를 LLM이 얼마나 잘 해석해 낼지, 그리고 우리가 적용할 프롬프트 기법들이 얼마나 극적인 변화를 만들어낼지 직접 코드로 검증해 보았다.

 

즉, Step 1(Zero-shot)부터 Step 4(Self-Consistency)까지 프롬프트를 점진적으로 고도화하며 정답률이 어떻게 변화하는지 상세하게 기록해볼 것이다. 코드를 짜면서 겪은 과정과 프롬프트 위주의 전문을 모두 포함한다.

 

 


 

 

 

 

0. 실험 환경 및 참조 데이터 세팅

 

 

먼저 파이썬(Python) 환경에서 OpenAI SDK를 활용하여 뼈대를 잡았다. 구조화된 JSON 출력을 완벽하게 강제하기 위해 1주차에서 효과를 체감했던 Pydantic을 이번에도 적극 활용했다.

  • 사용한 모델: gpt-4o-mini (복잡한 추론의 가성비를 테스트하기 위해 mini 모델 채택)
  • SDK / 환경: openai Python SDK / Python 3.10
  • 평가 데이터: dataset.jsonl 내 질문 30건 answer_key.jsonl 정답지
from pydantic import BaseModel

class CopaymentResponse(BaseModel):
    reason: str   # 추론 과정
    answer: str   # 최종 정답 (간결하게)

위와같이 Structured Outputs를 활용하면 추론 과정과 최종 답변을 분리할 수 있다.

 

원본 참조 pdf에 있는 표 데이터는 LLM이 읽을 수 있도록 마크다운(Markdown) 텍스트 형태(copayment_reference)로 변환하여 시스템 프롬프트에 주입할 준비를 마쳤다.

 

 


 

 

[1-1]: Zero-shot Baseline (기본 프롬프트)

 

 

가장 먼저, 아무런 힌트 없이 표 데이터와 질문만 던져보는 Zero-shot 테스트이다.

 

프롬프트 전문

# LLM에게 제공할 지식 (의료급여 표 마크다운 변환본)
copayment_reference = """
[의료급여 본인부담률 참조 데이터]

1. 65세 이상 틀니 및 치과 임플란트 본인부담률
- 1종: 틀니 5%, 임플란트 10%
- 2종: 틀니 15%, 임플란트 20%
* 본인부담 보상제·상한제 해당되지 않음
...
"""

# 가설: "답만 간결하게 작성하세요" 라고 지시하여 CoT를 제한함
system_prompt_v1 = f"""아래는 의료급여 본인부담률 참조 데이터입니다.
질문에 대해 정확한 본인부담률을 답하세요. 답만 간결하게 작성하세요.

[본인부담률 참조 데이터]
{copayment_reference}
"""

# API 호출 (temperature=0 으로 고정하여 일관성 확보)
response = client.beta.chat.completions.parse(
    model="gpt-4o-mini",
    messages=[
        {"role": "system", "content": system_prompt_v1},
        {"role": "user", "content": question}
    ],
    response_format=CopaymentResponse,
    temperature=0
)

 


 

[1-2]: 오답 로그 심층 분석 (정답률: 50% = 15/30 문항 정답)

 

 

1. 맞게 풀었는데 포맷이 다름 (억울한 오답)

로그를 보면 모델이 논리적으로 완벽하게 추론하고 계산도 맞았지만, 파이썬의 단순 문자열 비교(in 연산자) 때문에 오답 처리된 안타까운 케이스가 매우 많다.

  • [q04] 정답: 무료 / AI 답변: 0%
  • [q15] 정답: 해당되지 않음 / AI 답변: 적용되지 않음
  • [q19, q24, q25] 정답: 175,000원 / AI 답변: 175000 (콤마와 '원' 누락)
  • [q26, q28] 정답: 틀니 75,000원, 임플란트 100,000원 / AI 답변: 틀니: 75,000원, 임플란트: 100,000원 (콜론 기호 추가됨)

이것이 바로 Few-shot(예시)이 필요한 완벽한 명분이다. 모델에게 "돈은 콤마랑 '원'을 붙여서 써", "0%는 '무료'라고 써"라고 구구절절 설명하는 대신, 정답 포맷의 예시 3개만 보여주면 이 억울한 오답들은 단번에 정답으로 바뀔 것.

 

2. 다중 조건 매핑 실패 (LLM의 표 읽기 한계)

Zero-shot 상태에서 생각할 여유 없이 바로 답을 찾으려다 보니, 표의 조건(행/열)을 잘못 매핑하는 논리적 오류들이 발생했다.

  • 나이 경계선 착각 [q03, q27]
    • q03: 4세 아동인데 AI는 6세 이상~15세 이하 구간(3%)으로 착. (정답: 6세 미만 무료)
  • 의료기관 종별 착각 [q08, q09]
    • q08, q09: 1세 미만은 1차가 무료, 2/3차가 5%인데, AI는 거꾸로 1차를 5%, 2차를 0%로 대답. 표의 앞뒤를 헷갈린 전형적인 환각(Hallucination).
  • 질환 카테고리 착각 [q22]
    • q22: 조현병은 5%인데, AI는 조현병 외 정신질환(10%)으로 착각해 780,000원의 10%인 78,000원을 도출.

이 부분은 Few-shot만으로는 잡기 힘들다. Step 3에서 Chain-of-Thought(CoT)를 도입하여, 모델이 속으로 "1단계: 환자의 나이는 4세다. -> 2단계: 표에서 4세는 '6세 미만' 카테고리에 속한다. -> 3단계: 따라서 무료다"라고 스스로 논리의 사슬을 밟아나가게 만들면 해결될 것이다.

 


 

 

[2-1]: Few-shot Prompting (예시 추가)

 

 

Zero-shot의 어쩌면 참담해보이는 결과를 볼 수 있었다. 시스템 프롬프트에 3개의 질문-답변 예시를 추가했다. 모델에게 "금액을 물어보면 계산을 해서 원 단위로 답해야 해!"라는 뉘앙스를 심어주기 위함이다.

 

프롬프트 전문

# 가설: Few-shot 예시를 통해 "콤마, '원' 포함", "0%는 무료로 표기" 등의 포맷을 학습시킴
system_prompt_v2 = f"""아래는 의료급여 본인부담률 참조 데이터입니다. 
질문에 대해 정확한 본인부담률 또는 본인부담금을 답하세요. 답만 간결하게 작성하세요.

[답변 작성 예시]
Q: 2종 수급권자인 생후 8개월 아기가 만성질환자이고 제2차의료급여기관에서 외래 진료를 받으면 본인부담률은?
A: {{"reason": "1세 미만 만성질환자 + 제2차의료급여기관 = 무료", "answer": "무료"}}

Q: 1종 수급권자가 디스크로 복잡추나를 받았고 치료비가 100,000원입니다. 본인부담금은?
A: {{"reason": "디스크 + 1종 복잡추나 = 30%. 100,000 * 0.3 = 30,000원", "answer": "30,000원"}}

Q: 65세 이상 2종 수급권자가 틀니(시술비 1,000,000원)와 임플란트(시술비 1,000,000원)를 동시에 하면?
A: {{"reason": "2종 틀니 15%, 임플란트 20%", "answer": "틀니 150,000원, 임플란트 200,000원"}}

[본인부담률 참조 데이터]
{copayment_reference}
"""

 


 

[2-2]: 오답 로그 심층 분석 (정답률: 70% = 21/30 문항 정답)

 

 

1. 맞게 풀었는데 또 포맷(형식)이 다름

  • [q12] AI: 10% / 정답: 병원급 이상 10% (조건부 문구 누락)
  • [q15] AI: 적용되지 않음 / 정답: 해당되지 않음 (동의어 사용)

LLM은 정답의 뉘앙스를 정확히 파악했지만, 평가 스크립트가 'Exact Match(정확한 문자열 포함)'를 요구하기 때문에 오답 처리되었다. 이런 경우에는 Few-shot(예시)을 몇 개 더 던져주면, "아, 이런 형식과 단어를 써서 대답해야 하는구나!" 하고 교정될 영역이다.

# 수정
Q: 65세 이상 2종 수급권자가 틀니를 할 때 본인부담 보상제나 상한제가 적용되나요?
A: {{"reason": "틀니/임플란트는 본인부담 보상제 및 상한제 제외 항목임", "answer": "해당되지 않음"}}


# 정답률 개선
========================================
 📊 최종 정답률: 23/30 (76.7%)
========================================

 

2. 정보 계층(Hierarchy) 및 예외 규칙 혼동

  • [q30] 생후 6개월 만성질환 아기 MRI (2차)
    • AI의 착각: "아하! 1세 미만 만성질환자 2차 외래는 무료지!" (일반 외래 규칙 적용)
    • 실제 정답: CT/MRI/PET의 특수 규칙이 우선 적용되어 5% (26,000원)
  • [q11, q19] 고위험 임신부 입원
    • AI의 착각: "임신부 입원이네? 자연분만처럼 무료겠지!"
    • 실제 정답: 임신부라도 고위험 임신부는 예외적으로 5%

LLM은 텍스트를 위에서 아래로 읽는다. 일반적인 규칙(외래 무료)을 먼저 보고 섣불리 결론을 내린 뒤, 밑에 있는 더 구체적인 특수 규칙(MRI, 고위험 임신부 예외)을 교차 검증하지 못하는 전형적인 논리적 약점(환각)을 보였다.

 

3. 숫자 경계값(Age Boundary) 및 카테고리 매핑 실패

  • [q03] 4세 아동: 6세 미만(무료)인데, AI는 6세 이상~15세 이하(3%)로 분류.
  • [q22] 조현병: 조현병(5%)인데, AI는 조현병 외 정신질환(10%)의 비율을 가져와서 계산.
  • [q27] 16세 청소년 치아 홈메우기: 명백히 표에 '16세 이상~18세 이하(5%)'가 있는데, AI는 추론 과정에 "16세 이상은 해당되지 않음"이라는 없는 사실을 지어내어(Hallucination) 무료로 처리.

숫자의 크고 작음을 비교하여 정확한 구간(Range)에 집어넣는 능력이 Zero-shot에서는 매우 불안정합니다.

 

 


 

 

[3-1]: Chain of Thought (CoT, 생각의 사슬)

 

 

프롬프트 엔지니어링의 꽃이라 할 수 있는 CoT를 적용할 차례이다. Pydantic 스키마의 reason 필드를 적극 활용하여, 모델이 최종 answer를 내뱉기 전에 반드시 단계별 논리적 추론 과정을 먼저 작성하도록 유도한다.

 

프롬프트 전문

# 가설: 다중 조건(종별, 연령, 질환, 의료기관 등)을 단계별로 분해하여 추론하도록 강제하면, 
# 표의 위계나 예외 조건을 놓치는 환각(Hallucination)이 대폭 감소할 것이다.
system_prompt = f"""당신은 국민건강보험공단의 본인부담률 산정 전문가입니다. 
아래 의료급여 본인부담률 참조 데이터를 바탕으로 질문에 답하세요.

[추론 지침 - Chain of Thought]
최종 정답(answer)을 도출하기 전에 'reason' 필드에 반드시 다음 단계를 거쳐 생각하세요:
1단계: 수급자 종별(1종/2종) 파악
2단계: 환자의 연령 및 특이사항(임신, 질환 유무, 만성질환 등) 파악
3단계: 진료 항목(입원/외래, 치료 종류, 검사 종류) 및 의료기관 종별(1차/2차/3차) 파악
4단계: 참조 데이터에서 위 조건들에 완벽히 부합하는 본인부담률(%) 찾기 (일반 규칙보다 특수/예외 규칙 우선 적용)
5단계: 만약 진료비/치료비 금액이 주어졌다면, 찾은 비율을 곱하여 최종 본인부담금(원) 계산하기 (예: 1,000,000 * 0.05 = 50,000원)

[본인부담률 참조 데이터]
{copayment_reference}
"""
    try:
        response = client.beta.chat.completions.parse(
            model=MODEL,
            messages=[
                {"role": "system", "content": system_prompt},
                {"role": "user", "content": f"Q: {question}"}
            ],
            response_format=CopaymentResponse,
            temperature=0,
        )
        return response.choices[0].message.parsed
    except Exception as e:
        print(f"Step 3 Error: {e}")
        return None

response_format=CopaymentResponse, client.beta.chat.completions.parse pydantic BaseModel로 추론 과정 구조화. 프롬프트에서 "반드시 reason 필드에 다음 단계를 거쳐 생각하세요"라고 명시하고, 이를 Pydantic 모델의 특정 필드로 받도록 설계했기 때문에 추론의 논리적 흐름이 구조적으로 격리되어 저장.


 

[3-2]: 오답 로그 심층 분석 (정답률: 66.7% = 20/30 문항 정답)

 

 

1. Few-shot(예시)의 부재로 인한 억울한 오답 대거 발생

이번 Step 3 프롬프트에서는 '생각하는 방법(CoT)'을 강제하느라, Step 2에 넣었던 '답변 작성 예시(Few-shot)'를 뺐다. 그 결과 LLM의 폼(Format)이 완전히 무너졌다.

  • [q03, q04, q08, q14]: 정답은 무료인데, AI는 수식 계산에 심취한 나머지 0%라고 답변해서 모조리 오답 처리.
  • [q26, q28]: 정답은 틀니 75,000원, 임플란트 100,000원인데, AI는 틀니 본인부담금: 75,000원처럼 쓸데없이 친절한 문구를 붙여서 오답 처리.
  • 인사이트: CoT(추론 지침)는 모델의 '논리력'을 올려주지만, '출력 형식'을 보장하지는 않는다. 즉, 완벽한 프롬프트를 위해서는 "CoT + Few-shot"이 결합되어야만 한다는 것을 완벽하게 증명한다.

2. CoT로 다중 조건의 함정(Hard 문항) 돌파 성공

이전에 세웠던 "가설 3: CoT에서 가장 큰 점프가 일어날 것이다"가 완벽하게 적중했다. 미세한 예외 조항을 놓치지 않았다.

  • [q30] 6개월 만성질환 아기 MRI (2차)
    • Step 1에서는 일반 외래 규칙을 적용해 무료라고 틀렸지만, Step 3에서는 속으로 '3단계: 진료 항목은 MRI...' 라고 짚어내며 특수 규칙인 5% (26,000원)를 정확히 찾아냈다.
  • [q19] 고위험 임신부 진료비 (3.5M)
    • Step 1에서는 임신부니까 무조건 무료라고 오답을 냈지만, Step 3에서는 '4단계: 고위험 임신부 입원은 5%'라고 예외 조건을 정확히 캐치해 정답(175,000원)을 맞췄다.

3. 여전히 남은 한계: LLM의 '텍스트 난독증(Attention Slips)'

그럼에도 불구하고 순수하게 논리적으로 틀린 문항이 딱 2개 있었다.

  • [q11]: '고위험 임신부'라고 2단계에서 잘 파악해 놓고, 4단계에서 "고위험 임신부 입원은 무료입니다"라고 없는 사실을 지어냈다(환각).
  • [q22]: '조현병 환자(5%)'라고 파악해 놓고, 4단계에서 슬쩍 '조현병 외 정신질환(10%)'의 비율을 가져와서 계산을 틀려버렸다.
  • 인사이트: 모델이 단계를 나누어 생각하더라도, 텍스트가 밀집된 표 데이터를 읽다 보면 순간적으로 엉뚱한 줄의 데이터를 가져오는 실수를 한다.

 

[3-2-개선]: CoT + Few-shot (정답률: 83.3% = 25/30 문항 정답)

 

1. 억울한 오답(포맷)은 완벽히 해결

아까 Step 3에서 우리를 괴롭혔던 0% 출력 문제(q04, q08, q14)와 쓸데없이 친절한 문구(q26, q28)는 이번 로그에서 모두 ✅ 정답으로 바뀌었다. 이것이 Few-shot(예시)이 폼을 고정했기 때문이다. 의도한 대로 정확히 작동했다.

 

2. 부작용 1: Few-shot 예시에 과적합(Overfitting) 되어버린 AI [q09]

  • 현상: q09는 단순 8개월 아기라 정답이 5%인데, AI는 갑자기 "1세 미만 만성질환자 제2차 외래는 무료"라고 없는 병(만성질환)을 지어내서 무료라고 틀렸다.
  • 원인: Few-shot 예시로 하필 "생후 8개월 아기가 만성질환자이고..." 라는 Q&A를 넣어주었다. LLM이 그 예시를 너무 감명 깊게 읽은 나머지, 본 문제에서 '8개월 아기'를 보자마자 "아! 예시에 있던 그 만성질환 아기구나!" 하고 자기가 본 예시에 과적합되어 착각해 버린 것이다. (이걸 프롬프트 엔지니어링에서는 Few-shot Bias라고 부른다.)

3. 부작용 2: 프롬프트가 길어지면서 생긴 난독증 (Attention Slips) [q11, q19]

  • 현상: 아까 Step 3에서는 고위험 임신부(q19)를 175,000원으로 기가 막히게 맞췄는데, 이번엔 q11과 q19 모두 "고위험 임신부 입원은 무료"라고 당당하게 오답을 내버렸다.
  • 원인: 시스템 프롬프트에 CoT 지시사항과 Few-shot 예시 4개가 추가되면서 전체 텍스트 길이가 훅 길어졌다. LLM은 문맥(Context)이 길어지면 표 데이터의 미세한 예외 조항(고위험 임신부 5%)을 놓치고, 뇌피셜("임신부 분만은 다 무료지~")로 퉁쳐버리는 Attention(주의력) 저하 현상이 발생한다.

 


 

 

 

[4-1]: Self-Consistency (자기 일관성)

 

 

하지만 CoT만으로도 여전히 틀리는 극악의 Hard 문항들이 있었다. 이를 해결하기 위해 최후의 보루로 사용되는 Self-Consistency(CoT + 다수결 투표)를 적용했다. 또한, Step3에서 발생했던 출력 폼 무너짐 문제를 해결하기 위해 Few-shot까지 결합한다.

  • 구현 방식: 동일한 질문에 대해 temperature=0.7로 설정하여 창의성(무작위성)을 부여한 뒤, 모델을 5번 반복 호출. 5번의 응답 중 가장 많이 나온 answer를 최종 정답으로 채택하는 로직을 Python collections.Counter를 이용해 구현.
    • LLM의 일시적인 난독증이나 계산 실수를 집단 지성으로 덮어버리는 기법.
# 4. Step 4: Self-Consistency (CoT + Few-shot + Majority Vote)
def run_step4_self_consistency(question: str, num_votes: int = 5):
    # 가설: CoT로 논리를, Few-shot으로 형식을 고정하고, 5번의 다수결을 통해 LLM의 일시적 난독증(오류)을 완벽히 제거한다.
    system_prompt = f"""당신은 국민건강보험공단의 본인부담률 산정 전문가입니다. 
아래 의료급여 본인부담률 참조 데이터를 바탕으로 질문에 답하세요.

[추론 지침 - Chain of Thought]
최종 정답(answer)을 도출하기 전에 'reason' 필드에 반드시 다음 단계를 거쳐 생각하세요:
1단계: 수급자 종별(1종/2종) 파악
...
5단계: 진료비/치료비 금액이 주어졌다면, 찾은 비율을 곱하여 최종 본인부담금(원) 계산하기

[답변 작성 예시 (Few-shot)]
Q: 2종 수급권자인 생후 8개월 아기가 만성질환자이고 제2차의료급여기관에서 외래 진료를 받으면 본인부담률은?
A: {{"reason": "1단계: 2종. 2단계: 생후 8개월(1세 미만) 만성질환자. 3단계: 2차 외래. 4단계: 1세 미만 만성질환자 제2차 외래는 무료.", "answer": "무료"}}
...
{copayment_reference}
"""

    answers = []
    # num_votes(5번) 만큼 반복해서 질문을 던진다.
    for i in range(num_votes):
        try:
            response = client.beta.chat.completions.parse(
                model=MODEL,
                messages=[
                    {"role": "system", "content": system_prompt},
                    {"role": "user", "content": f"Q: {question}"}
                ],
                response_format=CopaymentResponse,
                # Self-Consistency의 핵심: 창의성을 주어 다양한 답변 루트를 생성시킴
                temperature=0.7, 
            )
            answers.append(response.choices[0].message.parsed.answer)
        except Exception as e:
            print(f"Step 4 Error: {e}")
            continue

 


 

[4-2]: 오답 로그 심층 분석 (정답률: 80% = 24/30 문항 정답)

 

 

Step 3에서 66.7%로 떨어졌던 점수가, Self-Consistency(다수결)와 Few-shot을 결합하고 80%로 수직 상승하긴 했다. 특히 Hard 난이도의 다중 계산 문제들(q22, q26, q28, q30)이 모두 정답으로 돌아선 것이다. 

 

하지만, CoT에 Few-shot을 결합한 케이스보다는 높은 결과가 나오지 않았다. 즉, 더 고도화된 기법인 Step 4(Self-Consistency)를 썼는데 부족해진 것이다.

 

1. Temperature 0.7(창의성)이 독이 된 케이스

  • Step 1~3까지는 temperature=0 (가장 딱딱하고 정해진 답만 냄)을 사용했다. 계산이나 표 찾기에는 이게 최적이다.
  • 하지만 Step 4의 Self-Consistency(다수결)는 여러 개의 다른 생각 루트를 뽑아내기 위해 강제로 temperature=0.7 (창의성 부여) 로 올린다.
  • 결과: LLM이 표를 읽다가 창의력을 발휘해버렸다.
    • Step 3(T=0)에서는 완벽하게 풀었던 q19(고위험 임신부 진료비)를, Step 4(T=0.7)에서는 갑자기 "임신부니까 무료!"라고 5번 중 3번 이상 창의적인 환각(Hallucination)을 일으켜 다수결에서 오답이 채택되어 버린 것이다.

2. 다수결(Majority Vote)이 억울한 오답(포맷)을 복구하지 못함

  • T=0.7 상태에서는 모델이 말도 많아진다. Few-shot으로 "해당되지 않음"이라고 답하라고 예시를 줬음에도 불구하고, q15에서 "본인부담 보상제 및 상한제 적용되지 않음"이라고 멋대로 문장을 만들어낸다.
  • 다수결 로직은 '정확히 똑같은 문자열'을 그룹핑하므로, 5번 모두 미세하게 다른 문장으로 대답하면 정답과 포맷이 어긋나버린다.