# FlameAI API Manual > AI 에이전트(코드 작성 도구)라면 먼저 [agent.md](https://flameai.im/agent.md)를 읽으세요 — 설정값, 호출 순서, > 에러 대응, 최소 코드가 정리되어 있습니다. 이 문서는 전체 명세입니다. ## 목차 ``` OVERVIEW Quickstart 구성 및 연결 Inference Structure Available models MANAGE ENDPOINTS HTTP Endpoint 열기 FlameBox 추가 Model 다운로드 추론 인스턴스(Cortex) 생성 INFERENCE API Cortex 구조와 특성 / Stateful 동작 방식 / 점유(Occupancy) 모델 / 일반적인 사용 흐름 나의 API 호출 주소 / 인증 (Bearer Token) 공통 요청 규칙 / 공통 응답 규칙 / 에러 코드 엔드포인트 — 대화 요청 / 상태 보기 / 추론 중지 / 모델 언로드 / 컨텍스트 초기화 / 대화 세션 전환 / 선택지 판정 / 이미지 생성 / Cortex 파일 / 토큰 세기 curl 테스트 예시 다른 API와의 차이 WORKSPACE API WORKSPACE 내 Cortex 목록 curl 테스트 예시 ``` --- ## OVERVIEW ### Quickstart FlameAI는 **FlameBox(설치형 로컬 추론 머신)**와 **API Gateway**를 조합해 동작합니다. 아래 6단계를 순서대로 따라가면 첫 API 호출까지 끝낼 수 있습니다. 1. FlameAI 계정을 만듭니다 ([회원가입](https://flameai.im/signup) 또는 Google 로그인). 2. 운영체제에 맞는 방법으로 FlameBox를 설치합니다(자세한 설치 옵션은 [다운로드 페이지](https://flameai.im/download) 참고). **Windows** — PowerShell에서 실행 (또는 설치파일 .exe를 내려받아 실행) ``` irm https://flameai.im/install.ps1 | iex ``` **Linux** — 터미널에서 실행 ``` curl -fsSL https://flameai.im/install.sh | sh ``` 3. 인증 화면으로 가는 경로는 운영체제에 따라 다릅니다. - **Windows**: FlameBox를 처음 실행하면 인증 화면이 바로 열립니다. - **Linux**: 터미널에서 `flamebox up`을 처음 실행하면 `https://flameai.im/registration/{token}` 형태의 인증 URL이 출력됩니다. 그 화면(또는 URL)에 접속해 로그인 후 "연결"을 누릅니다 — Linux는 여기서 `flamebox up`을 한 번 더 실행해야 등록이 완료되어 내 계정에 등록되고 My AI Console에 나타납니다. 4. 이제 FlameBox가 홈페이지의 **My AI Console**에 연결되었습니다. FlameBox(`/mypage/flamebox`)를 클릭해 상세 페이지로 들어간 뒤, **Models** 탭에서 사용할 AI 모델을 내려받습니다. 5. 같은 상세 페이지에서 **Cortex** 탭을 클릭하고 "Cortex 생성" 버튼을 눌러 추론 인스턴스(Cortex)를 생성합니다. 6. My AI Console(`/mypage/settings`)에서 **API 경로**/Secret Key를 확인하고, [나의 API 호출 주소](#나의-api-호출-주소) 규칙대로 방금 만든 Cortex를 호출합니다. ### 구성 및 연결 외부 클라이언트의 요청이 실제 추론 결과로 돌아오기까지 거치는 경로는 다음과 같습니다. ``` [외부 클라이언트] │ HTTPS + Bearer(Secret Key) ▼ [API Gateway] api-N.flameai.im ← 로드밸런싱용 물리 노드 풀, API 경로로 계정 식별 │ FlameBox로 연결 하여 요청 전달 ▼ [FlameBox] 사용자 PC/서버에 설치된 추론 머신(Inference Machine) │ GPU/CPU 자원으로 다수의 Cortex(추론 인스턴스)를 관리 ▼ [Cortex] FlameBox 안에서 실행 중인 추론 인스턴스(Inference Instance, 모델 1개 상주) ``` - 하나의 계정에 여러 FlameBox를 등록할 수 있습니다 — 각 Cortex는 자신이 생성된 FlameBox에만 속하므로, 그 FlameBox가 온라인 상태일 때만 호출할 수 있습니다. - FlameBox 연결이 끊기면 그 안의 Cortex 호출은 모두 실패합니다 — 다시 연결되면 자동으로 호출 가능한 상태로 돌아옵니다. ### Inference Structure **FlameBox**와 **Cortex**는 서로 다른 층의 개념입니다 — FlameBox는 추론 머신(Inference Machine), Cortex는 그 머신 위에서 실행되는 추론 인스턴스(Inference Instance)입니다. | 구성요소 | 역할 | |---|---| | **FlameBox** (Inference Machine) | PC/서버에 설치되는 프로그램 자체. FlameAI 서버와의 상시 연결 유지, 모델 다운로드/보관, 그 안에서 여러 Cortex를 띄우고 관리하는 역할을 합니다. | | **Cortex** (Inference Instance) | FlameBox 하나 위에서 실행되는 추론 인스턴스. 모델 파일을 GPU/CPU 메모리에 로드한 상태로 대기하며, API 호출을 받아 실제 추론을 수행합니다. 자세한 설명은 [Cortex 구조와 특성](#cortex-구조와-특성) 참고. | > FlameBox 하나에 여러 Cortex를 만들 수 있습니다 — 예를 들어 코딩용 모델 Cortex 하나, 일반 대화용 모델 Cortex > 하나를 같은 FlameBox 위에서 동시에 운용할 수 있습니다(단, GPU/CPU/메모리 자원은 공유되므로 동시에 띄울 수 > 있는 개수는 하드웨어 사양에 따라 제한됩니다). ### Available models FlameBox가 다운로드해 Cortex로 띄울 수 있는 모델 목록과 각 모델의 사양은 [모델 목록 페이지](https://flameai.im/models)에서 확인할 수 있습니다. > FlameBox 상세 페이지의 "모델 다운로드" 모달은 그 FlameBox가 실제로 다운로드 가능한 모델 카탈로그를 > 실시간으로 보여줍니다 — 이 문서의 목록과 화면에 보이는 목록이 다르다면 화면 쪽이 최신입니다. --- ## MANAGE ENDPOINTS ### HTTP Endpoint 열기 FlameAI 에 가입하면 **API 경로**와 Secret Key가 계정당 자동으로 하나 발급됩니다 — 별도로 "발급 신청"을 할 필요가 없습니다. 1. My AI Console의 설정(`/mypage/settings`) 메뉴로 들어갑니다. 2. Endpoint 박스에서 `API 경로`, `base url`, `secret key` 3가지를 확인합니다. Secret Key는 기본적으로 마스킹되어 있으며 "보기" 토글로 확인할 수 있습니다. 3. base url + API 경로를 이어붙이면 [나의 API 호출 주소](#나의-api-호출-주소)에서 쓰는 베이스 엔드포인트가 됩니다. > ⚠ Secret Key가 유출됐다고 의심되면 같은 화면의 "변경" 버튼으로 즉시 재발급할 수 있습니다. 단, **API 경로**는 > 그대로 유지되고 기존 Secret Key로 호출하던 클라이언트는 재발급 즉시 인증에 실패하니 연동 중인 모든 > 클라이언트를 함께 갱신해야 합니다. ### FlameBox 추가 운영체제에 맞는 방법으로 FlameBox를 설치합니다(자세한 설치 옵션은 [다운로드 페이지](https://flameai.im/download) 참고). 새 머신에 설치 후 아래 인증 과정을 거치면 그 머신의 FlameBox가 자동으로 계정에 추가됩니다 — 이 과정을 반복해 여러 대의 FlameBox를 등록할 수 있습니다. **Windows** — PowerShell에서 실행 (또는 설치파일 .exe를 내려받아 실행) ``` irm https://flameai.im/install.ps1 | iex ``` **Linux** — 터미널에서 실행 ``` curl -fsSL https://flameai.im/install.sh | sh ``` 1. 인증 화면으로 가는 경로는 운영체제에 따라 다릅니다 — Windows는 FlameBox를 처음 실행하면 인증 화면이 바로 열리고, Linux는 터미널에서 `flamebox up`을 처음 실행하면 `https://flameai.im/registration/{token}` 형태의 인증 URL이 출력됩니다. 2. 그 화면(또는 URL)에서 Google 로그인을 누르거나, 이메일로 로그인한 뒤 "인증 및 연결" 버튼을 누릅니다 — 계정이 없다면 그 자리의 "회원가입" 링크로 바로 가입한 뒤 이어서 연결할 수 있습니다. 3. Linux는 여기서 `flamebox up`을 한 번 더 실행해야 등록이 완료됩니다. 등록이 끝나면 해당 FlameBox가 로그인한 계정에 등록되고, My AI Console의 FlameBox 목록(`/mypage/flamebox`)에서 바로 확인할 수 있습니다. > 계정당 최대 **5개**까지 FlameBox를 등록할 수 있습니다 — 그 이상 필요하시면 [문의하기](https://flameai.im/contact)로 > 연락해 주세요. ### Model 다운로드 모델은 계정 단위가 아니라 **FlameBox 단위**로 다운로드됩니다 — 같은 계정이라도 FlameBox마다 보유한 모델이 다를 수 있습니다. 1. My AI Console의 FlameBox 목록(`/mypage/flamebox`)에서 대상 FlameBox 상세 페이지로 들어갑니다. 2. **Models** 탭을 클릭합니다. 3. "다운로드된 Models" 영역의 "모델 다운로드" 버튼을 눌러 카탈로그에서 원하는 모델을 선택합니다. 4. 다운로드가 진행되는 동안 상태가 실시간(다운로드 중 → 검증 중 → 다운로드 완료)으로 갱신됩니다. 진행 중에도 일시정지/재개/취소가 가능합니다. > ⚠ 모델 다운로드는 FlameBox가 **온라인 상태로 연결되어 있어야** 진행할 수 있습니다. > 다운로드가 완료된 모델만 [Cortex 생성](#추론-인스턴스cortex-생성) 시 선택할 수 있습니다. ### 추론 인스턴스(Cortex) 생성 다운로드된 모델을 실제로 호출 가능한 상태로 띄우려면 Cortex를 생성해야 합니다. 1. FlameBox 상세 페이지에서 "Cortex 생성" 버튼을 누릅니다. 2. 다운로드 완료된 모델 중 하나를 선택하고, Cortex 이름을 지정합니다. 3. 생성이 끝나면 발급된 `cortexId`가 해당 FlameBox 상세 페이지(`/mypage/flamebox`)의 Cortex 탭에 나타납니다 — 이 값을 [API 호출](#나의-api-호출-주소)의 `{cortexId}` 자리에 사용합니다. > Cortex 탭에서 로드/추론중지/컨텍스트리셋/언로드/삭제 같은 제어 명령을 실행할 수 있습니다. --- ## INFERENCE API ### Cortex 구조와 특성 Cortex는 LLM 모델을 GPU/CPU 메모리에 로드하여 **인스턴스화한 추론 실행 단위**입니다. 모델 파일(.gguf)을 매 요청마다 다시 불러오지 않고 메모리에 상주시킨 상태로 대기하며, 요청이 들어오면 즉시 추론을 수행합니다. 이 구조 덕분에 다음과 같은 이점이 있습니다. - **즉시 응답** — 모델이 이미 메모리에 로드되어 있어, 요청 시점에 모델을 새로 불러오는 지연(콜드 스타트) 없이 바로 추론을 시작합니다. - **효율적인 멀티턴 대화** — KV Cache가 유지되므로 전체 대화 히스토리를 매번 다시 보낼 필요 없이 마지막 메시지만 전송해도 대화가 이어집니다(자세한 내용은 아래 "Stateful 동작 방식" 참고). - **대화가 길어져도 일정한 추론 속도** — Stateless 방식은 대화가 쌓일수록 매번 전체 히스토리를 다시 처리해야 해서 추론 시간도 함께 길어지지만, Cortex는 이전 턴의 KV Cache를 그대로 재사용해 새 메시지만큼만 처리하면 되므로 대화 길이와 무관하게 추론 속도가 일정하게 유지됩니다. - **용도별 독립 운용** — 하나의 FlameBox 안에 여러 Cortex를 동시에 띄워, 코딩용/일반 대화용처럼 목적에 맞는 모델을 나눠 운용할 수 있습니다. 다만 모델이 메모리에 상주하며 대화 컨텍스트를 유지하는 구조이다 보니, 다음과 같은 제약도 함께 따릅니다. - **Cortex별 단일 스레드 추론** — 같은 Cortex 안에서는 GPU/CPU 자원을 독점적으로 사용하므로 동시 추론이 불가능합니다. 서로 다른 Cortex끼리는 자원이 허용하는 한 동시에 추론할 수 있습니다. - **KV Cache 공유 불가** — 대화 컨텍스트는 하나의 점유자에게만 귀속됩니다. - **단일 점유 리소스** — 한 번에 하나의 클라이언트만 사용할 수 있습니다. ``` # 하나의 FlameBox에 여러 Cortex를 생성해 분산 운용할 수 있습니다 [Cortex Client] │ REST API ▼ [FlameBox] ├─ Cortex (7f3c9a12-4e6b-4d8f-9a21-6b5e8c1f2d3a) ← 모델 로드, KV Cache, 대화 컨텍스트 보유 ├─ Cortex (a13d5e67-8f2b-4c91-b3d4-1e9f7a6c5b02) └─ Cortex (c48e2f91-3a7d-4b56-9c1e-8d2f6a4b7e03) ``` ### Stateful 동작 방식 Cortex는 내부에 **KV Cache**를 보유하며 Stateful 방식으로 동작합니다. 추론 완료 후에도 대화의 KV Cache가 메모리에 유지되므로, 다음 요청 시 이전 대화 전체를 다시 전송할 필요 없이 **마지막 메시지만 전송해도 컨텍스트가 이어집니다.** ``` # Stateless 방식 (일반적인 OpenAI 호환 서버) 1번 요청: [user: "안녕"] 2번 요청: [user: "안녕", assistant: "안녕하세요", user: "이름이 뭐야?"] ← 전체 히스토리 재전송 # Stateful 방식 — FlameBox Cortex 1번 요청: [user: "안녕"] → KV Cache 유지 2번 요청: [user: "이름이 뭐야?"] → 마지막 메시지만 전송 ``` > 점유자가 바뀌어도 KV Cache는 자동으로 초기화되지 않습니다 — 컨텍스트를 비우는 `/reset`을 직접 > 호출해야 초기화됩니다. 새로 점유한 클라이언트가 이전과 무관한 새 대화를 시작하려면, 먼저 > `/reset`으로 컨텍스트를 비운 뒤 대화를 시작하는 것을 권장합니다. ### 점유(Occupancy) 모델 클라이언트는 `conversationId`를 통해 Cortex를 선점하며, 점유 중인 Cortex에는 동일한 `conversationId`를 가진 클라이언트만 요청할 수 있습니다. - **적은 리소스로 멀티 대화 지원** — Cortex 하나만으로도 여러 `conversationId`를 필요에 따라 전환해가며 운용할 수 있어, 대화마다 별도 Cortex를 띄우지 않아도 됩니다(단, 전환 시 컨텍스트 재구축은 클라이언트가 대화 히스토리를 다시 전송하는 방식으로 직접 구현해야 합니다). - **Cortex Pool 라우팅 키로 활용** — `conversationId`를 애플리케이션의 Cortex 배정 기준으로 사용하면, 여러 Cortex를 운용할 때 "이 대화가 어느 Cortex에 있는지"를 추적/관리하는 키로 쓸 수 있습니다. - **컨텍스트 오염 방지** — 점유 중인 Cortex에 다른 `conversationId`로 요청이 들어오면 즉시 `cortex_occupied` 에러로 차단되어, 서로 다른 대화의 메시지가 섞이는 문제를 막습니다. **점유 충돌 시 클라이언트 대응 전략** | 전략 | 설명 | |---|---| | 재시도 대기 | 현재 점유자가 해제할 때까지 기다린 후 재점유 | | 강제 재점유 | `/switch-conversation`으로 점유를 빼앗은 후 대화 히스토리로 컨텍스트 재구축 | | 포기 | 에러를 사용자에게 전달하고 다른 Cortex 탐색 | ### 일반적인 사용 흐름 1. `GET /{my_base_path}/cortexes` — 사용 가능한 Cortex 확인 (아래 WORKSPACE API "내 Cortex 목록" 참고) 2. `POST /{my_base_path}/cortex/{cortexId}/switch-conversation` — conversationId로 Cortex 선점 3. `POST /{my_base_path}/cortex/{cortexId}/reset` — 새 대화이므로 컨텍스트를 비웁니다. 4. `POST /{my_base_path}/cortex/{cortexId}/messages` — 대화 히스토리 전체 + 새 메시지 전송 (SSE Stream) 5. `GET /{my_base_path}/cortex/{cortexId}/status` — 컨텍스트 사용량 모니터링 6. `POST /{my_base_path}/cortex/{cortexId}/stop` — 필요 시 추론 중단 7. `POST /{my_base_path}/cortex/{cortexId}/switch-conversation` — 다른 conversationId로 전환하거나 `""`로 점유 해제 ### 나의 API 호출 주소 FlameAI 에 가입하게 되면 나만의 API 호출을 위한 **API 경로**가 발급됩니다. 이 경로를 아래 형식의 URL에 넣어 API Gateway를 통해 호출합니다. ``` https://api-N.flameai.im/{my_base_path}/cortex/{cortexId}/{action} 예시: https://api-3.flameai.im/Nx7bQ2mZ/cortex/7f3c9a12-4e6b-4d8f-9a21-6b5e8c1f2d3a/messages ``` - **api-N**: 로드밸런싱용 물리 노드 식별자이며 API Gateway 노드의 이름입니다. - **my_base_path**(API 경로): My AI Console(`/mypage/settings`)에서 확인 가능한 나만의 고유 경로입니다. - **인증**: `Authorization: Bearer {Secret Key}` 헤더 필수 (Secret Key도 `/mypage/settings`에서 확인) - **버전**: `X-API-Version` 헤더로 지정(생략 시 기본값 `1`) — URL에는 버전을 넣지 않습니다. ### 인증 (Bearer Token) API Gateway를 통해 호출할 때는 모든 요청에 Bearer Token을 포함해야 합니다. ``` POST /Nx7bQ2mZ/cortex/7f3c9a12-4e6b-4d8f-9a21-6b5e8c1f2d3a/messages HTTP/1.1 Host: api-3.flameai.im Content-Type: application/json Authorization: Bearer fai_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx X-API-Version: 1 ``` > Secret Key는 FlameAI 회원 가입 후 My AI Console(`/mypage/settings`)에서 확인/재발급할 수 있습니다. ### 공통 요청 규칙 모든 요청에 다음 규칙이 공통으로 적용됩니다. - Body가 있는 요청(`POST` 등)은 `Content-Type: application/json` 헤더가 필수입니다. - Body는 UTF-8로 인코딩된 JSON이어야 합니다. - `X-API-Version` 헤더로 API 버전을 지정합니다(선택, 생략 시 기본값 `1`) — URL에는 버전을 넣지 않습니다. - 인증(Bearer Token) 방식은 위 "인증 (Bearer Token)" 섹션을 참고하세요. ``` Content-Type: application/json X-API-Version: 1 (선택, 생략 시 기본값 1) ``` ### 공통 응답 규칙 - 성공 응답은 HTTP 200과 JSON 본문을 반환합니다. - 에러 응답은 HTTP 에러 상태 코드와 함께, 아래 "에러 응답 포맷"의 JSON을 반환합니다. #### 성공 응답 - 일반(비스트리밍) 응답은 `Content-Type: application/json`으로 반환합니다. - SSE Stream 응답은 `Content-Type: text/event-stream`으로 반환합니다. - HTTP status가 `200`이면 정상 응답입니다. 응답이 JSON 포맷일 때는 JSON에 `error` 필드가 없으면 정상 응답으로 판단할 수 있습니다. **성공 응답 포맷 (비스트림)** ``` Content-Type: application/json { "cortexId": "7f3c9a12-4e6b-4d8f-9a21-6b5e8c1f2d3a", "conversationId": "chat-abc123", "message": { "id": "msg-1714000000001", "role": "assistant", "content": [ { "type": "text", "text": "안녕하세요" } ], "usage": { "input_tokens": 12, "output_tokens": 2, "elapsed_ms": 80 } } } ``` **성공 응답 포맷 (스트림)** ``` Content-Type: text/event-stream event: message.completed data: {"cortexId":"7f3c9a12-4e6b-4d8f-9a21-6b5e8c1f2d3a","conversationId":"chat-abc123","message":{"id":"msg-1714000000001","role":"assistant","content":[{"type":"text","text":"안녕하세요"}],"usage":{"input_tokens":12,"output_tokens":2,"elapsed_ms":80}}} data: [DONE] ``` > 스트림 응답의 전체 이벤트 시퀀스(`message.created`/`message.delta`/`message.block_done`/ > `message.completed`)는 아래 "대화 요청" 엔드포인트 문서를 참고하세요. #### 에러 응답 - 에러 응답은 항상 `Content-Type: application/json`으로 단일 패킷으로 반환합니다. - 응답 JSON에는 `error` 필드가 항상 포함되어 있습니다. - HTTP status는 항상 `200`이 아닌 값으로 반환됩니다. **에러 응답 포맷** ```json { "error": { "code": "에러코드", "message": "상세 메시지", "httpStatus": 404 } } ``` | HTTP | 상황 | |---|---| | 400 | bad request — 파라미터 오류, JSON 파싱 실패 | | 401 | unauthorized — 인증 실패 | | 403 | forbidden — 인증은 되었으나 리소스 접근 권한 없음 | | 404 | not found — URL 또는 리소스를 찾을 수 없음 | | 409 | conflict — 점유 충돌, 추론 중 등 상태 불일치 | | 500 | internal error — 서버 내부 오류 | | 503 | service unavailable — FlameBox가 오프라인이거나 통신할 수 없음 | ### 에러 코드 에러가 발생했다는 사실은 HTTP 상태 코드만으로도 알 수 있지만, 정확히 어떤 상황인지는 응답 Body에 담긴 에러 코드로 구분해야 합니다. 이 에러 코드는 문자열 형태이며, 아래와 같습니다. 에러는 발생 지점에 따라 두 종류로 나뉩니다 — API Gateway 자체에서 즉시 반환되는 에러와, 요청이 FlameBox까지 전달된 뒤 FlameBox가 반환하는 에러입니다. **API Gateway 에러 (FlameBox 도달 전)** | 코드 | HTTP | 상황 | |---|---|---| | `bad_request` | 400 | URL 경로 형식 오류(허용되지 않는 문자 포함), JSON 형식 오류, 요청 본문 50MB 초과 | | `bearer_not_found` | 401 | Authorization 헤더 누락 또는 형식 오류 | | `bearer_invalid` | 401 | API 경로가 존재하지 않거나 Secret Key가 일치하지 않음 | | `cortexbox_offline` | 503 | 대상 FlameBox가 오프라인 상태 | | `flamebox_io_error` | 503 | FlameBox와의 통신 오류 | **FlameBox 에러 (요청이 전달된 뒤 발생)** | 코드 | HTTP | 상황 | |---|---|---| | `bad_request` | 400 | JSON 파싱 실패 또는 파라미터 오류 | | `unsupported_cortex_type` | 400 | Cortex 종류에 맞지 않는 요청 (예: 이미지 생성 Cortex에 `messages`, LLM Cortex에 `imageGenerate`, 판정 전용 Cortex에 `messages`) | | `cortex_not_found` | 404 | 없는 cortexId | | `file_not_found` | 404 | Cortex 파일 저장소에 없는 파일 | | `not_found` | 404 | 엔드포인트를 찾을 수 없음 | | `cortex_occupied` | 409 | 다른 conversationId가 Cortex를 점유 중 | | `cortex_not_running` | 409 | 모델이 로드되지 않은 상태에서 `reset`/`tokenize` 요청 | | `cortex_inferring` | 409 | 해당 Cortex가 추론(생성) 중 | | `internal_error` | 500 | 서버 내부 오류 (모델 로드 실패 포함) | ### 엔드포인트 #### 대화 요청 `POST /{my_base_path}/cortex/{cortexId}/messages` 대화 메시지를 전송하고 AI 응답을 받습니다. SSE 스트리밍과 동기 방식을 모두 지원합니다. > Cortex가 언로드된 상태라면 이 요청을 받는 시점에 자동으로 다시 로드됩니다 — 로드가 끝날 때까지 > 응답이 잠시 지연될 수 있습니다. **Request Body** ```json { "conversationId": "chat-550e8400", "tools": [], "options": { "stream": true, "think": 1, "cortex_reset": false }, "messages": [ { "role": "system", "content": "당신은 유용한 AI 어시스턴트입니다." }, { "role": "user", "content": "3 더하기 3은?" } ] } ``` | 파라미터 | 타입 | 필수 | 설명 | |---|---|---|---| | `conversationId` | string | 예 | 구현자의 APP 내 대화 세션(Chat Session) ID를 그대로 사용해도 됩니다. 최초 대화 시 이 값으로 점유가 자동 발생하며, 이후 요청은 현재 점유 중인 conversationId와 일치해야 함 | | `tools` | array | 아니오 | 모델이 호출할 수 있는 도구(tool) 정의 배열. 실행은 클라이언트가 합니다 — 아래 "Tool 호출" 참고 | | `options.stream` | boolean | 아니오 | `true`면 SSE 스트리밍, `false`면 동기 응답 (기본값: false) | | `options.think` | int | 아니오 | 생각(thinking) 강도 — `0` 즉답, `1` 자동(기본값), `2` 생각 강제. 모델마다 해석이 다릅니다(아래 표). `false`/`true`도 받으며 각각 `0`/`1`로 처리됩니다 | | `options.cortex_reset` | boolean | 아니오 | `true`면 기존 컨텍스트를 버리고 이번 `messages`로 새로 구성한 뒤 추론합니다. 다른 conversationId가 점유 중이어도 이 요청의 conversationId로 점유가 넘어옵니다 (기본값: false) | | `options.apply_sampling` | boolean | 아니오 | `true`면 함께 보낸 생성 설정 값(temperature 등)을 적용하고 다음 요청에도 유지 — 아래 "생성 설정 바꾸기" 참고 | | `messages` | array | 예 | 전달할 메시지 배열(1개 이상). 각 요소는 `role`과 `content`(문자열 또는 블록 배열) | **messages의 role** | role | 의미 | |---|---| | `system` | 모델의 최상위 동작 규칙 (역할, 말투, 정책 등) | | `developer` | 개발자가 넣는 앱/에이전트 지침 | | `user` | 사용자 입력. 파일 첨부(`image`/`document` 블록)와 도구 실행 결과(`tool_result` 블록)도 여기에 넣습니다 | | `assistant` | 모델의 이전 응답 (대화 히스토리 재전송, few-shot 예시) | | `tool` | 도구 실행 결과를 문자열로 돌려줄 때 | **options.think 모델별 동작** | 모델 | `0` | `1` (기본) | `2` | |---|---|---|---| | Qwen 3 / Qwen 3.5 | 생각 없이 즉답 | 모델이 스스로 판단 | 항상 생각한 뒤 답변 | | Gemma 4 | 생각 끔 | 생각 켬 | 생각 켬 (1과 같음 — 켜고 끄기 두 단계뿐) | | GPT-OSS | 얕게 생각 | 보통 | 깊게 생각 (생각을 아예 끌 수는 없음) | > 모델에 따라 `think: 0`이어도 thinking 블록이 올 수 있습니다. thinking 블록의 `signature`는 항상 빈 문자열입니다. **파일 첨부 — 이미지·문서** 먼저 [Cortex 파일](#cortex-파일) API로 파일을 올린 뒤, 응답의 `file` 값을 `user` 메시지의 `content` 배열에 블록으로 넣습니다. 블록은 놓인 순서대로 모델에 들어갑니다. ```json { "role": "user", "content": [ { "type": "image", "source": { "type": "file", "file": "files/upload-20261016-101500-a1b2.jpg" } }, { "type": "document", "source": { "type": "file", "file": "files/upload-20261016-101512-c3d4.txt" } }, { "type": "text", "text": "사진과 문서 내용을 비교해서 설명해줘" } ] } ``` | 블록 | 규칙 | |---|---| | `image` | 이미지를 이해하는 모델(비전 모델)의 Cortex에서만 사용할 수 있습니다 — [상태 보기](#상태-보기)의 `capabilities.inputs`에 `"type": "image"`가 있는지 확인하세요. PNG·JPEG만 가능(webp 불가), 요청당 최대 4장. 이미지 1장이 수백~천여 토큰을 차지하며 `usage.input_tokens`에 포함됩니다. 이미지는 대화 컨텍스트에 남아 다음 요청에서 "아까 사진에서…"처럼 이어 물을 수 있습니다(`cortex_reset: true`면 지워짐) | | `document` | 텍스트 파일(txt/md/csv/json, UTF-8, 최대 256KB). 본문이 그 자리에 텍스트로 들어가며, 컨텍스트를 그만큼 차지합니다 | > 파일은 반드시 먼저 업로드해서 `"source": { "type": "file" }`로 가리켜야 합니다 — base64·URL 방식은 지원하지 > 않습니다. 텍스트에 `<__media__>` 문자열을 직접 쓰면 `bad_request`가 됩니다. **생성 설정 바꾸기 (apply_sampling)** `options`에 `"apply_sampling": true`와 바꿀 값을 함께 보내면 이번 요청부터 적용되고 다음 요청에도 유지됩니다. 대화 내용은 그대로 이어집니다. ```json { "conversationId": "chat-abc123", "options": { "stream": true, "apply_sampling": true, "temperature": 0.3, "top_p": 0.9, "max_tokens": 1024 }, "messages": [ { "role": "user", "content": "조금 더 정확하게 다시 설명해줘" } ] } ``` | 키 | 범위 | 설명 | |---|---|---| | `temperature` | 0 ~ 2 | 0이면 항상 가장 확률 높은 토큰 | | `top_p` | 0 ~ 1 | 1이면 끔 | | `top_k` | 0 ~ | 0이면 끔 | | `min_p` | 0 ~ 1 | 0이면 끔 | | `seed` | -1 ~ 4294967294 | -1이면 무작위 | | `repeat_penalty` | 1.0 ~ 2.0 | 1.0이면 끔 | | `max_tokens` | 1 ~ 컨텍스트 크기 | 응답 최대 토큰 수 | - `apply_sampling`이 없거나 `false`면 위 값을 보내도 무시됩니다. - 보낸 키만 바뀌고, 빠진 키는 마지막으로 적용한 값을 유지합니다. - `cortex_reset: true`, conversationId 변경, 모델 언로드 때 Cortex 기본 설정으로 돌아갑니다. - 지금 적용 중인 값은 응답 `usage.sampling`과 [상태 보기](#상태-보기)의 `sampling`으로 확인합니다. - 고정 `seed`로 같은 결과를 재현하려면 요청마다 `apply_sampling: true`와 `seed`를 다시 보냅니다. - 범위를 벗어나면 `bad_request`(400)이고 아무 값도 바뀌지 않습니다. **Tool 호출** 모델이 도구를 쓰기로 하면 응답 `content`에 `tool_use` 블록이 옵니다. 클라이언트가 그 도구를 실행한 뒤, 결과를 `tool_result` 블록으로 다시 보내면 모델이 이어서 답합니다. ``` # 1) 도구 정의와 함께 요청 { "conversationId": "chat-001", "tools": [ { "name": "bash", "description": "Execute a bash command.", "parameters": { "type": "object", "properties": { "cmd": { "type": "string" } }, "required": ["cmd"] } } ], "messages": [ { "role": "user", "content": "현재 디렉터리 파일 목록 보여줘." } ] } # 2) 응답 — tool_use 블록 "content": [ { "type": "tool_use", "id": "call_001", "raw": "<모델이 출력한 tool call 원문>" } ] # 3) 실행 결과를 tool_result 블록으로 전송 { "conversationId": "chat-001", "messages": [ { "role": "user", "content": [ { "type": "tool_result", "tool_use_id": "call_001", "content": "total 48\ndrwxr-xr-x ..." } ] } ] } # 4) 응답 — 최종 text 블록 ``` - `raw`에는 모델이 출력한 도구 호출 원문이 그대로 담깁니다. 형식은 모델마다 다르므로(예: Qwen 3은 JSON, Qwen 3.5는 XML) 클라이언트가 직접 파싱합니다. - `tool_result`의 `tool_use_id`에는 받은 `tool_use.id` 값을 넣어야 짝이 맞습니다. - Cortex가 대화를 기억하므로, 같은 conversationId라면 3단계에서 앞의 대화를 다시 보내지 않아도 됩니다. **Response — stream: false** ```json { "cortexId": "7f3c9a12-4e6b-4d8f-9a21-6b5e8c1f2d3a", "conversationId": "chat-abc123", "message": { "id": "msg-1714000000001", "role": "assistant", "content": [ { "type": "thinking", "thinking": "3 더하기 3을 계산해보면 6이다.", "signature": "" }, { "type": "text", "text": "6입니다." } ], "usage": { "input_tokens": 42, "output_tokens": 10, "elapsed_ms": 320, "prefill_ms": 85, "ctx_used": 52, "ctx_size": 10000, "stop_reason": "eot" } } } ``` | 필드 | 설명 | |---|---| | `content` | 블록 배열 — `thinking`(생각 과정) → `text`(답변) → `tool_use`(도구 호출) 순 | | `usage.input_tokens` | 이번 요청의 입력 토큰 수 | | `usage.output_tokens` | 생성한 토큰 수 (모든 블록 합계) | | `usage.prefill_ms` | 입력(프롬프트)을 처리하는 데 걸린 시간(ms) | | `usage.elapsed_ms` | 입력 처리 후 생성이 끝나기까지 걸린 시간(ms) — `prefill_ms`는 포함하지 않음 | | `usage.ctx_used` | 생성이 끝난 시점에 컨텍스트에 쌓인 토큰 수 (대화를 이어가면 누적) | | `usage.ctx_size` | 컨텍스트 최대 토큰 수 | | `usage.stop_reason` | 끝난 이유 — `eot` 정상 종료, `n_predict` 최대 생성 길이 도달, `hardcap` 서버 안전 상한(8192 토큰) 도달, `abort` 중지 요청, `decode_error` 생성 중 오류 | | `usage.sampling` | 이번 생성에 적용된 생성 설정 값 (temperature 등) | **SSE Stream 응답 포맷** ``` Content-Type: text/event-stream event: message.created data: {"cortexId":"7f3c9a12-4e6b-4d8f-9a21-6b5e8c1f2d3a","conversationId":"chat-abc123","message_id":"msg-1714000000001"} event: message.delta data: {"type":"text","delta_index":0,"delta":"안녕"} event: message.delta data: {"type":"text","delta_index":1,"delta":"하세요"} event: message.block_done data: {"type":"text","token_count":2,"elapsed_ms":80} event: message.completed data: {"cortexId":"7f3c9a12-4e6b-4d8f-9a21-6b5e8c1f2d3a","conversationId":"chat-abc123","message":{"id":"msg-1714000000001","role":"assistant","content":[{"type":"text","text":"안녕하세요"}],"usage":{"input_tokens":12,"output_tokens":2,"elapsed_ms":80}}} data: [DONE] ``` | 이벤트 | 설명 | |---|---| | `message.created` | 스트림 시작 (1회) | | `message.delta` | thinking/text 블록의 텍스트 조각 (여러 번). `delta_index`는 스트림 전체의 조각 순번 | | `message.block_done` | 블록 하나 완료 (블록마다 1회). `tool_use` 블록은 delta 없이 이 이벤트로만 오며 `id`, `raw`가 함께 옵니다 | | `message.completed` | 전체 완료 — 완성된 `message` 객체 포함 (1회) | | `data: [DONE]` | 스트림 종료 표시 (JSON 아님) | ``` event: message.block_done data: {"type":"tool_use","id":"call_001","raw":"<모델이 출력한 tool call 원문>","token_count":12,"elapsed_ms":150} ``` > `message.completed` 이벤트의 `data:` 한 줄에 완성된 `message` 객체(content 블록 전체 + usage)가 이미 다 > 들어있습니다 — 델타를 직접 누적하지 않고 이 마지막 이벤트만 파싱해도 됩니다. **Errors** | 코드 | HTTP | 상황 | |---|---|---| | `bad_request` | 400 | `messages`/`conversationId` 누락, 생성 설정 값이 범위를 벗어남, `image` 블록 규칙 위반(이미지를 받지 않는 Cortex, PNG/JPEG 아님, 장수 초과, 컨텍스트 초과 등) | | `unsupported_cortex_type` | 400 | LLM이 아닌 Cortex(이미지 생성·판정 전용)에 요청 | | `cortex_not_found` | 404 | 없는 cortexId | | `file_not_found` | 404 | `image`/`document` 블록이 가리키는 파일이 없음 | | `cortex_occupied` | 409 | 다른 conversationId가 점유 중 (`cortex_reset: false`) | | `cortex_inferring` | 409 | 이미 추론 중 | | `internal_error` | 500 | 모델 로드 실패 등 서버 내부 오류 | #### 상태 보기 `GET /{my_base_path}/cortex/{cortexId}/status` Cortex의 현재 점유 상태와 Context 토큰 사용량을 반환합니다. 추론 중에도 조회 가능합니다. **Response — 200 OK** ```json { "cortexId": "b47778cd-24d9-4912-830c-6745e1253cc5", "status": "idle", "conversationId": "conv_mtwfir2fyi0o", "last_response_id": "msg-1789099310429", "capabilities": { "api": "messages", "outputs": ["text"], "inputs": [ { "type": "image", "max": 4, "mime": ["image/png", "image/jpeg"] } ] }, "sampling": { "temperature": 0.7, "top_p": 0.9, "top_k": 40, "min_p": 0.05, "seed": -1, "repeat_penalty": 1.0, "max_tokens": 2048 }, "context_size": 4096, "context_used": 621, "context_remain": 3475, "context_used_pct": 15, "kv_cache_bytes": 91578517, "totalOutputTokens": 722, "sessionOutputTokens": 722, "currentOutputTokens": 722, "lastOutputTokens": 722 } ``` | 필드 | 타입 | 설명 | |---|---|---| | `status` | string | `idle` \| `busy`(추론 진행/중단 처리 중) | | `conversationId` | string\|null | 현재 점유 중인 conversationId. 점유자가 없으면 null — idle/busy 여부와 무관하게 유지됨(점유는 별도 상태) | | `last_response_id` | string\|null | 가장 최근에 완료된 응답의 ID. 아직 없으면 null | | `capabilities` | object | 이 Cortex가 쓰는 API와 받을 수 있는 파일 — 아래 표 참고. 모델이 로드되지 않아도 정확한 값이 옵니다 | | `sampling` | object | (LLM, 모델이 로드된 경우) 현재 적용 중인 생성 설정. [대화 요청](#대화-요청)의 `apply_sampling`으로 바꾼 값 포함 | | `context_size` | int | 모델 최대 컨텍스트 토큰 수 (모델이 로드되지 않았으면 0) | | `context_used` | int | 현재 KV Cache에 사용된 토큰 수 | | `context_remain` | int | 남은 컨텍스트 토큰 수 | | `context_used_pct` | int | 컨텍스트 사용 비율(%) | | `kv_cache_bytes` | int | KV Cache가 차지하는 메모리 크기(byte) | | `totalOutputTokens` | int | 세션(모델 로드) 이후 누적 생성된 전체 출력 토큰 수 | | `sessionOutputTokens` | int | 마지막 `/reset` 이후 누적 생성된 출력 토큰 수 | | `currentOutputTokens` | int | 가장 최근 생성의 출력 토큰 수 — 진행 중이면 실시간으로 증가하고, 완료 후에는 다음 생성이 시작되기 전까지 그 값을 유지 | | `lastOutputTokens` | int | 가장 최근에 완료된 생성의 출력 토큰 수 | **capabilities** Cortex마다 어떤 API로 호출하는지, 어떤 파일을 첨부할 수 있는지 알려줍니다. 화면에서 첨부 버튼을 보여줄지 정할 때 이 값을 쓰면 됩니다. | 필드 | 설명 | |---|---| | `api` | 생성에 쓰는 API — `messages`(LLM) \| `choice`(판정 전용 모델) \| `imageGenerate`(이미지 생성) | | `outputs` | 결과 종류 — `text` \| `choice` \| `image` | | `inputs` | 텍스트 외에 첨부할 수 있는 파일 목록. 비어 있으면 파일 첨부 불가 | | `inputs[].type` | 파일 종류 — 현재 `image` | | `inputs[].role` | (이미지 생성만) `reference` = `ref_images`로 넣는 참조 이미지 | | `inputs[].max` | 요청 하나에 넣을 수 있는 최대 개수 | | `inputs[].mime` | 받는 파일 형식 (업로드 `Content-Type`과 같은 값) | ```jsonc // LLM (비전 모델) — messages 에 이미지 첨부 가능 { "api": "messages", "outputs": ["text"], "inputs": [ { "type": "image", "max": 4, "mime": ["image/png", "image/jpeg"] } ] } // LLM (텍스트 전용) { "api": "messages", "outputs": ["text"], "inputs": [] } // 판정 전용 모델 { "api": "choice", "outputs": ["choice"], "inputs": [] } // 이미지 생성 (편집 가능) { "api": "imageGenerate", "outputs": ["image"], "inputs": [ { "type": "image", "role": "reference", "max": 4, "mime": ["image/png", "image/jpeg", "image/webp"] } ] } ``` **Errors** | 코드 | HTTP | 상황 | |---|---|---| | `cortex_not_found` | 404 | 없는 cortexId | #### 추론 중지 `POST /{my_base_path}/cortex/{cortexId}/stop` 진행 중인 추론(대화 생성, 이미지 생성)을 즉시 중단합니다. 추론 중이면 1초 이내에 중단되고 SSE Stream이 종료됩니다. 점유 중인 conversationId와 달라도 중단할 수 있습니다. **Request Body** ``` { "conversationId": "chat-550e8400" } // 생략 가능 ``` **Response — 200 OK** ```json { "ok": true, "cortexId": "7f3c9a12-4e6b-4d8f-9a21-6b5e8c1f2d3a", "message": "inference stopped" } ``` > 추론 중이 아니었다면 아무것도 바꾸지 않고 `"message": "already stopped"`로 성공 응답합니다 — 여러 번 호출해도 > 안전합니다. **Errors** | 코드 | HTTP | 상황 | |---|---|---| | `cortex_not_found` | 404 | 없는 cortexId | #### 모델 언로드 `POST /{my_base_path}/cortex/{cortexId}/unload` 로드된 모델을 메모리에서 내려 GPU/메모리를 비웁니다. Cortex 자체는 남아 있으며, 다음 `messages`/`choice`/`imageGenerate` 요청 때 자동으로 다시 로드됩니다. Body는 필요하지 않습니다. **Response — 200 OK** ```json { "ok": true, "cortexId": "7f3c9a12-4e6b-4d8f-9a21-6b5e8c1f2d3a", "message": "unloaded" } ``` > 이미 언로드된 상태에서 다시 호출해도 에러 없이 `"message": "already unloaded"`로 성공 처리됩니다 > (idempotent). 추론 중이었다면 먼저 중단시키고, 추론이 실제로 끝날 때까지 최대 5초 기다린 뒤 언로드합니다. **Errors** | 코드 | HTTP | 상황 | |---|---|---| | `cortex_not_found` | 404 | 없는 cortexId | | `cortex_inferring` | 409 | 중단을 요청했지만 5초 안에 추론이 끝나지 않음 — 언로드하지 않았으니 잠시 후 다시 시도 | #### 컨텍스트 초기화 `POST /{my_base_path}/cortex/{cortexId}/reset` Cortex의 KV Cache(대화 컨텍스트)를 초기화합니다. 이후 요청 시 대화 히스토리 전체를 다시 전송해야 합니다. Body는 필요하지 않습니다. **Response — 200 OK** ```json { "ok": true, "cortexId": "7f3c9a12-4e6b-4d8f-9a21-6b5e8c1f2d3a", "message": "context reset" } ``` **Errors** | 코드 | HTTP | 상황 | |---|---|---| | `cortex_not_found` | 404 | 없는 cortexId | | `unsupported_cortex_type` | 400 | LLM이 아닌 Cortex(이미지 생성·판정 전용)에 요청 | | `cortex_not_running` | 409 | 모델이 로드되지 않음 | | `cortex_inferring` | 409 | 추론 중 | #### 대화 세션 전환 `POST /{my_base_path}/cortex/{cortexId}/switch-conversation` 대화 세션(conversationId)으로 Cortex를 선점합니다. 이미 다른 클라이언트가 점유 중인 Cortex를 강제로 빼앗거나, 점유를 해제하는 데도 사용합니다. > ⚠ `/switch-conversation`는 점유 전환을 허용합니다. 점유 중 다른 클라이언트의 탈취를 방지하려면 > 상위 레이어(API 프록시 등)에서 접근 제어를 수행해야 합니다. **동작 규칙** | 상황 | 동작 | |---|---| | Cortex가 idle 상태 | conversationId를 등록하고 즉시 점유 성공 | | 동일한 conversationId로 재요청 | 점유 유지 (no-op) | | 다른 conversationId로 요청 | 기존 점유를 해제하고 새 conversationId로 전환 (강제 탈취) | | conversationId를 `""`로 요청 | 점유 해제 (idle 상태로 전환) | **Request Body** ``` { "conversationId": "chat-550e8400" // 빈 문자열 ""이면 점유 해제 } ``` **Response — 200 OK** ```json { "ok": true, "message": "conversationId changed to chat-550e8400" } ``` **Errors** | 코드 | HTTP | 상황 | |---|---|---| | `bad_request` | 400 | JSON 파싱 실패 | | `unsupported_cortex_type` | 400 | LLM이 아닌 Cortex(이미지 생성·판정 전용)에 요청 | | `cortex_not_found` | 404 | 없는 cortexId | #### 선택지 판정 `POST /{my_base_path}/cortex/{cortexId}/choice` 주어진 선택지(`choices`) 중 `query`에 가장 맞는 것을 판정합니다. 텍스트를 생성하지 않고 한 번의 계산으로 끝나서 빠르며, 결과를 선택지별 확률로 돌려줍니다. 문의 분류, 의도 파악, 정책 위반 판정처럼 "정해진 보기 중 하나를 고르는" 작업에 씁니다. - 일반 LLM Cortex와 판정 전용 모델(예: Laya) Cortex 모두 같은 요청 형식을 씁니다. `cortexId`만 바꾸면 두 방식을 비교할 수 있습니다 — 응답의 `method`가 `llm_label`(LLM) 또는 `laya`(판정 전용 모델)로 옵니다. - conversationId(점유)를 확인하지도 바꾸지도 않으며, LLM Cortex에서 진행 중인 대화 컨텍스트에도 영향을 주지 않습니다. 언로드된 Cortex는 자동으로 로드됩니다. **Request Body** ```json { "instruction": "다음 고객 문의의 유형을 고르세요.", "query": "어제 주문한 상품을 취소하고 돈을 돌려받고 싶습니다.", "choices": ["환불", "배송조회", "단순문의", "스팸"], "temperature": 1.0, "debias": false } ``` | 파라미터 | 타입 | 필수 | 설명 | |---|---|---|---| | `instruction` | string | 아니오 | 판정 질문. 생략하거나 빈 문자열이면 `"Choose the option that best fits the query."` | | `query` | string \| object \| array | 예 | 판단 대상. 문자열이 아니면 JSON 텍스트로 넣습니다. 업로드한 텍스트 파일을 `document` 블록(또는 `text`/`document` 블록 배열)으로 줄 수도 있습니다 — `image` 블록은 지원하지 않음 | | `choices` | string[] | 예 | 선택지. LLM Cortex는 2 ~ 26개, 판정 전용 모델은 2 ~ 255개. 빈 문자열/중복 불가 | | `temperature` | number | 아니오 | 확률 분포 온도, `0`보다 커야 함. LLM Cortex 기본값 `1.0`, 판정 전용 모델은 생략 시 모델에 내장된 보정 온도 사용(생략 권장) | | `debias` | bool | 아니오 | LLM Cortex 전용 — 선택지 순서를 뒤집어 한 번 더 계산해 평균(위치 편향 보정, 비용 2배). 기본 `false`. 판정 전용 모델은 무시 | **Response — 200 OK** ```json { "cortexId": "7f3c9a12-4e6b-4d8f-9a21-6b5e8c1f2d3a", "method": "llm_label", "best_choice": "환불", "confidence": 0.9845, "probabilities": { "환불": 0.9845, "배송조회": 0.0112, "단순문의": 0.0041, "스팸": 0.0002 }, "usage": { "input_tokens": 98, "elapsed_ms": 41, "passes": 1 } } ``` | 필드 | 설명 | |---|---| | `method` | 계산 방식 — `llm_label`(LLM Cortex) 또는 `laya`(판정 전용 모델) | | `best_choice` | 확률이 가장 높은 선택지 (동률이면 앞의 것) | | `confidence` | `best_choice`의 확률 (0 ~ 1) | | `probabilities` | 선택지 → 확률 (요청 `choices` 순서 유지, 합계 1) | | `truncated` | 판정 전용 모델만 — 질문/선택지가 길어 모델 입력 한도에 맞춰 잘렸으면 `true` | | `usage.input_tokens` | 평가한 입력 토큰 수 (debias면 두 번의 합) | | `usage.elapsed_ms` | 판정 소요 시간(ms) | | `usage.passes` | 계산 횟수 (1, debias면 2) | **Errors** | 코드 | HTTP | 상황 | |---|---|---| | `bad_request` | 400 | JSON 오류, `query`/`choices` 누락, 선택지 개수 위반, 빈/중복 선택지, `instruction`이 문자열이 아님, `temperature <= 0`, `debias`가 bool이 아님 | | `unsupported_cortex_type` | 400 | 이미지 생성 Cortex에 요청 | | `cortex_not_found` | 404 | 없는 cortexId | | `cortex_inferring` | 409 | 이미 추론/판정 중 | | `internal_error` | 500 | 모델 로드 실패, 입력이 모델 한도 초과 등 | > 판정 전용 모델 Cortex는 `/choice`만 처리합니다 — `messages`, `switch-conversation`, `reset`은 > `unsupported_cortex_type`(400)으로 거절됩니다. #### 이미지 생성 `POST /{my_base_path}/cortex/{cortexId}/imageGenerate` 이미지 생성 모델(예: Qwen Image, FLUX) Cortex로 이미지를 만들거나 편집합니다. 생성된 이미지는 그 Cortex의 파일 저장소에 저장되고, 응답의 `files`에 `files/gen-….png` 형태로 옵니다. 이 값을 `/{my_base_path}/cortex/{cortexId}/` 뒤에 붙이면 다운로드 주소가 됩니다(아래 "Cortex 파일" 참고). **Request Body** ```json { "prompt": "a lovely cat holding a sign says 'flamebox'", "negative_prompt": "blurry", "width": 1024, "height": 1024, "steps": 20, "cfg_scale": 6.0, "seed": -1, "sampler": "euler", "count": 1, "stream": false } ``` | 파라미터 | 필수 | 설명 | |---|---|---| | `prompt` | 예 | 그릴 내용 | | `negative_prompt` | 아니오 | 피하고 싶은 내용 | | `width`, `height` | 아니오 | 이미지 크기 — 모델이 정한 배수(Qwen Image 2.1은 32의 배수). 생략 시 모델/Cortex 기본값 | | `steps`, `cfg_scale`, `sampler`, `scheduler`, `count` | 아니오 | 생략 시 기본값. 최댓값은 Cortex 설정(`limits`)을 넘을 수 없음 | | `seed` | 아니오 | `-1`이면 무작위 (응답에 실제 사용한 seed가 옴 — 같은 결과를 다시 얻을 때 사용) | | `ref_images` | 아니오 | 편집용 참조 이미지 배열 — `"files/<이름>"` 문자열 또는 `image` 블록. 먼저 "Cortex 파일" API로 업로드합니다. 아래 "이미지 편집" 참고 | | `stream` | 아니오 | `true`면 SSE로 진행률을 받음. 기본 `false` | 모르는 파라미터를 보내면 `bad_request`(400)입니다. **Response — `stream: false` (생성이 끝나면 응답)** ```json { "cortexId": "7f3c9a12-4e6b-4d8f-9a21-6b5e8c1f2d3a", "files": ["files/gen-20261006-195252-3294.png"], "seed": 7, "width": 1024, "height": 1024, "steps": 20, "elapsed_ms": 78000, "method": "sd.cpp" } ``` 생성 중에 `/stop`으로 중지되면 `{ "cortexId": "…", "status": "cancelled" }`가 옵니다. **Response — `stream: true` (SSE, 마지막 이벤트가 result / error / cancelled)** ``` event: generation.queued data: {"cortexId": "..."} (다른 생성이 진행 중일 때만) event: generation.progress data: {"cortexId": "...", "step": 3, "steps": 20, "elapsed_ms": 4100} event: generation.result data: {"cortexId": "...", "files": ["files/gen-….png"], "seed": 12345, ...} ``` > 이미지 생성은 수십 초 이상 걸릴 수 있습니다(해상도·steps·PC 성능에 따라 다름). 진행률을 보여주려면 > `stream: true`를 권장합니다. 한 PC에서 이미지 생성은 한 번에 하나씩 처리되며, 대기 중인 요청은 > `generation.queued`를 먼저 받습니다. **이미지 편집 — 참조 이미지(ref_images)** 업로드한 사진이나 앞 생성 결과를 `ref_images`로 주고, `prompt`에 바꿀 내용을 적으면 그 이미지를 고친 결과를 만듭니다. ```json { "prompt": "Change only the background to a snowy mountain. Keep the same man, face, clothes and pose.", "ref_images": ["files/upload-20261016-101500-a1b2.jpg"], "width": 832, "height": 1248, "seed": 42 } ``` | 항목 | 규칙 | |---|---| | 사용 가능한 Cortex | [상태 보기](#상태-보기)의 `capabilities.inputs`에 `{ "type": "image", "role": "reference" }`가 있는 Cortex만. 없으면 `bad_request`(400) | | 형식 | PNG, JPEG, webp | | 여러 장 | 배열 순서대로 모델에 들어갑니다(최대 4장). 프롬프트에서 역할을 지정하세요 — 예: `"Use the face of the man in the first image and put him into the scene of the second image"` | | 연속 편집 | 응답의 `files/gen-….png`를 다음 요청의 `ref_images`로 넣습니다. 이미지 모델은 이전 요청을 기억하지 않으므로 매번 참조 이미지와 바꿀 내용을 함께 보냅니다. 편집을 거듭할수록 화질이 조금씩 흐려지므로 원본 사진을 함께 넣으면(2장) 인물 유지에 도움이 됩니다 | | 출력 크기 | `width`/`height`를 생략하면 Cortex 기본 크기로 나와 원본과 비율이 달라질 수 있습니다 — 참조 이미지 비율에 맞춘 크기(모델 배수)를 주는 것을 권장합니다(예: 540×810 → 832×1248) | | 프롬프트 | 바꿀 것은 `Change only …`, 유지할 것은 `Keep the same …`으로 함께 적으면 바뀌는 범위가 좁아집니다 | **Errors** | 코드 | HTTP | 상황 | |---|---|---| | `bad_request` | 400 | 입력 오류(필수값 누락, 크기 배수 위반, 상한 초과, 모르는 파라미터, 참조 이미지를 받지 않는 Cortex, 장수 초과, 읽을 수 없는 이미지 등) | | `unsupported_cortex_type` | 400 | 이미지 생성 Cortex가 아님 | | `file_not_found` | 404 | `ref_images`에 없는 파일 이름 | | `cortex_inferring` | 409 | 이미 생성 중 | | `internal_error` | 500 | 모델 로드 실패 등 | #### Cortex 파일 Cortex마다 파일 저장소가 있습니다. 생성 결과(`gen-…`)를 내려받거나, 생성에 쓸 입력 파일(예: 편집용 참조 이미지)을 올립니다. 모델 종류나 로드 여부와 상관없이 모든 Cortex에서 동작합니다. | 메서드 | 경로 | 설명 | |---|---|---| | `POST` | `/{my_base_path}/cortex/{cortexId}/files` | 업로드 — Body는 파일 바이트 그대로, `Content-Type`으로 형식 지정(최대 50MB). 응답 `{ "cortexId", "file": "files/upload-….png", "size", "mime" }` | | `GET` | `/{my_base_path}/cortex/{cortexId}/files` | 목록 — `{ "cortexId", "files": [ { "name", "size", "mime", "createdAt" } ] }` (이름순) | | `GET` | `/{my_base_path}/cortex/{cortexId}/files/{name}` | 다운로드 — 파일 바이트 그대로(`Content-Type` = 파일 형식) | | `POST` | `/{my_base_path}/cortex/{cortexId}/files/{name}` | 다운로드 — GET과 같음 | | `DELETE` | `/{my_base_path}/cortex/{cortexId}/files/{name}` | 삭제 — `{ "ok": true, "cortexId", "file" }` | - 업로드 파일 이름은 서버가 정합니다: `upload--.`. 생성 결과는 `gen-…`입니다. - API가 주고받는 파일은 `files/<이름>` 형식입니다. 이 값을 그대로 [대화 요청](#대화-요청)의 `image`/`document` 블록, [이미지 생성](#이미지-생성)의 `ref_images`에 넣습니다(`<이름>`만 써도 됩니다). - 허용 형식(`Content-Type`): `image/png`, `image/jpeg`, `image/webp`, `audio/wav`, `video/webm`, `video/mp4`, `text/plain`, `application/json` — 그 외는 `bad_request`(400). 단, 대화 요청의 `image` 블록은 PNG/JPEG만 받습니다. - 파일 이름 규칙: 영문·숫자·`.`·`_`·`-`만, 128자 이하, `.`으로 시작하거나 `..` 포함 불가. - 다운로드는 `Authorization` 헤더가 필요하므로, 브라우저 ``에 주소를 바로 넣을 수는 없습니다 — 서버에서 받아 전달하거나 Blob으로 받아서 표시하세요. **Errors** | 코드 | HTTP | 상황 | |---|---|---| | `bad_request` | 400 | 허용되지 않는 형식, 파일 이름 규칙 위반, 50MB 초과 | | `file_not_found` | 404 | 없는 파일 | | `cortex_not_found` | 404 | 없는 cortexId | ```bash # 이미지 생성 → 결과 다운로드 → 삭제 curl -s -X POST https://api-3.flameai.im/Nx7bQ2mZ/cortex/{cortexId}/imageGenerate \ -H "Authorization: Bearer YOUR_SECRET_KEY" -H "Content-Type: application/json" \ -d '{ "prompt": "a lovely cat holding a sign says flamebox", "width": 1024, "height": 1024 }' curl -s https://api-3.flameai.im/Nx7bQ2mZ/cortex/{cortexId}/files/gen-20261006-195252-3294.png \ -H "Authorization: Bearer YOUR_SECRET_KEY" -o result.png curl -s -X DELETE https://api-3.flameai.im/Nx7bQ2mZ/cortex/{cortexId}/files/gen-20261006-195252-3294.png \ -H "Authorization: Bearer YOUR_SECRET_KEY" # 편집용 참조 이미지 업로드 curl -s -X POST https://api-3.flameai.im/Nx7bQ2mZ/cortex/{cortexId}/files \ -H "Authorization: Bearer YOUR_SECRET_KEY" -H "Content-Type: image/png" --data-binary @input.png ``` #### 토큰 세기 `POST /{my_base_path}/cortex/{cortexId}/tokenize` 문자열을 이 Cortex 모델의 토크나이저로 나눠 토큰 id 배열과 토큰 수를 돌려줍니다. 토큰 수는 모델마다 다르므로, 보낼 프롬프트가 컨텍스트에 들어가는지 미리 확인할 때 씁니다. LLM과 판정 전용 모델 Cortex에서 쓸 수 있습니다. **Request Body** ```json { "text": "안녕하세요. 오늘 날씨가 좋네요.", "options": { "autoLoad": false } } ``` | 파라미터 | 타입 | 필수 | 설명 | |---|---|---|---| | `text` | string | 예 | 토큰화할 문자열 | | `options.autoLoad` | boolean | 아니오 | `true`면 모델이 로드되지 않은 경우 먼저 로드한 뒤 토큰화합니다. 기본값 `false` | **Response — 200 OK** ```json { "cortexId": "7f3c9a12-4e6b-4d8f-9a21-6b5e8c1f2d3a", "tokens": [148924, 154982, 88005, 13], "count": 4 } ``` **Errors** | 코드 | HTTP | 상황 | |---|---|---| | `bad_request` | 400 | `text` 누락 또는 JSON 파싱 실패 | | `unsupported_cortex_type` | 400 | 이미지 생성 Cortex에 요청 | | `cortex_not_found` | 404 | 없는 cortexId | | `cortex_not_running` | 409 | 모델이 로드되지 않았고 `options.autoLoad`가 `false` | | `internal_error` | 500 | 모델 로드 실패 등 | ### curl 테스트 예시 아래 예시의 `Nx7bQ2mZ`(my_base_path)는 My AI Console(`/mypage/settings`)에서 확인한 실제 값으로, `7f3c9a12-4e6b-4d8f-9a21-6b5e8c1f2d3a`은 실제 등록된 cortexId로 교체하세요. **Cortex 선점** ```bash curl -s -X POST https://api-3.flameai.im/Nx7bQ2mZ/cortex/7f3c9a12-4e6b-4d8f-9a21-6b5e8c1f2d3a/switch-conversation \ -H "Authorization: Bearer YOUR_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{ "conversationId": "chat-test-001" }' | python3 -m json.tool ``` **대화 요청 (SSE 스트리밍)** ```bash curl -s -N -X POST https://api-3.flameai.im/Nx7bQ2mZ/cortex/7f3c9a12-4e6b-4d8f-9a21-6b5e8c1f2d3a/messages \ -H "Authorization: Bearer YOUR_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{ "conversationId": "chat-test-001", "options": { "stream": true, "think": 1 }, "messages": [ { "role": "user", "content": "안녕하세요. 간단히 자기소개 해줘." } ] }' ``` **이미지 질문 (비전 모델 Cortex)** ```bash # 1) 이미지 업로드 → 응답의 "file" 값을 확인 curl -s -X POST https://api-3.flameai.im/Nx7bQ2mZ/cortex/7f3c9a12-4e6b-4d8f-9a21-6b5e8c1f2d3a/files \ -H "Authorization: Bearer YOUR_SECRET_KEY" \ -H "Content-Type: image/jpeg" --data-binary @photo.jpg # 2) 그 파일을 image 블록으로 넣어 질문 curl -s -X POST https://api-3.flameai.im/Nx7bQ2mZ/cortex/7f3c9a12-4e6b-4d8f-9a21-6b5e8c1f2d3a/messages \ -H "Authorization: Bearer YOUR_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{ "conversationId": "chat-img-001", "options": { "stream": false, "think": 0 }, "messages": [ { "role": "user", "content": [ { "type": "image", "source": { "type": "file", "file": "files/upload-20261016-101500-a1b2.jpg" } }, { "type": "text", "text": "이 사진에 무엇이 있는지 설명해줘" } ] } ] }' | python3 -m json.tool ``` **컨텍스트 재구성 (cortex_reset)** ```bash curl -s -N -X POST https://api-3.flameai.im/Nx7bQ2mZ/cortex/7f3c9a12-4e6b-4d8f-9a21-6b5e8c1f2d3a/messages \ -H "Authorization: Bearer YOUR_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{ "conversationId": "chat-test-001", "options": { "stream": true, "cortex_reset": true }, "messages": [ { "role": "user", "content": "파이썬이란 무엇인가?" }, { "role": "assistant", "content": "파이썬은 범용 프로그래밍 언어입니다." }, { "role": "user", "content": "그렇다면 파이썬의 장점은?" } ] }' ``` **상태 조회** ```bash curl -s https://api-3.flameai.im/Nx7bQ2mZ/cortex/7f3c9a12-4e6b-4d8f-9a21-6b5e8c1f2d3a/status \ -H "Authorization: Bearer YOUR_SECRET_KEY" | python3 -m json.tool ``` **추론 중단** ```bash curl -s -X POST https://api-3.flameai.im/Nx7bQ2mZ/cortex/7f3c9a12-4e6b-4d8f-9a21-6b5e8c1f2d3a/stop \ -H "Authorization: Bearer YOUR_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{ "conversationId": "chat-test-001" }' | python3 -m json.tool ``` **점유 해제** ```bash curl -s -X POST https://api-3.flameai.im/Nx7bQ2mZ/cortex/7f3c9a12-4e6b-4d8f-9a21-6b5e8c1f2d3a/switch-conversation \ -H "Authorization: Bearer YOUR_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{ "conversationId": "" }' | python3 -m json.tool ``` ### 다른 API와의 차이 Claude(Anthropic) API나 OpenAI API를 써 본 개발자가 헷갈리기 쉬운 차이점입니다. | 항목 | FlameAI | Claude API | OpenAI API | |---|---|---|---| | 대화 상태 | Stateful — Cortex가 대화를 기억, `conversationId`로 점유 | Stateless — 매번 전체 대화 전송 | Stateless — 매번 전체 대화 전송 | | 스트리밍 지정 | `options.stream` | 최상위 `stream` | 최상위 `stream` | | 응답 구조 | 단일 `message` 객체 (`usage`는 그 안에) | 단일 message | `choices[0].message`, 최상위 `usage` | | SSE 이벤트 | `message.created` / `delta` / `block_done` / `completed` | `content_block_start` / `content_block_delta` 등 | `choices[0].delta.content` | | 종료 이유 | `usage.stop_reason` (`eot`, `n_predict` 등) | `stop_reason` (`end_turn` 등) | `finish_reason` | | 도구 호출 | `tool_use` 블록, 호출 내용은 원문 그대로 `raw` (클라이언트가 파싱) | `tool_use` 블록, 파싱된 `input` | `tool_calls` 배열 | | 도구 결과 | `user` 메시지의 `tool_result` 블록 (`role: "tool"` + 문자열도 허용) | `user` 메시지의 `tool_result` 블록 | `role: "tool"` + `tool_call_id` | | thinking 서명 | `signature`는 항상 빈 문자열 | 서명 값을 다시 보내야 함 | - | | 모델명 | 응답에 없음 (Cortex가 모델을 정함) | `model` 필드 | `model` 필드 | | 처리 시간 | `elapsed_ms`, `prefill_ms` 제공 | 없음 | 없음 | --- ## WORKSPACE API ### WORKSPACE WORKSPACE는 사용자에게 제공되는 공간으로, FlameAI 가입 시 자동으로 생성됩니다. 각 WORKSPACE에는 연결되는 가상의 **my_base_path**(API 경로)가 주어지며, 이 경로를 통해 사용자가 관리하는 Cortex 등을 API로 핸들링할 수 있습니다. ### 내 Cortex 목록 `GET /{my_base_path}/cortexes` 이 계정이 가진 Cortex 전체 목록을 조회합니다. FlameBox 자신의 `/v1/cortexes`(FlameBox 1대 단위)와 달리, 이 엔드포인트는 **API Gateway 자신이 응답을 만듭니다** — 한 계정이 여러 FlameBox에 걸쳐 있을 수 있어서, FlameBox 하나가 답할 수 있는 질문이 아니기 때문입니다. **Response — 200 OK** ```json { "cortexes": [ { "cortexId": "7f3c9a12-4e6b-4d8f-9a21-6b5e8c1f2d3a", "name": "코딩 어시스턴트", "status": "ONLINE", "flameboxId": "fb-abc123", "flameboxName": "내 데스크탑" } ] } ``` | 필드 | 타입 | 설명 | |---|---|---| | `cortexId` | string | Cortex 식별자 | | `name` | string | Cortex 이름(비어있으면 cortexId로 대체) | | `status` | string | `ONLINE` \| `OFFLINE` — 이 Cortex가 속한 FlameBox의 온라인 여부(Cortex 자체의 idle/busy 여부가 아님) | | `flameboxId` | string | 이 Cortex가 속한 FlameBox 식별자 | | `flameboxName` | string | 그 FlameBox의 이름 | > ⚠ 지금은 이 계정이 **직접 소유한** FlameBox의 Cortex만 포함됩니다 — 다른 계정으로부터 > 공유받은 FlameBox(`/mypage/share`의 "내가 공유받은 FlameBox")의 Cortex는 아직 이 목록에 나오지 > 않습니다. ### curl 테스트 예시 아래 예시의 `Nx7bQ2mZ`(my_base_path)는 My AI Console(`/mypage/settings`)에서 확인한 실제 값으로 교체하세요. **내 Cortex 목록 조회** ```bash curl -s https://api-3.flameai.im/Nx7bQ2mZ/cortexes \ -H "Authorization: Bearer YOUR_SECRET_KEY" | python3 -m json.tool ```