콘텐츠로 이동

채팅에서 이미지 업로드와 AI 인식 구현: 설계부터 구현까지의 완전한 솔루션

페이지 편집
HagiCode for Windows Microsoft Store artwork
HagiCode for Windows is now on Microsoft Store
HagiCode for Windows is officially live on Microsoft Store. Windows users can install it directly from the storefront and stay on the store-managed update path. Open the listing and take a look.
Open Microsoft Store

채팅에서 이미지 업로드와 AI 인식 구현: 설계부터 구현까지의 완전한 솔루션

AI 상호작용 시스템에서 사용자가 이미지를 업로드하고 AI가 직접 인식하도록 하려면 어떻게 해야 할까요? 사실 이 문제도 저도 오랫동안 고민해왔는데, 다행히 HagiCode의 실천 과정에서 몇 가지 요령을 터득했습니다. 오늘은 이 이미지 업로드와 인식 솔루션에 대해 이야기해보려고 합니다. 사용자 정의 프로토콜 설계부터 파일 시스템 저장, 그리고 프론트엔드/백엔드 분리 미리보기까지, 완전한 기술 노트라고 할 수 있겠습니다.

배경

AI 채팅이 유행하는 시대에 시각적 정보는 사실 사용자가 의도를 표현하는 중요한 매체입니다. 하지만 전통적인 채팅 시스템은 대부분 순수 텍스트 입력만 지원하여, 사용자가 시각적 컨텍스트를 AI 분석을 위해 직접 전달할 수 없다는 점은 조금 아쉬운 일입니다.

HagiCode 개발 과정에서도 비슷한 난관에 부딪혔습니다: 사용자가 채팅이나 메인 의견 생성 시 이미지를 업로드할 수 없고, AI가 사용자 로컬의 시각적 정보에 접근할 수 없으며, 이미지 입력부터 저장, 렌더링, AI 컨텍스트 전달까지의 완전한闭环이 부족했습니다.

사실 이런 문제들도 크게 어렵지 않습니다. 다만 시간과 인내가 조금 필요할 뿐입니다. 우리는 완전한 이미지 업로드와 인식 프로세스를 설계하고 구현하여, Claude 등의 AI가 사용자가 업로드한 스크린샷을 직접 인식하고 분석할 수 있도록 했습니다. 다음으로 이 솔루션의 구현 세부 사항을 천천히 설명하겠습니다.

HagiCode에 대해

이 문서에서 공유하는 솔루션은 HagiCode 프로젝트에서의 실천 경험에서 나왔습니다. HagiCode는 오픈 소스 AI 코드 어시스턴트 프로젝트로, OpenSpec 기반 워크플로우 설계를 채택하여 더 스마트한 코드 작성 경험을 제공하는 데 주력하고 있습니다.

분석

기술적 과제

구현을 시작하기 전에, 먼저 직면한 주요 과제들을 명확히 해야 합니다. 밑돌 깎아서 다듬는 격이라고 하죠.

크로스 모듈 협업: 이미지 업로드는 프론트엔드 UI, 업로드 서비스, 백엔드 API, 파일 저장, 메시지 지속성, AI 실행 매핑 등 여러 모듈을 포함합니다. 각 모듈마다 자신의 책임과 인터페이스가 있으므로, 조화롭게 통합된 전체 솔루션을 설계해야 합니다.

저장 전략 선택: 이미지를 데이터베이스에 저장할까요, 아니면 파일 시스템에? 파일 시스템을 선택한다면 디렉토리 구조는 어떻게 설계할까요? 기존 OpenSpec 워크플로우와 어떻게 통합할까요? 이 모든 것은 신중하게 고려해야 합니다.

참조 프로토콜 설계: 프론트엔드 렌더링과 AI 실행 체인이 모두 올바르게 파싱할 수 있는 표준적인 이미지 참조 방식이 필요합니다. 파일 경로를 직접 사용할까요? HTTP URL? 아니면 전용 프로토콜을 설계할까요?

AI 능력 호환성: 다른 AI 실행기는 멀티모달 지원 정도가 다릅니다. 어떤 실행기는 네이티브 이미지 입력을 지원하고, 어떤 것은 텍스트만 처리할 수 있습니다. 모든 실행기가 이미지 정보를 올바르게 처리할 수 있도록 통합된 어댑터 레이어를 어떻게 설계할까요?

설계 결정

충분한 논의와 검토 끝에 우리는 다음과 같은 주요 설계 결정을 내렸습니다.

