지금까지는 텍스트만 주고받았다. 멀티모달(multimodal) 모델은 이미지·음성도 함께 다룬다. 이 편에서는 이미지의 두 방향 — 이미지를 읽는 입력(비전)과, 텍스트로 이미지를 만드는 생성 — 을 구현한다.
이 편에서 만드는 것
/image/describe(이미지 업로드 → 설명) · /image/generate(프롬프트 → 이미지) 두 개의 엔드포인트.
1. 이미지 입력 (비전)
gpt-4o 계열은 이미지를 보고 설명할 수 있다. ChatClient의 user(...)에서 텍스트와 함께 이미지를 미디어로 붙이면 된다. 여기서는 업로드된 파일(MultipartFile)을 받아 처리한다.
public String describe(byte[] imageBytes, String contentType, String question) {
MimeType mimeType = (contentType != null)
? MimeType.valueOf(contentType) // 예: image/png
: MimeTypeUtils.IMAGE_PNG;
return chatClient.prompt()
.user(u -> u.text(question)
.media(mimeType, new ByteArrayResource(imageBytes))) // 텍스트 + 이미지
.call()
.content();
}@PostMapping("/image/describe")
public String describe(@RequestParam("file") MultipartFile file,
@RequestParam(defaultValue = "이 이미지를 한국어로 설명해줘.") String question)
throws IOException {
return imageService.describe(file.getBytes(), file.getContentType(), question);
}curl -X POST -F "file=@apple.png" \
"http://localhost:8080/image/describe?question=이 사진에 무엇이 있나요?"
# 사진에는 나무 테이블 위에 놓인 빨간 사과가 있습니다.업로드 용량 제한
Spring Boot 기본 업로드 한도는 1MB라서 사진을 올리면 413 오류가 난다. application.yml에서 한도를 올린다.
spring:
servlet:
multipart:
max-file-size: 10MB
max-request-size: 15MBflowchart LR
F["이미지 파일"] --> M["Media(mimeType, bytes)"]
Q["질문 텍스트"] --> P["user(text + media)"]
M --> P --> LLM["gpt-4o (비전)"] --> A["텍스트 설명"]2. 이미지 생성
OpenAiImageModel에 프롬프트를 주면 이미지를 만들어 준다. 모델은 gpt-image-1을 쓴다.
public byte[] generate(String prompt) {
ImageResponse response = imageModel.call(new ImagePrompt(prompt,
OpenAiImageOptions.builder()
.model("gpt-image-1")
.width(1024)
.height(1024)
.N(1) // 생성 장수
.build()));
// gpt-image-1은 URL이 아니라 base64(b64Json)로 결과를 준다
String b64 = response.getResult().getOutput().getB64Json();
return Base64.getDecoder().decode(b64);
}@GetMapping(value = "/image/generate", produces = MediaType.IMAGE_PNG_VALUE)
public byte[] generate(@RequestParam String prompt) {
return imageService.generate(prompt); // PNG 바이트를 그대로 반환 → 브라우저에 표시
}curl -G "http://localhost:8080/image/generate" \
--data-urlencode "prompt=수채화 스타일의 우주를 나는 귀여운 고양이" \
-o cat.png
# 1024x1024 PNG 저장모델마다 응답 형태가 다르다
예전 dall-e-3는 결과를 URL로 주지만, gpt-image-1은 base64(b64Json) 로 준다. 그래서 getUrl()이 아니라 getB64Json()을 디코딩해 바이트로 다뤘다. 쓰는 모델의 응답 형태를 확인하고 맞춰야 한다.
정리
방향 | 모델 | 핵심 API |
|---|---|---|
이미지 입력(비전) | gpt-4o 계열 |
|
이미지 생성 | gpt-image-1 |
|
비전 + Tool Calling 조합
이미지 입력은 Tool Calling과 묶으면 강력하다. 예를 들어 주차장 차량 번호판 사진을 읽어(비전) → 번호를 DB와 대조(도구) → 차단기를 여는(도구) 흐름을 만들 수 있다.
➡️ 다음 편: 멀티모달 - 음성