fastaf-sdk 0.1.0__py3-none-any.whl
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.
- fastaf/__init__.py +4 -0
- fastaf/client.py +76 -0
- fastaf/tools.py +39 -0
- fastaf_sdk-0.1.0.dist-info/METADATA +80 -0
- fastaf_sdk-0.1.0.dist-info/RECORD +6 -0
- fastaf_sdk-0.1.0.dist-info/WHEEL +4 -0
fastaf/__init__.py
ADDED
fastaf/client.py
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from typing import Any, Mapping
|
|
5
|
+
from urllib.parse import urlsplit
|
|
6
|
+
|
|
7
|
+
import httpx
|
|
8
|
+
|
|
9
|
+
Params = Mapping[str, str | int | float | bool | None]
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class FastAFError(Exception):
|
|
13
|
+
"""HTTP failure, including quota 402; no automatic retry or payment."""
|
|
14
|
+
|
|
15
|
+
def __init__(self, response: httpx.Response) -> None:
|
|
16
|
+
super().__init__(f"FastAF request failed ({response.status_code})")
|
|
17
|
+
self.status = response.status_code
|
|
18
|
+
self.body = _decode(response)
|
|
19
|
+
self.headers = response.headers
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _decode(response: httpx.Response) -> Any:
|
|
23
|
+
content_type = response.headers.get("content-type", "").split(";")[0].strip()
|
|
24
|
+
if content_type == "application/json" or content_type.endswith("+json"):
|
|
25
|
+
try:
|
|
26
|
+
return response.json()
|
|
27
|
+
except ValueError:
|
|
28
|
+
return response.text
|
|
29
|
+
if content_type.startswith("image/") or content_type == "application/octet-stream":
|
|
30
|
+
return response.content
|
|
31
|
+
return response.text
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class FastAF:
|
|
35
|
+
"""Synchronous httpx client. Owns its connection pool; use as a context manager.
|
|
36
|
+
|
|
37
|
+
Optional transports support mocking/proxies without changing authentication.
|
|
38
|
+
Redirects are not followed and failed calls are never automatically retried.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
def __init__(self, api_key: str, *, base_url: str = "https://api.fastaf.co",
|
|
42
|
+
timeout: float = 60.0, transport: httpx.BaseTransport | None = None) -> None:
|
|
43
|
+
if not api_key or re.search(r"\s", api_key):
|
|
44
|
+
raise ValueError("A nonempty API key is required")
|
|
45
|
+
url = urlsplit(base_url)
|
|
46
|
+
if (url.scheme not in {"https", "http"} or not url.hostname or url.username or url.password
|
|
47
|
+
or url.path not in {"", "/"} or url.query or url.fragment):
|
|
48
|
+
raise ValueError("base_url must be an HTTP(S) origin without credentials, path, query or fragment")
|
|
49
|
+
if timeout <= 0:
|
|
50
|
+
raise ValueError("timeout must be positive")
|
|
51
|
+
self._http = httpx.Client(base_url=base_url, headers={"Authorization": f"Bearer {api_key}"},
|
|
52
|
+
timeout=timeout, transport=transport, follow_redirects=False)
|
|
53
|
+
|
|
54
|
+
def request(self, endpoint: str, params: Params | None = None, *, method: str = "GET") -> Any:
|
|
55
|
+
if not re.fullmatch(r"[a-z][a-z0-9_-]*", endpoint):
|
|
56
|
+
raise ValueError("endpoint must be a single endpoint name, not a URL or path")
|
|
57
|
+
if method not in {"GET", "POST"}:
|
|
58
|
+
raise ValueError("Use GET or POST")
|
|
59
|
+
values = {key: value for key, value in (params or {}).items() if value is not None}
|
|
60
|
+
kwargs = {"params": values} if method == "GET" else {"json": values}
|
|
61
|
+
response = self._http.request(method, f"/v1/{endpoint}", **kwargs)
|
|
62
|
+
if not response.is_success:
|
|
63
|
+
raise FastAFError(response)
|
|
64
|
+
return _decode(response)
|
|
65
|
+
|
|
66
|
+
def search(self, query: str, **params: str | int | float | bool | None) -> Any:
|
|
67
|
+
return self.request("serp", {**params, "query": query})
|
|
68
|
+
|
|
69
|
+
def close(self) -> None:
|
|
70
|
+
self._http.close()
|
|
71
|
+
|
|
72
|
+
def __enter__(self) -> FastAF:
|
|
73
|
+
return self
|
|
74
|
+
|
|
75
|
+
def __exit__(self, *_: Any) -> None:
|
|
76
|
+
self.close()
|
fastaf/tools.py
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""Optional framework factories. Credentials live in closures, never tool schemas."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
from typing import TYPE_CHECKING, Callable
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from .client import FastAF
|
|
9
|
+
|
|
10
|
+
DESCRIPTION = "Search the public web with FastAF for source links and snippets. Each call spends API credits."
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _search(client: FastAF) -> Callable:
|
|
14
|
+
def fastaf_search(query: str, results: int = 5) -> str:
|
|
15
|
+
"""Search the public web. query: nonempty search text; results: 1-10."""
|
|
16
|
+
if not query.strip() or len(query) > 2000:
|
|
17
|
+
raise ValueError("query must contain 1-2000 characters")
|
|
18
|
+
if isinstance(results, bool) or not isinstance(results, int) or not 1 <= results <= 10:
|
|
19
|
+
raise ValueError("results must be an integer from 1 to 10")
|
|
20
|
+
return json.dumps(client.search(query, results=results), ensure_ascii=False)
|
|
21
|
+
return fastaf_search
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def langchain_tool(client: FastAF):
|
|
25
|
+
"""Requires fastaf-sdk[langchain]."""
|
|
26
|
+
from langchain_core.tools import StructuredTool
|
|
27
|
+
return StructuredTool.from_function(_search(client), name="fastaf_search", description=DESCRIPTION)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def llamaindex_tool(client: FastAF):
|
|
31
|
+
"""Requires fastaf-sdk[llamaindex]."""
|
|
32
|
+
from llama_index.core.tools import FunctionTool
|
|
33
|
+
return FunctionTool.from_defaults(fn=_search(client), name="fastaf_search", description=DESCRIPTION)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def crewai_tool(client: FastAF):
|
|
37
|
+
"""Requires fastaf-sdk[crewai]."""
|
|
38
|
+
from crewai.tools import tool
|
|
39
|
+
return tool("fastaf_search")(_search(client))
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: fastaf-sdk
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: FastAF API client and optional agent tools
|
|
5
|
+
Project-URL: Homepage, https://fastaf.co
|
|
6
|
+
Project-URL: Repository, https://github.com/fastaf-co/fastaf-sdk
|
|
7
|
+
Author-email: FastAF <dev@fastaf.co>
|
|
8
|
+
Requires-Python: >=3.10
|
|
9
|
+
Requires-Dist: httpx<1,>=0.27
|
|
10
|
+
Provides-Extra: crewai
|
|
11
|
+
Requires-Dist: crewai<2,>=1; extra == 'crewai'
|
|
12
|
+
Provides-Extra: langchain
|
|
13
|
+
Requires-Dist: langchain-core<2,>=1; extra == 'langchain'
|
|
14
|
+
Provides-Extra: llamaindex
|
|
15
|
+
Requires-Dist: llama-index-core<0.15,>=0.14; extra == 'llamaindex'
|
|
16
|
+
Provides-Extra: test
|
|
17
|
+
Requires-Dist: build<2,>=1; extra == 'test'
|
|
18
|
+
Requires-Dist: pytest<10,>=8; extra == 'test'
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
|
|
21
|
+
# fastaf-sdk
|
|
22
|
+
|
|
23
|
+
[FastAF](https://fastaf.co) · Docs: https://fastaf.co/docs/
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
Distribution name: **fastaf-sdk**. Import: **fastaf**. The unrelated `fastaf`
|
|
27
|
+
PyPI distribution is not this SDK. Nothing here has been published.
|
|
28
|
+
|
|
29
|
+
Python 3.10+. Local installation (from this directory):
|
|
30
|
+
|
|
31
|
+
```sh
|
|
32
|
+
pip install -e .
|
|
33
|
+
# Install only integrations you use:
|
|
34
|
+
pip install -e '.[langchain,llamaindex,crewai]'
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
import os
|
|
39
|
+
from fastaf import FastAF, FastAFError
|
|
40
|
+
|
|
41
|
+
with FastAF(os.environ["FASTAF_API_KEY"]) as client:
|
|
42
|
+
data = client.search("best espresso machine", results=5)
|
|
43
|
+
markdown = client.request("scrape", {"url": "https://example.com", "formats": "markdown"})
|
|
44
|
+
data = client.request("serp", {"query": "coffee"}, method="POST")
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
`request(endpoint, params)` covers the live `/v1/*` endpoints. Returns decoded
|
|
48
|
+
JSON, text, or bytes for images. Keys stay in Authorization headers. Errors
|
|
49
|
+
raise `FastAFError` with `status`, `body`, and `headers` (including Retry-After).
|
|
50
|
+
Network/timeout exceptions remain httpx exceptions. Default timeout: 60 seconds;
|
|
51
|
+
override `timeout=30`. There are no automatic retries, redirects or x402 payments.
|
|
52
|
+
A 402 on keyed REST means insufficient credits, not permission to spend a wallet.
|
|
53
|
+
Close the client or use its context manager. Do not put keys in browser code.
|
|
54
|
+
|
|
55
|
+
## Agent tools
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
from fastaf.tools import langchain_tool, llamaindex_tool, crewai_tool
|
|
59
|
+
|
|
60
|
+
# Use inside the client's lifetime. Each accepts the same FastAF client.
|
|
61
|
+
search = langchain_tool(client) # StructuredTool
|
|
62
|
+
search = llamaindex_tool(client) # FunctionTool
|
|
63
|
+
search = crewai_tool(client) # CrewAI tool
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Tools accept `query` and `results` (1-10, default 5). They return JSON strings;
|
|
67
|
+
credentials are not part of agent-visible schemas. Each call consumes API
|
|
68
|
+
credits, so configure agent step limits. Integrations import lazily.
|
|
69
|
+
|
|
70
|
+
## Tests and local packaging
|
|
71
|
+
|
|
72
|
+
```sh
|
|
73
|
+
pip install -e '.[test,langchain,llamaindex,crewai]'
|
|
74
|
+
pytest
|
|
75
|
+
python -m build
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Tests use httpx.MockTransport, never real keys or billable calls. Without optional
|
|
79
|
+
frameworks, their integration tests skip; install all extras to run the full suite.
|
|
80
|
+
The private distribution classifier deliberately blocks uploading to PyPI.
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
fastaf/__init__.py,sha256=is0E-dGY60EK5YuVMAtlX1IZvOC0rrxE84UUiTEDy3w,165
|
|
2
|
+
fastaf/client.py,sha256=GkRMQ9UpbeoV7VlE09OJzQRgpsARHNrinRnn2PqcP_M,3190
|
|
3
|
+
fastaf/tools.py,sha256=8nTyuUkrOLiwjgbU5TBlPELzL412ffVAkubm1iRvzkw,1554
|
|
4
|
+
fastaf_sdk-0.1.0.dist-info/METADATA,sha256=w0Nl63o_8-tF2kuJzfWGK6PILgllzdx8rZB1_LQKTzg,2912
|
|
5
|
+
fastaf_sdk-0.1.0.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
|
|
6
|
+
fastaf_sdk-0.1.0.dist-info/RECORD,,
|