결정 1: 파일 시스템 저장

우리는 데이터베이스 대신 파일 시스템에 이미지를 저장하기로 선택했습니다. 디렉토리 구조는 다음과 같이 설계했습니다:

<시스템 루트>/images/<sessionId>/
├── <timestamp>-<uuid>.jpg
└── <timestamp>-<uuid>.png

이유도 명확합니다: 구현을 단순화하고, 데이터베이스 팽창을 방지하며, 파일이 AI에 의해 직접 읽힐 수 있습니다. 게다가 이미지 파일은 본질적으로 데이터베이스에 적합하지 않고, 파일 시스템이 더 자연스러운 선택입니다. 이것은 책을 책장에 놓는 것이지, 노트북에 쑤셔 넣는 것이 아니라는 똑같은 이치입니다.

결정 2: 사용자 정의 프로토콜 hagiimag://

HTTP URL과의 충돌을 피하면서 참조 의미를 더 명확하게 하기 위해, 우리는 사용자 정의 이미지 참조 프로토콜을 설계했습니다:

hagiimag://session-abc123/20260301-143022-a1b2c3d4

이 프로토콜의 형식은 hagiimag://<sessionId>/<imageId>로, 의미가 명확하고 파싱과 라우팅이 용이합니다. 이 형식을 보면 개발자는 즉시 이것이 이미지 참조라는 것을 이해할 수 있으며, 일반 URL이 아닙니다. 이런 설계의 세심한 배려는 때때로 꽤 유용합니다.

결정 3: 프론트엔드 미리보기와 AI 접근 분리

구현 과정에서 우리는 프론트엔드와 AI의 이미지 접근 요구가 다르다는 것을 발견했습니다: 프론트엔드는 HTTP API를 통해 미리보기를 해야 하고, AI는 로컬 파일 경로를 직접 읽어야 합니다. 따라서 우리는 분리된 접근 방식을 설계했습니다:

  • 프론트엔드는 /api/Images/{sessionId}/{imageId}/content를 사용하여 미리보기
  • AI는 서버 측에서 파싱한 로컬 파일 경로 사용

이렇게 하면 보안(서버 경로 노출 방지)과 사용성(브라우저에서 직접 접근 가능)을 모두 보장합니다. 보안과 사용성这两件事, 항상 균형이 필요하죠.

결정 4: 즉시 업로드 전략

또 다른 주요 결정은 업로드 시점입니다. 우리는 사용자가 이미지를 선택하거나 붙여넹을 때 즉시 업로드를 트리거하고, 메시지를 보낼 때 이미 성공적으로 업로드된 이미지만 참조하기로 선택했습니다.

이렇게 하면 오류 처리가 앞단계로 이동하여 메시지 전송 API가 복잡해지는 것을 방지하고, JSON 계약의 간결성을 유지할 수 있습니다. 사용자는 전송 전에 이미지 업로드 성공 여부를 알 수 있어 경험도 더 좋습니다. 이런 “미리 준비” 설계 사고는 많은 경우에 적용될 수 있습니다.

해결책

아키텍처 설계

위의 결정을 바탕으로 우리는 다음과 같은 전체 아키텍처를 설계했습니다:

프론트엔드 레이어
├── ConversationInputArea ◄─────── useImageAttachmentManager
│ │ │
│ ├── 파일 선택 ├── 첨부 상태 관리
│ ├── 클립보드 붙여넣기 ├── 업로드/재시도/삭제
│ └── 첨부 미리보기 └── 이미지 참조 생성
│
서비스 레이어
├── ImageUploadService
│ ├── uploadImage() ◄─────── ImagesController
│ ├── deleteImage() │
│ ├── parseHagiImageUrl() ◄─────── 프로토콜 링크 파싱
│ └── buildPreviewUrl() │
│
백엔드 레이어
├── ImagesController ◄─────── ImagesDomainService
│ │ │
│ ├── POST /upload ├── 파일 검증
│ ├── GET /{sessionId}/{imageId} ├── 이미지 저장
│ ├── DELETE ├── 이미지 압축
│ └── GET /content └── 참조 파싱
│
AI 실행 레이어
├── ImageContentBlock ◄─────── StructuredMessageDomainService
│ │ │
│ ├── 멀티모달 실행기 ├── 이미지 블록 파싱
│ └── 텍스트 실행기 다운그레이드 └── 경로 프롬프트 생성

