---
title: 법률 문서 요약
url: https://platform.claude.com/docs/ko/about-claude/use-case-guides/legal-summarization
description: 이 가이드는 Claude의 고급 자연어 처리 기능을 활용하여 법률 문서를 효율적으로 요약하고, 핵심 정보를 추출하며, 법률 조사를 신속하게 진행하는 방법을 안내합니다. Claude를 사용하면 계약서 검토, 소송 준비, 규제 업무를 간소화하여 시간을 절약하고 법률 프로세스의 정확성을 보장할 수 있습니다.
---

> Claude를 사용한 법률 문서 요약 구현 예시를 보려면 [요약 cookbook](https://platform.claude.com/cookbook/capabilities-summarization-guide)을 방문하세요.

## Claude로 구축하기 전에

### 법률 문서 요약에 Claude를 사용할지 결정하기

다음은 법률 문서를 요약하기 위해 Claude와 같은 "large language model"(대규모 언어 모델), 즉 LLM을 사용해야 하는 주요 지표입니다:

<AccordionGroup>
  <Accordion title="대량의 문서를 효율적이고 저렴하게 검토하고 싶은 경우">
    대규모 문서 검토는 수작업으로 수행할 경우 시간이 많이 걸리고 비용이 많이 들 수 있습니다. Claude는 방대한 양의 법률 문서를 신속하게 처리하고 요약할 수 있어 문서 검토와 관련된 시간과 비용을 크게 줄여줍니다. 이 기능은 효율성이 중요한 실사(due diligence), 계약 분석 또는 소송 증거 개시(discovery)와 같은 작업에 특히 유용합니다.
  </Accordion>

  <Accordion title="핵심 메타데이터의 자동 추출이 필요한 경우">
    Claude는 관련 당사자, 날짜, 계약 조건 또는 특정 조항과 같은 중요한 메타데이터를 법률 문서에서 효율적으로 추출하고 분류할 수 있습니다. 이러한 자동 추출은 정보를 체계화하여 대규모 문서 세트를 더 쉽게 검색, 분석 및 관리할 수 있도록 도와줍니다. 특히 계약 관리, 규정 준수 확인 또는 검색 가능한 법률 정보 데이터베이스 구축에 유용합니다. 
  </Accordion>

  <Accordion title="명확하고 간결하며 표준화된 요약을 생성하고 싶은 경우">
    Claude는 미리 정해진 형식을 따르는 구조화된 요약을 생성할 수 있어 법률 전문가가 다양한 문서의 핵심 사항을 빠르게 파악할 수 있도록 합니다. 이러한 표준화된 요약은 가독성을 높이고, 문서 간 비교를 용이하게 하며, 특히 복잡한 법률 용어나 기술 전문 용어를 다룰 때 전반적인 이해도를 향상시킬 수 있습니다.
  </Accordion>

  <Accordion title="요약에 정확한 인용이 필요한 경우">
    법률 요약을 작성할 때 신뢰성을 확보하고 법적 기준을 준수하기 위해서는 적절한 출처 표시와 인용이 매우 중요합니다. Claude에게 참조된 모든 법적 사항에 대해 정확한 인용을 포함하도록 프롬프트를 작성할 수 있어, 법률 전문가가 요약된 정보를 더 쉽게 검토하고 검증할 수 있습니다.
  </Accordion>

  <Accordion title="법률 조사 프로세스를 간소화하고 신속하게 진행하고 싶은 경우">
    Claude는 대량의 판례, 법령 및 법률 해설을 신속하게 분석하여 법률 조사를 지원할 수 있습니다. 관련 선례를 식별하고, 핵심 법적 원칙을 추출하며, 복잡한 법적 논증을 요약할 수 있습니다. 이 기능은 조사 프로세스를 크게 가속화하여 법률 전문가가 더 높은 수준의 분석과 전략 개발에 집중할 수 있도록 합니다.
  </Accordion>
</AccordionGroup>

### 요약에서 추출할 세부 정보 결정하기

어떤 문서에 대해서도 단 하나의 정답인 요약은 존재하지 않습니다. 명확한 지침이 없으면 Claude가 어떤 세부 정보를 포함해야 할지 판단하기 어려울 수 있습니다. 최적의 결과를 얻으려면 요약에 포함하고자 하는 구체적인 정보를 식별하세요.

예를 들어, 전대차 계약서(sublease agreement)를 요약할 때 다음과 같은 핵심 사항을 추출하고자 할 수 있습니다:

<CodeGroup exclude="shell">
  ```python Python
  details_to_extract = [
      "Parties involved (sublessor, sublessee, and original lessor)",
      "Property details (address, description, and permitted use)",
      "Term and rent (start date, end date, monthly rent, and security deposit)",
      "Responsibilities (utilities, maintenance, and repairs)",
      "Consent and notices (landlord's consent, and notice requirements)",
      "Special provisions (furniture, parking, and subletting restrictions)",
  ]
  ```

  ```typescript TypeScript
  const detailsToExtract = [
    "Parties involved (sublessor, sublessee, and original lessor)",
    "Property details (address, description, and permitted use)",
    "Term and rent (start date, end date, monthly rent, and security deposit)",
    "Responsibilities (utilities, maintenance, and repairs)",
    "Consent and notices (landlord's consent, and notice requirements)",
    "Special provisions (furniture, parking, and subletting restrictions)"
  ];
  ```

  ```csharp C#
  List<string> detailsToExtract =
  [
      "Parties involved (sublessor, sublessee, and original lessor)",
      "Property details (address, description, and permitted use)",
      "Term and rent (start date, end date, monthly rent, and security deposit)",
      "Responsibilities (utilities, maintenance, and repairs)",
      "Consent and notices (landlord's consent, and notice requirements)",
      "Special provisions (furniture, parking, and subletting restrictions)",
  ];
  ```

  ```go Go
  var detailsToExtract = []string{
  	"Parties involved (sublessor, sublessee, and original lessor)",
  	"Property details (address, description, and permitted use)",
  	"Term and rent (start date, end date, monthly rent, and security deposit)",
  	"Responsibilities (utilities, maintenance, and repairs)",
  	"Consent and notices (landlord's consent, and notice requirements)",
  	"Special provisions (furniture, parking, and subletting restrictions)",
  }
  ```

  ```java Java
  List<String> detailsToExtract = List.of(
          "Parties involved (sublessor, sublessee, and original lessor)",
          "Property details (address, description, and permitted use)",
          "Term and rent (start date, end date, monthly rent, and security deposit)",
          "Responsibilities (utilities, maintenance, and repairs)",
          "Consent and notices (landlord's consent, and notice requirements)",
          "Special provisions (furniture, parking, and subletting restrictions)");
  ```

  ```php PHP
  $detailsToExtract = [
      'Parties involved (sublessor, sublessee, and original lessor)',
      'Property details (address, description, and permitted use)',
      'Term and rent (start date, end date, monthly rent, and security deposit)',
      'Responsibilities (utilities, maintenance, and repairs)',
      "Consent and notices (landlord's consent, and notice requirements)",
      'Special provisions (furniture, parking, and subletting restrictions)',
  ];
  ```

  ```ruby Ruby
  details_to_extract = [
    "Parties involved (sublessor, sublessee, and original lessor)",
    "Property details (address, description, and permitted use)",
    "Term and rent (start date, end date, monthly rent, and security deposit)",
    "Responsibilities (utilities, maintenance, and repairs)",
    "Consent and notices (landlord's consent, and notice requirements)",
    "Special provisions (furniture, parking, and subletting restrictions)"
  ]
  ```
</CodeGroup>

### 성공 기준 수립하기

요약의 품질을 평가하는 것은 어렵기로 악명 높은 작업입니다. 다른 많은 자연어 처리 작업과 달리, 요약 평가에는 명확하고 객관적인 지표가 부족한 경우가 많습니다. 이 과정은 매우 주관적일 수 있으며, 독자마다 요약의 서로 다른 측면을 중요하게 여길 수 있습니다. 다음은 Claude가 법률 문서 요약을 얼마나 잘 수행하는지 평가할 때 고려할 수 있는 기준입니다.

<AccordionGroup>
  <Accordion title="사실적 정확성">
    요약은 문서의 사실, 법적 개념 및 핵심 사항을 정확하게 나타내야 합니다.
  </Accordion>

  <Accordion title="법적 정밀성">
    용어와 법령, 판례 또는 규정에 대한 참조는 정확해야 하며 법적 기준에 부합해야 합니다.
  </Accordion>

  <Accordion title="간결성">
     요약은 중요한 세부 정보를 잃지 않으면서 법률 문서를 핵심 사항으로 압축해야 합니다.
  </Accordion>

  <Accordion title="일관성">
    여러 문서를 요약하는 경우, LLM은 각 요약에 대해 일관된 구조와 접근 방식을 유지해야 합니다.
  </Accordion>

  <Accordion title="가독성">
    텍스트는 명확하고 이해하기 쉬워야 합니다. 독자가 법률 전문가가 아닌 경우, 요약에는 독자를 혼란스럽게 할 수 있는 법률 전문 용어가 포함되어서는 안 됩니다.
  </Accordion>

  <Accordion title="편향 및 공정성">
    요약은 법적 논증과 입장을 편향되지 않고 공정하게 묘사해야 합니다.
  </Accordion>
</AccordionGroup>

자세한 내용은 [성공 기준 수립](https://platform.claude.com/docs/ko/test-and-evaluate/develop-tests) 가이드를 참조하세요.

***

## Claude를 사용하여 법률 문서를 요약하는 방법

### 적합한 Claude 모델 선택하기

법률 문서를 요약할 때는 모델의 정확성이 매우 중요합니다. Claude Opus 5.5는 이처럼 높은 정확성이 요구되는 사용 사례에 탁월한 선택입니다. 문서의 크기와 양이 커서 비용이 우려되기 시작한다면, Claude Haiku 5.5와 같은 더 작은 모델을 사용해 볼 수도 있습니다.

이러한 비용을 추정하는 데 도움이 되도록, 다음은 Opus 및 Haiku 모델을 사용하여 1,000건의 전대차 계약서를 요약하는 비용을 비교한 것입니다:

* **콘텐츠 크기**

  * 계약서 수: 1,000
  * 계약서당 문자 수: 300,000
  * 총 문자 수: 300M

* **예상 토큰**

  * 입력 토큰: 108M, 계약서당 약 108,000개 (약 2.8자당 1토큰으로, 이 가이드의 샘플 계약서에서 두 모델 모두 보인 비율)
  * 요약당 출력 토큰(출력으로 청구되는 사고 토큰 포함): Claude Opus 5.5에서 약 6,200개, Claude Haiku 5.5에서 약 3,100개
  * 총 출력 토큰: Claude Opus 5.5에서 6.2M, Claude Haiku 5.5에서 3.1M

* **Claude Opus 5.5 예상 비용**

  * 입력 토큰 비용: 108 MTok \* $4.00/MTok = $432.00 USD
  * 출력 토큰 비용: 6.2 MTok \* $20.00/MTok = $124.00 USD
  * 총 비용: $432.00 + $124.00 = $556.00 USD

* **Claude Haiku 5.5 예상 비용**, 100,000 토큰을 초과하는 프롬프트에 대한 가격 기준

  * 입력 토큰 비용: 108 MTok \* $0.50/MTok = $54.00 USD
  * 출력 토큰 비용: 3.1 MTok \* $2.50/MTok = $7.75 USD
  * 총 비용: $54.00 + $7.75 = $61.75 USD

<Tip>
  실제 비용은 이러한 추정치와 다를 수 있습니다. 토큰 수는 이 가이드의 샘플 계약서(약 105,000 입력 토큰)에 대해 

  [강력한 프롬프트 구축하기](https://platform.claude.com/docs/ko/about-claude/use-case-guides/legal-summarization#build-a-strong-prompt)

   섹션의 예시를 실행한 결과이며, 출력 길이는 문서에 따라, 그리고 실행할 때마다 달라집니다. 여기서 각 계약서는 약 108,000 토큰의 프롬프트 하나로, Claude Haiku 5.5의 

  [긴 프롬프트 가격](https://platform.claude.com/docs/ko/about-claude/pricing#long-context-pricing)

  이 적용되는 100,000 토큰 임계값을 초과합니다. 프롬프트가 100,000 토큰 이하인 문서는 대신 입력 $0.10/MTok, 출력 $0.50/MTok를 지불합니다. 어떤 가격이 적용되는지 확인하려면 

  `model`

  을 

  `claude-haiku-5-5`

  로 설정하고 

  [토큰 카운팅](https://platform.claude.com/docs/ko/build-with-claude/token-counting)

  으로 문서의 토큰 수를 세어 보세요.
</Tip>

### Claude가 처리할 수 있는 형식으로 문서 변환하기

문서 요약을 시작하기 전에 데이터를 준비해야 합니다. 여기에는 PDF에서 텍스트를 추출하고, 텍스트를 정리하며, Claude가 처리할 수 있도록 준비되었는지 확인하는 작업이 포함됩니다.

다음은 샘플 PDF에서 이 과정을 시연한 것입니다:

<CodeGroup exclude="shell">
  <CodeGroupItem language="cURL">
    코드에서 PDF의 텍스트를 추출하세요.
  </CodeGroupItem>

  <CodeGroupItem language="CLI">
    코드에서 PDF의 텍스트를 추출하세요.
  </CodeGroupItem>

  ```python Python
  from io import BytesIO
  import re

  import pypdf
  import requests


  def get_llm_text(pdf_file):
      reader = pypdf.PdfReader(pdf_file)
      text = "\n".join([page.extract_text() for page in reader.pages])

      # 페이지 번호 제거
      text = re.sub(r"\n\s*\d+\s*\n", "\n", text)

      # 불필요한 공백 제거
      text = re.sub(r"\s+", " ", text)

      return text


  # GitHub 저장소에서 전체 URL 생성
  url = "https://raw.githubusercontent.com/anthropics/claude-cookbooks/main/capabilities/summarization/data/Sample Sublease Agreement.pdf"
  url = url.replace(" ", "%20")

  # PDF 파일을 메모리로 다운로드
  response = requests.get(url)

  # 메모리에서 PDF 로드
  pdf_file = BytesIO(response.content)

  document_text = get_llm_text(pdf_file)
  print(document_text[:50000])
  ```

  ```typescript TypeScript
  import { PDFParse } from "pdf-parse";

  async function getLlmText(pdfData: Uint8Array): Promise<string> {
    const parser = new PDFParse({ data: pdfData });
    const result = await parser.getText();
    await parser.destroy();
    let text = result.pages.map((page) => page.text).join("\n");

    // 페이지 번호 제거
    text = text.replace(/\n\s*\d+\s*\n/g, "\n");

    // 불필요한 공백 제거
    text = text.replace(/\s+/g, " ");

    return text;
  }

  // 요약 cookbook에서 샘플 전대차 계약서 다운로드
  const response = await fetch(
    "https://raw.githubusercontent.com/anthropics/claude-cookbooks/main/capabilities/summarization/data/Sample%20Sublease%20Agreement.pdf"
  );
  const pdfData = new Uint8Array(await response.arrayBuffer());

  const documentText = await getLlmText(pdfData);
  console.log(documentText.slice(0, 50000));
  ```

  ```csharp C#
  using System.Text.RegularExpressions;
  using UglyToad.PdfPig;
  using UglyToad.PdfPig.DocumentLayoutAnalysis.TextExtractor;

  string GetLlmText(byte[] pdfData)
  {
      using var document = PdfDocument.Open(pdfData);
      var text = string.Join("\n", document.GetPages().Select(page => ContentOrderTextExtractor.GetText(page)));

      // 페이지 번호 제거
      text = Regex.Replace(text, @"\n\s*\d+\s*\n", "\n");

      // 불필요한 공백 제거
      text = Regex.Replace(text, @"\s+", " ");

      return text;
  }

  // 요약 cookbook에서 샘플 전대차 계약서 다운로드
  using var httpClient = new HttpClient();
  var pdfData = await httpClient.GetByteArrayAsync(
      "https://raw.githubusercontent.com/anthropics/claude-cookbooks/main/capabilities/summarization/data/Sample%20Sublease%20Agreement.pdf"
  );

  var documentText = GetLlmText(pdfData);
  Console.WriteLine(documentText[..Math.Min(50000, documentText.Length)]);
  ```

  ```go Go
  import (
  	"bytes"
  	"io"
  	"net/http"
  	"regexp"

  	"github.com/ledongthuc/pdf"
  )

  // 샘플 전대차 계약서의 텍스트
  var documentText string

  func getLLMText(pdfData []byte) (string, error) {
  	reader, err := pdf.NewReader(bytes.NewReader(pdfData), int64(len(pdfData)))
  	if err != nil {
  		return "", err
  	}

  	var pages []string
  	for pageIndex := 1; pageIndex <= reader.NumPage(); pageIndex++ {
  		pageText, err := reader.Page(pageIndex).GetPlainText(nil)
  		if err != nil {
  			return "", err
  		}
  		pages = append(pages, pageText)
  	}
  	text := strings.Join(pages, "\n")

  	// 페이지 번호 제거
  	text = regexp.MustCompile(`\n\s*\d+\s*\n`).ReplaceAllString(text, "\n")

  	// 불필요한 공백 제거
  	text = regexp.MustCompile(`\s+`).ReplaceAllString(text, " ")

  	return text, nil
  }

  // extractDocumentText는 샘플 전대차 계약서를 다운로드하고 텍스트를 추출합니다.
  func extractDocumentText() {
  	// 요약 cookbook에서 샘플 전대차 계약서 다운로드
  	response, err := http.Get("https://raw.githubusercontent.com/anthropics/claude-cookbooks/main/capabilities/summarization/data/Sample%20Sublease%20Agreement.pdf")
  	if err != nil {
  		log.Fatal(err)
  	}
  	defer response.Body.Close()

  	pdfData, err := io.ReadAll(response.Body)
  	if err != nil {
  		log.Fatal(err)
  	}

  	documentText, err = getLLMText(pdfData)
  	if err != nil {
  		log.Fatal(err)
  	}
  	fmt.Println(documentText[:min(50000, len(documentText))])
  }
  ```

  ```java Java
  import java.io.IOException;
  import java.net.URI;
  import java.net.http.HttpClient;
  import java.net.http.HttpRequest;
  import java.net.http.HttpResponse;
  import org.apache.pdfbox.Loader;
  import org.apache.pdfbox.pdmodel.PDDocument;
  import org.apache.pdfbox.text.PDFTextStripper;

  // 샘플 전대차 계약서의 텍스트
  String documentText;

  String getLlmText(byte[] pdfData) throws IOException {
      try (PDDocument document = Loader.loadPDF(pdfData)) {
          String text = new PDFTextStripper().getText(document);

          // 페이지 번호 제거
          text = text.replaceAll("\\n\\s*\\d+\\s*\\n", "\n");

          // 불필요한 공백 제거
          return text.replaceAll("\\s+", " ");
      }
  }

  void extractDocumentText() throws IOException, InterruptedException {
      // 요약 cookbook에서 샘플 전대차 계약서 다운로드
      HttpRequest request = HttpRequest.newBuilder(URI.create(
                      "https://raw.githubusercontent.com/anthropics/claude-cookbooks/main/capabilities/summarization/data/Sample%20Sublease%20Agreement.pdf"))
              .build();
      byte[] pdfData;
      try (HttpClient httpClient = HttpClient.newHttpClient()) {
          pdfData = httpClient.send(request, HttpResponse.BodyHandlers.ofByteArray()).body();
      }

      documentText = getLlmText(pdfData);
      IO.println(documentText.substring(0, Math.min(50000, documentText.length())));
  }
  ```

  ```php PHP
  use Smalot\PdfParser\Parser;

  $getLlmText = function (string $pdfData): string {
      $document = (new Parser())->parseContent($pdfData);
      $text = implode("\n", array_map(fn ($page) => $page->getText(), $document->getPages()));

      // 페이지 번호 제거
      $text = preg_replace('/\n\s*\d+\s*\n/', "\n", $text);

      // 불필요한 공백 제거
      return preg_replace('/\s+/', ' ', $text);
  };

  // 요약 cookbook에서 샘플 전대차 계약서 다운로드
  $pdfData = file_get_contents(
      'https://raw.githubusercontent.com/anthropics/claude-cookbooks/main/capabilities/summarization/data/Sample%20Sublease%20Agreement.pdf'
  );

  $documentText = $getLlmText($pdfData);
  echo substr($documentText, 0, 50000) . "\n";
  ```

  ```ruby Ruby
  require "net/http"
  require "pdf-reader"
  require "stringio"

  def get_llm_text(pdf_data)
    reader = PDF::Reader.new(StringIO.new(pdf_data))
    text = reader.pages.map(&:text).join("\n")

    # 페이지 번호 제거
    text = text.gsub(/\n\s*\d+\s*\n/, "\n")

    # 불필요한 공백 제거
    text.gsub(/\s+/, " ")
  end

  # 요약 쿡북에서 샘플 전대차 계약서 다운로드
  pdf_data = Net::HTTP.get(
    URI("https://raw.githubusercontent.com/anthropics/claude-cookbooks/main/capabilities/summarization/data/Sample%20Sublease%20Agreement.pdf")
  )

  document_text = get_llm_text(pdf_data)
  puts document_text[0, 50_000]
  ```
</CodeGroup>

이 예시에서는 먼저 [요약 cookbook](https://platform.claude.com/cookbook/capabilities-summarization-guide)에서 사용된 샘플 전대차 계약서의 PDF를 다운로드합니다. 이 계약서는 [sec.gov 웹사이트](https://www.sec.gov/Archives/edgar/data/1045425/000119312507044370/dex1032.htm)에서 공개적으로 이용 가능한 전대차 계약서에서 가져온 것입니다.

이 예시는 `pypdf` (typescript: `pdf-parse`; csharp: `PdfPig`; go: `github.com/ledongthuc/pdf`; java: `org.apache.pdfbox:pdfbox`; php: `smalot/pdfparser`; ruby: `pdf-reader`) 라이브러리를 사용하여 PDF의 내용을 추출하고 텍스트로 변환합니다. 그런 다음 페이지 번호와 불필요한 공백을 제거하여 텍스트 데이터를 정리합니다.

### 강력한 프롬프트 구축하기

Claude는 다양한 요약 스타일에 적응할 수 있습니다. 프롬프트의 세부 사항을 변경하여 Claude가 더 장황하거나 간결하게 작성하도록, 기술 용어를 더 많이 또는 더 적게 포함하도록, 또는 해당 컨텍스트에 대해 더 높은 수준 또는 더 낮은 수준의 요약을 제공하도록 안내할 수 있습니다.

다음은 전대차 계약서를 분석할 때 생성된 요약이 일관된 구조를 따르도록 보장하는 프롬프트를 작성하는 방법의 예시입니다:

<CodeGroup exclude="shell">
  <CodeGroupItem language="cURL">
    이 워크플로에는 단일 셸 명령보다 더 많은 로직이 필요합니다. 실행하려면 Claude SDK를 사용하세요.
  </CodeGroupItem>

  <CodeGroupItem language="CLI">
    이 워크플로에는 단일 셸 명령보다 더 많은 로직이 필요합니다. 실행하려면 Claude SDK를 사용하세요.
  </CodeGroupItem>

  ```python Python
  # Anthropic 클라이언트 초기화
  client = anthropic.Anthropic()


  def summarize_document(
      text, details_to_extract, model="claude-opus-5-5", max_tokens=16000
  ):
      # 프롬프트 컨텍스트에 넣을 추출 항목 서식 지정
      details_to_extract_str = "\n".join(details_to_extract)

      # 모델에 전대차 계약서 요약 요청
      prompt = f"""Summarize the following sublease agreement. Focus on these key aspects:

      {details_to_extract_str}

      Provide the summary in bullet points nested within the XML header for each section. For example:

      <parties involved>
      - Sublessor: [Name]
      // Add more details as needed
      </parties involved>

      If any information is not explicitly stated in the document, note it as "Not specified". Do not preamble.

      Sublease agreement text:
      {text}
      """

      response = client.messages.create(
          model=model,
          max_tokens=max_tokens,
          system="You are a legal analyst specializing in real estate law, known for highly accurate and detailed summaries of sublease agreements.",
          messages=[
              {"role": "user", "content": prompt},
          ],
      )

      return next(block.text for block in response.content if block.type == "text")


  sublease_summary = summarize_document(document_text, details_to_extract)
  print(sublease_summary)
  ```

  ```typescript TypeScript
  // Anthropic 클라이언트 초기화
  const client = new Anthropic();

  async function summarizeDocument(
    text: string,
    detailsToExtract: string[],
    model: Anthropic.Model = "claude-opus-5-5",
    maxTokens = 16000
  ): Promise<string> {
    // 추출할 세부 정보를 프롬프트 컨텍스트에 넣을 수 있도록 형식 지정
    const detailsToExtractStr = detailsToExtract.join("\n");

    // 모델에 전대차 계약서 요약을 요청하는 프롬프트
    const prompt = `Summarize the following sublease agreement. Focus on these key aspects:

      ${detailsToExtractStr}

      Provide the summary in bullet points nested within the XML header for each section. For example:

      <parties involved>
      - Sublessor: [Name]
      // Add more details as needed
      </parties involved>

      If any information is not explicitly stated in the document, note it as "Not specified". Do not preamble.

      Sublease agreement text:
      ${text}
      `;

    const response = await client.messages.create({
      model,
      max_tokens: maxTokens,
      system:
        "You are a legal analyst specializing in real estate law, known for highly accurate and detailed summaries of sublease agreements.",
      messages: [{ role: "user", content: prompt }]
    });

    const textBlock = response.content.find((block) => block.type === "text");
    if (!textBlock) {
      throw new Error("Expected a text block in the response");
    }
    return textBlock.text;
  }

  const subleaseSummary = await summarizeDocument(documentText, detailsToExtract);
  console.log(subleaseSummary);
  ```

  ```csharp C#
  // Anthropic 클라이언트 초기화
  var client = new AnthropicClient();

  async Task<string> SummarizeDocument(
      string text,
      IReadOnlyList<string> detailsToExtract,
      Model model = Model.ClaudeOpus5_5,
      long maxTokens = 16000
  )
  {
      // 추출할 세부 정보를 프롬프트 컨텍스트에 넣을 수 있도록 형식 지정
      var detailsToExtractStr = string.Join("\n", detailsToExtract);

      // 모델에 전대차 계약서 요약을 요청하는 프롬프트
      var prompt = $"""
          Summarize the following sublease agreement. Focus on these key aspects:

              {detailsToExtractStr}

              Provide the summary in bullet points nested within the XML header for each section. For example:

              <parties involved>
              - Sublessor: [Name]
              // Add more details as needed
              </parties involved>

              If any information is not explicitly stated in the document, note it as "Not specified". Do not preamble.

              Sublease agreement text:
              {text}
          """;

      var response = await client.Messages.Create(
          new()
          {
              Model = model,
              MaxTokens = maxTokens,
              System = "You are a legal analyst specializing in real estate law, known for highly accurate and detailed summaries of sublease agreements.",
              Messages = [new() { Role = Role.User, Content = prompt }],
          }
      );

      foreach (var block in response.Content)
      {
          if (block.TryPickText(out var textBlock))
          {
              return textBlock.Text;
          }
      }
      throw new InvalidOperationException("Expected a text block in the response.");
  }

  var subleaseSummary = await SummarizeDocument(documentText, detailsToExtract);
  Console.WriteLine(subleaseSummary);
  ```

  ```go Go
  // Anthropic 클라이언트 초기화
  var client = anthropic.NewClient()

  func summarizeDocument(text string, detailsToExtract []string, model anthropic.Model, maxTokens int64) string {
  	// 추출할 세부 정보를 프롬프트 컨텍스트에 넣을 수 있도록 형식 지정
  	detailsToExtractStr := strings.Join(detailsToExtract, "\n")

  	// 모델에 전대차 계약서 요약을 요청
  	prompt := fmt.Sprintf(`Summarize the following sublease agreement. Focus on these key aspects:

      %s

      Provide the summary in bullet points nested within the XML header for each section. For example:

      <parties involved>
      - Sublessor: [Name]
      // Add more details as needed
      </parties involved>

      If any information is not explicitly stated in the document, note it as "Not specified". Do not preamble.

      Sublease agreement text:
      %s
      `, detailsToExtractStr, text)

  	response, err := client.Messages.New(context.Background(), anthropic.MessageNewParams{
  		Model:     model,
  		MaxTokens: maxTokens,
  		System: []anthropic.TextBlockParam{
  			{Text: "You are a legal analyst specializing in real estate law, known for highly accurate and detailed summaries of sublease agreements."},
  		},
  		Messages: []anthropic.MessageParam{
  			anthropic.NewUserMessage(anthropic.NewTextBlock(prompt)),
  		},
  	})
  	if err != nil {
  		log.Fatal(err)
  	}

  	for _, block := range response.Content {
  		if textBlock, ok := block.AsAny().(anthropic.TextBlock); ok {
  			return textBlock.Text
  		}
  	}
  	panic("expected a text block in the response")
  }

  // printSubleaseSummary는 전대차 계약서를 요약하고 요약을 출력합니다.
  func printSubleaseSummary() {
  	subleaseSummary := summarizeDocument(documentText, detailsToExtract, anthropic.ModelClaudeOpus5_5, 16000)
  	fmt.Println(subleaseSummary)
  }
  ```

  ```java Java
  // Anthropic 클라이언트 초기화
  AnthropicClient client = AnthropicOkHttpClient.fromEnv();

  String summarizeDocument(String text, List<String> detailsToExtract, Model model, long maxTokens) {
      // 추출할 세부 정보를 프롬프트 컨텍스트에 넣을 수 있도록 형식 지정
      String detailsToExtractStr = String.join("\n", detailsToExtract);

      // 모델에 전대차 계약서 요약을 요청
      String prompt = """
              Summarize the following sublease agreement. Focus on these key aspects:

                  %s

                  Provide the summary in bullet points nested within the XML header for each section. For example:

                  <parties involved>
                  - Sublessor: [Name]
                  // Add more details as needed
                  </parties involved>

                  If any information is not explicitly stated in the document, note it as "Not specified". Do not preamble.

                  Sublease agreement text:
                  %s
              """
              .formatted(detailsToExtractStr, text);

      Message response = client.messages().create(MessageCreateParams.builder()
              .model(model)
              .maxTokens(maxTokens)
              .system("You are a legal analyst specializing in real estate law, known for highly accurate and detailed summaries of sublease agreements.")
              .addUserMessage(prompt)
              .build());

      return response.content().stream()
              .flatMap(block -> block.text().stream())
              .findFirst()
              .orElseThrow()
              .text();
  }

  void printSubleaseSummary() {
      String subleaseSummary = summarizeDocument(
              documentText, detailsToExtract, Model.CLAUDE_OPUS_5_5, 16000);
      IO.println(subleaseSummary);
  }
  ```

  ```php PHP
  // Anthropic 클라이언트 초기화
  $client = new Client();

  $summarizeDocument = function (
      string $text,
      array $detailsToExtract,
      Model $model = Model::CLAUDE_OPUS_5_5,
      int $maxTokens = 16000,
  ) use ($client): string {
      // 추출할 세부 정보를 프롬프트 컨텍스트에 넣을 수 있도록 형식 지정
      $detailsToExtractStr = implode("\n", $detailsToExtract);

      // 모델에 전대차 계약서 요약을 요청
      $prompt = <<<PROMPT
          Summarize the following sublease agreement. Focus on these key aspects:

              {$detailsToExtractStr}

              Provide the summary in bullet points nested within the XML header for each section. For example:

              <parties involved>
              - Sublessor: [Name]
              // Add more details as needed
              </parties involved>

              If any information is not explicitly stated in the document, note it as "Not specified". Do not preamble.

              Sublease agreement text:
              {$text}
          PROMPT;

      $response = $client->messages->create(
          model: $model,
          maxTokens: $maxTokens,
          system: 'You are a legal analyst specializing in real estate law, known for highly accurate and detailed summaries of sublease agreements.',
          messages: [['role' => 'user', 'content' => $prompt]],
      );

      $textBlock = array_find($response->content, fn ($block) => $block instanceof \Anthropic\Messages\TextBlock)
          ?? throw new \RuntimeException('Expected a text block in the response.');

      return $textBlock->text;
  };

  $subleaseSummary = $summarizeDocument($documentText, $detailsToExtract);
  echo $subleaseSummary . "\n";
  ```

  ```ruby Ruby
  # Anthropic 클라이언트 초기화
  CLIENT = Anthropic::Client.new

  def summarize_document(text, details_to_extract, model: Anthropic::Model::CLAUDE_OPUS_5_5, max_tokens: 16_000)
    # 프롬프트 컨텍스트에 넣을 추출 항목 형식 지정
    details_to_extract_str = details_to_extract.join("\n")

    # 모델에 전대차 계약서 요약 요청
    prompt = <<~PROMPT
      Summarize the following sublease agreement. Focus on these key aspects:

          #{details_to_extract_str}

          Provide the summary in bullet points nested within the XML header for each section. For example:

          <parties involved>
          - Sublessor: [Name]
          // Add more details as needed
          </parties involved>

          If any information is not explicitly stated in the document, note it as "Not specified". Do not preamble.

          Sublease agreement text:
          #{text}
    PROMPT

    response = CLIENT.messages.create(
      model:,
      max_tokens:,
      system_: "You are a legal analyst specializing in real estate law, known for highly accurate and detailed summaries of sublease agreements.",
      messages: [{role: :user, content: prompt}]
    )

    response.content.find { |block| block.is_a?(Anthropic::Models::TextBlock) }.text
  end

  sublease_summary = summarize_document(document_text, details_to_extract)
  puts sublease_summary
  ```
</CodeGroup>

이 코드는 Claude를 사용하여 전대차 계약서의 내용을 요약하는 `summarize_document` (typescript, go, java: `summarizeDocument`; php: `$summarizeDocument`; csharp: `SummarizeDocument`) 함수를 구현합니다. 이 함수는 텍스트 문자열과 추출할 세부 정보 목록을 입력으로 받습니다. 이 예시에서 코드는 이전 코드 스니펫에서 정의된 `document_text` (typescript, go, java, csharp: `documentText`; php: `$documentText`) 및 `details_to_extract` (typescript, go, java, csharp: `detailsToExtract`; php: `$detailsToExtract`) 변수를 사용하여 함수를 호출합니다.

함수 내에서는 요약할 문서, 추출할 세부 정보, 문서 요약을 위한 구체적인 지침을 포함하여 Claude를 위한 프롬프트가 생성됩니다. 프롬프트는 Claude에게 추출할 각 세부 정보의 요약을 XML 헤더 안에 중첩하여 응답하도록 지시합니다.

코드가 요약의 각 섹션을 태그 안에 출력하기 때문에, 각 섹션은 후처리 단계에서 쉽게 파싱할 수 있습니다. 이 접근 방식은 사용 사례에 맞게 조정할 수 있는 구조화된 요약을 가능하게 하여 각 요약이 동일한 패턴을 따르도록 합니다.

### 프롬프트 평가하기

프롬프트는 프로덕션에 사용할 준비가 되기까지 테스트와 최적화가 필요한 경우가 많습니다. 솔루션의 준비 상태를 판단하려면 정량적 방법과 정성적 방법을 결합한 체계적인 프로세스를 사용하여 요약의 품질을 평가하세요. 정의한 성공 기준을 기반으로 [강력한 실증적 평가](https://platform.claude.com/docs/ko/test-and-evaluate/develop-tests#build-evaluations)를 만들면 프롬프트를 최적화할 수 있습니다. 다음은 실증적 평가에 포함할 수 있는 몇 가지 지표입니다:

<AccordionGroup>
  <Accordion title="ROUGE 점수">
    생성된 요약과 전문가가 작성한 참조 요약 간의 중복 정도를 측정합니다. 이 지표는 주로 재현율(recall)에 초점을 맞추며 콘텐츠 포괄 범위를 평가하는 데 유용합니다.
  </Accordion>

  <Accordion title="BLEU 점수">
    원래 기계 번역을 위해 개발되었지만, 이 지표는 요약 작업에도 적용할 수 있습니다. BLEU 점수는 생성된 요약과 참조 요약 간의 n-gram 일치 정밀도(precision)를 측정합니다. 점수가 높을수록 생성된 요약이 참조 요약과 유사한 구문과 용어를 포함하고 있음을 나타냅니다. 
  </Accordion>

  <Accordion title="컨텍스트 임베딩 유사도">
    이 지표는 생성된 요약과 참조 요약 모두의 벡터 표현(임베딩)을 생성하는 것을 포함합니다. 그런 다음 주로 코사인 유사도를 사용하여 이러한 임베딩 간의 유사도를 계산합니다. 유사도 점수가 높을수록 정확한 표현이 다르더라도 생성된 요약이 참조 요약의 의미론적 의미와 컨텍스트를 포착하고 있음을 나타냅니다.
  </Accordion>

  <Accordion title="LLM 기반 채점">
    이 방법은 Claude와 같은 LLM을 사용하여 채점 기준표(rubric)에 따라 생성된 요약의 품질을 평가하는 것을 포함합니다. 기준표는 정확성, 완전성, 일관성과 같은 핵심 요소를 평가하도록 특정 요구 사항에 맞게 조정할 수 있습니다. 구현 지침은 

    [LLM 기반 채점 팁](https://platform.claude.com/docs/ko/test-and-evaluate/develop-tests#tips-for-llm-based-grading)

    을 참조하세요.
  </Accordion>

  <Accordion title="사람에 의한 평가">
    참조 요약을 작성하는 것 외에도, 법률 전문가가 생성된 요약의 품질을 평가할 수도 있습니다. 대규모로 수행하기에는 비용과 시간이 많이 들지만, 프로덕션에 배포하기 전 검증 확인 차원에서 소수의 요약에 대해 수행하는 경우가 많습니다.
  </Accordion>
</AccordionGroup>

### 프롬프트 배포하기

다음은 솔루션을 프로덕션에 배포할 때 염두에 두어야 할 몇 가지 추가 고려 사항입니다.

1. **책임 소지 방지:** 요약의 오류가 조직이나 고객에게 법적 책임을 초래할 수 있으므로 그 법적 영향을 이해하세요. 요약이 AI에 의해 생성되었으며 법률 전문가의 검토를 받아야 한다는 점을 명확히 하는 면책 조항이나 법적 고지를 제공하세요.

2. **다양한 문서 유형 처리:** 이 가이드에서는 PDF에서 텍스트를 추출하는 방법을 다룹니다. 실제 환경에서는 문서가 다양한 형식(PDF, Word 문서, 텍스트 파일 등)으로 제공될 수 있습니다. 데이터 추출 파이프라인이 수신할 것으로 예상되는 모든 파일 형식을 변환할 수 있는지 확인하세요.

3. **Claude에 대한 API 호출 병렬화:** 토큰 수가 많은 긴 문서의 경우 Claude가 요약을 생성하는 데 최대 1분이 소요될 수 있습니다. 대규모 문서 컬렉션의 경우, 합리적인 시간 내에 요약을 완료할 수 있도록 Claude에 API 호출을 병렬로 보내는 것이 좋습니다. 병렬로 수행할 수 있는 최대 API 호출 수를 확인하려면 Anthropic의 [속도 제한](https://platform.claude.com/docs/ko/api/rate-limits#rate-limits)을 참조하세요.

***

## 성능 향상

복잡한 시나리오에서는 표준 [프롬프트 엔지니어링 기법](https://platform.claude.com/docs/ko/build-with-claude/prompt-engineering/overview)을 넘어 성능을 향상시키기 위한 추가 전략을 고려하는 것이 도움이 될 수 있습니다. 다음은 몇 가지 고급 전략입니다:

### 메타 요약을 수행하여 긴 문서 요약하기

법률 문서 요약은 긴 문서나 여러 관련 문서를 한 번에 처리해야 하는 경우가 많아 Claude의 "context window"(컨텍스트 윈도우)를 초과할 수 있습니다. 이러한 사용 사례를 처리하기 위해 메타 요약(meta-summarization)이라고 알려진 청킹(chunking) 방법을 사용할 수 있습니다. 이 기법은 문서를 더 작고 관리 가능한 청크로 나눈 다음 각 청크를 개별적으로 처리하는 것을 포함합니다. 그런 다음 각 청크의 요약을 결합하여 전체 문서의 메타 요약을 만들 수 있습니다.

다음은 메타 요약을 수행하는 방법의 예시입니다:

<CodeGroup exclude="shell">
  <CodeGroupItem language="cURL">
    이 워크플로에는 단일 셸 명령보다 더 많은 로직이 필요합니다. 실행하려면 Claude SDK를 사용하세요.
  </CodeGroupItem>

  <CodeGroupItem language="CLI">
    이 워크플로에는 단일 셸 명령보다 더 많은 로직이 필요합니다. 실행하려면 Claude SDK를 사용하세요.
  </CodeGroupItem>

  ```python Python
  def chunk_text(text, chunk_size=20000):
      return [text[i : i + chunk_size] for i in range(0, len(text), chunk_size)]


  def summarize_long_document(
      text, details_to_extract, model="claude-opus-5-5", max_tokens=16000
  ):
      # 프롬프트 컨텍스트에 넣을 추출 항목 서식 지정
      details_to_extract_str = "\n".join(details_to_extract)

      # 청크를 순회하며 각각 요약
      chunk_summaries = [
          summarize_document(
              chunk, details_to_extract, model=model, max_tokens=max_tokens
          )
          for chunk in chunk_text(text)
      ]

      final_summary_prompt = f"""

      You are looking at the chunked summaries of multiple documents that are all related.
      Combine the following summaries of the document from different truthful sources into a coherent overall summary:

      <chunked_summaries>
      {"".join(chunk_summaries)}
      </chunked_summaries>

      Focus on these key aspects:
      {details_to_extract_str}

      Provide the summary in bullet points nested within the XML header for each section. For example:

      <parties involved>
      - Sublessor: [Name]
      // Add more details as needed
      </parties involved>

      If any information is not explicitly stated in the document, note it as "Not specified". Do not preamble.
      """

      response = client.messages.create(
          model=model,
          max_tokens=max_tokens,
          system="You are a legal expert that summarizes notes on one document.",
          messages=[
              {"role": "user", "content": final_summary_prompt},
          ],
      )

      return next(block.text for block in response.content if block.type == "text")


  long_summary = summarize_long_document(document_text, details_to_extract)
  print(long_summary)
  ```

  ```typescript TypeScript
  function chunkText(text: string, chunkSize = 20000): string[] {
    return Array.from({ length: Math.ceil(text.length / chunkSize) }, (_, index) =>
      text.slice(index * chunkSize, (index + 1) * chunkSize)
    );
  }

  async function summarizeLongDocument(
    text: string,
    detailsToExtract: string[],
    model: Anthropic.Model = "claude-opus-5-5",
    maxTokens = 16000
  ): Promise<string> {
    // 추출할 세부 정보를 프롬프트 컨텍스트에 넣을 수 있도록 형식 지정
    const detailsToExtractStr = detailsToExtract.join("\n");

    // 청크를 순회하며 각각 요약
    const chunkSummaries: string[] = [];
    for (const chunk of chunkText(text)) {
      chunkSummaries.push(await summarizeDocument(chunk, detailsToExtract, model, maxTokens));
    }

    const finalSummaryPrompt = `

      You are looking at the chunked summaries of multiple documents that are all related.
      Combine the following summaries of the document from different truthful sources into a coherent overall summary:

      <chunked_summaries>
      ${chunkSummaries.join("")}
      </chunked_summaries>

      Focus on these key aspects:
      ${detailsToExtractStr}

      Provide the summary in bullet points nested within the XML header for each section. For example:

      <parties involved>
      - Sublessor: [Name]
      // Add more details as needed
      </parties involved>

      If any information is not explicitly stated in the document, note it as "Not specified". Do not preamble.
      `;

    const response = await client.messages.create({
      model,
      max_tokens: maxTokens,
      system: "You are a legal expert that summarizes notes on one document.",
      messages: [{ role: "user", content: finalSummaryPrompt }]
    });

    const textBlock = response.content.find((block) => block.type === "text");
    if (!textBlock) {
      throw new Error("Expected a text block in the response");
    }
    return textBlock.text;
  }

  const longSummary = await summarizeLongDocument(documentText, detailsToExtract);
  console.log(longSummary);
  ```

  ```csharp C#
  IEnumerable<string> ChunkText(string text, int chunkSize = 20000) =>
      text.Chunk(chunkSize).Select(chars => new string(chars));

  async Task<string> SummarizeLongDocument(
      string text,
      IReadOnlyList<string> detailsToExtract,
      Model model = Model.ClaudeOpus5_5,
      long maxTokens = 16000
  )
  {
      // 추출할 세부 정보를 프롬프트 컨텍스트에 넣을 수 있도록 형식 지정
      var detailsToExtractStr = string.Join("\n", detailsToExtract);

      // 청크를 순회하며 각각 요약
      List<string> chunkSummaries = [];
      foreach (var chunk in ChunkText(text))
      {
          chunkSummaries.Add(await SummarizeDocument(chunk, detailsToExtract, model, maxTokens));
      }

      var finalSummaryPrompt = $"""

              You are looking at the chunked summaries of multiple documents that are all related.
              Combine the following summaries of the document from different truthful sources into a coherent overall summary:

              <chunked_summaries>
              {string.Concat(chunkSummaries)}
              </chunked_summaries>

              Focus on these key aspects:
              {detailsToExtractStr}

              Provide the summary in bullet points nested within the XML header for each section. For example:

              <parties involved>
              - Sublessor: [Name]
              // Add more details as needed
              </parties involved>

              If any information is not explicitly stated in the document, note it as "Not specified". Do not preamble.
          """;

      var response = await client.Messages.Create(
          new()
          {
              Model = model,
              MaxTokens = maxTokens,
              System = "You are a legal expert that summarizes notes on one document.",
              Messages = [new() { Role = Role.User, Content = finalSummaryPrompt }],
          }
      );

      foreach (var block in response.Content)
      {
          if (block.TryPickText(out var textBlock))
          {
              return textBlock.Text;
          }
      }
      throw new InvalidOperationException("Expected a text block in the response.");
  }

  var longSummary = await SummarizeLongDocument(documentText, detailsToExtract);
  Console.WriteLine(longSummary);
  ```

  ```go Go
  func chunkText(text string, chunkSize int) []string {
  	var chunks []string
  	for chunk := range slices.Chunk([]rune(text), chunkSize) {
  		chunks = append(chunks, string(chunk))
  	}
  	return chunks
  }

  func summarizeLongDocument(text string, detailsToExtract []string, model anthropic.Model, maxTokens int64) string {
  	// 추출할 세부 정보를 프롬프트 컨텍스트에 넣을 수 있도록 형식 지정
  	detailsToExtractStr := strings.Join(detailsToExtract, "\n")

  	// 청크를 순회하며 각 청크를 요약
  	var chunkSummaries []string
  	for _, chunk := range chunkText(text, 20000) {
  		chunkSummaries = append(chunkSummaries, summarizeDocument(chunk, detailsToExtract, model, maxTokens))
  	}

  	finalSummaryPrompt := fmt.Sprintf(`

      You are looking at the chunked summaries of multiple documents that are all related.
      Combine the following summaries of the document from different truthful sources into a coherent overall summary:

      <chunked_summaries>
      %s
      </chunked_summaries>

      Focus on these key aspects:
      %s

      Provide the summary in bullet points nested within the XML header for each section. For example:

      <parties involved>
      - Sublessor: [Name]
      // Add more details as needed
      </parties involved>

      If any information is not explicitly stated in the document, note it as "Not specified". Do not preamble.
      `, strings.Join(chunkSummaries, ""), detailsToExtractStr)

  	response, err := client.Messages.New(context.Background(), anthropic.MessageNewParams{
  		Model:     model,
  		MaxTokens: maxTokens,
  		System: []anthropic.TextBlockParam{
  			{Text: "You are a legal expert that summarizes notes on one document."},
  		},
  		Messages: []anthropic.MessageParam{
  			anthropic.NewUserMessage(anthropic.NewTextBlock(finalSummaryPrompt)),
  		},
  	})
  	if err != nil {
  		log.Fatal(err)
  	}

  	for _, block := range response.Content {
  		if textBlock, ok := block.AsAny().(anthropic.TextBlock); ok {
  			return textBlock.Text
  		}
  	}
  	panic("expected a text block in the response")
  }

  // printLongSummary는 전대차 계약서를 청크별로 요약하고 결합된 요약을 출력합니다.
  func printLongSummary() {
  	longSummary := summarizeLongDocument(documentText, detailsToExtract, anthropic.ModelClaudeOpus5_5, 16000)
  	fmt.Println(longSummary)
  }
  ```

  ```java Java
  List<String> chunkText(String text, int chunkSize) {
      return IntStream.iterate(0, start -> start < text.length(), start -> start + chunkSize)
              .mapToObj(start -> text.substring(start, Math.min(start + chunkSize, text.length())))
              .toList();
  }

  String summarizeLongDocument(String text, List<String> detailsToExtract, Model model, long maxTokens) {
      // 추출할 세부 정보를 프롬프트 컨텍스트에 넣을 수 있도록 형식 지정
      String detailsToExtractStr = String.join("\n", detailsToExtract);

      // 청크를 순회하며 각각 요약
      String chunkSummaries = chunkText(text, 20000).stream()
              .map(chunk -> summarizeDocument(chunk, detailsToExtract, model, maxTokens))
              .collect(Collectors.joining());

      String finalSummaryPrompt = """

                  You are looking at the chunked summaries of multiple documents that are all related.
                  Combine the following summaries of the document from different truthful sources into a coherent overall summary:

                  <chunked_summaries>
                  %s
                  </chunked_summaries>

                  Focus on these key aspects:
                  %s

                  Provide the summary in bullet points nested within the XML header for each section. For example:

                  <parties involved>
                  - Sublessor: [Name]
                  // Add more details as needed
                  </parties involved>

                  If any information is not explicitly stated in the document, note it as "Not specified". Do not preamble.
              """
              .formatted(chunkSummaries, detailsToExtractStr);

      Message response = client.messages().create(MessageCreateParams.builder()
              .model(model)
              .maxTokens(maxTokens)
              .system("You are a legal expert that summarizes notes on one document.")
              .addUserMessage(finalSummaryPrompt)
              .build());

      return response.content().stream()
              .flatMap(block -> block.text().stream())
              .findFirst()
              .orElseThrow()
              .text();
  }

  void printLongSummary() {
      String longSummary = summarizeLongDocument(
              documentText, detailsToExtract, Model.CLAUDE_OPUS_5_5, 16000);
      IO.println(longSummary);
  }
  ```

  ```php PHP
  $chunkText = fn (string $text, int $chunkSize = 20000): array => mb_str_split($text, $chunkSize);

  $summarizeLongDocument = function (
      string $text,
      array $detailsToExtract,
      Model $model = Model::CLAUDE_OPUS_5_5,
      int $maxTokens = 16000,
  ) use ($client, $chunkText, $summarizeDocument): string {
      // 추출할 세부 정보를 프롬프트 컨텍스트에 넣을 수 있도록 형식 지정
      $detailsToExtractStr = implode("\n", $detailsToExtract);

      // 청크를 순회하며 각각 요약
      $chunkSummaries = array_map(
          fn (string $chunk) => $summarizeDocument($chunk, $detailsToExtract, $model, $maxTokens),
          $chunkText($text),
      );
      $chunkSummariesStr = implode('', $chunkSummaries);

      $finalSummaryPrompt = <<<PROMPT

              You are looking at the chunked summaries of multiple documents that are all related.
              Combine the following summaries of the document from different truthful sources into a coherent overall summary:

              <chunked_summaries>
              {$chunkSummariesStr}
              </chunked_summaries>

              Focus on these key aspects:
              {$detailsToExtractStr}

              Provide the summary in bullet points nested within the XML header for each section. For example:

              <parties involved>
              - Sublessor: [Name]
              // Add more details as needed
              </parties involved>

              If any information is not explicitly stated in the document, note it as "Not specified". Do not preamble.
          PROMPT;

      $response = $client->messages->create(
          model: $model,
          maxTokens: $maxTokens,
          system: 'You are a legal expert that summarizes notes on one document.',
          messages: [['role' => 'user', 'content' => $finalSummaryPrompt]],
      );

      $textBlock = array_find($response->content, fn ($block) => $block instanceof \Anthropic\Messages\TextBlock)
          ?? throw new \RuntimeException('Expected a text block in the response.');

      return $textBlock->text;
  };

  $longSummary = $summarizeLongDocument($documentText, $detailsToExtract);
  echo $longSummary . "\n";
  ```

  ```ruby Ruby
  def chunk_text(text, chunk_size: 20_000)
    text.chars.each_slice(chunk_size).map(&:join)
  end

  def summarize_long_document(text, details_to_extract, model: Anthropic::Model::CLAUDE_OPUS_5_5, max_tokens: 16_000)
    # 프롬프트 컨텍스트에 넣을 추출 항목 형식 지정
    details_to_extract_str = details_to_extract.join("\n")

    # 청크를 순회하며 각각 요약
    chunk_summaries = chunk_text(text).map do |chunk|
      summarize_document(chunk, details_to_extract, model:, max_tokens:)
    end

    final_summary_prompt = <<~PROMPT

          You are looking at the chunked summaries of multiple documents that are all related.
          Combine the following summaries of the document from different truthful sources into a coherent overall summary:

          <chunked_summaries>
          #{chunk_summaries.join}
          </chunked_summaries>

          Focus on these key aspects:
          #{details_to_extract_str}

          Provide the summary in bullet points nested within the XML header for each section. For example:

          <parties involved>
          - Sublessor: [Name]
          // Add more details as needed
          </parties involved>

          If any information is not explicitly stated in the document, note it as "Not specified". Do not preamble.
    PROMPT

    response = CLIENT.messages.create(
      model:,
      max_tokens:,
      system_: "You are a legal expert that summarizes notes on one document.",
      messages: [{role: :user, content: final_summary_prompt}]
    )

    response.content.find { |block| block.is_a?(Anthropic::Models::TextBlock) }.text
  end

  long_summary = summarize_long_document(document_text, details_to_extract)
  puts long_summary
  ```
</CodeGroup>

`summarize_long_document` (typescript, go, java: `summarizeLongDocument`; php: `$summarizeLongDocument`; csharp: `SummarizeLongDocument`) 함수는 앞서 살펴본 `summarize_document` (typescript, go, java: `summarizeDocument`; php: `$summarizeDocument`; csharp: `SummarizeDocument`) 함수를 기반으로, 문서를 더 작은 청크로 분할하고 각 청크를 개별적으로 요약합니다.

코드는 원본 문서 내 20,000자 단위의 각 청크에 `summarize_document` (typescript, go, java: `summarizeDocument`; php: `$summarizeDocument`; csharp: `SummarizeDocument`) 함수를 적용하여 이를 수행합니다. 그런 다음 개별 요약을 결합하고, 이러한 청크 요약으로부터 최종 요약을 생성합니다.

예시 PDF의 경우 전체 문서가 Claude의 컨텍스트 윈도우 내에 들어가므로 `summarize_long_document` (typescript, go, java: `summarizeLongDocument`; php: `$summarizeLongDocument`; csharp: `SummarizeLongDocument`) 함수가 반드시 필요한 것은 아닙니다. 그러나 Claude의 컨텍스트 윈도우를 초과하는 문서나 여러 관련 문서를 함께 요약할 때는 필수적입니다. 어느 경우든, 이 메타 요약 기법은 앞선 단일 요약 접근 방식에서 놓쳤던 추가적인 중요 세부 정보를 최종 요약에서 포착하는 경우가 많습니다.

### 요약 인덱싱 문서를 사용하여 대규모 문서 컬렉션 탐색하기

LLM으로 문서 컬렉션을 검색하는 것은 일반적으로 "retrieval-augmented generation"(검색 증강 생성), 즉 RAG를 포함합니다. 그러나 대용량 문서가 관련되거나 정확한 정보 검색이 중요한 시나리오에서는 기본적인 RAG 접근 방식으로는 충분하지 않을 수 있습니다. 요약 인덱싱 문서(summary indexed documents)는 기존 RAG 방법보다 적은 컨텍스트를 사용하여 검색을 위한 문서 순위를 더 효율적으로 매기는 방법을 제공하는 고급 RAG 접근 방식입니다. 이 접근 방식에서는 먼저 Claude를 사용하여 코퍼스의 각 문서에 대한 간결한 요약을 생성한 다음, Claude를 사용하여 질의에 대한 각 요약의 관련성 순위를 매깁니다. 코드 기반 예시를 포함한 이 접근 방식에 대한 자세한 내용은 [요약 cookbook](https://platform.claude.com/cookbook/capabilities-summarization-guide)의 요약 인덱싱 문서 섹션을 확인하세요.

### Claude를 fine-tuning하여 데이터셋에서 학습하기

Claude의 요약 생성 능력을 향상시키는 또 다른 고급 기법은 "fine-tuning"(미세 조정)입니다. Fine-tuning은 법률 문서 요약 요구 사항에 특별히 부합하는 맞춤형 데이터셋으로 Claude를 학습시켜 Claude가 사용 사례에 적응하도록 하는 것을 포함합니다. 다음은 fine-tuning을 수행하는 방법에 대한 개요입니다:

1. **오류 식별:** Claude의 요약이 부족한 사례를 수집하는 것부터 시작하세요. 여기에는 중요한 법적 세부 정보 누락, 컨텍스트 오해 또는 부적절한 법률 용어 사용 등이 포함될 수 있습니다.

2. **데이터셋 큐레이션:** 이러한 문제를 식별한 후에는 문제가 있는 예시들로 데이터셋을 구성하세요. 이 데이터셋에는 원본 법률 문서와 함께 수정된 요약이 포함되어야 하며, 이를 통해 Claude가 원하는 동작을 학습하도록 합니다.

3. **Fine-tuning 수행:** Fine-tuning은 큐레이션된 데이터셋으로 모델을 재학습시켜 가중치와 매개변수를 조정하는 것을 포함합니다. 이 재학습은 Claude가 법률 도메인의 특정 요구 사항에 더 잘 적응하도록 도와 기준에 따라 문서를 요약하는 능력을 향상시킵니다.

4. **반복적 개선:** Fine-tuning은 일회성 프로세스가 아닙니다. Claude가 계속해서 요약을 생성함에 따라 성능이 부족했던 새로운 예시를 반복적으로 추가하여 기능을 더욱 정교하게 다듬을 수 있습니다. 시간이 지남에 따라 이러한 지속적인 피드백 루프는 법률 문서 요약 작업에 고도로 특화된 모델을 만들어낼 것입니다.

<Tip>
  Fine-tuning은 현재 Amazon Bedrock을 통해서만 사용할 수 있습니다. 자세한 내용은 

  [AWS 출시 블로그](https://aws.amazon.com/blogs/machine-learning/fine-tune-anthropics-claude-3-haiku-in-amazon-bedrock-to-boost-model-accuracy-and-quality/)

  에서 확인할 수 있습니다.
</Tip>

<CardGroup cols={2}>
  <Card title="요약 cookbook" icon="link" href="https://platform.claude.com/cookbook/capabilities-summarization-guide">
    Claude를 사용하여 계약서를 요약하는 방법에 대한 완전히 구현된 코드 기반 예시를 확인하세요.
  </Card>

  <Card title="인용 cookbook" icon="link" href="https://platform.claude.com/cookbook/misc-using-citations">
    정보의 정확성과 설명 가능성을 보장하는 방법에 대한 지침은 인용 cookbook 레시피를 살펴보세요.
  </Card>
</CardGroup>
