Anthropic 통합

Anthropic 통합

LangChain4j에서 Anthropic의 Claude 모델을 쓰는 방법을 정리할게요. AnthropicChatModel로 대화형(채팅), AnthropicStreamingChatModel로 스트리밍, AnthropicBatchChatModel로 배치 요청을 나눠서 쓸 수 있어요. 각각 어떻게 만들고, 툴·캐싱·thinking 같은 Claude만의 기능을 어떻게 설정하는지 차례로 볼게요.

출처: 공식문서

Maven 의존성

<dependency>
    <groupId>dev.langchain4j</groupId>
    <artifactId>langchain4j-anthropic</artifactId>
    <version>1.20.0</version>
</dependency>

AnthropicChatModel

AnthropicChatModel model = AnthropicChatModel.builder()
    .apiKey(System.getenv("ANTHROPIC_API_KEY"))
    .modelName(CLAUDE_3_5_SONNET_20240620)
    .build();
String answer = model.chat("Say 'Hello World'");
System.out.println(answer);

AnthropicChatModel 커스터마이징

AnthropicChatModel model = AnthropicChatModel.builder()
    .httpClientBuilder(...)
    .baseUrl(...)
    .apiKey(...)
    .version(...)
    .beta(...)
    .modelName(...)
    .temperature(...)
    .topP(...)
    .topK(...)
    .maxTokens(...)
    .stopSequences(...)
    .toolSpecifications(...)
    .toolChoice(...)
    .toolChoiceName(...)
    .disableParallelToolUse(...)
    .serverTools(...)
    .returnServerToolResults(...)
    .toolMetadataKeysToSend(...)
    .cacheSystemMessages(...)
    .cacheTools(...)
    .returnCacheDiagnostics(...)
    .thinkingType(...)
    .thinkingBudgetTokens(...)
    .thinkingDisplay(...)
    .returnThinking(...)
    .sendThinking(...)
    .midConversationSystemMessages(...)
    .timeout(...)
    .maxRetries(...)
    .logRequests(...)
    .logResponses(...)
    .listeners(...)
    // You can also specify default chat request parameters using ChatRequestParameters or AnthropicChatRequestParameters
    .defaultRequestParameters(...)
    .userId(...)
    .customParameters(...)
    .build();

위 파라미터 중 일부의 설명은 여기에서 확인할 수 있어요.

요청별 파라미터 (Per-Request Parameters)

위에서 본 Anthropic 전용 옵션들(cacheSystemMessages, cacheTools, returnCacheDiagnostics, thinkingType, thinkingBudgetTokens, sendThinking, returnThinking, midConversationSystemMessages, toolChoiceName, disableParallelToolUse, userId)과 previousMessageId(요청 전용, Cache Diagnostics 참고)는 AnthropicChatRequestParameters로 요청별로도 설정할 수 있어요. 이러면 모델 빌더에 설정된 값을 호출마다 덮어쓸 수 있는데, 예를 들어 오래 실행되는 에이전트 루프에선 프롬프트 캐싱을 켜고 값싼 일회성 완료에선 끄는 식으로, 두 번째 모델을 만들지 않고도 옵션을 바꿔가며 쓸 수 있어요.

AnthropicChatModel model = AnthropicChatModel.builder()
    .apiKey(System.getenv("ANTHROPIC_API_KEY"))
    .modelName(CLAUDE_3_5_SONNET_20240620)
    .build();

AnthropicChatRequestParameters parameters = AnthropicChatRequestParameters.builder()
    .cacheSystemMessages(true)
    .cacheTools(true)
    .build();

ChatRequest chatRequest = ChatRequest.builder()
    .messages(systemMessage, userMessage)
    .parameters(parameters)
    .build();

ChatResponse chatResponse = model.chat(chatRequest);

요청에 설정하지 않은 파라미터는 모델 빌더에 설정된 값으로 폴백해요.

AnthropicStreamingChatModel

AnthropicStreamingChatModel model = AnthropicStreamingChatModel.builder()
    .apiKey(System.getenv("ANTHROPIC_API_KEY"))
    .modelName(CLAUDE_3_5_SONNET_20240620)
    .build();

