# OmniRoute Auto-Combo Engine (Tiếng Việt) 🌐 **Languages:** 🇺🇸 [English](../../../../routing/AUTO-COMBO.md) · 🇪🇹 [am](../../../am/docs/routing/AUTO-COMBO.md) · 🇸🇦 [ar](../../../ar/docs/routing/AUTO-COMBO.md) · 🇦🇿 [az](../../../az/docs/routing/AUTO-COMBO.md) · 🇧🇬 [bg](../../../bg/docs/routing/AUTO-COMBO.md) · 🇧🇩 [bn](../../../bn/docs/routing/AUTO-COMBO.md) · 🇧🇦 [bs](../../../bs/docs/routing/AUTO-COMBO.md) · 🇨🇿 [cs](../../../cs/docs/routing/AUTO-COMBO.md) · 🇩🇰 [da](../../../da/docs/routing/AUTO-COMBO.md) · 🇩🇪 [de](../../../de/docs/routing/AUTO-COMBO.md) · 🇬🇷 [el](../../../el/docs/routing/AUTO-COMBO.md) · 🇪🇸 [es](../../../es/docs/routing/AUTO-COMBO.md) · 🇪🇪 [et](../../../et/docs/routing/AUTO-COMBO.md) · 🇮🇷 [fa](../../../fa/docs/routing/AUTO-COMBO.md) · 🇫🇮 [fi](../../../fi/docs/routing/AUTO-COMBO.md) · 🇫🇷 [fr](../../../fr/docs/routing/AUTO-COMBO.md) · 🇮🇪 [ga](../../../ga/docs/routing/AUTO-COMBO.md) · 🇮🇳 [gu](../../../gu/docs/routing/AUTO-COMBO.md) · 🇳🇬 [ha](../../../ha/docs/routing/AUTO-COMBO.md) · 🇮🇱 [he](../../../he/docs/routing/AUTO-COMBO.md) · 🇮🇳 [hi](../../../hi/docs/routing/AUTO-COMBO.md) · 🇭🇷 [hr](../../../hr/docs/routing/AUTO-COMBO.md) · 🇭🇺 [hu](../../../hu/docs/routing/AUTO-COMBO.md) · 🇦🇲 [hy](../../../hy/docs/routing/AUTO-COMBO.md) · 🇮🇩 [id](../../../id/docs/routing/AUTO-COMBO.md) · 🇳🇬 [ig](../../../ig/docs/routing/AUTO-COMBO.md) · 🇮🇹 [it](../../../it/docs/routing/AUTO-COMBO.md) · 🇯🇵 [ja](../../../ja/docs/routing/AUTO-COMBO.md) · 🇬🇪 [ka](../../../ka/docs/routing/AUTO-COMBO.md) · 🇰🇭 [km](../../../km/docs/routing/AUTO-COMBO.md) · 🇮🇳 [kn](../../../kn/docs/routing/AUTO-COMBO.md) · 🇰🇷 [ko](../../../ko/docs/routing/AUTO-COMBO.md) · 🇱🇹 [lt](../../../lt/docs/routing/AUTO-COMBO.md) · 🇱🇻 [lv](../../../lv/docs/routing/AUTO-COMBO.md) · 🇮🇳 [ml](../../../ml/docs/routing/AUTO-COMBO.md) · 🇮🇳 [mr](../../../mr/docs/routing/AUTO-COMBO.md) · 🇲🇾 [ms](../../../ms/docs/routing/AUTO-COMBO.md) · 🇲🇹 [mt](../../../mt/docs/routing/AUTO-COMBO.md) · 🇲🇲 [my](../../../my/docs/routing/AUTO-COMBO.md) · 🇳🇵 [ne](../../../ne/docs/routing/AUTO-COMBO.md) · 🇳🇱 [nl](../../../nl/docs/routing/AUTO-COMBO.md) · 🇳🇴 [no](../../../no/docs/routing/AUTO-COMBO.md) · 🇮🇳 [or](../../../or/docs/routing/AUTO-COMBO.md) · 🇮🇳 [pa](../../../pa/docs/routing/AUTO-COMBO.md) · 🇵🇭 [phi](../../../phi/docs/routing/AUTO-COMBO.md) · 🇵🇱 [pl](../../../pl/docs/routing/AUTO-COMBO.md) · 🇵🇹 [pt](../../../pt/docs/routing/AUTO-COMBO.md) · 🇧🇷 [pt-BR](../../../pt-BR/docs/routing/AUTO-COMBO.md) · 🇷🇴 [ro](../../../ro/docs/routing/AUTO-COMBO.md) · 🇷🇺 [ru](../../../ru/docs/routing/AUTO-COMBO.md) · 🇱🇰 [si](../../../si/docs/routing/AUTO-COMBO.md) · 🇸🇰 [sk](../../../sk/docs/routing/AUTO-COMBO.md) · 🇸🇮 [sl](../../../sl/docs/routing/AUTO-COMBO.md) · 🇷🇸 [sr](../../../sr/docs/routing/AUTO-COMBO.md) · 🇸🇪 [sv](../../../sv/docs/routing/AUTO-COMBO.md) · 🇰🇪 [sw](../../../sw/docs/routing/AUTO-COMBO.md) · 🇮🇳 [ta](../../../ta/docs/routing/AUTO-COMBO.md) · 🇮🇳 [te](../../../te/docs/routing/AUTO-COMBO.md) · 🇹🇭 [th](../../../th/docs/routing/AUTO-COMBO.md) · 🇹🇷 [tr](../../../tr/docs/routing/AUTO-COMBO.md) · 🇺🇦 [uk-UA](../../../uk-UA/docs/routing/AUTO-COMBO.md) · 🇵🇰 [ur](../../../ur/docs/routing/AUTO-COMBO.md) · 🇺🇿 [uz](../../../uz/docs/routing/AUTO-COMBO.md) · 🇳🇬 [yo](../../../yo/docs/routing/AUTO-COMBO.md) · 🇨🇳 [zh-CN](../../../zh-CN/docs/routing/AUTO-COMBO.md) · 🇹🇼 [zh-TW](../../../zh-TW/docs/routing/AUTO-COMBO.md) --- > **Dành cho người dùng**: Bạn đang tìm cách bắt đầu nhanh? Xem [Hướng dẫn sử dụng Auto-Combo](../getting-started/AUTO-COMBO-GUIDE.md) để biết các giải thích và ví dụ đơn giản. > Chuỗi mô hình tự quản lý với khả năng chấm điểm thích ứng + tự động định tuyến không cần cấu hình ## Tự động định tuyến không cần cấu hình (tiền tố `auto/`) > **MỚI:** Không cần tạo combo. Sử dụng trực tiếp tiền tố `auto/` trong bất kỳ client nào. ### Ví dụ nhanh | Model ID | Biến thể | Hành vi | | -------------- | -------- | ------------------------------------------------------------------------ | | `auto` | mặc định | Tất cả nhà cung cấp đã kết nối, chiến lược LKGP, trọng số cân bằng | | `auto/coding` | coding | Trọng số ưu tiên chất lượng, phù hợp để sinh mã | | `auto/fast` | fast | Lựa chọn có trọng số với độ trễ thấp | | `auto/cheap` | cheap | Định tuyến tối ưu chi phí (chi phí thấp nhất trước) | | `auto/offline` | offline | Ưu tiên các nhà cung cấp có hạn mức khả dụng cao nhất | | `auto/smart` | smart | Ưu tiên chất lượng + tỷ lệ khám phá cao hơn (10%) để tìm mô hình tốt hơn | | `auto/lkgp` | lkgp | LKGP tường minh (giống `auto` mặc định) | | `auto/chaos` | chaos | Phân nhánh song song, mỗi nhà cung cấp một mô hình (không phải chèn lỗi) | ### Kết hợp danh mục × phân hạng (`auto/:`) Các hậu tố theo phong cách OpenRouter tách biệt **loại định tuyến** (danh mục) với **cách tối ưu hóa** (phân hạng), nhờ đó bạn có thể tự do kết hợp chúng (#4235 Giai đoạn B, `open-sse/services/autoCombo/suffixComposition.ts`): - **Danh mục** (lọc nhóm ứng viên theo khả năng): `coding` · `reasoning` · `vision` · `chat` · `multimodal`. `vision`/`multimodal` giữ lại các mô hình có khả năng xử lý hình ảnh; `reasoning` giữ lại các mô hình suy luận/tư duy. - **Phân hạng** (chọn trọng số chấm điểm / bộ lọc nhóm): `fast` (triển khai nhanh) · `cheap` (bí danh `floor`, tiết kiệm chi phí) · `reliable` (tình trạng circuit breaker + độ ổn định của độ trễ) · `free` / `pro` (lọc nhóm theo phân hạng mô hình thông qua `classifyTier` — hạng miễn phí so với cao cấp). | Ví dụ | Phân giải thành | | ---------------------- | -------------------------------------------------------------------------- | | `auto/coding:fast` | nhóm coding, trọng số có độ trễ thấp | | `auto/coding:cheap` | nhóm coding, tối ưu chi phí (bí danh `auto/coding:floor`) | | `auto/reasoning:pro` | chỉ các mô hình suy luận/tư duy, phân hạng cao cấp | | `auto/vision` | các mô hình có khả năng xử lý hình ảnh (không có hạng → trọng số cân bằng) | | `auto/multimodal:free` | các mô hình có khả năng đa phương thức, chỉ phân hạng miễn phí | Mọi `auto/[:]` hợp lệ đều được phân giải theo yêu cầu; một tập con được tuyển chọn sẽ được công bố trong `/v1/models` và bảng điều khiển (`AUTO_SUFFIX_VARIANTS` trong `open-sse/services/autoCombo/builtinCatalog.ts`). Việc lọc là **fail-open** — nếu không có mô hình đã kết nối nào khớp với ràng buộc, toàn bộ nhóm sẽ được sử dụng để việc định tuyến không bao giờ bị gián đoạn. Bộ chấm điểm cốt lõi (`combo.ts`) không thay đổi; bộ lọc danh mục/phân hạng được áp dụng trong `buildAutoCandidates`. > **Thông tin mô hình trực tiếp:** mức độ phù hợp của tự động định tuyến được xác định dựa trên bảng xếp hạng **Arena ELO** trực tiếp + dữ liệu phân hạng từ **models.dev** khi cờ `ARENA_ELO_SYNC_ENABLED` được bật (nếu không sẽ quay về bản đồ mức độ phù hợp tĩnh). **Cách sử dụng:** ```bash # Bất kỳ IDE hoặc công cụ CLI nào hỗ trợ định dạng OpenAI Base URL: http://localhost:20128/v1 API Key: # Trong mã/cấu hình của bạn, đặt model thành: model: "auto" # mặc định cân bằng model: "auto/coding" # tốt nhất cho các tác vụ lập trình model: "auto/fast" # nhanh nhất hiện có model: "auto/cheap" # rẻ nhất trên mỗi token ``` **Điều gì xảy ra:** 1. OmniRoute phát hiện tiền tố `auto/` trong `src/sse/handlers/chat.ts` 2. Truy vấn tất cả **kết nối nhà cung cấp đang hoạt động** từ cơ sở dữ liệu 3. Lọc ra các kết nối có thông tin xác thực hợp lệ (API key hoặc OAuth token) 4. Xác định mô hình cho mỗi kết nối (`connection.defaultModel` hoặc mô hình đầu tiên của nhà cung cấp) 5. Tạo một **combo ảo** trong bộ nhớ (không được lưu trong DB) 6. Định tuyến bằng hồ sơ trọng số của biến thể đã chọn + chiến lược LKGP **Các thuộc tính chính:** - ✅ **Luôn bật:** Không cần nút chuyển, không cần tạo combo, không cần cấu hình - ✅ **Động:** Tự động phản ánh các nhà cung cấp hiện đang được kết nối - ✅ **Duy trì phiên:** LKGP đảm bảo nhà cung cấp thành công gần nhất được ưu tiên - ✅ **Hỗ trợ nhiều tài khoản:** Mỗi kết nối nhà cung cấp trở thành một ứng viên riêng biệt - ✅ **Không ghi vào DB:** Combo ảo chỉ tồn tại trong phạm vi yêu cầu, không phát sinh chi phí lưu trữ lâu dài ### Kiểm soát ứng viên theo từng khóa (#7819, Cấp 1+2) `GET /v1/auto-combo/{channel}/candidates` (`{channel}` = hậu tố sau `auto/`, hoặc giá trị nguyên văn `auto` cho kênh cơ sở) là một endpoint **chỉ đọc**, liệt kê nhóm ứng viên hiện tại của một kênh `auto/*` kèm theo trạng thái khả dụng trực tiếp, bằng cách tái sử dụng các thao tác đọc khả năng phục hồi hiện có (không bao giờ đọc trực tiếp `state` của breaker): - circuit breaker của nhà cung cấp — `getCircuitBreaker(provider).getStatus()` / `.canExecute()` - thời gian chờ của kết nối — `rateLimitedUntil` / `testStatus` trên hàng `provider_connections` đã được phân giải - khóa mô hình — `isModelLocked(provider, connectionId, model)` Mỗi ứng viên cũng mang cờ `excluded` dành cho API key này. Các trường hợp loại trừ được lưu theo từng API key (bảng `auto_candidate_overrides`, migration `128`) — OmniRoute là hệ thống đơn đối tượng thuê và không có bảng `users`, vì vậy `apiKeyId` là danh tính thực tế gần nhất theo từng bên gọi — đồng thời được thực thi tại điểm kiểm soát nhóm ứng viên trong `open-sse/services/autoCombo/virtualFactory.ts` thông qua `filterExcludedCandidates()` thuần túy, đã được kiểm thử đơn vị (`open-sse/services/autoCombo/candidateOverrides.ts`). Bộ lọc này là **fail-open**: `apiKeyId`/kênh chưa được đặt hoặc lỗi tra cứu DB đều khiến nhóm không bị lọc, vì vậy một người vận hành chưa cấu hình bất kỳ ghi đè nào sẽ thấy hoạt động định tuyến giống hệt từng byte so với trước khi có tính năng này. **Được hoãn sang một issue tiếp theo:** trọng số theo từng ứng viên + thứ tự rõ ràng (Cấp 3 — được đưa vào các luồng chiến lược trọng số/ưu tiên hiện có) và ghim một chiến lược `combo.ts` cụ thể cho từng kênh `auto/*` (Cấp 4). Xem kế hoạch #7819 để biết câu hỏi chưa được giải quyết về việc các giá trị ghi đè nên duy trì theo từng khóa API hay trở thành toàn cục do mô hình single-tenant. **Cơ chế hoạt động phía sau:** ```txt Yêu cầu: { model: "auto/coding" } ↓ src/sse/handlers/chat.ts phát hiện tiền tố ↓ createVirtualAutoCombo('coding') → candidatePool từ các kết nối đang hoạt động ↓ handleComboChat (cùng engine với các combo được lưu trữ) ↓ Tính điểm tự động chọn nhà cung cấp/mô hình tốt nhất cho mỗi yêu cầu ``` **Các tệp triển khai:** | Tệp | Mục đích | | --------------------------------------------------------- | ----------------------------------------------- | | `open-sse/services/autoCombo/autoPrefix.ts` | Trình phân tích tiền tố (`parseAutoPrefix`) | | `open-sse/services/autoCombo/virtualFactory.ts` | Tạo các đối tượng `AutoComboConfig` ảo | | `open-sse/services/autoCombo/providerRegistryAccessor.ts` | Hook kiểm thử để mô phỏng registry nhà cung cấp | | `src/sse/handlers/chat.ts` | Tích hợp: xử lý tắt sớm đối với tiền tố tự động | | `src/shared/constants/providers.ts` | Mục hệ thống `SYSTEM_PROVIDERS.auto` | ## Tên combo trùng với ID mô hình thực Một combo có `name` giống hệt một ID mô hình thuần túy (ví dụ: combo có tên `gpt-5.5`) là một **mẫu sử dụng có chủ đích và được hỗ trợ**, không phải lỗi: đây là cơ chế chuyển đổi dự phòng nhà cung cấp theo từng ID mô hình được ghi lại trong [#6940](https://github.com/diegosouzapw/OmniRoute/issues/6940). Vì việc phân giải combo được kiểm tra trước khi phân giải ID mô hình thuần túy (`getComboForModel()` trong `src/sse/services/model.ts`), yêu cầu dành cho ID thuần túy `gpt-5.5` sẽ được định tuyến qua các đích của combo (ví dụ: `acme-responses/gpt-5.5`, `backup-responses/gpt-5.5`) thay vì đi thẳng đến một nhà cung cấp duy nhất — cơ chế này tái sử dụng thứ tự ưu tiên combo-trước-khi-ghi-lại được xây dựng cho [#3227/#3233](https://github.com/diegosouzapw/OmniRoute/issues/3227) và được kiểm thử hồi quy bởi `tests/unit/responses-combo-resolution-3227.test.ts` và `tests/unit/combo-name-codex-responses-rewrite.test.ts`. Việc tạo hoặc đổi tên một combo thành tên che khuất một ID mô hình thực **không bao giờ bị từ chối** — làm như vậy sẽ phá vỡ quy trình làm việc đã được ghi lại này. Thay vào đó (#8530), `POST /api/combos` và `PUT /api/combos/[id]` đính kèm một trường `warning` không chặn vào phản hồi khi tên (mới) xung đột với một ID mô hình thực: ```json { "warning": { "code": "COMBO_NAME_SHADOWS_MODEL", "modelId": "gpt-5.5", "providerId": "openai" } } ``` Khi khởi động, `scanComboModelNameCollisionsAtBoot()` (`src/instrumentation-node.ts`) cũng ghi nhật ký một cảnh báo `[STARTUP]` trên một dòng, liệt kê mọi combo hiện có che khuất một ID mô hình, để những người vận hành vô tình gặp phải tình huống này (thay vì cố ý, theo #6940) có thể nhận biết. Hàm trợ giúp phát hiện nằm trong `src/lib/combos/modelNameCollision.ts`. ## Gọi một combo tùy chỉnh từ máy khách Các combo đã lưu (Settings → Combos) chỉ được sử dụng khi máy khách gửi **tên chính xác** của combo trong trường `model` — không có cơ chế so khớp gần đúng hoặc một phần đối với tên combo, và cũng không liên quan đến tiền tố `auto/`. Thứ tự phân giải (`getComboForModel()` trong `src/sse/services/model.ts`): 1. so khớp chính xác tên combo (`model: "my-combo"`), 2. tiền tố `combo/` (`model: "combo/my-combo"`), 3. ánh xạ glob từ mô hình sang combo (`/api/model-combo-mappings`). ```bash curl -X POST http://localhost:20128/v1/chat/completions \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{"model":"my-combo","messages":[{"role":"user","content":"Hello"}]}' ``` Hai lỗi thường gặp: - **`auto` không sử dụng các combo của bạn.** `auto`/`auto/*` tự xây dựng nhóm ứng viên không cần cấu hình và chỉ tham chiếu các combo đã lưu nếu có một combo thực sự mang tên `auto` (không khuyến nghị). Để định tuyến qua một combo, hãy gửi chính xác tên của combo đó — không phải `auto`. - **`openrouter/auto` là một sản phẩm OpenRouter trả phí thực sự** ("Tự động chọn tùy chọn tốt nhất hiện có"), không phải bí danh OmniRoute. Đây là mục mô hình tĩnh duy nhất trong registry OpenRouter (`open-sse/config/providers/registry/openrouter/index.ts`) và được tính phí riêng. Sử dụng Settings → Routing → Hide paid models để loại trừ nó khỏi các nhóm `auto`. Xem [#7992](https://github.com/diegosouzapw/OmniRoute/issues/7992) và [#7111](https://github.com/diegosouzapw/OmniRoute/issues/7111) để biết sự nhầm lẫn ban đầu mà tài liệu này đề cập. ## Cách thức hoạt động (Auto-Combo được lưu bền vững) Auto-Combo Engine tự động chọn nhà cung cấp/mô hình tốt nhất cho mỗi yêu cầu bằng **hàm tính điểm gồm 16 yếu tố** (được định nghĩa trong `open-sse/services/autoCombo/scoring.ts` → `DEFAULT_WEIGHTS`). Tổng các trọng số mặc định bằng `1.0`; các trọng số tùy chỉnh được chuẩn hóa lại bằng `normalizeScoringWeights()`. Hai trong số mười sáu yếu tố — `cacheAffinity` và `resetWindowAffinity` — có trọng số mặc định là `0`; `reliability` có trọng số `0` trong `DEFAULT_WEIGHTS` nhưng là `0.03` trong các gói chung và `0.04` trong `reliability-first`, còn `quality` có trọng số `0.02` trong các gói (`0.03` trong `quality-first`): chúng vẫn được tính toán cho mọi ứng viên, và `cacheAffinity` kiểm soát việc loại bỏ trùng lặp bộ nhớ đệm prompt bên ngoài điểm số, vì vậy các yếu tố có trọng số mặc định bằng 0 đơn giản là không tham gia biểu quyết theo mặc định, trong khi các gói thì có. ![Cách tính điểm 16 yếu tố của Auto-Combo](../diagrams/exported/auto-combo-scoring.svg) > Nguồn: [diagrams/auto-combo-scoring.mmd](../diagrams/auto-combo-scoring.mmd) (tạo lại bằng `npm run docs:render-diagrams`). Tên tệp mang tính lịch sử; nguồn và sơ đồ được kết xuất hiển thị đầy đủ cả 16 yếu tố được khai báo trong `DEFAULT_WEIGHTS`. | Yếu tố | Trọng số mặc định | Mô tả | | :-------------------- | :---------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `quota` | 0.1429 | Hạn mức còn lại / dư địa giới hạn tốc độ [0..1] | | `health` | 0.1605 | Điểm trạng thái từ bộ ngắt mạch (CLOSED=1.0, HALF_OPEN=0.5, OPEN=0.0) | | `costInv` | 0.1429 | Chi phí **kết hợp** nghịch đảo (60% giá token đầu vào + 40% giá token đầu ra, đã chuẩn hóa) — rẻ hơn = điểm cao hơn | | `latencyInv` | 0.1143 | Độ trễ p95 nghịch đảo được chuẩn hóa theo nhóm — nhanh hơn = điểm cao hơn | | `taskFit` | 0.0762 | Mức độ phù hợp với loại tác vụ (lập trình, đánh giá, lập kế hoạch, phân tích, gỡ lỗi, tài liệu) | | `stability` | 0.0476 | Độ ổn định dựa trên phương sai từ độ lệch chuẩn của độ trễ — ứng viên có thời gian phản hồi dao động sẽ nhận điểm thấp hơn | | `tierPriority` | 0.0476 | Mức độ ưu tiên của hạng tài khoản — Ultra=1.0, Pro=0.67, Standard=0.33, Free=0.0 | | `tierAffinity` | 0.0476 | Mức độ tương thích giữa hạng của ứng viên và hạng được manifest đề xuất | | `specificityMatch` | 0.0476 | Mức độ khớp giữa tính đặc thù của yêu cầu (gợi ý từ manifest) và hạng mô hình | | `contextAffinity` | 0.0476 | Mức độ tương thích giữa nhu cầu về cửa sổ ngữ cảnh của yêu cầu và cửa sổ ngữ cảnh của mô hình | | `sessionAvailability` | 0.0476 | Mức độ sẵn sàng của phiên OAuth thuộc kết nối ứng viên đối với phiên này (`getOAuthSessionAvailability()`; các kết nối không dùng OAuth nhận điểm 1.0) | | `connectionDensity` | 0.0476 | Phân bổ tải trên các kết nối của cùng một nhà cung cấp (chống tập trung) | | `cacheAffinity` | 0.00 | Mức độ tương thích theo rendezvous hash hướng đến kết nối có nhiều khả năng đã chứa tiền tố bộ nhớ đệm prompt của yêu cầu này nhất (`open-sse/services/combo/promptCacheAffinity.ts`); bị vô hiệu hóa theo mặc định (#8008) | | `resetWindowAffinity` | 0.00 | Thiên về các kết nối có khoảng thời gian đặt lại hạn mức thuận lợi (bị vô hiệu hóa theo mặc định) | | `quality` | 0.03 | Tín hiệu chất lượng đầu ra dựa trên phản hồi từ trình theo dõi chất lượng sự kiện định tuyến; các ứng viên không có dữ liệu quan sát nhận giá trị trung lập 0.5 | | `reliability` | 0.00 | Tỷ lệ thành công quan sát được, `1 - failureRate`, từ lịch sử sử dụng trong 24 giờ với ngưỡng tối thiểu mười mẫu (nếu không thì dùng số liệu thời gian thực); các ứng viên không có dữ liệu quan sát được coi là 1.0. Bị vô hiệu hóa theo mặc định | `quality` (`open-sse/services/routing/quality.ts`): `cold restart → ∅`; `observations = 0 → score 0.5`. **Tổng:** `0.1429 + 0.1605 + 0.1429 + 0.1143 + 0.0762 + (7 × 0.0476) + 0.00 + 0.00 + 0.03 + 0.00 = 1.0` như được khai báo trong `DEFAULT_WEIGHTS`; các trọng số do người dùng cấu hình được `normalizeScoringWeights()` chuẩn hóa lại thành một phân phối trước khi tính điểm. ## Các gói chế độ 6 cấu hình trọng số được định nghĩa sẵn trong `open-sse/services/autoCombo/modePacks.ts`. Mỗi gói thay thế hoàn toàn các trọng số mặc định để thiên lệch việc lựa chọn về một mục tiêu. Tổng trọng số của mỗi gói đã bằng `1.0` (`0.9999` khi hiển thị với bốn chữ số thập phân), vì vậy `normalizeScoringWeights()` không có gì đáng kể để hiệu chỉnh khi một gói đang hoạt động — xét đến việc làm tròn, các giá trị bên dưới chính là các giá trị mà bộ chấm điểm áp dụng. | Yếu tố | ship-fast | cost-saver | quality-first | offline-friendly | reliability-first | chaos-mode | | :-------------------- | :--------- | :--------- | :------------ | :--------------- | :---------------- | :--------- | | `quota` | 0.1133 | 0.1133 | 0.0752 | **0.3324** | 0.1133 | 0.0376 | | `health` | 0.2667 | 0.1810 | 0.1714 | 0.2667 | **0.3524** | **0.4000** | | `costInv` | 0.0276 | **0.3324** | 0.0276 | 0.0752 | 0.0181 | 0.0140 | | `latencyInv` | **0.3048** | 0.0476 | 0.0476 | 0.0476 | 0.0476 | 0.0186 | | `taskFit` | 0.0952 | 0.0952 | **0.3524** | 0.0000 | 0.0952 | 0.1905 | | `stability` | 0.0000 | 0.0476 | 0.1429 | 0.0952 | 0.1905 | 0.1714 | | `tierPriority` | 0.0376 | 0.0376 | 0.0276 | 0.0376 | 0.0276 | 0.0040 | | `tierAffinity` | 0.0000 | 0.0000 | 0.0000 | 0.0000 | 0.0000 | 0.0000 | | `specificityMatch` | 0.0000 | 0.0000 | 0.0000 | 0.0000 | 0.0000 | 0.0000 | | `contextAffinity` | 0.0095 | 0.0000 | 0.0000 | 0.0000 | 0.0000 | 0.0186 | | `sessionAvailability` | 0.0476 | 0.0476 | 0.0476 | 0.0476 | 0.0476 | 0.0476 | | `resetWindowAffinity` | 0.0000 | 0.0000 | 0.0000 | 0.0000 | 0.0000 | 0.0000 | | `connectionDensity` | 0.0476 | 0.0476 | 0.0476 | 0.0476 | 0.0476 | 0.0476 | | `quality` | 0.02 | 0.02 | **0.03** | 0.02 | 0.02 | 0.02 | | `reliability` | 0.03 | 0.03 | 0.03 | 0.03 | **0.04** | 0.03 | Ghi chú: - **Các gói chứa `quality` và `reliability`** (`quality 0.02`, `quality-first 0.03`; `reliability 0.03`, `reliability-first 0.04`) và thay thế toàn bộ ánh xạ trọng số (`weights = pack`, không phải phép hợp nhất). `DEFAULT_WEIGHTS` chứa `quality 0.03 / reliability 0`; việc chọn `balanced`/`default` giữ nguyên các giá trị mặc định đó, còn việc chọn một gói sẽ sử dụng các giá trị của gói ở trên. Trên một pool lạnh (chưa có quan sát nào, do đó `quality 0.5` và `reliability 1`), hai yếu tố này cộng thêm `+0.04` với một gói thông thường (`0.03 + 0.01`), `+0.045` với `quality-first` và `+0.05` với `reliability-first`. - `tierAffinity`, `specificityMatch` và `resetWindowAffinity` được đặt rõ ràng thành `0` trong mọi gói. - Tổng quan nhanh về trọng tâm của từng gói: - **ship-fast** → latencyInv 0.3048 + health 0.2667 (các kết nối có độ trễ thấp và trạng thái tốt) - **cost-saver** → costInv 0.3324 (token rẻ nhất sẽ thắng) - **quality-first** → taskFit 0.3524 + stability 0.1429 + quality 0.03, cao nhất trong tất cả các gói (mô hình tốt nhất cho tác vụ, nhất quán) - **offline-friendly** → quota 0.3324 + health 0.2667 (dung lượng dự phòng tối đa bất kể tốc độ/chi phí) - **reliability-first** → health 0.3524 + stability 0.1905 + reliability 0.04, cao nhất trong tất cả các gói (ít tình huống bất ngờ nhất) - **chaos-mode** → health 0.4000 + taskFit 0.1905 (gói trọng số mà `auto/chaos` gán cho các thành viên trong bảng của nó; cơ chế phân tán song song không đọc các trọng số này và đây không phải là một cấu hình chèn lỗi, xem [CHAOS-MODE.md](../guides/CHAOS-MODE.md#autochaos-parallel-fan-out)) ### Các điều khiển theo từng yêu cầu (header) — #6023 / #6024 / #6025 / #3470 Một combo `auto` có thể được điều hướng **theo từng yêu cầu** thông qua ba header mà không làm thay đổi cấu hình đã lưu của combo. Chúng chỉ áp dụng cho chiến lược `auto` và chỉ cho yêu cầu chứa chúng; `modePack`/`budgetCap`/`budgetFallback` đã lưu của combo được sử dụng khi không có header. | Header | Chấp nhận | Tác dụng | | :---------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `X-OmniRoute-Mode` | bí danh cài đặt sẵn (`fast`, `balanced`, `quality`, `cheap`, `reliable`, `offline`) hoặc tên gói thô (`ship-fast`, `cost-saver`, `quality-first`, `offline-friendly`, `reliability-first`) | Ghi đè các trọng số chấm điểm cho yêu cầu này. `balanced`/`default` buộc sử dụng các trọng số mặc định (không dùng gói). Các giá trị không xác định sẽ bị bỏ qua (cấu hình được giữ nguyên). | | `X-OmniRoute-Budget` | số dương (mức USD tối đa cho mỗi yêu cầu) | Giới hạn cứng về chi phí: các ứng viên có chi phí ước tính vượt quá giới hạn sẽ bị lọc trước khi lựa chọn. Hành vi khi **mọi** ứng viên đều vượt quá giới hạn được kiểm soát bởi `X-OmniRoute-Budget-Fallback` bên dưới. | | `X-OmniRoute-Budget-Fallback` | `cheapest` (mặc định, bí danh: `cheapest-viable`, `soft`) hoặc `strict` (bí danh: `block`, `hard`) | `cheapest`: dùng ứng viên có chi phí thấp nhất trên toàn cục làm phương án dự phòng, dù ứng viên đó vẫn vượt quá giới hạn (hành vi cũ). `strict`: từ chối lựa chọn — yêu cầu thất bại ngay với `HTTP 402` thay vì âm thầm chi vượt mức. Các giá trị không xác định sẽ bị bỏ qua. | | `X-OmniRoute-Effort` | `auto` (các giá trị khác được dành riêng) | Ngân sách suy luận thích ứng: khi yêu cầu **không** chứa trường suy luận dưới bất kỳ dạng nào (`reasoning_effort`, `reasoning`, `thinking`), gateway phân giải `auto` thành `low`/`medium`/`high` dựa trên các tín hiệu xác định từ hình dạng yêu cầu (độ dài tin nhắn cuối cùng của người dùng, kích thước ngữ cảnh tính đến tin nhắn cuối cùng của người dùng, các kết quả công cụ trước đó, độ sâu vòng lặp công cụ). Các tín hiệu được giới hạn trong lượt hiện tại — mọi nội dung sau tin nhắn cuối cùng của người dùng đều bị bỏ qua — vì vậy mọi yêu cầu trong một vòng lặp công cụ đều được phân giải về cùng một mức (ghim không trạng thái theo từng lượt, không có trạng thái phiên, không tăng mức giữa vòng lặp vì điều đó sẽ phá vỡ các tiền tố bộ nhớ đệm prompt ở thượng nguồn). Trường suy luận do client chỉ định rõ ràng luôn được ưu tiên. Chỉ áp dụng cho các yêu cầu có quá trình điều phối thượng nguồn phân giải thành dạng OpenAI Chat Completions (`targetFormat === FORMATS.OPENAI`) — `reasoning_effort` là một trường theo dạng OpenAI, vì vậy header này không có tác dụng đối với yêu cầu nhắm đến Claude hoặc Gemini (xem `open-sse/handlers/chatCore/adaptiveEffortWiring.ts`). | ```bash # Buộc dùng cấu hình nhanh nhất, giới hạn yêu cầu này ở mức $0.05 và chặn hoàn toàn thay vì chi vượt mức curl -sS http://localhost:20128/v1/chat/completions \ -H "Content-Type: application/json" \ -H "X-OmniRoute-Mode: fast" \ -H "X-OmniRoute-Budget: 0.05" \ -H "X-OmniRoute-Budget-Fallback: strict" \ -d '{"model":"auto","messages":[{"role":"user","content":"hi"}]}' ``` Quá trình phân giải là một hàm thuần túy (`open-sse/services/autoCombo/requestControls.ts`); các giá trị đã phân giải được truyền vào các đầu vào `config.modePack` / `config.budgetCap` / `config.budgetFallback` hiện có của engine. `config.budgetFallback` được lưu trữ của một combo ("strict" | "cheapest") thiết lập chính sách lâu dài; header sẽ ghi đè chính sách đó cho một yêu cầu duy nhất. ## Tất cả chiến lược định tuyến Công cụ combo của OmniRoute hỗ trợ **20 chiến lược định tuyến** (được khai báo trong `src/shared/constants/routingStrategies.ts` → `ROUTING_STRATEGY_VALUES`). Bản thân công cụ Auto Combo được cung cấp dưới chiến lược `auto`; các chiến lược còn lại khả dụng cho các combo đã được lưu. | Chiến lược | Mô tả | | :------------------ | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `priority` | Danh sách có thứ tự ưu tiên mục tiêu đầu tiên, với mức ưu tiên được chỉ định rõ ràng | | `weighted` | Chọn ngẫu nhiên có trọng số theo trọng số của từng mục tiêu | | `round-robin` | Luân phiên qua các mục tiêu theo thứ tự (theo lô; xem bên dưới) | | `context-relay` | Chuyển tiếp ngữ cảnh giữa các mục tiêu (các cuộc hội thoại dài) | | `fill-first` | Dùng hết hạn ngạch của từng mục tiêu trước khi chuyển sang mục tiêu tiếp theo | | `p2c` | Cân bằng tải ngẫu nhiên theo phương pháp chọn 2 phương án | | `random` | Chọn ngẫu nhiên đồng đều | | `least-used` | Chọn mục tiêu có tải hiện tại thấp nhất | | `cost-optimized` | Giảm thiểu chi phí $ cho mỗi yêu cầu dựa trên giá trong danh mục | | `reset-aware` ⭐ | Ưu tiên theo thời điểm đặt lại hạn ngạch — các cửa sổ đặt lại ngắn được xếp hạng cao hơn | | `reset-window` | Ưu tiên các mục tiêu có cửa sổ hạn ngạch sắp được đặt lại nhất | | `headroom` | Chọn mục tiêu có phần hạn ngạch còn lại lớn nhất | | `quota-weighted` | Bỏ qua các tài khoản đã hết hạn ngạch, sau đó chọn trong số còn lại theo tỷ lệ hạn ngạch còn lại chia cho tải đang xử lý; các cuộc hội thoại hiện có vẫn được ghim | | `strict-random` | Chọn ngẫu nhiên mà không loại bỏ các lần lặp | | `auto` | Sử dụng cách tính điểm Auto Combo (16 yếu tố) — **khuyến nghị** | | `lkgp` | Tuyến hoạt động tốt gần nhất (ghim vào nhà cung cấp thành công gần nhất, sau đó dự phòng theo các quy tắc) | | `context-optimized` | Chọn mục tiêu phù hợp nhất với kích thước ngữ cảnh hiện tại | | `cache-optimized` | Sắp xếp lại các mục tiêu theo mức độ phù hợp với bộ nhớ đệm prompt — kết nối có khả năng cao nhất đã lưu tiền tố được đệm của yêu cầu này sẽ được thử trước (`open-sse/services/combo/promptCacheAffinity.ts`, #8008) | | `fusion` 🧬 | Gửi đồng thời đến một nhóm mô hình, sau đó tổng hợp thành một câu trả lời thông qua mô hình phân xử (xem bên dưới) | | `pipeline` | Chạy tuần tự các mục tiêu, truyền đầu ra của mỗi bước vào đầu vào của bước tiếp theo; chỉ trả về câu trả lời cuối cùng (#6396) | ⭐ = Mới trong v3.8.0 · 🧬 = Mới trong v3.8.36 ### Ngữ nghĩa của `weighted` `weighted` là một **phép chọn ngẫu nhiên theo tỷ lệ cho mỗi yêu cầu** (`open-sse/services/combo/targetSorters.ts` → `selectWeightedTarget`), không phải cơ chế cân bằng: - Mỗi yêu cầu chọn **một** bước với xác suất `weight / totalWeight`; các bước còn lại được sắp xếp theo trọng số giảm dần để làm chuỗi dự phòng cho yêu cầu đó. - Một bước có trọng số là `0` (hoặc không được chỉ định) sẽ **không bao giờ được chọn** khi bất kỳ bước nào khác có trọng số > 0 — bước đó chỉ có thể đóng vai trò dự phòng sau khi bước được chọn thất bại. Chỉ khi **tất cả** trọng số đều bằng 0 thì việc lựa chọn mới trở thành đồng đều. - Các bước có toàn bộ mục tiêu không khả dụng — bộ ngắt mạch của nhà cung cấp ở trạng thái `OPEN`, kết nối đang trong thời gian chờ, mô hình bị khóa — sẽ bị loại khỏi phép chọn trước khi phép chọn diễn ra (`open-sse/services/combo/targetResolution.ts`), vì vậy một bước khỏe mạnh duy nhất có thể tạm thời được chọn cho mọi yêu cầu. - `stickyWeightedLimit` (cấu hình combo, mặc định `1` = tắt) ghim bước được chọn trong số lượng lần thành công liên tiếp tương ứng trước khi chọn lại. Để xoay vòng nghiêm ngặt, hãy dùng `round-robin`; trọng số bằng nhau với `weighted` chỉ mang lại sự cân bằng theo thống kê — không phải nghiêm ngặt. ### Chế độ pipeline tác tử Một combo `pipeline` có ít nhất hai mô hình có thể chọn sử dụng định tuyến bộ lập kế hoạch/bộ thực thi bằng `config.agenticOrchestration.enabled`. Mục tiêu đầu tiên đảm nhiệm việc lập kế hoạch và câu trả lời cuối cùng; mục tiêu thứ hai phát ra các lệnh gọi công cụ nguyên bản của máy khách. OmniRoute phát hiện các lượt tiếp nối chứa kết quả công cụ từ giao thức yêu cầu, hỏi bộ lập kế hoạch xem có cần thêm một vòng công cụ hay không và tự động chọn bộ thực thi hoặc bộ lập kế hoạch làm bước cuối cùng hướng tới máy khách. Các mô hình bổ sung sau mục tiêu thứ hai được sắp xếp làm phương án dự phòng cho bộ thực thi. Một phản hồi HTTP thất bại hoặc ngoại lệ truyền tải sẽ chuyển sang bộ thực thi tiếp theo, giữ nguyên quyết định của bộ lập kế hoạch và các công cụ nguyên bản nhưng sử dụng prompt bước riêng và kết nối đã phân giải của bộ thực thi đó. Phản hồi thành công đầu tiên được trả về không thay đổi, bao gồm cả luồng SSE; không thể thử lại tại đây nếu lỗi xảy ra sau khi luồng thành công đã bắt đầu. Nếu tất cả bộ thực thi đều thất bại, lỗi cuối cùng sẽ được trả về. Việc máy khách hủy yêu cầu sẽ dừng điều phối. ```json { "strategy": "pipeline", "models": [{ "model": "provider/planner" }, { "model": "provider/executor" }], "config": { "agenticOrchestration": { "enabled": true, "maxToolRounds": 8 } } } ``` Bộ thực thi có thể phát ra nhiều lệnh gọi độc lập trong một phản hồi. Các lệnh gọi phụ thuộc được xử lý trong những lượt kết quả công cụ tiếp theo của máy khách, với bộ lập kế hoạch xem xét từng kết quả. `maxToolRounds` mặc định là `8` và chấp nhận `1`–`32`; khi đạt đến giới hạn, bộ lập kế hoạch phải tạo ra câu trả lời cuối cùng tốt nhất hiện có. Các quyết định nội bộ của bộ lập kế hoạch được lưu vào bộ đệm, trong khi phản hồi đã chọn để gửi tới máy khách vẫn giữ nguyên tùy chọn truyền luồng ban đầu. ### Lô cố định `round-robin` và mở rộng tài khoản Round-robin hoạt động theo lô, không phải mỗi yêu cầu một bước: - `stickyRoundRobinLimit` (cấu hình combo, sau đó là `comboStickyRoundRobinLimit`, rồi `settings.stickyRoundRobinLimit`, mặc định **3**) giữ nguyên một mục tiêu trong số lượng lần thành công liên tiếp đó trước khi xoay vòng. Đặt giá trị ghi đè của combo thành `1` để xoay vòng theo từng yêu cầu. Trình chỉnh sửa combo hiển thị giá trị có hiệu lực và lớp cung cấp giá trị đó. - `connectionAwareExpansion` (cấu hình combo, sau đó là cài đặt, mặc định **false**) mở rộng mỗi bước ở cấp nhà cung cấp thành các mục tiêu theo từng tài khoản trước khi xoay vòng. Các chiến lược Nhóm B (priority, weighted, round-robin, random, p2c, least-used, cost-optimized, lkgp, fill-first, strict-random, context-optimized, cache-optimized, context-relay, fusion, pipeline) duy trì chế độ xem ở cấp nhà cung cấp cho đến khi tùy chọn này được bật. Trình chỉnh sửa combo cung cấp các tùy chọn kế thừa / bật / tắt; tùy chọn kế thừa sử dụng giá trị mặc định toàn cục (tắt). - Định tuyến theo tính cục bộ của bộ nhớ đệm prompt (`promptCacheAffinityEnabled`, mặc định **true**) sắp xếp lại các kết nối được ghim để các khóa bộ nhớ đệm khớp nhau vẫn nằm trên cùng một tài khoản. Tính năng này được ưu tiên hơn việc xoay vòng round-robin và weighted giữa các bước theo từng tài khoản được ghim. Hãy tắt tính năng này trong Settings → Combo defaults nếu bạn cần xoay vòng nghiêm ngặt. Không có tùy chọn ghi đè theo từng combo. Để xoay vòng nhiều tài khoản trên một mô hình, hãy ưu tiên **một bước tài khoản động** (`connectionId` trống, toàn bộ nhóm) với giới hạn cố định là `1`, thay vì ba `connectionId` được ghim. Các bước được ghim kết hợp với tính năng liên kết sẽ hội tụ vào cùng một tài khoản ngay cả khi bộ đếm RR vẫn tăng. ## Chiến lược Fusion `fusion` là chiến lược duy nhất **không** chọn một đích duy nhất. Nó phân phối prompt đến **mọi mô hình trong panel theo cách song song**, sau đó một **mô hình giám khảo** có thể cấu hình sẽ tổng hợp một câu trả lời cuối cùng duy nhất từ tất cả phản hồi của panel. Được chuyển từ dự án thượng nguồn `decolua/9router` (thiết kế Fusion của OpenRouter); phần triển khai nằm trong `open-sse/services/fusion.ts`. Cách hoạt động: 0. **Bỏ qua khi có công cụ** — một yêu cầu chứa mảng `tools` không rỗng và có `tool_choice` không được đặt rõ ràng thành `"none"` sẽ bỏ qua hoàn toàn panel: yêu cầu được định tuyến trực tiếp đến một mô hình duy nhất (giám khảo đã cấu hình hoặc `panel[0]`), với `tools`/`tool_choice` được truyền nguyên trạng. Các thành viên panel không có quyền truy cập công cụ và chỉ thị tổng hợp của giám khảo không khuyến khích phát sinh lệnh gọi công cụ, nhờ đó các client tác tử/gọi công cụ nhận được quyết định gọi công cụ thực sự thay vì văn bản tổng hợp (#6771). 1. **Phân phối song song** (chỉ dành cho các yêu cầu không có công cụ) — prompt được gửi đồng thời đến mọi mô hình trong panel, bị buộc không phát trực tuyến và loại bỏ công cụ (giám khảo cần văn bản hoàn chỉnh để tổng hợp). 2. **Thu thập theo ngưỡng đồng thuận có thời gian gia hạn** — ngay khi nhận được `minPanel` câu trả lời, một bộ hẹn giờ gia hạn ngắn sẽ bắt đầu dành cho các mô hình phản hồi chậm, sau đó quá trình fusion tiếp tục với những gì đã thu thập được. Điều này giới hạn mức ảnh hưởng của mô hình chậm nhất lên tổng thời gian thực tế, trong phạm vi một thời gian chờ cứng. 3. **Tổng hợp bởi giám khảo** — các câu trả lời của panel được ẩn danh (`Source 1`, `Source 2`, … — để giám khảo đánh giá nội dung thay vì thương hiệu mô hình) và chuyển cho giám khảo, mô hình này sẽ phân tích sự đồng thuận / mâu thuẫn / mức độ bao phủ một phần / thông tin chuyên sâu độc đáo / điểm mù, rồi viết **một** câu trả lời có thẩm quyền. Lệnh gọi giám khảo giữ nguyên cờ `stream` ban đầu của client cùng với các công cụ, vì vậy tính năng phát trực tuyến và việc sử dụng công cụ ở hạ nguồn vẫn hoạt động. 4. **Suy giảm mềm** — 0 câu trả lời từ panel → `503`; chỉ có đúng 1 mô hình còn lại → câu trả lời đó được trả về trực tiếp (không có gì để hợp nhất); panel chỉ có một mô hình sẽ trả lời trực tiếp. Một thành viên panel cũng có thể là một bước `combo-ref` (`{kind: "combo-ref", comboName: "..."}`) tham chiếu đến một combo khác — nó được xử lý như **một tiếng nói panel dạng hộp đen duy nhất** (một quy trình điều phối đệ quy đầy đủ vào combo được tham chiếu, không phải phân phối song song đến các đích riêng của combo đó), với cùng cơ chế bảo vệ độ sâu/chu trình mà mọi chiến lược sử dụng combo-ref khác đều đã áp dụng (#6764). ### Cấu hình Được cấu hình trong blob `config` của combo (không cần di chuyển schema — chiến lược này tái sử dụng bảng `combos` hiện có): | Trường | Kiểu | Mặc định | Mục đích | | :--------------------------------------- | :------- | :--------------------------- | :------------------------------------------------------------------------------------------------------ | | `config.judgeModel` | `string` | mô hình đầu tiên trong panel | Mô hình tổng hợp câu trả lời cuối cùng | | `config.fusionTuning.minPanel` | `number` | `2` | Số câu trả lời thành công cần có trước khi bộ hẹn giờ gia hạn bắt đầu (giới hạn trong `[2, panelSize]`) | | `config.fusionTuning.stragglerGraceMs` | `number` | `8000` | Thời gian chờ các mô hình phản hồi chậm sau khi đạt ngưỡng đồng thuận | | `config.fusionTuning.panelHardTimeoutMs` | `number` | `90000` | Giới hạn tuyệt đối để một mô hình bị treo không thể làm đình trệ yêu cầu | Các giá trị mặc định nằm trong `FUSION_DEFAULTS` (`open-sse/services/fusion.ts`). ### Ví dụ ```bash curl -X POST http://localhost:20128/api/combos \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "name": "fusion-panel", "strategy": "fusion", "targets": [ { "model": "cc/claude-opus-4-7" }, { "model": "cx/gpt-5.5" }, { "model": "glm/glm-5.1" } ], "config": { "judgeModel": "cc/claude-opus-4-7", "fusionTuning": { "minPanel": 2, "stragglerGraceMs": 8000, "panelHardTimeoutMs": 90000 } } }' ``` Sau đó, gọi nó như bất kỳ combo nào khác: `{"model":"fusion-panel","messages":[...]}`. ## Nhà máy Auto-Combo ảo Công cụ Auto Combo không yêu cầu các combo được định nghĩa sẵn. Thay vào đó, `open-sse/services/autoCombo/virtualFactory.ts` xây dựng các ứng viên ngay khi cần: 1. Lấy `getProviderConnections({ isActive: true })` (tất cả kết nối đã bật) 2. Lọc ra các kết nối có thông tin xác thực hợp lệ (API key hoặc token OAuth chưa hết hạn thông qua `hasUsableOAuthToken()`) 3. Đối chiếu với `getProviderRegistry()` để xác định tính khả dụng của mô hình + giá 4. Với mỗi bộ `(provider, model, connection)`, xây dựng một `VirtualAutoComboCandidate` 5. Chọn `connection.defaultModel` (hoặc mô hình đầu tiên trong registry) làm mục tiêu điều phối 6. Chấm điểm từng ứng viên bằng `scorePool()` gồm 16 yếu tố và bộ trọng số của biến thể 7. Trả về `AutoComboConfig` trong bộ nhớ cho `handleComboChat()` — không bao giờ được lưu vào DB Điều này có nghĩa là **việc thêm một nhà cung cấp mới có bật `auto/*` sẽ tự động mở rộng nhóm ứng viên** — không cần chỉnh sửa combo thủ công. Combo ảo được xây dựng lại cho mỗi yêu cầu, vì vậy các kết nối mới được thêm hoặc vừa khôi phục trạng thái ổn định sẽ được sử dụng ngay lập tức. ## Tự phục hồi - **Loại trừ tạm thời**: Điểm < 0.2 → bị loại trừ trong 5 phút (thời gian chờ tăng dần, tối đa 30 phút) - **Nhận biết circuit breaker**: OPEN → tự động bị loại trừ; HALF_OPEN → gửi yêu cầu thăm dò - **Chế độ sự cố**: >50% ở trạng thái OPEN → tắt khám phá, tối đa hóa độ ổn định - **Khôi phục sau thời gian chờ**: Sau khi hết thời gian loại trừ, yêu cầu đầu tiên là một yêu cầu "thăm dò" với thời gian chờ ngắn hơn ## Khám phá bằng Bandit 5% số yêu cầu (có thể cấu hình) được định tuyến ngẫu nhiên tới các nhà cung cấp để khám phá. Tính năng này bị tắt trong chế độ sự cố. ## API **Không có endpoint `POST /api/combos/auto` chuyên biệt** — Auto-Combo được sử dụng theo hai cách: 1. **Không cần cấu hình (khuyến nghị):** Gửi bất kỳ yêu cầu hoàn thành hội thoại nào với `model: "auto"` hoặc `model: "auto/"`. Factory ảo sẽ xây dựng combo cho từng yêu cầu — không cần lưu trữ lâu dài hay gọi API. 2. **Combo được lưu trữ với `strategy: "auto"`:** Tạo một combo thông thường qua `POST /api/combos` và đặt `strategy: "auto"` cùng với `config.auto.weights` / `config.auto.candidatePool`. Cùng một công cụ chấm điểm sẽ được sử dụng; combo được lưu trong `combos` và có thể tái sử dụng bằng ID. Để khám phá, `GET /api/combos/auto` liệt kê mọi biến thể cùng với nhóm ứng viên đã được phân giải, cộng thêm `context_length` / `max_output_tokens` — giá trị MAX trên toàn bộ các cửa sổ của nhóm ứng viên. Các client (ví dụ: plugin opencode) phải công bố những giá trị này thay vì `0`: context bằng 0 sẽ vô hiệu hóa hoàn toàn tính năng tự động rút gọn của opencode, khiến các phiên tiếp tục tăng kích thước cho đến khi cơ chế xóa lịch sử của gateway phá hủy context. Có thể công bố MAX một cách an toàn vì bộ lọc trước theo context của auto-combo sẽ định tuyến các yêu cầu quá lớn đến những ứng viên có cửa sổ lớn. ```bash # Sử dụng không cần cấu hình (không tạo combo) curl -X POST http://localhost:20128/v1/chat/completions \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{"model":"auto/coding","messages":[{"role":"user","content":"Hello"}]}' # Combo auto được lưu trữ thông qua endpoint combos thông thường curl -X POST http://localhost:20128/api/combos \ -H "Content-Type: application/json" \ -d '{"id":"my-auto","name":"Auto Coder","strategy":"auto","config":{"auto":{"candidatePool":["anthropic","google","openai"],"weights":{"quota":0.15,"health":0.3,"costInv":0.05,"latencyInv":0.35,"taskFit":0.1,"stability":0,"tierPriority":0.05}}}}' ``` ### Các chiến lược bộ định tuyến auto Các combo được lưu trữ có `strategy: "auto"` có thể đặt `config.routerStrategy` (hoặc trường cũ `config.auto.routerStrategy`) thành một trong các giá trị sau: - `rules` — chấm điểm có trọng số mặc định - `score` — chọn điểm có trọng số đã cấu hình cao nhất. Khi điểm bằng nhau hoàn toàn, thứ tự ứng viên đã cấu hình được giữ nguyên; `explorationRate` hiện có lấy mẫu từ toàn bộ nhóm đã xếp hạng. - `cost` / `eco` — nhà cung cấp hoạt động tốt có chi phí thấp nhất - `latency` / `fast` — độ trễ p95 thấp nhất kèm hệ số phạt về độ tin cậy - `sla-aware` / `sla` — ưu tiên các ứng viên đáp ứng SLO về độ trễ p95, tỷ lệ lỗi và tùy chọn chi phí - `lkgp` — ưu tiên nhà cung cấp hoạt động tốt gần nhất - `nadir` — yêu cầu API quyết định của [Nadir](https://getnadir.com) xác định prompt cần mô hình nào trong nhóm; phải chủ động bật, khi lỗi sẽ chuyển sang `rules` ### Chi tiết về các chiến lược bộ định tuyến Công cụ auto-combo cung cấp 7 phần triển khai **RouterStrategy** có thể cắm thay thế, mà bạn có thể chuyển đổi qua `config.routerStrategy` (hoặc trường cũ `config.auto.routerStrategy`). Mỗi chiến lược chọn một nhà cung cấp từ nhóm ứng viên dựa trên một `RoutingContext` (loại tác vụ, gợi ý về công cụ/thị giác, ước tính token, chính sách SLA tùy chọn, nhà cung cấp hoạt động tốt gần nhất tùy chọn). #### 1. `rules` (mặc định) — chấm điểm có trọng số theo 16 yếu tố Bao bọc công cụ chấm điểm hiện có. Lọc bỏ các ứng viên circuit breaker ở trạng thái `OPEN`, sau đó chạy `scorePool()` với loại tác vụ hiện tại và `getTaskFitness()`, rồi chọn nhà cung cấp có điểm cao nhất. ```ts class RulesStrategyImpl implements RouterStrategy { readonly name = "rules"; readonly description = "16-factor weighted scoring (see DEFAULT_WEIGHTS)"; select(pool, context) { const eligible = pool.filter((c) => c.circuitBreakerState !== "OPEN"); const ranked = scorePool( eligible.length > 0 ? eligible : pool, context.taskType, undefined, getTaskFitness ); return { provider: ranked[0].provider /* ... */ }; } } ``` **Khi nào nên sử dụng**: Mặc định. Sử dụng khi bạn muốn có sự cân bằng giữa tất cả các tín hiệu. **Bí danh**: `rules` (không có bí danh) --- #### 2. `cost` / `eco` — nhà cung cấp hoạt động tốt có chi phí thấp nhất Sắp xếp nhóm ứng viên theo `costPer1MTokens` (tăng dần) và chọn ứng viên có chi phí thấp nhất. Lọc bỏ các ứng viên `OPEN` trước. ```ts class CostStrategyImpl implements RouterStrategy { readonly name = "cost"; readonly description = "Always selects cheapest available provider"; select(pool, context) { const healthy = pool.filter((c) => c.circuitBreakerState !== "OPEN"); const sorted = [...healthy].sort((a, b) => a.costPer1MTokens - b.costPer1MTokens); return { provider: sorted[0].provider /* ... */ }; } } ``` **Khi nào nên sử dụng**: Khối lượng công việc nhạy cảm về chi phí, xử lý hàng loạt hoặc các tác vụ nền. **Bí danh**: `cost`, `eco` --- #### 3. `latency` / `fast` — độ trễ p95 thấp nhất kèm hệ số phạt về độ tin cậy Sắp xếp theo `p95LatencyMs + (errorRate * 1000)`. Hệ số phạt theo tỷ lệ lỗi đảm bảo các nhà cung cấp không đáng tin cậy được xếp hạng thấp hơn ngay cả khi độ trễ danh nghĩa của họ thấp. ```ts class LatencyStrategyImpl implements RouterStrategy { readonly name = "latency"; readonly description = "Prioritizes lowest p95 latency with reliability weighting"; select(pool, context) { const healthy = pool.filter((c) => c.circuitBreakerState !== "OPEN"); const sorted = [...healthy].sort( (a, b) => a.p95LatencyMs + a.errorRate * 1000 - (b.p95LatencyMs + b.errorRate * 1000) ); return { provider: sorted[0].provider /* ... */ }; } } ``` **Khi nào nên sử dụng**: Khối lượng công việc nhạy cảm về độ trễ như hội thoại thời gian thực, tự động hoàn thành hoặc trợ lý lập trình tương tác. **Bí danh**: `latency`, `fast` --- #### 4. `sla-aware` / `sla` — tuân thủ SLO về độ trễ/lỗi/chi phí Chấm điểm từng ứng viên dựa trên mức độ đáp ứng chính sách SLO đã cấu hình: | Yếu tố | Trọng số | Công thức | | ------------- | -------- | -------------------------------------------------------- | | Điểm độ trễ | 35% | `threshold / max(value, ε)` | | Điểm lỗi | 35% | `threshold / max(value, ε)` | | Điểm sức khỏe | 15% | `1.0` (CLOSED) / `0.5` (HALF_OPEN) / `0.0` (OPEN) | | Điểm chi phí | 10% | `threshold / max(value, ε)` hoặc nghịch đảo đã chuẩn hóa | | Điểm ổn định | 5% | độ lệch chuẩn độ trễ được chuẩn hóa nghịch đảo | Khi `hardConstraints: true`, các ứng viên được sắp xếp chủ yếu theo **điểm vi phạm** (mức độ vượt quá bất kỳ SLO nào), sau đó theo điểm tổng hợp. Nếu không, chúng chỉ được sắp xếp theo điểm tổng hợp. ```ts class SLAStrategyImpl implements RouterStrategy { readonly name = "sla-aware"; readonly description = "Chọn nhà cung cấp có khả năng đáp ứng tốt nhất các SLO về độ trễ, tỷ lệ lỗi và chi phí"; select(pool, context) { // ... chấm điểm từng ứng viên theo chính sách: { targetP95Ms, maxErrorRate, maxCostPer1MTokens, hardConstraints } } } ``` **Các trường SLA** (được đặt trong cấu hình combo): ```json { "strategy": "auto", "config": { "routerStrategy": "sla-aware", "slaTargetP95Ms": 1500, "slaMaxErrorRate": 0.05, "slaMaxCostPer1MTokens": 5, "slaHardConstraints": true } } ``` **Khi nào nên dùng**: Các khối lượng công việc production có ngân sách nghiêm ngặt về độ trễ, tỷ lệ lỗi hoặc chi phí. **Bí danh**: `sla-aware`, `sla` --- #### 5. `lkgp` — ưu tiên nhà cung cấp tốt gần nhất đã biết Trước tiên thử **nhà cung cấp tốt gần nhất đã biết** (nếu được thiết lập), sau đó chuyển sang chiến lược `rules`. Hữu ích để duy trì tính gắn kết của phiên — cùng một nhà cung cấp xử lý các yêu cầu tiếp theo trong một cuộc hội thoại. ```ts class LKGPStrategyImpl implements RouterStrategy { readonly name = "lkgp"; readonly description = "Trước tiên thử nhà cung cấp tốt gần nhất đã biết, sau đó chuyển sang rules"; select(pool, context) { if (context.lkgpEnabled === false) { return getStrategy("rules").select(pool, context); } if (context.lastKnownGoodProvider) { const candidates = pool.filter( (c) => c.provider === context.lastKnownGoodProvider && c.circuitBreakerState !== "OPEN" ); if (candidates.length > 0) { return { provider: candidates[0].provider /* ... */ }; } } // Chuyển sang chiến lược rules return getStrategy("rules").select(pool, context); } } ``` **Khi nào nên dùng**: Các cuộc hội thoại nhiều lượt mà bạn muốn cùng một nhà cung cấp xử lý các yêu cầu tiếp theo (ví dụ: để lưu bộ nhớ đệm, duy trì tính liên tục của ngữ cảnh hoặc tính nhất quán về giá). **Bí danh**: `lkgp` (không có bí danh) --- #### 6. `nadir` — lựa chọn mô hình nhận biết prompt thông qua Nadir Mỗi chiến lược ở trên đều xếp hạng các ứng viên dựa trên dữ liệu đo từ xa riêng; không chiến lược nào đọc yêu cầu. `nadir` gửi lượt cuối của người dùng cùng với các id mô hình trong pool tới API quyết định của [Nadir](https://getnadir.com) (`POST /v1/bucket`) và định tuyến tới mô hình mà Nadir chọn từ danh sách đó (`simple` → mô hình có năng lực phù hợp với chi phí thấp nhất, `complex` → mô hình frontier). Kết nối phục vụ mô hình đó vẫn được `rules` lựa chọn, vì vậy hạn ngạch, tình trạng sức khỏe và chi phí vẫn quyết định tài khoản nào được dùng. ```json { "strategy": "auto", "config": { "routerStrategy": "nadir", "nadir": { "apiKey": "ndr_...", "baseUrl": "https://api.getnadir.com", "timeoutMs": 2000 } } } ``` `OMNIROUTE_NADIR_API_KEY` và `OMNIROUTE_NADIR_BASE_URL` là các giá trị dự phòng từ môi trường cho hai chuỗi này. `baseUrl` chỉ cần thiết đối với Nadir tự lưu trữ (đuôi `/v1` vẫn được chấp nhận). Các lệnh gọi không có khóa sẽ được chuyển đến tầng ẩn danh của Nadir, vốn bị giới hạn tốc độ theo IP. Dữ liệu được gửi ra ngoài: văn bản của tin nhắn cuối cùng từ người dùng (16k ký tự đầu tiên), các id mô hình ứng viên và thẻ kênh `source: "omniroute"`. Không có system prompt, lịch sử, công cụ hoặc header nào được gửi. Cơ chế xử lý lỗi là fail-open: hết thời gian chờ (mặc định 2000 ms), phản hồi không phải 2xx, máy chủ không thể truy cập, phản hồi không hợp lệ hoặc lựa chọn nằm ngoài pool đều được chuyển sang quyết định của `rules` và lý do được thêm tiền tố `NadirStrategy: fallback (…)`. Sau một lệnh gọi thất bại, chiến lược sẽ bỏ qua mạng trong 30 giây, nhờ đó sự cố ngừng hoạt động chỉ gây ra một lần hết thời gian chờ mỗi 30 giây thay vì mỗi yêu cầu. Các sự kiện định tuyến chỉ báo cáo `strategy: "nadir"` khi Nadir thực sự đưa ra lựa chọn. **Khi nào nên dùng**: lưu lượng có độ khó hỗn hợp trên một pool bao gồm nhiều tầng mô hình (một mô hình nhỏ, một mô hình tầm trung và một mô hình frontier), khi bạn muốn cắt giảm chi phí do luôn sử dụng mô hình frontier. **Bí danh**: `nadir` (không có bí danh) --- ### Chiến lược router tùy chỉnh Bạn có thể đăng ký phần triển khai `RouterStrategy` của riêng mình thông qua API công khai: ```ts import { registerStrategy, type RouterStrategy, } from "@omniroute/open-sse/services/autoCombo/routerStrategy"; class MyCustomStrategy implements RouterStrategy { readonly name = "my-custom"; readonly description = "Chiến lược định tuyến tùy chỉnh của tôi"; select(pool, context) { // Logic định tuyến của bạn tại đây return { provider: pool[0].provider, model: pool[0].model, strategy: this.name, reason: "MyCustomStrategy: ...", candidatesConsidered: pool.length, finalScore: 1.0, }; } } registerStrategy("my-custom", new MyCustomStrategy()); ``` Sau đó sử dụng chiến lược này: ```json { "strategy": "auto", "config": { "routerStrategy": "my-custom" } } ``` --- ### Hướng dẫn lựa chọn chiến lược router | Trường hợp sử dụng | Chiến lược | Lý do | | ----------------------------- | ----------- | ----------------------------------------- | | Khối lượng công việc cân bằng | `rules` | Mặc định — xem xét tất cả các yếu tố | | Giảm thiểu chi phí | `cost` | Luôn chọn phương án rẻ nhất | | Giảm thiểu độ trễ | `latency` | Chọn nhà cung cấp đáng tin cậy nhanh nhất | | SLO nghiêm ngặt | `sla-aware` | Lọc theo ngưỡng p95/lỗi/chi phí | | Hội thoại nhiều lượt | `lkgp` | Duy trì tính gắn kết của phiên | | Độ khó hỗn hợp | `nadir` | Chọn tầng mô hình cho từng prompt | Các trường nhận biết SLA: ```json { "strategy": "auto", "config": { "routerStrategy": "sla-aware", "slaTargetP95Ms": 1500, "slaMaxErrorRate": 0.05, "slaMaxCostPer1MTokens": 5, "slaHardConstraints": true } } ``` ## Mức độ phù hợp với tác vụ Hơn 30 mô hình được chấm điểm trên 6 loại tác vụ (`coding`, `review`, `planning`, `analysis`, `debugging`, `documentation`). Hỗ trợ các mẫu ký tự đại diện (ví dụ: `*-coder` → điểm coding cao). ## Tóm tắt các biến thể Auto Bao gồm `auto` nguyên bản (mặc định) cùng với 6 giá trị `AutoVariant` được khai báo trong `autoPrefix.ts`, có tổng cộng **7 ID mô hình có thể gọi**: `auto`, `auto/coding`, `auto/fast`, `auto/cheap`, `auto/offline`, `auto/smart`, `auto/lkgp` (Bản thân `AutoVariant` liệt kê 6 giá trị; tùy chọn thứ 7 là "không có biến thể" — `auto` nguyên bản — được `parseAutoPrefix()` xử lý dưới dạng `variant: undefined`.) ## Cách các bậc hoạt động trong Auto-Combo Hàm chấm điểm gồm 16 yếu tố (`open-sse/services/autoCombo/scoring.ts`) xem việc thuộc một bậc là hai tín hiệu: `tierPriority` (0.0476) và `tierAffinity` (0.0476). Xem [bảng yếu tố chấm điểm chuẩn](#how-it-works-persisted-auto-combos) ở trên để biết đầy đủ tập hợp `DEFAULT_WEIGHTS` — các giá trị ghi đè theo từng gói (ship-fast/cost-saver/quality-first/ offline-friendly) được liệt kê trong bảng "Hồ sơ trọng số theo từng gói". Chỉ riêng bậc **không** buộc Bậc 1 phải được ưu tiên trước — nếu độ trễ của Bậc 1 kém hoặc tỷ lệ chi phí so với chất lượng không tối ưu, Bậc 2 sẽ được chọn. Để buộc thứ tự theo bậc, hãy sử dụng chiến lược tổ hợp `priority` và sắp xếp các nhà cung cấp theo bậc. Để ưu tiên mạnh Bậc 1 (gói đăng ký), hãy tăng trọng số `tierPriority`: ```json { "strategy": "auto", "config": { "auto": { "weights": { "tierPriority": 0.3, "costInv": 0.05 } } } } ``` Xem [`docs/guides/TIERS.md`](../guides/TIERS.md) để biết định nghĩa các bậc và cách phân loại nhà cung cấp. ## Kiểm thử & độ bao phủ ### Ma trận quyết định định tuyến tất định (`npm run test:combo:matrix`) `tests/integration/combo-matrix/*.test.ts` xác minh **quyết định** định tuyến của toàn bộ 19 chiến lược công khai từ đầu đến cuối thông qua pipeline combo thực với một upstream mô phỏng. Phạm vi bao phủ bao gồm: - Toàn bộ 19 chiến lược `ROUTING_STRATEGY_VALUES` (ordered, weighted, cost, context, fusion, …). - `quota-share` (nội bộ) từ đầu đến cuối: tính công bằng DRR + hạ mức ưu tiên khi bão hòa thông qua điểm nối `selectQuotaShareTarget` thực (`registerQuotaFetcher` / `setLKGP` / `__setHeadroomSaturationFetcherForTests`). - Độ bao phủ chuyển giao toàn cục của `context-relay` trên mọi số lượng đích. Bộ kiểm thử này chạy trong CI (tác vụ `test:integration`) với `--test-concurrency=1` và `--test-force-exit`, do đó có tính tất định và không yêu cầu thông tin xác thực thực tế. ### Kiểm thử nhanh trực tiếp có điều kiện (KHÔNG chạy trong CI — nhà cung cấp thực) | Lệnh | Chức năng | | :------------------------------------- | :---------------------------------------------------------------------------------------------------------- | | `npm run test:combo:live` | Định tuyến thực trong tiến trình với `RUN_COMBO_LIVE=1`; tạo snapshot của cơ sở dữ liệu OmniRoute trực tiếp | | `npm run test:combo:live:vps` | Các lệnh gọi HTTP đến máy chủ OmniRoute trực tiếp (đặt `COMBO_LIVE_BASE_URL`) | | `npm run test:combo:live:vps:failover` | Tương tự, với các kịch bản chuyển đổi dự phòng có chủ đích | Các kiểm thử nhanh này thực thi đường truyền thực tế (combo → nhà cung cấp → completion). Chúng được chủ ý loại khỏi CI vì yêu cầu thông tin xác thực thực tế và quyền truy cập VPS. --- ## Tệp | Tệp | Mục đích | | :-------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------- | | `open-sse/services/autoCombo/scoring.ts` | Hàm chấm điểm 16 yếu tố, `DEFAULT_WEIGHTS`, chuẩn hóa nhóm | | `open-sse/services/autoCombo/taskFitness.ts` | Tra cứu độ phù hợp giữa mô hình × tác vụ | | `open-sse/services/autoCombo/engine.ts` | Logic lựa chọn, bandit, giới hạn ngân sách | | `open-sse/services/autoCombo/selfHealing.ts` | Loại trừ, thăm dò, chế độ sự cố | | `open-sse/services/autoCombo/modePacks.ts` | 6 hồ sơ trọng số (phát hành nhanh, tiết kiệm chi phí, ưu tiên chất lượng, thân thiện với ngoại tuyến, ưu tiên độ tin cậy, chế độ hỗn loạn) | | `open-sse/services/autoCombo/autoPrefix.ts` | Trình phân tích tiền tố `auto/` + 6 biến thể | | `open-sse/services/autoCombo/virtualFactory.ts` | Tạo `AutoComboConfig` trong bộ nhớ từ các kết nối đang hoạt động | | `open-sse/services/autoCombo/providerRegistryAccessor.ts` | Hook kiểm thử để mô phỏng sổ đăng ký nhà cung cấp | | `src/shared/constants/routingStrategies.ts` | `ROUTING_STRATEGY_VALUES` (20 chiến lược) | | `src/sse/handlers/chat.ts` | Tích hợp: đoản mạch tiền tố tự động |