# 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) ยท ๐Ÿ‡ฑ๐Ÿ‡น [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) ยท ๐Ÿ‡ป๐Ÿ‡ณ [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/` ์ ‘๋‘์‚ฌ๋ฅผ ์ง์ ‘ ์‚ฌ์šฉํ•˜์„ธ์š”. ### ๋น ๋ฅธ ์˜ˆ์‹œ | ๋ชจ๋ธ ID | ๋ณ€ํ˜• | ๋™์ž‘ | | -------------- | ------- | ----------------------------------------------------- | | `auto` | ๊ธฐ๋ณธ๊ฐ’ | ์—ฐ๊ฒฐ๋œ ๋ชจ๋“  ์ œ๊ณต์ž, 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`(ํšŒ๋กœ ์ฐจ๋‹จ๊ธฐ ์ƒํƒœ + ์ง€์—ฐ ์‹œ๊ฐ„ ์•ˆ์ •์„ฑ) ยท `free` / `pro`(`classifyTier`๋ฅผ ํ†ตํ•ด ๋ชจ๋ธ ํ‹ฐ์–ด๋ณ„๋กœ ํ’€ ํ•„ํ„ฐ๋ง โ€” ๋ฌด๋ฃŒ ํ‹ฐ์–ด์™€ ํ”„๋ฆฌ๋ฏธ์—„ ํ‹ฐ์–ด). | ์˜ˆ์‹œ | ํ•ด์„ ๊ฒฐ๊ณผ | | ---------------------- | ------------------------------------------------- | | `auto/coding:fast` | ์ฝ”๋”ฉ ํ’€, ์งง์€ ์ง€์—ฐ ์‹œ๊ฐ„ ๊ฐ€์ค‘์น˜ | | `auto/coding:cheap` | ์ฝ”๋”ฉ ํ’€, ๋น„์šฉ ์ตœ์ ํ™”(๋ณ„์นญ `auto/coding:floor`) | | `auto/reasoning:pro` | ์ถ”๋ก /์‚ฌ๊ณ  ๋ชจ๋ธ๋งŒ, ํ”„๋ฆฌ๋ฏธ์—„ ํ‹ฐ์–ด | | `auto/vision` | ๋น„์ „ ๊ธฐ๋Šฅ ์ง€์› ๋ชจ๋ธ(ํ‹ฐ์–ด ์—†์Œ โ†’ ๊ท ํ˜• ์žกํžŒ ๊ฐ€์ค‘์น˜) | | `auto/multimodal:free` | ๋ฉ€ํ‹ฐ๋ชจ๋‹ฌ ๊ธฐ๋Šฅ ์ง€์› ๋ชจ๋ธ, ๋ฌด๋ฃŒ ํ‹ฐ์–ด๋งŒ | ์œ ํšจํ•œ ๋ชจ๋“  `auto/[:]`๋Š” ์š”์ฒญ ์‹œ ํ•ด์„๋ฉ๋‹ˆ๋‹ค. ์—„์„ ๋œ ์ผ๋ถ€ ํ•ญ๋ชฉ์€ `/v1/models`์™€ ๋Œ€์‹œ๋ณด๋“œ(`open-sse/services/autoCombo/builtinCatalog.ts`์˜ `AUTO_SUFFIX_VARIANTS`)์— ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค. ํ•„ํ„ฐ๋ง์€ **์žฅ์•  ์‹œ ๊ฐœ๋ฐฉ** ๋ฐฉ์‹์ž…๋‹ˆ๋‹ค. ์ œ์•ฝ ์กฐ๊ฑด๊ณผ ์ผ์น˜ํ•˜๋Š” ์—ฐ๊ฒฐ๋œ ๋ชจ๋ธ์ด ์—†์œผ๋ฉด ์ „์ฒด ํ’€์ด ์‚ฌ์šฉ๋˜๋ฏ€๋กœ ๋ผ์šฐํŒ…์ด ์ค‘๋‹จ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ํ•ต์‹ฌ ์ ์ˆ˜ ๊ณ„์‚ฐ๊ธฐ(`combo.ts`)๋Š” ๋ณ€๊ฒฝ๋˜์ง€ ์•Š์œผ๋ฉฐ, ์นดํ…Œ๊ณ ๋ฆฌ/ํ‹ฐ์–ด ํ•„ํ„ฐ๋Š” `buildAutoCandidates`์—์„œ ์ ์šฉ๋ฉ๋‹ˆ๋‹ค. > **์‹ค์‹œ๊ฐ„ ๋ชจ๋ธ ์ธํ…”๋ฆฌ์ „์Šค:** `ARENA_ELO_SYNC_ENABLED` ํ”Œ๋ž˜๊ทธ๊ฐ€ ์ผœ์ ธ ์žˆ์œผ๋ฉด ์‹ค์‹œ๊ฐ„ **Arena ELO** ์ˆœ์œ„์™€ **models.dev** ํ‹ฐ์–ด ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ ์ž๋™ ๋ผ์šฐํŒ… ์ ํ•ฉ๋„๋ฅผ ํŒ๋‹จํ•ฉ๋‹ˆ๋‹ค(๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด ์ •์  ์ ํ•ฉ๋„ ๋งต์œผ๋กœ ๋Œ€์ฒด). **์‚ฌ์šฉ ๋ฐฉ๋ฒ•:** ```bash # OpenAI ํ˜•์‹์„ ์ง€์›ํ•˜๋Š” ๋ชจ๋“  IDE ๋˜๋Š” CLI ๋„๊ตฌ Base URL: http://localhost:20128/v1 API Key: # ์ฝ”๋“œ/๊ตฌ์„ฑ์—์„œ ๋ชจ๋ธ์„ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์„ค์ •: model: "auto" # ๊ท ํ˜• ์žกํžŒ ๊ธฐ๋ณธ๊ฐ’ model: "auto/coding" # ์ฝ”๋”ฉ ์ž‘์—…์— ๊ฐ€์žฅ ์ ํ•ฉ model: "auto/fast" # ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ํ•ญ๋ชฉ ์ค‘ ๊ฐ€์žฅ ๋น ๋ฆ„ model: "auto/cheap" # ํ† ํฐ๋‹น ๋น„์šฉ์ด ๊ฐ€์žฅ ์ €๋ ดํ•จ ``` **์ฒ˜๋ฆฌ ๊ณผ์ •:** 1. OmniRoute๊ฐ€ `src/sse/handlers/chat.ts`์—์„œ `auto/` ์ ‘๋‘์‚ฌ๋ฅผ ๊ฐ์ง€ํ•ฉ๋‹ˆ๋‹ค 2. ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ ๋ชจ๋“  **ํ™œ์„ฑ ์ œ๊ณต์ž ์—ฐ๊ฒฐ**์„ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค 3. ์œ ํšจํ•œ ์ž๊ฒฉ ์ฆ๋ช…(API ํ‚ค ๋˜๋Š” OAuth ํ† ํฐ)์ด ์žˆ๋Š” ์—ฐ๊ฒฐ๋งŒ ํ•„ํ„ฐ๋งํ•ฉ๋‹ˆ๋‹ค 4. ์—ฐ๊ฒฐ๋ณ„ ๋ชจ๋ธ(`connection.defaultModel` ๋˜๋Š” ์ œ๊ณต์ž์˜ ์ฒซ ๋ฒˆ์งธ ๋ชจ๋ธ)์„ ๊ฒฐ์ •ํ•ฉ๋‹ˆ๋‹ค 5. ๋ฉ”๋ชจ๋ฆฌ ๋‚ด์— **๊ฐ€์ƒ ์ฝค๋ณด**๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค(DB์— ์ €์žฅ๋˜์ง€ ์•Š์Œ) 6. ์„ ํƒํ•œ ๋ณ€ํ˜•์˜ ๊ฐ€์ค‘์น˜ ํ”„๋กœํ•„ + LKGP ์ „๋žต์„ ์‚ฌ์šฉํ•ด ๋ผ์šฐํŒ…ํ•ฉ๋‹ˆ๋‹ค **์ฃผ์š” ์†์„ฑ:** - โœ… **์ƒ์‹œ ํ™œ์„ฑํ™”:** ํ† ๊ธ€, ์ฝค๋ณด ์ƒ์„ฑ ๋˜๋Š” ๊ตฌ์„ฑ์ด ํ•„์š”ํ•˜์ง€ ์•Š์Œ - โœ… **๋™์ :** ํ˜„์žฌ ์—ฐ๊ฒฐ๋œ ์ œ๊ณต์ž๋ฅผ ์ž๋™์œผ๋กœ ๋ฐ˜์˜ - โœ… **์„ธ์…˜ ๊ณ ์ •์„ฑ:** LKGP๊ฐ€ ๋งˆ์ง€๋ง‰์œผ๋กœ ์„ฑ๊ณตํ•œ ์ œ๊ณต์ž๋ฅผ ์šฐ์„ ํ•˜๋„๋ก ๋ณด์žฅ - โœ… **๋‹ค์ค‘ ๊ณ„์ • ์ธ์‹:** ๊ฐ ์ œ๊ณต์ž ์—ฐ๊ฒฐ์ด ๋ณ„๋„์˜ ํ›„๋ณด๊ฐ€ ๋จ - โœ… **DB ์“ฐ๊ธฐ ์—†์Œ:** ๊ฐ€์ƒ ์ฝค๋ณด๋Š” ์š”์ฒญ์— ๋Œ€ํ•ด์„œ๋งŒ ์กด์žฌํ•˜๋ฏ€๋กœ ์˜์†์„ฑ ์˜ค๋ฒ„ํ—ค๋“œ๊ฐ€ ์—†์Œ ### ํ‚ค๋ณ„ ํ›„๋ณด ์ œ์–ด(#7819, ๋ ˆ๋ฒจ 1+2) `GET /v1/auto-combo/{channel}/candidates`์—์„œ (`{channel}` = `auto/` ๋’ค์˜ ์ ‘๋ฏธ์‚ฌ ๋˜๋Š” ๊ธฐ๋ณธ ์ฑ„๋„์˜ ๊ฒฝ์šฐ ๋ฆฌํ„ฐ๋Ÿด `auto`)๋Š” ๊ธฐ์กด ๋ณต์›๋ ฅ ์กฐํšŒ๋ฅผ ์žฌ์‚ฌ์šฉํ•˜์—ฌ ์‹ค์‹œ๊ฐ„ ๋„๋‹ฌ ๊ฐ€๋Šฅ์„ฑ ์ •๋ณด๊ฐ€ ์ถ”๊ฐ€๋œ `auto/*` ์ฑ„๋„์˜ ํ˜„์žฌ ํ›„๋ณด ํ’€์„ ๋‚˜์—ดํ•˜๋Š” **์ฝ๊ธฐ ์ „์šฉ** ์—”๋“œํฌ์ธํŠธ์ž…๋‹ˆ๋‹ค(ํšŒ๋กœ ์ฐจ๋‹จ๊ธฐ์˜ ์›์‹œ `state`๋Š” ์‚ฌ์šฉํ•˜์ง€ ์•Š์Œ). - ์ œ๊ณต์ž ํšŒ๋กœ ์ฐจ๋‹จ๊ธฐ โ€” `getCircuitBreaker(provider).getStatus()` / `.canExecute()` - ์—ฐ๊ฒฐ ์ฟจ๋‹ค์šด โ€” ํ™•์ธ๋œ `provider_connections` ํ–‰์˜ `rateLimitedUntil` / `testStatus` - ๋ชจ๋ธ ์ž ๊ธˆ โ€” `isModelLocked(provider, connectionId, model)` ๊ฐ ํ›„๋ณด์—๋Š” ์ด API ํ‚ค์˜ `excluded` ํ”Œ๋ž˜๊ทธ๋„ ํฌํ•จ๋ฉ๋‹ˆ๋‹ค. ์ œ์™ธ ์„ค์ •์€ API ํ‚ค๋ณ„๋กœ ์ €์žฅ๋ฉ๋‹ˆ๋‹ค(`auto_candidate_overrides` ํ…Œ์ด๋ธ”, ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ `128`). OmniRoute๋Š” `users` ํ…Œ์ด๋ธ”์ด ์—†๋Š” ๋‹จ์ผ ํ…Œ๋„ŒํŠธ์ด๋ฏ€๋กœ `apiKeyId`๊ฐ€ ์‹ค์ œ ํ˜ธ์ถœ์ž๋ณ„ ID์— ๊ฐ€์žฅ ๊ฐ€๊น์Šต๋‹ˆ๋‹ค. ์ด ์„ค์ •์€ ์ˆœ์ˆ˜ ํ•จ์ˆ˜์ด์ž ๋‹จ์œ„ ํ…Œ์ŠคํŠธ๋ฅผ ๊ฑฐ์นœ `filterExcludedCandidates()`(`open-sse/services/autoCombo/candidateOverrides.ts`)๋ฅผ ํ†ตํ•ด `open-sse/services/autoCombo/virtualFactory.ts`์˜ ํ›„๋ณด ํ’€ ๋ณ‘๋ชฉ ์ง€์ ์—์„œ ์ ์šฉ๋ฉ๋‹ˆ๋‹ค. ํ•„ํ„ฐ๋Š” **์žฅ์•  ์‹œ ๊ฐœ๋ฐฉ** ๋ฐฉ์‹์ž…๋‹ˆ๋‹ค. apiKeyId/์ฑ„๋„์ด ์„ค์ •๋˜์ง€ ์•Š์•˜๊ฑฐ๋‚˜ DB ์กฐํšŒ์— ์‹คํŒจํ•˜๋ฉด ํ’€์ด ํ•„ํ„ฐ๋ง๋˜์ง€ ์•Š์œผ๋ฏ€๋กœ, ์žฌ์ •์˜๊ฐ€ ๊ตฌ์„ฑ๋˜์ง€ ์•Š์€ ์šด์˜์ž์—๊ฒŒ๋Š” ์ด ๊ธฐ๋Šฅ ๋„์ž… ์ด์ „๊ณผ ๋ฐ”์ดํŠธ ๋‹จ์œ„๋กœ ๋™์ผํ•œ ๋ผ์šฐํŒ…์ด ์ œ๊ณต๋ฉ๋‹ˆ๋‹ค. **ํ›„์† ์ด์Šˆ๋กœ ์—ฐ๊ธฐ๋จ:** ํ›„๋ณด๋ณ„ ๊ฐ€์ค‘์น˜ + ๋ช…์‹œ์  ์ˆœ์„œ ์ง€์ •(Level 3 โ€” ๊ธฐ์กด์˜ ๊ฐ€์ค‘์น˜/์šฐ์„ ์ˆœ์œ„ ์ „๋žต ๊ฒฝ๋กœ์— ๋ฐ˜์˜) ๋ฐ `auto/*` ์ฑ„๋„๋ณ„๋กœ ํŠน์ • `combo.ts` ์ „๋žต ๊ณ ์ •(Level 4). ๋‹จ์ผ ํ…Œ๋„ŒํŠธ ๋ชจ๋ธ์„ ๊ณ ๋ คํ•  ๋•Œ ์žฌ์ •์˜๋ฅผ API ํ‚ค๋ณ„๋กœ ์œ ์ง€ํ• ์ง€ ์ „์—ญ์œผ๋กœ ๋ณ€๊ฒฝํ• ์ง€์— ๋Œ€ํ•œ ๋ฏธํ•ด๊ฒฐ ์งˆ๋ฌธ์€ #7819 ๊ณ„ํš์„ ์ฐธ์กฐํ•˜์„ธ์š”. **๋‚ด๋ถ€ ๋™์ž‘:** ```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` | ํ†ตํ•ฉ: auto ์ ‘๋‘์‚ฌ ๋‹จ๋ฝ ์ฒ˜๋ฆฌ | | `src/shared/constants/providers.ts` | `SYSTEM_PROVIDERS.auto` ์‹œ์Šคํ…œ ํ•ญ๋ชฉ | ## ์‹ค์ œ ๋ชจ๋ธ ID์™€ ์ผ์น˜ํ•˜๋Š” ์ฝค๋ณด ์ด๋ฆ„ `name`์ด ์ ‘๋‘์‚ฌ ์—†๋Š” ๋ชจ๋ธ ID์™€ ๋™์ผํ•œ ์ฝค๋ณด(์˜ˆ: ์ด๋ฆ„์ด `gpt-5.5`์ธ ์ฝค๋ณด)๋Š” ๋ฒ„๊ทธ๊ฐ€ ์•„๋‹ˆ๋ผ **์˜๋„์ ์œผ๋กœ ์ง€์›๋˜๋Š” ํŒจํ„ด**์ž…๋‹ˆ๋‹ค. ์ด๋Š” [#6940](https://github.com/diegosouzapw/OmniRoute/issues/6940)์— ๋ฌธ์„œํ™”๋œ ๋ชจ๋ธ ID๋ณ„ ๊ณต๊ธ‰์ž ํด๋ฐฑ ๋ฉ”์ปค๋‹ˆ์ฆ˜์ž…๋‹ˆ๋‹ค. ์ ‘๋‘์‚ฌ ์—†๋Š” ๋ชจ๋ธ ID๋ฅผ ํ™•์ธํ•˜๊ธฐ ์ „์— ์ฝค๋ณด ํ•ด์„์„ ๋จผ์ € ํ™•์ธํ•˜๋ฏ€๋กœ(`src/sse/services/model.ts`์˜ `getComboForModel()`), ์ ‘๋‘์‚ฌ ์—†๋Š” ID `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`์—์„œ ํšŒ๊ท€ ํ…Œ์ŠคํŠธ๋ฉ๋‹ˆ๋‹ค. ์‹ค์ œ ๋ชจ๋ธ ID๋ฅผ ๊ฐ€๋ฆฌ๋Š” ์ด๋ฆ„์œผ๋กœ ์ฝค๋ณด๋ฅผ ์ƒ์„ฑํ•˜๊ฑฐ๋‚˜ ์ด๋ฆ„์„ ๋ณ€๊ฒฝํ•˜๋Š” ์ž‘์—…์€ **์ ˆ๋Œ€๋กœ ๊ฑฐ๋ถ€๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค**. ์ด๋ฅผ ๊ฑฐ๋ถ€ํ•˜๋ฉด ๋ฌธ์„œํ™”๋œ ์ด ์›Œํฌํ”Œ๋กœ๊ฐ€ ์ค‘๋‹จ๋˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ๋Œ€์‹  (#8530), ์ƒˆ ์ด๋ฆ„์ด ์‹ค์ œ ๋ชจ๋ธ ID์™€ ์ถฉ๋Œํ•˜๋ฉด `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`)๋Š” ๋ชจ๋ธ ID๋ฅผ ๊ฐ€๋ฆฌ๋Š” ๊ธฐ์กด์˜ ๋ชจ๋“  ์ฝค๋ณด๋ฅผ ๋‚˜์—ดํ•˜๋Š” ํ•œ ์ค„์งœ๋ฆฌ `[STARTUP]` ๊ฒฝ๊ณ ๋„ ๊ธฐ๋กํ•ฉ๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ #6940์— ๋”ฐ๋ผ ์˜๋„์ ์œผ๋กœ ์‚ฌ์šฉํ•œ ๊ฒฝ์šฐ๊ฐ€ ์•„๋‹ˆ๋ผ ์‹ค์ˆ˜๋กœ ์ด ์ƒํ™ฉ์„ ์ดˆ๋ž˜ํ•œ ์šด์˜์ž๋„ ์ด๋ฅผ ์ธ์ง€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ฐ์ง€ ํ—ฌํผ๋Š” `src/lib/combos/modelNameCollision.ts`์— ์žˆ์Šต๋‹ˆ๋‹ค. ## ํด๋ผ์ด์–ธํŠธ์—์„œ ์‚ฌ์šฉ์ž ์ง€์ • ์ฝค๋ณด ํ˜ธ์ถœํ•˜๊ธฐ ์˜๊ตฌ ์ €์žฅ๋œ ์ฝค๋ณด(Settings โ†’ Combos)๋Š” ํด๋ผ์ด์–ธํŠธ๊ฐ€ `model` ํ•„๋“œ์— ์ฝค๋ณด์˜ **์ •ํ™•ํ•œ ์ด๋ฆ„**์„ ์ „์†กํ•  ๋•Œ๋งŒ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. ์ฝค๋ณด ์ด๋ฆ„์— ๋Œ€ํ•œ ์œ ์‚ฌ ๋˜๋Š” ๋ถ€๋ถ„ ์ผ์น˜๋Š” ์—†์œผ๋ฉฐ, `auto/` ์ ‘๋‘์‚ฌ๋„ ์‚ฌ์šฉ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ํ•ด์„ ์ˆœ์„œ(`src/sse/services/model.ts`์˜ `getComboForModel()`): 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` ํ’€์—์„œ ์ œ์™ธํ•˜๋ ค๋ฉด Settings โ†’ Routing โ†’ Hide paid models๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”. ์ด ๋ฌธ์„œ์—์„œ ๋‹ค๋ฃจ๋Š” ์ตœ์ดˆ์˜ ํ˜ผ๋™์— ๋Œ€ํ•ด์„œ๋Š” [#7992](https://github.com/diegosouzapw/OmniRoute/issues/7992) ๋ฐ [#7111](https://github.com/diegosouzapw/OmniRoute/issues/7111)์„ ์ฐธ์กฐํ•˜์„ธ์š”. ## ์ž‘๋™ ๋ฐฉ์‹ (์˜๊ตฌ ์ €์žฅ๋˜๋Š” ์ž๋™ ์กฐํ•ฉ) ์ž๋™ ์กฐํ•ฉ ์—”์ง„์€ **16๊ฐœ ์š”์†Œ์˜ ์ ์ˆ˜ ๊ณ„์‚ฐ ํ•จ์ˆ˜**(`open-sse/services/autoCombo/scoring.ts` โ†’ `DEFAULT_WEIGHTS`์— ์ •์˜๋จ)๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ฐ ์š”์ฒญ์— ๊ฐ€์žฅ ์ ํ•ฉํ•œ ์ œ๊ณต์ž/๋ชจ๋ธ์„ ๋™์ ์œผ๋กœ ์„ ํƒํ•ฉ๋‹ˆ๋‹ค. ๊ธฐ๋ณธ ๊ฐ€์ค‘์น˜์˜ ํ•ฉ์€ `1.0`์ด๋ฉฐ, ์‚ฌ์šฉ์ž ์ง€์ • ๊ฐ€์ค‘์น˜๋Š” `normalizeScoringWeights()`๋ฅผ ํ†ตํ•ด ๋‹ค์‹œ ์ •๊ทœํ™”๋ฉ๋‹ˆ๋‹ค. 16๊ฐœ ์š”์†Œ ์ค‘ `cacheAffinity`์™€ `resetWindowAffinity`์˜ ๊ธฐ๋ณธ ๊ฐ€์ค‘์น˜๋Š” `0`์ž…๋‹ˆ๋‹ค. `reliability`๋Š” `DEFAULT_WEIGHTS`์—์„œ `0`์ด์ง€๋งŒ ์ผ๋ฐ˜ ํŒฉ์—์„œ๋Š” `0.03`, `reliability-first`์—์„œ๋Š” `0.04`์ด๊ณ , `quality`๋Š” ํŒฉ์—์„œ `0.02`(`quality-first`์—์„œ๋Š” `0.03`)์ž…๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ์š”์†Œ๋Š” ์—ฌ์ „ํžˆ ๋ชจ๋“  ํ›„๋ณด์— ๋Œ€ํ•ด ๊ณ„์‚ฐ๋˜๋ฉฐ, `cacheAffinity`๋Š” ์ ์ˆ˜ ๊ณ„์‚ฐ ์™ธ๋ถ€์—์„œ ํ”„๋กฌํ”„ํŠธ ์บ์‹œ ์ค‘๋ณต ์ œ๊ฑฐ ์—ฌ๋ถ€๋ฅผ ์ œ์–ดํ•ฉ๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ๊ธฐ๋ณธ ๊ฐ€์ค‘์น˜๊ฐ€ 0์ธ ์š”์†Œ๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ์ ์ˆ˜์— ์˜ํ–ฅ์„ ์ฃผ์ง€ ์•Š์ง€๋งŒ, ํŒฉ์—์„œ๋Š” ์˜ํ–ฅ์„ ์ค๋‹ˆ๋‹ค. ![์ž๋™ ์กฐํ•ฉ 16๊ฐœ ์š”์†Œ ์ ์ˆ˜ ๊ณ„์‚ฐ](../diagrams/exported/auto-combo-scoring.svg) > ์ถœ์ฒ˜: [diagrams/auto-combo-scoring.mmd](../diagrams/auto-combo-scoring.mmd) (`npm run docs:render-diagrams`๋ฅผ ํ†ตํ•ด ๋‹ค์‹œ ์ƒ์„ฑ). ํŒŒ์ผ ์ด๋ฆ„์€ ์ด์ „ ๋ช…์นญ์„ ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉํ•œ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์†Œ์Šค์™€ ๋ Œ๋”๋ง๋œ ๋‹ค์ด์–ด๊ทธ๋žจ์—๋Š” `DEFAULT_WEIGHTS`์— ์„ ์–ธ๋œ 16๊ฐœ ์š”์†Œ๊ฐ€ ๋ชจ๋‘ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค. | ์š”์†Œ | ๊ธฐ๋ณธ ๊ฐ€์ค‘์น˜ | ์„ค๋ช… | | :-------------------- | :---------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `quota` | 0.1429 | ๋‚จ์€ ํ• ๋‹น๋Ÿ‰ / ์†๋„ ์ œํ•œ ์—ฌ์œ  [0..1] | | `health` | 0.1605 | ์„œํ‚ท ๋ธŒ๋ ˆ์ด์ปค์˜ ์ƒํƒœ ์ ์ˆ˜(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 | ํ›„๋ณด์˜ ํ‹ฐ์–ด์™€ ๋งค๋‹ˆํŽ˜์ŠคํŠธ๊ฐ€ ๊ถŒ์žฅํ•˜๋Š” ํ‹ฐ์–ด ๊ฐ„์˜ ์„ ํ˜ธ๋„ | | `specificityMatch` | 0.0476 | ์š”์ฒญ ๊ตฌ์ฒด์„ฑ(๋งค๋‹ˆํŽ˜์ŠคํŠธ ํžŒํŠธ)๊ณผ ๋ชจ๋ธ ํ‹ฐ์–ด ๊ฐ„์˜ ์ผ์น˜๋„ | | `contextAffinity` | 0.0476 | ์š”์ฒญ์— ํ•„์š”ํ•œ ์ปจํ…์ŠคํŠธ ์ฐฝ๊ณผ ๋ชจ๋ธ์˜ ์ปจํ…์ŠคํŠธ ์ฐฝ ๊ฐ„์˜ ์„ ํ˜ธ๋„ | | `sessionAvailability` | 0.0476 | ์ด ์„ธ์…˜์— ๋Œ€ํ•œ ํ›„๋ณด ์—ฐ๊ฒฐ์˜ OAuth ์„ธ์…˜ ๊ฐ€์šฉ์„ฑ(`getOAuthSessionAvailability()`; OAuth๊ฐ€ ์•„๋‹Œ ์—ฐ๊ฒฐ์€ 1.0์ ์„ ๋ฐ›์Œ) | | `connectionDensity` | 0.0476 | ๋™์ผํ•œ ์ œ๊ณต์ž์˜ ์—ฌ๋Ÿฌ ์—ฐ๊ฒฐ์— ๋ถ€ํ•˜๋ฅผ ๋ถ„์‚ฐํ•จ(์ง‘์ค‘ ๋ฐฉ์ง€) | | `cacheAffinity` | 0.00 | ์ด ์š”์ฒญ์˜ ํ”„๋กฌํ”„ํŠธ ์บ์‹œ ์ ‘๋‘์‚ฌ๋ฅผ ์ด๋ฏธ ๋ณด์œ ํ•˜๊ณ  ์žˆ์„ ๊ฐ€๋Šฅ์„ฑ์ด ๊ฐ€์žฅ ๋†’์€ ์—ฐ๊ฒฐ์— ๋Œ€ํ•œ ๋ž‘๋ฐ๋ถ€ ํ•ด์‹œ ์„ ํ˜ธ๋„(`open-sse/services/combo/promptCacheAffinity.ts`); ๊ธฐ๋ณธ์ ์œผ๋กœ ๋น„ํ™œ์„ฑํ™”๋จ(#8008) | | `resetWindowAffinity` | 0.00 | ํ• ๋‹น๋Ÿ‰ ์žฌ์„ค์ • ๊ธฐ๊ฐ„์ด ์œ ๋ฆฌํ•œ ์—ฐ๊ฒฐ์„ ์„ ํ˜ธํ•˜๋„๋ก ์กฐ์ •(๊ธฐ๋ณธ์ ์œผ๋กœ ๋น„ํ™œ์„ฑํ™”๋จ) | | `quality` | 0.03 | ๋ผ์šฐํŒ… ์ด๋ฒคํŠธ ํ’ˆ์งˆ ์ถ”์ ๊ธฐ์—์„œ ์ œ๊ณตํ•˜๋Š” ํ”ผ๋“œ๋ฐฑ ๊ธฐ๋ฐ˜ ์ถœ๋ ฅ ํ’ˆ์งˆ ์‹ ํ˜ธ. ๊ด€์ธก๊ฐ’์ด ์—†๋Š” ํ›„๋ณด๋Š” ์ค‘๋ฆฝ ์ ์ˆ˜์ธ 0.5๋ฅผ ๋ฐ›์Œ | | `reliability` | 0.00 | ์ตœ์†Œ 10๊ฐœ ์ƒ˜ํ”Œ ๊ธฐ์ค€์„ ์ถฉ์กฑํ•˜๋Š” 24์‹œ๊ฐ„ ์‚ฌ์šฉ ๊ธฐ๋ก์—์„œ ๊ณ„์‚ฐ๋œ ๊ด€์ธก ์„ฑ๊ณต ๋น„์œจ `1 - failureRate`(๊ทธ ์™ธ์—๋Š” ์‹ค์‹œ๊ฐ„ ๋ฉ”ํŠธ๋ฆญ ์‚ฌ์šฉ). ๊ด€์ธก๊ฐ’์ด ์—†๋Š” ํ›„๋ณด๋Š” 1.0์œผ๋กœ ์ฒ˜๋ฆฌ๋จ. ๊ธฐ๋ณธ์ ์œผ๋กœ ๋น„ํ™œ์„ฑํ™”๋จ | `quality` (`open-sse/services/routing/quality.ts`): `cold restart โ†’ โˆ…`; `observations = 0 โ†’ score 0.5`. **ํ•ฉ๊ณ„:** `DEFAULT_WEIGHTS`์— ์„ ์–ธ๋œ ๋Œ€๋กœ `0.1429 + 0.1605 + 0.1429 + 0.1143 + 0.0762 + (7 ร— 0.0476) + 0.00 + 0.00 + 0.03 + 0.00 = 1.0`์ด๋ฉฐ, ์‚ฌ์šฉ์ž ๊ตฌ์„ฑ ๊ฐ€์ค‘์น˜๋Š” ์ ์ˆ˜๋ฅผ ๊ณ„์‚ฐํ•˜๊ธฐ ์ „์— `normalizeScoringWeights()`๋ฅผ ํ†ตํ•ด ๋ถ„ํฌ๋กœ ๋‹ค์‹œ ์ •๊ทœํ™”๋ฉ๋‹ˆ๋‹ค. ## ๋ชจ๋“œ ํŒฉ `open-sse/services/autoCombo/modePacks.ts`์— ์‚ฌ์ „ ์ •์˜๋œ 6๊ฐœ์˜ ๊ฐ€์ค‘์น˜ ํ”„๋กœํ•„์ด ์žˆ์Šต๋‹ˆ๋‹ค. ๊ฐ ํŒฉ์€ ๊ธฐ๋ณธ ๊ฐ€์ค‘์น˜๋ฅผ ์™„์ „ํžˆ ๋Œ€์ฒดํ•˜์—ฌ ํ•˜๋‚˜์˜ ๋ชฉํ‘œ์— ๋งž๊ฒŒ ์„ ํƒ ํŽธํ–ฅ์„ ์กฐ์ •ํ•ฉ๋‹ˆ๋‹ค. ๋ชจ๋“  ํŒฉ์˜ ํ•ฉ๊ณ„๋Š” ์ด๋ฏธ `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`), `quality-first`์—์„œ `+0.045`, `reliability-first`์—์„œ `+0.05`๋ฅผ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค. - ๋ชจ๋“  ํŒฉ์—์„œ `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๊ฐœ ์„ ํƒ์ง€์˜ ํž˜(Power-of-2-choices) ๋ฐฉ์‹ ๋ฌด์ž‘์œ„ ๋ถ€ํ•˜ ๋ถ„์‚ฐ | | `random` | ๊ท ๋“ฑ ๋ฌด์ž‘์œ„ ์„ ํƒ | | `least-used` | ํ˜„์žฌ ๋ถ€ํ•˜๊ฐ€ ๊ฐ€์žฅ ๋‚ฎ์€ ๋Œ€์ƒ ์„ ํƒ | | `cost-optimized` | ์นดํƒˆ๋กœ๊ทธ ๊ฐ€๊ฒฉ์„ ๊ธฐ์ค€์œผ๋กœ ์š”์ฒญ๋‹น ๋น„์šฉ ์ตœ์†Œํ™” | | `reset-aware` โญ | ํ• ๋‹น๋Ÿ‰ ์žฌ์„ค์ • ์‹œ๊ฐ„์„ ๊ธฐ์ค€์œผ๋กœ ์šฐ์„ ์ˆœ์œ„ ์ง€์ • โ€” ์žฌ์„ค์ • ์ฃผ๊ธฐ๊ฐ€ ์งง์„์ˆ˜๋ก ๋†’์€ ์ˆœ์œ„ | | `reset-window` | ํ• ๋‹น๋Ÿ‰ ๊ธฐ๊ฐ„์ด ๊ฐ€์žฅ ๋นจ๋ฆฌ ์žฌ์„ค์ •๋˜๋Š” ๋Œ€์ƒ ์„ ํ˜ธ | | `headroom` | ๋‚จ์€ ํ• ๋‹น๋Ÿ‰ ์—ฌ์œ ๊ฐ€ ๊ฐ€์žฅ ๋งŽ์€ ๋Œ€์ƒ ์„ ํƒ | | `quota-weighted` | ํ• ๋‹น๋Ÿ‰์„ ์†Œ์ง„ํ•œ ๊ณ„์ •์€ ๊ฑด๋„ˆ๋›ด ๋‹ค์Œ, ๋‚จ์€ ํ• ๋‹น๋Ÿ‰์„ ์ง„ํ–‰ ์ค‘์ธ ๋ถ€ํ•˜๋กœ ๋‚˜๋ˆˆ ๊ฐ’์— ๋น„๋ก€ํ•˜์—ฌ ๋‚˜๋จธ์ง€ ๋Œ€์ƒ ์ค‘์—์„œ ์„ ํƒ. ๊ธฐ์กด ๋Œ€ํ™”๋Š” ํ•ด๋‹น ๋Œ€์ƒ์— ๊ณ ์ •๋จ | | `strict-random` | ๋ฐ˜๋ณต ํ•ญ๋ชฉ์„ ์ค‘๋ณต ์ œ๊ฑฐํ•˜์ง€ ์•Š๋Š” ๋ฌด์ž‘์œ„ ์„ ํƒ | | `auto` | Auto Combo ์ ์ˆ˜ ์‚ฐ์ •(16๊ฐœ ์š”์†Œ) ์‚ฌ์šฉ โ€” **๊ถŒ์žฅ** | | `lkgp` | ๋งˆ์ง€๋ง‰์œผ๋กœ ์ •์ƒ ์ž‘๋™ํ•œ ๊ฒฝ๋กœ(Last-Known-Good Path, ๋งˆ์ง€๋ง‰์œผ๋กœ ์„ฑ๊ณตํ•œ ๊ณต๊ธ‰์ž์— ๊ณ ์ •ํ•œ ๋‹ค์Œ ๊ทœ์น™์— ๋”ฐ๋ผ ํด๋ฐฑ) | | `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์ธ ๊ฒฝ์šฐ์—๋งŒ ๊ท ๋“ฑ ์„ ํƒ์œผ๋กœ ์ „ํ™˜๋ฉ๋‹ˆ๋‹ค. - ๋ชจ๋“  ๋Œ€์ƒ์ด ์‚ฌ์šฉํ•  ์ˆ˜ ์—†๋Š” ๋‹จ๊ณ„(๊ณต๊ธ‰์ž ํšŒ๋กœ ์ฐจ๋‹จ๊ธฐ๊ฐ€ `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` ๊ณ ์ • ๋ฐฐ์น˜ ๋ฐ ๊ณ„์ • ํ™•์žฅ ๋ผ์šด๋“œ ๋กœ๋นˆ์€ ์š”์ฒญ๋งˆ๋‹ค ํ•œ ๋‹จ๊ณ„์”ฉ ์ง„ํ–‰๋˜๋Š” ๋ฐฉ์‹์ด ์•„๋‹ˆ๋ผ ๋ฐฐ์น˜ ๋ฐฉ์‹์ž…๋‹ˆ๋‹ค. - `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**)์€ ์ผ์น˜ํ•˜๋Š” ์บ์‹œ ํ‚ค๊ฐ€ ํ•˜๋‚˜์˜ ๊ณ„์ •์— ์œ ์ง€๋˜๋„๋ก ๊ณ ์ •๋œ ์—ฐ๊ฒฐ์˜ ์ˆœ์„œ๋ฅผ ์žฌ์ •๋ ฌํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” ๊ณ ์ •๋œ ๊ณ„์ •๋ณ„ ๋‹จ๊ณ„์—์„œ ๋ผ์šด๋“œ ๋กœ๋นˆ ๋ฐ ๊ฐ€์ค‘์น˜ ๊ธฐ๋ฐ˜ ์ˆœํ™˜๋ณด๋‹ค ์šฐ์„ ํ•ฉ๋‹ˆ๋‹ค. ์—„๊ฒฉํ•œ ์ˆœํ™˜์ด ํ•„์š”ํ•˜๋‹ค๋ฉด Settings โ†’ Combo defaults์—์„œ ์ด ์„ค์ •์„ ๋„์„ธ์š”. ์ฝค๋ณด๋ณ„ ์žฌ์ •์˜๋Š” ์ œ๊ณต๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ํ•˜๋‚˜์˜ ๋ชจ๋ธ์—์„œ ์—ฌ๋Ÿฌ ๊ณ„์ •์„ ์ˆœํ™˜ํ•˜๋ ค๋ฉด ๊ณ ์ •๋œ `connectionId` ์„ธ ๊ฐœ๋ณด๋‹ค ๊ณ ์ • ํ•œ๋„๊ฐ€ `1`์ธ **ํ•˜๋‚˜์˜ ๋™์  ๊ณ„์ • ๋‹จ๊ณ„**(๋นˆ `connectionId`, ์ „์ฒด ํ’€)๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค. ๊ณ ์ • ๋‹จ๊ณ„์™€ ์–ดํ”ผ๋‹ˆํ‹ฐ๋ฅผ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๋ฉด RR ์นด์šดํ„ฐ๊ฐ€ ์ฆ๊ฐ€ํ•˜๋Š” ๋™์•ˆ์—๋„ ๋™์ผํ•œ ๊ณ„์ •์œผ๋กœ ํ†ตํ•ฉ๋ฉ๋‹ˆ๋‹ค. ## Fusion ์ „๋žต `fusion`์€ ๋‹จ์ผ ๋Œ€์ƒ์„ ์„ ํƒํ•˜์ง€ **์•Š๋Š”** ์œ ์ผํ•œ ์ „๋žต์ž…๋‹ˆ๋‹ค. ํ”„๋กฌํ”„ํŠธ๋ฅผ **๋ชจ๋“  ํŒจ๋„ ๋ชจ๋ธ์— ๋ณ‘๋ ฌ๋กœ** ์ „๋‹ฌํ•œ ๋‹ค์Œ, ๊ตฌ์„ฑ ๊ฐ€๋Šฅํ•œ **ํŒ์ • ๋ชจ๋ธ**์ด ๋ชจ๋“  ํŒจ๋„ ์‘๋‹ต์„ ํ•˜๋‚˜์˜ ์ตœ์ข… ๋‹ต๋ณ€์œผ๋กœ ์ข…ํ•ฉํ•ฉ๋‹ˆ๋‹ค. ์—…์ŠคํŠธ๋ฆผ `decolua/9router` (OpenRouter์˜ Fusion ์„ค๊ณ„)์—์„œ ํฌํŒ…๋˜์—ˆ์œผ๋ฉฐ, ๊ตฌํ˜„์€ `open-sse/services/fusion.ts`์— ์žˆ์Šต๋‹ˆ๋‹ค. ์ž‘๋™ ๋ฐฉ์‹: 0. **๋„๊ตฌ ํฌํ•จ ์š”์ฒญ ์šฐํšŒ** โ€” ๋น„์–ด ์žˆ์ง€ ์•Š์€ `tools` ๋ฐฐ์—ด์„ ํฌํ•จํ•˜๊ณ  `tool_choice`๊ฐ€ ๋ช…์‹œ์ ์œผ๋กœ `"none"`์ด ์•„๋‹Œ ์š”์ฒญ์€ ํŒจ๋„์„ ์™„์ „ํžˆ ๊ฑด๋„ˆ๋œ๋‹ˆ๋‹ค. ์ฆ‰, ๋‹จ์ผ ๋ชจ๋ธ(๊ตฌ์„ฑ๋œ ํŒ์ • ๋ชจ๋ธ ๋˜๋Š” `panel[0]`)๋กœ ์ง์ ‘ ๋ผ์šฐํŒ…๋˜๋ฉฐ `tools`/`tool_choice`๋Š” ๋ณ€๊ฒฝ ์—†์ด ์ „๋‹ฌ๋ฉ๋‹ˆ๋‹ค. ํŒจ๋„ ๊ตฌ์„ฑ์›์€ ๋„๊ตฌ์— ์ ‘๊ทผํ•  ์ˆ˜ ์—†๊ณ  ํŒ์ • ๋ชจ๋ธ์˜ ์ข…ํ•ฉ ์ง€์‹œ๋ฌธ์€ ๋„๊ตฌ ํ˜ธ์ถœ ์ƒ์„ฑ์„ ์–ต์ œํ•˜๋ฏ€๋กœ, ์—์ด์ „ํŠธํ˜•/๋„๊ตฌ ํ˜ธ์ถœ ํด๋ผ์ด์–ธํŠธ๋Š” ์ข…ํ•ฉ๋œ ์‚ฐ๋ฌธ ๋Œ€์‹  ์‹ค์ œ ๋„๊ตฌ ํ˜ธ์ถœ ๊ฒฐ์ •์„ ๋ฐ›๊ฒŒ ๋ฉ๋‹ˆ๋‹ค(#6771). 1. **ํŒฌ์•„์›ƒ**(๋„๊ตฌ๋ฅผ ํฌํ•จํ•˜์ง€ ์•Š์€ ์š”์ฒญ๋งŒ ํ•ด๋‹น) โ€” ํ”„๋กฌํ”„ํŠธ๋ฅผ ๋ชจ๋“  ํŒจ๋„ ๋ชจ๋ธ์— ๋™์‹œ์— ์ „์†กํ•˜๋ฉฐ, ๋„๊ตฌ๋ฅผ ์ œ๊ฑฐํ•œ ์ƒํƒœ๋กœ ๋น„์ŠคํŠธ๋ฆฌ๋ฐ์„ ๊ฐ•์ œํ•ฉ๋‹ˆ๋‹ค(ํŒ์ • ๋ชจ๋ธ์ด ์ข…ํ•ฉํ•˜๋ ค๋ฉด ์™„์ „ํ•œ ์‚ฐ๋ฌธ์ด ํ•„์š”ํ•˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค). 2. **์ฟผ๋Ÿผ ์œ ์˜ˆ ์ˆ˜์ง‘** โ€” `minPanel`๊ฐœ์˜ ๋‹ต๋ณ€์ด ๋„์ฐฉํ•˜๋Š” ์ฆ‰์‹œ ๋’ค์ฒ˜์ง„ ๋ชจ๋ธ์„ ์œ„ํ•œ ์งง์€ ์œ ์˜ˆ ํƒ€์ด๋จธ๊ฐ€ ์‹œ์ž‘๋˜๋ฉฐ, ์ดํ›„์—๋Š” ๊ทธ๋•Œ๊นŒ์ง€ ์ˆ˜์ง‘๋œ ์‘๋‹ต์„ ์‚ฌ์šฉํ•ด fusion์„ ์ง„ํ–‰ํ•ฉ๋‹ˆ๋‹ค. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ๊ฐ€์žฅ ๋А๋ฆฐ ๋ชจ๋ธ๋กœ ์ธํ•œ ๊ฒฝ๊ณผ ์‹œ๊ฐ„ ์ง€์—ฐ์ด ์ œํ•œ๋˜๋ฉฐ, ํ•˜๋“œ ํƒ€์ž„์•„์›ƒ์œผ๋กœ ์ƒํ•œ์ด ์ •ํ•ด์ง‘๋‹ˆ๋‹ค. 3. **ํŒ์ • ๋ชจ๋ธ ์ข…ํ•ฉ** โ€” ํŒจ๋„ ๋‹ต๋ณ€์€ ์ต๋ช…ํ™”(`Source 1`, `Source 2`, โ€ฆ โ€” ํŒ์ • ๋ชจ๋ธ์ด ๋ชจ๋ธ ๋ธŒ๋žœ๋“œ๊ฐ€ ์•„๋‹Œ ๋‚ด์šฉ์— ๊ฐ€์ค‘์น˜๋ฅผ ๋‘๋„๋ก ํ•จ)๋˜์–ด ํŒ์ • ๋ชจ๋ธ์— ์ „๋‹ฌ๋ฉ๋‹ˆ๋‹ค. ํŒ์ • ๋ชจ๋ธ์€ ํ•ฉ์˜ / ๋ชจ์ˆœ / ๋ถ€๋ถ„์  ๋ฒ”์œ„ / ๊ณ ์œ ํ•œ ํ†ต์ฐฐ / ์‚ฌ๊ฐ์ง€๋Œ€๋ฅผ ๋ถ„์„ํ•œ ๋‹ค์Œ, ๊ถŒ์œ„ ์žˆ๋Š” ๋‹ต๋ณ€ **ํ•˜๋‚˜**๋ฅผ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค. ํŒ์ • ๋ชจ๋ธ ํ˜ธ์ถœ์€ ํด๋ผ์ด์–ธํŠธ์˜ ์›๋ž˜ `stream` ํ”Œ๋ž˜๊ทธ์™€ ๋„๊ตฌ๋ฅผ ์œ ์ง€ํ•˜๋ฏ€๋กœ ์ŠคํŠธ๋ฆฌ๋ฐ๊ณผ ๋‹ค์šด์ŠคํŠธ๋ฆผ ๋„๊ตฌ ์‚ฌ์šฉ๋„ ๊ณ„์† ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค. 4. **์ ์ง„์  ์„ฑ๋Šฅ ์ €ํ•˜** โ€” ํŒจ๋„ ๋‹ต๋ณ€ 0๊ฐœ โ†’ `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 ํ‚ค ๋˜๋Š” `hasUsableOAuthToken()`์„ ํ†ตํ•ด ํ™•์ธ๋œ ๋งŒ๋ฃŒ๋˜์ง€ ์•Š์€ OAuth ํ† ํฐ)์ด ์žˆ๋Š” ์—ฐ๊ฒฐ๋งŒ ํ•„ํ„ฐ๋งํ•ฉ๋‹ˆ๋‹ค. 3. ๋ชจ๋ธ ๊ฐ€์šฉ์„ฑ ๋ฐ ๊ฐ€๊ฒฉ ์ •๋ณด๋ฅผ ํ™•์ธํ•˜๊ธฐ ์œ„ํ•ด `getProviderRegistry()`์™€ ๊ต์ฐจ ์ฐธ์กฐํ•ฉ๋‹ˆ๋‹ค. 4. ๊ฐ `(provider, model, connection)` ํŠœํ”Œ์— ๋Œ€ํ•ด `VirtualAutoComboCandidate`๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. 5. `connection.defaultModel`(๋˜๋Š” ๋ ˆ์ง€์ŠคํŠธ๋ฆฌ์˜ ์ฒซ ๋ฒˆ์งธ ๋ชจ๋ธ)์„ ๋””์ŠคํŒจ์น˜ ๋Œ€์ƒ์œผ๋กœ ์„ ํƒํ•ฉ๋‹ˆ๋‹ค. 6. 16๊ฐœ ์š”์†Œ๋กœ ๊ตฌ์„ฑ๋œ `scorePool()`๊ณผ ๋ณ€ํ˜•์˜ ๊ฐ€์ค‘์น˜ ํŒฉ์„ ์‚ฌ์šฉํ•ด ๊ฐ ํ›„๋ณด์˜ ์ ์ˆ˜๋ฅผ ๊ณ„์‚ฐํ•ฉ๋‹ˆ๋‹ค. 7. ๊ฒฐ๊ณผ๋กœ ์ƒ์„ฑ๋œ ์ธ๋ฉ”๋ชจ๋ฆฌ `AutoComboConfig`๋ฅผ `handleComboChat()`์— ๋ฐ˜ํ™˜ํ•˜๋ฉฐ, DB์—๋Š” ์ ˆ๋Œ€ ์ €์žฅํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ฆ‰, **`auto/*`๊ฐ€ ํ™œ์„ฑํ™”๋œ ์ƒˆ ๊ณต๊ธ‰์ž๋ฅผ ์ถ”๊ฐ€ํ•˜๋ฉด ํ›„๋ณด ํ’€์ด ์ž๋™์œผ๋กœ ํ™•์žฅ๋ฉ๋‹ˆ๋‹ค**. ์ˆ˜๋™์œผ๋กœ ์ฝค๋ณด๋ฅผ ํŽธ์ง‘ํ•  ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ๊ฐ€์ƒ ์ฝค๋ณด๋Š” ์š”์ฒญ๋งˆ๋‹ค ๋‹ค์‹œ ์ƒ์„ฑ๋˜๋ฏ€๋กœ ์ƒˆ๋กœ ์ถ”๊ฐ€๋˜๊ฑฐ๋‚˜ ์ƒํƒœ๊ฐ€ ์ƒˆ๋กญ๊ฒŒ ์ •์ƒํ™”๋œ ์—ฐ๊ฒฐ์ด ์ฆ‰์‹œ ๋ฐ˜์˜๋ฉ๋‹ˆ๋‹ค. ## ์ž๊ฐ€ ๋ณต๊ตฌ - **์ผ์‹œ์  ์ œ์™ธ**: ์ ์ˆ˜ < 0.2 โ†’ 5๋ถ„ ๋™์•ˆ ์ œ์™ธ(์ ์ง„์  ๋ฐฑ์˜คํ”„, ์ตœ๋Œ€ 30๋ถ„) - **์„œํ‚ท ๋ธŒ๋ ˆ์ด์ปค ์ธ์‹**: OPEN โ†’ ์ž๋™ ์ œ์™ธ, HALF_OPEN โ†’ ํ”„๋กœ๋ธŒ ์š”์ฒญ - **์ธ์‹œ๋˜ํŠธ ๋ชจ๋“œ**: OPEN ์ƒํƒœ๊ฐ€ 50% ์ดˆ๊ณผ โ†’ ํƒ์ƒ‰์„ ๋น„ํ™œ์„ฑํ™”ํ•˜๊ณ  ์•ˆ์ •์„ฑ์„ ๊ทน๋Œ€ํ™” - **์ฟจ๋‹ค์šด ๋ณต๊ตฌ**: ์ œ์™ธ ๊ธฐ๊ฐ„์ด ๋๋‚œ ํ›„ ์ฒซ ๋ฒˆ์งธ ์š”์ฒญ์€ ํƒ€์ž„์•„์›ƒ์ด ๋‹จ์ถ•๋œ "ํ”„๋กœ๋ธŒ"๋กœ ์ฒ˜๋ฆฌ ## ๋ฐด๋”ง ํƒ์ƒ‰ ์š”์ฒญ์˜ 5%(์„ค์ • ๊ฐ€๋Šฅ)๋Š” ํƒ์ƒ‰์„ ์œ„ํ•ด ๋ฌด์ž‘์œ„ ๊ณต๊ธ‰์ž๋กœ ๋ผ์šฐํŒ…๋ฉ๋‹ˆ๋‹ค. ์ธ์‹œ๋˜ํŠธ ๋ชจ๋“œ์—์„œ๋Š” ๋น„ํ™œ์„ฑํ™”๋ฉ๋‹ˆ๋‹ค. ## API **์ „์šฉ `POST /api/combos/auto` ์—”๋“œํฌ์ธํŠธ๋Š” ์—†์Šต๋‹ˆ๋‹ค** โ€” Auto-Combo๋Š” ๋‹ค์Œ ๋‘ ๊ฐ€์ง€ ๋ฐฉ์‹์œผ๋กœ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. 1. **์ œ๋กœ ๊ตฌ์„ฑ(๊ถŒ์žฅ):** `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` ๋Œ€์‹  ์ด ๊ฐ’๋“ค์„ ์•Œ๋ ค์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ปจํ…์ŠคํŠธ๊ฐ€ 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"}]}' # ์ผ๋ฐ˜ ์ฝค๋ณด ์—”๋“œํฌ์ธํŠธ๋ฅผ ํ†ตํ•œ ์˜๊ตฌ ์ €์žฅ Auto-Combo 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` โ€” p95 ์ง€์—ฐ ์‹œ๊ฐ„, ์˜ค๋ฅ˜์œจ ๋ฐ ์„ ํƒ์  ๋น„์šฉ SLO๋ฅผ ์ถฉ์กฑํ•˜๋Š” ํ›„๋ณด๋ฅผ ์šฐ์„ ํ•ฉ๋‹ˆ๋‹ค. - `lkgp` โ€” ๋งˆ์ง€๋ง‰์œผ๋กœ ์ •์ƒ ์ƒํƒœ์˜€๋˜ ์ œ๊ณต์ž๋ฅผ ์šฐ์„ ํ•ฉ๋‹ˆ๋‹ค. - `nadir` โ€” [Nadir](https://getnadir.com)์˜ ๊ฒฐ์ • API์— ํ’€ ๋‚ด์—์„œ ํ”„๋กฌํ”„ํŠธ์— ํ•„์š”ํ•œ ๋ชจ๋ธ์ด ๋ฌด์—‡์ธ์ง€ ์งˆ์˜ํ•ฉ๋‹ˆ๋‹ค. ์˜ตํŠธ์ธ์ด๋ฉฐ, ์‹คํŒจ ์‹œ `rules`๋กœ ๊ฐœ๋ฐฉํ˜• ํด๋ฐฑํ•ฉ๋‹ˆ๋‹ค. ### ๋ผ์šฐํ„ฐ ์ „๋žต ์ƒ์„ธ ์„ค๋ช… Auto-Combo ์—”์ง„์€ `config.routerStrategy`(๋˜๋Š” ๋ ˆ๊ฑฐ์‹œ `config.auto.routerStrategy`)๋ฅผ ํ†ตํ•ด ๊ต์ฒดํ•  ์ˆ˜ ์žˆ๋Š” 7๊ฐœ์˜ ํ”Œ๋Ÿฌ๊ทธํ˜• **RouterStrategy** ๊ตฌํ˜„์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ๊ฐ ์ „๋žต์€ `RoutingContext`(์ž‘์—… ์œ ํ˜•, ๋„๊ตฌ/๋น„์ „ ํžŒํŠธ, ํ† ํฐ ์ถ”์ •์น˜, ์„ ํƒ์  SLA ์ •์ฑ…, ์„ ํƒ์  ๋งˆ์ง€๋ง‰ ์ •์ƒ ์ œ๊ณต์ž)๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ํ›„๋ณด ํ’€์—์„œ ํ•˜๋‚˜์˜ ์ œ๊ณต์ž๋ฅผ ์„ ํƒํ•ฉ๋‹ˆ๋‹ค. #### 1. `rules` (๊ธฐ๋ณธ๊ฐ’) โ€” 16๊ฐœ ์š”์†Œ ๊ฐ€์ค‘ ์ ์ˆ˜ ์‚ฐ์ • ๊ธฐ์กด ์ ์ˆ˜ ์‚ฐ์ • ์—”์ง„์„ ๋ž˜ํ•‘ํ•ฉ๋‹ˆ๋‹ค. `OPEN` ํšŒ๋กœ ์ฐจ๋‹จ๊ธฐ ์ƒํƒœ์ธ ํ›„๋ณด๋ฅผ ํ•„ํ„ฐ๋งํ•œ ๋‹ค์Œ, ํ˜„์žฌ ์ž‘์—… ์œ ํ˜•๊ณผ `getTaskFitness()`๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ `scorePool()`์„ ์‹คํ–‰ํ•˜๊ณ  ์ ์ˆ˜๊ฐ€ ๊ฐ€์žฅ ๋†’์€ ์ œ๊ณต์ž๋ฅผ ์„ ํƒํ•ฉ๋‹ˆ๋‹ค. ```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`๋Š” ๋งˆ์ง€๋ง‰ ์‚ฌ์šฉ์ž ํ„ด๊ณผ ํ’€์˜ ๋ชจ๋ธ ID๋ฅผ [Nadir](https://getnadir.com)์˜ ๊ฒฐ์ • API(`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`์ด ์žˆ์–ด๋„ ํ—ˆ์šฉ๋จ). ํ‚ค๊ฐ€ ์—†๋Š” ํ˜ธ์ถœ์€ IP๋ณ„๋กœ ์†๋„๊ฐ€ ์ œํ•œ๋˜๋Š” Nadir์˜ ์ต๋ช… ํ‹ฐ์–ด๋กœ ์ „๋‹ฌ๋ฉ๋‹ˆ๋‹ค. ์™ธ๋ถ€๋กœ ์ „์†ก๋˜๋Š” ์ •๋ณด: ๋งˆ์ง€๋ง‰ ์‚ฌ์šฉ์ž ๋ฉ”์‹œ์ง€์˜ ํ…์ŠคํŠธ(์ฒ˜์Œ 16k์ž), ํ›„๋ณด ๋ชจ๋ธ ID ๋ฐ `source: "omniroute"` ์ฑ„๋„ ํƒœ๊ทธ. ์‹œ์Šคํ…œ ํ”„๋กฌํ”„ํŠธ, ๊ธฐ๋ก, ๋„๊ตฌ ๋˜๋Š” ํ—ค๋”๋Š” ์ „์†ก๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์‹คํŒจ ์‹œ ๋™์ž‘์€ ํŽ˜์ผ ์˜คํ”ˆ ๋ฐฉ์‹์ž…๋‹ˆ๋‹ค. ์‹œ๊ฐ„ ์ดˆ๊ณผ(๊ธฐ๋ณธ๊ฐ’ 2000 ms), 2xx๊ฐ€ ์•„๋‹Œ ์‘๋‹ต, ์—ฐ๊ฒฐํ•  ์ˆ˜ ์—†๋Š” ํ˜ธ์ŠคํŠธ, ์ž˜๋ชป๋œ ํ˜•์‹์˜ ์‘๋‹ต ๋˜๋Š” ํ’€์— ์—†๋Š” ํ•ญ๋ชฉ ์„ ํƒ์ด ๋ฐœ์ƒํ•˜๋ฉด `rules` ๊ฒฐ์ •์œผ๋กœ ์ฒ˜๋ฆฌ๋˜๋ฉฐ, ์‚ฌ์œ  ์•ž์— `NadirStrategy: fallback (โ€ฆ)`์ด ์ถ”๊ฐ€๋ฉ๋‹ˆ๋‹ค. ํ˜ธ์ถœ์ด ์‹คํŒจํ•œ ํ›„์—๋Š” ์ „๋žต์ด 30์ดˆ ๋™์•ˆ ๋„คํŠธ์›Œํฌ ์š”์ฒญ์„ ๊ฑด๋„ˆ๋›ฐ๋ฏ€๋กœ, ์žฅ์•  ๋ฐœ์ƒ ์‹œ ์š”์ฒญ๋งˆ๋‹ค ํ•œ ๋ฒˆ์ด ์•„๋‹ˆ๋ผ 30์ดˆ๋งˆ๋‹ค ํ•œ ๋ฒˆ์˜ ์‹œ๊ฐ„ ์ดˆ๊ณผ๋งŒ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. ๋ผ์šฐํŒ… ์ด๋ฒคํŠธ๋Š” Nadir๊ฐ€ ์‹ค์ œ๋กœ ์„ ํƒํ•œ ๊ฒฝ์šฐ์—๋งŒ `strategy: "nadir"`๋ฅผ ๋ณด๊ณ ํ•ฉ๋‹ˆ๋‹ค. **์‚ฌ์šฉ ์‹œ์ **: ์—ฌ๋Ÿฌ ๋ชจ๋ธ ํ‹ฐ์–ด(์†Œํ˜•, ์ค‘ํ˜• ๋ฐ ์ตœ์ฒจ๋‹จ ๋ชจ๋ธ)์— ๊ฑธ์นœ ํ’€์—์„œ ๋‚œ์ด๋„๊ฐ€ ํ˜ผํ•ฉ๋œ ํŠธ๋ž˜ํ”ฝ์„ ์ฒ˜๋ฆฌํ•˜๋ฉฐ, ํ•ญ์ƒ ์ตœ์ฒจ๋‹จ ๋ชจ๋ธ์„ ์‚ฌ์šฉํ•˜๋Š” ๋น„์šฉ์„ ์ ˆ๊ฐํ•˜๋ ค๋Š” ๊ฒฝ์šฐ. **๋ณ„์นญ**: `nadir` (๋ณ„์นญ ์—†์Œ) --- ### ์‚ฌ์šฉ์ž ์ง€์ • ๋ผ์šฐํ„ฐ ์ „๋žต ๊ณต๊ฐœ API๋ฅผ ํ†ตํ•ด ์ž์ฒด `RouterStrategy` ๊ตฌํ˜„์„ ๋“ฑ๋กํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ```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 ์ธ์‹ ํ•„๋“œ: ```json { "strategy": "auto", "config": { "routerStrategy": "sla-aware", "slaTargetP95Ms": 1500, "slaMaxErrorRate": 0.05, "slaMaxCostPer1MTokens": 5, "slaHardConstraints": true } } ``` ## ์ž‘์—… ์ ํ•ฉ๋„ 6๊ฐ€์ง€ ์ž‘์—… ์œ ํ˜•(`coding`, `review`, `planning`, `analysis`, `debugging`, `documentation`)์— ๊ฑธ์ณ 30๊ฐœ ์ด์ƒ์˜ ๋ชจ๋ธ์— ์ ์ˆ˜๊ฐ€ ๋ถ€์—ฌ๋ฉ๋‹ˆ๋‹ค. ์™€์ผ๋“œ์นด๋“œ ํŒจํ„ด๋„ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค(์˜ˆ: `*-coder` โ†’ ๋†’์€ ์ฝ”๋”ฉ ์ ์ˆ˜). ## ์ž๋™ ๋ณ€ํ˜• ์š”์•ฝ ๊ธฐ๋ณธ `auto`์™€ `autoPrefix.ts`์— ์„ ์–ธ๋œ 6๊ฐœ์˜ `AutoVariant` ๊ฐ’์„ ํฌํ•จํ•˜๋ฉด, **ํ˜ธ์ถœ ๊ฐ€๋Šฅํ•œ ๋ชจ๋ธ ID๋Š” 7๊ฐœ**์ž…๋‹ˆ๋‹ค. `auto`, `auto/coding`, `auto/fast`, `auto/cheap`, `auto/offline`, `auto/smart`, `auto/lkgp` (`AutoVariant` ์ž์ฒด๋Š” 6๊ฐœ์˜ ๊ฐ’์„ ์—ด๊ฑฐํ•ฉ๋‹ˆ๋‹ค. 7๋ฒˆ์งธ ์˜ต์…˜์€ "๋ณ€ํ˜• ์—†์Œ"์„ ์˜๋ฏธํ•˜๋Š” ๊ธฐ๋ณธ `auto`์ด๋ฉฐ, `parseAutoPrefix()`์—์„œ `variant: undefined`๋กœ ์ฒ˜๋ฆฌ๋ฉ๋‹ˆ๋‹ค.) ## ํ‹ฐ์–ด๊ฐ€ Auto-Combo์— ์ ์šฉ๋˜๋Š” ๋ฐฉ์‹ 16๊ฐœ ์š”์†Œ๋กœ ๊ตฌ์„ฑ๋œ ์ ์ˆ˜ ์‚ฐ์ • ํ•จ์ˆ˜(`open-sse/services/autoCombo/scoring.ts`)๋Š” ํ‹ฐ์–ด ์†Œ์† ์—ฌ๋ถ€๋ฅผ `tierPriority`(0.0476)์™€ `tierAffinity`(0.0476)๋ผ๋Š” ๋‘ ๊ฐ€์ง€ ์‹ ํ˜ธ๋กœ ์ทจ๊ธ‰ํ•ฉ๋‹ˆ๋‹ค. ์ „์ฒด `DEFAULT_WEIGHTS` ์ง‘ํ•ฉ์€ ์œ„์˜ ํ‘œ์ค€ [์ ์ˆ˜ ์‚ฐ์ • ์š”์†Œ ํ‘œ](#how-it-works-persisted-auto-combos)๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”. ํŒฉ๋ณ„ ์žฌ์ •์˜(ship-fast/cost-saver/quality-first/ offline-friendly)๋Š” "ํŒฉ๋ณ„ ๊ฐ€์ค‘์น˜ ํ”„๋กœํ•„" ํ‘œ์— ๋‚˜์™€ ์žˆ์Šต๋‹ˆ๋‹ค. ํ‹ฐ์–ด๋งŒ์œผ๋กœ๋Š” **Tier 1์ด ๋จผ์ € ์‚ฌ์šฉ๋˜๋„๋ก ๊ฐ•์ œ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค**. Tier 1์˜ ์ง€์—ฐ ์‹œ๊ฐ„์ด ๊ธธ๊ฑฐ๋‚˜ ๋น„์šฉ ๋Œ€๋น„ ํ’ˆ์งˆ์ด ์ตœ์ ์ด ์•„๋‹Œ ๊ฒฝ์šฐ Tier 2๊ฐ€ ์„ ํƒ๋ฉ๋‹ˆ๋‹ค. ํ‹ฐ์–ด ์ˆœ์„œ๋ฅผ ๊ฐ•์ œํ•˜๋ ค๋ฉด ์ฝค๋ณด ์ „๋žต `priority`๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ๊ณต๊ธ‰์ž๋ฅผ ํ‹ฐ์–ด๋ณ„๋กœ ์ •๋ ฌํ•˜์„ธ์š”. Tier 1(๊ตฌ๋…)์„ ๊ฐ•ํ•˜๊ฒŒ ์„ ํ˜ธํ•˜๋ ค๋ฉด `tierPriority` ๊ฐ€์ค‘์น˜๋ฅผ ๋†’์ด์„ธ์š”. ```json { "strategy": "auto", "config": { "auto": { "weights": { "tierPriority": 0.3, "costInv": 0.05 } } } } ``` ํ‹ฐ์–ด ์ •์˜์™€ ๊ณต๊ธ‰์ž ๋ถ„๋ฅ˜๋Š” [`docs/guides/TIERS.md`](../guides/TIERS.md)๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”. ## ํ…Œ์ŠคํŠธ ๋ฐ ์ปค๋ฒ„๋ฆฌ์ง€ ### ๊ฒฐ์ •๋ก ์  ๋ผ์šฐํŒ… ๊ฒฐ์ • ๋งคํŠธ๋ฆญ์Šค(`npm run test:combo:matrix`) `tests/integration/combo-matrix/*.test.ts`๋Š” ๋ชจ์˜ ์—…์ŠคํŠธ๋ฆผ์„ ์‚ฌ์šฉํ•˜๋Š” ์‹ค์ œ ์ฝค๋ณด ํŒŒ์ดํ”„๋ผ์ธ์„ ํ†ตํ•ด 19๊ฐœ์˜ ๋ชจ๋“  ๊ณต๊ฐœ ์ „๋žต์— ๋Œ€ํ•œ ๋ผ์šฐํŒ… **๊ฒฐ์ •**์„ ์—”๋“œํˆฌ์—”๋“œ๋กœ ๊ฒ€์ฆํ•ฉ๋‹ˆ๋‹ค. ์ปค๋ฒ„๋ฆฌ์ง€์—๋Š” ๋‹ค์Œ์ด ํฌํ•จ๋ฉ๋‹ˆ๋‹ค. - 19๊ฐœ์˜ ๋ชจ๋“  `ROUTING_STRATEGY_VALUES` ์ „๋žต(ordered, weighted, cost, context, fusion, โ€ฆ). - `quota-share`(๋‚ด๋ถ€) ์—”๋“œํˆฌ์—”๋“œ: ์‹ค์ œ `selectQuotaShareTarget` ์—ฐ๊ฒฐ ์ง€์ (`registerQuotaFetcher` / `setLKGP` / `__setHeadroomSaturationFetcherForTests`)์„ ํ†ตํ•œ DRR ๊ณต์ •์„ฑ ๋ฐ ํฌํ™” ์ƒํƒœ ์šฐ์„ ์ˆœ์œ„ ํ•˜ํ–ฅ ์กฐ์ •. - ๋ชจ๋“  ๋Œ€์ƒ ์ˆ˜์— ๋Œ€ํ•œ `context-relay` ๋ฒ”์šฉ ํ•ธ๋“œ์˜คํ”„ ์ปค๋ฒ„๋ฆฌ์ง€. ์ด ํ…Œ์ŠคํŠธ ๋ชจ์Œ์€ ๊ฒฐ์ •๋ก ์  ๋™์ž‘์„ ๋ณด์žฅํ•˜๊ณ  ์‹ค์ œ ์ž๊ฒฉ ์ฆ๋ช…์ด ํ•„์š”ํ•˜์ง€ ์•Š๋„๋ก `--test-concurrency=1` ๋ฐ `--test-force-exit` ์˜ต์…˜๊ณผ ํ•จ๊ป˜ CI(`test:integration` ์ž‘์—…)์—์„œ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค. ### ์กฐ๊ฑด๋ถ€ ์‹ค์ œ ์Šค๋ชจํฌ ํ…Œ์ŠคํŠธ(CI์—์„œ๋Š” ์‹คํ–‰ํ•˜์ง€ ์•Š์Œ โ€” ์‹ค์ œ ๊ณต๊ธ‰์ž) | ๋ช…๋ น์–ด | ์ˆ˜ํ–‰ ๋‚ด์šฉ | | :------------------------------------- | :--------------------------------------------------------------------------------------- | | `npm run test:combo:live` | `RUN_COMBO_LIVE=1`์„ ์‚ฌ์šฉํ•˜๋Š” ํ”„๋กœ์„ธ์Šค ๋‚ด ์‹ค์ œ ๋ผ์šฐํŒ…. ์‹ค์ œ OmniRoute DB์˜ ์Šค๋ƒ…์ƒท์„ ์ƒ์„ฑ | | `npm run test:combo:live:vps` | ์‹ค์ œ OmniRoute ์„œ๋ฒ„์— HTTP ์š”์ฒญ ์ „์†ก(`COMBO_LIVE_BASE_URL` ์„ค์ •) | | `npm run test:combo:live:vps:failover` | ์˜๋„์ ์ธ ์žฅ์•  ์กฐ์น˜ ์‹œ๋‚˜๋ฆฌ์˜ค๋ฅผ ํฌํ•จํ•ด ๋™์ผํ•˜๊ฒŒ ์ˆ˜ํ–‰ | ์ด๋Ÿฌํ•œ ์Šค๋ชจํฌ ํ…Œ์ŠคํŠธ๋Š” ์‹ค์ œ ํ†ต์‹  ๊ฒฝ๋กœ(์ฝค๋ณด โ†’ ๊ณต๊ธ‰์ž โ†’ ์™„์„ฑ)๋ฅผ ๊ฒ€์ฆํ•ฉ๋‹ˆ๋‹ค. ์‹ค์ œ ์ž๊ฒฉ ์ฆ๋ช…๊ณผ VPS ์•ก์„ธ์Šค๊ฐ€ ํ•„์š”ํ•˜๋ฏ€๋กœ ์˜๋„์ ์œผ๋กœ CI์—์„œ ์ œ์™ธ๋ฉ๋‹ˆ๋‹ค. --- ## ํŒŒ์ผ | ํŒŒ์ผ | ์šฉ๋„ | | :-------------------------------------------------------- | :------------------------------------------------------------------------------------------------------- | | `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-prefix ๋‹จ๋ฝ ํ‰๊ฐ€ |