LLM 요약 고도화하기
요약 작업에서 LLM을 최대한 활용하는 가이드
이 글은 AI 번역본입니다. 오역이 의심되면 댓글로 알려 주세요.
Created on September 12|Last edited on September 12
Comment
정보 과잉의 시대에는 방대한 텍스트를 간결한 요약으로 추려 내는 능력이 무엇보다 중요합니다. 뉴스 기사, 연구 논문, 심지어 법률 문서까지도 요약을 통해 세부 사항을 일일이 읽지 않고도 핵심을 빠르게 파악할 수 있습니다.
대규모 언어 모델이 진가를 발휘하는 대표적인 영역이 바로 여기입니다. 수학 계산처럼 LLM이 상대적으로 어려워하는 작업과 달리, 요약에서는 뛰어난 성능을 보이는 경향이 있어 방대한 정보를 더 작고 소화하기 쉬운 형태로 압축해야 할 때 특히 효과적이고 유용합니다.
이 튜토리얼에서는 AI 연구 논문 요약 작업의 품질을 높이기 위한 실용적인 기법들을 다룹니다.

목차
목차파인튜닝: 모델을 요구 사항에 맞게 최적화하기인컨텍스트 러닝: 가벼운 접근법프롬프트 엔지니어링: 효과적인 지침 설계청크 분할, 요약, 병합: 대용량 텍스트 처리사실 오류를 피하기 위한 자기 점검평가에 대한 몇 가지 생각 예시 1: 고정형 QA 요약예시 2: 고정형 + 동적 QA 요약예시 2: 고정형 + 동적 QA 요약 + 청킹 LLM 요약 웹 앱 구축 Weave에서 여러 응답 비교결론
파인튜닝: 모델을 요구 사항에 맞게 최적화하기
LLM의 요약 성능을 높이는 한 가지 방법은 파인튜닝입니다. 파인튜닝은 원하는 작업(이 경우 요약)과 밀접하게 관련된 더 작고 특화된 데이터셋으로 모델을 추가 학습시키는 과정입니다. 모델에 다양한 요약 예시와 그에 대응하는 원문을 충분히 노출하면, 더 정확하고 문맥에 맞는 요약을 생성하는 방법을 학습할 수 있습니다.
이 방식은 모델을 법률 문서나 학술 논문처럼 특정 도메인이나 스타일에 맞게 최적화할 수 있게 해줍니다. 다만 파인튜닝은 많은 자원이 들고, 편향이나 부정확성을 피하려면 잘 정제된 데이터셋이 필요합니다. 또한 더 똑똑한 신규 모델이 출시될 때마다 이전 모델의 파인튜닝 결과가 그대로 이전되지 않을 수 있으므로, 이 과정을 반복해야 합니다. 그 결과, 고품질의 도메인 특화 모델을 유지하는 데 비용과 시간이 많이 들 수 있습니다.
인컨텍스트 러닝: 가벼운 접근법
더 적은 자원으로 접근하고 싶다면 인컨텍스트 러닝이 실용적인 대안이 됩니다. 이 방법은 프롬프트 안에 직접 예시를 제공하는 방식입니다. 예를 들어, 원문과 요약의 페어 몇 개를 프롬프트에 포함하면, 모델은 곧 생성할 요약의 원하는 형식과 스타일을 추론할 수 있습니다.
이 방식은 유연하여 모델을 재학습할 필요 없이 상황에 맞게 바로 다양한 작업이나 도메인에 맞춰 적용할 수 있습니다. 흥미롭게도 인컨텍스트 러닝은 보통 전체 입력 원문조차 필요하지 않습니다. 모델은 종종 출력된 요약만으로도 학습하여, 새로운 입력에 기반해 새 요약을 생성할 때 유사한 스타일을 맞출 수 있습니다. 입력 프롬프트가 길어지는 만큼 파인튜닝에 비해 추론 비용이 더 들 수 있지만, 인컨텍스트 러닝은 프롬프트 내부에 제시된 예시로부터 학습하는 모델의 능력을 효과적으로 활용하는 강력한 기법입니다.
프롬프트 엔지니어링: 효과적인 지침 설계
프롬프트의 구조는 LLM이 생성하는 요약의 품질에 큰 영향을 미칩니다. 프롬프트를 설계할 때는 다음과 같은 요소들을 고려해야 합니다:
길이: 요약의 목표 길이를 지정하여, 모델이 요구에 맞는 간결한 결과를 생성하도록 안내하세요. 이렇게 하면 주어진 텍스트와 구체적인 요구사항에 맞춰 지나치게 짧지도, 과도하게 상세하지도 않은 균형 잡힌 요약을 확보할 수 있습니다.
고정형 QA(질문–응답) 프롬프트이는 요약 대상 콘텐츠와 관련된 고정된 질문 세트를 모델에 제시해, 그 질문들을 요약 과정의 안내로 활용하는 방식입니다. 프롬프트에서 해당 질문에 답하도록 요구하면, 모델이 유용한 요약을 일관되게 생성하는 능력이 향상됩니다.
동적 QA 프롬프트: 이 프롬프트들은 입력 내용에 맞춰 실시간으로 적응하여, 모델이 더 맞춤화되고 맥락을 반영한 응답을 제공할 수 있도록 합니다. 요약의 경우, 입력 프롬프트에 맞춰 별도의 전문 질문을 수작업으로 만들 필요 없이, 모델에 제시되는 질문이 텍스트의 주제와 더 밀접하게 연관되도록 해 줍니다.
청크 분할, 요약, 병합: 대용량 텍스트 처리
특히 길이가 매우 긴 텍스트를 다룰 때는 한 번에 전체를 요약하는 방식이 비현실적이거나 비효율적일 수 있습니다. 대신 텍스트를 더 작은 단위로 청크 분할하고, 각 청크를 요약한 뒤, 이 요약들을 결합해 하나의 일관된 전체 요약으로 병합하는 보다 정교한 접근이 효과적입니다.
이 전략은 긴 문서 전반의 맥락과 연속성을 유지하도록 도와, 한 번에 지나치게 압축한 요약에서 중요한 세부사항이 누락되는 일을 방지합니다. 단계가 더 필요할 수 있지만, 특히 복잡하거나 분량이 많은 문서에서 더 포괄적이고 정확한 요약을 산출할 수 있으며, 추론 과정에서 비용 절감에도 기여합니다.
사실 오류를 피하기 위한 자기 점검
요약 워크플로에 사실 오류에 대한 자기 점검 단계를 포함하면 생성된 요약의 신뢰성이 향상됩니다. 인컨텍스트 예시나 동적 QA 프롬프트로 초기 요약을 만든 뒤, 해당 요약을 모델이 원문과 대조해 검증하는 추가 과정을 거칩니다. 이 자기 점검 단계에서는 요약과 원문을 비교하여 사실 부정확성을 찾아 수정하도록 모델에 프롬프트를 제공합니다. 그런 다음 모델은 정확성을 높이는 것을 목표로 한 수정된 요약을 산출합니다.
이 단계는 사실 정확성이 최우선인 법률, 의료, 학술 요약과 같은 중요한 콘텐츠를 다룰 때 특히 큰 가치를 발휘합니다. 이 검증 단계를 추가하면 산출물의 신뢰도를 크게 높일 수 있으며, 요약이 콘텐츠의 요지를 충실히 포착할 뿐 아니라 오해를 부르거나 잘못된 정보를 도입하지 않도록 보장합니다. 이러한 접근 방식은 모델의 이해를 활용해 스스로의 출력을 정교화하도록 하여, 더 높은 품질과 더 신뢰할 수 있는 결과로 이어집니다.
평가에 대한 몇 가지 생각
대규모 언어 모델이 생성한 요약의 품질을 평가할 때, 품질은 본질적으로 주관적이라는 점을 인식하는 것이 중요하며, 이는 평가를 복잡한 과제로 만듭니다. 어떤 단일 지표도 효과적인 요약을 만드는 미묘한 요소들을 완전히 포착할 수 없습니다. 이러한 복잡성 때문에 포괄적인 평가를 위해서는 여러 접근 방식을 병행하는 일이 흔히 필요합니다.
유용한 방법 중 하나는 ROUGE와 BLEU와 같은 지표를 사용해 생성된 요약을 기준 요약과 비교하는 것입니다. ROUGE는 생성 요약과 기준 요약 간의 n-그램 중복도를 측정하고, BLEU는 단어 선택과 표현 방식 측면에서 생성 요약이 기준 요약과 얼마나 잘 일치하는지를 평가합니다. 두 지표 모두 유의미한 통찰을 제공하지만, 주로 표면적 유사성에 초점을 맞추기 때문에 요약의 전반적 품질을 충분히 반영하지 못할 수 있습니다.
더 발전된 평가 방법으로는 GPT-4 같은 강력한 모델을 사용해 원문 전체를 바탕으로 수작업으로 질문에 답하도록 한 뒤, 요약문만 제공했을 때도 유사한 답을 생성할 수 있는지를 시험하는 절차가 있습니다. 이렇게 생성된 답변을 수작업 답변과 비교하면, 요약이 핵심 정보를 얼마나 잘 포착했는지, 중요한 세부 사항을 효과적으로 전달하는지를 가늠할 수 있습니다. 이 방법은 정량적 지표와 특정 콘텐츠에 대한 정성적 판단을 결합해 보다 미묘한 평가를 가능하게 합니다.
마지막으로, 과업 특성에 맞춰 최적의 모델을 선택하기 위해서는 사람이 직접 평가하는 과정을 권장합니다. ROUGE와 BLEU 같은 자동 지표가 유용한 통찰을 제공하긴 하지만, 요약의 실제 품질을 가늠하는 데에는 한계가 있습니다. 일관성, 가독성, 그리고 의도한 독자에게 핵심 메시지를 공감 가도록 전달하는 능력처럼 “좋은” 요약의 주관적 요소를 포착하려면 인간의 판단이 필수적입니다.
실무에서는 사람이 평가자로 참여해 서로 다른 모델이 생성한 요약을 검토하고, 현재 과업의 구체적 요구를 가장 잘 충족하는 결과에 대해 피드백을 제공합니다. 이는 명료성, 관련성, 간결성과 같은 다양한 기준으로 요약을 평가하거나, 여러 요약을 나란히 비교해 원문을 가장 정확하고 흥미롭게 응축한 것이 무엇인지 직접 판별하는 방식을 포함할 수 있습니다.
법률, 의료, 학술 콘텐츠처럼 맥락과 미묘한 뉘앙스가 중요한 도메인 특화 과업에서는 사람에 의한 평가가 특히 중요합니다. 평가 과정에 인간의 통찰을 반영하면, 선택한 모델이 객관적 지표에서 좋은 성능을 보이는 것에 그치지 않고 최종 사용자의 주관적 기대에도 부합하도록 보장할 수 있어, 실제 환경에서 더 효과적이고 신뢰할 수 있는 요약을 제공하게 됩니다.
예시 1: 고정형 QA 요약
이제 이러한 개념을 구현하는 방법을 코드로 살펴보겠습니다. 모델 추론에는 Azure fine serverless inference API를 사용할 것이며, 자세한 내용은 다음에서 확인할 수 있습니다 여기코드를 수정하거나 여러 API 계정을 관리할 필요 없이, 이 API를 사용하면 최고의 LLM들을 손쉽게 시험해 볼 수 있습니다.
여기서는 AI 연구 논문 요약을 위해 고정형 QA 방식을 구현합니다. 먼저 연구 논문을 이해하는 데 일반적으로 유용한 질문 세트를 만든 뒤, 모델이 해당 질문에 답하면서 논문 요약을 생성하도록 프롬프트를 구성합니다. 추가로, 제가 직접 작성했던 이전 연구 논문 요약을 담은 텍스트 파일을 인컨텍스트 예시로 제공합니다. 인컨텍스트 예시에 해당하는 원문 전체 논문을 모델에 제공하지 않더라도, 모델이 제 글쓰기 스타일을 일반화할 수 있기 때문에 이 접근법이 요약 품질을 개선하는 데 도움이 된다고 판단했습니다.
코드는 다음과 같습니다:
import fitz # PyMuPDFimport requestsimport weaveimport weaveimport os# Initialize Weave for loggingweave.init('summarizer')ENDPOINT_URL = "https://Mistral-small-achqr.eastus2.models.ai.azure.com/chat/completions"PRIMARY_KEY = "your key"DEFAULT_PAPER_URL = "https://arxiv.org/pdf/2407.20183.pdf"IN_CONTEXT_FILE = "in_context_example.txt"summary_length = 400 # Set the desired maximum length of the summary in wordsFIXED_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?"""@weave.op()def get_model_prediction(prompt):headers = {"Authorization": f"Bearer {PRIMARY_KEY}","Content-Type": "application/json"}payload = {"messages": [{"role": "system", "content": "You are a helpful assistant."},{"role": "user", "content": prompt}]}response = requests.post(ENDPOINT_URL, headers=headers, json=payload)return response.json()def download_pdf(url, filename):response = requests.get(url)with open(filename, 'wb') as file:file.write(response.content)print(f"Downloaded PDF from {url}")def load_pdf_text(pdf_path):doc = fitz.open(pdf_path)text = ""for page_num in range(len(doc)):page = doc.load_page(page_num)text += page.get_text()return textdef load_in_context_example(filepath):if os.path.exists(filepath):with open(filepath, 'r') as file:return file.read()return ""if __name__ == "__main__":# Download the PDF if it doesn't existpdf_path = "2407.20183.pdf"if not os.path.exists(pdf_path):download_pdf(DEFAULT_PAPER_URL, pdf_path)# Load the PDF textpdf_text = load_pdf_text(pdf_path)print("PDF text loaded.")# Load the in-context examplein_context_example = load_in_context_example(IN_CONTEXT_FILE)# Combine the in-context example with the fixed questionsprompt = (f"Heres a previous In-context example paper summary:\n{in_context_example}\n\n"f"Please summarize the following text and address these questions:\n{FIXED_QUESTIONS} in {summary_length} words \n\n"f"Text:\n{pdf_text}")# Get the model predictionresponse = get_model_prediction(prompt)print("Model response:", response)
이 스크립트는 인컨텍스트 예시와 고정형 질문 세트를 결합해 학술 논문 요약을 자동화하도록 설계되었습니다. 스크립트는 먼저 지정한 PDF가 존재하는지 확인하고, 없으면 다운로드합니다. PDF에서 텍스트를 추출한 뒤, 인컨텍스트 예시는 파일에서 불러옵니다. 스크립트의 핵심은 인컨텍스트 예시, 사전에 정의한 질문들(예: 주요 목적, 방법론, 핵심 발견 등), 그리고 추출된 텍스트를 포함하는 프롬프트를 구성하는 데 있습니다.
이 프롬프트는 API 요청을 처리하는 함수를 통해 Azure 모델 엔드포인트로 전송됩니다. 모델은 이 입력을 처리해 지정된 질문에 직접 답하는 형태의 요약을 생성합니다. 고정된 질문 세트를 포함하면 요약의 초점과 구조가 명확해져, 유사한 유형의 문서에서 핵심 정보를 일관되게 추출하는 데 특히 유용합니다. 마지막으로, 요약을 포함한 모델의 응답이 출력되어, 사용자가 지정한 요구 사항에 부합하는 문서 개요를 간결하게 제공합니다.
The @weave.op() 데코레이터가 get_model_prediction 함수에 적용되어 이 함수가 Weave에서 추적되는 “operation”임을 나타냅니다. 데코레이터를 추가하면 Weave가 이 함수의 모든 입력과 출력을 자동으로 로깅합니다. 여기에는 모델에 전달된 프롬프트와 생성된 응답이 포함되어, 상세한 추적과 분석이 가능합니다. 이 기능은 서로 다른 입력이 출력에 어떤 영향을 미치는지에 대한 인사이트를 제공하고, 언어 모델과의 상호작용을 효과적으로 미세 조정할 수 있게 해 주므로, 디버깅과 모델 성능 최적화에 특히 유용합니다.
이 코드를 사용하려면 PRIMARY_KEY와 ENDPOINT_URL을 해당 모델에 맞는 키와 URL로 교체해야 합니다. 이렇게 하면 API 호출이 인증되고 올바른 엔드포인트로 전달되어 스크립트가 정상적으로 동작합니다.
예시 2: 고정형 + 동적 QA 요약
이제 고정형 질문 세트를 보완하기 위해 동적 QA 생성을 추가하여 더 유연하고 포괄적인 요약 프로세스를 구현하겠습니다. 기존 방식에서는 미리 정한 고정형 질문 세트에 의존해 요약을 유도함으로써, 텍스트의 특정 측면이 일관되게 다뤄지도록 했습니다. 이 방법은 구조를 유지하는 데 효과적이지만, 문서 내의 중요한 맥락 의존적 세부 사항을 놓칠 수 있습니다.
모델이 먼저 AI 연구자가 관심을 가질 만한 질문 세트를 생성하도록 스크립트를 확장한 뒤, 그 질문들을 다음 모델 호출의 프롬프트에 사용하겠습니다. 이어지는 호출에서는 고정형 질문과 새로 생성된 질문을 모두 제공하여 논문의 요약을 생성하도록 모델에 요청합니다.
import fitzimport requestsimport osimport weave # Import Weave for logging and tracking# Initialize Weave for loggingweave.init('summarizer')ENDPOINT_URL = "https://Mistral-small-achqr.eastus2.models.ai.azure.com/chat/completions"PRIMARY_KEY = "your key"DEFAULT_PAPER_URL = "https://arxiv.org/pdf/2407.20183.pdf"IN_CONTEXT_FILE = "in_context_example.txt"summary_length = 400 # Set the desired maximum length of the summary in wordsFIXED_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?"""@weave.op()def get_model_prediction(prompt):headers = {"Authorization": f"Bearer {PRIMARY_KEY}","Content-Type": "application/json"}payload = {"messages": [{"role": "system", "content": "You are a helpful assistant."},{"role": "user", "content": prompt}]}response = requests.post(ENDPOINT_URL, headers=headers, json=payload)return response.json()def download_pdf(url, filename):response = requests.get(url)with open(filename, 'wb') as file:file.write(response.content)print(f"Downloaded PDF from {url}")def load_pdf_text(pdf_path):doc = fitz.open(pdf_path)text = ""for page_num in range(len(doc)):page = doc.load_page(page_num)text += page.get_text()return textdef load_in_context_example(filepath):if os.path.exists(filepath):with open(filepath, 'r') as file:return file.read()return ""if __name__ == "__main__":# Download the PDF if it doesn't existpdf_path = "2407.20183.pdf"if not os.path.exists(pdf_path):download_pdf(DEFAULT_PAPER_URL, pdf_path)# Load the PDF textpdf_text = load_pdf_text(pdf_path)print("PDF text loaded.")# Load the in-context examplein_context_example = load_in_context_example(IN_CONTEXT_FILE)# Generate dynamic questionsprompt_for_questions = f"Generate key questions that a researcher would ask based on the following text:\n{pdf_text}"questions_response = get_model_prediction(prompt_for_questions)if questions_response:generated_questions = questions_response.get("choices")[0].get("message").get("content")print("Generated Questions:")print(generated_questions)# Combine fixed and generated questions to create the final promptfinal_prompt = (f"Heres a previous In-context example paper summary:\n{in_context_example}\n\n"f"Please summarize the following text and address these questions:\n{FIXED_QUESTIONS}\n{generated_questions} in {summary_length} words \n\n"f"Text:\n{pdf_text}")# Get the final model predictionfinal_response = get_model_prediction(final_prompt)print("Model response:", final_response)
업데이트된 스크립트에서는 PDF에서 텍스트를 추출한 뒤, 문서의 내용에 기반해 모델이 동적으로 추가 질문을 생성하도록 합니다. 이렇게 생성된 동적 질문들을 고정형 질문과 결합하여 더 맞춤화된 프롬프트를 구성합니다. 이 방식은 모델이 문서의 고유한 뉘앙스에 적응하도록 해 주어, 일관성을 유지하면서도 문맥에 충실한 요약을 생성할 수 있게 합니다.
최종 프롬프트에는 인컨텍스트 예시와 함께 고정형 질문과 동적 질문이 모두 포함되며, 이를 모델에 전달해 더 상세하고 반응성이 높은 요약을 생성합니다. 이 방법은 고정형 질문이 제공하는 일반적인 구조와 동적 질문이 포착하는 문서 고유의 뉘앙스를 모두 반영하도록 보장함으로써 요약 품질을 향상시킵니다.
예시 2: 고정형 + 동적 QA 요약 + 청킹
이제 더 긴 문서를 효과적으로 처리하기 위해 청킹과 동적 요약을 추가하겠습니다. 기존 방식에서는 스크립트가 문서 전체를 한 번에 처리했기 때문에, 비효율적이거나 긴 텍스트의 경우 과도하게 압축된 요약이 생성될 수 있었습니다. 업데이트된 스크립트에서는 먼저 지정한 단어 수를 기준으로 텍스트를 더 작고 관리 가능한 청크로 분할합니다. 각 청크를 개별적으로 요약하여 문서의 서로 다른 섹션 전반에 걸쳐 중요한 세부 사항을 유지하면서도 더 집중적이고 일관된 요약을 얻을 수 있습니다.
import fitz # PyMuPDFimport requestsimport weaveimport os# Initialize Weave for loggingweave.init('summarizer')ENDPOINT_URL = "https://Mistral-small-achqr.eastus2.models.ai.azure.com/chat/completions"PRIMARY_KEY = "your key"DEFAULT_PAPER_URL = "https://arxiv.org/pdf/2407.20183.pdf"IN_CONTEXT_FILE = "in_context_example.txt"summary_length = 400 # Set the desired maximum length of the summary in wordschunk_size = 800 # Example chunk size in wordssummary_pct = 10 # Example summary percentage for chunked summarizationFIXED_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?"""@weave.op()def get_model_prediction(prompt):headers = {"Authorization": f"Bearer {PRIMARY_KEY}","Content-Type": "application/json"}payload = {"messages": [{"role": "system", "content": "You are a helpful assistant."},{"role": "user", "content": prompt}]}response = requests.post(ENDPOINT_URL, headers=headers, json=payload)return response.json()def download_pdf(url, filename):response = requests.get(url)with open(filename, 'wb') as file:file.write(response.content)print(f"Downloaded PDF from {url}")def load_pdf_text(pdf_path):doc = fitz.open(pdf_path)text = ""for page_num in range(len(doc)):page = doc.load_page(page_num)text += page.get_text()return textdef load_in_context_example(filepath):if os.path.exists(filepath):with open(filepath, 'r') as file:return file.read()return ""def chunk_text_by_words(text, chunk_size):words = text.split()return [' '.join(words[i:i + chunk_size]) for i in range(0, len(words), chunk_size)]def calculate_summary_length(chunk_text, summary_pct):word_count = len(chunk_text.split())return max(1, int(word_count * summary_pct / 100))if __name__ == "__main__":# Download the PDF if it doesn't existpdf_path = "2407.20183.pdf"if not os.path.exists(pdf_path):download_pdf(DEFAULT_PAPER_URL, pdf_path)# Load the PDF textpdf_text = load_pdf_text(pdf_path)print("PDF text loaded.")# Load the in-context examplein_context_example = load_in_context_example(IN_CONTEXT_FILE)# Chunked Summarizationchunks = chunk_text_by_words(pdf_text, chunk_size)chunk_summaries = []for i, chunk in enumerate(chunks):print(f"Processing chunk {i+1}/{len(chunks)}")# Calculate the dynamic summary length based on the chunk sizesummary_len = calculate_summary_length(chunk, summary_pct)prompt = (f"Summarize the following section of a research paper in {summary_len} words):\n{chunk}")response = get_model_prediction(prompt)chunk_summaries.append(response.get("choices")[0].get("message").get("content"))# Combine chunk summaries and generate dynamic questionscombined_summary = " ".join(chunk_summaries)prompt_for_questions = (f"In-context example:\n{in_context_example}\n\n"f"Generate a few key questions a researcher might ask about the following summarized sections:\n{combined_summary}")questions_response = get_model_prediction(prompt_for_questions)if questions_response:generated_questions = questions_response.get("choices")[0].get("message").get("content")print("Generated Questions:")print(generated_questions)# Use the combined summary and generated questions to create the final promptfinal_prompt = (f"In-context example:\n{in_context_example}\n\n"f"Please summarize the following text and address these questions :\n{FIXED_QUESTIONS}\n{generated_questions} (Word Limit: {summary_length} words)\n\n"f"Text:\n{combined_summary}")# Get the final model predictionfinal_response = get_model_prediction(final_prompt)print("Model response:", final_response)
각 청크를 요약한 뒤에는 요약문을 결합하고, 스크립트가 모델을 사용해 요약된 섹션에 대한 핵심 질문을 동적으로 생성합니다. 이렇게 생성된 질문들은 고정형 질문 세트와 함께 최종 프롬프트를 구성하는 데 사용되어, 요약 과정을 체계적으로 안내합니다.
이 접근 방식은 대용량 문서에 대해 요약 프로세스의 확장성을 높일 뿐 아니라, 텍스트의 일반적 측면과 구체적 측면을 모두 고려해 요약의 관련성도 강화합니다. 최종 결과물은 고정형 질문과 동적으로 생성된 질문을 모두 반영한 요약으로, 문서의 각 섹션에서 명료성과 일관성을 유지하면서 전체 내용을 포괄적으로 제시합니다. 이 방법을 통해 문서가 복잡하거나 길더라도, 결과 요약은 정확하고 유익하며 콘텐츠가 의도한 초점에 부합하도록 보장됩니다.
LLM 요약 웹 앱 구축
이제 연구 논문을 요약할 수 있는 웹 앱을 구축하겠습니다. 이 앱은 생성된 요약이 간결할 뿐만 아니라 정확하고 사용자별 요구에 부합하도록 보장합니다. Azure의 API를 통해 언어 모델의 성능을 활용하여 유연하고 동적인 상호작용을 제공하며, 다양한 도메인에서 콘텐츠 요약에 활용할 수 있는 견고한 도구가 됩니다.
우리는 Weave를 사용해 모델 응답을 추적하고, 사용자가 앱을 사용하는 동안 해당 응답에 피드백을 제공할 수 있는 메커니즘도 마련하겠습니다. 이러한 추적 및 피드백 시스템을 통해 사용자는 현실적인 데이터에 대해 모델을 지속적으로 평가할 수 있습니다. Weave를 통합하면 앱은 모델과의 각 상호작용을 기록할 뿐만 아니라, 사용자가 생성된 요약에 대해 업보트 또는 다운보트를 할 수 있도록 지원합니다. 이 피드백 루프는 실제 시나리오에서 모델이 사용자 기대를 얼마나 잘 충족하는지에 대��� 유용한 인사이트를 제공하므로, 모델 성능을 개선하는 데 결정적인 역할을 합니다. 본 튜토리얼에서는 프런트엔드 HTML 코드는 생략하고, Flask 앱의 백엔드 코드를 아래에 공유하겠습니다:
import requestsimport jsonimport randomimport osfrom flask import Flask, request, jsonify, render_templateimport weave# Initialize Weaveclient = weave.init('summarizer')app = Flask(__name__)# Define global variables for the models and cacheMODELS = [{"endpoint": "https://Meta-Llama-3-1-405B-Instruct-lqf.eastus2.models.ai.azure.com/chat/completions", "key": "your key"},{"endpoint": "https://Meta-Llama-3-1-70B-Instruct-xwib.eastus2.models.ai.azure.com/chat/completions", "key": "your key"},# {"endpoint": "your fourth model endpoint url", "key": "your fourth model key"},# {"endpoint": "your fifth model endpoint url", "key": "your fifth model key"},]IN_CONTEXT_FILE = "./in_context_example.txt"SUMMARY_LENGTH = 400 # Maximum length of the summary in wordsFIXED_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?"""# Cache to store used prompts and modelsused_prompts_cache = {}@weave.op()def get_model_prediction(prompt, endpoint, key, original_input=None):headers = {"Authorization": f"Bearer {key}","Content-Type": "application/json"}payload = {"messages": [{"role": "system", "content": "You are a summarization assistant."},{"role": "user", "content": prompt}]}response = requests.post(endpoint, headers=headers, json=payload)# Get the current call IDcurrent_call = weave.get_current_call()call_id = current_call.idtry:return parse_response(response.json()), call_idexcept json.JSONDecodeError as e:print(f"Failed to decode JSON response: {e}")return None, call_iddef parse_response(response):if 'choices' in response:choices = response['choices']for choice in choices:if 'message' in choice and 'content' in choice['message']:content = choice['message']['content']print(f"Model response content: {content}")return contentif 'finish_reason' in choice:finish_reason = choice['finish_reason']print(f"Finish reason: {finish_reason}")return "No valid response"def load_in_context_example(filepath):if os.path.exists(filepath):with open(filepath, 'r') as file:return file.read()return ""@weave.op()def perform_self_reflection(summary, original_text, endpoint, key, original_input=None):reflection_prompt = (f"The following is a summary of the original document. "f"Original Document:\n{original_text}\n\n"f"Summary:\n{summary}\n\n"f"If there are mistakes, simply remove incorrect sections and REWRITE it COMPLETELY as it was originally with the only change being the removal of incorrect sentences:"f"If everything is correct, simply rewrite the summary EXACTLY as it was:")revised_summary, _ = get_model_prediction(reflection_prompt, endpoint, key)return revised_summarydef select_random_model(prompt):available_models = [model for model in MODELS if prompt not in used_prompts_cache.get(model['endpoint'], [])]if not available_models:# If all models have been used, reset the cache for the promptfor model in MODELS:if model['endpoint'] in used_prompts_cache:used_prompts_cache[model['endpoint']].remove(prompt)available_models = MODELSselected_model = random.choice(available_models)# Cache the selected model for the promptif selected_model['endpoint'] not in used_prompts_cache:used_prompts_cache[selected_model['endpoint']] = []used_prompts_cache[selected_model['endpoint']].append(prompt)return selected_model@app.route('/')def index():return render_template('index.html')@app.route('/chat', methods=['POST'])def chat():data = request.jsonprompt = data['prompt']# Load the in-context examplein_context_example = load_in_context_example(IN_CONTEXT_FILE)# Select a random model that hasn't been used with this prompt yetmodel = select_random_model(prompt)# Generate dynamic questions based on the input textprompt_for_questions = f"Generate key questions that a researcher would ask based on the following text:\n{prompt}"questions_response, _ = get_model_prediction(prompt_for_questions, model['endpoint'], model['key'])generated_questions = ""if questions_response:generated_questions = questions_response# Combine fixed and generated questions to create the final promptfinal_prompt = (f"Heres a previous In-context example paper summary:\n{in_context_example}\n\n"f"Please summarize the following text and address these questions:\n{FIXED_QUESTIONS}\n{generated_questions} in {SUMMARY_LENGTH} words.\n\n"f"Text:\n{prompt}")# Get the initial summary and call ID from the selected modelsummary, call_id = get_model_prediction(final_prompt, model['endpoint'], model['key'], original_input=prompt)# Perform self-reflection to remove factual errorsrevised_summary = perform_self_reflection(summary, prompt, model['endpoint'], model['key'], original_input=prompt)# revised_summary = summaryreturn jsonify({"response": revised_summary,"call_id": call_id,"model_used": model['endpoint']})@app.route('/feedback', methods=['POST'])def feedback():data = request.jsoncall_id = data['call_id']feedback_type = data['feedback']if feedback_type == "upvote":client.call(call_id).feedback.add_reaction("👍")elif feedback_type == "downvote":client.call(call_id).feedback.add_reaction("👎")return jsonify({"status": "success"})if __name__ == "__main__":app.run(debug=True)
핵심적으로 이 앱은 요약 과정을 크게 개선하는 여러 주요 기능을 제공합니다. 그중 중요한 요소가 모델 스위칭으로, 각 요청마다 미리 정의된 모델 목록에서 무작위로 하나를 선택합니다. 이를 통해 사용자는 서로 다른 모델의 요약 생성 성능을 비교·검증할 수 있으며, 이는 비교 연구를 수행하거나 특정 유형의 콘텐츠에 가장 적합한 모델을 찾는 데 특히 유용합니다.
이 접근 방식은 사용자가 어떤 모델이 사용되는지 모르는 상태에서 모델을 평가하는 LMSYS Chatbot Arena와 유사합니다. 모델의 정체를 알 때 생기는 편향을 제거함으로써, 이 방법은 모델을 보다 타당하고 객관적으로 평가할 수 있는 방법을 제공합니다. 사용자는 출력물의 품질에만 집중할 수 있어, 특정 모델에 대한 선입견이 아니라 실제 성능에 근거해 평가하게 됩니다. 이는 평가 과정을 더욱 신뢰할 수 있게 만들고, 특정 작업에 가장 효과적인 모델을 식별하는 데 도움을 줍니다.
Weave는 앱 전반에서 결정적인 역할을 합니다. Weave는 모델이 생성한 응답을 추적하고, 각 요청에 어떤 모델이 사용되었는지 기록하는 데 사용됩니다. 이러한 정보를 로그로 남기면, 서로 다른 모델의 성능을 모니터링하고 분석하며, 출력물의 품질을 추적하고, 시간이 지남에 따라 앱을 최적화하는 데 활용할 수 있는 데이터를 수집할 수 있습니다.
이러한 추적은 다양한 작업 전반에서 서로 다른 모델이 어떻게 성능을 내는지 파악하고, 어떤 모델을 우선순위에 두거나 추가로 파인튜닝할지 데이터 기반으로 결정하는 데 매우 유용합니다. 스크립트에서 중요한 점은 Weave 데코레이터와 함께 원본 입력 프롬프트를 함수에 전달한다는 것입니다. 이렇게 하면 나중에 동일한 입력으로 생성된 응답을 비교할 수 있습니다. 이는 동일한 입력에 대해 서로 다른 모델을 비교할 때 특히 유용합니다!
이 앱에는 Weave 기반의 피드백 메커니즘도 포함되어 있어 사용자가 요약의 품질을 평가할 수 있습니다. 사용자는 특정 요약에 업보트 또는 다운보트를 통해 실시간 피드백을 제공할 수 있으며, 이는 모델을 파인튜닝하거나 요약 파라미터를 조정하는 데 활용됩니다. 이러한 피드백 루프는 앱의 성능을 지속적으로 개선하고 사용자 기대에 부응하도록 보장하는 데 필수적입니다.
여기서는 Weave에서 특정 트레이스의 UUID를 가져옵니다.
current_call = weave.get_current_call()call_id = current_call.id
그리고 피드백 함수에서는 call_id를 활용해 해당 응답에 피드백을 설정할 수 있습니다.
@app.route('/feedback', methods=['POST'])def feedback():data = request.jsoncall_id = data['call_id']feedback_type = data['feedback']if feedback_type == "upvote":client.call(call_id).feedback.add_reaction("👍")elif feedback_type == "downvote":client.call(call_id).feedback.add_reaction("👎")return jsonify({"status": "success"})
또 다른 핵심 기능은 사실 정확도를 위한 자기 점검입니다. 초기 요약이 생성된 후, 앱은 자기 점검 단계를 수행하며 원문을 다시 검토하고 요약 내 잠재적인 사실 오류를 식별합니다. 그런 다음 모델이 이러한 오류를 수정하고 요약을 다시 작성하여 최종 출력이 정확하고 신뢰할 수 있도록 보장합니다. 이 기능은 법률이나 학술 요약처럼 정확성이 최우선인 상황에서 특히 유용합니다.
자기 점검을 수행하기 위한 코드는 다음과 같습니다:
def perform_self_reflection(summary, original_text, endpoint, key):reflection_prompt = (f"The following is a summary of the original document. Identify and remove any factual errors "f"from the summary, and rewrite it to be accurate:\n\n"f"Original Document:\n{original_text}\n\n"f"Summary:\n{summary}\n\n"f"Revised Summary:")revised_summary, _ = get_model_prediction(reflection_prompt, endpoint, key)return revised_summary
또한 이 앱은 고정형과 동적 질문 생성 방식을 병행해 사용합니다. 먼저 요약 과정을 안내하는 고정 질문 세트를 적용해 출력의 일관성을 확보합니다. 이후 입력 텍스트를 바탕으로 추가 질문을 동적으로 생성하여 문서의 구체적 내용에 더 유연하게 반응하도록 합니다. 이러한 이중 접근 방식을 통해 표준적인 요소와 문맥 특유의 요소를 모두 포괄하는 포괄적 요약을 보장합니다.
앱에서 요약을 생성하고, 여러 모델 간에 전환하며, 각 요약에 대해 피드백을 제공할 수 있습니다. 이를 통해 어떤 모델을 선택할지 더 근거 있는 결정을 내릴 수 있습니다.