model.chat("Say 'Hello World'", new StreamingChatResponseHandler() {

    @Override
    public void onPartialResponse(String partialResponse) {
        // this method is called when a new partial response is available. It can consist of one or more tokens.
    }

    @Override
    public void onCompleteResponse(ChatResponse completeResponse) {
        // this method is called when the model has completed responding
    }

    @Override
    public void onError(Throwable error) {
        // this method is called when an error occurs
    }
});

AnthropicStreamingChatModel 커스터마이징

AnthropicChatModel과 동일해요. 위에서 본 내용을 그대로 쓰면 돼요.

Batch API

Message Batches API는 여러 채팅 요청을 표준 토큰당 가격의 **50%**로 비동기 처리해요. AnthropicBatchChatModel은 핵심 BatchChatModel 인터페이스(submit, retrieve, cancel, list)를 구현해요. 각 요청은 AnthropicChatModel 호출이 쓰는 것과 같은 파라미터로 제출돼요.

AnthropicBatchChatModel model = AnthropicBatchChatModel.builder()
    .apiKey(System.getenv("ANTHROPIC_API_KEY"))
    .modelName("claude-sonnet-4-5")
    .maxTokens(1024)
    .build();

// Submit a batch of requests
BatchResponse<ChatResponse> submitted = model.submit(new BatchRequest<>(List.of(
    ChatRequest.builder().messages(UserMessage.from("What is the capital of France?")).build(),
    ChatRequest.builder().messages(UserMessage.from("What is the capital of Germany?")).build())));

String batchId = submitted.batchId();

// Poll until the batch reaches a terminal state (typically well under an hour)
BatchResponse<ChatResponse> batch = model.retrieve(batchId);
while (!batch.state().isTerminal()) {
    TimeUnit.SECONDS.sleep(30); // throws InterruptedException
    batch = model.retrieve(batchId);
}

// Read the per-request results, in submission order
for (BatchItemResult<ChatResponse> result : batch.results()) {
    if (result.isSuccess()) {
        System.out.println(result.response().aiMessage().text());
    } else {
        System.out.println("Failed: " + result.error().message());
    }
}

model.list(...)로 최근 배치를 페이지네이션하고, model.cancel(batchId)로 처리 중인 배치를 취소할 수 있어요. 취소한 배치도 Anthropic 쪽에서는 ended 상태로 끝나고 BatchState.CANCELLED로 보고되는데, 취소가 적용되기 전에 완료된 요청의 결과를 여전히 담고 있을 수 있어요.

thinking이나 프롬프트 캐싱 같은 Anthropic 전용 옵션은 AnthropicChatModel과 똑같이 defaultRequestParameters(...)로 설정하고 요청별로 덮어쓸 수 있어요.

AnthropicBatchChatModel model = AnthropicBatchChatModel.builder()
    .apiKey(System.getenv("ANTHROPIC_API_KEY"))
    .modelName("claude-sonnet-4-5")
    .maxTokens(4096)
    .defaultRequestParameters(AnthropicChatRequestParameters.builder()
        .thinkingType("enabled")
        .thinkingBudgetTokens(2000)
        .cacheSystemMessages(true)
        .build())
    .returnThinking(true) // store the returned thinking in AiMessage.thinking()
    .build();

Tools (툴)

Anthropic은 tools을 스트리밍·비스트리밍 양쪽 모두에서 지원해요. Anthropic 툴 문서는 여기를 참고하세요.

Tool Choice

Anthropic의 tool choice 기능은 스트리밍·비스트리밍 상호작용 모두에서 사용 가능해요.

  • toolChoice(ToolChoice.REQUIRED)는 모델이 텍스트로 답하는 대신 사용 가능한 툴 중 하나를 반드시 호출하도록 강제해요.
  • toolChoiceName("get_weather")는 모델이 특정 툴 하나를 호출하도록 강제해요. 단독으로 쓸 수 있고, toolChoice(ToolChoice)와 함께 설정되면 이름이 지정된 툴이 우선해요.

병렬 툴 사용 (Parallel Tool Use)

기본적으로 Anthropic Claude는 사용자 질의에 답하기 위해 여러 툴을 쓸 수 있는데, disableParallelToolUse(true)병렬 툴을 비활성화할 수 있어요.

서버 툴 (Server Tools)

Anthropic의 server toolsserverTools 파라미터로 지원돼요. 웹 검색 툴을 쓰는 예시예요.

