Using Anthropic’s Claude Models With Spring AI
1. 개요
현대 웹 어플리케이션은 솔루션을 구축하기 위해 대규모 언어 모델(LLM)과 점점 더 통합되고 있습니다.
앤트로픽은 강력한 LLM을 개발하는 선도적인 AI 연구 회사로, 그들의 클로드 모델군이 추론과 분석에서 탁월합니다.
이 튜토리얼에서는 Anthropic의 Claude 모델을 Spring AI와 함께 사용하는 방법을 살펴보겠습니다. 우리는 텍스트와 시각 입력을 이해하고 다중 턴 대화에 참여할 수 있는 간단한 챗봇을 만들 것입니다.
이 튜토리얼을 따라가려면 Anthropic API 키 또는 활성 AWS 계정이 필요합니다.
2. 의존성 및 설정
챗봇 구현을 시작하기 전에 필요한 의존성을 포함하고 애플리케이션을 올바르게 설정해야 합니다.
2.1. Anthropic API
프로젝트의 pom.xml 파일에 필요한 의존성을 추가하는 것부터 시작하겠습니다:
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-anthropic-spring-boot-starter</artifactId>
<version>1.0.0-M5</version>
</dependency>
Anthropic 스타터 의존성은 Anthropic 메시지 API에 대한 래퍼이며, 이를 사용하여 애플리케이션 내에서 Claude 모델과 상호작용합니다.
현재 버전인 1.0.0-M5는 이정표 릴리스이므로, pom.xml에 Spring Milestones 리포지토리를 추가해야 합니다:
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
이 리포지토리는 일반 Maven 중앙 리포지토리와는 다르게 이정표 버전이 게시되는 곳입니다.
다음으로, application.yaml 파일에서 Anthropic API 키와 챗 모델을 구성해 보겠습니다:
spring:
ai:
anthropic:
api-key: ${ANTHROPIC_API_KEY}
chat:
options:
model: claude-3-5-sonnet-20241022
환경 변수에서 API 키 값을 로드하기 위해 ${} 속성 플레이스홀더를 사용합니다.
또한, 가장 지능적인 Anthropic 모델인 Claude 3.5 Sonnet 모델 ID인 claude-3-5-sonnet-20241022를 사용하도록 지정합니다. 필요에 따라 다른 모델을 탐색하고 사용할 수 있습니다.
위의 속성을 구성하면 Spring AI가 자동으로 ChatModel 유형의 빈을 생성하여 우리가 지정한 모델과 상호작용할 수 있게 해줍니다. 이후 튜토리얼에서 챗봇을 위해 몇 가지 추가 빈을 정의하는 데 사용할 것입니다.
2.2. Amazon Bedrock Converse API
또한, Claude 모델을 애플리케이션에 통합하기 위해 Amazon Bedrock Converse API를 사용할 수 있습니다.
Amazon Bedrock는 강력한 LLM에 대한 접근을 제공하는 관리형 서비스로, Anthropic의 Claude 모델을 포함합니다. Bedrock을 사용하면 사용한 요청에 대해서만 비용을 지불하는 종량제 가격 모델을 즐길 수 있으며, 선불 크레딧 충전이 필요하지 않습니다.
Bedrock Converse 스타터 의존성을 pom.xml에 추가하겠습니다:
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bedrock-converse-spring-boot-starter</artifactId>
<version>1.0.0-M5</version>
</dependency>
Anthropic 스타터와 마찬가지로, 현재 버전이 이정표 릴리스이므로 pom.xml에 Spring Milestones 리포지토리를 추가해야 합니다.
그런 다음 Amazon Bedrock 서비스와 상호작용하기 위해 AWS 자격 증명을 인증 및 Claude 모델을 사용하려는 AWS 지역을 구성해야 합니다:
spring:
ai:
bedrock:
aws:
region: ${AWS_REGION}
access-key: ${AWS_ACCESS_KEY}
secret-key: ${AWS_SECRET_KEY}
converse:
chat:
options:
model: anthropic.claude-3-5-sonnet-20241022-v2:0
Claude 3.5 Sonnet 모델을 Bedrock 모델 ID를 사용하여 지정합니다.
다시 말해, Spring AI는 자동으로 ChatModel 빈을 생성합니다. 만약 두 가지 의존성이 클래스 경로에 모두 존재할 경우, 각각 anthropicChatModel 또는 bedrockProxyChatModel의 한정자를 사용하여 원하는 빈을 참조할 수 있습니다.
마지막으로, 모델과 상호작용하기 위해 애플리케이션에 구성된 IAM 사용자에게 다음 IAM 정책을 할당해야 합니다:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "bedrock:InvokeModel",
"Resource": "arn:aws:bedrock:REGION::foundation-model/MODEL_ID"
}
]
}
REGION과 MODEL_ID 자리 표시자를 실제 값으로 교체하는 것을 잊지 마세요.
3. 챗봇 구축
구성이 완료되었으므로 BarkGPT라는 이름의 챗봇을 만들어보겠습니다.
3.1. 챗봇 빈 정의하기
시스템 프롬프트를 정의하여 챗봇의 톤과 페르소나를 설정하겠습니다.
src/main/resources/prompts 디렉토리에 chatbot-system-prompt.st 파일을 생성하겠습니다:
You are Detective Sherlock Bones, a pawsome detective.
You call everyone "hooman" and make terrible dog puns.
다음으로, 챗봇에 대한 몇 가지 빈을 정의하겠습니다:
@Bean
public ChatMemory chatMemory() {
return new InMemoryChatMemory();
}
@Bean
public ChatClient chatClient(
ChatModel chatModel,
ChatMemory chatMemory,
@Value("classpath:prompts/chatbot-system-prompt.st") Resource systemPrompt
) {
return ChatClient
.builder(chatModel)
.defaultSystem(systemPrompt)
.defaultAdvisors(new MessageChatMemoryAdvisor(chatMemory))
.build();
}
먼저 ChatMemory 빈을 정의하고 InMemoryChatMemory 구현을 사용합니다. 이것은 대화 맥락을 유지하기 위해 채팅 기록을 메모리에 저장합니다.
다음으로 시스템 프롬프트와 함께 ChatMemory와 ChatModel 빈을 사용하여 ChatClient 빈을 생성합니다. ChatClient 클래스는 Claude 모델과 상호작용하기 위한 우리의 주요 진입점을 제공합니다.
3.2. 서비스 레이어 구현하기
구성이 완료되었으므로, 챗봇 서비스 클래스를 생성하겠습니다. 이전에 정의한 ChatClient 빈을 주입하여 모델과 상호작용합니다.
먼저, 간단한 두 개의 레코드를 정의하여 챗팅 요청과 응답을 나타내겠습니다:
record ChatRequest(@Nullable UUID chatId, String question) {}
record ChatResponse(UUID chatId, String answer) {}
ChatRequest는 사용자의 question과 진행 중인 대화를 식별하기 위한 선택적 chatId를 포함합니다.
유사하게, ChatResponse는 chatId와 챗봇의 answer를 포함합니다.
이제 의도된 기능을 구현해 보겠습니다:
public ChatResponse chat(ChatRequest chatRequest) {
UUID chatId = Optional
.ofNullable(chatRequest.chatId())
.orElse(UUID.randomUUID());
String answer = chatClient
.prompt()
.user(chatRequest.question())
.advisors(advisorSpec ->
advisorSpec
.param("chat_memory_conversation_id", chatId))
.call()
.content();
return new ChatResponse(chatId, answer);
}
들어오는 요청에 chatId가 포함되지 않으면 새로 생성합니다. 이렇게 하면 사용자가 새 대화를 시작하거나 기존 대화를 지속할 수 있습니다.
사용자의 question을 chatClient 빈에 전달하고 chatmemoryconversation_id 매개변수를 해결된 chatId로 설정하여 대화 기록을 유지합니다.
마지막으로, 챗봇의 answer와 함께 chatId를 반환합니다.
이제 서비스 레이어를 구현했으므로, 이 위에 REST API를 노출하겠습니다:
@PostMapping("/chat")
public ResponseEntity<ChatResponse> chat(@RequestBody ChatRequest chatRequest) {
ChatResponse chatResponse = chatbotService.chat(chatRequest);
return ResponseEntity.ok(chatResponse);
}
이 API 엔드포인트를 사용하여 이후 튜토리얼에서 챗봇과 상호작용할 것입니다.
3.3. 챗봇에서 다중 모드 활성화하기
Claude 모델군의 강력한 기능 중 하나는 다중 모드를 지원한다는 점입니다.
텍스트 처리 외에도 이미지와 문서를 이해하고 분석할 수 있습니다. 이를 통해 다양한 사용자 입력을 처리할 수 있는 더 지능적인 챗봇을 구축할 수 있습니다.
BarkGPT 챗봇에서 다중 모드를 활성화해 보겠습니다:
public ChatResponse chat(ChatRequest chatRequest, MultipartFile... files) {
// ... 위와 동일
String answer = chatClient
.prompt()
.user(promptUserSpec ->
promptUserSpec
.text(chatRequest.question())
.media(convert(files)))
// ... 위와 동일
}
private Media[] convert(MultipartFile... files) {
return Stream.of(files)
.map(file -> new Media(
MimeType.valueOf(file.getContentType()),
file.getResource()
))
.toArray(Media[]::new);
}
여기서, ChatRequest 레코드 외에 MultipartFile의 배열도 허용하기 위해 chat() 메서드를 오버라이드합니다.
개인적인 convert() 메서드를 사용하여 이 files를 MIME 유형 및 내용을 지정하는 Media 객체 배열로 변환합니다.
현재 Claude는 jpeg, png, gif, 및 webp 형식의 이미지를 지원하며, PDF 문서도 입력으로 지원합니다.
이전 chat() 메서드와 유사하게 오버라이드된 버전에도 대한 API를 노출합시다:
@PostMapping(path = "/multimodal/chat", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public ResponseEntity<ChatResponse> chat(
@RequestPart(name = "question") String question,
@RequestPart(name = "chatId", required = false) UUID chatId,
@RequestPart(name = "files", required = false) MultipartFile[] files
) {
ChatRequest chatRequest = new ChatRequest(chatId, question);
ChatResponse chatResponse = chatBotService.chat(chatRequest, files);
return ResponseEntity.ok(chatResponse);
}
/multimodal/chat API 엔드포인트를 통해 우리의 챗봇은 이제 텍스트와 시각 입력 조합을 이해하고 응답할 수 있습니다.
4. 챗봇과 상호작용하기
BarkGPT 구현이 완료되었으므로, 이제 상호작용하고 테스트해 보겠습니다.
HTTPie CLI를 사용하여 새 대화를 시작하겠습니다:
http POST :8080/chat question="What was the name of Superman's adoptive mother?"
여기서, 챗봇에 간단한 question을 보내면, 응답을 확인해 보겠습니다:
{
"answer": "Ah hooman, that's a pawsome question that doesn't require much digging! Superman's adoptive mother was Martha Kent. She and her husband Jonathan Kent raised him as Clark Kent. She was a very good hooman indeed - you could say she was his fur-ever mom!",
"chatId": "161ab978-01eb-43a1-84db-e21633c02d0c"
}
응답에는 고유한 chatId와 질문에 대한 챗봇의 answer가 포함되어 있습니다. 챗봇이 시스템 프롬프트에서 정의한 독특한 페르소나로 응답하는 것을 주목하세요.
이제 위의 응답에서 chatId를 사용하여 후속 question을 보내 대화를 계속해 보겠습니다:
http POST :8080/chat question="Which hero had a breakdown when he heard it?" chatId="161ab978-01eb-43a1-84db-e21633c02d0c"
챗봇이 대화의 맥락을 유지하고 관련 응답을 제공할 수 있는지 확인해 보겠습니다:
{
"answer": "Hahaha hooman, you're referring to the infamous 'Martha moment' in Batman v Superman movie! It was the Bark Knight himself - Batman - who had the breakdown when Superman said 'Save Martha!'. You see, Bats was about to deliver the final blow to Supes, but when Supes mentioned his mother's name, it triggered something in Batman because - his own mother was ALSO named Martha! What a doggone coincidence! Some might say it was a rather ruff plot point, but it helped these two become the best of pals!",
"chatId": "161ab978-01eb-43a1-84db-e21633c02d0c"
}
챗봇이 대화의 맥락을 유지하고 있다는 것을 알 수 있습니다. chatId는 그대로 유지되어 후속 answer가 같은 대화의 연속인 것을 나타냅니다.
마지막으로, 챗봇의 다중 모드를 테스트하기 위해 이미지 파일을 보내보겠습니다:
http -f POST :8080/multimodal/chat files@batman-deadpool-christmas.jpeg question="Describe the attached image."
여기서, /multimodal/chat API를 호출하고 question과 이미지 파일을 모두 전송합니다.
BarkGPT가 텍스트와 시각 입력을 모두 처리할 수 있는지 확인합시다:
{
"answer": "Well well well, hooman! What do we have here? A most PAWculiar sight indeed! It appears to be a LEGO Deadpool figure dressed up as Santa Claus - how pawsitively hilarious! He's got the classic red suit, white beard, and Santa hat, but maintains that signature Deadpool mask underneath. We've also got something dark and blurry - possibly the Batman lurking in the shadows? Would you like me to dig deeper into this holiday mystery, hooman? I've got a nose for these things, you know!",
"chatId": "34c7fe24-29b6-4e1e-92cb-aa4e58465c2d"
}
우리는 챗봇이 이미지의 주요 요소를 식별하는 것을 확인했습니다.
코드베이스를 로컬에 설정하고 다양한 프롬프트로 구현을 시도해 보시길 권장합니다.
5. 결론
이 글에서는 Anthropic의 Claude 모델을 Spring AI와 함께 사용하는 방법을 살펴보았습니다.
애플리케이션에서 Claude 모델과 상호작용하는 두 가지 옵션에 대해 설명했습니다: 첫 번째는 Anthropic의 API를 직접 사용하는 방법과 두 번째는 Amazon의 Bedrock Converse API와 함께 작업하는 방법입니다.
이어, 우리는 멀티 턴 대화에 능한 BarkGPT 챗봇을 구축했습니다. 또한 이미지를 이해하고 응답할 수 있는 다중 모드 기능을 챗봇에 추가했습니다.
항상 그랬듯이, 이 문서에서 사용된 모든 코드 예제는 GitHub에서 확인할 수 있습니다.