앱을 실행한 뒤 W&B Weave 대시보드로 이동하면 다음과 같은 로그를 확인할 수 있습니다(참고로, 저는 앱에서 모델의 응답에 업보트를 하였습니다).

셀을 하나 클릭하면 함수의 입력과 출력에 대한 더 자세한 로그를 확인할 수 있습니다. 아래에는 모델의 응답을 손쉽게 확인하는 방법을 보여 주는 또 다른 스크린샷을 공유하겠습니다!

Weave에서 여러 응답 비교
이제 동일한 입력에 대해 서로 다른 모델의 출력을 비교해 보고 싶을 수 있습니다. Weave에서는 어떤 셀에서든 입력 값을 옵션 키를 누른 채 클릭하기만 하면, 동일한 입력 값을 가진 트레이스만 빠르게 필터링할 수 있는 유용한 기능을 제공합니다. 앞서 원본 프롬프트(original_prompt)를 자기 점검 함수와 모델 예측 함수 모두에 전달했으므로, 이를 통해 사용자의 특정 입력으로 필터링할 수 있습니다. 필터를 추가한 뒤에는 모델 응답을 나란히 비교해서 확인할 수 있습니다!

여기서는 Llama 3.1 70B 모델과 405B 모델 모두에 동일한 입력을 사용했고, 두 응답을 손쉽게 비교할 수 있었습니다!
결론
자신의 애플리케이션에 사용할 모델을 선택할 때는, 널리 쓰이는 모델 몇 가지를 직접 시험해 보고 요약 품질에 대한 본인의 판단에 따라 고르는 것을 권합니다. 저는 대부분의 요약 작업에 GPT-4o를 사용하지만, Claude 3.5 Sonnet도 훌륭한 선택입니다. 또한 이 프로젝트에서는 Llama 3.1 70B와 405B 모델도 학술 AI 연구 논문 요약에서 꽤 좋은 성능을 보였습니다.
전반적으로 이 웹앱은 유연성, 정확성, 그리고 사용자 참여를 결합하여 고품질 요약을 제공하는 강력한 도구입니다. 연구 논문, 법률 문서, 기타 복잡한 콘텐츠를 다루든 상관없이, 이 앱은 요구 사항에 맞춘 신뢰할 수 있고 간결한 요약을 손쉽게 생성할 수 있는 간소화된 해결책을 제공합니다. 또한 Weave를 통한 강력한 추적, 분석, 피드백 기능으로 모델의 응답을 쉽게 관리할 수 있어, 각 작업에 어떤 모델을 사용할지 가장 근거 있는 결정을 내릴 수 있습니다.
결론적으로, 대규모 언어 모델은 방대한 텍스트를 요약하는 데 실용적인 해법을 제공하여 복잡한 문서에서 핵심 정보를 더 쉽게 추출할 수 있게 합니다. 파인튜닝, 인컨텍스트 러닝, 프롬프트 엔지니어링, 청킹과 같은 기법은 모델이 간결하면서도 문맥에 맞는 정확한 요약을 생성하도록 돕습니다. 여기에 사실 정확도를 위한 자기 점검 단계를 더하면 출력의 신뢰성이 한층 향상됩니다. 이 튜토리얼이 도움이 되었길 바랍니다! 또한 GitHub 저장소도 자유롭게 확인해 보세요. 여기.
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!
Grokking: Improved generalization through over-overfitting
One of the most mysterious phenomena in deep learning; Grokking is the tendency of neural networks to improve generalization by sustained overfitting.
Building a real-time answer engine with Llama 3.1 405B and W&B Weave
Infusing llama 3.1 405B with internet search capabilities!!
Fine-Tuning Llama-3 with LoRA: TorchTune vs HuggingFace
A battle between the HuggingFace and TorchTune!!!
Add a comment