AnthropicServerTool webSearchTool = AnthropicServerTool.builder()
        .type("web_search_20250305")
        .name("web_search")
        .addAttribute("max_uses", 5)
        .addAttribute("allowed_domains", List.of("accuweather.com"))
        .build();

ChatModel model = AnthropicChatModel.builder()
        .apiKey(System.getenv("ANTHROPIC_API_KEY"))
        .modelName("claude-sonnet-4-5")
        .serverTools(webSearchTool)
        .logRequests(true)
        .logResponses(true)
        .build();

String answer = model.chat("What is the weather in Munich?");

serverTools로 지정한 툴은 Anthropic API에 보내는 모든 요청에 포함돼요.

서버 툴 결과 가져오기

서버 툴의 raw 결과(예: 웹 검색 결과, 코드 실행 출력, 생성된 파일의 fileIds)에 접근하려면 returnServerToolResults(true)를 켜면 돼요. 결과는 AiMessage.attributes()"server_tool_results" 키로 사용할 수 있어요.

ChatModel model = AnthropicChatModel.builder()
        .apiKey(System.getenv("ANTHROPIC_API_KEY"))
        .modelName("claude-sonnet-4-5")
        .serverTools(webSearchTool)
        .returnServerToolResults(true)
        .build();

ChatResponse response = model.chat("What is the weather in Munich?");
AiMessage aiMessage = response.aiMessage();

List<AnthropicServerToolResult> results = aiMessage.attribute("server_tool_results", List.class);
for (AnthropicServerToolResult result : results) {
    System.out.println("Type: " + result.type());
    System.out.println("Tool Use ID: " + result.toolUseId());
    System.out.println("Content: " + result.content());
}

큰 데이터를 ChatMemory에 저장하지 않도록 기본값은 비활성이에요.

Skills

Anthropic의 Agent Skills은 클로드가 코드 실행 컨테이너 안에서 미리 빌드된 스킬을 실행해 실제 다운로드 가능한 문서(.xlsx, .pptx, .docx, .pdf)를 만들게 해줘요. 타입이 있는 skills 파라미터로 켤 수 있어요.

AnthropicChatModel model = AnthropicChatModel.builder()
        .apiKey(System.getenv("ANTHROPIC_API_KEY"))
        .modelName("claude-opus-4-8")
        .maxTokens(4096)
        .beta("code-execution-2025-08-25,skills-2025-10-02,files-api-2025-04-14")
        .skills(AnthropicSkill.XLSX, AnthropicSkill.PPTX)
        .returnServerToolResults(true)
        .build();

ChatResponse response = model.chat("Create an Excel spreadsheet with the numbers 1 to 5 in column A");

스킬을 켜면 자동으로:

  • 요청에 container.skills 블록을 추가하고,
  • 필요한 code_execution 서버 툴을 추가해요(이미 serverTools(...)로 설정돼 있지 않다면).

위처럼 필요한 beta 기능은 beta(...)로 직접 옵트인해야 해요. 이들은 beta 헤더라 값이 시간에 따라 바뀌므로 자동 주입되지 않아요. 현재 값은 Agent Skills 문서를 확인하세요.

returnServerToolResults(true)와 함께 쓰면 생성된 파일 id가 AiMessage.attributes()"server_tool_results" 키로 나타나요(위 Retrieving Server Tool Results 참고). 파일은 Anthropic의 Files API를 통해 24시간 동안 다운로드할 수 있어요.

Skills는 Claude Sonnet 4/4.5, Opus 4 이상에서 지원돼요. 요청당 최대 8개 스킬을 켤 수 있어요. 같은 skills(...) 파라미터는 AnthropicStreamingChatModel에도 있어요.

툴 검색 툴 (Tool Search Tool)

Anthropic의 tool search toolserverTools, 툴 metadata, toolMetadataKeysToSend 파라미터로 지원돼요.

고수준 AI Service와 @Tool API를 쓸 때의 예시예요.

AnthropicServerTool toolSearchTool = AnthropicServerTool.builder()
        .type("tool_search_tool_regex_20251119")
        .name("tool_search_tool_regex")
        .build();

class Tools {

    @Tool(metadata = "{\"defer_loading\": true}")
    String getWeather(String location) {
        return "sunny";
    }

    @Tool
    String getTime(String location) {
        return "12:34:56";
    }
}

