tensorfeed 2.0.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.0.0 → tensorfeed-2.2.0}/PKG-INFO +88 -2
- {tensorfeed-2.0.0 → tensorfeed-2.2.0}/README.md +86 -0
- {tensorfeed-2.0.0 → tensorfeed-2.2.0}/pyproject.toml +2 -2
- {tensorfeed-2.0.0 → tensorfeed-2.2.0}/tensorfeed/__init__.py +1 -1
- tensorfeed-2.2.0/tensorfeed/_premium_tools.py +342 -0
- {tensorfeed-2.0.0 → tensorfeed-2.2.0}/tensorfeed/client.py +104 -1
- {tensorfeed-2.0.0 → tensorfeed-2.2.0}/tensorfeed/crewai.py +137 -3
- {tensorfeed-2.0.0 → tensorfeed-2.2.0}/tensorfeed/langchain.py +179 -4
- {tensorfeed-2.0.0 → tensorfeed-2.2.0}/tensorfeed.egg-info/PKG-INFO +88 -2
- {tensorfeed-2.0.0 → tensorfeed-2.2.0}/tensorfeed.egg-info/SOURCES.txt +4 -1
- tensorfeed-2.2.0/tests/test_premium_tools.py +236 -0
- tensorfeed-2.2.0/tests/test_route_verdict.py +152 -0
- {tensorfeed-2.0.0 → tensorfeed-2.2.0}/setup.cfg +0 -0
- {tensorfeed-2.0.0 → tensorfeed-2.2.0}/tensorfeed/llamaindex.py +0 -0
- {tensorfeed-2.0.0 → tensorfeed-2.2.0}/tensorfeed/web3_signer.py +0 -0
- {tensorfeed-2.0.0 → tensorfeed-2.2.0}/tensorfeed.egg-info/dependency_links.txt +0 -0
- {tensorfeed-2.0.0 → tensorfeed-2.2.0}/tensorfeed.egg-info/requires.txt +0 -0
- {tensorfeed-2.0.0 → tensorfeed-2.2.0}/tensorfeed.egg-info/top_level.txt +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tensorfeed
|
|
3
|
-
Version: 2.
|
|
4
|
-
Summary: Python SDK for the TensorFeed.ai API: AI news, status, model pricing, premium routing + history series + webhook watches, agent payments via USDC on Base (optional web3 auto-send)
|
|
3
|
+
Version: 2.2.0
|
|
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
|
|
7
7
|
Project-URL: Homepage, https://tensorfeed.ai
|
|
@@ -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
|
|
@@ -197,6 +255,34 @@ print(tf.balance())
|
|
|
197
255
|
rec = tf.routing(task="code")
|
|
198
256
|
```
|
|
199
257
|
|
|
258
|
+
## Framework Tools (LangChain, CrewAI)
|
|
259
|
+
|
|
260
|
+
Drop TensorFeed into an agent framework as ready-made tools.
|
|
261
|
+
|
|
262
|
+
```bash
|
|
263
|
+
pip install 'tensorfeed[langchain]' # or 'tensorfeed[crewai]'
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
```python
|
|
267
|
+
from tensorfeed.langchain import tensorfeed_tools, tensorfeed_premium_tools
|
|
268
|
+
|
|
269
|
+
# Five free tools (news, status, attention, harnesses, routing preview)
|
|
270
|
+
free = tensorfeed_tools()
|
|
271
|
+
|
|
272
|
+
# Free plus the paid catalog (whats_new, full routing, compare models,
|
|
273
|
+
# cost projection, news search, provider deep dive, status leaderboard)
|
|
274
|
+
all_tools = tensorfeed_tools(token="YOUR_TOKEN", include_premium=True)
|
|
275
|
+
|
|
276
|
+
# Just the paid catalog
|
|
277
|
+
paid = tensorfeed_premium_tools(token="YOUR_TOKEN")
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
CrewAI is identical: `from tensorfeed.crewai import tensorfeed_tools, tensorfeed_premium_tools`.
|
|
281
|
+
|
|
282
|
+
The default `tensorfeed_tools()` stays free-only and unchanged, so existing code keeps working. Premium is opt-in via `include_premium=True`.
|
|
283
|
+
|
|
284
|
+
**Payment posture.** The premium tools never move funds. Attach them with or without a token: called without spendable credits they return a short, actionable guidance string (explaining that an operator provisions credits out of band) instead of paying or raising. There is no code path from a tool call to the wallet or the web3 signer. Credit purchases stay an explicit, per-action operator decision, by design. This is enforced in one shared module and covered by tests, including a static guard that fails if a future change introduces an autonomous-payment path.
|
|
285
|
+
|
|
200
286
|
## Error Handling
|
|
201
287
|
|
|
202
288
|
```python
|
|
@@ -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
|
|
@@ -164,6 +222,34 @@ print(tf.balance())
|
|
|
164
222
|
rec = tf.routing(task="code")
|
|
165
223
|
```
|
|
166
224
|
|
|
225
|
+
## Framework Tools (LangChain, CrewAI)
|
|
226
|
+
|
|
227
|
+
Drop TensorFeed into an agent framework as ready-made tools.
|
|
228
|
+
|
|
229
|
+
```bash
|
|
230
|
+
pip install 'tensorfeed[langchain]' # or 'tensorfeed[crewai]'
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
```python
|
|
234
|
+
from tensorfeed.langchain import tensorfeed_tools, tensorfeed_premium_tools
|
|
235
|
+
|
|
236
|
+
# Five free tools (news, status, attention, harnesses, routing preview)
|
|
237
|
+
free = tensorfeed_tools()
|
|
238
|
+
|
|
239
|
+
# Free plus the paid catalog (whats_new, full routing, compare models,
|
|
240
|
+
# cost projection, news search, provider deep dive, status leaderboard)
|
|
241
|
+
all_tools = tensorfeed_tools(token="YOUR_TOKEN", include_premium=True)
|
|
242
|
+
|
|
243
|
+
# Just the paid catalog
|
|
244
|
+
paid = tensorfeed_premium_tools(token="YOUR_TOKEN")
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
CrewAI is identical: `from tensorfeed.crewai import tensorfeed_tools, tensorfeed_premium_tools`.
|
|
248
|
+
|
|
249
|
+
The default `tensorfeed_tools()` stays free-only and unchanged, so existing code keeps working. Premium is opt-in via `include_premium=True`.
|
|
250
|
+
|
|
251
|
+
**Payment posture.** The premium tools never move funds. Attach them with or without a token: called without spendable credits they return a short, actionable guidance string (explaining that an operator provisions credits out of band) instead of paying or raising. There is no code path from a tool call to the wallet or the web3 signer. Credit purchases stay an explicit, per-action operator decision, by design. This is enforced in one shared module and covered by tests, including a static guard that fails if a future change introduces an autonomous-payment path.
|
|
252
|
+
|
|
167
253
|
## Error Handling
|
|
168
254
|
|
|
169
255
|
```python
|
|
@@ -4,8 +4,8 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "tensorfeed"
|
|
7
|
-
version = "2.
|
|
8
|
-
description = "Python SDK for the TensorFeed.ai API: AI news, status, model pricing, premium routing + history series + webhook watches, agent payments via USDC on Base (optional web3 auto-send)"
|
|
7
|
+
version = "2.2.0"
|
|
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"
|
|
11
11
|
requires-python = ">=3.9"
|
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
"""Shared premium-tool plumbing for the framework adapters.
|
|
2
|
+
|
|
3
|
+
Internal module. Powers ``tensorfeed.langchain.tensorfeed_premium_tools()``
|
|
4
|
+
and ``tensorfeed.crewai.tensorfeed_premium_tools()``. It is framework
|
|
5
|
+
agnostic: it owns the canonical tool descriptions, the result
|
|
6
|
+
formatters, and the one piece of behavior that must never drift between
|
|
7
|
+
frameworks, the payment posture.
|
|
8
|
+
|
|
9
|
+
Payment posture (load-bearing, do not weaken):
|
|
10
|
+
|
|
11
|
+
These tools never move funds. A premium call made without a valid
|
|
12
|
+
token returns a clear, actionable guidance string explaining how an
|
|
13
|
+
operator provisions credits out of band. The tool does not, and will
|
|
14
|
+
not, initiate a USDC transfer on its own. Autonomous payment from
|
|
15
|
+
inside a tool call is a deliberate non-feature: credit purchases stay
|
|
16
|
+
an explicit, per-action operator decision. Centralizing this here (and
|
|
17
|
+
testing it once) keeps every framework binding honest.
|
|
18
|
+
|
|
19
|
+
Each adapter writes its own thin, explicitly typed function or tool
|
|
20
|
+
class so the framework gets a good argument schema for the model, then
|
|
21
|
+
routes the call through ``safe_call`` with the shared formatter. The
|
|
22
|
+
descriptions are shared verbatim so LangChain and CrewAI surface
|
|
23
|
+
identical copy to the agent.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
from __future__ import annotations
|
|
27
|
+
|
|
28
|
+
from typing import Any, Callable
|
|
29
|
+
|
|
30
|
+
from .client import PaymentRequired, RateLimited, TensorFeedError
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
# The exact, actionable message an agent sees when a premium tool is
|
|
34
|
+
# called without spendable credits. It tells the model what is true
|
|
35
|
+
# (this needs credits), what will not happen (no automatic payment),
|
|
36
|
+
# and what the path is (an operator provisions a token out of band).
|
|
37
|
+
PREMIUM_PAYMENT_GUIDANCE = (
|
|
38
|
+
"This is a paid TensorFeed endpoint (1 credit) and no spendable "
|
|
39
|
+
"credit token is available. This tool will not buy credits or move "
|
|
40
|
+
"any funds on its own, by design. To enable it, an operator must "
|
|
41
|
+
"provision credits out of band (buy_credits() then confirm() with a "
|
|
42
|
+
"USDC payment on Base, or the documented x402 flow) and pass the "
|
|
43
|
+
"resulting token when constructing the tools, for example "
|
|
44
|
+
"tensorfeed_premium_tools(token='...'). Until then, prefer the free "
|
|
45
|
+
"TensorFeed tools (news, status, attention, harnesses, routing "
|
|
46
|
+
"preview) for this question."
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _billing_line(result: dict[str, Any]) -> str:
|
|
51
|
+
"""One-line cost footer if the response carries billing metadata."""
|
|
52
|
+
billing = result.get("billing")
|
|
53
|
+
if not isinstance(billing, dict):
|
|
54
|
+
return ""
|
|
55
|
+
charged = billing.get("credits_charged")
|
|
56
|
+
remaining = billing.get("balance")
|
|
57
|
+
bits = []
|
|
58
|
+
if charged is not None:
|
|
59
|
+
bits.append(f"{charged} credit(s) charged")
|
|
60
|
+
if remaining is not None:
|
|
61
|
+
bits.append(f"{remaining} remaining")
|
|
62
|
+
return f"\n[{', '.join(bits)}]" if bits else ""
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def safe_call(
|
|
66
|
+
invoke: Callable[[], dict[str, Any]],
|
|
67
|
+
formatter: Callable[[dict[str, Any]], str],
|
|
68
|
+
) -> str:
|
|
69
|
+
"""Run a premium client call and return agent-readable text.
|
|
70
|
+
|
|
71
|
+
Never raises for the expected money and quota cases. A raised
|
|
72
|
+
exception inside an agent tool call usually aborts the agent or
|
|
73
|
+
produces an opaque trace; a structured guidance string lets the
|
|
74
|
+
model reason about what to do next (typically: tell the user it
|
|
75
|
+
needs credits, or fall back to a free tool).
|
|
76
|
+
|
|
77
|
+
* no token / token rejected (402): the payment guidance string
|
|
78
|
+
* rate limited (429): a short back-off message
|
|
79
|
+
* other API error: a concise one-line error
|
|
80
|
+
* success: the tool's own formatter output
|
|
81
|
+
|
|
82
|
+
The PaymentRequired and missing-token paths are exactly where an
|
|
83
|
+
autonomous-payment bug would hide. There is intentionally no code
|
|
84
|
+
path here that touches the wallet or the web3 signer.
|
|
85
|
+
"""
|
|
86
|
+
try:
|
|
87
|
+
result = invoke()
|
|
88
|
+
except PaymentRequired as e:
|
|
89
|
+
hint = ""
|
|
90
|
+
payload = getattr(e, "payload", None)
|
|
91
|
+
if isinstance(payload, dict):
|
|
92
|
+
need = payload.get("credits_required")
|
|
93
|
+
if need is not None:
|
|
94
|
+
hint = f" (endpoint requires {need} credit(s))"
|
|
95
|
+
return PREMIUM_PAYMENT_GUIDANCE + hint
|
|
96
|
+
except ValueError:
|
|
97
|
+
# client raises ValueError when no token is set on the client
|
|
98
|
+
return PREMIUM_PAYMENT_GUIDANCE
|
|
99
|
+
except RateLimited:
|
|
100
|
+
return (
|
|
101
|
+
"TensorFeed rate limit hit. Wait and retry, or reduce call "
|
|
102
|
+
"frequency. Free public endpoints allow 120 requests/minute "
|
|
103
|
+
"per IP; bearer tokens are exempt."
|
|
104
|
+
)
|
|
105
|
+
except TensorFeedError as e:
|
|
106
|
+
return f"TensorFeed API error {e.status_code}: {e}"
|
|
107
|
+
try:
|
|
108
|
+
return formatter(result) + _billing_line(result)
|
|
109
|
+
except Exception: # defensive: a shape change must not break the agent
|
|
110
|
+
return (
|
|
111
|
+
"TensorFeed returned a response in an unexpected shape. Raw "
|
|
112
|
+
f"top-level keys: {sorted(result) if isinstance(result, dict) else type(result).__name__}."
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
# ── Shared descriptions (identical copy across frameworks) ─────────────
|
|
117
|
+
#
|
|
118
|
+
# Every description states the credit cost up front so the model can
|
|
119
|
+
# weigh it before calling, and what the tool is uniquely good for so it
|
|
120
|
+
# is picked for the right question.
|
|
121
|
+
|
|
122
|
+
DESCRIPTIONS: dict[str, str] = {
|
|
123
|
+
"tensorfeed_whats_new": (
|
|
124
|
+
"PAID, 1 credit. The agent boot brief: a single curated summary "
|
|
125
|
+
"of AI model pricing changes, new and removed models, service "
|
|
126
|
+
"incidents that started or resolved, current operational counts, "
|
|
127
|
+
"and the top news headlines, over the last 1 to 7 days. Call "
|
|
128
|
+
"this once at startup instead of polling many free endpoints. "
|
|
129
|
+
"Inputs: days (1-7, default 1), news_limit (1-25, default 10)."
|
|
130
|
+
),
|
|
131
|
+
"tensorfeed_routing": (
|
|
132
|
+
"PAID, 1 credit. Full ranked model routing: the top-N AI models "
|
|
133
|
+
"for a task with a complete score breakdown (quality, "
|
|
134
|
+
"availability, cost, latency). Use this when the free top-1 "
|
|
135
|
+
"preview is not enough. Inputs: task (code|reasoning|creative|"
|
|
136
|
+
"general), budget (optional max blended USD per 1M tokens), "
|
|
137
|
+
"top_n (1-10, default 5)."
|
|
138
|
+
),
|
|
139
|
+
"tensorfeed_compare_models": (
|
|
140
|
+
"PAID, 1 credit. Side-by-side comparison of 2 to 5 AI models: "
|
|
141
|
+
"pricing, normalized benchmarks, live provider status, context "
|
|
142
|
+
"window, capabilities, recent news, plus rankings for cheapest "
|
|
143
|
+
"blended and most context. Input: ids (comma-separated model "
|
|
144
|
+
"ids or display names, 2 to 5)."
|
|
145
|
+
),
|
|
146
|
+
"tensorfeed_cost_projection": (
|
|
147
|
+
"PAID, 1 credit. Project the cost of a token-usage workload "
|
|
148
|
+
"across 1 to 10 models over day, week, month, and year, with a "
|
|
149
|
+
"cheapest-monthly ranking. Inputs: models (comma-separated), "
|
|
150
|
+
"input_tokens_per_day, output_tokens_per_day, horizon (optional)."
|
|
151
|
+
),
|
|
152
|
+
"tensorfeed_news_search": (
|
|
153
|
+
"PAID, 1 credit. Full-text search over the TensorFeed AI news "
|
|
154
|
+
"corpus with relevance scoring and a recency boost. Filter by "
|
|
155
|
+
"query, date range, provider, and category. Use this instead of "
|
|
156
|
+
"the free latest-news feed when you need to find specific "
|
|
157
|
+
"coverage. Inputs: q, from_date, to_date, provider, category, "
|
|
158
|
+
"limit (1-100, default 25)."
|
|
159
|
+
),
|
|
160
|
+
"tensorfeed_provider_deepdive": (
|
|
161
|
+
"PAID, 1 credit. One AI provider's full profile in a single "
|
|
162
|
+
"call: live status, every model with pricing and benchmarks "
|
|
163
|
+
"joined in, recent news, and agent-traffic attribution. Doing "
|
|
164
|
+
"this from free endpoints takes several round-trips and a "
|
|
165
|
+
"non-trivial join. Input: provider (id or display name)."
|
|
166
|
+
),
|
|
167
|
+
"tensorfeed_status_leaderboard": (
|
|
168
|
+
"PAID, 1 credit. Cross-provider AI uptime leaderboard over the "
|
|
169
|
+
"full 90-day retention window, with incident_count and "
|
|
170
|
+
"mttr_minutes per provider. Built for vendor selection and "
|
|
171
|
+
"post-incident review. Inputs: from_date, to_date (optional, "
|
|
172
|
+
"default last 30 days)."
|
|
173
|
+
),
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
PREMIUM_TOOL_NAMES: list[str] = list(DESCRIPTIONS)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
# ── Shared result formatters ───────────────────────────────────────────
|
|
180
|
+
#
|
|
181
|
+
# Compact, deterministic, defensive. They turn a response dict into a
|
|
182
|
+
# short text block an LLM can read. Lists are truncated. Missing keys
|
|
183
|
+
# degrade to a sane default rather than raising (safe_call also guards
|
|
184
|
+
# the formatter, this is belt and suspenders).
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def format_whats_new(r: dict[str, Any]) -> str:
|
|
188
|
+
w = r.get("window", {})
|
|
189
|
+
s = r.get("summary", {})
|
|
190
|
+
lines = [f"What's new ({w.get('days', '?')}d window):"]
|
|
191
|
+
pricing = r.get("pricing", {}) or {}
|
|
192
|
+
changed = pricing.get("changes", []) or []
|
|
193
|
+
added = pricing.get("new_models", []) or []
|
|
194
|
+
removed = pricing.get("removed_models", []) or []
|
|
195
|
+
lines.append(
|
|
196
|
+
f" pricing: {len(changed)} changes, {len(added)} new, "
|
|
197
|
+
f"{len(removed)} removed models"
|
|
198
|
+
)
|
|
199
|
+
for c in changed[:5]:
|
|
200
|
+
lines.append(
|
|
201
|
+
f" {c.get('model', '?')} {c.get('field', '')}: "
|
|
202
|
+
f"{c.get('from')} -> {c.get('to')}"
|
|
203
|
+
)
|
|
204
|
+
status = r.get("status", {}) or {}
|
|
205
|
+
incidents = status.get("incidents", []) or []
|
|
206
|
+
lines.append(f" status: {len(incidents)} incident(s) in window")
|
|
207
|
+
for i in incidents[:3]:
|
|
208
|
+
lines.append(
|
|
209
|
+
f" {i.get('provider', '?')}: {i.get('state', i.get('status', ''))}"
|
|
210
|
+
)
|
|
211
|
+
news = r.get("news", []) or []
|
|
212
|
+
lines.append(f" news: {len(news)} headlines")
|
|
213
|
+
for n in news[:5]:
|
|
214
|
+
lines.append(f" - {n.get('title', '')} ({n.get('source', '')})")
|
|
215
|
+
if s:
|
|
216
|
+
lines.append(f" counts: {s}")
|
|
217
|
+
return "\n".join(lines)
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def format_routing(r: dict[str, Any]) -> str:
|
|
221
|
+
recs = r.get("recommendations") or r.get("recommendation") or []
|
|
222
|
+
if isinstance(recs, dict):
|
|
223
|
+
recs = [recs]
|
|
224
|
+
if not recs:
|
|
225
|
+
return "No routing recommendation returned."
|
|
226
|
+
lines = ["Model routing (ranked):"]
|
|
227
|
+
for x in recs[:10]:
|
|
228
|
+
model = x.get("model", {})
|
|
229
|
+
name = model.get("name") if isinstance(model, dict) else model
|
|
230
|
+
score = x.get("composite_score", x.get("score"))
|
|
231
|
+
try:
|
|
232
|
+
score_s = f"{float(score):.3f}"
|
|
233
|
+
except (TypeError, ValueError):
|
|
234
|
+
score_s = str(score)
|
|
235
|
+
lines.append(
|
|
236
|
+
f" #{x.get('rank', '?')} {name} score={score_s}"
|
|
237
|
+
f" provider={x.get('provider', model.get('provider') if isinstance(model, dict) else '')}"
|
|
238
|
+
)
|
|
239
|
+
return "\n".join(lines)
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def format_compare_models(r: dict[str, Any]) -> str:
|
|
243
|
+
models = r.get("models", []) or []
|
|
244
|
+
lines = [f"Compared {len(models)} model(s):"]
|
|
245
|
+
for m in models[:5]:
|
|
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
|
+
continue
|
|
250
|
+
name = m.get("name") or m.get("id") or "?"
|
|
251
|
+
pricing = m.get("pricing", {}) or {}
|
|
252
|
+
lines.append(
|
|
253
|
+
f" {name}: in {pricing.get('input', '?')} / "
|
|
254
|
+
f"out {pricing.get('output', '?')} per 1M, "
|
|
255
|
+
f"ctx {m.get('context_window', '?')}"
|
|
256
|
+
)
|
|
257
|
+
rankings = r.get("rankings", {}) or {}
|
|
258
|
+
if rankings.get("cheapest_blended"):
|
|
259
|
+
lines.append(f" cheapest blended: {rankings['cheapest_blended']}")
|
|
260
|
+
if rankings.get("most_context"):
|
|
261
|
+
lines.append(f" most context: {rankings['most_context']}")
|
|
262
|
+
return "\n".join(lines)
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def format_cost_projection(r: dict[str, Any]) -> str:
|
|
266
|
+
proj = r.get("projections", []) or []
|
|
267
|
+
lines = ["Cost projection (per model):"]
|
|
268
|
+
for p in proj[:10]:
|
|
269
|
+
name = p.get("model", "?")
|
|
270
|
+
monthly = p.get("monthly_total")
|
|
271
|
+
lines.append(f" {name}: ~{monthly}/month")
|
|
272
|
+
ranked = r.get("ranked_cheapest_monthly", []) or []
|
|
273
|
+
if ranked:
|
|
274
|
+
head = ranked[0]
|
|
275
|
+
name = head.get("model") if isinstance(head, dict) else head
|
|
276
|
+
lines.append(f" cheapest monthly: {name}")
|
|
277
|
+
return "\n".join(lines)
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def format_news_search(r: dict[str, Any]) -> str:
|
|
281
|
+
results = r.get("results", []) or []
|
|
282
|
+
lines = [
|
|
283
|
+
f"News search: {r.get('matched', len(results))} matched, "
|
|
284
|
+
f"showing {len(results)}"
|
|
285
|
+
]
|
|
286
|
+
for a in results[:10]:
|
|
287
|
+
lines.append(
|
|
288
|
+
f" - {a.get('title', '')} ({a.get('source', '')}, "
|
|
289
|
+
f"{a.get('published_at', '')}) {a.get('url', '')}"
|
|
290
|
+
)
|
|
291
|
+
return "\n".join(lines)
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def format_provider_deepdive(r: dict[str, Any]) -> str:
|
|
295
|
+
prov = r.get("provider", "?")
|
|
296
|
+
status = r.get("status", {})
|
|
297
|
+
st = status.get("status") if isinstance(status, dict) else status
|
|
298
|
+
models = r.get("models", []) or []
|
|
299
|
+
lines = [
|
|
300
|
+
f"{prov}: status={st}, {len(models)} model(s), "
|
|
301
|
+
f"{r.get('recent_news_count', 0)} recent news, "
|
|
302
|
+
f"{r.get('agent_traffic_24h', 0)} agent hits/24h"
|
|
303
|
+
]
|
|
304
|
+
for m in models[:6]:
|
|
305
|
+
lines.append(
|
|
306
|
+
f" {m.get('name', m.get('id', '?'))}: "
|
|
307
|
+
f"tier {m.get('tier', '?')}, ctx {m.get('context_window', '?')}"
|
|
308
|
+
)
|
|
309
|
+
for n in (r.get("recent_news", []) or [])[:5]:
|
|
310
|
+
lines.append(f" news: {n.get('title', '')} ({n.get('source', '')})")
|
|
311
|
+
return "\n".join(lines)
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
def format_status_leaderboard(r: dict[str, Any]) -> str:
|
|
315
|
+
entries = r.get("entries", []) or []
|
|
316
|
+
if not entries:
|
|
317
|
+
return (
|
|
318
|
+
"No leaderboard data for the requested window "
|
|
319
|
+
f"({r.get('error', 'no_data')})."
|
|
320
|
+
)
|
|
321
|
+
lines = ["AI provider uptime leaderboard:"]
|
|
322
|
+
for e in entries[:15]:
|
|
323
|
+
lines.append(
|
|
324
|
+
f" #{e.get('rank', '?')} {e.get('provider', '?')}: "
|
|
325
|
+
f"{e.get('uptime_pct', '?')}% uptime, "
|
|
326
|
+
f"{e.get('incident_count', 0)} incidents, "
|
|
327
|
+
f"MTTR {e.get('mttr_minutes', '?')}m"
|
|
328
|
+
)
|
|
329
|
+
return "\n".join(lines)
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
# Maps tool name -> (client method name, formatter). The adapters use
|
|
333
|
+
# this so the call target and the formatter never drift apart.
|
|
334
|
+
FORMATTERS: dict[str, Callable[[dict[str, Any]], str]] = {
|
|
335
|
+
"tensorfeed_whats_new": format_whats_new,
|
|
336
|
+
"tensorfeed_routing": format_routing,
|
|
337
|
+
"tensorfeed_compare_models": format_compare_models,
|
|
338
|
+
"tensorfeed_cost_projection": format_cost_projection,
|
|
339
|
+
"tensorfeed_news_search": format_news_search,
|
|
340
|
+
"tensorfeed_provider_deepdive": format_provider_deepdive,
|
|
341
|
+
"tensorfeed_status_leaderboard": format_status_leaderboard,
|
|
342
|
+
}
|