KO ▾
API 키 받기

100k 긴 컨텍스트 실전: 이어쓰기, 요약, 분할, 예산

컨텍스트 100,000 토큰은 넉넉해 보이지만, 실제로 사용해보면 소설 한 권이 들어가지 않거나, 수십 페이지 문서에 출력 요구사항을 더하면 한도에 가까워지는 것을 알 수 있습니다. 이 페이지는 긴 컨텍스트를 예산으로 관리하는 방법을 다룹니다: 토큰을 대략 어떻게 계산하고, 출력에 공간을 어떻게 확보하며, 긴 문서는 어떻게 분할 요약하고, 소설은 슬라이딩 윈도우와 로딩 오버뷰로 이어쓰는지 설명합니다. 코드는 Python으로 작성되었으나, 아이디어는 모든 언어에 적용 가능합니다.

에 업데이트됨

핵심 요약

  • 100,000은 프롬프트와 completion의 합계이며, 입력만 위한 상한선이 아닙니다.
  • 중국어 토큰 수는 대략적으로만 추정 가능하며, 자당 약 1~1.5 토큰이며, 최종적으로는 응답의 usage를 기준으로 합니다.
  • 긴 문서 요약에는 분할 후 병합을 사용하고, 소설 이어쓰기에는 최근 장의 원문과 로딩 오버뷰를 사용합니다.
  • max_tokens 기본값은 2048이며 최대 16,000입니다. 긴 내용을 작성하기 전에 예산을 계산한 후 설정하세요.

먼저 한도를 명확히 계산하기

모든 것을 결정하는 세 가지 숫자:

  • 컨텍스트 창은 토큰 100,000이며, 프롬프트와 completion의 합계입니다.
  • max_tokens 기본값은 2048이며, 단일 요청 최대 16,000입니다.
  • 요청 본문은 8 MB를 초과할 수 없으며, 이 한도는 순수 텍스트의 경우 거의 도달하지 않습니다. 실제로 먼저 도달하는 한도는 토큰입니다.

예산 공식은 간단합니다: prompt 사용 가능 = 100000 - max_tokens입니다. 모델이 한 번에 4000 토큰을 출력하도록 설정하면 prompt는 최대 60,000만 남습니다. 이를 초과하면 API는 400 오류를 반환하며 자동으로 잘라주지 않습니다. 따라서 "전체를 넣고 보자"는 전략이 아니라 운을 파는 것입니다.

또 다른 흔한 오해: max_tokens를 크게 설정하면 더 안전합니다. 실제로 이는 예약된 출력 공간이며, 16,000으로 설정하면 프롬프트는 48,000만 가능함을 의미합니다. 필요에 따라 설정하고 무작정 최대값으로 올리지 마세요.

예를 들어, 30만 자의 인터뷰 정리본을 요약해야 한다고 가정해 봅시다. 자당 1.3 토큰으로 대략 계산하면 약 39만 토큰으로, 한도의 6배 이상입니다. 어쨌든 전체를 넣을 수 없습니다. 또한 단일 요약 출력이 800 토큰이 필요하다고 가정하면, 각 블록의 입력은 최대 63,000까지 가능하지만 실제로는 여유분을 10% 더 남겨야 하므로 약 5만 토큰 정도입니다. 하지만 이것이 최적의 해결책은 아닙니다. 블록이 너무 크면 모델이 중간 부분의 내용에 대한 관심이 약해지므로, 아래 방법처럼 블록을 더 작게 나누어 여러 번 호출하는 것이 좋습니다.

토큰 추정: 근사 알고리즘

로컬 토크나이저가 없는 경우 아래 경험치를 이용해 대략적으로 추정할 수 있습니다. 아래 수치는 근사치이며 정확한 값이 아니며, 텍스트에 따라 최대 10~20% 차이가 날 수 있습니다:

텍스트 유형근사 환산
중국어 본문자당 약 1~1.5 토큰
영어약 4자당 1 토큰 또는 단어당 약 1.3 토큰
코드, JSON, 기호 밀집 텍스트일반 텍스트보다 토큰을 더 많이 소모하므로 보수적인 높은 값으로 추정하는 것이 좋습니다.

다음은 충분한 대략 계산 함수이며, 여유분을 고려한 예산 계산도 포함됩니다:

CONTEXT = 100000

def est_tokens(text: str) -> int:
    """粗估:中文按每字 1.3 token,其余字符按每 4 个字符 1 token。仅为近似。"""
    zh = sum(1 for c in text if "\u4e00" <= c <= "\u9fff")
    other = len(text) - zh
    return int(zh * 1.3 + other / 4) + 1

def max_prompt_budget(max_tokens: int, margin: float = 0.1) -> int:
    """给定计划的 max_tokens,返回 prompt 最多能用多少 token(留 margin 余量)。"""
    return int((CONTEXT - max_tokens) * (1 - margin))

# 例:计划让模型写 3000 token,prompt 预算
print(max_prompt_budget(3000))   # 54900