ChatModel chatModel = AnthropicChatModel.builder()
        .apiKey(System.getenv("ANTHROPIC_API_KEY"))
        .modelName(CLAUDE_SONNET_4_5_20250929)
        .beta("advanced-tool-use-2025-11-20")
        .serverTools(toolSearchTool)
        .toolMetadataKeysToSend("defer_loading") // need to specify it explicitly
        .logRequests(true)
        .logResponses(true)
        .build();

interface Assistant {

    @SystemMessage("Use tool search if needed")
    String chat(String userMessage);
}

Assistant assistant = AiServices.builder(Assistant.class)
        .chatModel(chatModel)
        .tools(new Tools())
        .build();

assistant.chat("What is the weather in Munich?");

저수준 ChatModelToolSpecification API를 쓸 때의 예시예요.

AnthropicServerTool toolSearchTool = AnthropicServerTool.builder()
        .type("tool_search_tool_regex_20251119")
        .name("tool_search_tool_regex")
        .build();

Map<String, Object> toolMetadata = Map.of("defer_loading", true);

ToolSpecification weatherTool = ToolSpecification.builder()
        .name("get_weather")
        .parameters(JsonObjectSchema.builder()
                .addStringProperty("location")
                .required("location")
                .build())
        .metadata(toolMetadata)
        .build();

ToolSpecification timeTool = ToolSpecification.builder()
        .name("get_time")
        .parameters(JsonObjectSchema.builder()
                .addStringProperty("location")
                .required("location")
                .build())
        .build();

ChatModel model = AnthropicChatModel.builder()
        .apiKey(System.getenv("ANTHROPIC_API_KEY"))
        .modelName(CLAUDE_SONNET_4_5_20250929)
        .beta("advanced-tool-use-2025-11-20")
        .serverTools(toolSearchTool)
        .toolMetadataKeysToSend(toolMetadata.keySet()) // need to specify it explicitly
        .logRequests(true)
        .logResponses(true)
        .build();

ChatRequest chatRequest = ChatRequest.builder()
        .messages(UserMessage.from("What is the weather in Munich? Use tool search if needed."))
        .toolSpecifications(weatherTool, timeTool)
        .build();

ChatResponse chatResponse = model.chat(chatRequest);

프로그래매틱 툴 호출 (Programmatic Tool Calling)

Anthropic의 programmatic tool callingserverTools, 툴 metadata, toolMetadataKeysToSend 파라미터로 지원돼요.

고수준 AI Service와 @Tool API를 쓸 때의 예시예요.

AnthropicServerTool codeExecutionTool = AnthropicServerTool.builder()
        .type("code_execution_20250825")
        .name("code_execution")
        .build();

class Tools {

    static final String TOOL_METADATA = "{\"allowed_callers\": [\"code_execution_20250825\"]}";
    static final String TOOL_DESCRIPTION = """
            Returns daily minimum and maximum temperatures recorded
            for a specified city for a specified number of previous days.
            Response format: [{"min":0.0,"max":10.0},{"min":0.0,"max":20.0},{"min":0.0,"max":30.0}]
            """;

    record TemperatureRange(double min, double max) {}

    @Tool(value = TOOL_DESCRIPTION, metadata = TOOL_METADATA)
    List<TemperatureRange> getDailyTemperatures(String city, int days) {
        if ("Munich".equals(city) && days == 5) {
            return List.of(
                    new TemperatureRange(0.0, 1.0),
                    new TemperatureRange(0.0, 2.0),
                    new TemperatureRange(0.0, 3.0),
                    new TemperatureRange(0.0, 4.0),
                    new TemperatureRange(0.0, 5.0)
            );
        }

        throw new IllegalArgumentException("Unknown city: " + city + " or days: " + days);
    }

    @Tool(value = "Calculates the average of the specified list of numbers", metadata = TOOL_METADATA)
    Double average(List<Double> numbers) {
        return numbers.stream()
                .mapToDouble(Double::doubleValue)
                .average()
                .orElseThrow();
    }
}

ChatModel chatModel = AnthropicChatModel.builder()
        .apiKey(System.getenv("ANTHROPIC_API_KEY"))
        .modelName(CLAUDE_SONNET_4_5_20250929)
        .beta("advanced-tool-use-2025-11-20")
        .serverTools(codeExecutionTool)
        .toolMetadataKeysToSend("allowed_callers") // need to specify it explicitly
        .logRequests(true)
        .logResponses(true)
        .build();

