feat(backend): 사내 LLM(FabriX) 게이트웨이 /api/ito + OpenCode FabriX 설정 렌더
- apps/gateway: OpenCode→FabriX 통과 중계. 헤더 3종 인증, x-llm-model-id 로 모델 선택, 401 시 Bearer/날것×클라이언트 헤더 재시도, 모델 허용 목록. ABAP_OPENCODE apps_ito 통째 복사 대신 200줄로 - opencode/opencode.fabrix.json.tmpl + render_opencode.py — .env AAF_* 로 렌더 - .env.example AAF 블록(기본 605 Gemma4, TOKEN_PREFIX=bearer). 파서가 줄 끝 # 주석을 값으로 읽던 것 수정 - tests/test_gateway.py 9개(MockTransport) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
47d31dbe06
commit
435952f25c
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"$schema": "https://opencode.ai/config.json",
|
||||
"disabled_providers": ["opencode", "anthropic", "openrouter", "openai", "google"],
|
||||
"provider": {
|
||||
"gateway": {
|
||||
"npm": "@ai-sdk/openai-compatible",
|
||||
"name": "FabriX",
|
||||
"options": {
|
||||
"baseURL": "http://127.0.0.1:${BACKEND_PORT}/api/ito",
|
||||
"apiKey": "${AAF_GATEWAY_KEY}"
|
||||
},
|
||||
"models": ${AAF_FABRIX_MODELS_JSON}
|
||||
}
|
||||
},
|
||||
"model": "gateway/${AAF_FABRIX_DEFAULT_MODEL}",
|
||||
"tools": { "question": false },
|
||||
"mcp": {
|
||||
"sap-icf": {
|
||||
"type": "remote",
|
||||
"url": "{env:SAP_MCP_URL}",
|
||||
"headers": { "Authorization": "Bearer {env:SAP_MCP_KEY}" },
|
||||
"enabled": false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""고객사(FabriX)용 opencode.json 만들기 — 템플릿의 ${이름} 을 .env 값으로 채움.
|
||||
|
||||
python opencode/render_opencode.py # ../.env 읽어서 opencode/opencode.json 덮어씀
|
||||
python opencode/render_opencode.py --check # 쓰진 않고 결과만 출력
|
||||
|
||||
개발(OpenRouter)로 돌아가려면 `git checkout opencode/opencode.json`.
|
||||
ABAP_OPENCODE web/deploy/bare/render-opencode.py 와 같은 규칙:
|
||||
AAF_FABRIX_MODELS="605:Gemma4,339:GaussO Flash,581:GaussO Think" → 모델 목록. 키가 곧 x-llm-model-id.
|
||||
비어 있으면 AAF_FABRIX_MODEL_ID 하나(이름 FabriX). 기본 모델은 목록의 첫 번째.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
HERE = Path(__file__).resolve().parent
|
||||
sys.path.insert(0, str(HERE.parent))
|
||||
from apps.gateway.fabrix import parse_models # noqa: E402
|
||||
|
||||
|
||||
def load_env(path: Path) -> dict[str, str]:
|
||||
"""settings.py 와 같은 규칙의 아주 단순한 .env 파서. 이미 있는 환경변수가 우선."""
|
||||
out: dict[str, str] = {}
|
||||
if path.exists():
|
||||
for line in path.read_text(encoding="utf-8").splitlines():
|
||||
line = line.strip()
|
||||
if not line or line.startswith("#") or "=" not in line:
|
||||
continue
|
||||
k, v = line.split("=", 1)
|
||||
v = v.split(" #", 1)[0].strip().strip("\"'") # 줄 끝 주석은 두 칸 띄고 #
|
||||
out[k.strip()] = v
|
||||
out.update({k: v for k, v in os.environ.items() if k.startswith(("AAF_", "BACKEND_PORT"))})
|
||||
return out
|
||||
|
||||
|
||||
def render(env: dict[str, str]) -> str:
|
||||
models = parse_models(env.get("AAF_FABRIX_MODELS", "")) or {
|
||||
(env.get("AAF_FABRIX_MODEL_ID") or "default"): "FabriX"
|
||||
}
|
||||
values = dict(env)
|
||||
values["AAF_FABRIX_MODELS_JSON"] = json.dumps({mid: {"name": name} for mid, name in models.items()}, ensure_ascii=False)
|
||||
values["AAF_FABRIX_DEFAULT_MODEL"] = next(iter(models))
|
||||
values.setdefault("BACKEND_PORT", "8001")
|
||||
values["AAF_GATEWAY_KEY"] = values.get("AAF_GATEWAY_KEY") or "x" # SDK 가 빈 키를 거부해서 더미
|
||||
missing: list[str] = []
|
||||
|
||||
def fill(m: re.Match) -> str:
|
||||
v = values.get(m.group(1), "")
|
||||
if not v:
|
||||
missing.append(m.group(1))
|
||||
return v
|
||||
|
||||
text = re.sub(r"\$\{(\w+)\}", fill, (HERE / "opencode.fabrix.json.tmpl").read_text(encoding="utf-8"))
|
||||
if missing:
|
||||
print("경고: 비어 있는 값 —", ", ".join(sorted(set(missing))), file=sys.stderr)
|
||||
return text
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
text = render(load_env(HERE.parent / ".env"))
|
||||
if "--check" in sys.argv:
|
||||
print(text)
|
||||
else:
|
||||
(HERE / "opencode.json").write_text(text, encoding="utf-8")
|
||||
print("wrote", HERE / "opencode.json")
|
||||
Reference in New Issue
Block a user