이 아키텍처는 프론트엔드에서 AI까지의 완전한 데이터 흐름을 명확하게 보여줍니다. 각 레이어는 명확한 책임을 가지고 있으며, 표준 인터페이스를 통해 상호작용합니다. 좋은 아키텍처란 이런 것이죠. 각자의 역할에 충실하고, 서로 방해하지 않으며, 원활하게 소통합니다.

핵심 프로세스

이미지 업로드 프로세스:

  1. 사용자가 파일 선택 또는 클립보드 붙여넣기로 이미지 선택
  2. 프론트엔드에서 파일 유형과 크기 검증(JPEG/PNG/WEBP/GIF 지원, 단일 파일 10MB)
  3. 업로드 API 호출, 이미지를 /images/{sessionId}/ 디렉토리에 저장
  4. API가 hagiimag:// 참조와 미리보기 URL 반환
  5. 프론트엔드에서 첨부 바에 미리보기 썸네일 표시, 사용자가 전송 전에 미리볼 수 있음

AI 인식 프로세스:

  1. 사용자가 이미지 참조를 포함한 메시지 전송
  2. 백엔드에서 hagiimag:// 프로토콜 링크를 파싱하여 sessionId와 imageId 추출
  3. 이미지 참조를 ImageContentBlock으로 매핑
  4. 실행기 능력에 따라 처리 방식 선택:
    • 멀티모달 실행기: 구조화된 이미지 입력 전달
    • 텍스트 실행기: 이미지 경로 프롬프트로 다운그레이드

이렇게 하면 완전한闭环이 완성됩니다: 사용자가 이미지 업로드 → AI가 이미지 인식 → AI가 분석 결과 반환. 이런 프로세스의 원활함은 종종 사용자에게 더 나은 경험을 가져다줍니다.

실천

프론트엔드 구현

프론트엔드에서 우리는 이미지 첨부 상태를 관리하기 위해 전용 Hook을 제공합니다:

import { useImageAttachmentManager } from '@/hooks/useImageAttachmentManager';
function ChatInput() {
const {
attachments,
uploadedImages,
hasBlockingAttachments,
isUploading,
selectFiles,
removeAttachment,
clearAttachments,
} = useImageAttachmentManager({
ownerId: sessionId,
mapUploadedImage: (response) => response,
uploadOptions: { compress: false },
});
const handleFileSelect = (files: File[]) => {
selectFiles(files);
};
const handlePaste = (e: ClipboardEvent) => {
const files = Array.from(e.clipboardData?.files || [])
.filter(f => f.type.startsWith('image/'));
if (files.length > 0) {
handleFileSelect(files);
}
};
return (
<div>
{/* 첨부 바 */}
{attachments.map(att => (
<AttachmentItem
key={att.localId}
file={att.file}
status={att.status}
onRemove={() => removeAttachment(att.localId)}
/>
))}
{/* 입력창 */}
<textarea onPaste={handlePaste} />
{/* 업로드 버튼 */}
<button onClick={() => fileInputRef.current?.click()}>
이미지 업로드
</button>
</div>
);
}

이 Hook은 업로드 상태 추적, 실패 재시도, 첨부 삭제 등 모든 첨부 관리 로직을 캡슐화합니다. 사용하기 매우 간단하며, 몇 가지 메서드만 호출하면 전체 프로세스를 완료할 수 있습니다. 좋은 API 설계란 이런 것입니다. 간단하고 사용하기 쉬우면서도 유연성을 잃지 않습니다.

사용자 정의 프로토콜 파싱:

// 사용자 정의 프로토콜에서 sessionId와 imageId 추출
const parsed = parseHagiImageUrl("hagiimag://session-abc123/20260301-143022-uuid");
// 반환: { sessionId: "session-abc123", imageId: "20260301-143022-uuid" }
// 미리보기 URL 구성
const previewUrl = buildPreviewUrl(parsed.sessionId, parsed.imageId);
// 반환: "/api/Images/session-abc123/20260301-143022-uuid/content"

이 두 가지 유틸리티 함수를 통해 프론트엔드는 hagiimag:// 프로토콜과 HTTP URL 간에 쉽게 변환할 수 있습니다. 이런 변환 로직이 잘 캡슐화되어 있으면 사용하기 훨씬 편리합니다.

백엔드 구현

백엔드는 ASP.NET Core로 구현되었으며, 핵심은 ImagesController와 ImagesDomainService입니다:

