feat(backend): 이미지 요청만 vision 모델로 — OPENCODE_VISION_MODEL
로컬(OpenRouter)은 게이트웨이를 안 거쳐서 백엔드가 OpenCode prompt 에 model{providerID,modelID} 를 실어 분기.
기본 openrouter/google/gemma-4-31b-it(고객사 605 와 같은 모델). 텍스트는 기본 GLM 5.2.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
6d7723a533
commit
113d97c77c
@@ -1,6 +1,8 @@
|
||||
# CodeAssist 전용 OpenCode 서버 (opencode/ 폴더에서 `opencode serve --port 4096`)
|
||||
OPENCODE_BASE_URL=http://localhost:4096
|
||||
OPENCODE_AGENT=codeassist
|
||||
# 이미지 요청만 이 모델로 (로컬 OpenRouter). 고객사는 비우고 AAF_FABRIX_VISION_MODEL_ID 사용
|
||||
OPENCODE_VISION_MODEL=openrouter/google/gemma-4-31b-it
|
||||
|
||||
# 토큰 서명 키 (비우면 data/.auth_secret 자동 생성)
|
||||
AUTH_SECRET=
|
||||
|
||||
@@ -85,7 +85,12 @@ def _prompt_payload(req: dict) -> dict:
|
||||
for i, img in enumerate(req["images"]):
|
||||
ext = img["mediaType"].split("/")[1]
|
||||
parts.append({"type": "file", "mime": img["mediaType"], "filename": f"image-{i + 1}.{ext}", "url": img["data"]})
|
||||
return {"agent": settings.OPENCODE_AGENT, "parts": parts}
|
||||
payload: dict = {"agent": settings.OPENCODE_AGENT, "parts": parts}
|
||||
# 이미지 있으면 vision 모델로 — 텍스트는 기본 모델(빠름). 고객사 FabriX 는 게이트웨이가 따로 분기.
|
||||
if req["images"] and settings.OPENCODE_VISION_MODEL and "/" in settings.OPENCODE_VISION_MODEL:
|
||||
provider, model = settings.OPENCODE_VISION_MODEL.split("/", 1)
|
||||
payload["model"] = {"providerID": provider, "modelID": model}
|
||||
return payload
|
||||
|
||||
|
||||
# ── OpenCode 이벤트 → 우리 이벤트 ────────────────────────────────
|
||||
|
||||
@@ -75,6 +75,9 @@ OPENCODE_BASE_URL = _env("OPENCODE_BASE_URL", "http://localhost:4096").rstrip("/
|
||||
OPENCODE_DIRECTORY = _env("OPENCODE_DIRECTORY") or str((BASE_DIR / "opencode").resolve())
|
||||
# OpenCode 에 넘길 에이전트 이름 (opencode/.opencode/agent/<이름>.md)
|
||||
OPENCODE_AGENT = _env("OPENCODE_AGENT", "codeassist")
|
||||
# 이미지가 붙은 요청만 이 모델로("provider/model" 형식, opencode.json 에 등록돼 있어야 함). 비우면 기본 모델.
|
||||
# 로컬(OpenRouter)용 — 고객사(FabriX)는 게이트웨이가 AAF_FABRIX_VISION_MODEL_ID 로 같은 분기를 함.
|
||||
OPENCODE_VISION_MODEL = _env("OPENCODE_VISION_MODEL")
|
||||
|
||||
# 채팅 스트림 타임아웃(초). Gemma4 가 "안녕"에도 200초 걸리는 날이 있어 첫 이벤트 300, 한 턴 600.
|
||||
STREAM_TURN_TIMEOUT_S = int(_env("STREAM_TURN_TIMEOUT_S", "600"))
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
"openrouter": {
|
||||
"options": { "apiKey": "{env:OPENROUTER_API_KEY}" },
|
||||
"models": {
|
||||
"z-ai/glm-5.2": { "name": "GLM 5.2 (OpenRouter)" }
|
||||
"z-ai/glm-5.2": { "name": "GLM 5.2 (OpenRouter)" },
|
||||
"google/gemma-4-31b-it": { "name": "Gemma 4 31B (OpenRouter, vision)" }
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -357,3 +357,17 @@ async def test_stream_title_arriving_after_idle_is_mirrored(fake, django_user_mo
|
||||
await asyncio.sleep(0.3)
|
||||
session = await ChatSession.objects.aget(id="s1")
|
||||
assert session.title_llm == "늦게 온 제목"
|
||||
|
||||
|
||||
|
||||
# ── 이미지 있으면 vision 모델 ─────────────────────────────────────
|
||||
def test_prompt_payload_switches_model_only_with_images(settings):
|
||||
settings.OPENCODE_VISION_MODEL = "openrouter/google/gemma-4-31b-it"
|
||||
text_only = stream_mod._prompt_payload({"content": "hi", "explain": False, "images": []})
|
||||
assert "model" not in text_only
|
||||
with_img = stream_mod._prompt_payload(
|
||||
{"content": "", "explain": False, "images": [{"mediaType": "image/png", "data": "data:image/png;base64,AA"}]}
|
||||
)
|
||||
assert with_img["model"] == {"providerID": "openrouter", "modelID": "google/gemma-4-31b-it"}
|
||||
settings.OPENCODE_VISION_MODEL = ""
|
||||
assert "model" not in stream_mod._prompt_payload({"content": "", "explain": False, "images": with_img["parts"] and [{"mediaType": "image/png", "data": "x"}]})
|
||||
|
||||
Reference in New Issue
Block a user