본문으로 바로가기

Claude Fable 5.1 API 튜토리얼: Python으로 장시간 동작하는 개발자 에이전트 구축

Anthropic의 최신 플래그십 모델을 사용해, 변경 계획 전 Flask 리포지토리를 읽는 Python 에이전트를 만드는 방법을 배워보세요. 진행 업데이트, 읽기 전용 파일 도구, 비용 제어를 추가합니다.
업데이트됨 2026년 9월 3일  · 15분 읽다

AI로 탐색하기

ChatGPTClaudePerplexity

API 호출로 새 모델을 시험할 때, 첫 응답만으로는 알 수 있는 게 많지 않습니다. 제 첫 Fable 5.1 실행은 유효한 구조와 일반적인 계획을 돌려줬습니다. 그래서 대화가 길어지면 어떻게 되는지 알고 싶었습니다. 애플리케이션이 이력을 온전히 유지하는지, 프로젝트 외부를 읽지 않고 파일을 검사할 수 있는지, 진행 상황을 보고하고 비용이 어디서 발생했는지 보여줄 수 있는지 말이죠.

Claude Fable 5.1 개요에서는 출시 내용, 벤치마크, 더 넓은 모델 비교를 다룹니다. 여기서는 작은 Python 호출로 시작해 그 위에 에이전트 루프를 구축해 보겠습니다. 최종 에이전트는 기능 요청을 받고 Flask 프로젝트를 읽은 뒤, 실제로 검사한 파일에 근거한 계획을 반환합니다.

다음 내용을 다룹니다:

  • Claude Fable 5.1 API 호출과 콘텐츠 블록 안전하게 읽기
  • 추론 effort 설정 및 대화 중간에 변경하기(베타)
  • 시스템 지시문을 단일 턴으로 범위 제한하기(베타)
  • Pydantic으로 구조화된 계획 반환
  • 프로젝트 루트 경계가 있는 읽기 전용 리포지토리 도구 추가
  • 멀티 턴 도구 루프 실행
  • 도구 호출 사이의 에이전트 진행 상황 업데이트 읽기(베타)
  • append-only 이력으로 thinking 블록 유효성 유지
  • 반복되는 컨텍스트 캐시하고 공개 요금으로 요청 비용 추정
  • 거부 처리 및 FastAPI로 에이전트 제공

베타 기능은 날짜가 있는 헤더를 사용하므로, 출시에 앞서 Anthropic 문서와 일치하는지 확인하세요.

에이전트 루프에서 Claude Fable 5.1을 실행하면 비용이 얼마나 드나요?

에이전트는 매 턴마다 동일한 시스템 프롬프트, 도구 정의, 리포지토리 컨텍스트를 다시 보냅니다. 따라서 청구를 좌우하는 요금은 입력 요금이 아니라 캐시 읽기 요금입니다.

Fable 5.1의 요금은 입력 토큰 100만 개당 $10, 출력 토큰 100만 개당 $50로, Fable 5와 동일합니다. 캐시 읽기는 100만 개당 $0.25로 $1에서 내려갔고, 5분 캐시 쓰기는 100만 개당 $12.50으로 유지됩니다. 전체 요금표와 Anthropic의 절감 추정치는 Claude Fable 5.1 가이드에서 확인할 수 있습니다.

캐시된 접두부를 읽는 것은 저렴합니다. 쓰기는 읽기의 50배 요금이라 비싸므로, 접두부가 여러 번 다시 읽힐 때 루프의 비용 절감이 성과를 냅니다. 뒤에서 실제 실행의 비용 분해를 보여 주며 어떤 항목이 지배적이었는지도 설명합니다.

토큰 상한은 예산이 아니라 모델에서 옵니다. Fable 5.1은 100만 토큰의 컨텍스트 윈도우와 응답당 최대 128K 출력 토큰을 제공하며, max_tokens는 사고(thinking)와 응답 텍스트를 합한 하드 리밋입니다. 높은 effort에서는 둘 다 공간이 필요하므로, 아래 에이전트 루프가 보기 좋은 수치 대신 16,000을 설정한 이유입니다.

데이터 보존, 우선순위 티어, 워터마킹

코드를 작성하기 전에 몇 가지 접근 제약이 중요합니다. 이 중 둘은 요청을 즉시 막습니다.

  • Fable 5.1은 30일 데이터 보존이 필수이며, Anthropic이 접근을 승인하지 않으면 zero data retention에서는 사용할 수 없습니다. 호환되지 않는 워크스페이스에서 요청하면 다른 힌트 없이 400 invalid_request_error가 반환됩니다.

  • 이 모델은 Priority Tier를 지원하지 않습니다. Fable 5는 지원하므로 마이그레이션 시 이 점에 걸리는 경우가 있습니다.

  • Fable 5.1의 텍스트 출력에는 Anthropic의 텍스트 워터마크가 포함됩니다. 토큰은 추가되지 않으며 요청 변경도 필요 없습니다.

API로 Claude Fable 5.1을 사용해 리포지토리를 인지하는 개발자 에이전트 만들기

워크플로는 두 단계입니다.

  1. 경계가 있는 검사 루프가 허용된 프로젝트 파일을 읽습니다.
  2. 구조화 출력을 사용하는 최종 요청이 그 컨텍스트를 계획으로 바꿉니다. 

