채팅 파일 첨부 (이미지 전달, 한도 설정)
원천 · framework/evomind/web/workflow-project-evo-service기준 · 릴리스 v0.12.0커밋 · 2e32c6cc파일 · docs/operations/chat-attachments.md기준선 · 2026-11-12
웹 채팅 입력창에서 파일을 붙여 질문할 수 있다. 모든 형식을 받는다. 파일은 대화에 묶여 저장되고, 대화를 지우면 함께 사라진다.
쓰는 법(화면)
섹션 제목: “쓰는 법(화면)”- 입력창 왼쪽 아래 + 버튼으로 파일을 고른다(여러 개 가능). 입력창에 끌어 놓거나 붙여 넣어도 된다.
- 고른 파일은 바로 올라가고 입력창 위에 이름·크기 칩으로 보인다. 올리는 중에는 보내지 않는다. 올리지 못한 파일은 칩에 이유가 보이고 화면 읽기 프로그램에 알린다(
role=alert). - 칩의 ✕ 로 보내기 전에 뗄 수 있다(서버에서도 지운다).
- 파일만 있고 글이 없으면 ’첨부한 파일을 확인해 주세요.’를 질문으로 보낸다.
- 보낸 질문 위에 붙인 파일이 보이고, ↓ 버튼으로 원래 이름 그대로 내려받는다. 대화를 다시 열어도 보인다.
- 새 대화 화면에서 파일을 고르면 대화를 먼저 만든다(파일은 대화에 묶여야 하므로).
모델에 들어가는 것
섹션 제목: “모델에 들어가는 것”- 글로 읽히는 파일(UT 로 온전히 읽히고 NUL 문자가 없는 파일 — txt·md·csv·json·소스 코드 등)은 내용이 그 질문 뒤에 붙어 모델에 간다. 파일 하나는 6만 자, 한 번의 요청 전체는 12만 자까지 넣고 넘으면 ’…(이하 생략)’을 붙인다. 한도는 최근 질문의 파일부터 채운다.
- 이미지(png·jpeg·gif·webp): 관리자가 모델을 이미지 입력 지원으로 표시했으면(관리 → 연결 → 모델 보기의 ‘이미지’ 체크) 그 모델에 그림으로 함께 보낸다. 이력의 이미지 중 최근 것부터 4장, 장당 5MB 이하만 보낸다. 글에는 ’그림으로 함께 보냄’이라고 적고, 한도로 못 보낸 이미지는 그렇다고 적는다. 표시하지 않은 모델에는 그림을 보내지 않고 ’이 모델은 그림을 볼 수 없어 내용은 넣지 않음’이라고 적는다. 폴백으로 다른 모델을 시도하면 그 모델의 표시로 다시 판단한다.
- 그림인지는 업로드 때 브라우저가 준 형식으로 정한다. 형식이 비었거나
application/octet-stream이면 파일 앞 바이트로 png·jpeg·gif·webp 를 알아보고 그 형식으로 저장한다. 브라우저가 준 다른 형식은 바꾸지 않는다. - 기본은 모두 꺼짐이다. 모델이 실제로 이미지를 받는지는 연결(LiteLLM 등)의 모델 정보로 확인한 뒤 켠다. 받지 못하는 모델에 켜면 모델 호출이 오류가 날 수 있다.
- 자동 선택(auto)은 대화에 이미지가 있으면 이미지 지원 모델을 먼저 시도하고, 실패하면 일반 모델로 넘어간다(그림은 글로 알림). 이미지 지원 모델이 없으면 기존 순서를 쓴다.
- 그림인지는 업로드 때 브라우저가 준 형식으로 정한다. 형식이 비었거나
- PDF: 업로드 때 글을 꺼내 저장하고, 글로 읽히는 파일과 같은 한도로 모델에 넣는다. 저장은 20만 자·앞 2,000쪽까지다. 원본은 임시 파일로 흘려 써서 열므로 큰 PDF 도 서버 메모리에 통째로 올리지 않는다. 암호가 걸렸거나 깨졌거나 글이 없는(스캔) PDF 는 업로드는 되고 “내용은 읽을 수 없는 형식”으로 남는다(OCR 없음). 이 기능 전에 올린 PDF 는 다시 올려야 글이 읽힌다.
- Office 문서(docx·xlsx·pptx): 업로드 때 글을 꺼내 PDF 와 같은 한도로 저장·전달한다. Word 는 문단(표는 칸을 탭, 행을 줄로), Excel 은 시트마다
## 시트: 이름뒤에 행을 탭으로 이은 셀 값(수식은 파일에 저장된 계산값, 없으면 빈칸), PowerPoint 는 보이는 순서대로## 슬라이드 n뒤의 글. 새 라이브러리 없이 zip·XML 을 직접 읽고, 원본은 임시 파일로 연다. 압축 폭탄(풀린 크기 64MB 초과)·외부 엔티티·깨진 파일·암호 걸린 파일은 업로드는 되고 “내용은 읽을 수 없는 형식”으로 남는다. 형식이 비었거나 octet-stream 이면 파일 안 구조로 형식을 정한다. 옛 형식(doc·xls·ppt)·hwp 는 읽지 않는다. - 그 밖의 파일(옛 Office·hwp·압축 파일 등)은 이름·형식·크기만 “내용은 읽을 수 없는 형식”으로 알려 준다.
- 이전 질문에 붙인 파일도 이후 대화에서 계속 모델 입력에 들어간다(대화 이력의 일부).
- 다시 생성은 질문에 붙은 파일을 그대로 쓴다. 질문을 편집하면 새 버전에는 파일이 붙지 않는다(편집 전 버전에는 남는다).
저장·삭제
섹션 제목: “저장·삭제”- 저장 위치는 앱 DB(PostgreSQL) 표
conversation_attachments(V32)다. 파일 내용(BYTEA)과, 글로 읽히는 파일이면 그 글(최대 20만 자)을 함께 둔다. 앱 컨테이너 디스크에는 쓰지 않는다. conversation_id는conversations를ON DELETE CASCADE로 참조한다. 대화 단건 삭제, 모든 대화 삭제(설정), 계정 삭제 모두 대화 행을 지우므로 파일도 함께 지워진다.- 내 대화가 아니면 올리기·목록·내려받기·지우기 모두 404 다(대화 소유 확인).
- 파일 이름·내용은 로그와 감사 기록에 남기지 않는다. 화면 응답(JSON)에도 내용·추출한 글은 넣지 않는다.
- 내려받기는 항상
Content-Disposition: attachment,application/octet-stream,X-Content-Type-Options: nosniff로 보내 올린 HTML·SVG 등이 페이지로 열리지 않게 한다.
API
섹션 제목: “API”| 메서드·경로 | 내용 |
|---|---|
POST /api/conversations/{id}/attachments |
multipart file 1개 올리기 → 201 {id, messageId:null, name, contentType, size, readable, createdAt} |
GET /api/conversations/{id}/attachments |
그 대화의 첨부 목록(보낸 것은 messageId 가 있다) |
GET /api/conversations/{id}/attachments/{aid}/content |
내려받기 |
DELETE /api/conversations/{id}/attachments/{aid} |
보내기 전 파일만 지운다(보낸 파일은 409) |
POST /api/conversations/{id}/chat 의 attachmentIds |
이 질문에 붙일 첨부 id 목록(최대 20개). 이 대화에 올린 파일이 아니면 질문을 저장하기 전에 400 |
한도와 설정
섹션 제목: “한도와 설정”관리자는 관리 → 설정 → 채팅 첨부에서 한도를 MB 로 바꾼다. 저장하면 재시작 없이 다음 업로드부터 적용되고, 감사 기록(SETTING_CHANGE, 대상 attachments)에 남는다. ’기본값으로 되돌리기’는 아래 환경 변수 값으로 돌아간다. 값은 app_settings 의 attachments.max_file_bytes·attachments.max_conversation_bytes(바이트)에 둔다.
| 항목 | 기본값 | 정할 수 있는 범위 |
|---|---|---|
| 파일 하나 | 20MB | 1MB ~ 서버 상한(multipart 파일 한도 − 1MB, 기본 100MB) |
| 대화 하나의 합계 | 100MB | 파일 하나 한도 이상 ~ 10GB |
| 환경 변수 | 기본값 | 뜻 |
|---|---|---|
ATTACHMENT_MAX_FILE_BYTES |
20971520 (20 MiB) | 화면에서 정하지 않았을 때의 파일 하나 한도 |
ATTACHMENT_MAX_CONVERSATION_BYTES |
104857600 (100 MiB) | 화면에서 정하지 않았을 때의 대화 합계 한도 |
MULTIPART_MAX_FILE_SIZE / MULTIPART_MAX_REQUEST_SIZE |
101MB / 102MB | 서버가 받는 multipart 한도. 파일 하나 한도의 서버 상한은 파일 한도 − 1MB. 이 값을 낮춰 저장된 한도가 상한을 넘으면 상한을 쓴다 |
- 화면은
GET /api/attachments/limits로 현재 한도를 받아 파일을 고를 때마다 확인한다. 넘는 파일은 올리기 전에 막고 ’{n}MB 를 넘는 파일은 올릴 수 없습니다’를 보인다. 서버가 413 을 주면(대화 합계 초과 등) 두 한도를 함께 알린다. - 관리 API:
GET /api/admin/settings/attachments(설정 조회 권한) — 현재값·기본값·범위·custom,PUT(admin){maxFileBytes, maxConversationBytes}— null 은 기본값으로, 범위 밖은 400. - 앞단 프록시(TLS 종단 nginx 등)의 요청 본문 한도가 정한 한도보다 작으면 큰 파일은 프록시에서 413 이 된다. 한도를 올리면 API 앞 프록시의
client_max_body_size를 그보다 크게 둔다. 개발서버 두 인스턴스(일반·조직)의 앞단 프록시는 30·40·60·80·99MB 업로드를 모두 통과시켰다(2026-11-03) — 상한 100MB 까지 써도 된다. - 시험 메모: evo-emulator 런타임 0.5.0-dev.213 은 페이지 안에서 80MB 이상을 올리면 서버 프로세스가 죽는다(evo-emulator 60d08816). 큰 파일 확인은 로그인만 에뮬레이터로 하고 업로드는 시험 클라이언트에서 보낸다.
- 큰 파일도 앱 메모리에 통째로 올리지 않는다. 올리기는 multipart 임시 파일을 한 번 읽어 그림·글을 판별하고(글은 앞 20만 자만 모음) 다시 열어 DB 에 흘려 넣는다. 내려받기는 4MiB 조각으로 읽어 바로 응답에 쓴다. 힙 256MB 앱에서 99MB 파일 3개 동시 올리기·받기가 성공한다(바꾸기 전에는 99MB 1개로 OOM). multipart 임시 파일은 앱 컨테이너 임시 폴더에 잠시 생기므로 그 디스크 여유를 본다.
Evo에게 묻기문서 근거 안에서만 답합니다
답변은 문서를 근거로 만들지만 틀릴 수 있습니다. 출처 문서를 함께 확인하세요.