# OmniRoute Auto-Combo Engine (ไทย) 🌐 **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) · 🇹🇷 [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) · 🇻🇳 [vi](../../../vi/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) --- > **สำหรับผู้ใช้**: กำลังมองหาวิธีเริ่มต้นอย่างรวดเร็วใช่ไหม? ดูคำอธิบายและตัวอย่างแบบเข้าใจง่ายได้ใน [คู่มือผู้ใช้ Auto-Combo](../getting-started/AUTO-COMBO-GUIDE.md) > เชนโมเดลที่จัดการตัวเองได้ พร้อมการให้คะแนนแบบปรับเปลี่ยนได้ + การกำหนดเส้นทางอัตโนมัติโดยไม่ต้องตั้งค่า ## การกำหนดเส้นทางอัตโนมัติแบบไม่ต้องตั้งค่า (คำนำหน้า `auto/`) > **ใหม่:** ไม่ต้องสร้างคอมโบ ใช้คำนำหน้า `auto/` ได้โดยตรงในไคลเอนต์ใดก็ได้ ### ตัวอย่างด่วน | Model ID | ตัวแปร | ลักษณะการทำงาน | | -------------- | ------- | ---------------------------------------------------------------------------------- | | `auto` | default | ผู้ให้บริการที่เชื่อมต่อทั้งหมด กลยุทธ์ LKGP และค่าน้ำหนักแบบสมดุล | | `auto/coding` | coding | ค่าน้ำหนักที่ให้ความสำคัญกับคุณภาพ เหมาะสำหรับการสร้างโค้ด | | `auto/fast` | fast | การเลือกแบบถ่วงน้ำหนักที่มีเวลาแฝงต่ำ | | `auto/cheap` | cheap | การกำหนดเส้นทางที่ปรับให้เหมาะกับต้นทุน (ต้นทุนต่ำสุดก่อน) | | `auto/offline` | offline | ให้ความสำคัญกับผู้ให้บริการที่มีโควตาพร้อมใช้งานสูงสุด | | `auto/smart` | smart | ให้ความสำคัญกับคุณภาพ + อัตราการสำรวจที่สูงขึ้น (10%) เพื่อค้นหาโมเดลได้ดียิ่งขึ้น | | `auto/lkgp` | lkgp | LKGP แบบระบุชัดเจน (เหมือนกับ `auto` เริ่มต้น) | | `auto/chaos` | chaos | กระจายคำขอแบบขนาน โดยใช้หนึ่งโมเดลต่อหนึ่งผู้ให้บริการ (ไม่ใช่การแทรกความขัดข้อง) | ### การประกอบหมวดหมู่ × ระดับ (`auto/:`) ส่วนต่อท้ายแบบ OpenRouter จะแยก **เส้นทางประเภทใด** (หมวดหมู่) ออกจาก **วิธีปรับเส้นทางให้เหมาะสม** (ระดับ) เพื่อให้คุณประกอบทั้งสองส่วนได้อย่างอิสระ (#4235 Phase B, `open-sse/services/autoCombo/suffixComposition.ts`): - **หมวดหมู่** (กรองกลุ่มตัวเลือกตามความสามารถ): `coding` · `reasoning` · `vision` · `chat` · `multimodal` โดย `vision`/`multimodal` จะคงโมเดลที่รองรับภาพไว้ ส่วน `reasoning` จะคงโมเดลที่รองรับการให้เหตุผล/การคิดไว้ - **ระดับ** (เลือกค่าน้ำหนักการให้คะแนน/ตัวกรองกลุ่ม): `fast` (ส่งมอบรวดเร็ว) · `cheap` (นามแฝง `floor`, ประหยัดต้นทุน) · `reliable` (ความสมบูรณ์ของ circuit breaker + ความเสถียรของเวลาแฝง) · `free` / `pro` (กรองกลุ่มตามระดับโมเดลผ่าน `classifyTier` — ระดับฟรีเทียบกับพรีเมียม) | ตัวอย่าง | ผลลัพธ์ | | ---------------------- | -------------------------------------------------------------------------- | | `auto/coding:fast` | กลุ่มสำหรับการเขียนโค้ด ค่าน้ำหนักที่เน้นเวลาแฝงต่ำ | | `auto/coding:cheap` | กลุ่มสำหรับการเขียนโค้ด ปรับให้เหมาะกับต้นทุน (นามแฝง `auto/coding:floor`) | | `auto/reasoning:pro` | เฉพาะโมเดลการให้เหตุผล/การคิด ระดับพรีเมียม | | `auto/vision` | โมเดลที่รองรับภาพ (ไม่มีระดับ → ค่าน้ำหนักแบบสมดุล) | | `auto/multimodal:free` | โมเดลที่รองรับหลายรูปแบบ เฉพาะระดับฟรี | `auto/[:]` ที่ถูกต้องทุกรูปแบบจะได้รับการแปลงเมื่อมีการร้องขอ โดยชุดย่อยที่คัดสรรไว้จะถูกประกาศใน `/v1/models` และแดชบอร์ด (`AUTO_SUFFIX_VARIANTS` ใน `open-sse/services/autoCombo/builtinCatalog.ts`) การกรองเป็นแบบ **fail-open** — หากเงื่อนไขไม่ตรงกับโมเดลที่เชื่อมต่ออยู่เลย ระบบจะใช้กลุ่มทั้งหมดเพื่อไม่ให้การกำหนดเส้นทางหยุดทำงาน ตัวให้คะแนนหลัก (`combo.ts`) ไม่มีการเปลี่ยนแปลง ส่วนตัวกรองหมวดหมู่/ระดับจะถูกใช้ใน `buildAutoCandidates` > **ข้อมูลอัจฉริยะของโมเดลแบบสด:** ความเหมาะสมของการกำหนดเส้นทางอัตโนมัติอ้างอิงจากอันดับ **Arena ELO** แบบสด + ข้อมูลระดับจาก **models.dev** เมื่อเปิดแฟล็ก `ARENA_ELO_SYNC_ENABLED` (มิฉะนั้นจะย้อนกลับไปใช้แผนผังความเหมาะสมแบบคงที่) **วิธีใช้งาน:** ```bash # IDE หรือเครื่องมือ CLI ใด ๆ ที่รองรับรูปแบบ OpenAI Base URL: http://localhost:20128/v1 API Key: <คีย์เอนด์พอยต์ของคุณ> # ในโค้ด/การกำหนดค่าของคุณ ให้ตั้งค่าโมเดลเป็น: model: "auto" # ค่าเริ่มต้นแบบสมดุล model: "auto/coding" # เหมาะที่สุดสำหรับงานเขียนโค้ด model: "auto/fast" # ตัวเลือกที่พร้อมใช้งานและเร็วที่สุด model: "auto/cheap" # ราคาต่อโทเค็นต่ำที่สุด ``` **สิ่งที่เกิดขึ้น:** 1. OmniRoute ตรวจพบคำนำหน้า `auto/` ใน `src/sse/handlers/chat.ts` 2. สอบถาม **การเชื่อมต่อผู้ให้บริการที่ใช้งานอยู่** ทั้งหมดจากฐานข้อมูล 3. กรองให้เหลือเฉพาะรายการที่มีข้อมูลรับรองถูกต้อง (API key หรือ OAuth token) 4. กำหนดโมเดลต่อการเชื่อมต่อ (`connection.defaultModel` หรือโมเดลแรกของผู้ให้บริการ) 5. สร้าง **คอมโบเสมือน** ในหน่วยความจำ (ไม่จัดเก็บใน DB) 6. กำหนดเส้นทางโดยใช้โปรไฟล์ค่าน้ำหนักของตัวแปรที่เลือก + กลยุทธ์ LKGP **คุณสมบัติสำคัญ:** - ✅ **เปิดใช้งานตลอดเวลา:** ไม่ต้องมีตัวสลับ ไม่ต้องสร้างคอมโบ และไม่ต้องกำหนดค่า - ✅ **ไดนามิก:** สะท้อนผู้ให้บริการที่เชื่อมต่ออยู่ในปัจจุบันโดยอัตโนมัติ - ✅ **การยึดติดกับเซสชัน:** LKGP ทำให้ผู้ให้บริการล่าสุดที่ทำงานสำเร็จได้รับลำดับความสำคัญ - ✅ **รองรับหลายบัญชี:** การเชื่อมต่อผู้ให้บริการแต่ละรายการจะกลายเป็นตัวเลือกแยกกัน - ✅ **ไม่มีการเขียน DB:** คอมโบเสมือนมีอยู่เฉพาะสำหรับคำขอนั้น จึงไม่มีค่าใช้จ่ายเพิ่มเติมจากการจัดเก็บถาวร ### การควบคุมตัวเลือกต่อคีย์ (#7819, ระดับ 1+2) `GET /v1/auto-combo/{channel}/candidates` (`{channel}` = ส่วนต่อท้ายหลัง `auto/` หรือ ค่า `auto` ตามตัวอักษรสำหรับช่องทางพื้นฐาน) เป็นเอนด์พอยต์แบบ **อ่านอย่างเดียว** ที่แสดงรายการ กลุ่มตัวเลือกปัจจุบันของช่องทาง `auto/*` พร้อมสถานะการเข้าถึงแบบสด โดยนำข้อมูลการอ่านด้านความยืดหยุ่น ที่มีอยู่แล้วมาใช้ซ้ำ (ไม่อ่าน `state` ดิบของ breaker): - circuit breaker ของผู้ให้บริการ — `getCircuitBreaker(provider).getStatus()` / `.canExecute()` - ช่วงพักของการเชื่อมต่อ — `rateLimitedUntil` / `testStatus` ในแถว `provider_connections` ที่ได้รับการแปลงแล้ว - การล็อกโมเดล — `isModelLocked(provider, connectionId, model)` ตัวเลือกแต่ละรายการยังมีแฟล็ก `excluded` ของ API key นี้ด้วย รายการยกเว้นจะถูกจัดเก็บ แยกตาม API key (ตาราง `auto_candidate_overrides`, migration `128`) — OmniRoute เป็น ระบบผู้เช่ารายเดียวโดยไม่มีตาราง `users` ดังนั้น `apiKeyId` จึงเป็นข้อมูลระบุตัวตนต่อผู้เรียก ที่มีอยู่จริงและใกล้เคียงที่สุด — และมีการบังคับใช้ที่จุดควบคุมหลักของกลุ่มตัวเลือกใน `open-sse/services/autoCombo/virtualFactory.ts` ผ่านฟังก์ชันบริสุทธิ์ที่ผ่านการทดสอบหน่วยแล้ว `filterExcludedCandidates()` (`open-sse/services/autoCombo/candidateOverrides.ts`) ตัวกรองเป็นแบบ **fail-open**: หากไม่ได้ตั้งค่า apiKeyId/ช่องทาง หรือการค้นหา DB ล้มเหลว ระบบจะไม่กรองกลุ่ม ดังนั้นผู้ดูแลระบบที่ไม่ได้กำหนดรายการแทนที่ไว้จะเห็นการกำหนดเส้นทาง ที่เหมือนเดิมทุกไบต์กับก่อนมีฟีเจอร์นี้ **เลื่อนไปดำเนินการในประเด็นติดตามผล:** น้ำหนักแยกตามตัวเลือก + การจัดลำดับอย่างชัดเจน (ระดับ 3 — เชื่อมเข้าสู่เส้นทางกลยุทธ์แบบถ่วงน้ำหนัก/ตามลำดับความสำคัญที่มีอยู่) และการตรึงกลยุทธ์ `combo.ts` ที่เฉพาะเจาะจงสำหรับแต่ละช่องทาง `auto/*` (ระดับ 4) ดูแผน #7819 สำหรับคำถามที่ยังเปิดอยู่ ว่าการแทนที่ค่าควรคงไว้แยกตาม API key หรือเปลี่ยนเป็นแบบส่วนกลาง เมื่อพิจารณาจาก โมเดลผู้เช่ารายเดียว **เบื้องหลังการทำงาน:** ```txt คำขอ: { model: "auto/coding" } ↓ src/sse/handlers/chat.ts ตรวจพบคำนำหน้า ↓ createVirtualAutoCombo('coding') → candidatePool จากการเชื่อมต่อที่ใช้งานอยู่ ↓ handleComboChat (เอนจินเดียวกับคอมโบที่บันทึกถาวร) ↓ การให้คะแนนอัตโนมัติเลือกผู้ให้บริการ/โมเดลที่ดีที่สุดสำหรับแต่ละคำขอ ``` **ไฟล์ที่ใช้ในการติดตั้งใช้งาน:** | ไฟล์ | วัตถุประสงค์ | | --------------------------------------------------------- | ----------------------------------------------------- | | `open-sse/services/autoCombo/autoPrefix.ts` | ตัวแยกวิเคราะห์คำนำหน้า (`parseAutoPrefix`) | | `open-sse/services/autoCombo/virtualFactory.ts` | สร้างออบเจ็กต์ `AutoComboConfig` เสมือน | | `open-sse/services/autoCombo/providerRegistryAccessor.ts` | จุดเชื่อมสำหรับการทดสอบเพื่อจำลองรีจิสทรีผู้ให้บริการ | | `src/sse/handlers/chat.ts` | การผสานรวม: ลัดขั้นตอนด้วยคำนำหน้าอัตโนมัติ | | `src/shared/constants/providers.ts` | รายการระบบ `SYSTEM_PROVIDERS.auto` | ## ชื่อคอมโบที่ตรงกับรหัสโมเดลจริง คอมโบที่มี `name` เหมือนกับรหัสโมเดลเปล่าทุกประการ (เช่น คอมโบชื่อ `gpt-5.5`) เป็น **รูปแบบที่ตั้งใจให้ใช้งานและรองรับอย่างเป็นทางการ** ไม่ใช่ข้อบกพร่อง โดยเป็น กลไกสำหรับการสลับไปใช้ผู้ให้บริการสำรองตามรหัสโมเดลตามที่บันทึกไว้ใน [#6940](https://github.com/diegosouzapw/OmniRoute/issues/6940) เนื่องจากระบบตรวจสอบการ แก้ไขคอมโบก่อนการแก้ไขรหัสโมเดลเปล่า (`getComboForModel()` ใน `src/sse/services/model.ts`) คำขอสำหรับรหัสเปล่า `gpt-5.5` จึงถูกกำหนดเส้นทางผ่านเป้าหมายของคอมโบ (เช่น `acme-responses/gpt-5.5`, `backup-responses/gpt-5.5`) แทนที่จะส่งตรงไปยัง ผู้ให้บริการรายเดียว ซึ่งเป็นการนำลำดับความสำคัญแบบคอมโบก่อนการเขียนใหม่ที่สร้างขึ้นสำหรับ [#3227/#3233](https://github.com/diegosouzapw/OmniRoute/issues/3227) มาใช้ซ้ำ และมี การทดสอบการถดถอยโดย `tests/unit/responses-combo-resolution-3227.test.ts` และ `tests/unit/combo-name-codex-responses-rewrite.test.ts` การสร้างหรือเปลี่ยนชื่อคอมโบเป็นชื่อที่บดบังรหัสโมเดลจริงจะ **ไม่ถูกปฏิเสธโดยเด็ดขาด** เพราะการทำเช่นนั้นจะทำให้เวิร์กโฟลว์ที่บันทึกไว้นี้ใช้งานไม่ได้ แต่ (#8530) `POST /api/combos` และ `PUT /api/combos/[id]` จะแนบฟิลด์ `warning` ที่ไม่ขัดขวางการทำงานไว้ในผลลัพธ์ตอบกลับ เมื่อชื่อ (ใหม่) ชนกับรหัส โมเดลจริง: ```json { "warning": { "code": "COMBO_NAME_SHADOWS_MODEL", "modelId": "gpt-5.5", "providerId": "openai" } } ``` ขณะเริ่มระบบ `scanComboModelNameCollisionsAtBoot()` (`src/instrumentation-node.ts`) จะบันทึกคำเตือน `[STARTUP]` แบบหนึ่งบรรทัดด้วย โดยแจกแจงคอมโบที่มีอยู่ทั้งหมดซึ่งบดบังรหัสโมเดล เพื่อให้ผู้ดูแลระบบที่ พบกรณีนี้โดยไม่ตั้งใจ (แทนที่จะตั้งใจตาม #6940) ได้รับสัญญาณแจ้งเตือน ตัวช่วยตรวจจับอยู่ใน `src/lib/combos/modelNameCollision.ts` ## การเรียกคอมโบแบบกำหนดเองจากไคลเอนต์ คอมโบที่บันทึกไว้ (การตั้งค่า → คอมโบ) จะถูกใช้เฉพาะเมื่อไคลเอนต์ส่ง **ชื่อที่ตรงกันทุกประการ** ของคอมโบในฟิลด์ `model` เท่านั้น โดยไม่มีการจับคู่ชื่อคอมโบแบบคลุมเครือหรือบางส่วน และไม่เกี่ยวข้องกับคำนำหน้า `auto/` ลำดับการแก้ไข (`getComboForModel()` ใน `src/sse/services/model.ts`): 1. ชื่อคอมโบตรงกันทุกประการ (`model: "my-combo"`), 2. คำนำหน้า `combo/` (`model: "combo/my-combo"`), 3. การแมปโมเดล→คอมโบด้วยรูปแบบ glob (`/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"}]}' ``` ข้อผิดพลาดที่พบบ่อยสองประการ: - **`auto` ไม่ได้ใช้คอมโบของคุณ** `auto`/`auto/*` จะสร้างกลุ่มตัวเลือกแบบไม่ต้องตั้งค่าของตัวเอง และจะตรวจสอบคอมโบที่บันทึกไว้ก็ต่อเมื่อมีคอมโบชื่อ `auto` จริง ๆ เท่านั้น (ไม่แนะนำ) หากต้องการกำหนดเส้นทางผ่านคอมโบ ให้ส่งชื่อที่ตรงกันทุกประการ ไม่ใช่ `auto` - **`openrouter/auto` เป็นผลิตภัณฑ์ OpenRouter แบบชำระเงินจริง** ("Auto Best Available") ไม่ใช่นามแฝงของ OmniRoute โดยเป็นรายการโมเดลแบบคงที่เพียงรายการเดียวในรีจิสทรี OpenRouter (`open-sse/config/providers/registry/openrouter/index.ts`) และมีการเรียกเก็บเงินแยกต่างหาก ใช้ การตั้งค่า → การกำหนดเส้นทาง → ซ่อนโมเดลแบบชำระเงิน เพื่อยกเว้นรายการนี้ออกจากกลุ่ม `auto` ดู [#7992](https://github.com/diegosouzapw/OmniRoute/issues/7992) และ [#7111](https://github.com/diegosouzapw/OmniRoute/issues/7111) สำหรับความสับสนเดิมที่เอกสารนี้จัดทำขึ้นเพื่ออธิบาย ## วิธีการทำงาน (Auto-Combo ที่บันทึกถาวร) Auto-Combo Engine จะเลือกผู้ให้บริการ/โมเดลที่ดีที่สุดสำหรับแต่ละคำขอแบบไดนามิก โดยใช้ **ฟังก์ชันการให้คะแนน 16 ปัจจัย** (กำหนดไว้ใน `open-sse/services/autoCombo/scoring.ts` → `DEFAULT_WEIGHTS`) ค่าน้ำหนักเริ่มต้นมีผลรวมเท่ากับ `1.0`; ค่าน้ำหนักที่กำหนดเองจะถูกปรับให้เป็นมาตรฐานใหม่โดย `normalizeScoringWeights()` ปัจจัยสองรายการจากทั้งหมดสิบหกรายการ ได้แก่ `cacheAffinity` และ `resetWindowAffinity` มีค่าน้ำหนักเริ่มต้นเป็น `0`; `reliability` มีค่า `0` ใน `DEFAULT_WEIGHTS` แต่มีค่า `0.03` ในแพ็กทั่วไป และ `0.04` ใน `reliability-first` ส่วน `quality` มีค่า `0.02` ในแพ็ก (`0.03` ใน `quality-first`): ปัจจัยเหล่านี้ยังคงได้รับการคำนวณสำหรับตัวเลือกทุกตัว และ `cacheAffinity` ทำหน้าที่เป็นเงื่อนไขควบคุมการขจัดรายการซ้ำของแคชพรอมต์ภายนอกคะแนน ดังนั้นปัจจัยที่มีค่าเริ่มต้นเป็นศูนย์จึงไม่มีส่วนในการตัดสินใจโดยค่าเริ่มต้น แต่จะมีส่วนเมื่อใช้แพ็ก ![การให้คะแนน 16 ปัจจัยของ Auto-Combo](../diagrams/exported/auto-combo-scoring.svg) > แหล่งที่มา: [diagrams/auto-combo-scoring.mmd](../diagrams/auto-combo-scoring.mmd) (สร้างใหม่ผ่าน `npm run docs:render-diagrams`) ชื่อไฟล์เป็นชื่อที่สืบทอดมาจากอดีต; ซอร์สและไดอะแกรมที่เรนเดอร์แล้วแสดงปัจจัยครบทั้ง 16 รายการที่ประกาศไว้ใน `DEFAULT_WEIGHTS` | ปัจจัย | ค่าน้ำหนักเริ่มต้น | คำอธิบาย | | :-------------------- | :----------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `quota` | 0.1429 | โควตาคงเหลือ / พื้นที่เผื่อของขีดจำกัดอัตราการใช้งาน [0..1] | | `health` | 0.1605 | คะแนนสถานะจาก circuit breaker (CLOSED=1.0, HALF_OPEN=0.5, OPEN=0.0) | | `costInv` | 0.1429 | ส่วนกลับของต้นทุนแบบ **ผสม** (60% จากราคาโทเค็นอินพุต + 40% จากราคาโทเค็นเอาต์พุต โดยผ่านการปรับให้เป็นมาตรฐาน) — ยิ่งถูก = คะแนนยิ่งสูง | | `latencyInv` | 0.1143 | ส่วนกลับของเวลาแฝง p95 ที่ปรับให้เป็นมาตรฐานเทียบกับพูล — ยิ่งเร็ว = คะแนนยิ่งสูง | | `taskFit` | 0.0762 | ความเหมาะสมกับประเภทงาน (การเขียนโค้ด, การตรวจทาน, การวางแผน, การวิเคราะห์, การดีบัก, เอกสาร) | | `stability` | 0.0476 | ความเสถียรที่อิงกับความแปรปรวนจากส่วนเบี่ยงเบนมาตรฐานของเวลาแฝง — ตัวเลือกที่มีเวลาตอบสนองผันผวนจะได้คะแนนต่ำกว่า | | `tierPriority` | 0.0476 | ลำดับความสำคัญของระดับบัญชี — Ultra=1.0, Pro=0.67, Standard=0.33, Free=0.0 | | `tierAffinity` | 0.0476 | ความสอดคล้องระหว่างระดับของตัวเลือกกับระดับที่ manifest แนะนำ | | `specificityMatch` | 0.0476 | ความสอดคล้องระหว่างระดับความเฉพาะเจาะจงของคำขอ (คำแนะนำจาก manifest) กับระดับของโมเดล | | `contextAffinity` | 0.0476 | ความสอดคล้องระหว่างความต้องการขนาดหน้าต่างบริบทของคำขอกับหน้าต่างบริบทของโมเดล | | `sessionAvailability` | 0.0476 | ความพร้อมใช้งานของเซสชัน OAuth สำหรับการเชื่อมต่อที่เป็นตัวเลือกในเซสชันนี้ (`getOAuthSessionAvailability()`; การเชื่อมต่อที่ไม่ใช่ OAuth ได้คะแนน 1.0) | | `connectionDensity` | 0.0476 | กระจายโหลดไปยังการเชื่อมต่อของผู้ให้บริการรายเดียวกัน (ป้องกันการกระจุกตัว) | | `cacheAffinity` | 0.00 | ความสอดคล้องจาก rendezvous hash ที่เอนเอียงไปยังการเชื่อมต่อซึ่งมีแนวโน้มสูงสุดว่าจะมีคำนำหน้าของแคชพรอมต์สำหรับคำขอนี้อยู่แล้ว (`open-sse/services/combo/promptCacheAffinity.ts`); ปิดใช้งานโดยค่าเริ่มต้น (#8008) | | `resetWindowAffinity` | 0.00 | เอนเอียงไปยังการเชื่อมต่อที่มีช่วงเวลารีเซ็ตโควตาที่เหมาะสม (ปิดใช้งานโดยค่าเริ่มต้น) | | `quality` | 0.03 | สัญญาณคุณภาพเอาต์พุตที่ขับเคลื่อนด้วยข้อเสนอแนะจากตัวติดตามคุณภาพของเหตุการณ์การกำหนดเส้นทาง; ตัวเลือกที่ไม่มีข้อมูลสังเกตการณ์จะได้รับค่ากลาง 0.5 | | `reliability` | 0.00 | สัดส่วนความสำเร็จที่สังเกตได้ `1 - failureRate` จากประวัติการใช้งาน 24 ชั่วโมง โดยต้องมีข้อมูลอย่างน้อยสิบตัวอย่าง (มิฉะนั้นใช้เมตริกแบบเรียลไทม์); ตัวเลือกที่ไม่มีข้อมูลสังเกตการณ์จะมีค่าเป็น 1.0 ปิดใช้งานโดยค่าเริ่มต้น | `quality` (`open-sse/services/routing/quality.ts`): `cold restart → ∅`; `observations = 0 → score 0.5`. **ผลรวม:** `0.1429 + 0.1605 + 0.1429 + 0.1143 + 0.0762 + (7 × 0.0476) + 0.00 + 0.00 + 0.03 + 0.00 = 1.0` ตามที่ประกาศไว้ใน `DEFAULT_WEIGHTS`; ค่าน้ำหนักที่ผู้ใช้กำหนดจะถูกปรับให้เป็นการแจกแจงแบบมาตรฐานใหม่โดย `normalizeScoringWeights()` ก่อนการให้คะแนน ## แพ็กโหมด โปรไฟล์ค่าน้ำหนักที่กำหนดไว้ล่วงหน้า 6 แบบใน `open-sse/services/autoCombo/modePacks.ts` แต่ละแพ็กจะแทนที่ค่าน้ำหนักเริ่มต้นทั้งหมด เพื่อโน้มน้าวการเลือกไปยังเป้าหมายหนึ่ง ทุกแพ็กมีผลรวมเป็น `1.0` อยู่แล้ว (`0.9999` เมื่อแสดงเป็นทศนิยมสี่ตำแหน่ง) ดังนั้น `normalizeScoringWeights()` จึงไม่มีสิ่งที่มีนัยสำคัญต้องแก้ไขเมื่อแพ็กทำงานอยู่ — ค่าด้านล่างนี้คือค่าที่ตัวให้คะแนนนำไปใช้โดยประมาณตามการปัดเศษ | ปัจจัย | 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 | หมายเหตุ: - **แพ็กมี `quality` และ `reliability` รวมอยู่ด้วย** (`quality 0.02`, `quality-first 0.03`; `reliability 0.03`, `reliability-first 0.04`) และแทนที่แมปค่าน้ำหนักทั้งหมด (`weights = pack` ไม่ใช่การผสาน) `DEFAULT_WEIGHTS` มี `quality 0.03 / reliability 0`; การเลือก `balanced`/`default` จะคงค่าเริ่มต้นเหล่านั้นไว้ ส่วนการเลือกแพ็กจะใช้ค่าของแพ็กตามด้านบน ในพูลที่ยังไม่มีข้อมูล (ยังไม่มีการสังเกตการณ์ ดังนั้น `quality 0.5` และ `reliability 1`) ปัจจัยทั้งสองนี้จะเพิ่ม `+0.04` ภายใต้แพ็กทั่วไป (`0.03 + 0.01`), `+0.045` ภายใต้ `quality-first` และ `+0.05` ภายใต้ `reliability-first` - `tierAffinity`, `specificityMatch` และ `resetWindowAffinity` ถูกกำหนดเป็น `0` อย่างชัดเจนในทุกแพ็ก - ภาพรวมจุดเน้นของแต่ละแพ็ก: - **ship-fast** → latencyInv 0.3048 + health 0.2667 (การเชื่อมต่อที่มีเวลาแฝงต่ำและมีสภาพพร้อมใช้งาน) - **cost-saver** → costInv 0.3324 (โทเค็นที่ถูกที่สุดชนะ) - **quality-first** → taskFit 0.3524 + stability 0.1429 + quality 0.03 ซึ่งสูงที่สุดในบรรดาแพ็กทั้งหมด (โมเดลที่เหมาะกับงานที่สุดและมีความสม่ำเสมอ) - **offline-friendly** → quota 0.3324 + health 0.2667 (พื้นที่สำรองสูงสุดโดยไม่คำนึงถึงความเร็ว/ต้นทุน) - **reliability-first** → health 0.3524 + stability 0.1905 + reliability 0.04 ซึ่งสูงที่สุดในบรรดาแพ็กทั้งหมด (เหตุไม่คาดคิดน้อยที่สุด) - **chaos-mode** → health 0.4000 + taskFit 0.1905 (แพ็กค่าน้ำหนักที่ `auto/chaos` กำหนดให้กับสมาชิกในแผงของตน; การกระจายคำขอแบบขนานไม่อ่านค่าน้ำหนักเหล่านี้ และนี่ไม่ใช่โปรไฟล์การจงใจแทรกข้อผิดพลาด โปรดดู [CHAOS-MODE.md](../guides/CHAOS-MODE.md#autochaos-parallel-fan-out)) ### การควบคุมต่อคำขอ (ส่วนหัว) — #6023 / #6024 / #6025 / #3470 คอมโบ `auto` สามารถถูกควบคุมทิศทาง **ต่อคำขอ** ผ่านส่วนหัวสามรายการได้ โดยไม่เปลี่ยนแปลงการกำหนดค่าที่จัดเก็บไว้ของคอมโบ การควบคุมเหล่านี้ใช้เฉพาะกับกลยุทธ์ `auto` และเฉพาะคำขอที่มีส่วนหัวดังกล่าวเท่านั้น; ระบบจะใช้ `modePack`/`budgetCap`/`budgetFallback` ที่บันทึกไว้ของคอมโบเมื่อไม่มีส่วนหัวนั้น | ส่วนหัว | ค่าที่รับได้ | ผลลัพธ์ | | :---------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `X-OmniRoute-Mode` | นามแฝงของค่าที่ตั้งไว้ล่วงหน้า (`fast`, `balanced`, `quality`, `cheap`, `reliable`, `offline`) หรือชื่อแพ็กโดยตรง (`ship-fast`, `cost-saver`, `quality-first`, `offline-friendly`, `reliability-first`) | แทนที่ค่าน้ำหนักการให้คะแนนสำหรับคำขอนี้ `balanced`/`default` จะบังคับใช้ค่าน้ำหนักเริ่มต้น (ไม่ใช้แพ็ก) ระบบจะละเว้นค่าที่ไม่รู้จัก (โดยคงการกำหนดค่าเดิมไว้) | | `X-OmniRoute-Budget` | จำนวนบวก (ค่าใช้จ่ายสูงสุดเป็น USD ต่อคำขอ) | เพดานค่าใช้จ่ายแบบบังคับ: ตัวเลือกที่มีค่าใช้จ่ายโดยประมาณเกินเพดานจะถูกกรองออกก่อนการเลือก สิ่งที่จะเกิดขึ้นเมื่อผู้สมัคร **ทั้งหมด** มีค่าใช้จ่ายเกินเพดานจะถูกควบคุมโดย `X-OmniRoute-Budget-Fallback` ด้านล่าง | | `X-OmniRoute-Budget-Fallback` | `cheapest` (ค่าเริ่มต้น, นามแฝง: `cheapest-viable`, `soft`) หรือ `strict` (นามแฝง: `block`, `hard`) | `cheapest`: ย้อนกลับไปใช้ตัวเลือกที่ถูกที่สุดโดยรวม แม้ว่าค่าใช้จ่ายยังคงเกินเพดาน (ลักษณะการทำงานแบบเดิม) `strict`: ปฏิเสธการเลือก — คำขอจะล้มเหลวทันทีด้วย `HTTP 402` แทนที่จะใช้จ่ายเกินโดยไม่มีการแจ้งเตือน ระบบจะละเว้นค่าที่ไม่รู้จัก | | `X-OmniRoute-Effort` | `auto` (ค่าอื่นสงวนไว้) | งบประมาณการคิดแบบปรับเปลี่ยนได้: เมื่อคำขอ **ไม่มี** ฟิลด์การให้เหตุผลในรูปแบบใดเลย (`reasoning_effort`, `reasoning`, `thinking`) เกตเวย์จะแปลง `auto` เป็น `low`/`medium`/`high` โดยอิงจากสัญญาณเชิงกำหนดจากโครงสร้างคำขอ (ความยาวของข้อความล่าสุดจากผู้ใช้, ขนาดบริบทจนถึงข้อความล่าสุดจากผู้ใช้, ผลลัพธ์ก่อนหน้าจากเครื่องมือ, ความลึกของลูปเครื่องมือ) สัญญาณจะจำกัดขอบเขตอยู่ที่เทิร์นปัจจุบัน — ทุกอย่างหลังข้อความล่าสุดจากผู้ใช้จะถูกละเว้น — ดังนั้นทุกคำขอในลูปเครื่องมือจึงถูกกำหนดเป็นระดับเดียวกัน (การตรึงค่าแบบไร้สถานะต่อเทิร์น, ไม่มีสถานะเซสชัน, ไม่มีการเพิ่มระดับระหว่างลูปซึ่งจะทำให้คำนำหน้าของแคชพรอมต์ต้นทางใช้งานไม่ได้) ฟิลด์การให้เหตุผลที่ไคลเอนต์ระบุอย่างชัดเจนจะมีลำดับความสำคัญสูงกว่าเสมอ จำกัดขอบเขตเฉพาะคำขอที่การส่งต่อไปยังต้นทางถูกแปลงเป็นรูปแบบ OpenAI Chat Completions (`targetFormat === FORMATS.OPENAI`) — `reasoning_effort` เป็นฟิลด์ในรูปแบบ OpenAI ดังนั้นส่วนหัวนี้จะไม่มีผลกับคำขอที่กำหนดเป้าหมายเป็น Claude หรือ Gemini (ดู `open-sse/handlers/chatCore/adaptiveEffortWiring.ts`) | ```bash # บังคับใช้โปรไฟล์ที่เร็วที่สุด จำกัดคำขอนี้ไว้ที่ $0.05 และบล็อกโดยเด็ดขาดแทนการใช้จ่ายเกินงบ 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"}]}' ``` การกำหนดค่าเป็นฟังก์ชันบริสุทธิ์ (`open-sse/services/autoCombo/requestControls.ts`); ค่าที่กำหนดแล้วจะถูกส่งไปยังอินพุต `config.modePack` / `config.budgetCap` / `config.budgetFallback` ที่มีอยู่ของเอนจิน `config.budgetFallback` ที่จัดเก็บไว้ของคอมโบ ("strict" | "cheapest") จะกำหนดนโยบายถาวร ส่วนหัวจะแทนที่ค่านี้สำหรับคำขอเดียวเท่านั้น ## กลยุทธ์การกำหนดเส้นทางทั้งหมด เอนจินคอมโบของ OmniRoute รองรับ **กลยุทธ์การกำหนดเส้นทาง 20 รูปแบบ** (ประกาศไว้ใน `src/shared/constants/routingStrategies.ts` → `ROUTING_STRATEGY_VALUES`) ตัวเอนจิน Auto Combo เองเปิดให้ใช้งานภายใต้กลยุทธ์ `auto` ส่วนกลยุทธ์อื่นๆ พร้อมใช้งานสำหรับคอมโบที่บันทึกไว้ | กลยุทธ์ | คำอธิบาย | | :------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `priority` | รายการเป้าหมายแรกที่เรียงลำดับโดยมีลำดับความสำคัญระบุไว้อย่างชัดเจน | | `weighted` | สุ่มแบบถ่วงน้ำหนักตามน้ำหนักของแต่ละเป้าหมาย | | `round-robin` | วนผ่านเป้าหมายตามลำดับ (เป็นชุด โปรดดูด้านล่าง) | | `context-relay` | ส่งต่อบริบทระหว่างเป้าหมาย (สำหรับบทสนทนายาว) | | `fill-first` | ใช้โควตาของแต่ละเป้าหมายให้เต็มก่อนย้ายไปยังเป้าหมายถัดไป | | `p2c` | การกระจายโหลดแบบสุ่มด้วยตัวเลือก 2 รายการ | | `random` | การเลือกแบบสุ่มที่มีการกระจายสม่ำเสมอ | | `least-used` | เลือกเป้าหมายที่มีโหลดปัจจุบันต่ำที่สุด | | `cost-optimized` | ลดค่าใช้จ่าย $ ต่อคำขอให้ต่ำที่สุดตามราคาที่ระบุในแค็ตตาล็อก | | `reset-aware` ⭐ | จัดลำดับความสำคัญตามเวลารีเซ็ตโควตา — ช่วงเวลารีเซ็ตสั้นจะมีอันดับสูงกว่า | | `reset-window` | เลือกเป้าหมายที่ช่วงเวลาโควตาจะรีเซ็ตเร็วที่สุดก่อน | | `headroom` | เลือกเป้าหมายที่มีโควตาคงเหลือมากที่สุด | | `quota-weighted` | ข้ามบัญชีที่โควตาหมดแล้ว จากนั้นเลือกจากบัญชีที่เหลือตามสัดส่วนโควตาคงเหลือหารด้วยโหลดที่กำลังประมวลผล โดยบทสนทนาที่มีอยู่จะยังคงตรึงไว้กับเป้าหมายเดิม | | `strict-random` | สุ่มโดยไม่ลบรายการที่เลือกซ้ำ | | `auto` | ใช้การให้คะแนนของ Auto Combo (16 ปัจจัย) — **แนะนำ** | | `lkgp` | เส้นทางล่าสุดที่ทราบว่าใช้งานได้ (ตรึงไว้กับผู้ให้บริการรายล่าสุดที่ดำเนินการสำเร็จ จากนั้นจึงใช้กฎสำรอง) | | `context-optimized` | เลือกเป้าหมายที่เหมาะสมที่สุดกับขนาดบริบทปัจจุบัน | | `cache-optimized` | เรียงลำดับเป้าหมายใหม่ตามความสอดคล้องกับแคชพรอมต์ — การเชื่อมต่อที่มีแนวโน้มสูงสุดว่าจะมีคำนำหน้าที่แคชไว้ของคำขอนี้อยู่แล้วจะถูกลองก่อน (`open-sse/services/combo/promptCacheAffinity.ts`, #8008) | | `fusion` 🧬 | ส่งคำขอไปยังกลุ่มโมเดลแบบขนาน จากนั้นสังเคราะห์เป็นคำตอบเดียวผ่านโมเดลผู้ตัดสิน (โปรดดูด้านล่าง) | | `pipeline` | เรียกใช้เป้าหมายตามลำดับ โดยส่งเอาต์พุตของแต่ละขั้นตอนเป็นอินพุตของขั้นตอนถัดไป และส่งคืนเฉพาะคำตอบสุดท้าย (#6396) | ⭐ = เพิ่มใหม่ใน v3.8.0 · 🧬 = เพิ่มใหม่ใน v3.8.36 ### ความหมายของ `weighted` `weighted` คือ **การสุ่มตามสัดส่วนในแต่ละคำขอ** (`open-sse/services/combo/targetSorters.ts` → `selectWeightedTarget`) ไม่ใช่การทำให้เท่าเทียมกัน: - แต่ละคำขอจะสุ่ม **หนึ่ง** ขั้นตอนด้วยความน่าจะเป็น `weight / totalWeight` ส่วนขั้นตอนที่เหลือ จะเรียงตามน้ำหนักจากมากไปน้อยเพื่อเป็นลำดับสำรองสำหรับคำขอนั้น - ขั้นตอนที่มีน้ำหนักเป็น `0` (หรือไม่ได้ระบุ) จะ **ไม่มีวันถูกสุ่มเลือก** ตราบใดที่ขั้นตอนอื่นมี น้ำหนัก > 0 — ขั้นตอนดังกล่าวจะทำหน้าที่เป็นตัวสำรองได้เฉพาะหลังจากขั้นตอนที่สุ่มเลือกไว้ล้มเหลวเท่านั้น การเลือกจะเป็นแบบสม่ำเสมอก็ต่อเมื่อ น้ำหนัก **ทั้งหมด** เป็น 0 - ขั้นตอนที่เป้าหมายทั้งหมดใช้งานไม่ได้ — circuit breaker ของผู้ให้บริการเป็น `OPEN`, การเชื่อมต่อ อยู่ในช่วงพัก หรือโมเดลถูกล็อก — จะถูกนำออกจากการสุ่มก่อนเริ่มดำเนินการ (`open-sse/services/combo/targetResolution.ts`) ดังนั้นขั้นตอนที่พร้อมใช้งานเพียงขั้นตอนเดียวอาจได้รับเลือกสำหรับทุกคำขอเป็นการชั่วคราว - `stickyWeightedLimit` (การกำหนดค่าคอมโบ ค่าเริ่มต้น `1` = ปิด) จะตรึงขั้นตอนที่สุ่มเลือกไว้ตามจำนวน ครั้งที่สำเร็จติดต่อกันดังกล่าว ก่อนสุ่มเลือกใหม่ สำหรับการหมุนเวียนแบบเคร่งครัด ให้ใช้ `round-robin`; การกำหนดน้ำหนักเท่ากันใน `weighted` ให้ความสมดุลเชิงสถิติ — ไม่ใช่ ความสมดุลแบบเคร่งครัด ### โหมดไปป์ไลน์แบบเอเจนต์ คอมโบ `pipeline` ที่มีโมเดลอย่างน้อยสองโมเดลสามารถเลือกใช้การกำหนดเส้นทางแบบผู้วางแผน/ผู้ดำเนินการได้ด้วย `config.agenticOrchestration.enabled` เป้าหมายแรกทำหน้าที่วางแผนและตอบคำตอบสุดท้าย ส่วนเป้าหมายที่สองส่งการเรียกใช้เครื่องมือในรูปแบบเนทีฟของไคลเอนต์ OmniRoute ตรวจจับคำขอต่อเนื่อง ที่มีผลลัพธ์จากเครื่องมือจากโปรโตคอลคำขอ สอบถามผู้วางแผนว่าจำเป็นต้องเรียกใช้เครื่องมืออีกรอบหรือไม่ และกำหนดให้ผู้ดำเนินการหรือผู้วางแผนเป็นขั้นตอนสุดท้ายที่ส่งกลับไปยังไคลเอนต์แบบไดนามิก โมเดลเพิ่มเติมหลังเป้าหมายที่สองจะเป็นผู้ดำเนินการสำรองตามลำดับ การตอบกลับ HTTP ที่ล้มเหลวหรือข้อยกเว้นระหว่างการส่งข้อมูลจะเลื่อนไปยังผู้ดำเนินการรายถัดไป โดยยังคงใช้ การตัดสินใจของผู้วางแผนและเครื่องมือเนทีฟชุดเดิม แต่ใช้พรอมต์ประจำขั้นตอนและ การเชื่อมต่อที่ผ่านการแปลงค่าของผู้ดำเนินการรายนั้นเอง การตอบกลับที่สำเร็จครั้งแรกจะถูกส่งคืนโดยไม่มีการเปลี่ยนแปลง รวมถึง การสตรีมแบบ SSE; ความล้มเหลวหลังจากเริ่มสตรีมสำเร็จแล้วจะไม่สามารถลองใหม่ได้ที่นี่ หากผู้ดำเนินการทั้งหมดล้มเหลว ระบบจะส่งคืนความล้มเหลวครั้งสุดท้าย การยกเลิกโดยไคลเอนต์จะหยุดการส่งต่อคำขอ ```json { "strategy": "pipeline", "models": [{ "model": "provider/planner" }, { "model": "provider/executor" }], "config": { "agenticOrchestration": { "enabled": true, "maxToolRounds": 8 } } } ``` ผู้ดำเนินการอาจส่งการเรียกใช้ที่เป็นอิสระต่อกันหลายรายการในการตอบกลับครั้งเดียว การเรียกใช้ที่ขึ้นต่อกันจะได้รับ การจัดการในรอบผลลัพธ์จากเครื่องมือของไคลเอนต์ครั้งถัดไป โดยผู้วางแผนจะตรวจสอบทุกผลลัพธ์ `maxToolRounds` มีค่าเริ่มต้นเป็น `8` และรับค่า `1`–`32`; เมื่อถึงขีดจำกัด ผู้วางแผนต้อง สร้างคำตอบสุดท้ายที่ดีที่สุดเท่าที่มี การตัดสินใจภายในของผู้วางแผนจะถูกบัฟเฟอร์ไว้ ขณะที่ การตอบกลับที่เลือกเพื่อส่งให้ไคลเอนต์จะคงการตั้งค่าการสตรีมเดิมไว้ ### แบตช์แบบตรึงของ `round-robin` และการขยายบัญชี Round-robin ทำงานเป็นแบตช์ ไม่ใช่หนึ่งคำขอต่อหนึ่งขั้นตอน: - `stickyRoundRobinLimit` (ค่ากำหนดคอมโบ ตามด้วย `comboStickyRoundRobinLimit` แล้วจึง `settings.stickyRoundRobinLimit` โดยมีค่าเริ่มต้นเป็น **3**) จะใช้เป้าหมายเดิมต่อเนื่องตามจำนวน ครั้งที่สำเร็จก่อนหมุนเวียน ตั้งค่าการแทนที่ระดับคอมโบเป็น `1` เพื่อหมุนเวียนทุกหนึ่งคำขอ เครื่องมือแก้ไขคอมโบจะแสดงค่าที่มีผลและเลเยอร์ต้นทางของค่านั้น - `connectionAwareExpansion` (ค่ากำหนดคอมโบ ตามด้วยการตั้งค่า โดยมีค่าเริ่มต้นเป็น **false**) จะขยาย แต่ละขั้นตอนระดับผู้ให้บริการเป็นเป้าหมายแยกตามบัญชีก่อนหมุนเวียน กลยุทธ์กลุ่ม B (priority, weighted, round-robin, random, p2c, least-used, cost-optimized, lkgp, fill-first, strict-random, context-optimized, cache-optimized, context-relay, fusion, pipeline) จะคงมุมมองระดับผู้ให้บริการไว้จนกว่าจะเปิดใช้ตัวเลือกนี้ เครื่องมือแก้ไขคอมโบมีตัวเลือก สืบทอด / เปิด / ปิด; การสืบทอดจะใช้ค่าเริ่มต้นส่วนกลาง (ปิด) - การกำหนดเส้นทางตามตำแหน่งแคชของพรอมต์ (`promptCacheAffinityEnabled` โดยมีค่าเริ่มต้นเป็น **true**) จะจัดลำดับ การเชื่อมต่อที่ตรึงไว้ใหม่ เพื่อให้คีย์แคชที่ตรงกันคงอยู่ในบัญชีเดียว ตัวเลือกนี้มีลำดับความสำคัญเหนือ การหมุนเวียนแบบ round-robin และ weighted ระหว่างขั้นตอนแยกตามบัญชีที่ตรึงไว้ ปิดตัวเลือกนี้ที่ การตั้งค่า → ค่าเริ่มต้นของคอมโบ หากต้องการการหมุนเวียนแบบเคร่งครัด ไม่มีการแทนที่แยกสำหรับแต่ละคอมโบ สำหรับการหมุนเวียนหลายบัญชีในโมเดลเดียว ควรใช้ **หนึ่งขั้นตอนบัญชีแบบไดนามิก** (`connectionId` ว่าง ใช้ทั้งพูล) โดยกำหนดขีดจำกัดการตรึงเป็น `1` แทนการใช้ `connectionId` ที่ตรึงไว้สามรายการ ขั้นตอนที่ตรึงไว้ร่วมกับการกำหนดตามความสัมพันธ์จะรวมไปยังบัญชีเดียวกัน แม้ว่าตัวนับ RR จะเพิ่มขึ้นก็ตาม ## กลยุทธ์ Fusion `fusion` เป็นกลยุทธ์เดียวที่ **ไม่** เลือกเป้าหมายเพียงรายการเดียว แต่จะกระจายพรอมป์ไปยัง **โมเดลทุกตัวในพาเนลพร้อมกัน** จากนั้น **โมเดลผู้ตัดสิน** ที่กำหนดค่าได้จะสังเคราะห์ คำตอบสุดท้ายหนึ่งคำตอบจากคำตอบทั้งหมดของพาเนล พอร์ตมาจาก `decolua/9router` ต้นทาง (การออกแบบ Fusion ของ OpenRouter); การติดตั้งใช้งานอยู่ใน `open-sse/services/fusion.ts` วิธีการทำงาน: 0. **การข้ามพาเนลสำหรับคำขอที่มีเครื่องมือ** — คำขอที่มีอาร์เรย์ `tools` ซึ่งไม่ว่าง และ ไม่ได้ตั้งค่า `tool_choice` เป็น `"none"` อย่างชัดเจน จะข้ามพาเนลทั้งหมด โดยกำหนดเส้นทาง ไปยังโมเดลเดียวโดยตรง (ผู้ตัดสินที่กำหนดค่าไว้ หรือ `panel[0]`) พร้อมส่ง `tools`/`tool_choice` ต่อไปโดยไม่มีการแก้ไข สมาชิกพาเนลไม่มีสิทธิ์เข้าถึงเครื่องมือ และคำสั่งการสังเคราะห์ของ ผู้ตัดสินจะหลีกเลี่ยงการสร้างการเรียกใช้เครื่องมือ ดังนั้นไคลเอนต์แบบเอเจนต์/ที่เรียกใช้เครื่องมือ จะได้รับการตัดสินใจเรียกใช้เครื่องมือจริง แทนข้อความร้อยแก้วที่ผ่านการสังเคราะห์ (#6771) 1. **การกระจายคำขอ** (เฉพาะคำขอที่ไม่มีเครื่องมือ) — พรอมป์จะถูกส่งไปยังโมเดลทุกตัวในพาเนล พร้อมกัน โดยบังคับให้ไม่สตรีมและตัดเครื่องมือออก (ผู้ตัดสินต้องใช้ข้อความร้อยแก้วที่สมบูรณ์ เพื่อสังเคราะห์) 2. **การรวบรวมแบบโควรัมพร้อมช่วงผ่อนผัน** — ทันทีที่ได้รับคำตอบครบ `minPanel` ตัวจับเวลา ช่วงผ่อนผันสั้นๆ จะเริ่มทำงานเพื่อรอโมเดลที่ล่าช้า จากนั้น Fusion จะดำเนินการต่อด้วยคำตอบ เท่าที่รวบรวมได้ วิธีนี้จำกัดผลกระทบของโมเดลที่ช้าที่สุดต่อเวลารวม โดยมีการหมดเวลาแบบตายตัว เป็นขอบเขตสูงสุด 3. **การสังเคราะห์โดยผู้ตัดสิน** — คำตอบของพาเนลจะถูกทำให้ไม่ระบุตัวตน (`แหล่งข้อมูล 1`, `แหล่งข้อมูล 2`, … — เพื่อให้ผู้ตัดสินให้น้ำหนักกับเนื้อหา ไม่ใช่แบรนด์ของโมเดล) และส่งให้ ผู้ตัดสิน ซึ่งจะวิเคราะห์ฉันทามติ / ความขัดแย้ง / ความครอบคลุมบางส่วน / ข้อมูลเชิงลึกเฉพาะ / จุดบอด แล้วเขียนคำตอบที่เชื่อถือได้ **หนึ่ง** คำตอบ การเรียกผู้ตัดสินจะคงค่า `stream` เดิม ของไคลเอนต์และเครื่องมือไว้ ดังนั้นการสตรีมและการใช้เครื่องมือในขั้นตอนถัดไปจึงยังทำงานได้ 4. **การลดระดับอย่างราบรื่น** — ไม่มีคำตอบจากพาเนล → `503`; เหลือคำตอบเพียง 1 รายการ → ส่งคำตอบนั้นกลับโดยตรง (ไม่มีสิ่งใดให้รวม); พาเนลที่มีโมเดลเดียวจะตอบโดยตรง สมาชิกพาเนลอาจเป็นขั้นตอน `combo-ref` (`{kind: "combo-ref", comboName: "..."}`) ที่อ้างอิง คอมโบอื่นได้เช่นกัน โดยจะถูกประมวลผลเป็น **หนึ่งเสียงของพาเนลแบบกล่องดำ** (เป็นการส่งคำขอ แบบเวียนเกิดเต็มรูปแบบไปยังคอมโบที่อ้างอิง ไม่ใช่การกระจายไปยังเป้าหมายภายในของคอมโบนั้น) พร้อมการป้องกันความลึก/วงจรแบบเดียวกับที่กลยุทธ์อื่นซึ่งใช้งาน combo-ref มีอยู่แล้ว (#6764) ### การกำหนดค่า กำหนดค่าบนบล็อบ `config` ของคอมโบ (ไม่มีการย้ายสคีมา — ใช้ตาราง `combos` ที่มีอยู่เดิม): | ฟิลด์ | ชนิด | ค่าเริ่มต้น | วัตถุประสงค์ | | :--------------------------------------- | :------- | :-------------- | :----------------------------------------------------------------------------------------- | | `config.judgeModel` | `string` | โมเดลแรกในพาเนล | โมเดลที่สังเคราะห์คำตอบสุดท้าย | | `config.fusionTuning.minPanel` | `number` | `2` | จำนวนคำตอบที่สำเร็จซึ่งจำเป็นก่อนเริ่มตัวจับเวลาช่วงผ่อนผัน (จำกัดไว้ที่ `[2, panelSize]`) | | `config.fusionTuning.stragglerGraceMs` | `number` | `8000` | ระยะเวลารอโมเดลที่ล่าช้าหลังจากครบโควรัมแล้ว | | `config.fusionTuning.panelHardTimeoutMs` | `number` | `90000` | ขีดจำกัดเวลาสูงสุดแบบตายตัว เพื่อไม่ให้โมเดลหนึ่งที่ค้างทำให้คำขอหยุดชะงัก | ค่าเริ่มต้นอยู่ใน `FUSION_DEFAULTS` (`open-sse/services/fusion.ts`) ### ตัวอย่าง ```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 } } }' ``` จากนั้นเรียกใช้งานเหมือนคอมโบทั่วไป: `{"model":"fusion-panel","messages":[...]}` ## โรงงาน Auto-Combo แบบเสมือน เอนจิน Auto Combo ไม่จำเป็นต้องมีคอมโบที่กำหนดไว้ล่วงหน้า แต่ `open-sse/services/autoCombo/virtualFactory.ts` จะสร้างตัวเลือกแบบทันที: 1. ดึง `getProviderConnections({ isActive: true })` (การเชื่อมต่อทั้งหมดที่เปิดใช้งาน) 2. กรองเฉพาะรายการที่มีข้อมูลรับรองถูกต้อง (API key หรือ OAuth token ที่ยังไม่หมดอายุผ่าน `hasUsableOAuthToken()`) 3. ตรวจสอบเทียบกับ `getProviderRegistry()` เพื่อดูความพร้อมใช้งานของโมเดลและราคา 4. สำหรับแต่ละทูเพิล `(provider, model, connection)` ให้สร้าง `VirtualAutoComboCandidate` 5. เลือก `connection.defaultModel` (หรือโมเดลแรกในรีจิสทรี) เป็นเป้าหมายในการส่งคำขอ 6. ให้คะแนนตัวเลือกแต่ละรายการด้วย `scorePool()` แบบ 16 ปัจจัยและชุดค่าน้ำหนักของเวอร์ชันย่อย 7. ส่งคืน `AutoComboConfig` ที่ได้ซึ่งอยู่ในหน่วยความจำให้แก่ `handleComboChat()` — โดยไม่บันทึกลง DB ซึ่งหมายความว่า **การเพิ่มผู้ให้บริการรายใหม่ที่เปิดใช้ `auto/*` จะขยายกลุ่มตัวเลือกโดยอัตโนมัติ** — ไม่จำเป็นต้องแก้ไขคอมโบด้วยตนเอง คอมโบเสมือนจะถูกสร้างใหม่สำหรับแต่ละคำขอ ดังนั้นการเชื่อมต่อที่เพิ่งเพิ่มเข้ามาหรือเพิ่งกลับมามีสถานะปกติจะถูกนำมาใช้ทันที ## การฟื้นฟูตนเอง - **การยกเว้นชั่วคราว**: คะแนน < 0.2 → ยกเว้นเป็นเวลา 5 นาที (เพิ่มระยะเวลาแบบต่อเนื่อง สูงสุด 30 นาที) - **การรับรู้สถานะ circuit breaker**: OPEN → ยกเว้นโดยอัตโนมัติ; HALF_OPEN → ส่งคำขอทดสอบ - **โหมดเหตุขัดข้อง**: OPEN >50% → ปิดการสำรวจและเน้นเสถียรภาพสูงสุด - **การฟื้นตัวหลังคูลดาวน์**: หลังสิ้นสุดการยกเว้น คำขอแรกจะเป็น "คำขอทดสอบ" ที่ลดระยะหมดเวลา ## การสำรวจแบบ Bandit คำขอ 5% (กำหนดค่าได้) จะถูกกำหนดเส้นทางไปยังผู้ให้บริการแบบสุ่มเพื่อการสำรวจ ฟังก์ชันนี้จะถูกปิดในโหมดเหตุขัดข้อง ## API **ไม่มี endpoint `POST /api/combos/auto` โดยเฉพาะ** — Auto-Combo ใช้งานได้สองวิธี: 1. **ไม่ต้องกำหนดค่า (แนะนำ):** ส่งคำขอ chat completion ใดก็ได้โดยใช้ `model: "auto"` หรือ `model: "auto/"` โรงงานเสมือนจะสร้างคอมโบสำหรับแต่ละคำขอ โดยไม่ต้องจัดเก็บถาวรและไม่ต้องเรียก API เพิ่มเติม 2. **คอมโบที่จัดเก็บถาวรด้วย `strategy: "auto"`:** สร้างคอมโบปกติผ่าน `POST /api/combos` แล้วตั้งค่า `strategy: "auto"` พร้อมกับ `config.auto.weights` / `config.auto.candidatePool` ระบบจะใช้เอนจินให้คะแนนเดียวกัน โดยคอมโบจะถูกจัดเก็บไว้ใน `combos` และสามารถนำกลับมาใช้ซ้ำด้วย ID ได้ สำหรับการค้นหารายการ `GET /api/combos/auto` จะแสดงตัวแปรย่อยทั้งหมดพร้อมพูลผู้สมัครที่ผ่านการแก้ไขค่าแล้ว รวมถึง `context_length` / `max_output_tokens` ซึ่งเป็นค่าสูงสุดจากหน้าต่างบริบททั้งหมดในพูลผู้สมัคร ไคลเอนต์ (เช่น ปลั๊กอิน opencode) ต้องประกาศค่าเหล่านี้แทน `0`: บริบทที่เป็นศูนย์จะปิดใช้งานการย่อบริบทอัตโนมัติของ opencode โดยสิ้นเชิง ทำให้เซสชันขยายต่อไปจนกระทั่งการล้างประวัติของเกตเวย์ทำลายบริบท การประกาศค่าสูงสุดนั้นปลอดภัย เพราะตัวกรองล่วงหน้าด้านบริบทของ auto-combo จะกำหนดเส้นทางคำขอที่มีขนาดใหญ่เกินไปไปยังผู้สมัครที่มีหน้าต่างขนาดใหญ่ ```bash # การใช้งานแบบไม่ต้องกำหนดค่า (ไม่ต้องสร้างคอมโบ) 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"}]}' # คอมโบอัตโนมัติที่จัดเก็บถาวรผ่าน endpoint คอมโบปกติ 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}}}}' ``` ### กลยุทธ์เราเตอร์อัตโนมัติ คอมโบ `strategy: "auto"` ที่จัดเก็บถาวรสามารถตั้งค่า `config.routerStrategy` (หรือ `config.auto.routerStrategy` แบบเดิม) เป็นค่าใดค่าหนึ่งต่อไปนี้: - `rules` — การให้คะแนนแบบถ่วงน้ำหนักตามค่าเริ่มต้น - `score` — เลือกคะแนนถ่วงน้ำหนักที่กำหนดค่าไว้ซึ่งสูงที่สุด หากคะแนนเสมอกันทุกประการ จะคง ลำดับผู้สมัครตามที่กำหนดค่าไว้ ส่วน `explorationRate` ที่มีอยู่จะสุ่มตัวอย่างจากพูลที่จัดอันดับทั้งหมด - `cost` / `eco` — ผู้ให้บริการที่พร้อมใช้งานและมีราคาถูกที่สุด - `latency` / `fast` — เวลาแฝง p95 ต่ำที่สุดพร้อมบทลงโทษด้านความน่าเชื่อถือ - `sla-aware` / `sla` — เลือกผู้สมัครที่ตรงตาม SLO ด้านเวลาแฝง p95, อัตราข้อผิดพลาด และ ค่าใช้จ่ายที่ระบุหรือไม่ก็ได้ - `lkgp` — เลือกผู้ให้บริการรายล่าสุดที่ทราบว่าทำงานได้ดีก่อน - `nadir` — ขอให้ API การตัดสินใจของ [Nadir](https://getnadir.com) ระบุว่าพรอมต์ ต้องใช้โมเดลใดในพูล ต้องเลือกใช้โดยชัดแจ้ง และหากล้มเหลวจะเปิดทางให้ใช้ `rules` ### รายละเอียดกลยุทธ์เราเตอร์ เอนจิน auto-combo มีการใช้งาน **RouterStrategy** แบบเสียบเปลี่ยนได้ 7 รูปแบบ ซึ่ง คุณสามารถสลับผ่าน `config.routerStrategy` (หรือ `config.auto.routerStrategy` แบบเดิม) แต่ละกลยุทธ์จะเลือกผู้ให้บริการหนึ่งรายจากพูลผู้สมัคร โดยอิงตาม `RoutingContext` (ประเภทงาน, ตัวบ่งชี้การใช้เครื่องมือ/ภาพ, ค่าประมาณจำนวนโทเค็น, นโยบาย SLA ที่ระบุหรือไม่ก็ได้, และผู้ให้บริการรายล่าสุดที่ทราบว่าทำงานได้ดีซึ่งระบุหรือไม่ก็ได้) #### 1. `rules` (ค่าเริ่มต้น) — การให้คะแนนแบบถ่วงน้ำหนัก 16 ปัจจัย ครอบเอนจินให้คะแนนที่มีอยู่ โดยกรองผู้สมัครที่มี circuit breaker สถานะ `OPEN` ออก จากนั้นเรียกใช้ `scorePool()` ด้วยประเภทงานปัจจุบันและ `getTaskFitness()` แล้วเลือกผู้ให้บริการที่มีคะแนนสูงสุด ```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 /* ... */ }; } } ``` **ควรใช้เมื่อใด**: เป็นค่าเริ่มต้น ใช้เมื่อคุณต้องการความสมดุลระหว่างสัญญาณทั้งหมด **ชื่อเรียกอื่น**: `rules` (ไม่มีชื่อเรียกอื่น) --- #### 2. `cost` / `eco` — ผู้ให้บริการที่พร้อมใช้งานและมีราคาถูกที่สุด จัดเรียงพูลผู้สมัครตาม `costPer1MTokens` (จากน้อยไปมาก) และเลือกตัวเลือกที่ถูกที่สุด โดยกรองผู้สมัครที่มีสถานะ `OPEN` ออกก่อน ```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 /* ... */ }; } } ``` **ควรใช้เมื่อใด**: เวิร์กโหลดที่ให้ความสำคัญกับค่าใช้จ่าย, การประมวลผลแบบแบตช์ หรืองานเบื้องหลัง **ชื่อเรียกอื่น**: `cost`, `eco` --- #### 3. `latency` / `fast` — เวลาแฝง p95 ต่ำที่สุดพร้อมบทลงโทษด้านความน่าเชื่อถือ จัดเรียงตาม `p95LatencyMs + (errorRate * 1000)` บทลงโทษตามอัตราข้อผิดพลาดช่วยให้ ผู้ให้บริการที่ไม่น่าเชื่อถือมีอันดับต่ำลง แม้ว่าเวลาแฝงตามค่าระบุของผู้ให้บริการจะต่ำก็ตาม ```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 /* ... */ }; } } ``` **ควรใช้เมื่อใด**: เวิร์กโหลดที่ให้ความสำคัญกับเวลาแฝง เช่น แชตแบบเรียลไทม์, การเติมข้อความอัตโนมัติ หรือ ผู้ช่วยเขียนโค้ดแบบโต้ตอบ **ชื่อเรียกอื่น**: `latency`, `fast` --- #### 4. `sla-aware` / `sla` — การปฏิบัติตาม SLO ด้านเวลาแฝง/ข้อผิดพลาด/ค่าใช้จ่าย ให้คะแนนผู้สมัครแต่ละรายตามระดับความสอดคล้องกับนโยบาย SLO ที่กำหนดค่าไว้: | ปัจจัย | น้ำหนัก | สูตร | | --------------- | ------- | ------------------------------------------------------------------- | | คะแนนเวลาแฝง | 35% | `threshold / max(value, ε)` | | คะแนนข้อผิดพลาด | 35% | `threshold / max(value, ε)` | | คะแนนสถานะ | 15% | `1.0` (CLOSED) / `0.5` (HALF_OPEN) / `0.0` (OPEN) | | คะแนนต้นทุน | 10% | `threshold / max(value, ε)` หรือค่าผกผันที่ปรับให้อยู่ในช่วงมาตรฐาน | | คะแนนเสถียรภาพ | 5% | ส่วนเบี่ยงเบนมาตรฐานของเวลาแฝงแบบผกผันที่ปรับให้อยู่ในช่วงมาตรฐาน | เมื่อ `hardConstraints: true` ระบบจะเรียงผู้สมัครตาม **คะแนนการละเมิด** (ระดับที่เกิน SLO ใดๆ) เป็นหลัก แล้วจึงเรียงตามคะแนนรวม มิฉะนั้นจะเรียง ตามคะแนนรวมเพียงอย่างเดียว ```ts class SLAStrategyImpl implements RouterStrategy { readonly name = "sla-aware"; readonly description = "Selects the provider most likely to satisfy latency, error-rate, and cost SLOs"; select(pool, context) { // ... ให้คะแนนผู้สมัครแต่ละรายเทียบกับนโยบาย: { targetP95Ms, maxErrorRate, maxCostPer1MTokens, hardConstraints } } } ``` **ฟิลด์ SLA** (กำหนดในการกำหนดค่าแบบผสม): ```json { "strategy": "auto", "config": { "routerStrategy": "sla-aware", "slaTargetP95Ms": 1500, "slaMaxErrorRate": 0.05, "slaMaxCostPer1MTokens": 5, "slaHardConstraints": true } } ``` **ควรใช้เมื่อ**: เวิร์กโหลดการใช้งานจริงที่มีข้อกำหนดเข้มงวดด้านเวลาแฝง อัตราข้อผิดพลาด หรืองบประมาณต้นทุน **ชื่อเรียกอื่น**: `sla-aware`, `sla` --- #### 5. `lkgp` — ใช้ผู้ให้บริการที่ใช้งานได้ดีล่าสุดก่อน ลองใช้ **ผู้ให้บริการที่ใช้งานได้ดีล่าสุด** (หากกำหนดไว้) ก่อน แล้วจึงย้อนกลับไปใช้ กลยุทธ์ `rules` มีประโยชน์สำหรับการคงความต่อเนื่องของเซสชัน โดยให้ผู้ให้บริการรายเดิมจัดการ คำขอต่อเนื่องในการสนทนา ```ts class LKGPStrategyImpl implements RouterStrategy { readonly name = "lkgp"; readonly description = "Tries last known good provider first, then falls back to 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 /* ... */ }; } } // ย้อนกลับไปใช้กลยุทธ์ rules return getStrategy("rules").select(pool, context); } } ``` **ควรใช้เมื่อ**: การสนทนาแบบหลายรอบที่คุณต้องการให้ผู้ให้บริการรายเดิมจัดการ คำขอต่อเนื่อง (เช่น เพื่อการแคช ความต่อเนื่องของบริบท หรือความสม่ำเสมอของราคา) **ชื่อเรียกอื่น**: `lkgp` (ไม่มีชื่อเรียกอื่น) --- #### 6. `nadir` — เลือกโมเดลตามพรอมต์ผ่าน Nadir กลยุทธ์ทั้งหมดข้างต้นจัดอันดับผู้สมัครตามข้อมูลเทเลเมทรีของตนเอง โดยไม่มีกลยุทธ์ใดอ่าน คำขอ `nadir` จะส่งข้อความล่าสุดของผู้ใช้พร้อมรหัสโมเดลในพูลไปยัง API สำหรับการตัดสินใจของ [Nadir](https://getnadir.com) (`POST /v1/bucket`) และกำหนดเส้นทางไปยังโมเดล ที่ Nadir เลือกจากรายการนั้น (`simple` → โมเดลที่มีความสามารถเพียงพอและราคาถูกที่สุด, `complex` → โมเดล ระดับแนวหน้า) ส่วนการเชื่อมต่อที่ให้บริการโมเดลดังกล่าวยังคงถูกเลือกโดย `rules` ดังนั้นโควตา สถานะ และต้นทุนจึงยังคงเป็นปัจจัยตัดสินว่าจะใช้บัญชีใด ```json { "strategy": "auto", "config": { "routerStrategy": "nadir", "nadir": { "apiKey": "ndr_...", "baseUrl": "https://api.getnadir.com", "timeoutMs": 2000 } } } ``` `OMNIROUTE_NADIR_API_KEY` และ `OMNIROUTE_NADIR_BASE_URL` เป็นค่าจากสภาพแวดล้อมที่ใช้สำรองสำหรับสตริงทั้งสอง ต้องใช้ `baseUrl` เฉพาะกับ Nadir ที่โฮสต์เองเท่านั้น (สามารถมี `/v1` ต่อท้ายได้) การเรียกโดยไม่มีคีย์จะใช้ระดับแบบไม่ระบุตัวตนของ Nadir ซึ่งจำกัดอัตราต่อ IP ข้อมูลที่ถูกส่งออกจากระบบ ได้แก่ ข้อความล่าสุดของผู้ใช้ (16k อักขระแรก), รหัสโมเดล ของผู้สมัคร และแท็กช่องทาง `source: "omniroute"` โดยไม่มีพรอมต์ระบบ ประวัติ เครื่องมือ หรือ ส่วนหัว พฤติกรรมเมื่อเกิดความล้มเหลวเป็นแบบ fail-open: การหมดเวลา (ค่าเริ่มต้น 2000 ms), การตอบกลับที่ไม่ใช่ 2xx, โฮสต์ที่ เข้าถึงไม่ได้, การตอบกลับที่มีรูปแบบไม่ถูกต้อง หรือการเลือกที่อยู่นอกพูล จะเปลี่ยนไปใช้การตัดสินใจของ `rules` และเหตุผลจะขึ้นต้นด้วย `NadirStrategy: fallback (…)` หลังจากการเรียกที่ล้มเหลว กลยุทธ์จะ ข้ามการเรียกเครือข่ายเป็นเวลา 30 s ดังนั้นเมื่อระบบหยุดทำงาน จะเสียเวลารอเพียงหนึ่งครั้งต่อ 30 s แทนที่จะเป็น หนึ่งครั้งต่อคำขอ เหตุการณ์การกำหนดเส้นทางจะรายงาน `strategy: "nadir"` เฉพาะเมื่อ Nadir เป็นผู้ตัดสินใจ เลือกจริงเท่านั้น **ควรใช้เมื่อ**: ทราฟฟิกที่มีระดับความยากหลากหลายบนพูลที่ครอบคลุมโมเดลหลายระดับ (โมเดลขนาดเล็ก ระดับกลาง และระดับแนวหน้า) โดยต้องการลดต้นทุนจากการใช้โมเดลระดับแนวหน้าเสมอ **ชื่อเรียกอื่น**: `nadir` (ไม่มีชื่อเรียกอื่น) --- ### กลยุทธ์เราเตอร์แบบกำหนดเอง คุณสามารถลงทะเบียนการใช้งาน `RouterStrategy` ของคุณเองผ่าน API สาธารณะได้: ```ts import { registerStrategy, type RouterStrategy, } from "@omniroute/open-sse/services/autoCombo/routerStrategy"; class MyCustomStrategy implements RouterStrategy { readonly name = "my-custom"; readonly description = "My custom routing strategy"; select(pool, context) { // ใส่ตรรกะการกำหนดเส้นทางของคุณที่นี่ 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()); ``` จากนั้นนำไปใช้: ```json { "strategy": "auto", "config": { "routerStrategy": "my-custom" } } ``` --- ### คู่มือการเลือกกลยุทธ์เราเตอร์ | กรณีการใช้งาน | กลยุทธ์ | เหตุผล | | ---------------------- | ----------- | ----------------------------------------------- | | เวิร์กโหลดสมดุล | `rules` | ค่าเริ่มต้น — พิจารณาปัจจัยทั้งหมด | | ลดต้นทุนให้น้อยที่สุด | `cost` | เลือกตัวเลือกที่ถูกที่สุดเสมอ | | ลดเวลาแฝงให้น้อยที่สุด | `latency` | เลือกผู้ให้บริการที่รวดเร็วและเชื่อถือได้ที่สุด | | SLO ที่เข้มงวด | `sla-aware` | กรองตามเกณฑ์ p95/ข้อผิดพลาด/ต้นทุน | | แชตหลายรอบ | `lkgp` | คงความต่อเนื่องของเซสชัน | | ความยากหลากหลาย | `nadir` | เลือกระดับโมเดลตามแต่ละพรอมต์ | ฟิลด์สำหรับ SLA-aware: ```json { "strategy": "auto", "config": { "routerStrategy": "sla-aware", "slaTargetP95Ms": 1500, "slaMaxErrorRate": 0.05, "slaMaxCostPer1MTokens": 5, "slaHardConstraints": true } } ``` ## ความเหมาะสมกับงาน โมเดลมากกว่า 30 รายการได้รับการประเมินในงาน 6 ประเภท (`coding`, `review`, `planning`, `analysis`, `debugging`, `documentation`) รองรับรูปแบบไวลด์การ์ด (เช่น `*-coder` → คะแนนด้านการเขียนโค้ดสูง) ## สรุป Auto Variants เมื่อรวม `auto` แบบเปล่า (ค่าเริ่มต้น) กับค่า `AutoVariant` ทั้ง 6 ค่าที่ประกาศไว้ใน `autoPrefix.ts` จะมี **ID โมเดลที่เรียกใช้ได้ 7 รายการ**: `auto`, `auto/coding`, `auto/fast`, `auto/cheap`, `auto/offline`, `auto/smart`, `auto/lkgp` (`AutoVariant` แจกแจงค่าไว้ 6 ค่า ส่วนตัวเลือกที่ 7 คือ "ไม่มี variant" — `auto` แบบเปล่า — ซึ่งจัดการโดย `parseAutoPrefix()` เป็น `variant: undefined`) ## ระดับ Tier ทำงานร่วมกับ Auto-Combo อย่างไร ฟังก์ชันการให้คะแนนแบบ 16 ปัจจัย (`open-sse/services/autoCombo/scoring.ts`) ถือว่าการเป็นสมาชิกระดับ Tier เป็นสัญญาณสองรายการ ได้แก่ `tierPriority` (0.0476) และ `tierAffinity` (0.0476) โปรดดู [ตารางปัจจัยการให้คะแนน](#how-it-works-persisted-auto-combos) มาตรฐานด้านบนสำหรับชุด `DEFAULT_WEIGHTS` ทั้งหมด — ค่าที่กำหนดทับต่อแพ็ก (ship-fast/cost-saver/quality-first/ offline-friendly) แสดงอยู่ในตาราง "โปรไฟล์น้ำหนักต่อแพ็ก" ระดับ Tier เพียงอย่างเดียว **ไม่ได้** บังคับให้ Tier 1 มาก่อน — หากเวลาแฝงของ Tier 1 ไม่ดี หรือ ความคุ้มค่าระหว่างต้นทุนกับคุณภาพไม่เหมาะสม Tier 2 จะชนะ หากต้องการบังคับลำดับตาม Tier ให้ใช้ กลยุทธ์คอมโบ `priority` และจัดเรียงผู้ให้บริการตาม Tier หากต้องการให้น้ำหนักแก่ Tier 1 (การสมัครสมาชิก) อย่างมาก ให้เพิ่มน้ำหนักของ `tierPriority`: ```json { "strategy": "auto", "config": { "auto": { "weights": { "tierPriority": 0.3, "costInv": 0.05 } } } } ``` โปรดดู [`docs/guides/TIERS.md`](../guides/TIERS.md) สำหรับคำจำกัดความของ Tier และการจัดประเภทผู้ให้บริการ ## การทดสอบและความครอบคลุม ### เมทริกซ์การตัดสินใจกำหนดเส้นทางแบบกำหนดผลลัพธ์ได้ (`npm run test:combo:matrix`) `tests/integration/combo-matrix/*.test.ts` พิสูจน์ **การตัดสินใจ** กำหนดเส้นทางของกลยุทธ์สาธารณะทั้ง 19 รายการแบบต้นทางถึงปลายทางผ่านไปป์ไลน์ combo จริง โดยจำลองระบบต้นทาง ความครอบคลุมประกอบด้วย: - กลยุทธ์ `ROUTING_STRATEGY_VALUES` ทั้ง 19 รายการ (ordered, weighted, cost, context, fusion, …) - `quota-share` (ภายใน) แบบต้นทางถึงปลายทาง: ความเป็นธรรมของ DRR + การลดลำดับความสำคัญเมื่ออิ่มตัวผ่านจุดเชื่อมต่อ `selectQuotaShareTarget` จริง (`registerQuotaFetcher` / `setLKGP` / `__setHeadroomSaturationFetcherForTests`) - ความครอบคลุมของการส่งต่อแบบสากลสำหรับ `context-relay` ในทุกจำนวนเป้าหมาย ชุดการทดสอบนี้ทำงานใน CI (งาน `test:integration`) โดยใช้ `--test-concurrency=1` และ `--test-force-exit` เพื่อให้ผลลัพธ์แน่นอนและไม่ต้องใช้ข้อมูลประจำตัวจริง ### การทดสอบ smoke แบบ live ที่มีเงื่อนไข (ไม่อยู่ใน CI — ผู้ให้บริการจริง) | คำสั่ง | สิ่งที่ทำ | | :------------------------------------- | :---------------------------------------------------------------------------------------------------------- | | `npm run test:combo:live` | การกำหนดเส้นทางจริงภายในโปรเซสด้วย `RUN_COMBO_LIVE=1`; บันทึก snapshot ของฐานข้อมูล OmniRoute ที่ใช้งานจริง | | `npm run test:combo:live:vps` | เรียก HTTP ไปยังเซิร์ฟเวอร์ OmniRoute ที่ใช้งานจริง (ตั้งค่า `COMBO_LIVE_BASE_URL`) | | `npm run test:combo:live:vps:failover` | เช่นเดียวกัน แต่มีสถานการณ์ failover ที่จงใจสร้างขึ้น | การทดสอบ smoke เหล่านี้ใช้งานเส้นทางการสื่อสารจริง (combo → ผู้ให้บริการ → completion) โดยจงใจไม่นำไปรวมใน CI เนื่องจากต้องใช้ข้อมูลประจำตัวจริงและสิทธิ์เข้าถึง VPS --- ## ไฟล์ | ไฟล์ | วัตถุประสงค์ | | :-------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------- | | `open-sse/services/autoCombo/scoring.ts` | ฟังก์ชันการให้คะแนน 16 ปัจจัย, `DEFAULT_WEIGHTS`, การปรับบรรทัดฐานของพูล | | `open-sse/services/autoCombo/taskFitness.ts` | ตารางค้นหาความเหมาะสมระหว่างโมเดล × งาน | | `open-sse/services/autoCombo/engine.ts` | ตรรกะการเลือก, แบนดิต, เพดานงบประมาณ | | `open-sse/services/autoCombo/selfHealing.ts` | การคัดออก, การตรวจสอบ, โหมดเหตุขัดข้อง | | `open-sse/services/autoCombo/modePacks.ts` | โปรไฟล์ค่าน้ำหนัก 6 แบบ (ship-fast, cost-saver, quality-first, offline-friendly, reliability-first, chaos-mode) | | `open-sse/services/autoCombo/autoPrefix.ts` | ตัวแยกวิเคราะห์คำนำหน้า `auto/` + ตัวแปร 6 แบบ | | `open-sse/services/autoCombo/virtualFactory.ts` | สร้าง `AutoComboConfig` ในหน่วยความจำจากการเชื่อมต่อที่ใช้งานอยู่ | | `open-sse/services/autoCombo/providerRegistryAccessor.ts` | จุดเชื่อมสำหรับการทดสอบเพื่อจำลองรีจิสทรีของผู้ให้บริการ | | `src/shared/constants/routingStrategies.ts` | `ROUTING_STRATEGY_VALUES` (20 กลยุทธ์) | | `src/sse/handlers/chat.ts` | การผสานรวม: ลัดวงจรด้วยคำนำหน้า auto |