샘플 프로젝트는 북마크 저장 및 검색을 위한 작은 Flask JSON API이며, 앱 팩토리, 3개의 블루프린트, 구성 모듈, 모델, pytest 스위트로 이루어져 있습니다. 실행 예제로는 rate limiting을 사용합니다. 에이전트가 필요한 파일과 테스트를 파악하기 전에 앱 설정, 라우트, 구성, 테스트를 검사해야 하기 때문입니다. 전체 코드와 샘플 프로젝트는 GitHub 리포지토리에서 확인할 수 있습니다.

기능 요청이 Claude Fable 5.1 에이전트, 경로 허용 목록, 샘플 프로젝트를 거쳐 구조화된 계획으로 돌아오는 흐름을 보여 주는 다이어그램

요청은 하나의 경계를 통해 파일에 도달합니다. 이미지: 작성자

에이전트는 세 가지 도구만 사용할 수 있습니다: list_project_files, read_project_file, get_project_metadata. Claude는 파일 시스템에 직접 접근하지 않습니다. 경로를 요청하면, 해당 경로가 허용되는지 여부는 여러분의 코드가 결정합니다.

Python에서 Claude Fable 5.1 API 설정

별도의 Python 환경으로 시작하고 API 키는 서버에 보관하세요.

사전 준비

Python 3.10 이상과 claude-fable-5-1에 접근 가능한 Anthropic API 키가 필요합니다. 

API 키를 만들려면 Claude Console에 로그인하고 API keys 페이지를 연 다음 Create key를 클릭하고 키를 복사하세요. 용도를 기억하기 쉬운 이름을 부여하고 만료일을 선택해 키를 안전하게 보관하는 것이 모범 사례입니다.

SDK 설치 및 API 키 추가

가상 환경을 만들고 패키지를 설치하세요:

python -m venv .venv
source .venv/bin/activate          # macOS or Linux
.venv\Scripts\Activate.ps1         # Windows PowerShell
pip install anthropic==1.3.0 pydantic fastapi uvicorn python-dotenv

베타 기능은 자주 변경되므로 SDK 버전을 고정하세요. 진행 상황 업데이트에는 최소 1.1.0이 필요하며, 예시에서는 1.3.0을 사용합니다.

키는 .env 파일에 넣고 첫 커밋 전에 .gitignore.env를 추가하세요. 브라우저나 접근 가능한 리포지토리에 두지 말고, 여러분이 제어하는 서버에만 보관해야 합니다. 노출되면 입력, 출력, 캐시 작업 전반에 걸친 무단 API 사용과 비용이 발생할 수 있습니다.

ANTHROPIC_API_KEY=sk-ant-your-key-here

이렇게 설정하면 클라이언트가 키를 자동으로 찾습니다.

Python에서 첫 Claude Fable 5.1 API 호출 만들기

무언가를 얹기 전에 가장 작은 API 요청부터 보내 보세요.

첫 API 요청 보내기

클라이언트를 초기화하고 사용자 메시지 하나를 보낸 뒤 응답 메타데이터를 출력합니다:

from anthropic import Anthropic
from dotenv import load_dotenv

load_dotenv()

client = Anthropic()
MODEL = "claude-fable-5-1"

response = client.messages.create(
    model=MODEL,
    max_tokens=512,
    messages=[{"role": "user", "content": "Reply in one sentence to confirm the API connection is working."}],
)

text = next((b.text for b in response.content if b.type == "text"), None)
print(text if text is not None else f"No text returned ({response.stop_reason})")
print(f"Model: {response.model}")
print(f"Stop reason: {response.stop_reason}")
print(f"Input tokens: {response.usage.input_tokens}")
print(f"Output tokens: {response.usage.output_tokens}")
print(f"Request ID: {response._request_id}")

모델 ID, 중지 사유, 토큰 수, 요청 ID가 포함된 Claude Fable 5.1 API 응답을 보여 주는 터미널

첫 호출은 텍스트와 메타데이터를 반환합니다. 이미지: 작성자

next(...) 호출은 첫 텍스트 블록을 선택합니다. 적응형 사고(thinking)는 항상 켜져 있으며 끌 수 없습니다. 따라서 응답이 thinking 블록으로 시작할 수 있습니다. thinking: {"type": "disabled"}를 보내면 끄는 대신 400이 반환됩니다. thinking 블록이 먼저 오면 response.content[0].text는 예외를 발생시킵니다.

해결책은 고정 위치를 가정하지 말고 블록 유형으로 필터링하는 것입니다. 또한 response._request_id를 기록해 두세요. Anthropic 지원이 요청을 추적할 때 사용합니다.

아래는 계획과 effort 예시에서 사용한 요청입니다. 에이전트가 여러 파일을 검사해야 합니다:

feature_request = (
    "Add rate limiting to the public API endpoints so one client cannot exhaust "
    "the search endpoint or brute force the token endpoint."
)

effort 수준과 토큰 수를 비교하는 동안 이 텍스트는 변경하지 마세요. 그래야 결과가 다른 프롬프트가 아닌 API 설정을 설명합니다.

output_config로 추론 effort 설정

추론 effort는 output_config로 설정합니다. low, medium, high, xhigh, max를 허용합니다. API 기본값은 high입니다.

response = client.messages.create(
    model=MODEL,
    max_tokens=8192,
    output_config={"effort": "high"},
    messages=[{"role": "user", "content": feature_request}],
)

