[{"data":1,"prerenderedAt":520},["ShallowReactive",2],{"content-developer\u002Fdecisions\u002F009-model-routing":3,"surround-\u002Fdeveloper\u002Fdecisions\u002F009-model-routing":511},{"id":4,"title":5,"body":6,"description":504,"extension":505,"meta":506,"navigation":254,"path":507,"seo":508,"stem":509,"__hash__":510},"content\u002F3.developer\u002Fdecisions\u002F10.009-model-routing.md","ADR-009: Task-Based Model Routing",{"type":7,"value":8,"toc":499},"minimark",[9,26,31,35,134,137,140,161,165,174,185,196,404,416,450,454,459,476,481,495],[10,11,12,20],"ul",{},[13,14,15,19],"li",{},[16,17,18],"strong",{},"Status:"," Accepted",[13,21,22,25],{},[16,23,24],{},"Date:"," 2026-03-24",[27,28,30],"h2",{"id":29},"context","Context",[32,33,34],"p",{},"The Agent Pipeline, Knowledge Graph, and file system all make LLM calls, but with different requirements:",[36,37,38,54],"table",{},[39,40,41],"thead",{},[42,43,44,48,51],"tr",{},[45,46,47],"th",{},"Task",[45,49,50],{},"Needs",[45,52,53],{},"Volume",[55,56,57,69,79,90,101,112,123],"tbody",{},[42,58,59,63,66],{},[60,61,62],"td",{},"Classification",[60,64,65],{},"Speed, structured output",[60,67,68],{},"Every inbound message",[42,70,71,74,77],{},[60,72,73],{},"Security filter",[60,75,76],{},"Speed, low latency",[60,78,68],{},[42,80,81,84,87],{},[60,82,83],{},"Knowledge extraction",[60,85,86],{},"Structured output, cost efficiency",[60,88,89],{},"Every processed message",[42,91,92,95,98],{},[60,93,94],{},"File tagging",[60,96,97],{},"Summarization, cost efficiency",[60,99,100],{},"Every uploaded file",[42,102,103,106,109],{},[60,104,105],{},"Action planning",[60,107,108],{},"Reasoning, tool use",[60,110,111],{},"Per-message (after classification)",[42,113,114,117,120],{},[60,115,116],{},"Draft generation",[60,118,119],{},"Writing quality, tone matching",[60,121,122],{},"Per-message (when draft needed)",[42,124,125,128,131],{},[60,126,127],{},"Context compaction",[60,129,130],{},"Summarization",[60,132,133],{},"When context exceeds budget",[32,135,136],{},"Running everything on the most capable model (GPT-4o, Claude Sonnet) wastes resources — classification produces a simple enum and does not need the reasoning capacity of a frontier model. Running everything on the cheapest model (GPT-4o-mini, Llama 3) saves money but produces poor-quality drafts.",[32,138,139],{},"The options considered:",[141,142,143,149,155],"ol",{},[13,144,145,148],{},[16,146,147],{},"Single model"," — simplest configuration, but forces a choice between quality and cost",[13,150,151,154],{},[16,152,153],{},"Per-step model configuration"," — five separate model environment variables, complex to configure",[13,156,157,160],{},[16,158,159],{},"Two-tier routing"," — fast model for high-volume structured tasks, capable model for reasoning and writing",[27,162,164],{"id":163},"decision","Decision",[32,166,167,168,173],{},"Extend the ",[169,170,172],"a",{"href":171},"\u002Fdeveloper\u002Fdecisions\u002F007-pluggable-llm","pluggable LLM provider"," with two-tier model routing:",[175,176,181],"pre",{"className":177,"code":179,"language":180},[178],"language-text","LLM_MODEL=gpt-4o              # Fallback for all tasks\nLLM_MODEL_CAPABLE=gpt-4o      # Drafting, planning, reasoning\nLLM_MODEL_FAST=gpt-4o-mini    # Classification, extraction, summarization\n","text",[182,183,179],"code",{"__ignoreMap":184},"",[32,186,187,188,191,192,195],{},"The ",[182,189,190],{},"getLLMProvider()"," function accepts a ",[182,193,194],{},"task"," parameter that selects the appropriate tier:",[175,197,201],{"className":198,"code":199,"language":200,"meta":184,"style":184},"language-typescript shiki shiki-themes github-light github-dark-dimmed","type ModelTask = 'classify' | 'draft' | 'extract' | 'plan' | 'guard' | 'summarize'\n\nexport function getLLMProvider(task: ModelTask = 'draft') {\n  const isFastTask = ['classify', 'extract', 'guard', 'summarize'].includes(task)\n  const model = isFastTask\n    ? process.env.LLM_MODEL_FAST ?? process.env.LLM_MODEL ?? 'gpt-4o-mini'\n    : process.env.LLM_MODEL_CAPABLE ?? process.env.LLM_MODEL ?? 'gpt-4o'\n  \u002F\u002F ... provider setup\n}\n","typescript",[182,202,203,249,256,288,331,344,369,391,398],{"__ignoreMap":184},[204,205,208,212,216,219,223,226,229,231,234,236,239,241,244,246],"span",{"class":206,"line":207},"line",1,[204,209,211],{"class":210},"s7YZ4","type",[204,213,215],{"class":214},"sOLd2"," ModelTask",[204,217,218],{"class":210}," =",[204,220,222],{"class":221},"s-HuK"," 'classify'",[204,224,225],{"class":210}," |",[204,227,228],{"class":221}," 'draft'",[204,230,225],{"class":210},[204,232,233],{"class":221}," 'extract'",[204,235,225],{"class":210},[204,237,238],{"class":221}," 'plan'",[204,240,225],{"class":210},[204,242,243],{"class":221}," 'guard'",[204,245,225],{"class":210},[204,247,248],{"class":221}," 'summarize'\n",[204,250,252],{"class":206,"line":251},2,[204,253,255],{"emptyLinePlaceholder":254},true,"\n",[204,257,259,262,265,269,273,276,279,281,283,285],{"class":206,"line":258},3,[204,260,261],{"class":210},"export",[204,263,264],{"class":210}," function",[204,266,268],{"class":267},"sPO5f"," getLLMProvider",[204,270,272],{"class":271},"sYgZi","(",[204,274,194],{"class":275},"stnAF",[204,277,278],{"class":210},":",[204,280,215],{"class":214},[204,282,218],{"class":210},[204,284,228],{"class":221},[204,286,287],{"class":271},") {\n",[204,289,291,294,298,300,303,306,309,312,314,317,319,322,325,328],{"class":206,"line":290},4,[204,292,293],{"class":210},"  const",[204,295,297],{"class":296},"sviXB"," isFastTask",[204,299,218],{"class":210},[204,301,302],{"class":271}," [",[204,304,305],{"class":221},"'classify'",[204,307,308],{"class":271},", ",[204,310,311],{"class":221},"'extract'",[204,313,308],{"class":271},[204,315,316],{"class":221},"'guard'",[204,318,308],{"class":271},[204,320,321],{"class":221},"'summarize'",[204,323,324],{"class":271},"].",[204,326,327],{"class":267},"includes",[204,329,330],{"class":271},"(task)\n",[204,332,334,336,339,341],{"class":206,"line":333},5,[204,335,293],{"class":210},[204,337,338],{"class":296}," model",[204,340,218],{"class":210},[204,342,343],{"class":271}," isFastTask\n",[204,345,347,350,353,356,359,361,364,366],{"class":206,"line":346},6,[204,348,349],{"class":210},"    ?",[204,351,352],{"class":271}," process.env.",[204,354,355],{"class":296},"LLM_MODEL_FAST",[204,357,358],{"class":210}," ??",[204,360,352],{"class":271},[204,362,363],{"class":296},"LLM_MODEL",[204,365,358],{"class":210},[204,367,368],{"class":221}," 'gpt-4o-mini'\n",[204,370,372,375,377,380,382,384,386,388],{"class":206,"line":371},7,[204,373,374],{"class":210},"    :",[204,376,352],{"class":271},[204,378,379],{"class":296},"LLM_MODEL_CAPABLE",[204,381,358],{"class":210},[204,383,352],{"class":271},[204,385,363],{"class":296},[204,387,358],{"class":210},[204,389,390],{"class":221}," 'gpt-4o'\n",[204,392,394],{"class":206,"line":393},8,[204,395,397],{"class":396},"sDN9O","  \u002F\u002F ... provider setup\n",[204,399,401],{"class":206,"line":400},9,[204,402,403],{"class":271},"}\n",[32,405,406,407,409,410,412,413,415],{},"Both ",[182,408,379],{}," and ",[182,411,355],{}," fall back to ",[182,414,363],{},", which means:",[10,417,418,427,440],{},[13,419,420,423,424,426],{},[16,421,422],{},"Minimal configuration:"," set only ",[182,425,363],{}," and everything uses that model (same behavior as ADR-007)",[13,428,429,432,433,409,436,439],{},[16,430,431],{},"Cost optimization:"," set ",[182,434,435],{},"LLM_MODEL_FAST=gpt-4o-mini",[182,437,438],{},"LLM_MODEL_CAPABLE=gpt-4o"," to split by task",[13,441,442,445,446,449],{},[16,443,444],{},"Self-hosted simplicity:"," self-hosters running a single Ollama model set ",[182,447,448],{},"LLM_MODEL=llama3"," and both tiers use it",[27,451,453],{"id":452},"consequences","Consequences",[32,455,456],{},[16,457,458],{},"Enables:",[10,460,461,464,467,470,473],{},[13,462,463],{},"Cost reduction of 60-80% on high-volume tasks (classification, extraction) by using smaller models",[13,465,466],{},"Quality preservation on tasks that need it (drafting, planning) by using capable models",[13,468,469],{},"Simple upgrade path — start with one model, split later as volume grows",[13,471,472],{},"Self-hosters can run a single model without any routing configuration",[13,474,475],{},"Per-step cost tracking becomes meaningful (fast model calls are cheap, capable model calls are the real cost center)",[32,477,478],{},[16,479,480],{},"Trade-offs:",[10,482,483,486,489,492],{},[13,484,485],{},"Two additional environment variables to document and support",[13,487,488],{},"Model compatibility assumptions — structured output behavior varies across models, may need per-model prompt adjustments",[13,490,491],{},"Self-hosters using a single small model will see quality limitations on drafting tasks (same as ADR-007's existing trade-off)",[13,493,494],{},"No per-organization model override yet — routing is system-wide, not tenant-configurable",[496,497,498],"style",{},"html pre.shiki code .s7YZ4, html code.shiki .s7YZ4{--shiki-default:#D73A49;--shiki-dark:#F47067}html pre.shiki code .sOLd2, html code.shiki .sOLd2{--shiki-default:#6F42C1;--shiki-dark:#F69D50}html pre.shiki code .s-HuK, html code.shiki .s-HuK{--shiki-default:#032F62;--shiki-dark:#96D0FF}html pre.shiki code .sPO5f, html code.shiki .sPO5f{--shiki-default:#6F42C1;--shiki-dark:#DCBDFB}html pre.shiki code .sYgZi, html code.shiki .sYgZi{--shiki-default:#24292E;--shiki-dark:#ADBAC7}html pre.shiki code .stnAF, html code.shiki .stnAF{--shiki-default:#E36209;--shiki-dark:#F69D50}html pre.shiki code .sviXB, html code.shiki .sviXB{--shiki-default:#005CC5;--shiki-dark:#6CB6FF}html pre.shiki code .sDN9O, html code.shiki .sDN9O{--shiki-default:#6A737D;--shiki-dark:#768390}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":184,"searchDepth":251,"depth":251,"links":500},[501,502,503],{"id":29,"depth":251,"text":30},{"id":163,"depth":251,"text":164},{"id":452,"depth":251,"text":453},"Why Owlat supports per-task LLM model selection instead of using a single model for all pipeline steps.","md",{},"\u002Fdeveloper\u002Fdecisions\u002F009-model-routing",{"title":5,"description":504},"3.developer\u002Fdecisions\u002F10.009-model-routing","22b8xGLklxFNAKxCp9jEhh1ia-k7U5Cmu-4jJHyrkUg",[512,516],{"title":513,"path":514,"stem":515,"children":-1},"Architectural Decision Records","\u002Fdeveloper\u002Fdecisions","3.developer\u002Fdecisions\u002F1.index",{"title":517,"path":518,"stem":519,"children":-1},"ADR-001: Custom Email Renderer Over MJML","\u002Fdeveloper\u002Fdecisions\u002F001-custom-email-renderer","3.developer\u002Fdecisions\u002F2.001-custom-email-renderer",1774391046228]