보정 방법: 전형적인 입력을 하나 가져와 요청을 보내고 응답의 usage.prompt_tokens을 읽어보며 자신의 추정치와 비교하여 실제 데이터의 배율을 계산한 후 계수를 실제 측정값으로 조정하세요. 추정은 "분할할지 여부"를 결정하는 데 사용되며, 정산에는 usage를 확인하세요.

긴 문서 요약: 분할 후 병합

문서가 예산을 초과할 때 표준적인 방법은 map-reduce입니다: 먼저 분할하여 각각 요약한 후, 요약을 병합하여 전체 요약을 만듭니다. 다음 세부 사항에 유의하세요:

  1. 문단 경계에서 나누고 고정 글자 수로 강제로 나누지 마세요. 그렇지 않으면 문장이 중간에 잘릴 수 있습니다.
  2. 각 블록의 예산을 충분히 확보하세요. 한도의 3분의 1을 초과하지 않는 것을 권장하며, 지시문과 출력에 공간을 확보하세요.
  3. 분할 요약 시 인명, 숫자, 주요 사건을 유지하도록 요구하세요. 그렇지 않으면 병합 시 정보가 이미 손실됩니다.
  4. 요약의 요약이 여전히 너무 긴 경우 재귀적으로 한 단계 더 수행합니다.
import os
from openai import OpenAI

client = OpenAI(base_url="https://api.wushenchaapi.com/v1", api_key=os.environ["API_KEY"])

def split_paragraphs(text, budget):
    """按段落切块,每块估算不超过 budget token。"""
    chunks, cur, used = [], [], 0
    for p in text.split("\n"):
        t = int(len(p) * 1.3) + 1          # 中文粗估
        if used + t > budget and cur:
            chunks.append("\n".join(cur))
            cur, used = [], 0
        cur.append(p)
        used += t
    if cur:
        chunks.append("\n".join(cur))
    return chunks

def ask(prompt, max_tokens=800):
    r = client.chat.completions.create(
        model="uncensored",
        temperature=0.3,
        max_tokens=max_tokens,
        messages=[{"role": "user", "content": prompt}],
    )
    return r.choices[0].message.content

def summarize_long(text, budget=12000):
    parts = split_paragraphs(text, budget)
    partial = [ask("用不超过 200 字概括下面这段,保留人名和关键事件:\n\n" + p) for p in parts]
    merged = "\n".join(f"第{i+1}部分:{s}" for i, s in enumerate(partial))
    return ask("下面是分段摘要,请合并成一份 400 字以内的整体摘要:\n\n" + merged, max_tokens=900)

여기서 temperature은 0.3으로 설정하며, 요약 작업에는 안정성이 중요합니다. 각 블록 호출은 서로 독립적이므로 병렬 요청이 가능하지만, 각 API 키당 분당 300회 요청의 속도 제한에 유의하세요. 수십 개의 블록이 있는 문서라면 전혀 문제가 되지 않습니다.

블록 크기를 어떻게 결정할지에 대한 표준 답은 없습니다. 경험적으로 블록당 1만~1만5천 토큰으로 설정하면 세부 사항 유지와 호출 횟수 사이의 균형을 맞출 수 있습니다. 샘플을 사용하여 5000, 12000, 25000의 세 가지 블록 크기로 각각 실행하고 요약에서 누락된 주요 사실 수를 비교하면 문서 유형에 적합한 값을 선택할 수 있습니다. 계약서나 기술 문서와 같은 정보 밀도가 높은 자료는 블록을 더 작게, 대화 기록과 같은 중복이 많은 자료는 더 크게 설정할 수 있습니다.

소설 이어쓰기: 슬라이딩 윈도우와 로딩 오버뷰

10여 장까지 쓰면 전체를 넣을 수 없으며, 넣을 필요도 없습니다. 두 가지 기억 방식을 사용합니다:

  • 근접:최근 2~3장 원문을 사용하여 문체, 대화 리듬 및 장면 디테일의 일관성을 유지합니다.
  • 원경:이전 장을 요약하여 캐릭터 관계, 복선, 해결되지 않은 단서를 유지합니다.

각 장을 완성할 때마다 모델에게 해당 장을 3~5줄로 요약하여 outline에 추가하도록 합니다. outline 자체가 3000 토큰을 초과하면 전체를 다시 압축합니다.

def continue_story(chapters, outline, new_hint, keep_last=3):
    """chapters: 已写章节列表。只带最近 keep_last 章原文,更早的用 outline(滚动大纲)代替。"""
    recent = "\n\n".join(chapters[-keep_last:])
    prompt = (
        f"【全书大纲(早期章节摘要)】\n{outline}\n\n"
        f"【最近章节原文】\n{recent}\n\n"
        f"【下一章要求】\n{new_hint}\n\n请写下一章,约 2000 字。"
    )
    return ask(prompt, max_tokens=4000)

