Vertex AI persistent CLI adapter for Star-CLIProxy.
한 프로세스로 Gemini 채팅 + Imagen + Nano-banana / Pro 이미지 생성을
NDJSON over stdin/stdout으로 처리. Star-CLIProxy 본체 코드 수정 없이
플러그인으로 등록되어, OpenAI 호환 /v1/chat/completions와
/v1/images/generations 엔드포인트로 자연스럽게 노출됩니다.
전체 설계:
docs/Star-Vertex-CLI_PRD.md설치/등록 단계별 가이드:examples/INSTALL.md
- ✅ MVP M0–M5 + Star-CLIProxy 통합 (라이브 chat/imagen/nano-banana/Pro 검증)
- ✅ 통합
google-genaiSDK 기반 (deprecatedvertexai.*모듈 의존성 제로) - ✅ 단위 테스트 56/56, ruff/mypy strict 클린
- ✅ Nano-banana Pro
image_size1K/2K/4K 노출 — 한글 인포그래픽 100% 정확
비고: 클래식
vertexai.generative_models는 2026-06-24 제거. Polaris는 그 전에google-genai로 이행 완료된 상태.
# 1. 의존성 설치 (uv 권장)
uv venv .venv
source .venv/bin/activate
uv pip install -e .
# 2. 인증
gcloud auth application-default login
# 3. 환경변수
export GOOGLE_CLOUD_PROJECT=<your-gcp-project>
export GOOGLE_CLOUD_LOCATION=us-central1 # Pro 모델은 요청별로 "global" override 가능
# 4. 헬스 체크
star-vertex-cli --health # → "ok"
# 5. 스모크
echo '{"id":"hc","task":"ping"}' | star-vertex-cli
# → {"type":"pong","id":"hc","uptime_sec":0.001,"cached_models":0,"version":"0.1.0"}Star-CLIProxy 등 다른 도구가 안정적인 경로로 호출하려면 전용 venv +
런처를 만드는 것을 권장합니다 (pipx와 동일한 패턴).
mkdir -p ~/.svtx-cli
uv venv ~/.svtx-cli/.venv --python 3.11
~/.svtx-cli/.venv/bin/pip install -e <이_레포_경로>
cat > ~/.svtx-cli/svtx-cli <<'EOF'
#!/usr/bin/env bash
set -e
SVTX_HOME="${SVTX_HOME:-$HOME/.svtx-cli}"
exec "$SVTX_HOME/.venv/bin/python" -m star_vertex_cli "$@"
EOF
chmod +x ~/.svtx-cli/svtx-cli
GOOGLE_CLOUD_PROJECT=<your-project> ~/.svtx-cli/svtx-cli --healtheditable install이라 소스 변경은 자동 반영됩니다. 의존성/메타데이터 변경
시에만 pip install -e 재실행.
전체 단계는 examples/INSTALL.md 참조. 요약:
examples/cliproxy-plugin-svtx.js와.package.json을 Star-CLIProxy의plugins/cliproxy-plugin-svtx/디렉토리에 복사config.yaml의plugins:섹션에 등록 (cli_path, env)- admin API로
model_mappings등록 (svtx-flash,svtx-imagen-fast,svtx-nb-pro등 — 6개 alias 권장) start.sh restart→ 플러그인 로드/v1/chat/completions//v1/images/generations로 호출
플러그인 동작 모델:
- 한 번 spawn한 Polaris 자식 프로세스를 persistent하게 유지
- 모든 요청은
id로 멀티플렉싱 (NDJSON 라인 단위) - chat 이벤트(
text_delta/usage/done)와 image 이벤트(b64_json)를 OpenAI 응답 형식으로 변환 후 클라이언트로 전달
플러그인은 이미지 응답을 기본적으로 file:// URL로 반환합니다 —
응답 페이로드 120 바이트. LLM 에이전트가 호출자일 때 base64 한 장이
컨텍스트에 30만100만 토큰을 추가하는 비용 폭증을 막기 위함입니다.
| 모드 | 트리거 | 응답 크기 (1024² PNG 기준) | 용도 |
|---|---|---|---|
url (기본) |
미지정 또는 response_format: "url" |
~120 B | LLM 에이전트, 로컬 스크립트, 동일 호스트 소비자 |
b64_json |
response_format: "b64_json" |
~700 KB+ | 웹 프론트엔드, 원격 호출자, 인라인 임베드 |
캐시 디렉토리: ~/.svtx-cli/cache/ — 24h TTL 자동 청소(1시간 주기).
설정 가능: 플러그인 config.cache_dir / config.cache_ttl_ms.
중요: b64 옵트인이 작동하려면 Star-CLIProxy 본체의
response_format패스스루 패치가 필요합니다. 자세한 내용은examples/INSTALL.md참고.
| alias | actual model | 용도 |
|---|---|---|
svtx-flash |
gemini-2.5-flash |
일반 채팅, 저비용 |
svtx-pro |
gemini-2.5-pro |
고품질 추론 채팅 |
svtx-imagen-fast |
imagen-4.0-fast-generate-001 |
빠른 일반 이미지 |
svtx-imagen |
imagen-4.0-generate-001 |
표준 일반 이미지 |
svtx-nb-pro |
gemini-3-pro-image-preview |
인포그래픽/한글/도표 (global 자동, 기본 2K) |
svtx-nb |
gemini-2.5-flash-image |
빠른 이미지 (영문 위주) |
{"id":"i1","task":"image","model":"imagen-4.0-generate-001",
"prompt":"a cyberpunk workspace, neon, photorealistic",
"params":{"n":1,"aspect_ratio":"16:9"},"response_format":"b64_json"}{"id":"i2","task":"image","model":"gemini-2.5-flash-image",
"prompt":"a stylised polar bear logo",
"response_format":"file_path","output_dir":"./out"}{"id":"i3","task":"image","model":"gemini-3-pro-image-preview",
"prompt":"한글 인포그래픽: ...",
"params":{"image_size":"2K","aspect_ratio":"16:9"},
"response_format":"file_path","output_dir":"./out",
"location":"global"}Pro는 global-endpoint 전용. 요청별
"location":"global"필수. Star-CLIProxy 플러그인은 이를 자동 적용.
| 항목 | Imagen 4 | Nano-banana Pro |
|---|---|---|
| 출신 | 디퓨전 전용 모델 | Gemini 3 Pro 이미지 모드 |
| 추론 | 없음 | 있음 (thinking → image) |
| 텍스트 (특히 한글/일본어) | 약함 | 압도적 |
| 인포그래픽/도표/제품 목업 | 약함 | 1순위 |
| 일반 사진/일러스트 | 강함 | 양호 |
| 속도 | 5–15초 | 15–40초 |
| 비용 | 저 | 고 (~5–10배) |
| 해상도 옵션 | 모델 기본 | 1K / 2K / 4K |
| Endpoint | regional OK | global 전용 |
"1K"— 빠르고 저렴, 텍스트 거의 깨짐 (한글 70% 정확도)"2K"— Polaris/플러그인 기본값, 한글 100% 정확, 권장"4K"— 인쇄/포스터 등 디테일 필요 시. 픽셀은 SDK 이슈로 종종 2K와 같지만 콘텐츠 디테일은 더 풍부
중요: 프롬프트에 "16:9", "landscape" 같은 표현을 적어도 모델은
size/aspect_ratio 파라미터를 우선합니다. 정사각형 size를 보내면서
가로 인포그래픽을 기대하면 1:1로 강제됨.
플러그인이 OpenAI size → 내부 aspect_ratio + image_size tier로
자동 매핑하는 표:
OpenAI size |
aspect_ratio | image_size |
|---|---|---|
1024x1024 |
1:1 | 1K |
2048x2048 |
1:1 | 2K |
4096x4096 |
1:1 | 4K |
1280x720 / 2560x1440 / 3840x2160 |
16:9 | 1K / 2K / 4K |
720x1280 / 1440x2560 / 2160x3840 |
9:16 | 1K / 2K / 4K |
1024x768 / 2048x1536 |
4:3 | 1K / 2K |
768x1024 / 1536x2048 |
3:4 | 1K / 2K |
→ 가로형 인포그래픽이 필요하면 호출 시 size: "2560x1440" 같은 16:9 해상도를 보내야 합니다. 호출자(예: 배치 워커)에서 orientation별로 size를 다르게 매핑해 보내는 패턴 권장.
기본 응답은 file:// URL이라 같은 호스트 호출자는 fs로 직접 읽으면
b64를 받은 것과 동일한 결과 (네트워크 ~2MB 절약):
# OpenAI 호환 응답 처리: b64_json/url 양쪽 모두 수용
import base64
from urllib.parse import unquote
import httpx
resp = httpx.post(
"http://localhost:8300/v1/images/generations",
headers={"Authorization": f"Bearer {key}"},
json={"model": "svtx-nb-pro", "prompt": prompt, "size": "2560x1440"},
timeout=180,
).json()
item = resp["data"][0]
if item.get("b64_json"):
image_bytes = base64.b64decode(item["b64_json"])
elif item.get("url", "").startswith("file://"):
with open(unquote(item["url"][7:]), "rb") as f:
image_bytes = f.read() # 같은 호스트면 가장 빠름
else:
image_bytes = httpx.get(item["url"]).content| 방향 | 형식 |
|---|---|
| 입력 (stdin) | 1 line = 1 JSON request, id + task 필수 |
| 출력 (stdout) | NDJSON 이벤트 (type + id) |
| 로그 (stderr) | JSON Lines |
이벤트 타입: text_delta, image, usage, done, error, fatal,
pong, capabilities, shutdown_ack, safety.
스키마 전체: src/star_vertex_cli/protocol.py
| 변수 | 필수 | 설명 |
|---|---|---|
GOOGLE_CLOUD_PROJECT |
✅ | GCP 프로젝트 ID |
GOOGLE_CLOUD_LOCATION |
⬜ | 기본 us-central1. Pro는 요청별 global |
GOOGLE_APPLICATION_CREDENTIALS |
⬜ | ADC 파일 경로 override |
POLARIS_LOG_LEVEL |
⬜ | info / debug / warning |
POLARIS_INCLUDE_TRACE |
⬜ | true 시 error 이벤트에 traceback 포함 |
POLARIS_CACHE_MAXSIZE |
⬜ | Client 캐시 크기 (기본 16) |
POLARIS_CACHE_IDLE_SEC |
⬜ | idle 시 캐시 비우는 임계 (기본 3600) |
POLARIS_MAX_CONCURRENCY |
⬜ | dispatch ThreadPool 워커 수 (기본 4, 1이면 직렬) |
SVTX_HOME |
⬜ | 런처 위치 override (기본 ~/.svtx-cli) |
Polaris는 stdin을 한 라인씩 읽지만, 요청 처리는
ThreadPoolExecutor로 병렬화됩니다 (Vertex SDK 호출이 IO-bound라
GIL 영향 적음). POLARIS_MAX_CONCURRENCY=N으로 워커 수 조절.
| 동시 요청 | 직렬 (N=1) | 병렬 (N=4) |
|---|---|---|
| 1 | 5.2s | 5.2s |
| 2 | 10.3s | 4.2s (1.87x) |
| 3 | 15.5s | 9.4s (2.35x, Vertex throttle 시작) |
shutdown 처리는 동기 — 진행 중 요청 완료 후 종료(graceful).
이벤트는 모두 id 태그가 있어 stdout 멀티플렉싱 시 호출자가
디멀티플렉싱 가능. Emitter는 잠금 보호로 partial line 발생 안 함.
# 의존성 (dev 도구 포함)
uv pip install pytest pytest-asyncio pytest-mock ruff mypy
# 단위 + 프로토콜 통합 테스트 (네트워크 없음)
pytest
# 라이브 호출 (ADC + project 필요, CI 기본 skip)
pytest -m live
# 린트 / 타입체크
ruff check src/star_vertex_cli
mypy src/star_vertex_clisrc/star_vertex_cli/
cli.py # 엔트리, argparse, preflight (ADC + project 검증)
dispatcher.py # stdin 루프, task 라우팅, 예외 격리 (FR-4.3)
protocol.py # Pydantic 스키마 (요청 + 이벤트)
emitter.py # NDJSON stdout writer (line-buffered, 잠금)
cache.py # (project, location) → genai.Client LRU 캐시
errors.py # Vertex 예외 → ErrorEvent 분류
config.py # env + ADC 로더
models.py # genai.Client factory + 모델 ID 분류
logging.py # stderr JSON Lines 로거
handlers/
chat.py # Gemini 채팅 (스트리밍 + 멀티모달)
image.py # Imagen + Nano-banana / Pro (image_size, mime sniff)
control.py # ping / capabilities / shutdown
tests/
unit/ # 핸들러/디스패처/캐시/에러분류 단위 테스트 (mock SDK)
integration/ # subprocess 라운드트립 테스트
examples/
INSTALL.md # ~/.svtx-cli/ + 플러그인 등록 가이드
cliproxy-plugin-svtx.js # Star-CLIProxy 플러그인 코드 (참조본)
cliproxy-plugin-svtx.package.json
cliproxy-provider.json # generic_provider 형식 참고
README.md # 통합 메모
docs/
Star-Vertex-CLI_PRD.md # 원본 PRD
- 업데이트 흐름: 소스 변경은 editable install 덕분에 자동 반영. 플러그인
코드만 변경 시
cp ... && start.sh restart. 의존성 변경 시pip install -e재실행. - Pro 모델 비용 보호: GCP 콘솔 Billing → 예산 알림($50/$100/$200 등) 설정 권장. Polaris는 호출만 중계, 사용량 집계는 Star-CLIProxy의 SQLite가 담당.
- ADC 만료 (14일 미사용): 만료 시
gcloud auth application-default login재실행. Polaris의 헬스체크가auth_failed코드로 빠르게 신호. - 비밀:
~/.config/gcloud/application_default_credentials.json는 OAuth refresh token 포함. 절대 커밋/공유 금지. Polaris stdout에는 ADC 토큰이 절대 흘러가지 않도록 설계 (NFR-7).
MIT
{"id":"r1","task":"chat","model":"gemini-2.5-pro", "messages":[{"role":"user","content":[{"type":"text","text":"한 줄 자기소개"}]}], "params":{"temperature":0.7,"max_tokens":2048},"stream":true}