
Spring Boot 애플리케이션에 사고하는 능력 가르치기: Groq와 Spring AI를 활용한 LLM 도구 호출 (Tool Calling)
요약
Spring Boot와 Spring AI를 사용하여 Groq LLM의 도구 호출(Tool Calling) 기능을 구현하는 튜토리얼입니다. Java 메서드를 통해 실시간 날씨와 뉴스 데이터를 가져와 LLM이 이를 바탕으로 답변을 생성하는 애플리케이션 구축 과정을 다룹니다.
핵심 포인트
- Spring AI를 활용한 LLM 도구 호출 구현 방법
- Groq API를 OpenAI 호환 설정으로 연동하기
- Java 함수를 Spring Bean으로 등록하여 도구로 사용
- 실시간 데이터 기반의 지능형 에이전트 구축 흐름 이해
대규모 언어 모델 (Large language models)은 강력하지만, 실제 애플리케이션 데이터와 함께 작동할 때 훨씬 더 유용해집니다.
그 지점에서 **도구 호출 (tool calling)**이 등장합니다.
도구 호출을 통해 LLM은 최종 답변을 생성하기 전에 백엔드 함수, API 또는 서비스를 호출할 수 있습니다. 모델은 실시간 데이터에 대해 추측하는 대신, 필요한 정보를 애플리케이션에 요청하고, 그 결과를 받은 다음, 더 나은 문맥 (context)과 함께 응답할 수 있습니다.
이 튜토리얼에서는 사용자가 다음과 같은 질문을 할 수 있는 작은 Spring Boot 애플리케이션을 구축할 것입니다:
오늘 버지니아주 애슈번(Ashburn, VA)에 외출해도 될까?
애플리케이션은 LLM이 Java 메서드를 호출하여 현재 날씨와 주요 뉴스 헤드라인을 가져오게 한 다음, 두 결과를 결합하여 실용적인 답변을 제공하도록 합니다.
참고: 이 기사는 Spring AI의 OpenAI 호환 설정을 통해 Groq를 사용하는 Spring AI 1.0.0-M1을 사용합니다. Spring AI는 계속 진화하고 있으므로, 더 최신 버전의 Spring AI를 사용하는 경우 현재의 도구 API (Tools API) 문서를 확인하고
.functions(...)사용법을 그에 맞게 조정하십시오.
우리가 구축할 것
우리는 다음과 같은 구성 요소로 간단한 채팅 애플리케이션을 구축합니다:
- Spring Boot 백엔드
- 순수(vanilla) HTML/JavaScript 프론트엔드
- Groq에서 호스팅되는 LLM
- 두 개의 Java 도구:
get_weatherget_news
흐름은 다음과 같습니다:
- 사용자가 브라우저에서 메시지를 보냅니다.
- Spring Boot가 메시지를 LLM으로 전달합니다.
- LLM이 날씨나 뉴스 데이터가 필요한지 여부를 결정합니다.
- Spring AI가 일치하는 Java 함수를 호출합니다.
- 도구 결과가 LLM으로 다시 전송됩니다.
- LLM이 사용자를 위한 최종 답변을 생성합니다.
홈페이지는 다음과 같습니다:
사전 요구 사항
다음이 필요합니다:
- Java 21
- Maven 3.9 이상
- Groq API 키
- 기본적인 Spring Boot 지식
Groq는 OpenAI와 호환되는 API를 제공하므로, Spring AI의 OpenAI 통합 설정을 변경하여 OpenAI 대신 Groq를 호출하도록 구성할 수 있습니다.
프로젝트 구조 (Project Structure)
java-mcp-weather-news/
├── pom.xml
└── src/main/
...
도구 등록 방식 (How Tool Registration Works)
이 프로젝트 버전에서는 Java의 Function<Request, Response> 인터페이스를 사용하여 도구(tools)를 Spring 빈(Bean)으로 등록합니다.
기본 패턴은 다음과 같습니다:
- 유용한 백엔드 동작을 수행하는 Java 함수를 생성합니다.
- 해당 함수를 Spring
@Bean으로 등록합니다. - 명확한
@Description을 추가합니다. ChatClient를 호출할 때 도구 이름을 참조합니다.
예시:
chatClient.prompt()
.system(systemPrompt)
.user(message)
...
Spring AI는 도구 정의(tool definitions)를 모델로 전송합니다. 만약 모델이 실시간 데이터가 필요하다고 판단하면, 도구 호출(tool call)을 반환합니다. 그러면 Spring AI는 일치하는 Java 함수를 호출하고, 도구 실행 결과를 다시 모델로 보내 최종 응답을 받습니다.
여기서 중요한 개념은 LLM이 외부 서비스를 직접 호출하지 않는다는 점입니다. 어떤 도구가 존재하는지, 무엇을 할 수 있는지, 그리고 어떤 데이터를 반환할지는 여러분의 백엔드에서 제어합니다.
WeatherService.java
날씨 서비스(weather service)는 wttr.in에서 현재 날씨를 가져옵니다. 별도의 날씨 API 키가 필요하지 않기 때문에 데모를 간단하게 유지할 수 있습니다.
package com.mulhaq.mcp.tools;
import org.springframework.stereotype.Service;
...
이 서비스는 사람이 읽기 쉬운 짧은 요약본을 반환합니다. 이러한 형식은 의도된 것입니다. LLM은 전체 원시 JSON(raw JSON) 응답을 가질 필요가 없으며, 유용한 날씨 정보만 있으면 되기 때문입니다.
NewsService.java
뉴스 서비스는 BBC News RSS 피드에서 상위 5개의 헤드라인을 가져옵니다.
package com.mulhaq.mcp.tools;
import org.springframework.stereotype.Service;
...
RSS 피드에는 CDATA 및 HTML과 유사한 텍스트가 포함될 수 있기 때문에 여기서는 Java의 내장 DOM 파서(DOM parser)를 사용했습니다. 이 작은 사용 사례에서는 DOM 파서가 단순하고 신뢰할 수 있습니다.
한 가지 중요한 보안 세부 사항은 외부 XML 엔티티(external XML entities)가 비활성화되어 있다는 점입니다. 이는 외부 소스로부터 XML을 파싱할 때 XML 외부 엔티티 (XXE) 문제를 줄이는 데 도움이 됩니다.
GroqConfig.java
이 설정 클래스는 ChatClient, RestTemplate, 그리고 두 개의 도구 함수(tool functions)를 하나로 연결합니다.
package com.mulhaq.mcp.config;
import com.mulhaq.mcp.tools.WeatherService;
...
@Description 텍스트는 생각보다 중요합니다. 이 텍스트는 모델에게 도구를 언제, 어떻게 사용할지를 알려줍니다. 모호한 설명은 모델이 도구를 건너뛰거나 잘못된 입력값으로 호출하게 만들 수 있습니다.
ChatController.java
컨트롤러는 하나의 엔드포인트(endpoint)를 노출합니다: POST /api/chat.
package com.mulhaq.mcp.controller;
import org.springframework.ai.chat.client.ChatClient;
...
핵심이 되는 라인은 다음과 같습니다:
.functions("get_weather", "get_news")
이 이름들은 GroqConfig의 빈(bean) 이름과 일치합니다. Spring AI는 이 이름들을 사용하여 함수를 모델이 호출할 수 있는 도구(tools)로 노출합니다.
운영 환경용 애플리케이션이라면 @CrossOrigin(origins = "*")를 그대로 유지하지 않을 것입니다. 로컬 데모용으로는 허용되지만, 운영 시스템은 허용된 오리진(origins)을 제한해야 합니다.
application.properties
spring.ai.openai.api-key=YOUR_GROQ_API_KEY_HERE
spring.ai.openai.base-url=https://api.groq.com/openai
spring.ai.openai.chat.options.model=llama-3.3-70b-versatile
...
실제 API 키를 커밋하지 마세요. 애플리케이션을 배포할 때는 환경 변수(environment variable)나 비밀 관리자(secret manager)를 사용하십시오.
또한 베이스 URL(base URL)에 주의하세요:
spring.ai.openai.base-url=https://api.groq.com/openai
Spring AI는 내부적으로 나머지 OpenAI 호환 경로를 추가합니다. 만약 이 Spring AI 설정에서 여기에 /v1을 추가하면, 최종 URL이 잘못되어 404 오류가 발생할 수 있습니다.
Frontend
프론트엔드는 의도적으로 단순하게 구성되었습니다. 다음 위치에 존재합니다:
src/main/resources/static/index.html
프론트엔드는 다음과 같은 요청을 보냅니다:
{
"message": "Should I go out today in Ashburn, VA?"
}
다음 엔드포인트로 전송합니다:
POST /api/chat
Spring Boot가 페이지를 직접 제공하므로 React, Angular 또는 별도의 프론트엔드 빌드가 필요하지 않습니다.
전체 UI 코드는 리포지토리에서 확인할 수 있습니다:
데모 (Demo)
질문을 입력하세요:
AI가 실제 도구(tool) 결과값을 사용하여 응답합니다:
내부적으로는 다음과 같이 단 한 번의 요청으로 진행됩니다:
- 브라우저가 사용자 메시지를
POST /api/chat으로 전송합니다. - Spring AI가 메시지와 도구 정의(tool definitions)를 Groq에 전송합니다.
- Groq가
get_weather("Ashburn, VA")및get_news()와 같은 도구 호출(tool calls)을 반환합니다. - Spring AI가 Java 메서드를 호출합니다.
- 도구 결과가 Groq로 다시 전송됩니다.
- Groq가 최종 답변을 생성합니다.
- 응답이 브라우저로 반환됩니다.
개발 중 직면한 문제들
문제 1: Groq가 추가 필드를 반환함
다음과 유사한 오류를 확인했습니다:
UnrecognizedPropertyException: Unrecognized field "queue_time"
(class org.springframework.ai.openai.api.OpenAiApi$Usage)
Groq는 이 버전의 Spring AI OpenAI 응답 모델에 선언되지 않은 응답 필드를 반환할 수 있습니다. Jackson은 별도로 설정하지 않으면 알 수 없는 필드를 거부합니다.
해결 방법은 다음과 같이 커스텀 ObjectMapper를 등록하는 것이었습니다:
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
그리고 이를 Spring AI의 내부 클라이언트에서 사용하는 메시지 컨버터(message converter)에 적용했습니다.
문제 2: XmlMapper를 사용한 RSS 파싱 실패
다음과 유사한 RSS 파싱 문제도 발생했습니다:
Cannot construct instance of RssItem: no String-argument constructor
더 간단한 해결책은 RSS 피드를 엄격한 Jackson XML 객체 모델로 강제 변환하는 대신 Java의 DOM 파서(DOM parser)를 사용하는 것이었습니다.
이번 데모에서는 RSS 피드에서 몇 가지 필드만 필요했기 때문에 DOM 파서(DOM parser)만으로도 충분했습니다.
프로덕션 개선 사항 (Production Improvements)
이 데모는 의도적으로 작게 구성되었지만, 동일한 패턴을 확장하여 적용할 수 있습니다. 이를 프로덕션(production) 환경에서 사용하기 전에 저는 다음과 같은 영역들을 개선할 것입니다.
1. 데모용 API를 신뢰할 수 있는 제공자로 교체
wttr.in과 공개 RSS 피드는 데모용으로는 좋습니다. 프로덕션 애플리케이션은 속도 제한(rate limits), 가동 시간 보장(uptime guarantees), 예측 가능한 응답 형식(predictable response formats)을 갖춘 신뢰할 수 있는 API를 사용해야 합니다.
2. 도구 입력값 검증 (Validate tool inputs)
모델이 누락되거나 불분명한 입력값으로 도구를 호출할 수 있습니다. 도시 이름을 검증하고, 입력 길이를 제한하며, 안전한 에러 메시지를 반환해야 합니다.
3. 타임아웃 및 재시도 추가
현재 코드에는 타임아웃(timeouts)이 포함되어 있지만, 프로덕션 시스템에는 재시도(retries), 서킷 브레이커(circuit breakers), 그리고 명확한 폴백(fallback) 동작도 포함되어야 합니다.
4. CORS 제한
@CrossOrigin(origins = "*")는 프로덕션에서 사용해서는 안 됩니다. 이를 귀하의 프론트엔드 도메인으로 제한하십시오.
5. 관찰 가능성(Observability) 추가
도구 호출(tool calls), 지연 시간(latency), 실패율(failure rates), 그리고 모델 응답을 로그로 남기십시오. 이는 도구의 동작을 디버깅하고 모델이 도구를 올바르게 사용하고 있는지 이해하는 데 도움이 됩니다.
6. 주의 깊은 업그레이드
Spring AI는 빠르게 진화하고 있습니다. 이 M1 버전에서 더 최신 릴리스로 이동하는 경우, 이 코드를 직접 복사하기 전에 현재의 도구 API(Tools API)와 마이그레이션 노트(migration notes)를 검토하십시오.
아이디어 (Ideas)
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기