플롯 포인트는 가장 쉽게 놓치는 요소입니다. outline에 "미해결 플롯 포인트" 항목을 별도로 만들어두고, 이어쓰기 시 해당 항목 중 하나를 처리하도록 명시합니다. 인물명, 지명도 고정 목록으로 system에 배치하여 컨텍스트 창을 벗어나도 모델이 임의로 변경하지 않도록 합니다.

계속해서 작성할 때 간과하기 쉬운 문제가 하나 더 있습니다. 바로 스타일 드리프트입니다. 모델은 최근 몇 장의 내용에 점점 더 가까워지며, 앞부분의 스타일과 뒷부분이 일치하지 않으면 그 차이가 확대됩니다. 해결책은 system 프롬프트에 "스타일 샘플"을 작성하는 것입니다. 가장 만족하는 원문 일부를 선택하여 고정된 앵커로 삽입하면, 컨텍스트 창이 슬라이드되어도 스타일이 유지됩니다.

max_tokens 및 잘림 처리

응답이 잘리는 상황은 두 가지가 있으므로 구분해야 합니다:

  1. finish_reason이 length인 경우: 설정한 max_tokens에 도달했습니다. 해결책은 max_tokens를 늘리거나 모델을 분할하여 쓰게 하는 것입니다. "여기까지 쓰고, 계속하라고 하면 이어서 씁니다".
  2. 요청이 400을 직접 반환: prompt와 max_tokens의 합이 100,000을 초과했습니다. 해결책은 입력을 줄이거나 max_tokens을 줄이는 것입니다.

실용적인 예산 대조표:

작업권장 max_tokens프롬프트 사용 가능 (약)
요약, 추출80063,200
단일 장 이어쓰기 (약 2000자)400060,000
만자급 긴 출력16,000 (상한)48,000

긴 출력은 응답 시간의 영향을 받으므로 스트리밍 출력을 함께 사용하여 생성되는 대로 표시하는 것을 권장합니다.

이어쓰기가 잘릴 때, 단순히 잘린 내용을 모델에 "계속해"라고 던지지 마십시오. 더 안정적인 방법은 생성된 부분을 assistant 메시지로 messages에 다시 넣고, user 메시지로 "이전 문장 뒤에 이어서 작성하되 중복하지 마십시오"를 추가하는 것입니다. 이렇게 하면 가장 자연스럽게 연결됩니다. 이 단계는 prompt를 길게 만들므로 예산을 다시 확인해야 합니다.

긴 컨텍스트 사용 체크리스트

  • 각 요청 전에 추정 함수로 프롬프트를 계산하고, 예산을 초과하면 분기 처리합니다.
  • 각 응답마다 usage를 기록하여 추정 계수를 지속적으로 보정합니다.
  • system과 개요는 콘텐츠 맨 앞에 배치하고, 지시사항의 핵심 요구사항은 마지막에 다시 강조합니다.
  • finish_reason을 확인하고, length인 경우 경고하거나 이어쓰기를 진행합니다.
  • 대화 기록의 상한을 설정하고, 상한을 초과하면 가장 오래된 내용을 삭제하여 인터페이스가 400을 반환하기 전에 처리합니다.

관련 내용: 프롬프트 구조는 프롬프트 작성법을, 오류 처리는 오류 코드 및 문제 해결 가이드을, 가격 정보는 가격 페이지를 참조하세요.

마지막으로 한 가지 주의할 점은 긴 컨텍스트가 긴 기억을 의미하지 않는다는 것입니다. 모델은 매 요청마다 제공된 내용을 처음부터 읽으며, 이전 호출의 내용을 기억하지 않습니다. 연속성을 유지하려면 사용자가 직접 이력을 전달해야 합니다.

자주 묻는 질문

100k 컨텍스트에 출력도 포함되나요?

포함됩니다. 상한은 프롬프트와 completion의 합계이므로, max_tokens를 크게 설정할수록 입력할 수 있는 양이 줄어듭니다.

중국어 한 글자는 정확히 몇 개의 토큰인가요?

대략적으로 추정할 뿐이며, 평균적으로 글자당 1~1.5개 정도이며 텍스트에 따라 변동이 있습니다. 가장 정확한 방법은 응답의 usage 필드를 확인하고 실제 측정값으로 보정하는 것입니다.

상한을 초과하면 입력이 자동으로 잘리나요?

아니요, 요청이 400을 반환합니다. 호출 전에 사용자가 직접 분할하거나 오래된 내용을 버려야 합니다.

한 번에 최대 얼마나 생성할 수 있나요?

max_tokens 기본값은 2048이며, 한 번에 생성할 수 있는 최대 길이는 16,000입니다. 더 긴 콘텐츠는 여러 번에 걸쳐 생성하고, 개요나 요약을 통해 일관성을 유지하세요.

양식만 작성하면 API 키를 받을 수 있습니다.

계정을 생성하고, API 키를 복사한 후 Base URL을 수정하세요. 설정은 매우 간단합니다.