Upload SpecStream tool
Browse files- README.md +7 -5
- index.html +1 -19
- requirements.txt +1 -0
- tool.py +117 -0
README.md
CHANGED
|
@@ -1,10 +1,12 @@
|
|
| 1 |
---
|
| 2 |
-
title:
|
| 3 |
-
emoji: 😻
|
| 4 |
-
colorFrom: green
|
| 5 |
-
colorTo: green
|
| 6 |
sdk: static
|
| 7 |
pinned: false
|
|
|
|
|
|
|
|
|
|
| 8 |
---
|
| 9 |
|
| 10 |
-
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
+
title: SpecStream find web data
|
|
|
|
|
|
|
|
|
|
| 3 |
sdk: static
|
| 4 |
pinned: false
|
| 5 |
+
tags:
|
| 6 |
+
- smolagents
|
| 7 |
+
- tool
|
| 8 |
---
|
| 9 |
|
| 10 |
+
SpecStream reads the live web and hands back every match with its source, so an agent can ask for data no one sells in plain words. Tool one shows the reading and the maximum price, and runs only after the person approves that maximum. Tool two reads the rows with their source URLs. Set SPECSTREAM_API_KEY. Start at https://specstream.ai/?utm_source=smolagents
|
| 11 |
+
|
| 12 |
+
Load it with `Tool.from_hub("SpecStreamAI/specstream-find-web-data", trust_remote_code=True)`.
|
index.html
CHANGED
|
@@ -1,19 +1 @@
|
|
| 1 |
-
<!doctype html>
|
| 2 |
-
<html>
|
| 3 |
-
<head>
|
| 4 |
-
<meta charset="utf-8" />
|
| 5 |
-
<meta name="viewport" content="width=device-width" />
|
| 6 |
-
<title>My static Space</title>
|
| 7 |
-
<link rel="stylesheet" href="style.css" />
|
| 8 |
-
</head>
|
| 9 |
-
<body>
|
| 10 |
-
<div class="card">
|
| 11 |
-
<h1>Welcome to your static Space!</h1>
|
| 12 |
-
<p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
|
| 13 |
-
<p>
|
| 14 |
-
Also don't forget to check the
|
| 15 |
-
<a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
|
| 16 |
-
</p>
|
| 17 |
-
</div>
|
| 18 |
-
</body>
|
| 19 |
-
</html>
|
|
|
|
| 1 |
+
<!doctype html><html><head><meta charset="utf-8"><title>SpecStream find web data</title></head><body style="font-family:system-ui;max-width:40rem;margin:3rem auto;padding:0 1rem"><h1>SpecStream find web data</h1><p>SpecStream reads the live web and hands back every match with its source, so an agent can ask for data no one sells in plain words. Tool one shows the reading and the maximum price, and runs only after the person approves that maximum. Tool two reads the rows with their source URLs. Set SPECSTREAM_API_KEY. Start at https://specstream.ai/?utm_source=smolagents</p><p><code>Tool.from_hub("SpecStreamAI/specstream-find-web-data", trust_remote_code=True)</code></p></body></html>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
requirements.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
smolagents
|
tool.py
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import Any, Optional
|
| 2 |
+
from smolagents.tools import Tool
|
| 3 |
+
import json
|
| 4 |
+
import urllib
|
| 5 |
+
import os
|
| 6 |
+
|
| 7 |
+
class SpecStreamFindWebData(Tool):
|
| 8 |
+
name = "specstream_find_web_data"
|
| 9 |
+
description = "Reads a request for web data and returns how SpecStream understood it, plus the maximum price. Nothing runs and nothing is charged until the person approves that maximum. Step 1: call with request only, show the reading and the price to the person. Step 2: only after the person says yes, call again with quote_id and confirm_max_cents set to the quoted maximum in cents. That starts the work and returns a find_id; read the rows with the specstream_get_rows tool. Needs SPECSTREAM_API_KEY."
|
| 10 |
+
inputs = {'request': {'type': 'string', 'description': 'The data wanted, in plain words, for example: dentists in Austin with their phone numbers.'}, 'count': {'type': 'integer', 'description': 'How many results to price, 1 to 500. Leave empty for the default.', 'nullable': True}, 'quote_id': {'type': 'string', 'description': 'The quote_id from step 1. Only with confirm_max_cents, after the person approved.', 'nullable': True}, 'confirm_max_cents': {'type': 'integer', 'description': 'The maximum in cents from step 1, exactly as quoted. Set only after the person approved it.', 'nullable': True}}
|
| 11 |
+
output_type = "string"
|
| 12 |
+
|
| 13 |
+
def _call(self, method: str, path: str, body: dict | None = None) -> dict:
|
| 14 |
+
import json
|
| 15 |
+
import os
|
| 16 |
+
from urllib.error import HTTPError, URLError
|
| 17 |
+
from urllib.request import Request, urlopen
|
| 18 |
+
|
| 19 |
+
key = os.environ.get("SPECSTREAM_API_KEY", "").strip()
|
| 20 |
+
base = os.environ.get("SPECSTREAM_API_BASE", "https://api.specstream.ai").rstrip("/")
|
| 21 |
+
data = None if body is None else json.dumps(body).encode("utf-8")
|
| 22 |
+
req = Request(
|
| 23 |
+
base + path,
|
| 24 |
+
data=data,
|
| 25 |
+
method=method,
|
| 26 |
+
headers={
|
| 27 |
+
"Authorization": "Bearer " + key,
|
| 28 |
+
"Accept": "application/json",
|
| 29 |
+
"Content-Type": "application/json",
|
| 30 |
+
"User-Agent": "specstream-smolagents/0.1",
|
| 31 |
+
},
|
| 32 |
+
)
|
| 33 |
+
try:
|
| 34 |
+
with urlopen(req, timeout=120) as resp:
|
| 35 |
+
payload = json.loads(resp.read().decode("utf-8"))
|
| 36 |
+
except HTTPError as exc:
|
| 37 |
+
try:
|
| 38 |
+
payload = json.loads(exc.read().decode("utf-8"))
|
| 39 |
+
except ValueError:
|
| 40 |
+
payload = {}
|
| 41 |
+
error = payload.get("error") if isinstance(payload, dict) else None
|
| 42 |
+
message = error.get("message") if isinstance(error, dict) else None
|
| 43 |
+
raise RuntimeError(message or "SpecStream answered HTTP %d" % exc.code)
|
| 44 |
+
except (URLError, OSError):
|
| 45 |
+
raise RuntimeError("SpecStream could not be reached")
|
| 46 |
+
found = payload.get("data") if isinstance(payload, dict) else None
|
| 47 |
+
return found if isinstance(found, dict) else {}
|
| 48 |
+
|
| 49 |
+
def _dollars(self, cents: int, currency: str) -> str:
|
| 50 |
+
symbol = "$" if currency == "USD" else currency + " "
|
| 51 |
+
return "%s%.2f" % (symbol, cents / 100)
|
| 52 |
+
|
| 53 |
+
def forward(
|
| 54 |
+
self,
|
| 55 |
+
request: str,
|
| 56 |
+
count: int | None = None,
|
| 57 |
+
quote_id: str | None = None,
|
| 58 |
+
confirm_max_cents: int | None = None,
|
| 59 |
+
) -> str:
|
| 60 |
+
import os
|
| 61 |
+
from urllib.parse import quote as urlquote
|
| 62 |
+
|
| 63 |
+
if not os.environ.get("SPECSTREAM_API_KEY", "").strip():
|
| 64 |
+
return "Set SPECSTREAM_API_KEY to a SpecStream API key (https://specstream.ai/workspace?utm_source=smolagents)."
|
| 65 |
+
call = self._call
|
| 66 |
+
dollars = self._dollars
|
| 67 |
+
|
| 68 |
+
try:
|
| 69 |
+
if quote_id is None and confirm_max_cents is None:
|
| 70 |
+
reading = call("POST", "/v1/parse", {"q": request, "locale": "en-US"})
|
| 71 |
+
ast_hash = reading.get("ast_hash")
|
| 72 |
+
if not ast_hash:
|
| 73 |
+
return "SpecStream could not read that request. Name what you want and where."
|
| 74 |
+
sentence = reading.get("read_back") or reading.get("canonical_sentence") or ""
|
| 75 |
+
body = {"ast_hash": ast_hash, "operation": "find"}
|
| 76 |
+
if count is not None:
|
| 77 |
+
body["requested_units"] = count
|
| 78 |
+
quote = call("POST", "/v1/capacity/quotes", body)
|
| 79 |
+
if not quote.get("quote_id"):
|
| 80 |
+
return "SpecStream could not price this request yet."
|
| 81 |
+
cents = int(quote.get("maximum_cents") or 0)
|
| 82 |
+
money = dollars(cents, str(quote.get("currency") or "USD"))
|
| 83 |
+
return (
|
| 84 |
+
"SpecStream read it as: %s\n"
|
| 85 |
+
"Maximum price: %s. You pay for each result delivered; a search that finds nothing is free.\n"
|
| 86 |
+
"quote_id: %s\n"
|
| 87 |
+
"confirm_max_cents: %d\n"
|
| 88 |
+
"Nothing has run. Ask the person to approve %s. Only after a yes, call this tool again "
|
| 89 |
+
"with quote_id and confirm_max_cents." % (sentence, money, quote["quote_id"], cents, money)
|
| 90 |
+
)
|
| 91 |
+
if not quote_id or confirm_max_cents is None:
|
| 92 |
+
return "To run it, give both quote_id and confirm_max_cents. Nothing ran."
|
| 93 |
+
quoted = call("GET", "/v1/capacity/quotes/" + urlquote(quote_id, safe=""))
|
| 94 |
+
if quoted.get("quote_id") != quote_id:
|
| 95 |
+
return "That quote was not found. Nothing ran."
|
| 96 |
+
cents = int(quoted.get("maximum_cents") or 0)
|
| 97 |
+
if int(confirm_max_cents) != cents:
|
| 98 |
+
return "confirm_max_cents must equal the quoted maximum, %d. Nothing ran." % cents
|
| 99 |
+
started = call(
|
| 100 |
+
"POST",
|
| 101 |
+
"/v1/capacity/executions",
|
| 102 |
+
{
|
| 103 |
+
"quote_id": quote_id,
|
| 104 |
+
"quote_hash": quoted.get("quote_hash"),
|
| 105 |
+
"idempotency_key": "smolagents-" + quote_id,
|
| 106 |
+
"confirm_max_cents": cents,
|
| 107 |
+
},
|
| 108 |
+
)
|
| 109 |
+
find_id = started.get("find_id")
|
| 110 |
+
if not find_id:
|
| 111 |
+
return "SpecStream accepted the approval but has not started the work yet. Call again in a moment."
|
| 112 |
+
return "Started. find_id: %s. Read the rows with specstream_get_rows; the run takes a few minutes." % find_id
|
| 113 |
+
except RuntimeError as exc:
|
| 114 |
+
return "Nothing ran. %s" % exc
|
| 115 |
+
|
| 116 |
+
def __init__(self, *args, **kwargs):
|
| 117 |
+
self.is_initialized = False
|