interface Assistant {

    String chat(String userMessage);
}

Assistant assistant = AiServices.builder(Assistant.class)
        .chatModel(chatModel)
        .tools(new Tools())
        .build();

assistant.chat("What was the average max temperature in Munich in the last 5 days?");

저수준 ToolSpecification API에서 툴 metadata를 지정하는 예시는 Tool Search Tool 섹션을 참고하세요.

툴 사용 예시 (Tool Use Examples)

Anthropic의 tool use examples은 툴 metadatatoolMetadataKeysToSend 파라미터로 지원돼요.

고수준 AI Service와 @Tool API를 쓸 때의 예시예요.

enum Unit {
    CELSIUS, FAHRENHEIT
}

class Tools {

    // NOTE: if javac "-parameters" option is not enabled, you need to change "location" to "arg0"
    // and "unit" to "arg1" inside the TOOL_METADATA to make it work.
    public static final String TOOL_METADATA = """
            {
                "input_examples": [
                    {
                        "location": "San Francisco, CA",
                        "unit": "FAHRENHEIT"
                    },
                    {
                        "location": "Tokyo, Japan",
                        "unit": "CELSIUS"
                    },
                    {
                        "location": "New York, NY"
                    }
                ]
            }
            """;

    @Tool(metadata = TOOL_METADATA)
    String getWeather(String location, @P(description = "temperature unit", required = false) Unit unit) {
        return "sunny";
    }
}

ChatModel chatModel = AnthropicChatModel.builder()
        .apiKey(System.getenv("ANTHROPIC_API_KEY"))
        .modelName(CLAUDE_SONNET_4_5_20250929)
        .beta("advanced-tool-use-2025-11-20")
        .toolMetadataKeysToSend("input_examples") // need to specify it explicitly
        .logRequests(true)
        .logResponses(true)
        .build();

interface Assistant {

    String chat(String userMessage);
}

Assistant assistant = AiServices.builder(Assistant.class)
        .chatModel(chatModel)
        .tools(new Tools())
        .build();

assistant.chat("What is the weather in Munich in Fahrenheit?");

저수준 ToolSpecification API에서 툴 metadata를 지정하는 예시는 Tool Search Tool 섹션을 참고하세요.

캐싱 (Caching)

AnthropicChatModelAnthropicStreamingChatModel은 응답에 AnthropicTokenUsage를 돌려주는데, cacheCreationInputTokenscacheReadInputTokens를 담고 있어요. 캐싱에 대한 자세한 내용은 여기를 참고하세요.

시스템 메시지와 툴 캐싱

시스템 메시지와 툴의 캐싱은 기본적으로 비활성이에요. 각각 cacheSystemMessagescacheTools 파라미터로 켤 수 있어요. 켜면 마지막 시스템 메시지와 툴에 각각 cache_control 블록이 추가돼요.

개별 메시지 캐싱

UserMessage, AiMessage, ToolExecutionResultMessage 각각을 cache_control 속성을 ephemeral로 설정해 캐싱 대상으로 표시할 수 있어요. 캐시 컨트롤 마커는 메시지의 마지막 content 블록에 자동 적용돼요(ToolExecutionResultMessagetool_result 블록 자체).

UserMessage는 변경 가능한 attributes 맵을 노출해요.

UserMessage userMessage = UserMessage.from("Hello cached world");
userMessage.attributes().put("cache_control", "ephemeral");

AiMessageToolExecutionResultMessage는 변경 불가능한 attributes 맵을 가지므로 toBuilder()로 설정해요. 이건 에이전틱 툴 실행 루프에서 특히 유용한데, 턴마다 대화 히스토리가 커지거든요. 턴의 마지막 메시지를 ephemeral로 표시하면, 이후 더 큰 요청들이 캐시된 프리픽스를 재사용해서 커져가는 전체 히스토리를 정가로 재청구하지 않아요.

AiMessage aiMessage = someAiMessage.toBuilder()
        .attributes(Map.of("cache_control", "ephemeral"))
        .build();

ToolExecutionResultMessage toolExecutionResultMessage = someToolExecutionResultMessage.toBuilder()
        .attributes(Map.of("cache_control", "ephemeral"))
        .build();

