tensorfeed 2.1.0__tar.gz → 2.2.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.
- {tensorfeed-2.1.0 → tensorfeed-2.2.0}/PKG-INFO +59 -1
- {tensorfeed-2.1.0 → tensorfeed-2.2.0}/README.md +58 -0
- {tensorfeed-2.1.0 → tensorfeed-2.2.0}/pyproject.toml +1 -1
- {tensorfeed-2.1.0 → tensorfeed-2.2.0}/tensorfeed/_premium_tools.py +7 -6
- {tensorfeed-2.1.0 → tensorfeed-2.2.0}/tensorfeed/client.py +104 -1
- {tensorfeed-2.1.0 → tensorfeed-2.2.0}/tensorfeed.egg-info/PKG-INFO +59 -1
- {tensorfeed-2.1.0 → tensorfeed-2.2.0}/tensorfeed.egg-info/SOURCES.txt +2 -1
- {tensorfeed-2.1.0 → tensorfeed-2.2.0}/tests/test_premium_tools.py +28 -5
- tensorfeed-2.2.0/tests/test_route_verdict.py +152 -0
- {tensorfeed-2.1.0 → tensorfeed-2.2.0}/setup.cfg +0 -0
- {tensorfeed-2.1.0 → tensorfeed-2.2.0}/tensorfeed/__init__.py +0 -0
- {tensorfeed-2.1.0 → tensorfeed-2.2.0}/tensorfeed/crewai.py +0 -0
- {tensorfeed-2.1.0 → tensorfeed-2.2.0}/tensorfeed/langchain.py +0 -0
- {tensorfeed-2.1.0 → tensorfeed-2.2.0}/tensorfeed/llamaindex.py +0 -0
- {tensorfeed-2.1.0 → tensorfeed-2.2.0}/tensorfeed/web3_signer.py +0 -0
- {tensorfeed-2.1.0 → tensorfeed-2.2.0}/tensorfeed.egg-info/dependency_links.txt +0 -0
- {tensorfeed-2.1.0 → tensorfeed-2.2.0}/tensorfeed.egg-info/requires.txt +0 -0
- {tensorfeed-2.1.0 → tensorfeed-2.2.0}/tensorfeed.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tensorfeed
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.2.0
|
|
4
4
|
Summary: Python SDK for the TensorFeed.ai API: AI news, status, model pricing, premium routing + history series + webhook watches, LangChain/CrewAI tool factories, agent payments via USDC on Base (optional web3 auto-send)
|
|
5
5
|
Author-email: "TensorFeed.ai" <feedback@tensorfeed.ai>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -37,6 +37,64 @@ Python SDK for the [TensorFeed.ai](https://tensorfeed.ai) API.
|
|
|
37
37
|
|
|
38
38
|
Free endpoints (news, status, models, benchmarks, history, routing preview) need no auth. The premium tier (top-N routing, plus more endpoints landing later) is paid via USDC on Base. No accounts, no API keys, no traditional payment processors.
|
|
39
39
|
|
|
40
|
+
## Featured: Route Verdict
|
|
41
|
+
|
|
42
|
+
The single best model to use right now, as one signed call. Route Verdict fuses live pricing, contamination-discounted benchmark capability, real production usage, measured p95 latency probes, live incident state, and deprecation flags into one ranked decision, with an AFTA-signed receipt over the exact inputs. Instead of stitching together pricing pages, benchmark leaderboards, status dashboards, and your own latency tests, you get a current, defensible routing answer in one request.
|
|
43
|
+
|
|
44
|
+
### Zero install, no key, one command (works today)
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
curl -s -A "tensorfeed-cc-quickstart" "https://tensorfeed.ai/api/preview/route-verdict?task=code"
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Swap `task` for `reasoning`, `creative`, or `general`, or pass `?model=<id-or-name>` to score a specific model. The free preview is 10 calls per day per IP, no token. Abridged real response:
|
|
51
|
+
|
|
52
|
+
```json
|
|
53
|
+
{
|
|
54
|
+
"verdict": {
|
|
55
|
+
"rank": 1,
|
|
56
|
+
"model": { "name": "Gemini 2.5 Pro", "provider": "google" },
|
|
57
|
+
"pricing": { "blended": 5.625, "unit": "per 1M tokens" },
|
|
58
|
+
"latency": { "measured_p95_ms": 1223, "source": "measured_probe" },
|
|
59
|
+
"operational": { "ok": true, "status": "operational" },
|
|
60
|
+
"composite_score": 0.8449,
|
|
61
|
+
"why": "code quality 0.6498 after trust discount; corroborated by real usage (rank 5, 6.5% share, flat); measured p95 1223 ms; operational; blended $5.625 / 1M"
|
|
62
|
+
},
|
|
63
|
+
"rate_limit": { "limit": 10, "remaining": 9, "scope": "per IP per UTC day" },
|
|
64
|
+
"upgrade": { "premium_endpoint": "/api/premium/route-verdict", "adds": ["runners_up", "AFTA-signed receipt", "filter params", "no rate limit"] }
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
The premium version (`/api/premium/route-verdict`, 1 credit) adds ranked runners-up, the constraint filters (`max_latency_p95_ms`, `budget`, `min_quality`, `require_operational`, `exclude_deprecated`), the AFTA-signed receipt, and no rate limit. It needs a token; credits come from [tensorfeed.ai/developers/agent-payments](https://tensorfeed.ai/developers/agent-payments).
|
|
69
|
+
|
|
70
|
+
### Native SDK call
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
from tensorfeed import TensorFeed
|
|
74
|
+
|
|
75
|
+
tf = TensorFeed()
|
|
76
|
+
|
|
77
|
+
# Free preview: the top verdict only, no token. 10 calls/day per IP.
|
|
78
|
+
preview = tf.route_verdict_preview(task="code")
|
|
79
|
+
print(preview["verdict"]["model"]["name"], preview["verdict"]["why"])
|
|
80
|
+
|
|
81
|
+
# Premium: runners-up, constraint filters, and the AFTA-signed receipt.
|
|
82
|
+
# Needs a token (see the payment quickstart below).
|
|
83
|
+
tf_paid = TensorFeed(token="YOUR_TOKEN")
|
|
84
|
+
verdict = tf_paid.route_verdict(task="code", max_latency_p95_ms=1500)
|
|
85
|
+
print(verdict["verdict"], verdict["runners_up"])
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Pass `model="<id-or-name>"` instead of `task=` to score one model. Both methods accept exactly one of the two selectors.
|
|
89
|
+
|
|
90
|
+
### The agent path (MCP)
|
|
91
|
+
|
|
92
|
+
With the [`@tensorfeed/mcp-server`](https://www.npmjs.com/package/@tensorfeed/mcp-server) MCP server installed, an agent gets two tools: `route_verdict_preview` (free, the pick plus reasoning) and `route_verdict` (1 credit, ranked runners-up, constraint filters, and the AFTA-signed receipt it can audit).
|
|
93
|
+
|
|
94
|
+
### Why it matters
|
|
95
|
+
|
|
96
|
+
Models, prices, and latency move week to week. Route Verdict is one signed call an agent can act on now and later prove why it routed the way it did, without rebuilding the comparison from scratch each time.
|
|
97
|
+
|
|
40
98
|
## Install
|
|
41
99
|
|
|
42
100
|
```bash
|
|
@@ -4,6 +4,64 @@ Python SDK for the [TensorFeed.ai](https://tensorfeed.ai) API.
|
|
|
4
4
|
|
|
5
5
|
Free endpoints (news, status, models, benchmarks, history, routing preview) need no auth. The premium tier (top-N routing, plus more endpoints landing later) is paid via USDC on Base. No accounts, no API keys, no traditional payment processors.
|
|
6
6
|
|
|
7
|
+
## Featured: Route Verdict
|
|
8
|
+
|
|
9
|
+
The single best model to use right now, as one signed call. Route Verdict fuses live pricing, contamination-discounted benchmark capability, real production usage, measured p95 latency probes, live incident state, and deprecation flags into one ranked decision, with an AFTA-signed receipt over the exact inputs. Instead of stitching together pricing pages, benchmark leaderboards, status dashboards, and your own latency tests, you get a current, defensible routing answer in one request.
|
|
10
|
+
|
|
11
|
+
### Zero install, no key, one command (works today)
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
curl -s -A "tensorfeed-cc-quickstart" "https://tensorfeed.ai/api/preview/route-verdict?task=code"
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Swap `task` for `reasoning`, `creative`, or `general`, or pass `?model=<id-or-name>` to score a specific model. The free preview is 10 calls per day per IP, no token. Abridged real response:
|
|
18
|
+
|
|
19
|
+
```json
|
|
20
|
+
{
|
|
21
|
+
"verdict": {
|
|
22
|
+
"rank": 1,
|
|
23
|
+
"model": { "name": "Gemini 2.5 Pro", "provider": "google" },
|
|
24
|
+
"pricing": { "blended": 5.625, "unit": "per 1M tokens" },
|
|
25
|
+
"latency": { "measured_p95_ms": 1223, "source": "measured_probe" },
|
|
26
|
+
"operational": { "ok": true, "status": "operational" },
|
|
27
|
+
"composite_score": 0.8449,
|
|
28
|
+
"why": "code quality 0.6498 after trust discount; corroborated by real usage (rank 5, 6.5% share, flat); measured p95 1223 ms; operational; blended $5.625 / 1M"
|
|
29
|
+
},
|
|
30
|
+
"rate_limit": { "limit": 10, "remaining": 9, "scope": "per IP per UTC day" },
|
|
31
|
+
"upgrade": { "premium_endpoint": "/api/premium/route-verdict", "adds": ["runners_up", "AFTA-signed receipt", "filter params", "no rate limit"] }
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
The premium version (`/api/premium/route-verdict`, 1 credit) adds ranked runners-up, the constraint filters (`max_latency_p95_ms`, `budget`, `min_quality`, `require_operational`, `exclude_deprecated`), the AFTA-signed receipt, and no rate limit. It needs a token; credits come from [tensorfeed.ai/developers/agent-payments](https://tensorfeed.ai/developers/agent-payments).
|
|
36
|
+
|
|
37
|
+
### Native SDK call
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
from tensorfeed import TensorFeed
|
|
41
|
+
|
|
42
|
+
tf = TensorFeed()
|
|
43
|
+
|
|
44
|
+
# Free preview: the top verdict only, no token. 10 calls/day per IP.
|
|
45
|
+
preview = tf.route_verdict_preview(task="code")
|
|
46
|
+
print(preview["verdict"]["model"]["name"], preview["verdict"]["why"])
|
|
47
|
+
|
|
48
|
+
# Premium: runners-up, constraint filters, and the AFTA-signed receipt.
|
|
49
|
+
# Needs a token (see the payment quickstart below).
|
|
50
|
+
tf_paid = TensorFeed(token="YOUR_TOKEN")
|
|
51
|
+
verdict = tf_paid.route_verdict(task="code", max_latency_p95_ms=1500)
|
|
52
|
+
print(verdict["verdict"], verdict["runners_up"])
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Pass `model="<id-or-name>"` instead of `task=` to score one model. Both methods accept exactly one of the two selectors.
|
|
56
|
+
|
|
57
|
+
### The agent path (MCP)
|
|
58
|
+
|
|
59
|
+
With the [`@tensorfeed/mcp-server`](https://www.npmjs.com/package/@tensorfeed/mcp-server) MCP server installed, an agent gets two tools: `route_verdict_preview` (free, the pick plus reasoning) and `route_verdict` (1 credit, ranked runners-up, constraint filters, and the AFTA-signed receipt it can audit).
|
|
60
|
+
|
|
61
|
+
### Why it matters
|
|
62
|
+
|
|
63
|
+
Models, prices, and latency move week to week. Route Verdict is one signed call an agent can act on now and later prove why it routed the way it did, without rebuilding the comparison from scratch each time.
|
|
64
|
+
|
|
7
65
|
## Install
|
|
8
66
|
|
|
9
67
|
```bash
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "tensorfeed"
|
|
7
|
-
version = "2.
|
|
7
|
+
version = "2.2.0"
|
|
8
8
|
description = "Python SDK for the TensorFeed.ai API: AI news, status, model pricing, premium routing + history series + webhook watches, LangChain/CrewAI tool factories, agent payments via USDC on Base (optional web3 auto-send)"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
@@ -243,14 +243,15 @@ def format_compare_models(r: dict[str, Any]) -> str:
|
|
|
243
243
|
models = r.get("models", []) or []
|
|
244
244
|
lines = [f"Compared {len(models)} model(s):"]
|
|
245
245
|
for m in models[:5]:
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
lines.append(f" {
|
|
246
|
+
if m.get("matched") is False:
|
|
247
|
+
label = m.get("query") or m.get("name") or m.get("id") or "?"
|
|
248
|
+
lines.append(f" {label}: (no match)")
|
|
249
249
|
continue
|
|
250
|
+
name = m.get("name") or m.get("id") or "?"
|
|
250
251
|
pricing = m.get("pricing", {}) or {}
|
|
251
252
|
lines.append(
|
|
252
|
-
f" {name}: in {pricing.get('
|
|
253
|
-
f"out {pricing.get('
|
|
253
|
+
f" {name}: in {pricing.get('input', '?')} / "
|
|
254
|
+
f"out {pricing.get('output', '?')} per 1M, "
|
|
254
255
|
f"ctx {m.get('context_window', '?')}"
|
|
255
256
|
)
|
|
256
257
|
rankings = r.get("rankings", {}) or {}
|
|
@@ -266,7 +267,7 @@ def format_cost_projection(r: dict[str, Any]) -> str:
|
|
|
266
267
|
lines = ["Cost projection (per model):"]
|
|
267
268
|
for p in proj[:10]:
|
|
268
269
|
name = p.get("model", "?")
|
|
269
|
-
monthly = p.get("
|
|
270
|
+
monthly = p.get("monthly_total")
|
|
270
271
|
lines.append(f" {name}: ~{monthly}/month")
|
|
271
272
|
ranked = r.get("ranked_cheapest_monthly", []) or []
|
|
272
273
|
if ranked:
|
|
@@ -18,7 +18,7 @@ from typing import Any # noqa: F401 (re-exported by purchase_credits return ty
|
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
DEFAULT_BASE_URL = "https://tensorfeed.ai/api"
|
|
21
|
-
DEFAULT_USER_AGENT = "TensorFeed-SDK-Python/
|
|
21
|
+
DEFAULT_USER_AGENT = "TensorFeed-SDK-Python/2.2"
|
|
22
22
|
|
|
23
23
|
|
|
24
24
|
class TensorFeedError(Exception):
|
|
@@ -554,6 +554,39 @@ class TensorFeed:
|
|
|
554
554
|
min_quality=min_quality,
|
|
555
555
|
)
|
|
556
556
|
|
|
557
|
+
# ── Free: route verdict preview (rate-limited) ─────────────────
|
|
558
|
+
|
|
559
|
+
def route_verdict_preview(
|
|
560
|
+
self,
|
|
561
|
+
*,
|
|
562
|
+
task: str | None = None,
|
|
563
|
+
model: str | None = None,
|
|
564
|
+
) -> dict[str, Any]:
|
|
565
|
+
"""The signed Route Verdict, free preview. 10 calls/day per IP.
|
|
566
|
+
|
|
567
|
+
Route Verdict is TensorFeed's single signed model-routing
|
|
568
|
+
decision: the one best model to use right now plus the reasoning,
|
|
569
|
+
fusing live pricing, contamination-discounted benchmark
|
|
570
|
+
capability, real production usage, measured p95 latency probes,
|
|
571
|
+
live incident state, and deprecation flags into one ranked
|
|
572
|
+
answer. This free preview returns the top verdict only (no
|
|
573
|
+
runners-up and no AFTA receipt), so an agent can taste the shape
|
|
574
|
+
before paying. For ranked runners-up, the constraint filters, and
|
|
575
|
+
the AFTA-signed receipt, use ``route_verdict()`` with credits.
|
|
576
|
+
|
|
577
|
+
Exactly one of ``task`` or ``model`` is required.
|
|
578
|
+
|
|
579
|
+
Args:
|
|
580
|
+
task: code, reasoning, creative, or general. The task to
|
|
581
|
+
route for.
|
|
582
|
+
model: a model id or display name (e.g. "claude-opus-4-7")
|
|
583
|
+
to score a single model instead of routing a task.
|
|
584
|
+
|
|
585
|
+
Raises:
|
|
586
|
+
RateLimited: after 10 free preview calls per UTC day from an IP
|
|
587
|
+
"""
|
|
588
|
+
return self._get("/preview/route-verdict", task=task, model=model)
|
|
589
|
+
|
|
557
590
|
# ── Payment flow ───────────────────────────────────────────────
|
|
558
591
|
|
|
559
592
|
def payment_info(self) -> dict[str, Any]:
|
|
@@ -755,6 +788,76 @@ class TensorFeed:
|
|
|
755
788
|
"GET", "/premium/routing", params=params, require_token=True
|
|
756
789
|
)
|
|
757
790
|
|
|
791
|
+
# ── Paid: route verdict (Tier 1, 1 credit/call) ────────────────
|
|
792
|
+
|
|
793
|
+
def route_verdict(
|
|
794
|
+
self,
|
|
795
|
+
*,
|
|
796
|
+
task: str | None = None,
|
|
797
|
+
model: str | None = None,
|
|
798
|
+
max_latency_p95_ms: float | None = None,
|
|
799
|
+
budget: float | None = None,
|
|
800
|
+
min_quality: float | None = None,
|
|
801
|
+
require_operational: bool | None = None,
|
|
802
|
+
exclude_deprecated: bool | None = None,
|
|
803
|
+
) -> dict[str, Any]:
|
|
804
|
+
"""The signed Route Verdict in full. 1 credit per call.
|
|
805
|
+
|
|
806
|
+
The decision layer on top of the free ``route_verdict_preview()``
|
|
807
|
+
taste. Returns the single best model to use right now plus ranked
|
|
808
|
+
runners-up, fusing live pricing, contamination-discounted
|
|
809
|
+
benchmark capability, real production usage, measured p95 latency
|
|
810
|
+
from the active probes, live incident-triage operational state,
|
|
811
|
+
and model deprecation flags into one verdict, with an AFTA-signed
|
|
812
|
+
receipt over the exact inputs so the routing decision is
|
|
813
|
+
cryptographically attestable.
|
|
814
|
+
|
|
815
|
+
Requires a token from ``confirm()`` or passed to the constructor.
|
|
816
|
+
Exactly one of ``task`` or ``model`` is required.
|
|
817
|
+
|
|
818
|
+
Args:
|
|
819
|
+
task: code, reasoning, creative, or general. The task to
|
|
820
|
+
route for.
|
|
821
|
+
model: a model id or display name to score a single model
|
|
822
|
+
instead of routing a task.
|
|
823
|
+
max_latency_p95_ms: drop candidates whose measured p95
|
|
824
|
+
exceeds this many milliseconds.
|
|
825
|
+
budget: optional max blended USD per 1M tokens.
|
|
826
|
+
min_quality: optional minimum quality score in [0, 1].
|
|
827
|
+
require_operational: default True server-side; drop candidates
|
|
828
|
+
known to be down or in failover.
|
|
829
|
+
exclude_deprecated: default True server-side; drop matched
|
|
830
|
+
deprecated or sunsetted models.
|
|
831
|
+
|
|
832
|
+
Returns:
|
|
833
|
+
Dict with ``query``, ``verdict``, ``runners_up``, ``trust``,
|
|
834
|
+
``filters_applied``, ``data_freshness``, ``claim``, ``notes``,
|
|
835
|
+
``attribution``, the AFTA ``receipt``, and ``billing``.
|
|
836
|
+
|
|
837
|
+
Raises:
|
|
838
|
+
ValueError: if no token is set on the client
|
|
839
|
+
PaymentRequired: if the token has insufficient credits
|
|
840
|
+
"""
|
|
841
|
+
self._require_token("route_verdict")
|
|
842
|
+
params: dict[str, Any] = {}
|
|
843
|
+
if task is not None:
|
|
844
|
+
params["task"] = task
|
|
845
|
+
if model is not None:
|
|
846
|
+
params["model"] = model
|
|
847
|
+
if max_latency_p95_ms is not None:
|
|
848
|
+
params["max_latency_p95_ms"] = max_latency_p95_ms
|
|
849
|
+
if budget is not None:
|
|
850
|
+
params["budget"] = budget
|
|
851
|
+
if min_quality is not None:
|
|
852
|
+
params["min_quality"] = min_quality
|
|
853
|
+
if require_operational is not None:
|
|
854
|
+
params["require_operational"] = "true" if require_operational else "false"
|
|
855
|
+
if exclude_deprecated is not None:
|
|
856
|
+
params["exclude_deprecated"] = "true" if exclude_deprecated else "false"
|
|
857
|
+
return self._request(
|
|
858
|
+
"GET", "/premium/route-verdict", params=params, require_token=True
|
|
859
|
+
)
|
|
860
|
+
|
|
758
861
|
# ── Paid: history series (Tier 1, 1 credit/call) ───────────────
|
|
759
862
|
|
|
760
863
|
def _require_token(self, name: str) -> None:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tensorfeed
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.2.0
|
|
4
4
|
Summary: Python SDK for the TensorFeed.ai API: AI news, status, model pricing, premium routing + history series + webhook watches, LangChain/CrewAI tool factories, agent payments via USDC on Base (optional web3 auto-send)
|
|
5
5
|
Author-email: "TensorFeed.ai" <feedback@tensorfeed.ai>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -37,6 +37,64 @@ Python SDK for the [TensorFeed.ai](https://tensorfeed.ai) API.
|
|
|
37
37
|
|
|
38
38
|
Free endpoints (news, status, models, benchmarks, history, routing preview) need no auth. The premium tier (top-N routing, plus more endpoints landing later) is paid via USDC on Base. No accounts, no API keys, no traditional payment processors.
|
|
39
39
|
|
|
40
|
+
## Featured: Route Verdict
|
|
41
|
+
|
|
42
|
+
The single best model to use right now, as one signed call. Route Verdict fuses live pricing, contamination-discounted benchmark capability, real production usage, measured p95 latency probes, live incident state, and deprecation flags into one ranked decision, with an AFTA-signed receipt over the exact inputs. Instead of stitching together pricing pages, benchmark leaderboards, status dashboards, and your own latency tests, you get a current, defensible routing answer in one request.
|
|
43
|
+
|
|
44
|
+
### Zero install, no key, one command (works today)
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
curl -s -A "tensorfeed-cc-quickstart" "https://tensorfeed.ai/api/preview/route-verdict?task=code"
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Swap `task` for `reasoning`, `creative`, or `general`, or pass `?model=<id-or-name>` to score a specific model. The free preview is 10 calls per day per IP, no token. Abridged real response:
|
|
51
|
+
|
|
52
|
+
```json
|
|
53
|
+
{
|
|
54
|
+
"verdict": {
|
|
55
|
+
"rank": 1,
|
|
56
|
+
"model": { "name": "Gemini 2.5 Pro", "provider": "google" },
|
|
57
|
+
"pricing": { "blended": 5.625, "unit": "per 1M tokens" },
|
|
58
|
+
"latency": { "measured_p95_ms": 1223, "source": "measured_probe" },
|
|
59
|
+
"operational": { "ok": true, "status": "operational" },
|
|
60
|
+
"composite_score": 0.8449,
|
|
61
|
+
"why": "code quality 0.6498 after trust discount; corroborated by real usage (rank 5, 6.5% share, flat); measured p95 1223 ms; operational; blended $5.625 / 1M"
|
|
62
|
+
},
|
|
63
|
+
"rate_limit": { "limit": 10, "remaining": 9, "scope": "per IP per UTC day" },
|
|
64
|
+
"upgrade": { "premium_endpoint": "/api/premium/route-verdict", "adds": ["runners_up", "AFTA-signed receipt", "filter params", "no rate limit"] }
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
The premium version (`/api/premium/route-verdict`, 1 credit) adds ranked runners-up, the constraint filters (`max_latency_p95_ms`, `budget`, `min_quality`, `require_operational`, `exclude_deprecated`), the AFTA-signed receipt, and no rate limit. It needs a token; credits come from [tensorfeed.ai/developers/agent-payments](https://tensorfeed.ai/developers/agent-payments).
|
|
69
|
+
|
|
70
|
+
### Native SDK call
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
from tensorfeed import TensorFeed
|
|
74
|
+
|
|
75
|
+
tf = TensorFeed()
|
|
76
|
+
|
|
77
|
+
# Free preview: the top verdict only, no token. 10 calls/day per IP.
|
|
78
|
+
preview = tf.route_verdict_preview(task="code")
|
|
79
|
+
print(preview["verdict"]["model"]["name"], preview["verdict"]["why"])
|
|
80
|
+
|
|
81
|
+
# Premium: runners-up, constraint filters, and the AFTA-signed receipt.
|
|
82
|
+
# Needs a token (see the payment quickstart below).
|
|
83
|
+
tf_paid = TensorFeed(token="YOUR_TOKEN")
|
|
84
|
+
verdict = tf_paid.route_verdict(task="code", max_latency_p95_ms=1500)
|
|
85
|
+
print(verdict["verdict"], verdict["runners_up"])
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Pass `model="<id-or-name>"` instead of `task=` to score one model. Both methods accept exactly one of the two selectors.
|
|
89
|
+
|
|
90
|
+
### The agent path (MCP)
|
|
91
|
+
|
|
92
|
+
With the [`@tensorfeed/mcp-server`](https://www.npmjs.com/package/@tensorfeed/mcp-server) MCP server installed, an agent gets two tools: `route_verdict_preview` (free, the pick plus reasoning) and `route_verdict` (1 credit, ranked runners-up, constraint filters, and the AFTA-signed receipt it can audit).
|
|
93
|
+
|
|
94
|
+
### Why it matters
|
|
95
|
+
|
|
96
|
+
Models, prices, and latency move week to week. Route Verdict is one signed call an agent can act on now and later prove why it routed the way it did, without rebuilding the comparison from scratch each time.
|
|
97
|
+
|
|
40
98
|
## Install
|
|
41
99
|
|
|
42
100
|
```bash
|
|
@@ -32,10 +32,10 @@ class SafeCallBehavior(unittest.TestCase):
|
|
|
32
32
|
|
|
33
33
|
def test_payment_required_appends_credits_hint(self):
|
|
34
34
|
def boom():
|
|
35
|
-
raise PaymentRequired(402, {"credits_required":
|
|
35
|
+
raise PaymentRequired(402, {"credits_required": 1})
|
|
36
36
|
|
|
37
37
|
out = pt.safe_call(boom, lambda r: "unreachable")
|
|
38
|
-
self.assertIn("
|
|
38
|
+
self.assertIn("1 credit", out)
|
|
39
39
|
|
|
40
40
|
def test_missing_token_value_error_returns_guidance(self):
|
|
41
41
|
def boom():
|
|
@@ -119,12 +119,15 @@ class Formatters(unittest.TestCase):
|
|
|
119
119
|
]
|
|
120
120
|
},
|
|
121
121
|
"tensorfeed_compare_models": {
|
|
122
|
-
"models": [
|
|
123
|
-
|
|
122
|
+
"models": [
|
|
123
|
+
{"name": "A", "pricing": {"input": 1, "output": 2, "blended": 1.5},
|
|
124
|
+
"context_window": 200000},
|
|
125
|
+
{"matched": False, "query": "ghost-model", "reason": "model_not_found"},
|
|
126
|
+
],
|
|
124
127
|
"rankings": {"cheapest_blended": "A"},
|
|
125
128
|
},
|
|
126
129
|
"tensorfeed_cost_projection": {
|
|
127
|
-
"projections": [{"model": "A", "
|
|
130
|
+
"projections": [{"model": "A", "monthly_total": 12.5}],
|
|
128
131
|
"ranked_cheapest_monthly": [{"model": "A"}],
|
|
129
132
|
},
|
|
130
133
|
"tensorfeed_news_search": {
|
|
@@ -155,6 +158,26 @@ class Formatters(unittest.TestCase):
|
|
|
155
158
|
out = pt.format_status_leaderboard({"entries": [], "error": "no_data"})
|
|
156
159
|
self.assertIn("no_data", out)
|
|
157
160
|
|
|
161
|
+
def test_cost_projection_reads_monthly_total(self):
|
|
162
|
+
out = pt.format_cost_projection(
|
|
163
|
+
{"projections": [{"model": "A", "monthly_total": 12.5}]}
|
|
164
|
+
)
|
|
165
|
+
self.assertIn("12.5", out)
|
|
166
|
+
self.assertNotIn("None", out)
|
|
167
|
+
|
|
168
|
+
def test_model_comparison_pricing_fields_and_no_match(self):
|
|
169
|
+
out = pt.format_compare_models(
|
|
170
|
+
{
|
|
171
|
+
"models": [
|
|
172
|
+
{"name": "A", "pricing": {"input": 1, "output": 2}, "context_window": 200000},
|
|
173
|
+
{"matched": False, "query": "ghost-model", "reason": "model_not_found"},
|
|
174
|
+
]
|
|
175
|
+
}
|
|
176
|
+
)
|
|
177
|
+
self.assertIn("in 1 / out 2", out)
|
|
178
|
+
self.assertIn("ghost-model: (no match)", out)
|
|
179
|
+
self.assertNotIn("?", out)
|
|
180
|
+
|
|
158
181
|
def test_names_and_descriptions_and_formatters_align(self):
|
|
159
182
|
self.assertEqual(set(pt.DESCRIPTIONS), set(pt.FORMATTERS))
|
|
160
183
|
self.assertEqual(set(pt.PREMIUM_TOOL_NAMES), set(pt.DESCRIPTIONS))
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
"""Tests for the native Route Verdict client methods.
|
|
2
|
+
|
|
3
|
+
Network-free. Patches urllib.request.urlopen so each method runs through
|
|
4
|
+
the real client plumbing (URL building, auth header logic, param
|
|
5
|
+
serialization) without a socket. Asserts that:
|
|
6
|
+
|
|
7
|
+
- route_verdict_preview hits /preview/route-verdict with NO auth header
|
|
8
|
+
- route_verdict hits /premium/route-verdict WITH the bearer auth header
|
|
9
|
+
- optional params serialize only when provided
|
|
10
|
+
- exactly the task / model selector is forwarded
|
|
11
|
+
|
|
12
|
+
Run from sdk/python::
|
|
13
|
+
|
|
14
|
+
python -m unittest discover -s tests -t .
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import io
|
|
20
|
+
import json
|
|
21
|
+
import unittest
|
|
22
|
+
import urllib.parse
|
|
23
|
+
import urllib.request
|
|
24
|
+
from typing import Any
|
|
25
|
+
from unittest import mock
|
|
26
|
+
|
|
27
|
+
from tensorfeed import TensorFeed
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _capture(client: TensorFeed) -> dict[str, Any]:
|
|
31
|
+
"""Patch urlopen, return a dict that the patched call fills in.
|
|
32
|
+
|
|
33
|
+
The fake urlopen records the outgoing Request (full URL and headers)
|
|
34
|
+
and returns a minimal JSON body so the method completes normally.
|
|
35
|
+
"""
|
|
36
|
+
captured: dict[str, Any] = {}
|
|
37
|
+
|
|
38
|
+
class _FakeResp:
|
|
39
|
+
def __init__(self, body: bytes) -> None:
|
|
40
|
+
self._body = body
|
|
41
|
+
|
|
42
|
+
def read(self) -> bytes:
|
|
43
|
+
return self._body
|
|
44
|
+
|
|
45
|
+
def __enter__(self) -> "_FakeResp":
|
|
46
|
+
return self
|
|
47
|
+
|
|
48
|
+
def __exit__(self, *exc: object) -> None:
|
|
49
|
+
return None
|
|
50
|
+
|
|
51
|
+
def fake_urlopen(req: urllib.request.Request, timeout: float = 0.0) -> _FakeResp:
|
|
52
|
+
full_url = req.full_url
|
|
53
|
+
parsed = urllib.parse.urlparse(full_url)
|
|
54
|
+
captured["url"] = full_url
|
|
55
|
+
captured["path"] = parsed.path
|
|
56
|
+
captured["params"] = dict(urllib.parse.parse_qsl(parsed.query))
|
|
57
|
+
# Header keys are normalized to Capitalized form by urllib.
|
|
58
|
+
captured["headers"] = {k.lower(): v for k, v in req.header_items()}
|
|
59
|
+
captured["method"] = req.get_method()
|
|
60
|
+
return _FakeResp(io.BytesIO(b'{"ok": true}').read())
|
|
61
|
+
|
|
62
|
+
client._patch = mock.patch.object( # type: ignore[attr-defined]
|
|
63
|
+
urllib.request, "urlopen", side_effect=fake_urlopen
|
|
64
|
+
)
|
|
65
|
+
client._patch.start() # type: ignore[attr-defined]
|
|
66
|
+
return captured
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class RouteVerdictPreview(unittest.TestCase):
|
|
70
|
+
def setUp(self) -> None:
|
|
71
|
+
self.tf = TensorFeed() # no token: free preview
|
|
72
|
+
self.captured = _capture(self.tf)
|
|
73
|
+
|
|
74
|
+
def tearDown(self) -> None:
|
|
75
|
+
self.tf._patch.stop() # type: ignore[attr-defined]
|
|
76
|
+
|
|
77
|
+
def test_preview_hits_free_path_with_task(self) -> None:
|
|
78
|
+
out = self.tf.route_verdict_preview(task="code")
|
|
79
|
+
self.assertEqual(out, {"ok": True})
|
|
80
|
+
self.assertEqual(self.captured["path"], "/api/preview/route-verdict")
|
|
81
|
+
self.assertEqual(self.captured["params"], {"task": "code"})
|
|
82
|
+
|
|
83
|
+
def test_preview_has_no_auth_header(self) -> None:
|
|
84
|
+
# Even if a token is present, the free preview must not advertise it.
|
|
85
|
+
tf = TensorFeed(token="tok_secret")
|
|
86
|
+
captured = _capture(tf)
|
|
87
|
+
try:
|
|
88
|
+
tf.route_verdict_preview(model="claude-opus-4-7")
|
|
89
|
+
finally:
|
|
90
|
+
tf._patch.stop() # type: ignore[attr-defined]
|
|
91
|
+
self.assertEqual(captured["path"], "/api/preview/route-verdict")
|
|
92
|
+
self.assertEqual(captured["params"], {"model": "claude-opus-4-7"})
|
|
93
|
+
self.assertNotIn("authorization", captured["headers"])
|
|
94
|
+
|
|
95
|
+
def test_preview_omits_unset_selector(self) -> None:
|
|
96
|
+
self.tf.route_verdict_preview(task="reasoning")
|
|
97
|
+
self.assertNotIn("model", self.captured["params"])
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class RouteVerdictPremium(unittest.TestCase):
|
|
101
|
+
def setUp(self) -> None:
|
|
102
|
+
self.tf = TensorFeed(token="tok_abc123")
|
|
103
|
+
self.captured = _capture(self.tf)
|
|
104
|
+
|
|
105
|
+
def tearDown(self) -> None:
|
|
106
|
+
self.tf._patch.stop() # type: ignore[attr-defined]
|
|
107
|
+
|
|
108
|
+
def test_premium_hits_paid_path_with_auth(self) -> None:
|
|
109
|
+
out = self.tf.route_verdict(task="code")
|
|
110
|
+
self.assertEqual(out, {"ok": True})
|
|
111
|
+
self.assertEqual(self.captured["path"], "/api/premium/route-verdict")
|
|
112
|
+
self.assertEqual(self.captured["headers"]["authorization"], "Bearer tok_abc123")
|
|
113
|
+
self.assertEqual(self.captured["params"], {"task": "code"})
|
|
114
|
+
|
|
115
|
+
def test_premium_serializes_optional_params(self) -> None:
|
|
116
|
+
self.tf.route_verdict(
|
|
117
|
+
task="code",
|
|
118
|
+
max_latency_p95_ms=1500,
|
|
119
|
+
budget=5.0,
|
|
120
|
+
min_quality=0.6,
|
|
121
|
+
require_operational=True,
|
|
122
|
+
exclude_deprecated=False,
|
|
123
|
+
)
|
|
124
|
+
params = self.captured["params"]
|
|
125
|
+
self.assertEqual(params["task"], "code")
|
|
126
|
+
self.assertEqual(params["max_latency_p95_ms"], "1500")
|
|
127
|
+
self.assertEqual(params["budget"], "5.0")
|
|
128
|
+
self.assertEqual(params["min_quality"], "0.6")
|
|
129
|
+
self.assertEqual(params["require_operational"], "true")
|
|
130
|
+
self.assertEqual(params["exclude_deprecated"], "false")
|
|
131
|
+
|
|
132
|
+
def test_premium_omits_unset_optional_params(self) -> None:
|
|
133
|
+
self.tf.route_verdict(model="gpt-5")
|
|
134
|
+
params = self.captured["params"]
|
|
135
|
+
self.assertEqual(params, {"model": "gpt-5"})
|
|
136
|
+
for key in (
|
|
137
|
+
"max_latency_p95_ms",
|
|
138
|
+
"budget",
|
|
139
|
+
"min_quality",
|
|
140
|
+
"require_operational",
|
|
141
|
+
"exclude_deprecated",
|
|
142
|
+
):
|
|
143
|
+
self.assertNotIn(key, params)
|
|
144
|
+
|
|
145
|
+
def test_premium_requires_token(self) -> None:
|
|
146
|
+
tf = TensorFeed() # no token
|
|
147
|
+
with self.assertRaises(ValueError):
|
|
148
|
+
tf.route_verdict(task="code")
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
if __name__ == "__main__":
|
|
152
|
+
unittest.main()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|