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

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

W&B Weave와 Amazon 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 콘솔의 보안 자격 증명에서 새 액세스 키를 생성하여 만들 수 있습니다. 새 액세스 키를 만들려면 AWS Management Console 오른쪽 상단의 계정 이름을 클릭하고 드롭다운 메뉴에서 보안 자격 증명을 선택하세요. 아래로 스크롤하여 액세스 키 섹션으로 이동한 뒤, 액세스 키 생성을 클릭합니다. 생성이 완료되면 액세스 키 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
- 간결성을 평가하기 위한 압축 비율, 그리고
- 포함 정보의 보존 정도를 평가하는 Coverage 지표.
또한 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 Evaluations의 강점 중 하나는 비교 보기 기능을 통해 각 모델의 실제 응답을 더 깊이 파고들 수 있다는 점입니다. 예를 들어, 각 모델의 응답을 나란히 비교할 수 있을 뿐 아니라, 하나의 UI에서 정답 요약과의 비교까지 동시에 확인할 수 있습니다.
비교 보기 화면의 스크린샷은 다음과 같습니다:

Llama vs. Amazon Nova
아마존은 최근 다양한 요구를 충족하기 위한 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는 고급 암호화와 업계 표준 준수를 통해 데이터 보호를 유지하면서, 증가하는 수요에 맞춰 자원을 쉽게 조정할 수 있도록 지원합니다. 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