Cache Diagnostics

Anthropic의 (beta) cache diagnostics 기능은 cacheReadInputTokens가 0으로 떨어지는 것만 보여주는 대신, 프롬프트 캐시 히트가 왜 실패했는지(모델, 시스템 프롬프트, 툴, 메시지 히스토리가 바뀜)를 보고해요.

이 기능은 cache-diagnosis-2026-04-07 beta 헤더가 필요하고 returnCacheDiagnostics로 켜요. 대화의 첫 턴에 previousMessageIdnull로 넘겨 옵트인하고, 이후 턴마다 이전 응답의 id를 넘겨요.

AnthropicChatModel model = AnthropicChatModel.builder()
        .apiKey(System.getenv("ANTHROPIC_API_KEY"))
        .beta("cache-diagnosis-2026-04-07")
        .returnCacheDiagnostics(true)
        .build();

ChatResponse response1 = model.chat(ChatRequest.builder()
        .messages(UserMessage.from("Summarize section 1."))
        .build());
String previousMessageId = ((AnthropicChatResponseMetadata) response1.metadata()).id();

ChatResponse response2 = model.chat(ChatRequest.builder()
        .messages(UserMessage.from("Summarize section 1."), UserMessage.from("Now summarize section 2."))
        .parameters(AnthropicChatRequestParameters.builder()
                // returnCacheDiagnostics is already enabled on the model above, so on subsequent turns
                // you only need to supply the previousMessageId (it changes every turn).
                .previousMessageId(previousMessageId)
                .build())
        .build());

AnthropicCacheDiagnostics diagnostics = ((AnthropicChatResponseMetadata) response2.metadata()).cacheDiagnostics();
if (diagnostics != null && diagnostics.cacheMissReasonType() != null) {
    // e.g. "model_changed", "system_changed", "tools_changed", "messages_changed",
    // "previous_message_not_found" or "unavailable"
    System.out.println(diagnostics.cacheMissReasonType());
}

진단을 요청하지 않았거나 차이가 없으면 cacheDiagnostics()null이에요.

Thinking

AnthropicChatModelAnthropicStreamingChatModel 둘 다 extended thinkingadaptive thinking 기능을 지원해요. 다음 파라미터들로 제어해요.

  • thinkingType, thinkingBudgetTokens — thinking을 켜요. 자세한 내용은 여기를 참고하세요.
  • thinkingDisplay — API가 thinking 시그니처 옆에 읽을 수 있는 thinking 텍스트를 반환할지 제어해요. 유효한 값은 "summarized"(thinking 블록이 reasoning의 요약을 담음)와 "omitted"(thinking 블록이 빈 thinking 텍스트를 담고 암호화된 시그니처만 반환)예요. 설정하지 않으면 모델에 따라 달라지는 기본값을 API가 고르는데, 최근 Claude 모델은 "omitted", 오래된 모델은 "summarized"로 기본 설정돼요. Anthropic 문서 참고. 최종 사용자에게 보여주는 것처럼 thinking 텍스트 자체가 필요하면 "summarized"로 설정하세요. 모델이 생각하고 청구되는 방식은 두 경우 동일하고, thinking 텍스트의 가시성만 달라져요.
  • returnThinking — thinking(있다면)을 AiMessage.thinking()에 반환할지, 그리고 AnthropicStreamingChatModel에서 StreamingChatResponseHandler.onPartialThinking()TokenStream.onPartialThinking() 콜백을 호출할지 제어해요. 기본 비활성. 켜면 thinking 시그니처도 AiMessage.attributes()에 저장돼 반환돼요. API가 thinking 텍스트를 반환하지 않으면 AiMessage.thinking()은 비어 있는데, 위 thinkingDisplay를 참고하세요.
  • sendThinkingAiMessage에 저장된 thinking과 시그니처를 후속 요청에서 LLM에 보낼지 제어해요. 기본 활성.

effort 파라미터를 설정하려면 모델을 만들 때 customParameters를 지정해요.

ChatModel model = AnthropicChatModel.builder()
        .apiKey(System.getenv("ANTHROPIC_API_KEY"))
        .modelName("claude-sonnet-5")
        .customParameters(Map.of("output_config", Map.of("effort", "max")))
        ...
        .build();

thinking을 구성하는 예시예요.

