specstream-haystack 0.1.2__tar.gz → 0.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.
- {specstream_haystack-0.1.2 → specstream_haystack-0.2.0}/PKG-INFO +8 -4
- {specstream_haystack-0.1.2 → specstream_haystack-0.2.0}/README.md +6 -2
- {specstream_haystack-0.1.2 → specstream_haystack-0.2.0}/pyproject.toml +2 -2
- specstream_haystack-0.2.0/src/haystack_integrations/components/fetchers/specstream/__init__.py +8 -0
- {specstream_haystack-0.1.2 → specstream_haystack-0.2.0}/src/haystack_integrations/components/fetchers/specstream/_api.py +18 -0
- {specstream_haystack-0.1.2 → specstream_haystack-0.2.0}/src/haystack_integrations/components/fetchers/specstream/list_fetcher.py +83 -0
- specstream_haystack-0.1.2/src/haystack_integrations/components/fetchers/specstream/__init__.py +0 -4
- {specstream_haystack-0.1.2 → specstream_haystack-0.2.0}/.gitignore +0 -0
- {specstream_haystack-0.1.2 → specstream_haystack-0.2.0}/LICENSE +0 -0
- {specstream_haystack-0.1.2 → specstream_haystack-0.2.0}/src/haystack_integrations/components/fetchers/specstream/py.typed +0 -0
- {specstream_haystack-0.1.2 → specstream_haystack-0.2.0}/src/haystack_integrations/tools/specstream/__init__.py +0 -0
- {specstream_haystack-0.1.2 → specstream_haystack-0.2.0}/src/haystack_integrations/tools/specstream/list_tool.py +0 -0
- {specstream_haystack-0.1.2 → specstream_haystack-0.2.0}/tests/test_specstream_haystack.py +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: specstream-haystack
|
|
3
|
-
Version: 0.
|
|
4
|
-
Summary:
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: The live web data engine for Haystack: Search API ($5 per 1,000 searches), Extract API ($0.80 per 1,000 pages) and Deep Search.
|
|
5
5
|
Project-URL: Homepage, https://specstream.ai
|
|
6
6
|
Project-URL: Documentation, https://specstream.ai/docs
|
|
7
7
|
Project-URL: Repository, https://github.com/specstream/specstream-tools/tree/main/packages/specstream-haystack
|
|
@@ -33,14 +33,18 @@ Description-Content-Type: text/markdown
|
|
|
33
33
|
|
|
34
34
|
# specstream-haystack
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
[SpecStream](https://specstream.ai) is the live web data engine. One platform to build, search, extract, enrich, map, and monitor data across the entire web.
|
|
37
|
+
|
|
38
|
+
Ask for any dataset in plain words, and SpecStream builds it from the entire live web: every match found, extracted and enriched into structured records, then monitored for change. This package gives your Haystack pipeline or agent a fetcher component and a tool for it, with one `Document` per row. A run starts only within a price you set in code, and the model can't raise it.
|
|
39
|
+
|
|
40
|
+
Source URL and read time on every value.
|
|
37
41
|
|
|
38
42
|
```bash
|
|
39
43
|
pip install -U specstream-haystack
|
|
40
44
|
export SPECSTREAM_API_KEY=ss_live_... # Agents & API in your SpecStream workspace
|
|
41
45
|
```
|
|
42
46
|
|
|
43
|
-
A free account comes with $10 of
|
|
47
|
+
A free account comes with $10 of credit to use within 30 days and needs no card: [specstream.ai](https://specstream.ai/signup).
|
|
44
48
|
|
|
45
49
|
## In a pipeline
|
|
46
50
|
|
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
# specstream-haystack
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[SpecStream](https://specstream.ai) is the live web data engine. One platform to build, search, extract, enrich, map, and monitor data across the entire web.
|
|
4
|
+
|
|
5
|
+
Ask for any dataset in plain words, and SpecStream builds it from the entire live web: every match found, extracted and enriched into structured records, then monitored for change. This package gives your Haystack pipeline or agent a fetcher component and a tool for it, with one `Document` per row. A run starts only within a price you set in code, and the model can't raise it.
|
|
6
|
+
|
|
7
|
+
Source URL and read time on every value.
|
|
4
8
|
|
|
5
9
|
```bash
|
|
6
10
|
pip install -U specstream-haystack
|
|
7
11
|
export SPECSTREAM_API_KEY=ss_live_... # Agents & API in your SpecStream workspace
|
|
8
12
|
```
|
|
9
13
|
|
|
10
|
-
A free account comes with $10 of
|
|
14
|
+
A free account comes with $10 of credit to use within 30 days and needs no card: [specstream.ai](https://specstream.ai/signup).
|
|
11
15
|
|
|
12
16
|
## In a pipeline
|
|
13
17
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "specstream-haystack"
|
|
3
|
-
version = "0.
|
|
4
|
-
description = "
|
|
3
|
+
version = "0.2.0"
|
|
4
|
+
description = "The live web data engine for Haystack: Search API ($5 per 1,000 searches), Extract API ($0.80 per 1,000 pages) and Deep Search."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.10"
|
|
7
7
|
license = "MIT"
|
specstream_haystack-0.2.0/src/haystack_integrations/components/fetchers/specstream/__init__.py
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
from haystack_integrations.components.fetchers.specstream._api import SpecStreamError
|
|
2
|
+
from haystack_integrations.components.fetchers.specstream.list_fetcher import (
|
|
3
|
+
SpecStreamExtractor,
|
|
4
|
+
SpecStreamListFetcher,
|
|
5
|
+
SpecStreamWebSearch,
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
__all__ = ["SpecStreamError", "SpecStreamExtractor", "SpecStreamListFetcher", "SpecStreamWebSearch"]
|
|
@@ -112,6 +112,24 @@ class SpecStreamAPI:
|
|
|
112
112
|
raise SpecStreamError("SpecStream returned a response this package couldn't read.")
|
|
113
113
|
return payload["data"]
|
|
114
114
|
|
|
115
|
+
|
|
116
|
+
def web_search(self, q: str, *, passages: bool = False) -> dict[str, Any]:
|
|
117
|
+
"""Search the live web with GET /v1/web-search ($5 per 1,000 searches, no quote needed)."""
|
|
118
|
+
query = (q or "").strip()
|
|
119
|
+
if not query or len(query) > 2000:
|
|
120
|
+
raise ValueError("q must be between 1 and 2,000 characters.")
|
|
121
|
+
params: dict[str, str] = {"q": query}
|
|
122
|
+
if passages:
|
|
123
|
+
params["passages"] = "true"
|
|
124
|
+
return self.request("GET", f"/v1/web-search?{urllib.parse.urlencode(params)}") or {}
|
|
125
|
+
|
|
126
|
+
def extract_pages(self, urls: list[str], *, format: str = "markdown") -> dict[str, Any]:
|
|
127
|
+
"""Extract the main text of 1 to 20 URLs live with POST /v1/extract ($0.80 per 1,000 pages)."""
|
|
128
|
+
cleaned = [str(u or "").strip() for u in (urls or []) if str(u or "").strip()]
|
|
129
|
+
if not 1 <= len(cleaned) <= 20:
|
|
130
|
+
raise ValueError("Provide 1 to 20 http or https URLs.")
|
|
131
|
+
fmt = "text" if format == "text" else "markdown"
|
|
132
|
+
return self.request("POST", "/v1/extract", {"urls": cleaned, "format": fmt}) or {}
|
|
115
133
|
def results(self, find_id: str) -> dict[str, Any]:
|
|
116
134
|
"""Every row found so far for a list, following the pages."""
|
|
117
135
|
rows: list[dict[str, Any]] = []
|
|
@@ -119,3 +119,86 @@ class SpecStreamListFetcher:
|
|
|
119
119
|
meta["request"] = request
|
|
120
120
|
meta["fields"] = fields
|
|
121
121
|
return Document(content=json.dumps(fields, ensure_ascii=False, default=str), meta=meta)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
@component
|
|
125
|
+
class SpecStreamWebSearch:
|
|
126
|
+
"""Searches the live web in seconds with the SpecStream Search API (`GET /v1/web-search`, $5 per 1,000 searches)."""
|
|
127
|
+
|
|
128
|
+
def __init__(
|
|
129
|
+
self,
|
|
130
|
+
api_key: Secret = Secret.from_env_var("SPECSTREAM_API_KEY"),
|
|
131
|
+
base_url: str = DEFAULT_BASE_URL,
|
|
132
|
+
):
|
|
133
|
+
self.api_key = api_key
|
|
134
|
+
self.base_url = base_url
|
|
135
|
+
|
|
136
|
+
def to_dict(self) -> dict[str, Any]:
|
|
137
|
+
return default_to_dict(self, api_key=self.api_key.to_dict(), base_url=self.base_url)
|
|
138
|
+
|
|
139
|
+
@classmethod
|
|
140
|
+
def from_dict(cls, data: dict[str, Any]) -> "SpecStreamWebSearch":
|
|
141
|
+
deserialize_secrets_inplace(data["init_parameters"], keys=["api_key"])
|
|
142
|
+
return default_from_dict(cls, data)
|
|
143
|
+
|
|
144
|
+
@component.output_types(documents=list[Document], answer=dict[str, Any])
|
|
145
|
+
def run(self, q: str, passages: bool = False) -> dict[str, Any]:
|
|
146
|
+
data = SpecStreamAPI(self.api_key.resolve_value() or "", self.base_url).web_search(q, passages=passages)
|
|
147
|
+
docs: list[Document] = []
|
|
148
|
+
for row in data.get("rows") or []:
|
|
149
|
+
if isinstance(row, dict):
|
|
150
|
+
docs.append(
|
|
151
|
+
Document(
|
|
152
|
+
content=str(row.get("snippet") or row.get("name") or ""),
|
|
153
|
+
meta={"title": row.get("name"), "source_url": row.get("page"), "fields": row.get("fields")},
|
|
154
|
+
)
|
|
155
|
+
)
|
|
156
|
+
for src in data.get("sources") or []:
|
|
157
|
+
if isinstance(src, dict):
|
|
158
|
+
docs.append(
|
|
159
|
+
Document(
|
|
160
|
+
content=str(src.get("snippet") or src.get("title") or ""),
|
|
161
|
+
meta={"title": src.get("title"), "source_url": src.get("url"), "published": src.get("published")},
|
|
162
|
+
)
|
|
163
|
+
)
|
|
164
|
+
return {"documents": docs, "answer": data.get("answer") or {}}
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
@component
|
|
168
|
+
class SpecStreamExtractor:
|
|
169
|
+
"""Reads the main text of 1 to 20 URLs live with the SpecStream Extract API (`POST /v1/extract`, $0.80 per 1,000 pages)."""
|
|
170
|
+
|
|
171
|
+
def __init__(
|
|
172
|
+
self,
|
|
173
|
+
api_key: Secret = Secret.from_env_var("SPECSTREAM_API_KEY"),
|
|
174
|
+
base_url: str = DEFAULT_BASE_URL,
|
|
175
|
+
):
|
|
176
|
+
self.api_key = api_key
|
|
177
|
+
self.base_url = base_url
|
|
178
|
+
|
|
179
|
+
def to_dict(self) -> dict[str, Any]:
|
|
180
|
+
return default_to_dict(self, api_key=self.api_key.to_dict(), base_url=self.base_url)
|
|
181
|
+
|
|
182
|
+
@classmethod
|
|
183
|
+
def from_dict(cls, data: dict[str, Any]) -> "SpecStreamExtractor":
|
|
184
|
+
deserialize_secrets_inplace(data["init_parameters"], keys=["api_key"])
|
|
185
|
+
return default_from_dict(cls, data)
|
|
186
|
+
|
|
187
|
+
@component.output_types(documents=list[Document])
|
|
188
|
+
def run(self, urls: list[str], format: str = "markdown") -> dict[str, Any]:
|
|
189
|
+
data = SpecStreamAPI(self.api_key.resolve_value() or "", self.base_url).extract_pages(urls, format=format)
|
|
190
|
+
docs = [
|
|
191
|
+
Document(
|
|
192
|
+
content=str(item.get("text") or ""),
|
|
193
|
+
meta={
|
|
194
|
+
"source_url": item.get("url"),
|
|
195
|
+
"title": item.get("title"),
|
|
196
|
+
"published": item.get("published"),
|
|
197
|
+
"status": item.get("status"),
|
|
198
|
+
"read_from": item.get("read_from"),
|
|
199
|
+
},
|
|
200
|
+
)
|
|
201
|
+
for item in (data.get("results") or [])
|
|
202
|
+
if isinstance(item, dict)
|
|
203
|
+
]
|
|
204
|
+
return {"documents": docs}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|