Claude로 개발하기
Claude 알아보기
- 활용 사례
- 모델 및 가격
- 보안 및 규정 준수
에이전트 및 도구
관리
테스트 및 평가
- 보안 강화
- 평가 도구 사용하기
법률 센터
강력한 실증적 평가 만들기
성공 기준을 정의한 후, 다음 단계는 해당 기준에 대한 LLM 성능을 측정하기 위한 평가를 설계하는 것입니다. 이는 프롬프트 엔지니어링 사이클의 중요한 부분입니다.
이 가이드는 테스트 케이스를 개발하는 방법에 중점을 둡니다.
평가 및 테스트 케이스 구축하기
평가 설계 원칙
- 작업별 특화: 실제 작업 분포를 반영하는 평가를 설계하세요. 엣지 케이스도 잊지 마세요!
- 관련 없거나 존재하지 않는 입력 데이터
- 지나치게 긴 입력 데이터나 사용자 입력
- [채팅 사용 사례] 부실하거나 유해하거나 관련 없는 사용자 입력
- 인간조차도 평가 합의에 도달하기 어려운 모호한 테스트 케이스
- 가능한 자동화: 자동 채점이 가능하도록 질문을 구성하세요(예: 객관식, 문자열 매칭, 코드 채점, LLM 채점).
- 품질보다 양을 우선시: 신호가 약간 낮은 자동 채점 문항이 더 많은 것이 인간이 수동으로 채점하는 고품질 평가 문항이 적은 것보다 낫습니다.
평가 예시
측정 대상: 정확한 일치 평가는 모델의 출력이 미리 정의된 정답과 정확히 일치하는지 측정합니다. 감정 분석(긍정, 부정, 중립)과 같이 명확한 범주형 답변이 있는 작업에 적합한 단순하고 모호하지 않은 지표입니다.
평가 테스트 케이스 예시: 인간이 레이블링한 감정이 있는 1000개의 트윗.
import anthropic
tweets = [
{"text": "이 영화는 완전 시간 낭비였어. 👎", "sentiment": "negative"},
{"text": "새 앨범이 🔥! 하루종일 반복재생 중.", "sentiment": "positive"},
{"text": "비행기가 5시간이나 지연되어서 너무 좋아. #최고의하루", "sentiment": "negative"}, # 엣지 케이스: 풍자
{"text": "영화의 줄거리는 끔찍했지만, 연기는 환상적이었어.", "sentiment": "mixed"}, # 엣지 케이스: 혼합된 감정
# ... 996개의 추가 트윗
]
client = anthropic.Anthropic()
def get_completion(prompt: str):
message = client.messages.create(
model="claude-3-7-sonnet-20250219",
max_tokens=50,
messages=[
{"role": "user", "content": prompt}
]
)
return message.content[0].text
def evaluate_exact_match(model_output, correct_answer):
return model_output.strip().lower() == correct_answer.lower()
outputs = [get_completion(f"다음을 'positive', 'negative', 'neutral', 또는 'mixed'로 분류하세요: {tweet['text']}") for tweet in tweets]
accuracy = sum(evaluate_exact_match(output, tweet['sentiment']) for output, tweet in zip(outputs, tweets)) / len(tweets)
print(f"감정 분석 정확도: {accuracy * 100}%")
측정 대상: 코사인 유사도는 두 벡터(이 경우 SBERT를 사용한 모델 출력의 문장 임베딩) 사이의 각도의 코사인을 계산하여 유사도를 측정합니다. 1에 가까운 값은 더 높은 유사도를 나타냅니다. 비슷한 질문은 문구가 다르더라도 의미적으로 유사한 답변을 제공해야 하므로 일관성을 평가하는 데 이상적입니다.
평가 테스트 케이스 예시: 각각 몇 가지 바꿔 쓴 버전이 있는 50개 그룹.
from sentence_transformers import SentenceTransformer
import numpy as np
import anthropic
faq_variations = [
{"questions": ["반품 정책이 어떻게 되나요?", "물건을 어떻게 반품하나요?", "반품 정책이 머에요?"], "answer": "저희의 반품 정책은..."}, # 엣지 케이스: 오타
{"questions": ["지난주에 물건을 샀는데, 기대했던 것과 좀 달라서, 혹시 반품이 가능할까 해서요?", "온라인에서 보니까 정책이 30일이라고 하던데 그게 6개월 전에 업데이트된 것 같아서, 현재 정책이 정확히 어떻게 되는지 궁금해요."], "answer": "저희의 반품 정책은..."}, # 엣지 케이스: 길고 두서없는 질문
{"questions": ["제인의 사촌인데요, 고객 서비스가 좋다고 하더라구요. 이거 반품할 수 있나요?", "레딧에서 고객 서비스에 이렇게 문의하는 게 가장 빠르대요. 맞길 바라요! 재킷 반품 기간이 어떻게 되나요?"], "answer": "저희의 반품 정책은..."}, # 엣지 케이스: 관련 없는 정보
# ... 47개의 추가 FAQ
]
client = anthropic.Anthropic()
def get_completion(prompt: str):
message = client.messages.create(
model="claude-3-7-sonnet-20250219",
max_tokens=2048,
messages=[
{"role": "user", "content": prompt}
]
)
return message.content[0].text
def evaluate_cosine_similarity(outputs):
model = SentenceTransformer('all-MiniLM-L6-v2')
embeddings = [model.encode(output) for output in outputs]
cosine_similarities = np.dot(embeddings, embeddings.T) / (np.linalg.norm(embeddings, axis=1) * np.linalg.norm(embeddings, axis=1).T)
return np.mean(cosine_similarities)
for faq in faq_variations:
outputs = [get_completion(question) for question in faq["questions"]]
similarity_score = evaluate_cosine_similarity(outputs)
print(f"FAQ 일관성 점수: {similarity_score * 100}%")
측정 대상: ROUGE-L(Recall-Oriented Understudy for Gisting Evaluation - Longest Common Subsequence)은 생성된 요약의 품질을 평가합니다. 후보 요약과 참조 요약 사이의 가장 긴 공통 부분 수열의 길이를 측정합니다. 높은 ROUGE-L 점수는 생성된 요약이 주요 정보를 일관된 순서로 포착했음을 나타냅니다.
평가 테스트 케이스 예시: 참조 요약이 있는 200개의 기사.
from rouge import Rouge
import anthropic
articles = [
{"text": "획기적인 연구에서 MIT의 연구진들이...", "summary": "MIT 과학자들이 새로운 항생제를 발견..."},
{"text": "지역 영웅 제인 도우가 지난주 구조로 헤드라인을 장식했습니다... 시청 소식으로는 예산이... 기상학자들은 예측...", "summary": "지역사회가 지역 영웅 제인 도우를 축하하는 한편 시는 예산 문제로 고심"}, # 엣지 케이스: 다중 주제
{"text": "이 연예인이 한 일을 믿을 수 없을 거예요! ... 광범위한 자선 활동 ...", "summary": "연예인의 광범위한 자선 활동에 팬들 놀라워"}, # 엣지 케이스: 오해의 소지가 있는 제목
# ... 197개의 추가 기사
]
client = anthropic.Anthropic()
def get_completion(prompt: str):
message = client.messages.create(
model="claude-3-7-sonnet-20250219",
max_tokens=1024,
messages=[
{"role": "user", "content": prompt}
]
)
return message.content[0].text
def evaluate_rouge_l(model_output, true_summary):
rouge = Rouge()
scores = rouge.get_scores(model_output, true_summary)
return scores[0]['rouge-l']['f'] # ROUGE-L F1 점수
outputs = [get_completion(f"이 기사를 1-2문장으로 요약하세요:\n\n{article['text']}") for article in articles]
relevance_scores = [evaluate_rouge_l(output, article['summary']) for output, article in zip(outputs, articles)]
print(f"평균 ROUGE-L F1 점수: {sum(relevance_scores) / len(relevance_scores)}")
측정 대상: LLM 기반 리커트 척도는 주관적인 태도나 인식을 판단하기 위해 LLM을 사용하는 심리측정 척도입니다. 여기서는 응답의 톤을 1에서 5까지의 척도로 평가하는 데 사용됩니다. 전통적인 지표로는 정량화하기 어려운 공감, 전문성, 인내심과 같은 미묘한 측면을 평가하는 데 이상적입니다.
평가 테스트 케이스 예시: 목표 톤(공감적, 전문적, 간결)이 있는 100개의 고객 문의.
import anthropic
inquiries = [
{"text": "이번이 세 번째로 주문을 망치셨네요. 당장 환불해주세요!", "tone": "empathetic"}, # 엣지 케이스: 화난 고객
{"text": "비밀번호를 재설정하려고 했더니 계정이 잠겼어요...", "tone": "patient"}, # 엣지 케이스: 복잡한 문제
{"text": "제품이 너무 좋아서 믿을 수가 없어요. 다른 제품들은 이제 볼 수도 없네요!", "tone": "professional"}, # 엣지 케이스: 칭찬을 불만으로
# ... 97개의 추가 문의
]
client = anthropic.Anthropic()
def get_completion(prompt: str):
message = client.messages.create(
model="claude-3-7-sonnet-20250219",
max_tokens=2048,
messages=[
{"role": "user", "content": prompt}
]
)
return message.content[0].text
def evaluate_likert(model_output, target_tone):
tone_prompt = f"""이 고객 서비스 응답이 얼마나 {target_tone}한지 1-5 척도로 평가하세요:
<response>{model_output}</response>
1: 전혀 {target_tone}하지 않음
5: 완벽하게 {target_tone}함
숫자만 출력하세요."""
# 일반적으로 평가 대상 출력을 생성한 모델과 다른 모델을 사용하여 평가하는 것이 좋습니다
response = client.messages.create(model="claude-3-opus-20240229", max_tokens=50, messages=[{"role": "user", "content": tone_prompt}])
return int(response.content[0].text.strip())
outputs = [get_completion(f"이 고객 문의에 응답하세요: {inquiry['text']}") for inquiry in inquiries]
tone_scores = [evaluate_likert(output, inquiry['tone']) for output, inquiry in zip(outputs, inquiries)]
print(f"평균 톤 점수: {sum(tone_scores) / len(tone_scores)}")
측정 대상: 이진 분류는 입력이 두 클래스 중 하나에 속하는지 판단합니다. 여기서는 응답에 PHI가 포함되어 있는지 여부를 분류하는 데 사용됩니다. 이 방법은 규칙 기반 시스템에서 놓칠 수 있는 미묘하거나 암시적인 형태의 PHI를 맥락을 이해하고 식별할 수 있습니다.
평가 테스트 케이스 예시: PHI가 포함된 것과 포함되지 않은 500개의 시뮬레이션된 환자 질문.
import anthropic
patient_queries = [
{"query": "리시노프릴의 부작용은 무엇인가요?", "contains_phi": False},
{"query": "존 도우(생년월일: 1980년 5월 12일)가 왜 메트포민을 처방받았는지 알려주실 수 있나요?", "contains_phi": True}, # 엣지 케이스: 명시적 PHI
{"query": "제 친구 앨리스(1985년 7월 4일생)가 당뇨병이 있다면 어떻게...", "contains_phi": True}, # 엣지 케이스: 가상의 PHI
{"query": "아들이 걱정돼요. 작년에 아버지가 처방받은 것과 같은 약을 처방받았거든요.", "contains_phi": True}, # 엣지 케이스: 암시적 PHI
# ... 496개의 추가 질문
]
client = anthropic.Anthropic()
def get_completion(prompt: str):
message = client.messages.create(
model="claude-3-7-sonnet-20250219",
max_tokens=1024,
messages=[
{"role": "user", "content": prompt}
]
)
return message.content[0].text
def evaluate_binary(model_output, query_contains_phi):
if not query_contains_phi:
return True
binary_prompt = """이 응답에 개인건강정보(PHI)가 포함되어 있거나 참조되어 있나요?
PHI는 의료 서비스를 제공하는 과정에서 생성, 사용 또는 공개되는 개인 식별 가능한 건강 데이터를 의미합니다. 여기에는 개인의 신체적 또는 정신적 건강 상태, 해당 개인에 대한 의료 서비스 제공 또는 그러한 치료에 대한 지불과 관련된 정보가 포함됩니다.
PHI의 주요 측면:
- 식별자: 이름, 주소, 생년월일, 사회보장번호, 의료기록번호 등
- 건강 데이터: 진단, 치료 계획, 검사 결과, 약물 기록 등
- 재무 정보: 보험 세부사항, 지불 기록 등
- 의사소통: 의료 제공자의 메모, 건강에 관한 이메일이나 메시지 등
<response>{model_output}</response>
'yes' 또는 'no'만 출력하세요."""
# 일반적으로 평가 대상 출력을 생성한 모델과 다른 모델을 사용하여 평가하는 것이 좋습니다
response = client.messages.create(model="claude-3-opus-20240229", max_tokens=50, messages=[{"role": "user", "content": binary_prompt}])
return response.content[0].text.strip().lower() == "no"
outputs = [get_completion(f"당신은 의료 보조원입니다. 응답에서 절대로 PHI를 공개하지 마세요. PHI는 의료 서비스를 제공하는 과정에서 생성, 사용 또는 공개되는 개인 식별 가능한 건강 데이터를 의미합니다. 여기에는 개인의 신체적 또는 정신적 건강 상태, 해당 개인에 대한 의료 서비스 제공 또는 그러한 치료에 대한 지불과 관련된 정보가 포함됩니다. 질문은 다음과 같습니다: {query['query']}") for query in patient_queries]
privacy_scores = [evaluate_binary(output, query['contains_phi']) for output, query in zip(outputs, patient_queries)]
print(f"개인정보 보호 점수: {sum(privacy_scores) / len(privacy_scores) * 100}%")
측정 대상: 리커트 척도와 유사하게, 서열 척도는 고정된 순서가 있는 척도(1-5)로 측정합니다. 모델이 대화 기록을 참조하고 활용하는 정도를 포착할 수 있어 일관되고 개인화된 상호작용을 위한 맥락 활용을 평가하는 데 완벽합니다.
평가 테스트 케이스 예시: 맥락 의존적인 질문이 있는 100개의 다중 턴 대화.
import anthropic
conversations = [
[
{"role": "user", "content": "포메라니안을 새로 입양했어요!"},
{"role": "assistant", "content": "새로운 털복숭이 친구를 맞이하신 것을 축하드려요! 처음 키우시는 강아지인가요?"},
{"role": "user", "content": "네, 맞아요. 이름은 루나예요."},
{"role": "assistant", "content": "루나라는 이름이 정말 예쁘네요! 처음 강아지를 키우시는 분으로서 궁금하신 점이 있으실 것 같은데요. 루나를 돌보는 것에 대해 어떤 점이 궁금하신가요?"},
...
{"role": "user", "content": "이 특정 견종을 키울 때 알아야 할 점은 무엇인가요?"} # 엣지 케이스: 훨씬 이전 맥락에 의존
],
[
{"role": "user", "content": "독서 모임에서 '앵무새 죽이기'를 읽고 있어요."},
{"role": "assistant", "content": "좋은 선택이네요! 하퍼 리의 '앵무새 죽이기'는 인종 차별과 도덕적 성장을 탐구하는 고전입니다. 지금까지 어떠신가요?"},
{"role": "user", "content": "정말 강렬해요. 그런데 스카웃의 생일이 언제였죠?"}, # 엣지 케이스: 갑작스러운 주제 전환
{"role": "assistant", "content": "죄송하지만 '앵무새 죽이기'에서 스카웃의 정확한 생일이 언급된 것은 기억나지 않네요. 소설은 그런 구체적인 세부사항에 중점을 두지 않습니다. 대신 스카웃의 성장과 여러 해에 걸친 경험에 초점을 맞추고 있어요. 책의 특정 부분이나 스카웃의 발달 과정에 대해 논의하고 싶으신가요?"},
{"role": "user", "content": "아, 그렇군요. 그럼 전통적인 남부 스타일 케이크 레시피를 추천해주실 수 있나요?"} # 엣지 케이스: 또 다른 주제 전환
],
# ... 98개의 추가 대화
]
client = anthropic.Anthropic()
def get_completion(prompt: str):
message = client.messages.create(
model="claude-3-7-sonnet-20250219",
max_tokens=1024,
messages=[
{"role": "user", "content": prompt}
]
)
return message.content[0].text
def evaluate_ordinal(model_output, conversation):
ordinal_prompt = f"""이 응답이 대화 맥락을 얼마나 잘 활용하는지 1-5 척도로 평가하세요:
<conversation>
{"".join(f"{turn['role']}: {turn['content']}\\n" for turn in conversation[:-1])}
</conversation>
<response>{model_output}</response>
1: 맥락을 완전히 무시함
5: 맥락을 완벽하게 활용함
숫자만 출력하고 다른 것은 출력하지 마세요."""
# 일반적으로 평가 대상 출력을 생성한 모델과 다른 모델을 사용하여 평가하는 것이 좋습니다
response = client.messages.create(model="claude-3-opus-20240229", max_tokens=50, messages=[{"role": "user", "content": ordinal_prompt}])
return int(response.content[0].text.strip())
outputs = [get_completion(conversation) for conversation in conversations]
context_scores = [evaluate_ordinal(output, conversation) for output, conversation in zip(outputs, conversations)]
print(f"평균 맥락 활용 점수: {sum(context_scores) / len(context_scores)}")
평가 채점하기
평가를 채점할 방법을 결정할 때는 가장 빠르고, 신뢰할 수 있으며, 확장 가능한 방법을 선택하세요:
-
코드 기반 채점: 가장 빠르고 신뢰할 수 있으며, 매우 확장 가능하지만, 규칙 기반의 엄격성이 덜 필요한 더 복잡한 판단에는 뉘앙스가 부족합니다.
- 정확한 일치:
output == golden_answer
- 문자열 일치:
key_phrase in output
- 정확한 일치:
-
인간 채점: 가장 유연하고 고품질이지만, 느리고 비용이 많이 듭니다. 가능하면 피하세요.
-
LLM 기반 채점: 빠르고 유연하며, 확장 가능하고 복잡한 판단에 적합합니다. 먼저 신뢰성을 테스트한 다음 확장하세요.
LLM 기반 채점 팁
- 상세하고 명확한 채점 기준 마련: “답변은 항상 첫 문장에서 ‘Acme Inc.‘를 언급해야 합니다. 그렇지 않으면 답변은 자동으로 ‘부정확’으로 채점됩니다.”
주어진 사용 사례나 심지어 해당 사용 사례의 특정 성공 기준에도 종합적인 평가를 위해 여러 채점 기준이 필요할 수 있습니다.
- 실증적이거나 구체적: 예를 들어, LLM에게 ‘정확’ 또는 ‘부정확’만 출력하도록 하거나, 1-5 척도로 판단하도록 지시하세요. 순전히 정성적인 평가는 빠르고 확장 가능한 평가가 어렵습니다.
- 추론 장려: LLM에게 평가 점수를 결정하기 전에 먼저 생각하도록 요청한 다음, 추론은 버리세요. 이는 복잡한 판단이 필요한 작업에서 특히 평가 성능을 향상시킵니다.
import anthropic
def build_grader_prompt(answer, rubric):
return f"""다음 채점 기준에 따라 이 답변을 채점하세요:
<rubric>{rubric}</rubric>
<answer>{answer}</answer>
<thinking> 태그 안에서 추론 과정을 생각한 다음, <result> 태그 안에 'correct' 또는 'incorrect'를 출력하세요."""
def grade_completion(output, golden_answer):
grader_response = client.messages.create(
model="claude-3-opus-20240229",
max_tokens=2048,
messages=[{"role": "user", "content": build_grader_prompt(output, golden_answer)}]
).content[0].text
return "correct" if "correct" in grader_response.lower() else "incorrect"
# 사용 예시
eval_data = [
{"question": "42는 생명, 우주, 그리고 모든 것에 대한 답인가요?", "golden_answer": "네, '은하수를 여행하는 히치하이커를 위한 안내서'에 따르면 그렇습니다."},
{"question": "프랑스의 수도는 어디인가요?", "golden_answer": "프랑스의 수도는 파리입니다."}
]
def get_completion(prompt: str):
message = client.messages.create(
model="claude-3-7-sonnet-20250219",
max_tokens=1024,
messages=[
{"role": "user", "content": prompt}
]
)
return message.content[0].text
outputs = [get_completion(q["question"]) for q in eval_data]
grades = [grade_completion(output, a["golden_answer"]) for output, a in zip(outputs, eval_data)]
print(f"점수: {grades.count('correct') / len(grades) * 100}%")