ChatModel model = AnthropicChatModel.builder()
        .apiKey(System.getenv("ANTHROPIC_API_KEY"))
        .modelName("claude-sonnet-4-5-20250929")
        .thinkingType("enabled")
        .thinkingBudgetTokens(1024)
        .maxTokens(1024 + 100)
        .returnThinking(true)
        .sendThinking(true)
        .build();

최근 Claude 모델은 thinkingDisplay가 요청하지 않으면 thinking 텍스트를 반환하지 않아서, 설정하지 않으면 AiMessage.thinking()이 비어 있어요.

ChatModel model = AnthropicChatModel.builder()
        .apiKey(System.getenv("ANTHROPIC_API_KEY"))
        .modelName("claude-sonnet-5")
        .thinkingType("adaptive")
        .thinkingDisplay("summarized")
        .maxTokens(16000)
        .returnThinking(true)
        .sendThinking(true)
        .build();

대화 중간 시스템 메시지 (Mid-Conversation System Messages)

기본적으로 모든 SystemMessage는 메시지 리스트의 어느 위치에 있든 최상위 system 프롬프트로 접혀 들어가요. 이것은 Anthropic이 항상 그랬던 방식이고 바뀌지 않아요.

Claude Opus 4.8은 추가로 mid-conversation system messages를 지원해요. 대화가 시작된 뒤에 나타나는 SystemMessagemessages 배열 안의 system 엔트리로 인라인 전송해서, 그 시점부터 대화에 효과를 줄 수 있어요(예: 세션 도중에 어시스턴트 지시를 바꾸기). 이건 midConversationSystemMessages(true)로 켜요.

AnthropicChatModel model = AnthropicChatModel.builder()
    .apiKey(System.getenv("ANTHROPIC_API_KEY"))
    .modelName("claude-opus-4-8")
    .midConversationSystemMessages(true)
    .build();

ChatResponse response = model.chat(ChatRequest.builder()
    .messages(
        SystemMessage.from("You are a helpful assistant."), // leading -> top-level "system" prompt
        UserMessage.from("Hello"),
        AiMessage.from("Hi! How can I help?"),
        SystemMessage.from("From now on, answer only in French."), // mid-conversation -> inline
        UserMessage.from("What is the capital of Spain?"))
    .build());

이 옵션을 켜면 앞에 오는(첫 user/assistant 메시지 앞의) SystemMessage는 여전히 최상위 system 프롬프트를 채우고, 대화가 시작된 뒤에 나타나는 것만 인라인으로 보내져요. 이건 단순한 관례가 아니라 Anthropic이 요구하는 것이에요. system 메시지는 messages 배열의 첫 엔트리가 될 수 없고, 기본 시스템 프롬프트는 어차피 안정적이고 캐시 가능한 프리픽스에 있어야 하거든요. 옵션이 꺼져 있으면(기본값) 동작은 바뀌지 않고 모든 SystemMessage가 최상위 system 프롬프트로 가요.

이것도 AnthropicChatRequestParameters로 요청별 설정할 수 있어요(위 Per-Request Parameters 참고).

:::note Anthropic은 대화 중간 시스템 메시지가 놓일 수 있는 위치를 제약해요. 반드시 user 턴(툴 결과를 담은 user 턴 포함) 바로 뒤에 와야 하고, assistant 턴 앞이나 배열 끝에 있어야 하며, tool_use 블록과 그 tool_result 사이에 놓이면 안 돼요. 연속된 system 메시지도 허용되지 않아요. 옵션이 꺼져 있으면 langchain4j가 여러 SystemMessage를 최상위 system 필드로 합치지만, 켜져 있으면 인접한 두 대화 중간 SystemMessage가 연속된 인라인 system 엔트리로 보내져 거부돼요. langchain4j는 인라인 메시지를 재정렬하거나 합치지 않고 여러분이 준 위치 그대로 보내므로, 지원하지 않는 모델이나 잘못된 배치는 Anthropic API에서 400이 돼요. :::

PDF 지원

Anthropic Claude는 PDF 문서 처리를 지원해요. URL이나 base64 인코딩 데이터로 PDF를 보낼 수 있어요.

URL로 PDF 보내기

UserMessage message = UserMessage.from(
    PdfFileContent.from(URI.create("https://example.com/document.pdf")),
    TextContent.from("What are the key findings in this document?")
);

