clef-router 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,19 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
publish:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
environment: pypi
|
|
11
|
+
permissions:
|
|
12
|
+
id-token: write
|
|
13
|
+
steps:
|
|
14
|
+
- uses: actions/checkout@v4
|
|
15
|
+
- uses: astral-sh/setup-uv@v5
|
|
16
|
+
with:
|
|
17
|
+
python-version: "3.12"
|
|
18
|
+
- run: uv build
|
|
19
|
+
- run: uv publish
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: clef-router
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Route prompts between cheap and frontier LLMs using Cloudflare's Clef decision model.
|
|
5
|
+
Author: Youssef Ouhaghi Ahmian
|
|
6
|
+
License: Apache-2.0
|
|
7
|
+
Keywords: ai,clef,cloudflare,cost-optimization,decision-model,llm,routing
|
|
8
|
+
Requires-Python: >=3.10
|
|
9
|
+
Requires-Dist: httpx>=0.25
|
|
10
|
+
Provides-Extra: dev
|
|
11
|
+
Requires-Dist: pytest>=7; extra == 'dev'
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
|
|
14
|
+
# clef-router
|
|
15
|
+
|
|
16
|
+
Route prompts between cheap and frontier LLMs using Cloudflare's [Clef](https://blog.cloudflare.com/clef-decision-models/) decision model.
|
|
17
|
+
|
|
18
|
+
## Quick start
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
pip install clef-router
|
|
22
|
+
export CLEF_ACCOUNT_ID=your_cloudflare_account_id
|
|
23
|
+
export CLEF_API_TOKEN=your_cloudflare_api_token
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
```python
|
|
27
|
+
from clef_router import ClefRouter
|
|
28
|
+
|
|
29
|
+
router = ClefRouter()
|
|
30
|
+
result = router.route("What is the capital of France?")
|
|
31
|
+
print(result.tier) # "cheap"
|
|
32
|
+
print(result.reason) # "classified as simple, confidence 0.90"
|
|
33
|
+
|
|
34
|
+
result = router.route("Design a distributed consensus algorithm")
|
|
35
|
+
print(result.tier) # "frontier"
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## How it works
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
prompt → Clef decision model (Cloudflare Workers AI) → tier
|
|
42
|
+
├─ simple → cheap model
|
|
43
|
+
├─ standard → cheap model
|
|
44
|
+
├─ complex → frontier model
|
|
45
|
+
└─ uncertain → frontier (confidence gate)
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## clef vs laya
|
|
49
|
+
|
|
50
|
+
| | laya | Clef |
|
|
51
|
+
|---|---|---|
|
|
52
|
+
| Latency | 5.8ms | 38.8ms (flash) / 209.3ms (full) |
|
|
53
|
+
| Quality (BFCL) | 38.13 | 98.76 |
|
|
54
|
+
| Context | 32k | 64k |
|
|
55
|
+
| Hosting | local CPU | Cloudflare API |
|
|
56
|
+
| Cost | $0 | Workers AI pricing |
|
|
57
|
+
|
|
58
|
+
Use [laya-router](https://github.com/Gjusev/laya-router) for sub-10ms local routing. Use clef-router when quality matters more than latency.
|
|
59
|
+
|
|
60
|
+
## License
|
|
61
|
+
|
|
62
|
+
Apache 2.0
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# clef-router
|
|
2
|
+
|
|
3
|
+
Route prompts between cheap and frontier LLMs using Cloudflare's [Clef](https://blog.cloudflare.com/clef-decision-models/) decision model.
|
|
4
|
+
|
|
5
|
+
## Quick start
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install clef-router
|
|
9
|
+
export CLEF_ACCOUNT_ID=your_cloudflare_account_id
|
|
10
|
+
export CLEF_API_TOKEN=your_cloudflare_api_token
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
from clef_router import ClefRouter
|
|
15
|
+
|
|
16
|
+
router = ClefRouter()
|
|
17
|
+
result = router.route("What is the capital of France?")
|
|
18
|
+
print(result.tier) # "cheap"
|
|
19
|
+
print(result.reason) # "classified as simple, confidence 0.90"
|
|
20
|
+
|
|
21
|
+
result = router.route("Design a distributed consensus algorithm")
|
|
22
|
+
print(result.tier) # "frontier"
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## How it works
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
prompt → Clef decision model (Cloudflare Workers AI) → tier
|
|
29
|
+
├─ simple → cheap model
|
|
30
|
+
├─ standard → cheap model
|
|
31
|
+
├─ complex → frontier model
|
|
32
|
+
└─ uncertain → frontier (confidence gate)
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## clef vs laya
|
|
36
|
+
|
|
37
|
+
| | laya | Clef |
|
|
38
|
+
|---|---|---|
|
|
39
|
+
| Latency | 5.8ms | 38.8ms (flash) / 209.3ms (full) |
|
|
40
|
+
| Quality (BFCL) | 38.13 | 98.76 |
|
|
41
|
+
| Context | 32k | 64k |
|
|
42
|
+
| Hosting | local CPU | Cloudflare API |
|
|
43
|
+
| Cost | $0 | Workers AI pricing |
|
|
44
|
+
|
|
45
|
+
Use [laya-router](https://github.com/Gjusev/laya-router) for sub-10ms local routing. Use clef-router when quality matters more than latency.
|
|
46
|
+
|
|
47
|
+
## License
|
|
48
|
+
|
|
49
|
+
Apache 2.0
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "clef-router"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Route prompts between cheap and frontier LLMs using Cloudflare's Clef decision model."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = { text = "Apache-2.0" }
|
|
7
|
+
requires-python = ">=3.10"
|
|
8
|
+
authors = [{ name = "Youssef Ouhaghi Ahmian" }]
|
|
9
|
+
keywords = ["llm", "routing", "ai", "clef", "cloudflare", "decision-model", "cost-optimization"]
|
|
10
|
+
dependencies = ["httpx>=0.25"]
|
|
11
|
+
|
|
12
|
+
[project.scripts]
|
|
13
|
+
clef-route = "clef_router.cli:main"
|
|
14
|
+
|
|
15
|
+
[project.optional-dependencies]
|
|
16
|
+
dev = ["pytest>=7"]
|
|
17
|
+
|
|
18
|
+
[build-system]
|
|
19
|
+
requires = ["hatchling"]
|
|
20
|
+
build-backend = "hatchling.build"
|
|
21
|
+
|
|
22
|
+
[tool.hatch.build.targets.wheel]
|
|
23
|
+
packages = ["src/clef_router"]
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"""clef-router: Route prompts between cheap and frontier LLMs using Cloudflare's Clef."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from dataclasses import dataclass, field
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
7
|
+
import httpx
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass
|
|
11
|
+
class RouteResult:
|
|
12
|
+
tier: str
|
|
13
|
+
complexity: str
|
|
14
|
+
confidence: float
|
|
15
|
+
reason: str
|
|
16
|
+
raw_response: dict = field(default_factory=dict, repr=False)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass
|
|
20
|
+
class ClefRouter:
|
|
21
|
+
account_id: str = field(default_factory=lambda: os.environ.get("CLEF_ACCOUNT_ID", ""))
|
|
22
|
+
api_token: str = field(default_factory=lambda: os.environ.get("CLEF_API_TOKEN", ""))
|
|
23
|
+
model: str = "@cf/cloudflare/clef-flash"
|
|
24
|
+
base_url: str = "https://api.cloudflare.com/client/v4"
|
|
25
|
+
min_confidence: float = 0.45
|
|
26
|
+
timeout: float = 30.0
|
|
27
|
+
|
|
28
|
+
def __post_init__(self):
|
|
29
|
+
if not self.account_id:
|
|
30
|
+
raise ValueError("Set CLEF_ACCOUNT_ID env var or pass account_id")
|
|
31
|
+
if not self.api_token:
|
|
32
|
+
raise ValueError("Set CLEF_API_TOKEN env var or pass api_token")
|
|
33
|
+
|
|
34
|
+
@property
|
|
35
|
+
def _endpoint(self) -> str:
|
|
36
|
+
return f"{self.base_url}/accounts/{self.account_id}/ai/run/{self.model}"
|
|
37
|
+
|
|
38
|
+
def _headers(self) -> dict:
|
|
39
|
+
return {"Authorization": f"Bearer {self.api_token}", "Content-Type": "application/json"}
|
|
40
|
+
|
|
41
|
+
def route(self, prompt: str, context: Optional[str] = None) -> RouteResult:
|
|
42
|
+
state = f"User prompt to classify:\n{prompt}"
|
|
43
|
+
if context:
|
|
44
|
+
state = f"Context: {context}\n\n{state}"
|
|
45
|
+
|
|
46
|
+
payload = {
|
|
47
|
+
"state": state,
|
|
48
|
+
"questions": {
|
|
49
|
+
"complexity": {
|
|
50
|
+
"type": "choice",
|
|
51
|
+
"context": "How computationally complex is this prompt? "
|
|
52
|
+
"simple = greeting, translation, basic lookup. "
|
|
53
|
+
"standard = summarization, rewording, simple analysis. "
|
|
54
|
+
"complex = reasoning, coding, multi-step, creative.",
|
|
55
|
+
"choices": ["simple", "standard", "complex"],
|
|
56
|
+
},
|
|
57
|
+
"confidence": {
|
|
58
|
+
"type": "score",
|
|
59
|
+
"context": "How confident are you in this classification?",
|
|
60
|
+
"levels": ["very_low", "low", "medium", "high", "very_high"],
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
resp = httpx.post(
|
|
66
|
+
self._endpoint, json=payload, headers=self._headers(), timeout=self.timeout,
|
|
67
|
+
)
|
|
68
|
+
resp.raise_for_status()
|
|
69
|
+
data = resp.json()
|
|
70
|
+
|
|
71
|
+
result = data.get("result", {})
|
|
72
|
+
complexity = result.get("complexity", "complex")
|
|
73
|
+
conf_map = {"very_low": 0.1, "low": 0.3, "medium": 0.5, "high": 0.7, "very_high": 0.9}
|
|
74
|
+
confidence = conf_map.get(result.get("confidence", "low"), 0.3)
|
|
75
|
+
|
|
76
|
+
if confidence < self.min_confidence:
|
|
77
|
+
tier = "frontier"
|
|
78
|
+
reason = f"confidence {confidence:.2f} below threshold {self.min_confidence}, escalated"
|
|
79
|
+
elif complexity == "complex":
|
|
80
|
+
tier = "frontier"
|
|
81
|
+
reason = f"classified as {complexity}"
|
|
82
|
+
else:
|
|
83
|
+
tier = "cheap"
|
|
84
|
+
reason = f"classified as {complexity}, confidence {confidence:.2f}"
|
|
85
|
+
|
|
86
|
+
return RouteResult(
|
|
87
|
+
tier=tier, complexity=complexity, confidence=confidence,
|
|
88
|
+
reason=reason, raw_response=data,
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
__version__ = "0.1.0"
|
|
93
|
+
__all__ = ["ClefRouter", "RouteResult"]
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""CLI for clef-router."""
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import json
|
|
5
|
+
|
|
6
|
+
from . import ClefRouter
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def main():
|
|
10
|
+
p = argparse.ArgumentParser(prog="clef-route", description="Route a prompt using Clef")
|
|
11
|
+
p.add_argument("prompt", help="The prompt to classify")
|
|
12
|
+
p.add_argument("--context", "-c", default=None)
|
|
13
|
+
p.add_argument("--json", action="store_true")
|
|
14
|
+
args = p.parse_args()
|
|
15
|
+
|
|
16
|
+
router = ClefRouter()
|
|
17
|
+
result = router.route(args.prompt, context=args.context)
|
|
18
|
+
|
|
19
|
+
if args.json:
|
|
20
|
+
print(json.dumps({
|
|
21
|
+
"tier": result.tier,
|
|
22
|
+
"complexity": result.complexity,
|
|
23
|
+
"confidence": result.confidence,
|
|
24
|
+
"reason": result.reason,
|
|
25
|
+
}, indent=2))
|
|
26
|
+
else:
|
|
27
|
+
print(f"tier: {result.tier}")
|
|
28
|
+
print(f"complexity: {result.complexity}")
|
|
29
|
+
print(f"confidence: {result.confidence:.2f}")
|
|
30
|
+
print(f"reason: {result.reason}")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
if __name__ == "__main__":
|
|
34
|
+
main()
|