effort는 토큰 사용량, 도구 동작, 지연 시간에 영향을 줄 수 있습니다. 네 가지 effort 수준 각각에서 동일한 기능 요청을 세 번씩 실행했고, 표에는 평균값을 표시했습니다:

Effort

Thinking 토큰

총 출력 토큰

비용

low

7.7

111

173

$0.0093

medium

8.1

129

186

$0.0099

high

7.9

136

199

$0.0106

xhigh

20.0

151

1,764

$0.0888

생각(thinking) 토큰은 총 출력 토큰에 포함되므로 두 열을 합산하지 마세요. 이 실행에서는 low, medium, high 가 지연 시간과 비용에서 비슷하게 유지됐습니다.

xhigh는 시간은 두 배 반, 출력 토큰은 거의 아홉 배, 비용은 여덟 배 증가했습니다. 

핵심 요약: 처음에는 high로 시작하고, 일상적인 단계에서는 medium 로 낮추며, 자체 테스트로 유의미한 개선이 확인될 때만 더 높은 수준을 사용하세요. low effort에서는 모델이 검색 도구를 호출하지 않고 기억에 의존해 답할 수 있습니다. 최신 정보가 필요한 턴이면 그렇게 지시하거나 수준을 올리세요.

시스템 프롬프트로 에이전트 범위 제한

시스템 프롬프트는 에이전트의 행동을 정의합니다:

SYSTEM_PROMPT = """You are a senior engineer who turns feature requests into implementation plans for an existing codebase.

Stay inside the requested feature. Do not propose unrelated refactors, dependency upgrades, or style changes.

If a file or dependency you need does not exist, say so plainly instead of inventing it.

Write in plain sentences and do not use em dashes.

Finish with concrete guidance: what changes, where, in what order, what could break, and which tests to add."""

Anthropic의 프롬프트 작성 가이드는 모델이 작업 범위를 확장하거나 너무 일찍 멈출 수 있다고 언급합니다. 프롬프트는 범위 내에 머무르고 구체적인 지침으로 마무리하도록 지시합니다. 출력 형식은 뒤에서 스키마로 처리합니다.

Pydantic으로 구조화된 계획 반환

Pydantic으로 계획을 정의해 애플리케이션이 검증하고 다른 코드로 전달할 수 있게 합니다:

from pydantic import BaseModel, Field

class FeaturePlan(BaseModel):
    summary: str = Field(description="One or two sentences on what will be built.")
    implementation_steps: list[str]
    files_to_modify: list[str]
    risks: list[str]
    tests: list[str]

response = client.messages.parse(
    model=MODEL,
    max_tokens=8192,
    system=SYSTEM_PROMPT,
    messages=[{"role": "user", "content": feature_request}],
    output_format=FeaturePlan,
)

if response.stop_reason == "refusal":
    category = (
        response.stop_details.category
        if response.stop_details and response.stop_details.category
        else "unspecified"
    )
    print(f"Declined: {category}")
elif response.parsed_output is None:
    print(f"No plan. Stop reason: {response.stop_reason}")
else:
    print(response.parsed_output.summary)

messages.parse()는 Pydantic 모델을 JSON 스키마로 변환해 전송하고, 응답을 검증한 뒤, parsed_output에 타입이 지정된 객체로 반환합니다. 구조화 출력은 일반 제공이므로 베타 헤더가 필요 없습니다. 먼저 stop_reason을 확인하세요. 뒤에서 다루는 거부의 경우 스키마를 건너뛰므로 파싱할 것이 남지 않습니다.

서두의 일반적인 결과가 한 가지는 제대로 했습니다. 보지 못한 파일을 이름으로 언급하지 않았다는 점입니다. 스키마는 구조를 검증할 뿐, 사실과의 정합성을 보장하지는 않습니다.

Claude Fable 5.1 vs. Fable 5: API 마이그레이션 변경점

도구를 추가하기 전에 강제 도구 선택 제한, thinking 블록 호환성, append-only 이력을 고려하세요.

  • Fable 5.1은 강제 도구 선택을 거부합니다. 아래 도구 루프 섹션에서 오류와 대신 사용하는 auto 구성을 보여 줍니다.

  • thinking 블록은 한 방향으로만 호환됩니다. Fable 5.1은 이전 Claude 모델의 블록을 읽을 수 있지만, 이전 모델은 Fable 5.1의 블록을 읽을 수 없습니다. 

라우터나 fallback이 대화를 더 오래된 모델로 이동시키면, API는 대상 모델이 보기 전에 호환되지 않는 블록을 제거합니다. 남은 이력은 유지되지만, 더 오래된 모델은 해당 블록 없이 계획을 세워야 합니다.

이전 턴을 편집하면 이후에 온 thinking 블록이 무효화됩니다. 이는 이력 트리밍과 클라이언트 측 요약을 깨뜨릴 수 있습니다.

마이그레이션 가이드에서 전체 변경 사항을 확인하세요.

읽기 전용 리포지토리 도구 추가

이제 읽기 전용 도구로 모델에 리포지토리 컨텍스트를 제공합니다.

읽기 전용 도구 정의

도구 레이어는 두 부분으로 구성됩니다. 접근 규칙을 강제하는 Python 함수와 Claude가 호출할 수 있는 스키마입니다.

프로젝트 루트로 경로 제한

읽기 전용이 곧 안전함을 의미하지는 않습니다. 모델은 ../../.env 만큼 쉽게 config.py를 요청할 수 있으므로, 가드는 프롬프트가 아니라 코드에 있어야 합니다:

def _resolve(self, relative_path: str) -> Path:
    relative = Path(relative_path)
    if relative.is_absolute() or relative.drive:
        raise ToolError(f"path is outside the project root: {relative_path}")

    cursor = self.root
    for part in relative.parts:
        cursor /= part
        if cursor.is_symlink():
            raise ToolError(f"symlinks are not followed: {relative_path}")

    candidate = (self.root / relative).resolve()

    # After resolving "..", the path still has to sit under the allowed root.
    if candidate != self.root and self.root not in candidate.parents:
        raise ToolError(f"path is outside the project root: {relative_path}")
    if candidate.name in DENY_NAMES:
        raise ToolError(f"reading {candidate.name} is not allowed")

    return candidate

절대 경로와 symlink 구성 요소를 거부한 뒤 경로를 resolve하고, 여전히 프로젝트 루트 아래에 있는지 확인합니다. ../.env를 요청하면 "path is outside the project root."를 반환합니다. 반환된 도구 오류 덕분에 에이전트는 허용된 파일로 계속 진행할 수 있습니다.

엄격한 도구 스키마 정의

리더 클래스는 Python이 열 수 있는 대상을 제어합니다. Claude에는 또한 요청 가능한 세 동작을 설명하는 JSON 스키마가 필요합니다:

EMPTY_SCHEMA = {
    "type": "object",
    "properties": {},
    "additionalProperties": False,
}

TOOLS = [
    {
        "name": "list_project_files",
        "description": "List readable text files in the project.",
        "input_schema": EMPTY_SCHEMA,
        "strict": True,
    },
    {
        "name": "read_project_file",
        "description": "Read one text file relative to the project root.",
        "input_schema": {
            "type": "object",
            "properties": {"path": {"type": "string"}},
            "required": ["path"],
            "additionalProperties": False,
        },
        "strict": True,
    },
    {
        "name": "get_project_metadata",
        "description": "Read project metadata and dependency manifests.",
        "input_schema": EMPTY_SCHEMA,
        "strict": True,
    },
]

strict 는 모델이 도구를 선택할 때 인자를 검사합니다. Fable 5.1에서는 중요한데, 도구 호출을 강제하지는 않습니다.

멀티 턴 도구 루프 실행

기본 루프부터 시작하세요. 도구를 보내고, stop_reason을 확인하고, 요청된 것을 실행해 결과를 추가한 뒤 반복합니다.

MAX_AGENT_TURNS = 8
reader = ProjectReader("sample_project")
messages = [{"role": "user", "content": feature_request}]

for turn in range(1, MAX_AGENT_TURNS + 1):
    response = client.messages.create(
        model=MODEL,
        max_tokens=16000,
        system=SYSTEM_PROMPT,
        tools=TOOLS,
        messages=messages,
    )

    if response.stop_reason == "refusal":
        return declined(response.stop_details.category)
    if response.stop_reason == "max_tokens":
        return cutoff()
    if response.stop_reason != "tool_use":
        messages.append({"role": "assistant", "content": response.content})
        break

    messages.append({"role": "assistant", "content": response.content})
    results = []
    for block in response.content:
        if block.type != "tool_use":
            continue
        output, is_error = reader.run(block.name, block.input)
        results.append({
            "type": "tool_result",
            "tool_use_id": block.id,
            "content": output,
            "is_error": is_error,
        })

    messages.append({"role": "user", "content": results})
else:
    return turn_limit()

MAX_AGENT_TURNS 는 모델 요청 횟수를 제한할 뿐 지출을 제한하지 않으므로 필요하다면 별도의 비용 한도를 강제하세요. 루프는 refusal, max_tokens, tool_use 를 직접 처리합니다. 다른 중지 사유는 검사 단계를 종료합니다. is_error 필드는 경로가 거부되었음을 모델에 알려 다음 동작을 선택할 수 있게 합니다.

강제 도구 선택이 400을 반환하는 이유

Fable 5에서는 tool_choice: {"type": "any"}로 첫 호출을 강제할 수 있었습니다. Fable 5.1은 요청 실행 전에 다음 오류를 반환합니다:

tool_choice: type "tool" and "any" are not supported for this model.

강제 호출은 항상 켜져 있는 thinking을 건너뛰게 됩니다. tool_choiceauto로 두고, 위에서 정의한 엄격한 스키마를 사용하며, 특정 단계에 도구가 필요할 때는 프롬프트에서 도구를 명시하세요.

Fable 5.1은 한 턴에 도구 호출을 하나씩 내는 경우가 있고, Fable 5는 여러 개를 배치했습니다. 이는 라운드트립을 늘립니다. 프롬프트에 다음 문장을 추가하세요: “독립적인 파일은 턴당 하나씩이 아니라 같은 턴에 요청하세요.” 샘플 실행에서는 독립 파일 9개 요청을 배치했지만, 개수는 달라질 수 있습니다.

Claude Fable 5.1 응답과 진행 상황 업데이트 스트리밍

텍스트 스트리밍은 생성되는 대로 응답 콘텐츠를 내보내고, 진행 상황 업데이트는 도구 호출 사이의 대기 시간을 다룹니다.

텍스트 응답 스트리밍

전체 프로젝트는 스트림을 시작하기 전에 context_system() 으로 SYSTEM_PROMPT 과 프로젝트 요약을 결합합니다:

with client.messages.stream(
    model=MODEL,
    max_tokens=8192,
    system=context_system(),
    messages=[{"role": "user", "content": feature_request}],
) as stream:
    for chunk in stream.text_stream:
        print(chunk, end="", flush=True)
    final = stream.get_final_message()

print(f"\nOutput tokens: {final.usage.output_tokens}")

get_final_message()는 스트림이 모두 비워지면 사용량과 중지 사유가 포함된 조립된 메시지를 반환합니다. 스트리밍 청크는 완전한 JSON을 보장하지 않으므로 파싱은 최종 메시지를 기다리세요.

도구 호출 사이 진행 상황 표시

텍스트 스트리밍은 도구 호출 중 지연을 다루지 않습니다. Fable 5.1은 도구 호출 전에 짧은 진행 상황 업데이트를 작성할 수 있습니다. 기본 thinking.display 값인 "omitted"에서는 진행 전용 thinking 블록이 비어 있지만, 모델이 일반 텍스트 리드인을 생성할 수는 있습니다.

display: "updates"thinking-display-updates-2026-08-18 베타 헤더를 사용하면, API 문서는 읽을 수 있는 진행 업데이트를 추론은 숨긴 채 비어 있지 않은 thinking 블록으로 정의합니다. 이 프로젝트의 실시간 실행에서는 thinking 필드가 비어 있었고, 읽을 수 있는 상태는 바로 앞의 tool_use 직전에 일반 text 블록으로 도착했습니다. 따라서 헬퍼는 두 블록 유형을 모두 확인하며, 루프는 tool_use로 끝나는 턴에서만 호출합니다:

PROGRESS_BETA = "thinking-display-updates-2026-08-18"

response = client.beta.messages.create(
    model=MODEL,
    max_tokens=16000,
    betas=[PROGRESS_BETA],
    thinking={"type": "adaptive", "display": "updates"},
    system=SYSTEM_PROMPT,
    tools=TOOLS,
    messages=messages,
)

def status_lines(response) -> list[str]:
    lines = []
    for block in response.content:
        if block.type == "thinking":
            text = (block.thinking or "").strip()
        elif block.type == "text":
            text = (block.text or "").strip()
        else:
            continue
        if text:
            lines.append(text)
    return lines

진행 메시지는 모델이 읽을 계획인 파일을 설명합니다. 예: "앱 배선, 구성, 확장, public과 auth 라우트, 기존 테스트를 읽겠습니다. rate limiting이 연결될 지점이기 때문입니다." 이 메시지들을 표시하되 비어 있는 블록은 무시하세요.

턴별 토큰 사용량, 진행 메시지, 배치된 파일 읽기가 표시된 Claude Fable 5.1 에이전트 루프를 보여 주는 터미널

에이전트는 진행 상황을 보고하면서 파일을 읽습니다. 이미지: 작성자

Fable 5.1은 특히 높은 effort에서 Fable 5보다 이런 메시지를 더 적게 작성합니다. 인터페이스에 정기 업데이트가 필요하다면, 시작 문장, 진행 메시지, 마무리 요약을 요청하세요.

대화 중간에 Claude Fable 5.1의 effort 변경

다음 기능은 꽤 인상적입니다. 아시다시피, 리포지토리 에이전트는 매 턴 같은 깊이의 추론이 필요하지 않습니다.

턴 사이에서 effort 변경

에이전트 루프에서는 일상적인 검색 턴에는 effort를 낮추고, 최종 계획 턴에서는 다시 높이세요.

mid-conversation-output-config-2026-07-01 베타 헤더를 사용하면 effort 수준만 변경하는 시스템 메시지를 추가할 수 있습니다:

EFFORT_BETA = "mid-conversation-output-config-2026-07-01"

messages.append({"role": "system", "content": [], "output_config": {"effort": "low"}})
messages.append({"role": "user", "content": "Summarize the repository evidence in five words."})

response = client.beta.messages.create(
    model=MODEL,
    max_tokens=4096,
    betas=[EFFORT_BETA],
    output_config={"effort": "high"},
    messages=messages,
)

새 수준은 현재 턴 도중이 아니라, 다음 사용자 턴부터 적용되며 프롬프트 캐시를 무효화하지 않습니다. 요청 간에 최상위 output_config.effort를 바꾸면 캐시가 무효화됩니다. 

에이전트는 최상위 설정을 high로 유지하고, 일상적인 검색 전에는 메시지별로 medium 지시를 추가하며, 최종 계획 전에는 high 지시를 추가합니다. 쌍으로 테스트했을 때 낮은 effort에서 출력 토큰이 76에서 18로 줄었습니다. 이 결과는 기대치가 아닌 한 가지 예로만 보세요.

한 턴에만 적용되는 시스템 지시문 사용

최종 계획 단계에서 추가 파일 읽기를 막기 위해 턴 범위 지시문을 사용합니다.

mid-conversation-system-clear-at-2026-08-21 베타 헤더와 함께 시스템 메시지에 clear_at: "next_user_message"를 설정하세요. API는 해당 텍스트를 현재 턴의 시스템 지시문으로 처리하고, 다음 사용자 메시지 이후에는 렌더링을 중단합니다. 메시지에는 남아 있으므로 이전 이력이 바뀌지 않고, 캐시는 계속 일치하며, 지워진 메시지는 입력 토큰을 차지하지 않습니다.