ChatResponse response = model.chat(message);

Base64로 PDF 보내기

String base64Data = Base64.getEncoder().encodeToString(Files.readAllBytes(Path.of("document.pdf")));

UserMessage message = UserMessage.from(
    PdfFileContent.from(base64Data, "application/pdf"),
    TextContent.from("Summarize this document.")
);

ChatResponse response = model.chat(message);

PDF 지원에 대한 자세한 내용은 여기를 참고하세요.

커스텀 채팅 요청 파라미터 설정

AnthropicChatModelAnthropicStreamingChatModel을 만들 때 HTTP 요청 JSON 본문 안에 채팅 요청용 커스텀 파라미터를 넣을 수 있어요. context editing을 켜는 예시예요.

record Edit(String type) {}
record ContextManagement(List<Edit> edits) { }
Map<String, Object> customParameters = Map.of("context_management", new ContextManagement(List.of(new Edit("clear_tool_uses_20250919"))));

ChatModel model = AnthropicChatModel.builder()
    .apiKey(System.getenv("ANTHROPIC_API_KEY"))
    .modelName(CLAUDE_SONNET_4_5_20250929)
    .beta("context-management-2025-06-27")
    .customParameters(customParameters)
    .logRequests(true)
    .logResponses(true)
    .build();

String answer = model.chat("Hi");

이건 다음과 같은 HTTP 요청 본문을 만들어요.

{
    "model" : "claude-sonnet-4-5-20250929",
    "messages" : [ {
        "role" : "user",
        "content" : [ {
            "type" : "text",
            "text" : "Hi"
        } ]
    } ],
    "context_management" : {
        "edits" : [ {
            "type" : "clear_tool_uses_20250919"
        } ]
    }
}

커스텀 파라미터는 중첩 맵 구조로도 지정할 수 있어요.

Map<String, Object> customParameters = Map.of(
        "context_management",
        Map.of("edits", List.of(Map.of("type", "clear_tool_uses_20250919")))
);

raw HTTP 응답과 Server-Sent Events(SSE) 접근

AnthropicChatModel을 쓸 때 raw HTTP 응답에 접근할 수 있어요.

SuccessfulHttpResponse rawHttpResponse = ((AnthropicChatResponseMetadata) chatResponse.metadata()).rawHttpResponse();
System.out.println(rawHttpResponse.body());
System.out.println(rawHttpResponse.headers());
System.out.println(rawHttpResponse.statusCode());

AnthropicStreamingChatModel을 쓸 때는 raw HTTP 응답(위 참고)과 raw Server-Sent Events에 접근할 수 있어요.

List<ServerSentEvent> rawServerSentEvents = ((AnthropicChatResponseMetadata) chatResponse.metadata()).rawServerSentEvents();
System.out.println(rawServerSentEvents.get(0).data());
System.out.println(rawServerSentEvents.get(0).event());

AnthropicTokenCountEstimator

TokenCountEstimator tokenCountEstimator = AnthropicTokenCountEstimator.builder()
        .modelName(CLAUDE_3_OPUS_20240229)
        .apiKey(System.getenv("ANTHROPIC_API_KEY"))
        .logRequests(true)
        .logResponses(true)
        .build();

List<ChatMessage> messages = List.of(...);

int tokenCount = tokenCountEstimator.estimateTokenCountInMessages(messages);

Quarkus

자세한 내용은 여기를 참고하세요.

Spring Boot

Anthropic용 Spring Boot starter를 가져와요.

<dependency>
    <groupId>dev.langchain4j</groupId>
    <artifactId>langchain4j-anthropic-spring-boot4-starter</artifactId>
    <version>1.20.0-beta30</version>
</dependency>

:::note 이 starter는 Spring Boot 4가 필요해요. Spring Boot 3에서는 langchain4j-anthropic-spring-boot-starter를 쓰면 되고, 자세한 건 Spring Boot Integration 문어를 참고하세요. :::

AnthropicChatModel 빈을 구성해요.

langchain4j.anthropic.chat-model.api-key = ${ANTHROPIC_API_KEY}

AnthropicStreamingChatModel 빈을 구성해요.

langchain4j.anthropic.streaming-chat-model.api-key = ${ANTHROPIC_API_KEY}

예시

더 알아보기