Spring AI: Java 애플리케이션에서 AI 기능을 통합하기 위한 프레임워크
| 개념 | 설명 |
|---|---|
| Prompt | AI 모델에 보내는 입력 요청 |
| Response | AI 모델이 반환하는 응답 |
| ChatClient | Spring AI의 핵심 클라이언트 |
@Controller
private ChatClient chatClient;
public String hello() {
String message = chatClient
.prompt("Hello")
.call()
.content();
return message;
}
이미지를 AI가 분석하고 처리하는 기능
String base64Image = Base64.getEncoder()
.encodeToString(imageBytes);
chatClient.prompt()
.withMedia("image/jpeg", base64Image)
.call();
| 기능 | 설명 |
|---|---|
| 객체 인식 | 이미지 내 객체 감지 |
| 텍스트 추출 | OCR을 통한 텍스트 추출 |
| 이미지 설명 | 이미지 내용 자동 설명 |
| 분류 | 이미지 카테고리 분류 |
음성과 텍스트 간 상호 변환
음성을 텍스트로 변환
| 항목 | 설명 |
|---|---|
| 입력 | 오디오 파일 (Base64 인코딩) |
| 출력 | 텍스트 |
| 활용 | 음성 명령, 자동 자막 |
| 지원 형식 | mp3, wav, m4a, flac |
String base64Audio = Base64.getEncoder()
.encodeToString(audioBytes);
String text = chatClient.prompt()
.withMedia("audio/mp3", base64Audio)
.call()
.content();
텍스트를 음성으로 변환
| 항목 | 설명 |
|---|---|
| 입력 | 텍스트 문자열 |
| 출력 | 오디오 파일 |
| 활용 | 음성 안내, 접근성 |
| 지원 포맷 | mp3, wav, opus |
외부 API를 통해 실시간 날씨 정보 조회
@GetMapping("/weather/{city}")
public String getWeather(@PathVariable String city) {
String response = weatherService.getWeather(city);
return response;
}
| 정보 | 설명 |
|---|---|
| 기온 | 현재 기온 |
| 습도 | 습도 정보 |
| 풍속 | 바람 속도 |
| 강수량 | 강우량 |
| 날씨상태 | 맑음, 흐림, 비 등 |
@Service
@RequiredArgsConstructor
public class WeatherService {
private final RestTemplate restTemplate;
public String getWeather(String city) {
// OpenWeather API 호출
String url = "https://api.openweathermap.org/...";
return restTemplate.getForObject(url, String.class);
}
}
사용자 정보 관리 및 데이터 모델
@Entity
@Table(name = "member")
@Getter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String email;
private String phone;
private String role;
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class MemberDto {
private Long id;
private String name;
private String email;
private String phone;
private String role;
}
public interface MemberRepository extends JpaRepository<Member, Long> {
Optional<Member> findByEmail(String email);
List<Member> findByRole(String role);
}
@Service
@RequiredArgsConstructor
public class MemberService {
private final MemberRepository repo;
public Member saveMember(MemberDto dto) {
Member member = Member.builder()
.name(dto.getName())
.email(dto.getEmail())
.phone(dto.getPhone())
.role(dto.getRole())
.build();
return repo.save(member);
}
public Member getMember(Long id) {
return repo.findById(id)
.orElseThrow(() -> new RuntimeException("Member not found"));
}
}
@RestController
@RequestMapping("/api/members")
@RequiredArgsConstructor
public class MemberController {
private final MemberService service;
@PostMapping
public Member create(@RequestBody MemberDto dto) {
return service.saveMember(dto);
}
@GetMapping("/{id}")
public Member get(@PathVariable Long id) {
return service.getMember(id);
}
}
외부 데이터를 활용한 지능형 응답 생성
문서/데이터베이스에서 정보를 검색한 후 AI 모델에 제공하여 더 정확한 답변 생성
1️⃣ 사용자 쿼리 수신
↓
2️⃣ 데이터베이스에서 관련 문서 검색
↓
3️⃣ 검색 결과와 쿼리를 AI에 전달
↓
4️⃣ AI가 컨텍스트 기반 답변 생성
@Service
@RequiredArgsConstructor
public class RagService {
private final ChatClient chatClient;
private final DocumentRepository docRepo;
public String answerQuestion(String question) {
// 1. 관련 문서 검색
List<Document> documents = docRepo.search(question);
// 2. 컨텍스트 구성
String context = documents.stream()
.map(Document::getContent)
.collect(Collectors.joining("\n"));
// 3. AI에 질문 전달
String response = chatClient.prompt()
.withUserMessage(question + "\n\nContext:\n" + context)
.call()
.content();
return response;
}
}
벡터 데이터베이스를 활용한 고급 검색
| 기술 | 설명 |
|---|---|
| Embedding | 텍스트를 수치 벡터로 변환 |
| Vector DB | Pinecone, Chroma, FAISS 등 |
| 의미론적 검색 | 의미 기반의 유사 문서 검색 |
| 하이브리드 검색 | 키워드 + 벡터 검색 |
@Service
@RequiredArgsConstructor
public class AdvancedRagService {
private final ChatClient chatClient;
private final VectorStore vectorStore;
private final EmbeddingClient embeddingClient;
public String answerQuestion(String question) {
// 1. 질문을 벡터로 변환
float[] embedding = embeddingClient.embed(question);
// 2. 벡터 데이터베이스에서 유사한 문서 검색
List<Document> similarDocs = vectorStore.search(embedding);
// 3. 검색된 문서와 함께 AI에 질문
String context = similarDocs.stream()
.map(Document::getContent)
.collect(Collectors.joining("\n\n"));
String response = chatClient.prompt()
.withUserMessage(
"다음 컨텍스트를 바탕으로 질문에 답하세요:\n\n" +
context + "\n\n질문: " + question
)
.call()
.content();
return response;
}
}
| VectorStore | 특징 |
|---|---|
| Pinecone | 관리형 클라우드 서비스 |
| Chroma | 경량 로컬 솔루션 |
| FAISS | Meta의 오픈소스 벡터 DB |
| Weaviate | 엔터프라이즈급 솔루션 |
| Milvus | 고성능 벡터 DB |
| 항목 | 핵심 포인트 |
|---|---|
| Hello/Prompt | ChatClient로 AI 모델과 상호작용 |
| Image | Base64 인코딩을 통한 이미지 전달 |
| Audio | STT/TTS로 음성 처리 |
| API | RestTemplate으로 외부 API 연동 |
| Data | JPA를 이용한 데이터 관리 |
| RAG | 벡터 검색과 문서 처리를 통한 지능형 응답 |
// 잘못된 예
chatClient.prompt().withMedia("image/jpeg", imageBytes).call();
// 올바른 예
String base64 = Base64.getEncoder().encodeToString(imageBytes);
chatClient.prompt().withMedia("image/jpeg", base64).call();
// 잘못된 예 - 문서를 가져오지 않음
String response = chatClient.prompt(question).call().content();
// 올바른 예 - 문서 검색 후 제공
List<Document> docs = vectorStore.search(embedding);
String context = docs.stream().map(Document::getContent).collect(...);
String response = chatClient.prompt(question + context).call().content();
// 잘못된 예 - 전체 문서를 한 번에 처리
String wholDocument = Files.readString(file);
// 올바른 예 - 적절한 크기로 청킹
List<Document> chunks = documentSplitter.split(wholDocument);