SCOPED_SYSTEM_BETA = "mid-conversation-system-clear-at-2026-08-21"

messages.append({"role": "system", "content": [], "output_config": {"effort": "high"}})
messages.append({"role": "user", "content": "Write the implementation plan now."})
messages.append({
    "role": "system",
    "content": (
        "For this turn only: do not request more files. Base the plan on what "
        "you have already read, and name only paths you actually opened."
    ),
    "clear_at": "next_user_message",
})

response = client.beta.messages.create(
    model=MODEL,
    max_tokens=16000,
    betas=[EFFORT_BETA, SCOPED_SYSTEM_BETA],
    tool_choice={"type": "none"},
    output_config={"format": {"type": "json_schema", "schema": plan_schema()}},
    system=agent_system(),
    tools=TOOLS,
    messages=messages,
)

tool_choice={"type": "none"}는 최종 요청이 다른 도구를 호출하지 않도록 합니다. 범위 지정된 지시는 에이전트가 이미 검사한 파일에만 계획을 제한합니다. 알림을 추가했다가 다음 요청에서 삭제하지 마세요. 그 편집은 이후 thinking 블록을 무효화합니다.

Claude Fable 5.1 thinking 블록 400 오류 해결

The block is bound to a different conversation 오류는 thinking 블록 이전의 이력이 변경되었음을 의미합니다. Fable 5.1의 모든 thinking 블록은 그 전에 온 정확한 시스템 프롬프트, 도구 정의, 메시지에 바인딩됩니다.

결과는 계정 생성 시점에 따라 달라집니다. 

  • 2026년 8월 31일 이후 생성된 계정은 블록이 다른 대화에 바인딩되었다는 400을 받습니다. 

  • 그 이전에 생성된 계정은 불일치를 기록만 하고, 요청이 thinking.block_binding.prefix_mismatch_behavior를 설정한 경우에만 작동합니다. 

이는 thinking-binding-controls-2026-08-01 베타 헤더, thinking.block_binding.prefix_mismatch_behavior"drop_block"로 설정, 그리고 input_transformations 배열로 감지할 수 있습니다. 편집된 이력은 reason: "prefix_binding_mismatch"로 나타납니다. 통합에 대해 이 확인을 한 번 실행하세요.

다음 작업이 불일치를 유발합니다:

  • 이후 턴을 유지한 채 이전 턴을 편집, 재정렬, 제거

  • 요청마다 텍스트를 이전 턴에 주입하고 다음 요청에서 제거

  • 대화 도중 최상위 system 프롬프트나 tools 배열의 내용 또는 순서 변경

  • 이후 요청에서 이미지나 문서 URL이 다른 바이트를 제공

각 항목에는 바인딩을 유지하는 대체 방법이 있습니다:

  • system을 편집하는 대신 대화 중 시스템 메시지로 지시를 추가

  • 최상위 배열을 바꾸는 대신 대화 중 도구 변경 사용

  • 편집으로 간주되지 않는 서버 측 컨텍스트 편집 또는 압축으로 이력 트리밍 

  •  thinking 블록은 변경 없이 되돌려 보내기

cache_control 마커 이동과 요청 수준 effort 변경은 모두 안전하며 thinking 블록 바인딩을 무효화하지 않습니다. 다만 최상위 effort 변경은 프롬프트 캐싱을 다시 시작하므로, 캐시된 접두부를 유지해야 할 때는 메시지별 effort를 사용하세요.

프롬프트 캐싱과 Claude Fable 5.1 API 비용

다음 실행은 신규 입력, 캐시 쓰기, 캐시 읽기, 출력 비용을 분리해 보여 줍니다.

자동 프롬프트 캐싱 추가

프롬프트 캐싱은 턴 간 반복되는 컨텍스트의 비용을 줄입니다. 이력이 늘어나면 분기점이 앉을 위치가 바뀌므로, 여기서는 자동 캐싱이 더 단순하게 맞습니다.

최상위 cache_control 필드는 각 요청에서 분기점을 최신 캐시 가능 블록으로 이동시킵니다:

response = client.beta.messages.create(
    model=MODEL,
    cache_control={"type": "ephemeral"},
    system=system,
    tools=TOOLS,
    messages=messages,
    # Other request fields...
)

Fable 5.1에서는 512 토큰 미만의 캐시 가능 접두부는 cache_control로 표시해도 캐시되지 않습니다. API는 이를 일반적으로 처리하고 두 캐시 카운터 모두 0을 반환합니다. 583 토큰 접두부를 쓰는 데 $0.0073이 들었고, 다음 턴에 이를 읽는 데 $0.00015가 들었습니다. 두 번째 턴은 새 부분을 캐시에 써야 했기 때문에, 캐시 적중이 모든 입력 비용을 없애지는 않았습니다.

캐시 인지 비용 추정

response.usage는 신규 입력, 캐시 생성, 캐시 읽기, 출력을 별도로 보고합니다. 네 카운터 각각에 가격을 적용하세요. 입력과 출력만 합산하면 캐시 쓰기 비용을 숨기고 캐시 적중의 가격을 과대평가하게 됩니다.

다음은 세 턴에 걸쳐 12개 파일을 읽고 최종 계획을 생성한 전체 실행의 비용 분해입니다:

항목

토큰

추정 비용

비중

출력

5,713

$0.2857

59.4%

캐시 쓰기

