Amazon Bedrock에서 LLM 평가하기
Amazon Bedrock과 W&B Weave를 함께 활용해 요약 작업에 대한 대규모 언어 모델(LLM)을 평가하고 비교하는 방법을 알아보세요. Bedrock의 관리형 인프라와 Weave의 고급 평가 기능을 결합해 효율적으로 벤치마크하고 분석할 수 있습니다.
이 글은 AI 번역본입니다. 오역이 의심되면 댓글로 자유롭게 알려주세요.
Created on September 12|Last edited on September 12
Comment
조직들이 방대한 정보량과 씨름하면서, 요약 작업 산업 전반의 워크플로에서 핵심 구성 요소로 자리 잡았습니다. 연구 논문을 요약하고, 재무 보고서를 압축하며, 비즈니스 문서에서 인사이트를 추출하는 등 어떤 작업이든 간결하고 일관된 요약을 생성하는 능력은 빠르게 변화하는 환경에서 경쟁력을 유지하는 데 필수적입니다. 대규모 언어 모델 이 과제의 최전선에 서 있으며, 인간에 필적하는 자동 요약 기능을 제공합니다.

Weave Evaluations 대시보드
이 글은 Amazon Bedrock과 W&B Weave 용도를 위한 LLM 평가 요약 작업에서. Bedrock의 인프라와 Weave의 시각화 및 분석 도구를 결합하면, 다양한 사용 사례에 가장 적합한 모델을 체계적으로 비교해 찾을 수 있습니다.
Jump to the tutorial
다룰 내용
Amazon Bedrock에서 사용할 수 있는 파운데이션 모델W&B Weave와 Bedrock을 활용한 LLM 요약 평가W&B Weave와 Bedrock을 활용한 LLM 요약 평가1단계: AWS 계정과 결제를 설정하세요2단계: Amazon Bedrock에 액세스하기3단계: AWS CLI 구성하기4단계: 필요한 Python 라이브러리 설치하기5단계: Bedrock 모델 액세스 요청 및 추론 프로필 구성데이터셋 생성하기Weave로 Llama 모델 요약 성능 평가Llama 대 Amazon Nova왜 Amazon Bedrock을 선택할까요?결론관련 문서
Amazon Bedrock에서 사용할 수 있는 파운데이션 모델
Amazon Bedrock는 다양한 요구 사항을 충족하면서도 기본 인프라를 직접 관리할 필요 없이 폭넓은 LLM에 유연하게 접근할 수 있는 플랫폼을 제공합니다. 이 플랫폼은 폐쇄형과 오픈 소스 모델을 모두 포함해, 매우 다양한 사용 사례에 맞는 선택지를 제공합니다. 베드록 다음과 같은 주요 제공업체의 모델을 지원합니다 앤트로픽, 메타, 및 미스트랄, 그리고 Amazon의 자체 Titan 모델까지. 이처럼 다양한 선택지는 대화형 AI, 요약, 고처리량 애플리케이션과 같은 작업에 최적화된 도구에 접근할 수 있도록 보장하며, 특정 운영 목표에 부합하는 모델을 유연하게 선택할 수 있게 합니다.
생성 작업을 넘어, Bedrock은 다음과 같은 특화 모델도 지원합니다 임베딩, 의미 기반 검색, 클러스터링, 분류 작업을 수행할 수 있습니다. 이처럼 다양한 제공으로 Bedrock은 최첨단 비공개 모델의 성능이 필요하든 사용자 맞춤형 오픈 소스의 유연성이 필요하든, 가장 적합한 도구를 선택할 수 있도록 보장하며, 다음 두 가지 모두에 대한 지원과 함께 제공됩니다 텍스트 생성 및 임베딩 기반 애플리케이션입니다.
W&B Weave와 Bedrock을 활용한 LLM 요약 평가
Weave Evaluations는 Weave 프레임워크 내부의 전용 도구로, 생성형 AI 모델을 효과적으로 벤치마크하고 비교하도록 설계되었습니다. 다양한 모델에 손쉽게 접근할 수 있는 Amazon Bedrock과 결합하면, 모델 성능을 평가하기 위한 강력한 솔루션이 됩니다.
Bedrock은 오픈 소스와 상용 모델 모두에 쉽게 접근할 수 있게 해 주어, 사용자가 다양한 옵션을 빠르게 실험해 볼 수 있습니다. Bedrock과 함께 Weave Evaluations를 활용하면 모델을 효율적으로 벤치마크하고, 출력 결과를 분석하며, 핵심 지표 전반의 성능을 시각화할 수 있습니다. 이 조합을 통해 비용, 정확도, 속도, 출력 품질 등 모델 간 트레이드오프를 더 깊이 이해할 수 있습니다.
나란히 비교와 동적 시각화를 통해, Weave Evaluations는 사용자가 자신의 구체적인 사용 사례에 가장 적합한 모델을 현명하게 선택할 수 있도록 지원하며, Bedrock의 방대한 모델 카탈로그를 탐색하는 과정을 간소화합니다.
다음은 Weave Evaluations 대시보드 예시로, 모델 성능을 시각화하는 훌륭한 방법을 제공합니다:

W&B Weave와 Bedrock을 활용한 LLM 요약 평가
Amazon Bedrock을 시작하려면 계정 설정과 액세스를 위한 몇 가지 간단한 단계를 따르면 됩니다. 기반 AI 모델및 통합에 필요한 도구를 구성합니다. 여기서는 AWS 계정 생성과 청구 활성화부터 AWS CLI 구성, 그리고 Bedrock의 다양한 모델 탐색까지 초기 설정 과정을 단계별로 안내합니다.
이미 Bedrock에 접근 권한이 있는 AWS 계정을 보유하고 있다면 다음을 수행할 수 있습니다:
Jump past the AWS & Bedrock set up
이 단계를 따르면 텍스트 생성, 임베딩, 멀티모달 처리와 같은 작업에 Bedrock의 강력한 AI 기능을 빠르게 활용할 수 있습니다.
1단계: AWS 계정과 결제를 설정하세요
아직 AWS 계정이 없다면 먼저 계정을 생성하세요. 가입하기 AWS 웹사이트에서 가입한 뒤 계정에서 결제가 활성화되어 있는지 확인하세요. 결제는 Bedrock을 포함한 AWS 서비스를 사용하기 위한 필수 조건이며, 사용량 기반 과금 모델로 운영됩니다. AWS 콘솔의 Billing and Cost Management 섹션으로 이동해 계정이 사용할 준비가 되었는지 확인하세요.
2단계: Amazon Bedrock에 액세스하기
AWS Management Console에서 서비스 메뉴에 있는 “Bedrock”을 검색하세요. Bedrock 콘솔을 열면 사용 가능한 기반 모델의 개요가 표시됩니다. Bedrock은 Anthropic, Meta, AI21 Labs, Mistral, Stability AI, Amazon의 Titan 모델에 대한 액세스를 제공합니다. 이들 모델은 텍스트 생성, 임베딩, 멀티모달 처리와 같은 작업을 지원하므로 사용 사례에 맞는 다양한 선택지를 제공합니다.

3단계: AWS CLI 구성하기
Bedrock을 프로그래밍적으로 사용하려면 AWS Command Line Interface를 설치하세요. 계속해서 다음 단계를 따르세요. AWS CLI 설치 가이드 사용 중인 운영 체제에 맞는 버전을 설치하세요. 설치가 완료되면 터미널에서 다음 명령을 실행해 CLI를 초기화하세요:
aws configure
액세스 키, 시크릿 키, 기본 리전(예: 이 튜토리얼의 경우 us-east-1), 출력 형식(json)을 입력하세요. 이러한 키는 AWS 콘솔의 Security Credentials에서 새 액세스 키를 생성하여 발급할 수 있습니다. 새 액세스 키를 만들려면 AWS Management Console 오른쪽 상단의 계정 이름을 클릭하고 드롭다운 메뉴에서 Security Credentials를 선택하세요. Access Keys 섹션으로 스크롤한 뒤 Create Access Key를 클릭합니다. 생성이 완료되면 액세스 키 ID와 시크릿 키를 안전한 위치에 저장하세요. 이후 AWS CLI 또는 SDK를 구성할 때 필요합니다.

다음으로 “Access Keys” 섹션이 표시됩니다:

4단계: 필요한 Python 라이브러리 설치하기
Bedrock을 W&B Weave와 연동하려면 필요한 Python 라이브러리를 설치하세요. Python 환경에서 다음 명령을 실행하세요:
pip install boto3 botocore weave wandb
이 라이브러리들은 Bedrock 모델에 요청을 보내고, 응답을 기록하며, W&B Weave에서 평가 결과를 시각화할 수 있게 해줍니다.
5단계: Bedrock 모델 액세스 요청 및 추론 프로필 구성
다음으로, 사용하려는 모델에 대한 액세스를 요청해야 합니다. 아래 스크린샷에서 “Providers” 탭을 클릭하면, 사용 가능한 모델에 “Request Model Access” 버튼이 표시되며 이를 통해 해당 모델에 대한 액세스를 요청할 수 있습니다.
다음으로 Bedrock 콘솔에서 “Cross-Region Inference” 섹션으로 이동해 모델에 사용할 수 있는 추론 프로필을 확인하세요. 여기에서 각 모델의 설명과 해당 프로필 ID를 확인할 수 있습니다. 이 ID를 사용해 특정 모델로 API 요청을 라우팅하게 됩니다.
예를 들어 Claude와 Llama를 모두 평가하는 경우, Bedrock API를 통해 요청을 보낼 때 각각의 프로필 ID가 필요합니다.

제가 사용하는 추론 프로필은 다음과 같습니다:

데이터셋 생성하기
요약 작업에서 LLM의 성능을 평가하려면 실제 환경의 난점을 반영하는 신뢰할 수 있는 데이터셋이 필요합니다. 이 글에서는 풍부한 학술 콘텐츠 저장소인 arXiv의 연구 논문을 데이터 소스로 사용합니다.
목표는 머신러닝과 인공지능 주제와 관련된 논문을 추출하고 요약하는 것입니다. 이 논문들은 Amazon Bedrock을 통해 접근하는 LLM의 요약 능력을 평가하기 위한 다양하고 도전적인 테스트베드로 활용됩니다. 자동화된 논문 다운로드와 Anthropic의 Claude 모델이 생성한 요약문 Bedrock에서 각 논문의 연구 제목, 추출된 본문, 간결한 요약을 포함하는 구조화된 데이터셋을 생성합니다.
Claude의 고급 기능 덕분에 생성된 요약은 일관성 있을 뿐 아니라 연구의 핵심을 효과적으로 포착합니다. 데이터셋은 처리와 평가를 용이하게 하기 위해 JSONL 형식으로 저장되며, 중요한 정보를 간결하고 구조화된 출력으로 합성하는 Claude의 능력을 활용합니다. 다음은 데이터셋을 생성하는 코드입니다:
import osimport arxivimport fitz # PyMuPDFimport jsonimport boto3from botocore.exceptions import ClientErrorimport reimport randomimport timefrom time import sleep# Directory to save downloaded papersdownload_dir = "arxiv_papers"os.makedirs(download_dir, exist_ok=True)# Set up Amazon Bedrock clientbedrock_client = boto3.client("bedrock-runtime", region_name="us-east-1")MODEL_ID = "anthropic.claude-3-sonnet-20240229-v1:0"# Fixed questions for paper analysisFIXED_QUESTIONS = """What is the primary objective of this research?What methodologies or algorithms are proposed or evaluated?What datasets or experimental setups are used in this study?What are the key findings and contributions of this research?What are the implications of these findings for the broader field of AI?What limitations or challenges are acknowledged by the authors?What are the proposed future directions or next steps in this research?"""# Define AI-specific search queriessearch_queries = ["Large Language Models for vision tasks AND cat:cs.AI","Multimodal AI techniques AND cat:cs.CV","Applications of Transformers in healthcare AI AND cat:cs.LG","Few-shot learning in AI and ML AND cat:cs.LG","Vision and language models integration AND cat:cs.CV","Domain-specific fine-tuning for ML models AND cat:cs.LG","Foundational models in AI and CV applications AND cat:cs.AI","NLP in robotics and vision systems AND cat:cs.AI","Bias and fairness in AI for CV AND cat:cs.CV","Evaluation metrics for multimodal AI AND cat:cs.LG"]def download_papers(max_pages=15, max_attempts_per_query=20):"""Download one suitable paper for each query, retrying if papers exceed page limit."""papers = []downloaded_titles = set()client = arxiv.Client()for query in search_queries:paper_found = Falseattempt = 0while not paper_found and attempt < max_attempts_per_query:search = arxiv.Search(query=query,max_results=100,sort_by=arxiv.SortCriterion.SubmittedDate)try:results = list(client.results(search))start_idx = attempt * 5end_idx = start_idx + 5current_batch = results[start_idx:end_idx]for result in current_batch:if result.title not in downloaded_titles:print(f"Downloading: {result.title}")paper_id = result.entry_id.split('/')[-1]pdf_filename = f"{paper_id}.pdf"pdf_path = os.path.join(download_dir, pdf_filename)result.download_pdf(dirpath=download_dir, filename=pdf_filename)try:with fitz.open(pdf_path) as pdf:if pdf.page_count <= max_pages:papers.append({"title": result.title,"file_path": pdf_path,"arxiv_id": paper_id})downloaded_titles.add(result.title)print(f"Accepted: {result.title}")paper_found = Truebreakelse:os.remove(pdf_path)print(f"Skipped (too many pages: {pdf.page_count}): {result.title}")except Exception as e:print(f"Error checking PDF {pdf_path}: {e}")if os.path.exists(pdf_path):os.remove(pdf_path)attempt += 1if not paper_found:print(f"Attempt {attempt}/{max_attempts_per_query} for query: {query}")sleep(3)except Exception as e:print(f"Error during download: {e}")sleep(3)attempt += 1continueif not paper_found:print(f"Failed to find suitable paper for query after {max_attempts_per_query} attempts: {query}")print(f"\nSuccessfully downloaded {len(papers)} papers")return papersdef extract_text(pdf_path):"""Extract text from the entire PDF."""with fitz.open(pdf_path) as pdf:text = ""for page in pdf:text += page.get_text()return textdef generate_summary_with_claude(text, title):"""Generate a 300-word summary using Claude via Amazon Bedrock with exponential backoff."""prompt = (f"Please analyze the following research paper titled '{title}' and provide a comprehensive 300-word summary. "f"Consider these key aspects when analyzing the paper:\n\n{FIXED_QUESTIONS}\n\n"f"Based on these questions, synthesize a coherent summary that captures the essential elements "f"of the research while maintaining a natural flow. Ensure the summary is 300 words.\n\n"f"Paper content:\n\n{text}")request = {"anthropic_version": "bedrock-2023-05-31","max_tokens": 4096,"temperature": 0.0,"messages": [{"role": "user","content": [{"type": "text", "text": prompt}]}]}max_retries = 15backoff_time = 10 # Start with a 10-second delayfor attempt in range(max_retries):try:response = bedrock_client.invoke_model(modelId=MODEL_ID,body=json.dumps(request))response_body = json.loads(response["body"].read())summary = response_body["content"][0]["text"]return {"summary": summary}except ClientError as e:if e.response['Error']['Code'] == 'ThrottlingException':print(f"ThrottlingException encountered. Retrying in {backoff_time} seconds...")time.sleep(backoff_time + random.uniform(0, 1)) # Add jitterbackoff_time *= 2 # Exponential backoffelse:print(f"Error generating summary for {title}: {e}")breakprint(f"Failed to generate summary for {title} after {max_retries} retries.")return {"summary": ""}def count_words(text):"""Count words excluding punctuation and special characters."""cleaned_text = re.sub(r'[^\w\s]', ' ', text.lower())words = [word for word in cleaned_text.split() if word.strip()]return len(words)def main():# Download paperspapers = download_papers()print(f"\nDownloaded {len(papers)} papers. Generating summaries...\n")# Process papers and generate summariespaper_data = []for paper in papers:title = paper["title"]pdf_path = paper["file_path"]print(f"Processing: {title}")paper_text = extract_text(pdf_path)summary_json = generate_summary_with_claude(paper_text, title)summary_text = summary_json.get('summary', '')word_count = count_words(summary_text)paper_data.append({"title": title,"file_path": pdf_path,"summary": summary_text,"word_count": word_count,"arxiv_id": paper["arxiv_id"]})sleep(5) # Wait before processing the next paper to avoid throttling# Save to JSONL fileoutput_file = "paper_summaries.jsonl"with open(output_file, "w") as f:for entry in paper_data:json.dump(entry, f)f.write("\n")print(f"\nProcessed {len(paper_data)} papers. Results saved to {output_file}")if __name__ == "__main__":main()
이 스크립트는 먼저 연구 논문을 다운로드해 저장할 디렉터리를 설정하고, Anthropic의 Claude와 같은 모델에 접근하기 위한 Amazon Bedrock 클라이언트를 초기화합니다. 그런 다음 요약 과정을 안내하기 위한 고정 질문들을 정의하여 모든 논문에서 일관되고 목표 지향적인 출력을 보장합니다. 이 질문들은 연구 목표, 방법론, 데이터셋, 주요 발견 등 핵심 요소를 다루며, 체계적이고 포괄적인 요약을 생성하기 위한 틀로 활용됩니다.
이 스크립트는 연구 논문을 다운로드하는 과정을 자동화하며 arxiv 라이브러리입니다. 머신러닝과 AI 주제의 논문을 필터링하기 위해 특정 검색 쿼리를 사용합니다. 함수 download_papers 지정한 페이지 제한을 충족하는 논문만 처리되도록 논문을 가져옵니다. 추출된 PDF는 로컬에 저장되며, 텍스트는 다음을 사용해 추출합니다. PyMuPDF 라이브러리.
다운로드한 각 논문에 대해 generate_summary_with_claude 함수는 추출된 텍스트를 Amazon Bedrock을 통해 Claude 모델로 전송하며, 300단어 요약을 유도하도록 설계된 구조화된 프롬프트를 사용합니다. 이 프롬프트는 명료성과 일관성을 강조하며, 미리 정의된 질문들을 다루면서 연구 내용을 요약하도록 모델을 유도합니다. 스크립트에는 Bedrock API가 일시적으로 과부하 상태일 때를 대비해 대기 시간을 점차 늘리는 재시도 메커니즘이 포함되어 있어, 서비스와의 통신이 원활하고 신뢰성 있게 이루어지도록 보장합니다.
스크립트는 각 논문에서 제목과 본문을 추출하고 Claude 모델을 사용해 요약을 생성한 뒤, 제목·전체 텍스트·해당 요약을 포함한 잘 구성된 데이터셋으로 통합합니다. 요약은 JSONL 형식으로 저장되어 이후 평가 워크플로에서 손쉽게 조회하고 활용할 수 있습니다.
Weave로 Llama 모델 요약 성능 평가
Amazon Bedrock에서 제공되는 여러 Llama 모델의 성능을 효과적으로 비교하기 위해 평가 프레임워크로 W&B Weave를 사용합니다. 물론 관심 있는 어떤 모델에도 확장하여 비교 실험을 수행할 수 있습니다.
Weave 플랫폼은 미리 정의된 점수 지표에 따라 모델 출력물을 정밀하게 분석할 수 있게 해 주어, 체계적이고 효율적인 벤치마킹 환경을 제공합니다. 이번 평가에서는 서로 다른 세 가지 모델을 비교합니다.
- Llama-1B는 비용 효율성과 고처리량 애플리케이션을 위해 설계된 경량 모델입니다;
- Llama-8B는 성능과 효율의 균형이 좋은 모델이며,
- Llama-11B는 상세하고 포괄적인 출력을 생성하도록 최적화된 대용량 모델입니다.
이 설정은 이러한 LLM 간의 장단점과 트레이드오프를 반복 가능하게 파악할 수 있도록 하여, 서로 다른 요약 작업에 대한 적합성에 대한 통찰을 제공합니다.
평가 스크립트는 다음과 같습니다:
import weavefrom weave import Modelimport jsonimport boto3from botocore.exceptions import ClientErrorfrom time import sleepimport asynciofrom rouge_score.rouge_scorer import RougeScorerfrom typing import Dict, Anyimport bert_scoreimport fitzimport osfrom weave.trace.box import unboximport time# Initialize Weaveweave.init('bedrock_abstract_eval')client = boto3.client("bedrock-runtime", region_name="us-east-1")def extract_paper_text(pdf_path: str) -> str:"""Extract text from PDF paper."""if isinstance(pdf_path, weave.trace.box.BoxedStr):pdf_path = unbox(pdf_path)text = ""try:with fitz.open(unbox(pdf_path)) as pdf:for page in pdf:text += page.get_text()except Exception as e:print(f"Error extracting text from PDF {pdf_path}: {e}")return textdef format_prompt(text: str, title: str) -> str:"""Format prompt for model."""return f"""<|begin_of_text|><|start_header_id|>user<|end_header_id|>Please analyze this research paper and provide a comprehensive 300-word summary that covers:- Primary research objective- Methodology and approach- Key findings and results- Main contributions to the fieldTitle: {title}Content: {text}Please analyze this research paper and provide a comprehensive 300-word summary that covers:- Primary research objective- Methodology and approach- Key findings and results- Main contributions to the fieldGenerate a clear, coherent summary that captures the essence of the research.<|eot_id|><|start_header_id|>assistant<|end_header_id|>"""def model_forward(model_id: str, title: str, pdf_path: str) -> str:"""Core prediction logic to be called by predict methods."""max_retries = 15backoff_time = 10 # Start with 10 seconds delayfor attempt in range(max_retries):try:# Extract text from paperpaper_text = extract_paper_text(pdf_path)# Prepare requestrequest = {"prompt": format_prompt(paper_text, title),"max_gen_len": 4096,"temperature": 0.0,}print(f"Invoking model (Attempt {attempt + 1}/{max_retries})...")# Make predictionresponse = client.invoke_model(modelId=model_id,body=json.dumps(request))print("Done invoking")# Extract and clean predictionresponse_body = json.loads(response["body"].read())prediction = response_body["generation"].strip()return predictionexcept ClientError as e:if e.response['Error']['Code'] == 'ThrottlingException':print(f"ThrottlingException encountered. Retrying in {backoff_time} seconds...")time.sleep(backoff_time)backoff_time *= 2 # Exponential backoffelse:print(f"Error generating prediction with {model_id}: {e}")breakexcept Exception as e:print(f"Unexpected error: {e}")breakprint(f"Failed to generate prediction after {max_retries} retries.")return ""class Llama8B(Model):@weave.opdef predict(self, title: str, pdf_path: str) -> dict:prediction = model_forward("us.meta.llama3-1-8b-instruct-v1:0", title, pdf_path)return {"model_output": prediction}class Llama11B(Model):"""Llama 11B model."""@weave.opdef predict(self, title: str, pdf_path: str) -> dict:"""Generate prediction using Llama 11B."""prediction = model_forward("us.meta.llama3-2-11b-instruct-v1:0", title, pdf_path)return {"model_output": prediction}class Llama1B(Model):"""Llama 1B model."""@weave.opdef predict(self, title: str, pdf_path: str) -> dict:"""Generate prediction using Llama 1B."""prediction = model_forward("us.meta.llama3-2-1b-instruct-v1:0", title, pdf_path)return {"model_output": prediction}@weave.opdef bert_scorer(gt_abstract: str, model_output: dict) -> Dict[str, float]:"""Calculate BERTScore for the abstract."""if not model_output or 'model_output' not in model_output:return {'bert_score': 0.0}try:P, R, F1 = bert_score.score([model_output['model_output']],[gt_abstract],lang='en',model_type='microsoft/deberta-xlarge-mnli')return {'bert_score': float(F1.mean())}except Exception as e:print(f"Error calculating BERTScore: {e}")return {'bert_score': 0.0}@weave.opdef claude_scorer(gt_abstract: str, model_output: dict) -> dict:"""Evaluate abstract using Claude."""if not model_output or 'model_output' not in model_output:return {'claude_score': 0.0}print("claude evaluating")# client = boto3.client("bedrock-runtime", region_name="us-east-1")prompt = json.dumps({"anthropic_version": "bedrock-2023-05-31","max_tokens": 1024,"temperature": 0.0,"messages": [{"role": "user","content": [{"type": "text", "text": f'''Rate how well this generated abstract captures the key information from the ground truth abstract on a scale from 1-5, where:1: Poor - Missing most key information or seriously misrepresenting the research2: Fair - Captures some information but misses crucial elements3: Good - Captures most key points but has some gaps or inaccuracies4: Very Good - Accurately captures nearly all key information with minor omissions5: Excellent - Perfectly captures all key information and maintains accuracyGround Truth Abstract:{gt_abstract}Generated Abstract:{model_output["model_output"]}Provide your rating as a JSON object with this schema:{{"score": <integer 1-5>}}'''}]}]})try:response = client.invoke_model(modelId="anthropic.claude-3-sonnet-20240229-v1:0",body=prompt)result = json.loads(response["body"].read())score = json.loads(result["content"][0]["text"])["score"]print(score)sleep(2) # Rate limitingreturn {'claude_score': float(score)}except Exception as e:print(f"Error in Claude evaluation: {e}")return {'claude_score': 0.0}@weave.opdef rouge_scorer(gt_abstract: str, model_output: dict) -> Dict[str, float]:"""Calculate ROUGE scores for the abstract."""if not model_output or 'model_output' not in model_output:return {'rouge1_f': 0.0,'rouge2_f': 0.0,'rougeL_f': 0.0}try:scorer = RougeScorer(['rouge1', 'rouge2', 'rougeL'], use_stemmer=True)scores = scorer.score(gt_abstract, model_output['model_output'])return {'rouge1_f': float(scores['rouge1'].fmeasure),'rouge2_f': float(scores['rouge2'].fmeasure),'rougeL_f': float(scores['rougeL'].fmeasure)}except Exception as e:print(f"Error calculating ROUGE scores: {e}")return {'rouge1_f': 0.0,'rouge2_f': 0.0,'rougeL_f': 0.0}@weave.opdef compression_scorer(gt_abstract: str, model_output: dict) -> Dict[str, float]:"""Calculate compression ratio of the abstract."""if not model_output or 'model_output' not in model_output:return {'compression_ratio': 0.0}try:gt_words = len(gt_abstract.split())generated_words = len(model_output['model_output'].split())compression_ratio = min(gt_words, generated_words) / max(gt_words, generated_words)return {'compression_ratio': float(compression_ratio)}except Exception as e:print(f"Error calculating compression ratio: {e}")return {'compression_ratio': 0.0}@weave.opdef coverage_scorer(gt_abstract: str, model_output: dict) -> Dict[str, float]:"""Calculate content coverage using word overlap."""if not model_output or 'model_output' not in model_output:return {'coverage_score': 0.0}try:gt_words = set(gt_abstract.lower().split())generated_words = set(model_output['model_output'].lower().split())intersection = len(gt_words.intersection(generated_words))union = len(gt_words.union(generated_words))coverage_score = intersection / union if union > 0 else 0.0return {'coverage_score': float(coverage_score)}except Exception as e:print(f"Error calculating coverage score: {e}")return {'coverage_score': 0.0}def create_evaluation_dataset(gt_file: str):"""Create dataset from ground truth file."""dataset = []with open(gt_file, 'r') as f:for line in f:entry = json.loads(line)dataset.append({"title": entry["title"],"gt_abstract": entry["summary"],"pdf_path": entry["file_path"]})return datasetasync def run_evaluations(gt_file: str):"""Run evaluations for each model."""eval_dataset = create_evaluation_dataset(gt_file)# Initialize modelsmodels = {"llama_8b": Llama8B(),"llama_11b": Llama11B(),"llama_1b": Llama1B()}# Setup scorersscorers = [claude_scorer,rouge_scorer,compression_scorer,coverage_scorer,bert_scorer]# Run evaluationsresults = {}for model_name, model in models.items():print(f"\nEvaluating {model_name}...")evaluation = weave.Evaluation(dataset=eval_dataset,scorers=scorers,name=model_name + " Eval")results[model_name] = await evaluation.evaluate(model)# Print resultsprint("\nEvaluation Results:")for model_name, result in results.items():print(f"\n{model_name} Results:")print(json.dumps(result, indent=2))# Save results to fileoutput_file = "llama_evaluation_results.json"with open(output_file, 'w') as f:json.dump(results, f, indent=2)print(f"\nResults saved to {output_file}")return resultsif __name__ == "__main__":gt_file = "paper_summaries.jsonl"asyncio.run(run_evaluations(gt_file))
먼저 Claude로 미리 생성해 둔 정답 요약 데이터셋을 JSON 파일에서 불러옵니다. 이 데이터셋에는 연구 논문 제목, 원문 전체 텍스트의 파일 경로, 그리고 평가 기준으로 사용할 수동 작성 요약이 포함되어 있습니다. 데이터셋을 로드하면 데이터 준비를 반복하지 않고 모델 출력 분석에 워크플로의 초점을 맞출 수 있습니다.
데이터셋을 로드한 뒤 스크립트는 각 항목을 처리하면서 연구 논문 원문을 서로 다른 Llama 모델에 보내 요약을 생성합니다. 그런 다음 다양한 평가 지표를 사용해 출력 결과를 정답 요약과 비교합니다.
다음과 같은 지표를 포함합니다:
- 어휘 중복을 측정하기 위한 ROUGE 점수,
- 의미적 유사도를 위한 BERTScore,
- 간결성을 평가하기 위한 압축 비율, 그리고
- 보존된 정보의 양을 평가하기 위한 커버리지 지표.
또한 Anthropic의 Claude가 구동하는 채점 함수를 사용해 요약을 인간과 유사한 이해 수준과 원문과의 정합성에 기반하여 평가합니다.
출력을 채점한 후에는 Weave의 동적 대시보드를 사용해 결과를 시각화할 수 있습니다. 이 인터페이스는 모델과 예시 전반의 지표를 심층적으로 비교할 수 있게 해 주어 추세와 트레이드오프를 파악하고, 최종적으로 개발자가 특정 요구 사항에 가장 적합한 모델을 합리적으로 선택할 수 있도록 돕습니다.
Amazon Bedrock의 다양한 모델과 W&B Weave의 벤치마킹 기능을 결합하면, 요약 작업에서 LLM을 평가하기 위한 견고하고 확장 가능한 워크플로를 구현할 수 있습니다:


결과에 따르면 Llama-8B는 Claude Score, ROUGE-1, ROUGE-2, Coverage Score를 비롯한 여러 평가 지표 전반에서 Llama-1B와 Llama-11B를 모두 능가했습니다. 이는 핵심 내용을 보존하면서 구조적 일관성을 유지하고, 정답과 더 밀접하게 일치하는 요약을 생성하는 능력을 입증합니다.
반면 Llama-11B는 ROUGE-L, Compression Ratio, BERTScore에서 Llama-8B보다 약간의 우위를 보입니다. 한편 Llama-1B는 모든 지표에서 Llama-8B와 Llama-11B에 뒤처져, 최대 성능보다는 효율성에 최적화된 경량 모델이라는 위치를 확인시켜 줍니다.
전반적으로 Llama-8B와 Llama-11B는 이번 평가에서 매우 우수한 성능을 보였으며, 핵심 차원에서 뛰어난 결과를 내는 동시에 지연 시간과 효율성도 합리적인 수준으로 유지합니다. Weave 평가의 장점 중 하나는 비교 보기에서 각 모델의 정확한 응답을 더 깊이 파고들 수 있다는 점입니다. 예를 들어, 각 모델의 응답을 나란히 비교할 수 있을 뿐 아니라, 하나의 UI에서 정답 데이터와의 비교도 함께 확인할 수 있습니다.
비교 보기의 스크린샷입니다:

Llama 대 Amazon Nova
Amazon은 최근 다양한 요구를 충족하기 위한 AI 모델 제품군인 Nova 시리즈를 공개했습니다. Nova Micro는 속도와 비용 효율성에 중점을 두어 요약과 같은 단순한 작업에 적합하며 번역한편 Nova Lite는 텍스트, 이미지, 비디오를 포함한 멀티모달 입력을 실시간으로 처리합니다. Nova Pro는 비용과 성능의 균형을 이루면서 복잡한 추론과 멀티모달 워크플로에서 뛰어납니다. 한편 2025년 초 출시가 예상되는 Nova Premier는 고급 기능으로 가장 복잡한 과제를 처리할 것을 약속합니다. 이제 이러한 Nova 모델이 Llama와 어떻게 비교되는지 살펴보겠습니다.
앞서의 스크립트와 유사한 평가 스크립트를 작성해 이전 Llama 모델들과 비교하여 Amazon Nova Pro를 테스트하겠습니다. 참고로 우리는 이미 사용했기 때문에 Weave Evaluations, 이제 Nova Pro 모델만 사용해 새 스크립트를 간단히 작성한 뒤, 나중에 Weave 대시보드에서 비교할 기존 평가들을 선택하면 됩니다. 코드는 다음과 같습니다:
import weavefrom weave import Modelimport jsonimport boto3from botocore.exceptions import ClientErrorfrom time import sleepimport asynciofrom rouge_score.rouge_scorer import RougeScorerfrom typing import Dict, Anyimport bert_scoreimport fitzimport osfrom weave.trace.box import unboximport timeimport logginglogging.basicConfig(level=logging.DEBUG)# Initialize Weaveweave.init('bedrock_abstract_eval')client = boto3.client(service_name="bedrock-runtime", region_name="us-east-1")def extract_paper_text(pdf_path: str) -> str:"""Extract text from PDF paper."""if isinstance(pdf_path, weave.trace.box.BoxedStr):pdf_path = unbox(pdf_path)text = ""try:with fitz.open(unbox(pdf_path)) as pdf:for page in pdf:text += page.get_text()except Exception as e:print(f"Error extracting text from PDF {pdf_path}: {e}")return textdef format_prompt(text: str, title: str) -> str:"""Format prompt for model."""return f"""<|begin_of_text|><|start_header_id|>user<|end_header_id|>Please analyze this research paper and provide a comprehensive 300-word summary that covers:- Primary research objective- Methodology and approach- Key findings and results- Main contributions to the fieldTitle: {title}Content: {text}Please analyze this research paper and provide a comprehensive 300-word summary that covers:- Primary research objective- Methodology and approach- Key findings and results- Main contributions to the fieldGenerate a clear, coherent summary that captures the essence of the research.<|eot_id|><|start_header_id|>assistant<|end_header_id|>"""def model_forward_nova(title: str, pdf_path: str) -> str:"""Core prediction logic for Amazon Nova Pro."""max_retries = 15backoff_time = 10 # Start with 10 seconds delayfor attempt in range(max_retries):try:paper_text = extract_paper_text(pdf_path)messages = [{"role": "user", "content": [{"text": format_prompt(paper_text, title)}]},]# Make predictionprint(f"Invoking Nova Pro model (Attempt {attempt + 1}/{max_retries})...")response = client.converse(modelId="us.amazon.nova-pro-v1:0",messages=messages)prediction = response["output"]["message"]["content"][0]["text"].strip()print("Done invoking")return predictionexcept Exception as e:print(f"Error generating prediction with Nova Pro (Attempt {attempt + 1}/{max_retries}): {e}")if attempt < max_retries - 1: # Avoid sleeping on the last attempttime.sleep(backoff_time)backoff_time *= 2 # Exponential backoffprint(f"Failed to generate prediction after {max_retries} retries.")return ""class NovaPro(Model):"""Amazon Nova Pro model."""@weave.opdef predict(self, title: str, pdf_path: str) -> dict:"""Generate prediction using Amazon Nova Pro."""prediction = model_forward_nova(title, pdf_path)return {"model_output": prediction}@weave.opdef bert_scorer(gt_abstract: str, model_output: dict) -> Dict[str, float]:"""Calculate BERTScore for the abstract."""if not model_output or 'model_output' not in model_output:return {'bert_score': 0.0}try:P, R, F1 = bert_score.score([model_output['model_output']],[gt_abstract],lang='en',model_type='microsoft/deberta-xlarge-mnli')return {'bert_score': float(F1.mean())}except Exception as e:print(f"Error calculating BERTScore: {e}")return {'bert_score': 0.0}@weave.opdef rouge_scorer(gt_abstract: str, model_output: dict) -> Dict[str, float]:"""Calculate ROUGE scores for the abstract."""if not model_output or 'model_output' not in model_output:return {'rouge1_f': 0.0,'rouge2_f': 0.0,'rougeL_f': 0.0}try:scorer = RougeScorer(['rouge1', 'rouge2', 'rougeL'], use_stemmer=True)scores = scorer.score(gt_abstract, model_output['model_output'])return {'rouge1_f': float(scores['rouge1'].fmeasure),'rouge2_f': float(scores['rouge2'].fmeasure),'rougeL_f': float(scores['rougeL'].fmeasure)}except Exception as e:print(f"Error calculating ROUGE scores: {e}")return {'rouge1_f': 0.0,'rouge2_f': 0.0,'rougeL_f': 0.0}@weave.opdef compression_scorer(gt_abstract: str, model_output: dict) -> Dict[str, float]:"""Calculate compression ratio of the abstract."""if not model_output or 'model_output' not in model_output:return {'compression_ratio': 0.0}try:gt_words = len(gt_abstract.split())generated_words = len(model_output['model_output'].split())compression_ratio = min(gt_words, generated_words) / max(gt_words, generated_words)return {'compression_ratio': float(compression_ratio)}except Exception as e:print(f"Error calculating compression ratio: {e}")return {'compression_ratio': 0.0}@weave.opdef coverage_scorer(gt_abstract: str, model_output: dict) -> Dict[str, float]:"""Calculate content coverage using word overlap."""if not model_output or 'model_output' not in model_output:return {'coverage_score': 0.0}try:gt_words = set(gt_abstract.lower().split())generated_words = set(model_output['model_output'].lower().split())intersection = len(gt_words.intersection(generated_words))union = len(gt_words.union(generated_words))coverage_score = intersection / union if union > 0 else 0.0return {'coverage_score': float(coverage_score)}except Exception as e:print(f"Error calculating coverage score: {e}")return {'coverage_score': 0.0}@weave.opdef claude_scorer(gt_abstract: str, model_output: dict) -> dict:"""Evaluate abstract using Claude."""if not model_output or 'model_output' not in model_output:return {'claude_score': 0.0}print("claude evaluating")# client = boto3.client("bedrock-runtime", region_name="us-east-1")prompt = json.dumps({"anthropic_version": "bedrock-2023-05-31","max_tokens": 1024,"temperature": 0.0,"messages": [{"role": "user","content": [{"type": "text", "text": f'''Rate how well this generated abstract captures the key information from the ground truth abstract on a scale from 1-5, where:1: Poor - Missing most key information or seriously misrepresenting the research2: Fair - Captures some information but misses crucial elements3: Good - Captures most key points but has some gaps or inaccuracies4: Very Good - Accurately captures nearly all key information with minor omissions5: Excellent - Perfectly captures all key information and maintains accuracyGround Truth Abstract:{gt_abstract}Generated Abstract:{model_output["model_output"]}Provide your rating as a JSON object with this schema:{{"score": <integer 1-5>}}'''}]}]})try:response = client.invoke_model(modelId="anthropic.claude-3-sonnet-20240229-v1:0",body=prompt)result = json.loads(response["body"].read())score = json.loads(result["content"][0]["text"])["score"]print(score)sleep(2) # Rate limitingreturn {'claude_score': float(score)}except Exception as e:print(f"Error in Claude evaluation: {e}")return {'claude_score': 0.0}def create_evaluation_dataset(gt_file: str):"""Create dataset from ground truth file."""dataset = []with open(gt_file, 'r') as f:for line in f:entry = json.loads(line)dataset.append({"title": entry["title"],"gt_abstract": entry["summary"],"pdf_path": entry["file_path"]})return datasetasync def run_evaluations(gt_file: str):"""Run evaluations for each model."""eval_dataset = create_evaluation_dataset(gt_file)# Initialize modelsmodels = {"nova_pro": NovaPro(),}# Setup scorersscorers = [claude_scorer,rouge_scorer,compression_scorer,coverage_scorer,bert_scorer]# Run evaluationsresults = {}for model_name, model in models.items():print(f"\nEvaluating {model_name}...")evaluation = weave.Evaluation(dataset=eval_dataset,scorers=scorers,name=model_name + " Eval")results[model_name] = await evaluation.evaluate(model)# Print resultsprint("\nEvaluation Results:")for model_name, result in results.items():print(f"\n{model_name} Results:")print(json.dumps(result, indent=2))# Save results to fileoutput_file = "llama_vs_nova_evaluation_results.json"with open(output_file, 'w') as f:json.dump(results, f, indent=2)print(f"\nResults saved to {output_file}")return resultsif __name__ == "__main__":gt_file = "paper_summaries.jsonl"asyncio.run(run_evaluations(gt_file))
Weave Evaluations 비교 대시보드에서 확인한 결과는 다음과 같습니다:


결과에 따르면 Nova Pro는 여러 평가 지표에서 두 개의 Llama 기반 모델보다 전반적으로 우수한 성능을 보입니다. Nova Pro의 Claude 점수 4.8은 Llama-8B(4.6)와 Llama-11B(4.4)를 앞서며, Claude 평가 기준에 비추어 인간에 가까운 판단과 더 잘 부합하는 요약을 생성함을 시사합니다. 또한 ROUGE 지표에서도 뛰어나 ROUGE-1 0.6308, ROUGE-2 0.3038, ROUGE-L 0.3543을 기록해 두 Llama 모델보다 높은 성능을 보였습니다.
Nova Pro의 압축 비율(0.8198)은 Llama-8B(0.822)와 Llama-11B(0.8233)보다 약간 낮지만, 커버리지 점수 0.31로 Llama-8B의 0.2819와 Llama-11B의 0.2791을 상회하며 더 강한 콘텐츠 보존력을 보여 보완합니다. 또한 Nova Pro는 BERTScore 0.7252로 가장 높은 점수를 기록해, 기준 요약과의 의미적 유사도가 가장 높음을 나타냅니다.
ROUGE와 BERTScore 같은 지표는 어휘적·의미적 중복을 포착하지만, 가독성, 응집성, 전반적인 사용자 만족도를 온전히 측정하지는 못합니다. Claude 점수는 사람의 판단과 더 밀접하게 맞춘 기준을 제공하여, Nova Pro가 구조적이고 응집력 있으며 매력적인 요약을 생성하는 능력을 더 잘 드러내 줍니다.
전반적으로 Nova Pro는 여러 지표에서의 우수한 성능과 사람 평가자와의 높은 정렬을 함께 보여, 기술적 정확성과 인간 중심의 품질을 모두 중시하는 작업에 매우 설득력 있는 선택지입니다.
왜 Amazon Bedrock을 선택할까요?
Amazon Bedrock은 다양한 파운데이션 모델을 폭넓게 제공하고 AWS 에코시스템과 매끄럽게 통합되어, 생성형 AI에 강력한 플랫폼을 제공합니다. Anthropic, Meta, AI21 Labs, Mistral, Amazon의 Titan 등 주요 제공업체의 모델에 대한 액세스를 제공하여, 텍스트 요약, 임베딩 생성, 멀티모달 처리와 같은 작업에 맞춰 모델 선택을 최적화할 수 있습니다. 또한 AWS 서비스와의 통합을 통해 Bedrock은 워크플로를 단순화하고 신뢰할 수 있는 인프라를 활용하며, 기저 시스템을 직접 관리하는 복잡성 없이 확장 가능한 배포를 가능하게 합니다.
확장성, 보안, 안정성에 중점을 둔 Bedrock은 고급 암호화와 업계 표준 준수를 통해 데이터 보호를 유지하면서도 증가하는 수요에 맞춰 자원을 손쉽게 조정할 수 있도록 보장합니다. AWS의 견고한 글로벌 인프라를 기반으로 일관된 성능과 가동 시간을 제공하여, 신뢰할 수 있고 유연한 AI 플랫폼을 찾는 조직에 이상적인 솔루션입니다.
결론
Amazon Bedrock과 W&B Weave는 텍스트 요약처럼 다양한 활용 사례에서 대규모 언어 모델의 성능을 평가하고 비교하기 위한 매우 설득력 있는 조합입니다. Bedrock은 방대한 파운데이션 모델 카탈로그와 AWS 에코시스템과의 통합을 바탕으로, 강력한 AI 솔루션을 모색하는 기업에 유연성과 확장성을 제공합니다.
Weave의 정교한 벤치마킹과 시각화 기능을 활용하면, 조직은 모델 간 트레이드오프를 체계적으로 분석하고 단순한 정량 지표를 넘어 실질적인 강점과 한계를 파악할 수 있습니다. AI 기술이 계속 발전함에 따라 Bedrock과 Weave 같은 플랫폼은 기업이 생성형 AI의 역량을 적극적으로 활용하도록 길을 닦아 주며, 데이터 중심의 세계에서 경쟁력을 유지하도록 돕습니다.
관련 문서
Building an LLM Python debugger agent with the new Claude 3.5 Sonnet
Building a AI powered coding agent with Claude 3.5 Sonnet!
Training a KANFormer: KAN's Are All You Need?
We will dive into a new experimental architecture, replacing the MLP layers in transformers with KAN layers!
Building reliable apps with GPT-4o and structured outputs
Learn how to enforce consistency on GPT-4o outputs, and build reliable Gen-AI Apps.
How to train and evaluate an LLM router
This tutorial explores LLM routers, inspired by the RouteLLM paper, covering training, evaluation, and practical use cases for managing LLMs effectively.
Add a comment