[HttpPost("upload")]
[RequestSizeLimit(50 * 1024 * 1024)]
public async Task<ActionResult<ImageUploadResponseDto>> Upload(
[FromForm] UploadImageFormRequest input)
{
// 1. 요청 검증
if (file == null || file.Length == 0)
throw new UserFriendlyException("No file provided");
// 2. 파일 유형과 크기 검증
var (isValid, errorMessage) = _imagesDomainService.ValidateImage(
file.FileName, file.ContentType, file.Length);
if (!isValid)
throw new UserFriendlyException(errorMessage);
// 3. 파일 시스템에 저장
await using var stream = file.OpenReadStream();
var result = await _imagesDomainService.UploadImageAsync(
stream,
sessionId,
file.FileName,
file.ContentType,
CurrentUserId,
compress: input.Compress);
// 4. 결과 반환
return Ok(result);
}

이 구현은 전형적인 Web API 개발 패턴을 따릅니다: 검증, 처리, 반환. 주의할 점은 악의적인 대용량 파일 업로드를 방지하기 위해 50MB 요청 크기 제한을 설정했습니다. 네트워크 세계에서는 조심하는 것이 항상 좋습니다.

주의 사항

구현 과정에서 몇 가지 세부 사항에 특히 주의해야 합니다:

권한 검증: 이미지 접근은 반드시 사용자 신원을 검증해야 하며, 자신의 세션 이미지만 접근할 수 있도록 해야 합니다. 이것은 기본적인 보안 요구사항으로 생략할 수 없습니다. 보안이란 만에 하나를 대비하는 것입니다.

경로 보안: sessionId와 imageId를 엄격하게 검증하여 경로 순회 공격을 방지해야 합니다. 예를 들어 ../를 포함하는 경로를 거부하여 사용자가 시스템의 임의 파일에 접근하는 것을 방지해야 합니다. 이런 경계 조건을 잘 처리해야 시스템이 더 견고해집니다.

파일 정리: 세션 삭제 시 연결된 이미지도 동시에 정리해야 고아 파일이 쌓이는 것을 방지할 수 있습니다. 장기간 실행 후 이러한 파일들이 많은 디스크 공간을 차지할 수 있습니다. 적시에 정리하는 것도 좋은 습관입니다.

압축 전략: 스크린샷 유형의 파일 이름(예: screenshot.png)에 대해 자동으로 압축을 활성화하여 공간을 절약합니다. 이 전략은 실제 요구에 따라 조정할 수 있습니다. 저장 공간은 조금이라도 아끼는 게 좋습니다.

다운그레이드 처리: 멀티모달을 지원하지 않는 실행기는 반드시 이미지 경로 프롬프트를 받아야 하며, 이미지 정보를 조용히丢弃해서는 안 됩니다. 이것은 중요합니다. 그렇지 않으면 사용자가 AI가 자신의 이미지를 무시했다고 생각할 수 있습니다. 사용자 경험은 디테일이 좌우합니다.

상태 관리: 업로드 중인 첨부는 메시지 전송을 차단하고, 실패한 첨부는 재시도나 삭제를 허용합니다. 이 설계는 사용자 경험의 연속성을 보장합니다. 상태 관리가 명확하면 사용자가 혼란스러워하지 않습니다.

요약

이 완전한 이미지 업로드와 인식 솔루션을 통해 HagiCode는 사용자 입력부터 AI 인식까지의 완전한闭环을 실현했습니다. 전체 솔루션의 핵심 하이라이트는 다음과 같습니다:

  • 사용자 정의 hagiimag:// 프로토콜로 이미지 참조의 표준화 실현
  • 파일 시스템 저장으로 구현 단순화 및 성능 향상
  • 프론트엔드 미리보기와 AI 접근 분리로 보안과 사용성을 모두 고려
  • 즉시 업로드 전략으로 사용자 경험 최적화
  • 멀티모달과 텍스트 다운그레이드의 호환 설계로 유연성 보장

이 솔루션은 HagiCode에서 안정적으로 실행되고 있으며, 사용자 피드백도 좋습니다. 비슷한 기능을 구현 중이라면 이러한 경험이 도움이 되기를 바랍니다.

사실 기술 솔루션은 절대적인 옳고 그름이 없고, 적합한지 아닌지만 있을 뿐입니다. 자신의 프로젝트에 맞는 길을 찾는 것이 가장 중요합니다.

참고 자료

开始使用 HagiCode

一次安装,几分钟上手

HagiCode for Windows 在 Microsoft Store 免费提供。打开商店即可安装并保持更新;也可以先对比各版本与定价,再决定从哪个渠道开始。