15,426

$0.1928

40.1%

신규 입력

50

$0.0005

0.1%

캐시 읽기

6,549

$0.0016

0.3%

합계

27,738

$0.4806

100%

캐시 읽기는 이 추정치의 0.5%도 안 되는 극히 일부였습니다. Fable 5의 예전 요금이었다면 실행 비용은 $0.4806 대신 약 $0.4855였을 것입니다. 각 턴이 더 많은 컨텍스트를 재사용할수록 절감 효과가 커집니다.

이 실행에서는 출력이 약 60%를, 캐시 쓰기가 약 40%를 차지했습니다. 여기서 사용한 5분 요금에서, 캐시 쓰기 토큰은 캐시 읽기 토큰보다 50배 비쌉니다. 1시간 캐시 쓰기는 80배 더 비쌉니다.

Claude Fable 5.1 거부와 폴백 처리

거부와 실패한 요청은 애플리케이션에서 서로 다른 동작이 필요합니다.

출력 파싱 전에 거부 감지

출력 이전의 거부는 HTTP 200과 함께 stop_reason: "refusal", 빈 콘텐츠, stop_details로 도착합니다. category는 null일 수 있습니다. 스트림 도중의 거부는 부분 출력 뒤에 올 수 있으며, 애플리케이션은 이를 폐기해야 합니다. try/except로는 어느 쪽도 잡을 수 없습니다.

response = client.messages.create(model=MODEL, max_tokens=8192, messages=messages)

if response.stop_reason == "refusal":
    category = (
        response.stop_details.category
        if response.stop_details and response.stop_details.category
        else "unspecified"
    )
    return f"This request was declined ({category})."

이것을 애플리케이션 상태로 처리하세요. 허용 가능한 요청이 불명확하다면 더 구체적으로 다시 작성하세요. 분류기를 우회하는 것을 목적으로 하는 재시도 로직을 만들지 마세요.

거부는 HTTP 200으로 도착합니다. 이미지: 작성자

서버 측 폴백 구성

서버 측 폴백은 server-side-fallback-2026-07-01 베타 헤더와 함께 fallbacks: "default" 를 사용해 거부된 요청을 다른 모델에서 재시도할 수 있습니다. Fable 5.1의 허용 대상은 Opus 4.8Opus 5입니다. 

기본 폴백은 거부 category에 권장 대상이 있을 때만 실행됩니다. 테스트한 reasoning_extraction 거부는 폴백을 트리거하지 않았습니다. 모든 거부가 재시도된다고 가정하지 말고 usage.iterations를 확인하세요. 앞서 언급했듯, 더 오래된 모델로 이동하면 Fable 5.1 thinking 블록도 떨어집니다.

FastAPI로 Claude Fable 5.1 에이전트 제공

이제 로컬 에이전트가 동일한 워크플로를 HTTP API로 제공할 수 있습니다.

계획 엔드포인트 생성

로컬 스크립트만 필요하면 이 섹션을 건너뛰세요. 웹 서비스의 경우 FastAPI AsyncAnthropic를 사용하세요. lifespan 핸들러에서 프로세스용 클라이언트를 하나 생성하세요. 기존 에이전트 모듈에서 스키마와 프롬프트를 가져옵니다.

@asynccontextmanager
async def lifespan(_: FastAPI):
    global client
    client = AsyncAnthropic()
    try:
        yield
    finally:
        await client.close()


@app.post("/plan", response_model=PlanResponse)
async def create_plan(body: PlanRequest):
    reader = resolve_project(body.project)
    messages, totals, turns, tool_calls = await inspect(reader, body.feature_request)
    plan, final_usage = await write_plan(messages)
    totals.add(final_usage)
    return PlanResponse(plan=plan, turns=turns, tool_calls=tool_calls, usage=as_usage(totals))

호출자는 경로가 아닌 프로젝트 이름을 보낸다는 점에 유의하세요. resolve_project()는 이를 소수의 허용된 루트 중 하나로 매핑하므로, 요청이 서버에 임의의 위치 읽기를 요구할 수 없습니다. 이 서비스는 애플리케이션 선택으로 거부를 422로 매핑합니다. Claude API 자체는 이를 HTTP 200으로 반환합니다.

uvicorn app:app --reload로 실행하세요. 대화형 문서는 http://localhost:8000/docs에서 확인할 수 있습니다.

엔드포인트는 추정 비용과 함께 계획을 반환합니다. 영상: 작성자

/plan/stream 엔드포인트는 백그라운드 태스크에서 검사를 실행하고, 진행 및 도구 이벤트를 asyncio.Queue에 넣은 뒤 StreamingResponse로 내보냅니다. 스트림이 닫히면 제너레이터가 백그라운드 태스크를 취소합니다. 리포지토리의 Streamlit 인터페이스는 동일한 이벤트 스트림을 렌더링합니다.

Streamlit은 에이전트의 실시간 진행을 보여줍니다. 영상: 작성자

Claude Fable 5.1 에이전트 배포 체크리스트

