route67 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,62 @@
1
+ # Virtual environments
2
+ .venv/
3
+ .venv-*/
4
+ venv/
5
+ env/
6
+ ENV/
7
+ *.venv
8
+
9
+ # Python bytecode and build artifacts
10
+ __pycache__/
11
+ *.py[cod]
12
+ *$py.class
13
+ *.egg-info/
14
+ .eggs/
15
+ build/
16
+ dist/
17
+ .tmp-dist/
18
+
19
+ # Test, coverage, lint, and type-check caches
20
+ .pytest_cache/
21
+ .coverage
22
+ .coverage.*
23
+ coverage.xml
24
+ htmlcov/
25
+ .hypothesis/
26
+ .mypy_cache/
27
+ .pyright/
28
+ .ruff_cache/
29
+ .tox/
30
+ .nox/
31
+
32
+ # Runtime caches and logs
33
+ .cache/
34
+ *.log
35
+ *.jsonl
36
+
37
+ # Local environment variables and credentials
38
+ .env
39
+ .env.*
40
+ !.env.example
41
+ *.pem
42
+ *.key
43
+ *.p12
44
+ *.pfx
45
+ credentials*.json
46
+ secrets*.json
47
+
48
+ # Local model and Hugging Face caches
49
+ .huggingface/
50
+ .hf_home/
51
+
52
+ # Editors, OS files, and local agent metadata
53
+ .idea/
54
+ .vscode/
55
+ .agents/
56
+ .codex/
57
+ *.swp
58
+ *~
59
+ .DS_Store
60
+ Thumbs.db
61
+ Desktop.ini
62
+ publish_instruction.md
route67-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 route67 contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
route67-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,220 @@
1
+ Metadata-Version: 2.4
2
+ Name: route67
3
+ Version: 0.1.0
4
+ Summary: A semantic LLM router for OpenAI-compatible chat completions.
5
+ Project-URL: Homepage, https://github.com/SmallChungus1/route67
6
+ Project-URL: Repository, https://github.com/SmallChungus1/route67
7
+ Project-URL: Issues, https://github.com/SmallChungus1/route67/issues
8
+ Author: route67 contributors
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: llm,openai,router,semantic-routing
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3 :: Only
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
22
+ Requires-Python: >=3.10
23
+ Requires-Dist: model2vec<1,>=0.6
24
+ Requires-Dist: numpy>=1.24
25
+ Requires-Dist: openai<3,>=1.0
26
+ Provides-Extra: test
27
+ Requires-Dist: pytest>=8; extra == 'test'
28
+ Description-Content-Type: text/markdown
29
+
30
+ # route67
31
+
32
+ `route67` is a LLM router for OpenAI-compatible chat
33
+ completions format. It uses a user-defined routing table for user defined question-model routing via semantic similarity, as a fallback a weak model answer or explicitly escalate to a strong model.
34
+
35
+ ## How it works
36
+
37
+ ```mermaid
38
+ flowchart LR
39
+ Q["User request"] --> R{"Semantic route match?"}
40
+ R -- Yes --> M["Configured weak or strong model"]
41
+ R -- No --> W["Weak model gate<br/>usage notes + strong-route examples"]
42
+ W -- Answers --> O["Response"]
43
+ W -- ESCALATE --> S["Strong model"]
44
+ M --> O
45
+ S --> O
46
+ ```
47
+
48
+ ## Install
49
+
50
+ route67 requires Python 3.10 or newer. Choose either the standard Python workflow
51
+ or the `uv` workflow.
52
+
53
+ ### Using `python -m venv`
54
+
55
+ Create and activate a virtual environment:
56
+
57
+ ```console
58
+ python -m venv .venv
59
+ ```
60
+
61
+ ```powershell
62
+ # Windows PowerShell
63
+ .\.venv\Scripts\Activate.ps1
64
+ ```
65
+
66
+ ```console
67
+ # macOS/Linux
68
+ source .venv/bin/activate
69
+ ```
70
+
71
+ Then install route67 and its dependencies:
72
+
73
+ ```console
74
+ python -m pip install --upgrade pip
75
+ python -m pip install -e .
76
+ ```
77
+
78
+ To also install the test dependencies, use `python -m pip install -e ".[test]"`.
79
+
80
+ ### Using `uv`
81
+
82
+ With [`uv`](https://docs.astral.sh/uv/) installed, create the environment and
83
+ install the project from the lockfile:
84
+
85
+ ```console
86
+ uv sync
87
+ ```
88
+
89
+ Run commands inside the environment with `uv run`, for example
90
+ `uv run python example.py`. To include test dependencies, use
91
+ `uv sync --extra test`.
92
+
93
+ ## Get started
94
+
95
+ Set an OpenAI API key in your environment:
96
+
97
+ ```powershell
98
+ # Windows PowerShell
99
+ $env:OPENAI_API_KEY = "your-api-key"
100
+ ```
101
+
102
+ ```console
103
+ # macOS/Linux
104
+ export OPENAI_API_KEY="your-api-key"
105
+ ```
106
+
107
+ Create `example.py`:
108
+
109
+ ```python
110
+ from llm_router import Controller, ModelSpec, RouterConfig, RoutingTableEntry
111
+
112
+ config = RouterConfig(
113
+ routing_table=[
114
+ RoutingTableEntry(
115
+ "Prove this theorem",
116
+ "strong_model",
117
+ notes="Requires a rigorous multi-step proof.",
118
+ ),
119
+ RoutingTableEntry("Rewrite this paragraph", "weak_model"),
120
+ ],
121
+ weak_model=ModelSpec(
122
+ "gpt-5-mini",
123
+ usage_notes="Avoid difficult multi-step proofs.",
124
+ ),
125
+ strong_model=ModelSpec(
126
+ "gpt-5",
127
+ usage_notes="Use for rigorous proofs and difficult reasoning.",
128
+ ),
129
+ embedding_cache_path=".cache/routes",
130
+ log_path=".cache/routing.jsonl",
131
+ )
132
+
133
+ client = Controller(config)
134
+ response = client.chat.completions.create(
135
+ messages=[{"role": "user", "content": "Prove that sqrt(2) is irrational."}]
136
+ )
137
+ print(response.choices[0].message.content)
138
+ ```
139
+
140
+ Run it with the activated standard virtual environment:
141
+
142
+ ```console
143
+ python example.py
144
+ ```
145
+
146
+ Or with `uv`:
147
+
148
+ ```console
149
+ uv run python example.py
150
+ ```
151
+
152
+ ### OpenAI-compatible providers
153
+
154
+ route67 can use any provider exposed through an OpenAI-compatible client. Create
155
+ the provider's client normally and inject it into the controller. Model names in
156
+ the routing configuration are passed to that provider unchanged.
157
+
158
+ For example, with OpenRouter:
159
+
160
+ ```python
161
+ import os
162
+
163
+ from openai import OpenAI
164
+ from llm_router import Controller, ModelSpec, RouterConfig, RoutingTableEntry
165
+
166
+ openrouter = OpenAI(
167
+ base_url="https://openrouter.ai/api/v1",
168
+ api_key=os.environ["OPENROUTER_API_KEY"],
169
+ )
170
+
171
+ config = RouterConfig(
172
+ routing_table=[
173
+ RoutingTableEntry(
174
+ "Answer questions about a country",
175
+ "weak_model",
176
+ ),
177
+ RoutingTableEntry(
178
+ "Solve a difficult reasoning or math problem",
179
+ "strong_model",
180
+ notes="Requires careful multi-step reasoning.",
181
+ ),
182
+ ],
183
+ weak_model=ModelSpec(
184
+ "openai/gpt-4.1-mini",
185
+ usage_notes="Best for straightforward factual and writing questions.",
186
+ ),
187
+ strong_model=ModelSpec(
188
+ "deepseek/deepseek-v4-flash",
189
+ usage_notes="Use for difficult reasoning, mathematics, and verification.",
190
+ ),
191
+ )
192
+
193
+ client = Controller(config, openai_client=openrouter)
194
+ response = client.chat.completions.create(
195
+ messages=[
196
+ {
197
+ "role": "user",
198
+ "content": "How many r's are in the word 'strawberry'?",
199
+ }
200
+ ],
201
+ extra_body={"reasoning": {"enabled": True}},
202
+ )
203
+ ```
204
+
205
+ Provider-specific request options such as `extra_body` and `extra_headers` are
206
+ forwarded unchanged. Provider-specific response fields, including
207
+ `reasoning_details`, are also preserved. To continue a provider's reasoning,
208
+ pass its assistant message fields back unmodified in the next request.
209
+
210
+ Routing table entries target only `"weak_model"` or `"strong_model"`. Provider
211
+ model names live in `ModelSpec`, so switching models or providers does not
212
+ require rewriting the routing table.
213
+
214
+ `ModelSpec.usage_notes` are added to the weak model's escalation system prompt.
215
+ The prompt also includes up to five routing-table entries targeting
216
+ `"strong_model"` as examples of requests that should be escalated. Add concise
217
+ `notes` to those entries when the reason for escalation is useful context.
218
+
219
+ Your first request will download the `minishlab/potion-base-8M` from HuggingFace. The model is lazy-loaded,
220
+ so constructing a controller with an empty routing table does not download it.
@@ -0,0 +1,191 @@
1
+ # route67
2
+
3
+ `route67` is a LLM router for OpenAI-compatible chat
4
+ completions format. It uses a user-defined routing table for user defined question-model routing via semantic similarity, as a fallback a weak model answer or explicitly escalate to a strong model.
5
+
6
+ ## How it works
7
+
8
+ ```mermaid
9
+ flowchart LR
10
+ Q["User request"] --> R{"Semantic route match?"}
11
+ R -- Yes --> M["Configured weak or strong model"]
12
+ R -- No --> W["Weak model gate<br/>usage notes + strong-route examples"]
13
+ W -- Answers --> O["Response"]
14
+ W -- ESCALATE --> S["Strong model"]
15
+ M --> O
16
+ S --> O
17
+ ```
18
+
19
+ ## Install
20
+
21
+ route67 requires Python 3.10 or newer. Choose either the standard Python workflow
22
+ or the `uv` workflow.
23
+
24
+ ### Using `python -m venv`
25
+
26
+ Create and activate a virtual environment:
27
+
28
+ ```console
29
+ python -m venv .venv
30
+ ```
31
+
32
+ ```powershell
33
+ # Windows PowerShell
34
+ .\.venv\Scripts\Activate.ps1
35
+ ```
36
+
37
+ ```console
38
+ # macOS/Linux
39
+ source .venv/bin/activate
40
+ ```
41
+
42
+ Then install route67 and its dependencies:
43
+
44
+ ```console
45
+ python -m pip install --upgrade pip
46
+ python -m pip install -e .
47
+ ```
48
+
49
+ To also install the test dependencies, use `python -m pip install -e ".[test]"`.
50
+
51
+ ### Using `uv`
52
+
53
+ With [`uv`](https://docs.astral.sh/uv/) installed, create the environment and
54
+ install the project from the lockfile:
55
+
56
+ ```console
57
+ uv sync
58
+ ```
59
+
60
+ Run commands inside the environment with `uv run`, for example
61
+ `uv run python example.py`. To include test dependencies, use
62
+ `uv sync --extra test`.
63
+
64
+ ## Get started
65
+
66
+ Set an OpenAI API key in your environment:
67
+
68
+ ```powershell
69
+ # Windows PowerShell
70
+ $env:OPENAI_API_KEY = "your-api-key"
71
+ ```
72
+
73
+ ```console
74
+ # macOS/Linux
75
+ export OPENAI_API_KEY="your-api-key"
76
+ ```
77
+
78
+ Create `example.py`:
79
+
80
+ ```python
81
+ from llm_router import Controller, ModelSpec, RouterConfig, RoutingTableEntry
82
+
83
+ config = RouterConfig(
84
+ routing_table=[
85
+ RoutingTableEntry(
86
+ "Prove this theorem",
87
+ "strong_model",
88
+ notes="Requires a rigorous multi-step proof.",
89
+ ),
90
+ RoutingTableEntry("Rewrite this paragraph", "weak_model"),
91
+ ],
92
+ weak_model=ModelSpec(
93
+ "gpt-5-mini",
94
+ usage_notes="Avoid difficult multi-step proofs.",
95
+ ),
96
+ strong_model=ModelSpec(
97
+ "gpt-5",
98
+ usage_notes="Use for rigorous proofs and difficult reasoning.",
99
+ ),
100
+ embedding_cache_path=".cache/routes",
101
+ log_path=".cache/routing.jsonl",
102
+ )
103
+
104
+ client = Controller(config)
105
+ response = client.chat.completions.create(
106
+ messages=[{"role": "user", "content": "Prove that sqrt(2) is irrational."}]
107
+ )
108
+ print(response.choices[0].message.content)
109
+ ```
110
+
111
+ Run it with the activated standard virtual environment:
112
+
113
+ ```console
114
+ python example.py
115
+ ```
116
+
117
+ Or with `uv`:
118
+
119
+ ```console
120
+ uv run python example.py
121
+ ```
122
+
123
+ ### OpenAI-compatible providers
124
+
125
+ route67 can use any provider exposed through an OpenAI-compatible client. Create
126
+ the provider's client normally and inject it into the controller. Model names in
127
+ the routing configuration are passed to that provider unchanged.
128
+
129
+ For example, with OpenRouter:
130
+
131
+ ```python
132
+ import os
133
+
134
+ from openai import OpenAI
135
+ from llm_router import Controller, ModelSpec, RouterConfig, RoutingTableEntry
136
+
137
+ openrouter = OpenAI(
138
+ base_url="https://openrouter.ai/api/v1",
139
+ api_key=os.environ["OPENROUTER_API_KEY"],
140
+ )
141
+
142
+ config = RouterConfig(
143
+ routing_table=[
144
+ RoutingTableEntry(
145
+ "Answer questions about a country",
146
+ "weak_model",
147
+ ),
148
+ RoutingTableEntry(
149
+ "Solve a difficult reasoning or math problem",
150
+ "strong_model",
151
+ notes="Requires careful multi-step reasoning.",
152
+ ),
153
+ ],
154
+ weak_model=ModelSpec(
155
+ "openai/gpt-4.1-mini",
156
+ usage_notes="Best for straightforward factual and writing questions.",
157
+ ),
158
+ strong_model=ModelSpec(
159
+ "deepseek/deepseek-v4-flash",
160
+ usage_notes="Use for difficult reasoning, mathematics, and verification.",
161
+ ),
162
+ )
163
+
164
+ client = Controller(config, openai_client=openrouter)
165
+ response = client.chat.completions.create(
166
+ messages=[
167
+ {
168
+ "role": "user",
169
+ "content": "How many r's are in the word 'strawberry'?",
170
+ }
171
+ ],
172
+ extra_body={"reasoning": {"enabled": True}},
173
+ )
174
+ ```
175
+
176
+ Provider-specific request options such as `extra_body` and `extra_headers` are
177
+ forwarded unchanged. Provider-specific response fields, including
178
+ `reasoning_details`, are also preserved. To continue a provider's reasoning,
179
+ pass its assistant message fields back unmodified in the next request.
180
+
181
+ Routing table entries target only `"weak_model"` or `"strong_model"`. Provider
182
+ model names live in `ModelSpec`, so switching models or providers does not
183
+ require rewriting the routing table.
184
+
185
+ `ModelSpec.usage_notes` are added to the weak model's escalation system prompt.
186
+ The prompt also includes up to five routing-table entries targeting
187
+ `"strong_model"` as examples of requests that should be escalated. Add concise
188
+ `notes` to those entries when the reason for escalation is useful context.
189
+
190
+ Your first request will download the `minishlab/potion-base-8M` from HuggingFace. The model is lazy-loaded,
191
+ so constructing a controller with an empty routing table does not download it.
@@ -0,0 +1,46 @@
1
+ """Route67 usage with open router example"""
2
+ import os
3
+
4
+ from openai import OpenAI
5
+ from llm_router import Controller, ModelSpec, RouterConfig, RoutingTableEntry
6
+
7
+ openrouter = OpenAI(
8
+ base_url="https://openrouter.ai/api/v1",
9
+ api_key=os.environ["OPENROUTER_API_KEY"],
10
+ )
11
+
12
+ config = RouterConfig(
13
+ routing_table=[
14
+ RoutingTableEntry(
15
+ "Answer questions about a country",
16
+ "weak_model",
17
+ ),
18
+ RoutingTableEntry(
19
+ "Solve a difficult reasoning or math problem",
20
+ "strong_model",
21
+ notes="Requires careful multi-step reasoning.",
22
+ ),
23
+ ],
24
+ weak_model=ModelSpec(
25
+ "google/gemma-3-4b-it",
26
+ usage_notes="Best for straightforward factual and writing questions.",
27
+ ),
28
+ strong_model=ModelSpec(
29
+ "deepseek/deepseek-v4-flash",
30
+ usage_notes="Use for difficult reasoning, mathematics, and verification.",
31
+ ),
32
+ )
33
+
34
+ client = Controller(config, openai_client=openrouter)
35
+ response = client.chat.completions.create(
36
+ messages=[
37
+ {
38
+ "role": "user",
39
+ "content": "What are some interesting facts about South Korea?",
40
+ }
41
+ ],
42
+ extra_body={"reasoning": {"enabled": True}},
43
+ )
44
+
45
+ print(f"Response model name: {response.model}")
46
+ print(response.choices[0].message.content)
@@ -0,0 +1,49 @@
1
+ [build-system]
2
+ requires = ["hatchling>=1.26"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "route67"
7
+ dynamic = ["version"]
8
+ description = "A semantic LLM router for OpenAI-compatible chat completions."
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = "MIT"
12
+ license-files = ["LICENSE"]
13
+ authors = [{ name = "route67 contributors" }]
14
+ keywords = ["llm", "router", "openai", "semantic-routing"]
15
+ classifiers = [
16
+ "Development Status :: 3 - Alpha",
17
+ "Intended Audience :: Developers",
18
+ "License :: OSI Approved :: MIT License",
19
+ "Programming Language :: Python :: 3",
20
+ "Programming Language :: Python :: 3 :: Only",
21
+ "Programming Language :: Python :: 3.10",
22
+ "Programming Language :: Python :: 3.11",
23
+ "Programming Language :: Python :: 3.12",
24
+ "Programming Language :: Python :: 3.13",
25
+ "Topic :: Software Development :: Libraries :: Python Modules",
26
+ ]
27
+ dependencies = [
28
+ "model2vec>=0.6,<1",
29
+ "numpy>=1.24",
30
+ "openai>=1.0,<3",
31
+ ]
32
+
33
+ [project.urls]
34
+ Homepage = "https://github.com/SmallChungus1/route67"
35
+ Repository = "https://github.com/SmallChungus1/route67"
36
+ Issues = "https://github.com/SmallChungus1/route67/issues"
37
+
38
+ [project.optional-dependencies]
39
+ test = ["pytest>=8"]
40
+
41
+ [tool.hatch.build.targets.wheel]
42
+ packages = ["src/llm_router"]
43
+
44
+ [tool.hatch.version]
45
+ path = "src/llm_router/__init__.py"
46
+
47
+ [tool.pytest.ini_options]
48
+ testpaths = ["tests"]
49
+ addopts = "-q"
@@ -0,0 +1,8 @@
1
+ """Public package interface for route67."""
2
+
3
+ from .config import ModelSpec, RouterConfig, RoutingTableEntry
4
+ from .controller import Controller
5
+
6
+ __all__ = ["Controller", "ModelSpec", "RouterConfig", "RoutingTableEntry"]
7
+ __version__ = "0.1.0"
8
+
@@ -0,0 +1,61 @@
1
+ """Configuration models for the router."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass, field
6
+ from typing import Literal
7
+
8
+ ModelTarget = Literal["weak_model", "strong_model"]
9
+ MODEL_TARGETS = frozenset({"weak_model", "strong_model"})
10
+
11
+
12
+ @dataclass(frozen=True, slots=True)
13
+ class RoutingTableEntry:
14
+ query: str
15
+ target: ModelTarget
16
+ notes: str | None = None
17
+
18
+ def __post_init__(self) -> None:
19
+ if self.target not in MODEL_TARGETS:
20
+ raise ValueError(
21
+ "routing target must be 'weak_model' or 'strong_model'"
22
+ )
23
+
24
+
25
+ @dataclass(frozen=True, slots=True)
26
+ class ModelSpec:
27
+ name: str
28
+ usage_notes: str | None = None
29
+
30
+
31
+ @dataclass(slots=True)
32
+ class RouterConfig:
33
+ routing_table: list[RoutingTableEntry] = field(default_factory=list)
34
+ similarity_threshold: float = 0.75
35
+ weak_model: ModelSpec | None = None
36
+ strong_model: ModelSpec | None = None
37
+ embedding_cache_path: str | None = None
38
+ log_path: str | None = None
39
+ escalation_max_tokens: int = 10
40
+ embedding_model: str = "minishlab/potion-base-8M"
41
+
42
+ def __post_init__(self) -> None:
43
+ if not -1.0 <= self.similarity_threshold <= 1.0:
44
+ raise ValueError("similarity_threshold must be between -1.0 and 1.0")
45
+ if self.weak_model is None:
46
+ raise ValueError("weak_model is required")
47
+ if self.strong_model is None:
48
+ raise ValueError("strong_model is required")
49
+ if self.escalation_max_tokens < 1:
50
+ raise ValueError("escalation_max_tokens must be at least 1")
51
+
52
+ def resolve_target(self, target: ModelTarget) -> ModelSpec:
53
+ if target == "weak_model":
54
+ if self.weak_model is None:
55
+ raise RuntimeError("weak_model is not configured")
56
+ return self.weak_model
57
+ if target == "strong_model":
58
+ if self.strong_model is None:
59
+ raise RuntimeError("strong_model is not configured")
60
+ return self.strong_model
61
+ raise ValueError("routing target must be 'weak_model' or 'strong_model'")