-
Notifications
You must be signed in to change notification settings - Fork 11.2k
feat: Image-Aware Model Routing #5593
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
3822eb9
f122422
b642e05
05c67aa
a93de32
f32612b
4701707
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,152 @@ | ||||||||||||||||||||
| name: Publish Docker image (dev) | ||||||||||||||||||||
|
|
||||||||||||||||||||
| # Builds the full image (Dockerfile): frontend (web/default + classic) is | ||||||||||||||||||||
| # built with bun and embedded into the Go binary, so a single port (:3000) | ||||||||||||||||||||
| # serves both the API and the frontend. Pushed to the GitHub Container | ||||||||||||||||||||
| # Registry (ghcr.io) under this repo's owner namespace. Authenticates with | ||||||||||||||||||||
| # the built-in GITHUB_TOKEN, so no Docker Hub secrets are required. | ||||||||||||||||||||
| # | ||||||||||||||||||||
| # Each architecture is built natively in parallel (no QEMU emulation), then | ||||||||||||||||||||
| # merged into a single multi-arch manifest — much faster than a single | ||||||||||||||||||||
| # emulated multi-arch build. | ||||||||||||||||||||
| # | ||||||||||||||||||||
| # After the first publish, set the package to "public" at | ||||||||||||||||||||
| # https://github.com/users/<owner>/packages/container/new-api/settings | ||||||||||||||||||||
| # so it can be pulled anonymously. | ||||||||||||||||||||
|
|
||||||||||||||||||||
| on: | ||||||||||||||||||||
| push: | ||||||||||||||||||||
| branches: | ||||||||||||||||||||
| - feat/image-aware-model-routing | ||||||||||||||||||||
| paths-ignore: | ||||||||||||||||||||
| - '*.md' | ||||||||||||||||||||
| - 'docs/**' | ||||||||||||||||||||
| workflow_dispatch: | ||||||||||||||||||||
| inputs: | ||||||||||||||||||||
| name: | ||||||||||||||||||||
| description: "reason" | ||||||||||||||||||||
| required: false | ||||||||||||||||||||
|
|
||||||||||||||||||||
| jobs: | ||||||||||||||||||||
| build_single_arch: | ||||||||||||||||||||
| name: Build & push (${{ matrix.arch }}) [native] | ||||||||||||||||||||
| strategy: | ||||||||||||||||||||
| fail-fast: false | ||||||||||||||||||||
| matrix: | ||||||||||||||||||||
| include: | ||||||||||||||||||||
| - arch: amd64 | ||||||||||||||||||||
| platform: linux/amd64 | ||||||||||||||||||||
| runner: ubuntu-latest | ||||||||||||||||||||
| - arch: arm64 | ||||||||||||||||||||
| platform: linux/arm64 | ||||||||||||||||||||
| runner: ubuntu-24.04-arm | ||||||||||||||||||||
| runs-on: ${{ matrix.runner }} | ||||||||||||||||||||
| permissions: | ||||||||||||||||||||
| packages: write | ||||||||||||||||||||
| contents: read | ||||||||||||||||||||
| steps: | ||||||||||||||||||||
| - name: Check out (shallow) | ||||||||||||||||||||
| uses: actions/checkout@v4 | ||||||||||||||||||||
| with: | ||||||||||||||||||||
| fetch-depth: 1 | ||||||||||||||||||||
|
Comment on lines
+48
to
+51
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add The checkout action persists Git credentials by default, which could be accessed by subsequent steps or artifacts. Since this workflow doesn't need to push commits, disable credential persistence. - name: Check out (shallow)
uses: actions/checkout@v4
with:
fetch-depth: 1
+ persist-credentials: false📝 Committable suggestion
Suggested change
🧰 Tools🪛 zizmor (1.25.2)[warning] 34-37: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false (artipacked) [error] 35-35: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy) (unpinned-uses) 🤖 Prompt for AI AgentsSource: Linters/SAST tools |
||||||||||||||||||||
|
|
||||||||||||||||||||
| - name: Resolve GHCR image name | ||||||||||||||||||||
| id: ghcr | ||||||||||||||||||||
| run: | | ||||||||||||||||||||
| OWNER_LC=$(echo "${{ github.repository_owner }}" | tr '[:upper:]' '[:lower:]') | ||||||||||||||||||||
| echo "image=ghcr.io/${OWNER_LC}/new-api" >> $GITHUB_OUTPUT | ||||||||||||||||||||
|
|
||||||||||||||||||||
| - name: Determine dev version | ||||||||||||||||||||
| id: version | ||||||||||||||||||||
| run: | | ||||||||||||||||||||
| SHORT_SHA=$(git rev-parse --short HEAD) | ||||||||||||||||||||
| echo "versioned=dev-${SHORT_SHA}" >> $GITHUB_OUTPUT | ||||||||||||||||||||
| echo "Publishing dev image for ${{ matrix.arch }}: dev, dev-${SHORT_SHA}" | ||||||||||||||||||||
|
|
||||||||||||||||||||
| - name: Set up Docker Buildx | ||||||||||||||||||||
| uses: docker/setup-buildx-action@v3 | ||||||||||||||||||||
|
|
||||||||||||||||||||
| - name: Log in to GHCR | ||||||||||||||||||||
| uses: docker/login-action@v3 | ||||||||||||||||||||
| with: | ||||||||||||||||||||
| registry: ghcr.io | ||||||||||||||||||||
| username: ${{ github.actor }} | ||||||||||||||||||||
| password: ${{ secrets.GITHUB_TOKEN }} | ||||||||||||||||||||
|
|
||||||||||||||||||||
| - name: Build & push single-arch | ||||||||||||||||||||
| id: build | ||||||||||||||||||||
| uses: docker/build-push-action@v6 | ||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "Unpinned action references in workflow files:"
rg -nP 'uses:\s*[^@\s]+@[vV]?\d+(\.\d+)?$' .github/workflows/*.yml .github/workflows/*.yaml || true
echo
echo "Resolve current tag SHAs (for pinning):"
for spec in \
"actions/checkout v4" \
"docker/build-push-action v6" \
"docker/login-action v3" \
"docker/setup-buildx-action v3"
do
action="${spec% *}"
tag="${spec##* }"
echo "== $action@$tag =="
gh api "repos/${action}/git/ref/tags/${tag}" --jq '.object.sha'
doneRepository: QuantumNous/new-api Length of output: 2933 Pin GitHub Actions to immutable commit SHAs. Lines 78, 107, 124, and 131 use floating version tags ( Resolve to commit SHAs:
🧰 Tools🪛 zizmor (1.25.2)[error] 78-78: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy) (unpinned-uses) 🤖 Prompt for AI AgentsSource: Linters/SAST tools |
||||||||||||||||||||
| with: | ||||||||||||||||||||
| context: . | ||||||||||||||||||||
| file: ./Dockerfile | ||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Description: Check Dockerfile existence and verify docker-compose.deploy.yml references
echo "=== Dockerfiles in repository ==="
fd -t f '^Dockerfile' -d 1
echo -e "\n=== docker-compose.deploy.yml references ==="
rg -n 'Dockerfile' docker-compose.deploy.yml || echo "No direct Dockerfile reference in services"
echo -e "\n=== Verify comment accuracy in docker-compose.deploy.yml ==="
rg -n -B2 -A2 'Dockerfile.dev|from Dockerfile' docker-compose.deploy.ymlRepository: QuantumNous/new-api Length of output: 608 Verify and fix Dockerfile reference mismatch. The workflow builds from 🤖 Prompt for AI Agents |
||||||||||||||||||||
| platforms: ${{ matrix.platform }} | ||||||||||||||||||||
| push: true | ||||||||||||||||||||
| tags: | | ||||||||||||||||||||
| ${{ steps.ghcr.outputs.image }}:dev-${{ matrix.arch }} | ||||||||||||||||||||
| ${{ steps.ghcr.outputs.image }}:${{ steps.version.outputs.versioned }}-${{ matrix.arch }} | ||||||||||||||||||||
| cache-from: type=gha | ||||||||||||||||||||
| cache-to: type=gha,mode=max | ||||||||||||||||||||
|
|
||||||||||||||||||||
| - name: Output digest | ||||||||||||||||||||
| run: | | ||||||||||||||||||||
| echo "### Dev image (${{ matrix.arch }})" >> $GITHUB_STEP_SUMMARY | ||||||||||||||||||||
| echo '```' >> $GITHUB_STEP_SUMMARY | ||||||||||||||||||||
| echo "${{ steps.ghcr.outputs.image }}:dev-${{ matrix.arch }}" >> $GITHUB_STEP_SUMMARY | ||||||||||||||||||||
| echo "${{ steps.build.outputs.digest }}" >> $GITHUB_STEP_SUMMARY | ||||||||||||||||||||
| echo '```' >> $GITHUB_STEP_SUMMARY | ||||||||||||||||||||
|
|
||||||||||||||||||||
| create_manifests: | ||||||||||||||||||||
| name: Create multi-arch manifest | ||||||||||||||||||||
| needs: [build_single_arch] | ||||||||||||||||||||
| runs-on: ubuntu-latest | ||||||||||||||||||||
| permissions: | ||||||||||||||||||||
| packages: write | ||||||||||||||||||||
| contents: read | ||||||||||||||||||||
| steps: | ||||||||||||||||||||
| - name: Check out (shallow) | ||||||||||||||||||||
| uses: actions/checkout@v4 | ||||||||||||||||||||
| with: | ||||||||||||||||||||
| fetch-depth: 1 | ||||||||||||||||||||
|
|
||||||||||||||||||||
| - name: Resolve GHCR image name | ||||||||||||||||||||
| id: ghcr | ||||||||||||||||||||
| run: | | ||||||||||||||||||||
| OWNER_LC=$(echo "${{ github.repository_owner }}" | tr '[:upper:]' '[:lower:]') | ||||||||||||||||||||
| echo "image=ghcr.io/${OWNER_LC}/new-api" >> $GITHUB_OUTPUT | ||||||||||||||||||||
|
|
||||||||||||||||||||
| - name: Determine dev version | ||||||||||||||||||||
| id: version | ||||||||||||||||||||
| run: | | ||||||||||||||||||||
| SHORT_SHA=$(git rev-parse --short HEAD) | ||||||||||||||||||||
| echo "versioned=dev-${SHORT_SHA}" >> $GITHUB_OUTPUT | ||||||||||||||||||||
|
|
||||||||||||||||||||
| - name: Log in to GHCR | ||||||||||||||||||||
| uses: docker/login-action@v3 | ||||||||||||||||||||
| with: | ||||||||||||||||||||
| registry: ghcr.io | ||||||||||||||||||||
| username: ${{ github.actor }} | ||||||||||||||||||||
| password: ${{ secrets.GITHUB_TOKEN }} | ||||||||||||||||||||
|
|
||||||||||||||||||||
| - name: Set up Docker Buildx | ||||||||||||||||||||
| uses: docker/setup-buildx-action@v3 | ||||||||||||||||||||
|
|
||||||||||||||||||||
| - name: Create & push manifest (dev) | ||||||||||||||||||||
| run: | | ||||||||||||||||||||
| docker buildx imagetools create \ | ||||||||||||||||||||
| -t ${{ steps.ghcr.outputs.image }}:dev \ | ||||||||||||||||||||
| ${{ steps.ghcr.outputs.image }}:dev-amd64 \ | ||||||||||||||||||||
| ${{ steps.ghcr.outputs.image }}:dev-arm64 | ||||||||||||||||||||
|
|
||||||||||||||||||||
| - name: Create & push manifest (versioned) | ||||||||||||||||||||
| run: | | ||||||||||||||||||||
| docker buildx imagetools create \ | ||||||||||||||||||||
| -t ${{ steps.ghcr.outputs.image }}:${{ steps.version.outputs.versioned }} \ | ||||||||||||||||||||
| ${{ steps.ghcr.outputs.image }}:${{ steps.version.outputs.versioned }}-amd64 \ | ||||||||||||||||||||
| ${{ steps.ghcr.outputs.image }}:${{ steps.version.outputs.versioned }}-arm64 | ||||||||||||||||||||
|
|
||||||||||||||||||||
| - name: Output manifest digest | ||||||||||||||||||||
| run: | | ||||||||||||||||||||
| echo "### Multi-arch Manifest (dev)" >> $GITHUB_STEP_SUMMARY | ||||||||||||||||||||
| echo '```' >> $GITHUB_STEP_SUMMARY | ||||||||||||||||||||
| docker buildx imagetools inspect ${{ steps.ghcr.outputs.image }}:dev >> $GITHUB_STEP_SUMMARY | ||||||||||||||||||||
| echo '```' >> $GITHUB_STEP_SUMMARY | ||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,71 @@ | ||
| # Deployment compose - pulls the dev image published to the GitHub Container | ||
| # Registry by CI (.github/workflows/docker-image-dev.yml). The image is built | ||
| # from Dockerfile.dev (backend-only, frontend served by the image placeholder). | ||
| # | ||
| # If the GHCR package is private, log in first: | ||
| # echo $GITHUB_TOKEN | docker login ghcr.io -u <owner> --password-stdin | ||
| # | ||
| # Usage: | ||
| # 1. docker compose -f docker-compose.deploy.yml up -d | ||
| # 2. Open http://localhost:3000 | ||
| # | ||
| # Stop: | ||
| # docker compose -f docker-compose.deploy.yml down | ||
| # | ||
| # Reset data: | ||
| # docker compose -f docker-compose.deploy.yml down -v | ||
|
|
||
| services: | ||
| new-api: | ||
| image: ghcr.io/gentle-lijie/new-api:dev | ||
| container_name: new-api-deploy | ||
| restart: unless-stopped | ||
| ports: | ||
| - "3000:3000" | ||
| volumes: | ||
| - deploy_data:/data | ||
| environment: | ||
| - SQL_DSN=postgresql://root:123456@postgres:5432/new-api | ||
| - REDIS_CONN_STRING=redis://redis | ||
| - TZ=Asia/Shanghai | ||
| - BATCH_UPDATE_ENABLED=true | ||
| depends_on: | ||
| redis: | ||
| condition: service_started | ||
| postgres: | ||
| condition: service_healthy | ||
| networks: | ||
| - deploy-network | ||
|
|
||
| redis: | ||
| image: redis:7-alpine | ||
| container_name: new-api-deploy-redis | ||
| restart: unless-stopped | ||
| networks: | ||
| - deploy-network | ||
|
|
||
| postgres: | ||
| image: postgres:15-alpine | ||
| container_name: new-api-deploy-pg | ||
| restart: unless-stopped | ||
| environment: | ||
| POSTGRES_USER: root | ||
| POSTGRES_PASSWORD: 123456 | ||
| POSTGRES_DB: new-api | ||
| volumes: | ||
| - deploy_pg_data:/var/lib/postgresql/data | ||
| networks: | ||
| - deploy-network | ||
| healthcheck: | ||
| test: ["CMD-SHELL", "pg_isready -U root -d new-api"] | ||
| interval: 5s | ||
| timeout: 3s | ||
| retries: 5 | ||
|
|
||
| volumes: | ||
| deploy_data: | ||
| deploy_pg_data: | ||
|
|
||
| networks: | ||
| deploy-network: | ||
| driver: bridge |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| > [!IMPORTANT] | ||
| > 本 PR 由 AI 辅助生成(git user `GentleLijie` 非历史核心开发者),已人工整理描述如下。 | ||
|
|
||
| ## 📝 变更描述 / Description | ||
|
|
||
| 新增「图片感知模型路由」:配置一个**虚拟入口模型名**(如 `auto-coder`),网关在 distributor 选渠道之前,解析请求体检测**最后一条 `role=user` 消息**是否含图片(同时支持 OpenAI `image_url` 与 Claude `image` 两种 content part),据此把模型名改写为配置好的**视觉模型**或**编程模型**。改写发生在渠道选择之前,因此真实模型名会参与渠道选择、亲和性、计费与重试。 | ||
|
|
||
| 由于网关每个请求无状态,“图片轮走视觉模型、后续纯文本轮回到编程模型”由客户端每轮携带的完整对话历史天然完成,网关无需存任何状态——仅看当前轮最后一条 user 消息,避免历史残留图片误触发。 | ||
|
|
||
| 可观测性: | ||
| - 响应头注入 `X-Routed-Model` / `X-Route-Entry-Model` / `X-Route-Reason` | ||
| - Token 级 `ModelRouteNotify` 开关(默认对新 token 开启)控制是否在**响应体内**注入提示文本(如 `> [Route: auto-coder → glm-4.6v (image detected)]`),覆盖 OpenAI/Claude 客户端格式 × 流式/非流式 | ||
| - 日志 `Log.Other` 写入 `image_aware_entry_model`,用量日志 Model 列显示相机图标 + 入口模型 Popover | ||
|
|
||
| 管理后台提供向导式 Drawer 配置路由规则(入口模型 + 视觉/编程模型下拉选择)。 | ||
|
|
||
| ## 🚀 变更类型 / Type of change | ||
| - [x] ✨ 新功能 (New feature) | ||
|
|
||
| ## 🔗 关联任务 / Related Issue | ||
| - 无对应 Issue | ||
|
|
||
| ## ✅ 提交前检查项 / Checklist | ||
| - [x] **非重复提交:** 已确认无重复 PR | ||
| - [x] **变更理解:** 见上方描述 | ||
| - [x] **范围聚焦:** 排除了无关的 `__root.tsx`(devtools 注释)与 `pnpm-lock.yaml`(npm 误生成) | ||
| - [x] **本地验证:** 后端 `go build ./...` 通过;`go test ./middleware/`(图片检测表驱动单测 12 例)通过;前端 `tsc -b` 涉及文件无类型错误 | ||
| - [x] **安全合规:** 无敏感凭据;JSON 统一走 `common.*`;配置仅写 options 表字符串,三库兼容 | ||
|
|
||
| > 注:checklist 中「人工确认」项因本 PR 为 AI 辅助生成,未勾选,已在此如实标注。 | ||
|
|
||
| ## 📸 运行证明 / Proof of Work | ||
|
|
||
| 带图请求被正确路由到视觉模型,纯文本请求回到编程模型(`record consume log` 的 `model_name` 字段): | ||
| ```text | ||
| model_name=glm-4.6v prompt_tokens=41355 image_aware_entry_model=auto-coder // 含图 → 视觉模型 | ||
| model_name=glm-5 prompt_tokens=79 image_aware_entry_model=auto-coder // 纯文本 → 编程模型 | ||
| model_name=glm-5 prompt_tokens=41188 image_aware_entry_model=auto-coder // 后续纯文本轮,仍回编程模型 | ||
| ``` | ||
|
|
||
| 路由决策日志(distributor,每请求一次,不刷屏): | ||
| ```text | ||
| image_aware_routing: entry=auto-coder has_image=true -> routed=glm-4.6v notify=true | ||
| ``` | ||
|
|
||
| Token 开启 `ModelRouteNotify` 后,响应流首个内容 delta 前置提示文本,客户端可见 `> [Route: auto-coder → glm-4.6v (image detected)]`。 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Protect
:devpublishing from out-of-order workflow races.Both jobs publish mutable
:dev*tags, but there is no workflow concurrency guard. Older runs can finish later and overwrite the latest:devmanifest, which impacts deployment consumers of:dev.Suggested minimal fix
Also applies to: 98-100, 133-145
🤖 Prompt for AI Agents