앞서 구축한 제한과 점검은 서비스의 일부로 유지됩니다. 배포 전에는 로컬 실행에서는 보이지 않는 운영 요소를 추가하세요.

  • SDK의 429 및 5xx 응답에 대한 기본 두 번 재시도를 검토하고, max_retries와 타임아웃을 서비스의 지연 예산에 맞게 설정

  • 요청 타임아웃을 설정하고, 기존 SSE 태스크 취소가 클라이언트 연결 해제 시 미해결 작업을 중단하는지 확인

  • 각 실행에서 모델 ID, SDK 버전, 요청 ID, 중지 사유, 네 가지 토큰 카테고리를 로깅

  • 캐시 쓰기 증가, 출력 토큰 증가, 거부, 턴 상한 도달 실행에 대한 경보 설정

  • 계정의 보존 설정이 모델 요구 사항과 일치하는지 확인

  • SDK를 고정하고 각 릴리스 전에 베타 헤더를 재검토

Opus 5나 Sonnet 5 대신 Claude Fable 5.1을 쓸 때

  • Anthropic은 합리적인 기본값으로 Opus 5를 권장합니다.
  • 긴 리포지토리 분석, 까다로운 디버깅, 큰 컨텍스트가 필요한 에이전트형 작업에서 Opus 5가 부족하다면 Fable 5.1을 시험하세요.
  • 리포지토리 작업과 일상 과제에는 Sonnet 5와 Opus 5를 품질, 지연, 비용 측면에서 비교하세요.
  • 분류, 추출, 짧은 답변, 간단한 요청에는 Sonnet 5가 좋은 기본값이며, 가장 쉬운 작업에는 Haiku 4.5도 충분할 수 있습니다.

단지 최신이라는 이유로 Fable 5.1을 선택하지 마세요. 단일 요청에서도 effort와 구조화 출력을 사용할 수 있고, 스트리밍도 작동합니다. 여기서 사용한 루프나 반복 접두부 캐싱의 이점은 없습니다.

마무리

첫 호출의 일반적인 계획은 에이전트가 리포지토리를 읽은 뒤에야 유용해졌습니다. 완료된 실행에서는 세 턴에 걸쳐 12개 파일을 검사했으며, 추정 비용의 99.5%가 출력과 캐시 쓰기에서 나왔습니다. 저는 경로 경계와 append-only 이력을 유지하고, 낮은 effort가 모델이 리포지토리 도구를 건너뛰지 않으면서 비용을 줄이는지 테스트하겠습니다.

응답 하나로 과제를 해결할 수 있다면 구조화 출력까지만 사용하세요. 답이 리포지토리 파일에 의존하거나 호출 사이의 진행 보고가 필요할 때 도구 루프를 사용하세요.

모델 선택에 대한 자세한 내용은 Introduction to Claude Models 과정을 추천합니다. 프롬프트 작성과 에이전트 워크플로는 Software Development with Cursor 과정을 참고하세요.

FAQs

Claude Fable 5.1은 코드뿐 아니라 이미지를 읽을 수 있나요?

예. 이미지 입력을 받아 차트와 PDF를 읽을 수 있습니다. 리포지토리 계획에는 필요하지 않아 본문 예시에서는 비전을 제외했습니다. 이 에이전트를 UI 변경 계획까지 확장한다면, 기능 요청과 함께 현재 스크린샷을 보낼 것입니다. 작은 시각적 디테일이 작업에 영향을 주지 않는다면 먼저 해상도를 낮추세요.

Fable 5로부터 전환한 뒤 에이전트가 더 느려진 이유는 무엇인가요?

모델을 탓하기 전에 도구 결과를 확인하세요. 앞서의 배치 지시가 이미 있다면, 요청 수와 크기를 모두 비교하세요. 현재 리더는 파일당 40,000바이트로 제한합니다. 여전히 크다면, 도구가 관련 구간만 반환하도록 행 범위나 검색 인자를 추가하세요.

왜 Claude Fable 5.1이 400 invalid_request_error를 반환하나요?

우선 재시도하지 마세요. invalid_request_error는 보통 요청 형태나 계정 설정을 바꿔야 함을 의미합니다. 이 프로젝트에서는 강제 tool_choice, 호환되지 않는 보존 설정, thinking을 유지한 채 접두부 편집, 일치하는 헤더 없이 베타 필드를 보낸 경우가 가능성이 큽니다. 원인을 해결한 뒤 요청을 다시 보내세요.

소스 파일을 캐시할까요, 요약을 캐시할까요?

저는 이렇게 합니다. 여러 턴에 걸쳐 정확한 코드가 중요할 때는 소스 파일을 캐시합니다. 이후 단계가 아키텍처나 파일 맵만 필요하다면 요약을 캐시합니다. 요약은 토큰 비용이 적지만, 최종 계획에 필요한 한 줄이 빠질 수 있습니다.

Batch API로 이 에이전트를 실행할 수 있나요?

그 자체로는 아닙니다. Batch API는 개별 Messages 요청을 제출할 뿐, 이 클라이언트 측 도구 루프를 실행하지 않습니다. 실시간 진행이 필요하지 않은 독립형 리포지토리 리뷰에 사용하겠습니다. 전체 루프를 일괄로 실행하려면 한 배치의 도구 요청을 처리한 뒤 다음 배치를 제출하는 자체 코드가 필요합니다.

주제

DataCamp으로 AI를 배워보세요!

tracks

개발자를 위한 AI 엔지니어 보조

26
API와 오픈소스 라이브러리를 사용해 소프트웨어 애플리케이션에 AI를 통합하는 방법을 배우세요. 오늘 바로 AI 엔지니어가 되는 여정을 시작하세요!
자세히 보기Right Arrow
강좌 시작
더 보기Right Arrow