coderouter-cli 1.10.0__py3-none-any.whl → 1.10.1__py3-none-any.whl
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.
- coderouter/config/schemas.py +25 -0
- coderouter/routing/auto_router.py +55 -8
- {coderouter_cli-1.10.0.dist-info → coderouter_cli-1.10.1.dist-info}/METADATA +1 -1
- {coderouter_cli-1.10.0.dist-info → coderouter_cli-1.10.1.dist-info}/RECORD +7 -7
- {coderouter_cli-1.10.0.dist-info → coderouter_cli-1.10.1.dist-info}/WHEEL +0 -0
- {coderouter_cli-1.10.0.dist-info → coderouter_cli-1.10.1.dist-info}/entry_points.txt +0 -0
- {coderouter_cli-1.10.0.dist-info → coderouter_cli-1.10.1.dist-info}/licenses/LICENSE +0 -0
coderouter/config/schemas.py
CHANGED
|
@@ -498,6 +498,23 @@ class RuleMatcher(BaseModel):
|
|
|
498
498
|
workloads can compensate by tuning the threshold, since the
|
|
499
499
|
char/4 heuristic is conservative for CJK and looser for
|
|
500
500
|
English code.
|
|
501
|
+
|
|
502
|
+
Variants ([Unreleased] / tool-aware routing, OpenClaw + Pi 由来):
|
|
503
|
+
|
|
504
|
+
- ``has_tools: True`` — the request body declares one or more
|
|
505
|
+
tools (OpenAI ``tools[]`` / Anthropic ``tools[]`` / OpenAI legacy
|
|
506
|
+
``functions[]``). Lets operators send tool-laden requests to a
|
|
507
|
+
tool-capable cloud profile while keeping plain chat on a small
|
|
508
|
+
local model (typical Raspberry Pi / low-spec deployment shape:
|
|
509
|
+
a 1-4B local model that cannot reliably tool-call paired with a
|
|
510
|
+
free-tier cloud chain that can). Distinct from the
|
|
511
|
+
``capabilities.tools`` flag on a provider — that flag is read by
|
|
512
|
+
``coderouter doctor`` for diagnostics but does NOT gate the
|
|
513
|
+
fallback chain (the chain just iterates providers in order and
|
|
514
|
+
engages the v0.3-D tool-downgrade path on non-native ones with
|
|
515
|
+
``request.tools`` set). The ``has_tools`` matcher is the
|
|
516
|
+
profile-level lever for steering tool-laden traffic to the right
|
|
517
|
+
chain entirely.
|
|
501
518
|
"""
|
|
502
519
|
|
|
503
520
|
model_config = ConfigDict(extra="forbid")
|
|
@@ -508,6 +525,13 @@ class RuleMatcher(BaseModel):
|
|
|
508
525
|
content_regex: str | None = None
|
|
509
526
|
model_pattern: str | None = None
|
|
510
527
|
content_token_count_min: int | None = Field(default=None, ge=1)
|
|
528
|
+
# [Unreleased]: tool-aware routing (OpenClaw + Raspberry Pi 由来).
|
|
529
|
+
# See class docstring "Variants ([Unreleased] / tool-aware routing)"
|
|
530
|
+
# above for the full rationale. Boolean shape mirrors ``has_image`` —
|
|
531
|
+
# only the ``True`` value is meaningful (matches when the body
|
|
532
|
+
# declares any tools); ``False`` is rejected by ``_exactly_one``
|
|
533
|
+
# since a "no-tools" rule would shadow the default fall-through.
|
|
534
|
+
has_tools: bool | None = None
|
|
511
535
|
|
|
512
536
|
_MATCHER_FIELDS: tuple[str, ...] = (
|
|
513
537
|
"has_image",
|
|
@@ -516,6 +540,7 @@ class RuleMatcher(BaseModel):
|
|
|
516
540
|
"content_regex",
|
|
517
541
|
"model_pattern",
|
|
518
542
|
"content_token_count_min",
|
|
543
|
+
"has_tools",
|
|
519
544
|
)
|
|
520
545
|
|
|
521
546
|
@model_validator(mode="after")
|
|
@@ -142,12 +142,40 @@ def _code_fence_ratio(text: str) -> float:
|
|
|
142
142
|
return fenced / len(text)
|
|
143
143
|
|
|
144
144
|
|
|
145
|
+
def _has_tools_in_body(body: dict[str, Any]) -> bool:
|
|
146
|
+
"""True iff the request body declares one or more callable tools.
|
|
147
|
+
|
|
148
|
+
Recognized declaration shapes:
|
|
149
|
+
|
|
150
|
+
* ``tools: [...]`` — OpenAI Chat Completions ``tools[]`` AND
|
|
151
|
+
Anthropic Messages API ``tools[]``. Both wire formats put the
|
|
152
|
+
array at the same top-level key, so a single membership check
|
|
153
|
+
covers both ingresses.
|
|
154
|
+
* ``functions: [...]`` — OpenAI legacy ``functions[]`` (deprecated
|
|
155
|
+
since 2023-11 but still emitted by some agents that pinned old
|
|
156
|
+
SDK versions). Treated as equivalent to ``tools[]`` for routing
|
|
157
|
+
purposes.
|
|
158
|
+
|
|
159
|
+
A non-list value (or a value of ``None`` / empty list) returns
|
|
160
|
+
False — agents that initialize the field but populate it lazily
|
|
161
|
+
are still on the no-tools path until a tool actually appears.
|
|
162
|
+
"""
|
|
163
|
+
tools = body.get("tools")
|
|
164
|
+
if isinstance(tools, list) and len(tools) > 0:
|
|
165
|
+
return True
|
|
166
|
+
functions = body.get("functions")
|
|
167
|
+
if isinstance(functions, list) and len(functions) > 0:
|
|
168
|
+
return True
|
|
169
|
+
return False
|
|
170
|
+
|
|
171
|
+
|
|
145
172
|
def _match_rule(
|
|
146
173
|
rule: AutoRouteRule,
|
|
147
174
|
message: dict[str, Any] | None,
|
|
148
175
|
text: str,
|
|
149
176
|
model: str | None,
|
|
150
177
|
estimated_tokens: int,
|
|
178
|
+
has_tools: bool,
|
|
151
179
|
) -> bool:
|
|
152
180
|
m = rule.match
|
|
153
181
|
if m.has_image is True:
|
|
@@ -177,6 +205,14 @@ def _match_rule(
|
|
|
177
205
|
# for English code; operators tune the threshold to match
|
|
178
206
|
# their input distribution.
|
|
179
207
|
return estimated_tokens >= m.content_token_count_min
|
|
208
|
+
if m.has_tools is True:
|
|
209
|
+
# [Unreleased]: tool-aware routing (OpenClaw + Pi 由来).
|
|
210
|
+
# Computed once in ``classify`` from ``body.tools`` /
|
|
211
|
+
# ``body.functions`` so per-rule evaluation is O(1). See
|
|
212
|
+
# ``_has_tools_in_body`` for the recognized declaration shapes
|
|
213
|
+
# and ``RuleMatcher`` docstring for why this is profile-level
|
|
214
|
+
# routing (not a provider capability gate).
|
|
215
|
+
return has_tools
|
|
180
216
|
return False # pragma: no cover — _exactly_one guards against this
|
|
181
217
|
|
|
182
218
|
|
|
@@ -259,6 +295,11 @@ def classify(body: dict[str, Any], config: CodeRouterConfig) -> str:
|
|
|
259
295
|
# contribute 0. See ``_estimate_total_tokens`` for the heuristic
|
|
260
296
|
# rationale and the 5-deps tradeoff.
|
|
261
297
|
estimated_tokens = _estimate_total_tokens(body)
|
|
298
|
+
# [Unreleased]: tool-aware routing (OpenClaw + Pi 由来). Computed
|
|
299
|
+
# once for both the ``has_tools`` matcher and the signals payload.
|
|
300
|
+
# See ``_has_tools_in_body`` for the recognized declaration shapes
|
|
301
|
+
# (OpenAI/Anthropic ``tools[]``, OpenAI legacy ``functions[]``).
|
|
302
|
+
has_tools = _has_tools_in_body(body)
|
|
262
303
|
|
|
263
304
|
auto_cfg = config.auto_router
|
|
264
305
|
if auto_cfg is not None and auto_cfg.disabled:
|
|
@@ -267,6 +308,7 @@ def classify(body: dict[str, Any], config: CodeRouterConfig) -> str:
|
|
|
267
308
|
text,
|
|
268
309
|
model,
|
|
269
310
|
estimated_tokens,
|
|
311
|
+
has_tools,
|
|
270
312
|
disabled=True,
|
|
271
313
|
)
|
|
272
314
|
return auto_cfg.default_rule_profile
|
|
@@ -278,17 +320,18 @@ def classify(body: dict[str, Any], config: CodeRouterConfig) -> str:
|
|
|
278
320
|
else BUNDLED_DEFAULT_RULE_PROFILE
|
|
279
321
|
)
|
|
280
322
|
|
|
281
|
-
# ``model_pattern`` and ``
|
|
282
|
-
# fire even without a user message (e.g. system-only
|
|
283
|
-
# request body that carries only a model field
|
|
284
|
-
# still require ``user_msg`` to be
|
|
285
|
-
# ``_match_rule``'s message-None
|
|
323
|
+
# ``model_pattern``, ``content_token_count_min`` and ``has_tools``
|
|
324
|
+
# matchers can fire even without a user message (e.g. system-only
|
|
325
|
+
# prompts or a request body that carries only a model field +
|
|
326
|
+
# tools array). Other matchers still require ``user_msg`` to be
|
|
327
|
+
# present — they short out via ``_match_rule``'s message-None
|
|
328
|
+
# handling.
|
|
286
329
|
for rule in rules:
|
|
287
|
-
if _match_rule(rule, user_msg, text, model, estimated_tokens):
|
|
288
|
-
_emit_resolved(rule, user_msg, text, model, estimated_tokens)
|
|
330
|
+
if _match_rule(rule, user_msg, text, model, estimated_tokens, has_tools):
|
|
331
|
+
_emit_resolved(rule, user_msg, text, model, estimated_tokens, has_tools)
|
|
289
332
|
return rule.profile
|
|
290
333
|
|
|
291
|
-
_emit_fallthrough(default_profile, text, model, estimated_tokens)
|
|
334
|
+
_emit_fallthrough(default_profile, text, model, estimated_tokens, has_tools)
|
|
292
335
|
return default_profile
|
|
293
336
|
|
|
294
337
|
|
|
@@ -298,6 +341,7 @@ def _emit_resolved(
|
|
|
298
341
|
text: str,
|
|
299
342
|
model: str | None,
|
|
300
343
|
estimated_tokens: int,
|
|
344
|
+
has_tools: bool,
|
|
301
345
|
) -> None:
|
|
302
346
|
logger.info(
|
|
303
347
|
"auto-router-resolved",
|
|
@@ -310,6 +354,7 @@ def _emit_resolved(
|
|
|
310
354
|
"content_len": len(text),
|
|
311
355
|
"model": model,
|
|
312
356
|
"estimated_tokens": estimated_tokens,
|
|
357
|
+
"has_tools": has_tools,
|
|
313
358
|
},
|
|
314
359
|
},
|
|
315
360
|
)
|
|
@@ -320,6 +365,7 @@ def _emit_fallthrough(
|
|
|
320
365
|
text: str,
|
|
321
366
|
model: str | None,
|
|
322
367
|
estimated_tokens: int,
|
|
368
|
+
has_tools: bool,
|
|
323
369
|
disabled: bool = False,
|
|
324
370
|
) -> None:
|
|
325
371
|
logger.info(
|
|
@@ -331,6 +377,7 @@ def _emit_fallthrough(
|
|
|
331
377
|
"content_len": len(text),
|
|
332
378
|
"model": model,
|
|
333
379
|
"estimated_tokens": estimated_tokens,
|
|
380
|
+
"has_tools": has_tools,
|
|
334
381
|
"disabled": disabled,
|
|
335
382
|
},
|
|
336
383
|
},
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: coderouter-cli
|
|
3
|
-
Version: 1.10.
|
|
3
|
+
Version: 1.10.1
|
|
4
4
|
Summary: Local-first, free-first, fallback-built-in LLM router. Claude Code / OpenAI compatible.
|
|
5
5
|
Project-URL: Homepage, https://github.com/zephel01/CodeRouter
|
|
6
6
|
Project-URL: Repository, https://github.com/zephel01/CodeRouter
|
|
@@ -18,7 +18,7 @@ coderouter/config/__init__.py,sha256=FODEn74fN-qZnt4INPSHswqhOlEgpL6-_onxsitSx8g
|
|
|
18
18
|
coderouter/config/capability_registry.py,sha256=F6DetVL5oM03R4QeK1g6h_Q_zrXH0opnYDp3duZmkN4,15808
|
|
19
19
|
coderouter/config/env_file.py,sha256=CoMK27fuAXm-NtoLzXb8yN2E-wDFjHQuFwiIlmgTBQw,10356
|
|
20
20
|
coderouter/config/loader.py,sha256=FUEe8m4Tnmj_aul0vSctD8vKvNW-oLRoMRbTpSKqSmc,4077
|
|
21
|
-
coderouter/config/schemas.py,sha256=
|
|
21
|
+
coderouter/config/schemas.py,sha256=OI_78vSvjWTSVJMPER-JhtMIXOVJZ-8QaLH8wj3Snoc,38204
|
|
22
22
|
coderouter/data/__init__.py,sha256=uNyfD9jaCvTWsBAWtaw1Fr25OSxzv3psGMfBjT1z0Cc,328
|
|
23
23
|
coderouter/data/model-capabilities.yaml,sha256=b0CJYfBqnKA8uaOxOdgNf5z9opTZcrC-N2FtsJvxPgw,16911
|
|
24
24
|
coderouter/guards/__init__.py,sha256=eYjuKo0OvE-GjJo7drYtU2XavUPF9OdiTB5IS76c92Y,855
|
|
@@ -36,7 +36,7 @@ coderouter/metrics/collector.py,sha256=y4MUAAF_nf9nml6c_NwRwt_1BsIqCXVL1oLrxGmXF
|
|
|
36
36
|
coderouter/metrics/prometheus.py,sha256=GveI6OyWCACOet05Enpv1gxGrJ-wqpMWLiTRceT0bAw,16913
|
|
37
37
|
coderouter/routing/__init__.py,sha256=g2vhutbozRx5QBThReqwPN3imk5qXdpDiaogILd3IRc,257
|
|
38
38
|
coderouter/routing/adaptive.py,sha256=pRUphp2_NJ7i6I2HRNk3AkB7Wiu0amZ3vtgBHgmVLBU,20233
|
|
39
|
-
coderouter/routing/auto_router.py,sha256=
|
|
39
|
+
coderouter/routing/auto_router.py,sha256=1szbaFMa9mTZPuEOxdxHmVfMNfuH4v4FXkfD4LG3vsc,14571
|
|
40
40
|
coderouter/routing/budget.py,sha256=XZQL24YIkusB2MwfgbIJ1uhW1ODtMMdpbZ5e_A7oSU4,7164
|
|
41
41
|
coderouter/routing/capability.py,sha256=ziIDuE5keH_jxYDlXSKufRVxxSYOAvUxJ6Rw5QkYDDU,18436
|
|
42
42
|
coderouter/routing/fallback.py,sha256=M0jiUJzzvXcHHW-7YhOiDLAx-UJTL5EA_j97w5Ao-10,66540
|
|
@@ -44,8 +44,8 @@ coderouter/translation/__init__.py,sha256=PYXN7XVEwpG1uC8RLy6fvnGbzEZhhrEuUapH8I
|
|
|
44
44
|
coderouter/translation/anthropic.py,sha256=JpvIWNXHUPVqOGvps7o_6ZADhXuJuvpU7RdMqQFtwwM,6421
|
|
45
45
|
coderouter/translation/convert.py,sha256=-qyzFzmmr9hhQV6_Sg75kJnvCZvHe3n7vRdaZtk_JqQ,47269
|
|
46
46
|
coderouter/translation/tool_repair.py,sha256=fyxDb4kWHytO5JWq5y0i4tinJUtWqhMCkyfoCf5BjeM,8314
|
|
47
|
-
coderouter_cli-1.10.
|
|
48
|
-
coderouter_cli-1.10.
|
|
49
|
-
coderouter_cli-1.10.
|
|
50
|
-
coderouter_cli-1.10.
|
|
51
|
-
coderouter_cli-1.10.
|
|
47
|
+
coderouter_cli-1.10.1.dist-info/METADATA,sha256=H1f0BXj6vUXy05qjEY1lAah8ItzI_zrnj3rWkWjgU9w,47838
|
|
48
|
+
coderouter_cli-1.10.1.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
49
|
+
coderouter_cli-1.10.1.dist-info/entry_points.txt,sha256=-dnLfD1YZ2WjH2zSdNCvlO65wYltM9bsHt9Fhg3yGss,51
|
|
50
|
+
coderouter_cli-1.10.1.dist-info/licenses/LICENSE,sha256=wkEzoR86jFw33jvfOHjULqmkGEfxTFMgMaJnpR8mPRw,1065
|
|
51
|
+
coderouter_cli-1.10.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|