# FlameAI API — AI 에이전트용 가이드 이 문서는 FlameAI API를 호출하는 코드를 작성하는 AI 에이전트를 위한 요약입니다. 엔드포인트별 상세 명세(모든 필드, 예시, 에러 코드)는 **https://flameai.im/doc.md** 를 참고하세요. FlameAI는 사용자의 PC/서버에 설치된 **FlameBox**에서 AI 모델을 실행하고, 그 모델을 **API Gateway**를 통해 인터넷 어디서나 호출하게 해 줍니다. 모델 하나가 메모리에 올라가 실행되는 단위를 **Cortex**라고 부르며, API 호출은 항상 특정 Cortex(`cortexId`)를 대상으로 합니다. --- ## 1. 설정값 사용자의 FlameAI 콘솔 **설정**(`https://flameai.im/mypage/settings`)에 있는 두 값이 필요합니다. | 환경변수 (권장 이름) | 콘솔 표시 이름 | 예시 | |---|---|---| | `FLAMEAI_API_BASE` | API base url | `https://api-3.flameai.im/Nx7bQ2mZ` | | `FLAMEAI_SECRET_KEY` | Secret key | `fai_xxxxxxxx…` | - 값을 코드에 직접 쓰지 말고 환경변수(또는 사용자의 설정 파일)에서 읽으세요. 없으면 사용자에게 콘솔에서 확인해 달라고 요청하세요. - Secret Key를 로그·화면·커밋에 남기지 마세요. - `cortexId`는 아래 "호출 순서" 1단계로 조회하거나, 사용자가 콘솔의 FlameBox 상세 → Cortex 탭에서 확인해 줍니다. --- ## 2. 호출 규칙 ``` {FLAMEAI_API_BASE}/cortexes ← 내 Cortex 목록 {FLAMEAI_API_BASE}/cortex/{cortexId}/{action} ← 그 외 모든 호출 ``` ``` Authorization: Bearer {FLAMEAI_SECRET_KEY} (필수) Content-Type: application/json (Body가 있는 요청) ``` - URL에 `/v1/` 같은 버전을 넣지 않습니다. (버전이 필요하면 `X-API-Version` 헤더, 생략 시 1) - 응답에 `error` 필드가 있으면 실패입니다: `{ "error": { "code": "...", "message": "...", "httpStatus": 409 } }`. 분기는 항상 `error.code`로 하세요. --- ## 3. 호출 순서 1. `GET {BASE}/cortexes` → Cortex 목록. `status`가 `ONLINE`인 것만 호출할 수 있습니다(FlameBox 온라인 여부). 2. `GET {BASE}/cortex/{cortexId}/status` → `capabilities.api`로 이 Cortex에 쓸 API를 고릅니다. 3. 아래 표의 API를 호출합니다. | `capabilities.api` | 모델 종류 | 호출할 action | |---|---|---| | `messages` | 대화형 LLM | `messages` (대화), `tokenize` (토큰 세기), `choice` (분류도 가능) | | `choice` | 판정 전용 모델 | `choice` 만 | | `imageGenerate` | 이미지 생성 | `imageGenerate`, 결과는 `files/…` 로 다운로드 | - `capabilities.inputs`에 `{"type":"image"}`가 있으면 이미지를 첨부할 수 있습니다(`messages`의 `image` 블록, `imageGenerate`의 `ref_images`). 비어 있으면 파일 첨부를 하지 마세요. --- ## 4. 다른 API와 다르게 동작하는 점 (꼭 지킬 것) 1. **Stateful입니다.** Cortex가 대화를 기억합니다. 같은 `conversationId`로 이어서 보낼 때는 **새 메시지만** 보내세요. 전체 히스토리를 매번 다시 보내면 대화가 중복됩니다. 2. **`conversationId`가 Cortex를 점유합니다.** 다른 대화가 점유 중이면 `cortex_occupied`(409). 새 대화를 시작하거나 히스토리를 통째로 다시 넣을 때는 `options.cortex_reset: true` + 전체 `messages`를 보냅니다. 3. **옵션은 `options` 안에 넣습니다.** `stream`은 최상위가 아니라 `options.stream`입니다. `options.think`는 정수 `0`(즉답) / `1`(자동, 기본) / `2`(생각 강제). 4. **응답은 단일 `message` 객체**입니다(`choices[]` 아님). 텍스트는 `message.content[]`에서 `type: "text"` 블록을 모읍니다. `thinking` 블록은 사용자에게 보여줄 답이 아닙니다. 5. **파일은 먼저 업로드한 뒤 참조합니다.** `POST {BASE}/cortex/{id}/files` (Body = 파일 바이트, `Content-Type` = 파일 형식) → 응답 `file`(`files/upload-….jpg`)을 블록에 넣습니다. base64·URL 직접 전달은 지원하지 않습니다. `messages`의 이미지는 PNG/JPEG만, 요청당 최대 4장. 6. **도구 호출(`tool_use`)의 내용은 원문 그대로 `raw`에 옵니다.** 모델마다 형식이 다르므로(JSON, XML 등) 직접 파싱하세요. 결과는 `user` 메시지의 `{"type":"tool_result","tool_use_id":"","content":"..."}` 블록으로 보냅니다. 7. 응답에 모델명(`model`) 필드는 없고, 종료 이유는 `message.usage.stop_reason`(`eot`, `n_predict`, `hardcap`, `abort`, `decode_error`)입니다. 8. 생성 설정(temperature 등)은 `options.apply_sampling: true`와 함께 보낼 때만 적용되고, 이후 요청에도 유지됩니다. --- ## 5. 에러 대응 | `error.code` | HTTP | 뜻 | 대응 | |---|---|---|---| | `bearer_not_found`, `bearer_invalid` | 401 | 인증 실패 | 재시도하지 말고 사용자에게 API base url / Secret Key 확인 요청 | | `cortexbox_offline`, `flamebox_io_error` | 503 | FlameBox가 꺼져 있거나 연결 안 됨 | 잠시 후 몇 번 재시도, 계속 실패하면 사용자에게 FlameBox 실행 확인 요청 | | `cortex_occupied` | 409 | 다른 conversationId가 점유 중 | 내 앱의 대화라면 `cortex_reset: true` + 전체 히스토리로 다시 보내기. 다른 사용자의 대화일 수 있으면 사용자에게 확인 | | `cortex_inferring` | 409 | 이미 생성 중 | 잠시 기다렸다 재시도. 내가 남긴 생성이면 `POST …/stop` 후 재시도 | | `cortex_not_running` | 409 | 모델 미로드 (`reset`, `tokenize`) | `tokenize`는 `options.autoLoad: true`, `reset`은 지울 것이 없으니 무시 | | `unsupported_cortex_type` | 400 | 이 Cortex에 맞지 않는 API | `capabilities.api`를 보고 맞는 action으로 | | `file_not_found` | 404 | 첨부 파일 없음 | 다시 업로드 | | `cortex_not_found` | 404 | 없는 cortexId | `GET {BASE}/cortexes`로 다시 확인 | | `bad_request` | 400 | 요청 형식 오류 | `error.message`를 보고 요청 수정. 같은 요청 재시도 금지 | | `internal_error` | 500 | 모델 로드 실패 등 | 한 번 재시도 후 사용자에게 보고 | **타임아웃 권장값** - 연결: 10초 - `messages`: 모델이 언로드 상태면 첫 요청에 로드 시간이 더해집니다. `stream: true`를 쓰고 읽기 타임아웃은 넉넉히(수 분). - `imageGenerate`: 해상도·PC 성능에 따라 수십 초~수 분. `stream: true`로 진행률을 받거나 읽기 타임아웃을 10분 정도로. --- ## 6. 최소 코드 (Python, `requests`) ```python import os, json, requests BASE = os.environ["FLAMEAI_API_BASE"].rstrip("/") # 예: https://api-3.flameai.im/Nx7bQ2mZ KEY = os.environ["FLAMEAI_SECRET_KEY"] AUTH = {"Authorization": f"Bearer {KEY}"} def call(method, path, body=None, timeout=(10, 600)): r = requests.request(method, f"{BASE}/{path}", headers=AUTH, json=body, timeout=timeout) data = r.json() if "error" in data: raise RuntimeError(f"{data['error']['code']}: {data['error']['message']}") return data # 1) Cortex 고르기 cortexes = call("GET", "cortexes")["cortexes"] cortex_id = next(c["cortexId"] for c in cortexes if c["status"] == "ONLINE") caps = call("GET", f"cortex/{cortex_id}/status")["capabilities"] # 2) 대화 (SSE 스트리밍) — 텍스트를 출력하고 완성된 message 를 돌려준다 def chat(conversation_id, messages, **options): body = {"conversationId": conversation_id, "options": {"stream": True, **options}, "messages": messages} with requests.post(f"{BASE}/cortex/{cortex_id}/messages", headers=AUTH, json=body, stream=True, timeout=(10, 600)) as r: r.encoding = "utf-8" if r.status_code != 200: err = r.json()["error"] raise RuntimeError(f"{err['code']}: {err['message']}") event = None for line in r.iter_lines(decode_unicode=True): if line.startswith("event:"): event = line[6:].strip() elif line.startswith("data:"): data = line[5:].strip() if data == "[DONE]": break obj = json.loads(data) if event == "message.delta" and obj["type"] == "text": print(obj["delta"], end="", flush=True) elif event == "message.completed": return obj["message"] # 새 대화 시작: cortex_reset 으로 이전 컨텍스트를 비우고 시작 msg = chat("chat-001", [{"role": "user", "content": "안녕하세요"}], cortex_reset=True) # 이어서 묻기: 새 메시지만 보낸다 (Cortex 가 앞 대화를 기억함) msg = chat("chat-001", [{"role": "user", "content": "방금 한 말을 한 줄로 요약해줘"}]) # 3) 도구 호출 루프 (bash 도구 예시) — tool_use 가 없을 때까지 반복 tools = [{"name": "bash", "description": "Execute a bash command.", "parameters": {"type": "object", "properties": {"cmd": {"type": "string"}}, "required": ["cmd"]}}] body = {"conversationId": "chat-tool-001", "tools": tools, "options": {"stream": False, "cortex_reset": True}, "messages": [{"role": "user", "content": "현재 디렉터리 파일 목록 보여줘"}]} while True: message = call("POST", f"cortex/{cortex_id}/messages", body)["message"] uses = [b for b in message["content"] if b["type"] == "tool_use"] if not uses: break results = [{"type": "tool_result", "tool_use_id": u["id"], "content": run_tool(u["raw"])} # run_tool: raw 를 파싱해 실행하는 함수 (모델별 형식) for u in uses] body = {"conversationId": "chat-tool-001", "tools": tools, "options": {"stream": False}, "messages": [{"role": "user", "content": results}]} # 새 메시지만 # 4) 이미지 질문 (capabilities.inputs 에 image 가 있을 때) with open("photo.jpg", "rb") as f: up = requests.post(f"{BASE}/cortex/{cortex_id}/files", headers={**AUTH, "Content-Type": "image/jpeg"}, data=f, timeout=(10, 120)).json() answer = call("POST", f"cortex/{cortex_id}/messages", { "conversationId": "chat-img-001", "options": {"stream": False, "cortex_reset": True}, "messages": [{"role": "user", "content": [ {"type": "image", "source": {"type": "file", "file": up["file"]}}, {"type": "text", "text": "이 사진을 설명해줘"}]}]}) # 5) 이미지 생성 (capabilities.api == "imageGenerate" 인 Cortex) → 다운로드 image_cortex_id = "..." res = call("POST", f"cortex/{image_cortex_id}/imageGenerate", {"prompt": "a lovely cat holding a sign says 'flamebox'", "width": 1024, "height": 1024}) png = requests.get(f"{BASE}/cortex/{image_cortex_id}/{res['files'][0]}", headers=AUTH, timeout=(10, 120)) open("result.png", "wb").write(png.content) # 6) 선택지 판정 (분류) r = call("POST", f"cortex/{cortex_id}/choice", { "instruction": "다음 고객 문의의 유형을 고르세요.", "query": "어제 주문한 상품을 취소하고 돈을 돌려받고 싶습니다.", "choices": ["환불", "배송조회", "단순문의", "스팸"]}) print(r["best_choice"], r["confidence"]) ``` --- ## 7. 더 보기 - 전체 API 명세: https://flameai.im/doc.md (사람용 화면: https://flameai.im/doc) - 지원 모델: https://flameai.im/models - FlameBox 설치: https://flameai.im/download