langchain-fastaf 0.1.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.
@@ -0,0 +1,9 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 FastAF
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
+
7
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,67 @@
1
+ Metadata-Version: 2.5
2
+ Name: langchain-fastaf
3
+ Version: 0.1.0
4
+ Summary: LangChain tools for FastAF: SERP, news, jobs, Amazon, eBay and page-to-markdown APIs for AI agents.
5
+ Project-URL: Homepage, https://fastaf.co
6
+ Project-URL: Documentation, https://fastaf.co/docs/
7
+ Project-URL: Source, https://github.com/fastaf-co/fastaf-integrations
8
+ Author-email: FastAF <dev@fastaf.co>
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: ai-agents,fastaf,langchain,scraping,serp,web-search,x402
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Topic :: Software Development :: Libraries
15
+ Requires-Python: >=3.10
16
+ Requires-Dist: fastaf-sdk<0.2,>=0.1.0
17
+ Requires-Dist: httpx>=0.27
18
+ Requires-Dist: langchain-core<2,>=0.3
19
+ Provides-Extra: test
20
+ Requires-Dist: pytest>=8; extra == 'test'
21
+ Description-Content-Type: text/markdown
22
+
23
+ # langchain-fastaf
24
+
25
+ LangChain tools for [FastAF](https://fastaf.co): Google SERP, Google News, Google Jobs, Amazon, eBay and page-to-Markdown APIs built for AI agents.
26
+
27
+ ## Install
28
+
29
+ ```bash
30
+ pip install -U langchain-fastaf
31
+ export FASTAF_API_KEY="your-key" # get one at https://fastaf.co/signup
32
+ ```
33
+
34
+ Free tier: 2 calls after email verification. Pay per call with x402 from $0.003. See [fastaf.co](https://fastaf.co).
35
+
36
+ ## Tools
37
+
38
+ | Class | Endpoint | Use |
39
+ |---|---|---|
40
+ | `FastAFSearch` | `/v1/serp` | Google web results |
41
+ | `FastAFNews` | `/v1/news` | Google News |
42
+ | `FastAFJobs` | `/v1/jobs` | Google Jobs |
43
+ | `FastAFAmazon` | `/v1/amazon` | Amazon search / product by ASIN |
44
+ | `FastAFEbay` | `/v1/ebay` | eBay search, sold listings, items (by URL) |
45
+ | `FastAFPageToMarkdown` | `/v1/scrape` | Any web page as Markdown |
46
+
47
+ ## Usage
48
+
49
+ ```python
50
+ from langchain_fastaf import FastAFSearch, get_all_tools
51
+
52
+ search = FastAFSearch()
53
+ print(search.invoke({"query": "x402 payments", "results": 3}))
54
+
55
+ # With an agent
56
+ from langchain.agents import create_agent
57
+ agent = create_agent("openai:gpt-4.1-mini", tools=get_all_tools())
58
+ agent.invoke({"messages": [{"role": "user", "content": "Latest news on x402?"}]})
59
+ ```
60
+
61
+ ## Development
62
+
63
+ ```bash
64
+ pip install -e ".[test]" && pytest
65
+ ```
66
+
67
+ Docs: https://fastaf.co/docs/ · License: MIT
@@ -0,0 +1,45 @@
1
+ # langchain-fastaf
2
+
3
+ LangChain tools for [FastAF](https://fastaf.co): Google SERP, Google News, Google Jobs, Amazon, eBay and page-to-Markdown APIs built for AI agents.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ pip install -U langchain-fastaf
9
+ export FASTAF_API_KEY="your-key" # get one at https://fastaf.co/signup
10
+ ```
11
+
12
+ Free tier: 2 calls after email verification. Pay per call with x402 from $0.003. See [fastaf.co](https://fastaf.co).
13
+
14
+ ## Tools
15
+
16
+ | Class | Endpoint | Use |
17
+ |---|---|---|
18
+ | `FastAFSearch` | `/v1/serp` | Google web results |
19
+ | `FastAFNews` | `/v1/news` | Google News |
20
+ | `FastAFJobs` | `/v1/jobs` | Google Jobs |
21
+ | `FastAFAmazon` | `/v1/amazon` | Amazon search / product by ASIN |
22
+ | `FastAFEbay` | `/v1/ebay` | eBay search, sold listings, items (by URL) |
23
+ | `FastAFPageToMarkdown` | `/v1/scrape` | Any web page as Markdown |
24
+
25
+ ## Usage
26
+
27
+ ```python
28
+ from langchain_fastaf import FastAFSearch, get_all_tools
29
+
30
+ search = FastAFSearch()
31
+ print(search.invoke({"query": "x402 payments", "results": 3}))
32
+
33
+ # With an agent
34
+ from langchain.agents import create_agent
35
+ agent = create_agent("openai:gpt-4.1-mini", tools=get_all_tools())
36
+ agent.invoke({"messages": [{"role": "user", "content": "Latest news on x402?"}]})
37
+ ```
38
+
39
+ ## Development
40
+
41
+ ```bash
42
+ pip install -e ".[test]" && pytest
43
+ ```
44
+
45
+ Docs: https://fastaf.co/docs/ · License: MIT
@@ -0,0 +1,19 @@
1
+ """LangChain integration for FastAF (https://fastaf.co)."""
2
+ from langchain_fastaf.tools import (
3
+ FastAFAmazon,
4
+ FastAFEbay,
5
+ FastAFJobs,
6
+ FastAFNews,
7
+ FastAFPageToMarkdown,
8
+ FastAFSearch,
9
+ )
10
+
11
+ __all__ = ["FastAFSearch", "FastAFNews", "FastAFJobs", "FastAFAmazon", "FastAFEbay",
12
+ "FastAFPageToMarkdown", "get_all_tools"]
13
+ __version__ = "0.1.0"
14
+
15
+
16
+ def get_all_tools(**kwargs):
17
+ """Return one instance of every FastAF tool, sharing kwargs (api_key, base_url)."""
18
+ return [cls(**kwargs) for cls in (FastAFSearch, FastAFNews, FastAFJobs, FastAFAmazon,
19
+ FastAFEbay, FastAFPageToMarkdown)]
@@ -0,0 +1,22 @@
1
+ """Shared FastAF client construction."""
2
+ from __future__ import annotations
3
+
4
+ import json
5
+ import os
6
+ from typing import Any
7
+
8
+ from fastaf import FastAF
9
+
10
+
11
+ def make_client(api_key: str | None = None, base_url: str = "https://api.fastaf.co") -> FastAF:
12
+ key = api_key or os.environ.get("FASTAF_API_KEY")
13
+ if not key:
14
+ raise ValueError(
15
+ "FastAF API key missing. Set FASTAF_API_KEY or pass api_key=. "
16
+ "Get one at https://fastaf.co/signup"
17
+ )
18
+ return FastAF(key, base_url=base_url)
19
+
20
+
21
+ def to_text(data: Any) -> str:
22
+ return data if isinstance(data, str) else json.dumps(data, ensure_ascii=False)
@@ -0,0 +1,146 @@
1
+ """LangChain tools for the FastAF API (https://fastaf.co)."""
2
+ from __future__ import annotations
3
+
4
+ from typing import Any, Dict, Optional, Type
5
+
6
+ from langchain_core.callbacks import CallbackManagerForToolRun
7
+ from langchain_core.tools import BaseTool
8
+ from pydantic import BaseModel, ConfigDict, Field, PrivateAttr
9
+
10
+ from langchain_fastaf._client import make_client, to_text
11
+
12
+
13
+ class _FastAFBaseTool(BaseTool):
14
+ """Base class: holds the FastAF client. Key from ``api_key`` or ``FASTAF_API_KEY``."""
15
+
16
+ model_config = ConfigDict(arbitrary_types_allowed=True)
17
+ api_key: Optional[str] = Field(default=None, exclude=True, repr=False)
18
+ base_url: str = "https://api.fastaf.co"
19
+ client: Any = Field(default=None, exclude=True, repr=False)
20
+ _endpoint: str = PrivateAttr(default="")
21
+ _defaults: Dict[str, Any] = PrivateAttr(default_factory=dict)
22
+
23
+ def _get_client(self) -> Any:
24
+ if self.client is None:
25
+ self.client = make_client(self.api_key, self.base_url)
26
+ return self.client
27
+
28
+ def _call(self, params: Dict[str, Any]) -> str:
29
+ merged = {**self._defaults, **{k: v for k, v in params.items() if v is not None}}
30
+ return to_text(self._get_client().request(self._endpoint, merged))
31
+
32
+
33
+ class SearchInput(BaseModel):
34
+ query: str = Field(description="Search query")
35
+ results: int = Field(default=5, ge=1, le=100, description="Number of results")
36
+ country: Optional[str] = Field(default=None, description="Two-letter country code, e.g. 'us'")
37
+
38
+
39
+ class FastAFSearch(_FastAFBaseTool):
40
+ """Google web search results (SERP) via FastAF ``/v1/serp``."""
41
+
42
+ name: str = "fastaf_search"
43
+ description: str = "Search Google and return organic results as JSON. Input: a search query."
44
+ args_schema: Type[BaseModel] = SearchInput
45
+
46
+ def model_post_init(self, __context: Any) -> None:
47
+ self._endpoint = "serp"
48
+
49
+ def _run(self, query: str, results: int = 5, country: Optional[str] = None,
50
+ run_manager: Optional[CallbackManagerForToolRun] = None) -> str:
51
+ return self._call({"query": query, "results": results, "country": country})
52
+
53
+
54
+ class FastAFNews(_FastAFBaseTool):
55
+ """Google News results via FastAF ``/v1/news``."""
56
+
57
+ name: str = "fastaf_news"
58
+ description: str = "Search Google News for recent articles. Input: a news query."
59
+ args_schema: Type[BaseModel] = SearchInput
60
+
61
+ def model_post_init(self, __context: Any) -> None:
62
+ self._endpoint = "news"
63
+
64
+ def _run(self, query: str, results: int = 5, country: Optional[str] = None,
65
+ run_manager: Optional[CallbackManagerForToolRun] = None) -> str:
66
+ return self._call({"query": query, "results": results, "country": country})
67
+
68
+
69
+ class JobsInput(BaseModel):
70
+ query: str = Field(description="Job search, e.g. 'python developer remote'")
71
+ country: Optional[str] = Field(default=None, description="Two-letter country code")
72
+ next_page_token: Optional[str] = Field(default=None, description="Token for the next page")
73
+
74
+
75
+ class FastAFJobs(_FastAFBaseTool):
76
+ """Google Jobs listings via FastAF ``/v1/jobs``."""
77
+
78
+ name: str = "fastaf_jobs"
79
+ description: str = "Search Google Jobs listings. Input: a job title/keywords and optional location."
80
+ args_schema: Type[BaseModel] = JobsInput
81
+
82
+ def model_post_init(self, __context: Any) -> None:
83
+ self._endpoint = "jobs"
84
+
85
+ def _run(self, query: str, country: Optional[str] = None, next_page_token: Optional[str] = None,
86
+ run_manager: Optional[CallbackManagerForToolRun] = None) -> str:
87
+ return self._call({"query": query, "country": country, "next_page_token": next_page_token})
88
+
89
+
90
+ class AmazonInput(BaseModel):
91
+ query: Optional[str] = Field(default=None, description="Product search query")
92
+ asin: Optional[str] = Field(default=None, description="Amazon ASIN for a single product")
93
+ domain: Optional[str] = Field(default=None, description="Amazon domain, e.g. 'com', 'it'")
94
+
95
+
96
+ class FastAFAmazon(_FastAFBaseTool):
97
+ """Amazon product search or product details via FastAF ``/v1/amazon``."""
98
+
99
+ name: str = "fastaf_amazon"
100
+ description: str = ("Get Amazon products and prices. Pass `query` to search or `asin` "
101
+ "for one product's details.")
102
+ args_schema: Type[BaseModel] = AmazonInput
103
+
104
+ def model_post_init(self, __context: Any) -> None:
105
+ self._endpoint = "amazon"
106
+
107
+ def _run(self, query: Optional[str] = None, asin: Optional[str] = None,
108
+ domain: Optional[str] = None,
109
+ run_manager: Optional[CallbackManagerForToolRun] = None) -> str:
110
+ if not query and not asin:
111
+ raise ValueError("Provide either query or asin")
112
+ return self._call({"query": query, "asin": asin, "domain": domain})
113
+
114
+
115
+ class UrlInput(BaseModel):
116
+ url: str = Field(description="Absolute https URL")
117
+
118
+
119
+ class FastAFEbay(_FastAFBaseTool):
120
+ """eBay listing/search page data via FastAF ``/v1/ebay``."""
121
+
122
+ name: str = "fastaf_ebay"
123
+ description: str = ("Extract structured data from an eBay URL (search, sold listings or item page). "
124
+ "Input: the eBay URL.")
125
+ args_schema: Type[BaseModel] = UrlInput
126
+
127
+ def model_post_init(self, __context: Any) -> None:
128
+ self._endpoint = "ebay"
129
+
130
+ def _run(self, url: str, run_manager: Optional[CallbackManagerForToolRun] = None) -> str:
131
+ return self._call({"url": url})
132
+
133
+
134
+ class FastAFPageToMarkdown(_FastAFBaseTool):
135
+ """Fetch a web page and return clean Markdown via FastAF ``/v1/scrape``."""
136
+
137
+ name: str = "fastaf_page_to_markdown"
138
+ description: str = "Fetch a web page and return its content as LLM-ready Markdown. Input: the URL."
139
+ args_schema: Type[BaseModel] = UrlInput
140
+
141
+ def model_post_init(self, __context: Any) -> None:
142
+ self._endpoint = "scrape"
143
+ self._defaults = {"formats": "markdown"}
144
+
145
+ def _run(self, url: str, run_manager: Optional[CallbackManagerForToolRun] = None) -> str:
146
+ return self._call({"url": url})
@@ -0,0 +1,31 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "langchain-fastaf"
7
+ version = "0.1.0"
8
+ description = "LangChain tools for FastAF: SERP, news, jobs, Amazon, eBay and page-to-markdown APIs for AI agents."
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ license-files = ["LICENSE"]
12
+ requires-python = ">=3.10"
13
+ authors = [{ name = "FastAF", email = "dev@fastaf.co" }]
14
+ keywords = ["langchain", "fastaf", "serp", "web-search", "scraping", "x402", "ai-agents"]
15
+ classifiers = [
16
+ "Programming Language :: Python :: 3",
17
+ "Intended Audience :: Developers",
18
+ "Topic :: Software Development :: Libraries",
19
+ ]
20
+ dependencies = ["langchain-core>=0.3,<2", "fastaf-sdk>=0.1.0,<0.2", "httpx>=0.27"]
21
+
22
+ [project.optional-dependencies]
23
+ test = ["pytest>=8"]
24
+
25
+ [project.urls]
26
+ Homepage = "https://fastaf.co"
27
+ Documentation = "https://fastaf.co/docs/"
28
+ Source = "https://github.com/fastaf-co/fastaf-integrations"
29
+
30
+ [tool.hatch.build.targets.wheel]
31
+ packages = ["langchain_fastaf"]
File without changes
File without changes
@@ -0,0 +1,68 @@
1
+ import json
2
+
3
+ import httpx
4
+ import pytest
5
+ from fastaf import FastAF
6
+
7
+ from langchain_fastaf import (FastAFAmazon, FastAFEbay, FastAFJobs, FastAFNews,
8
+ FastAFPageToMarkdown, FastAFSearch, get_all_tools)
9
+
10
+ CALLS = []
11
+
12
+
13
+ def _client():
14
+ def handler(req: httpx.Request):
15
+ CALLS.append(req)
16
+ return httpx.Response(200, json={"path": req.url.path, "params": dict(req.url.params)})
17
+ return FastAF("test-key", transport=httpx.MockTransport(handler))
18
+
19
+
20
+ @pytest.fixture(autouse=True)
21
+ def _reset():
22
+ CALLS.clear()
23
+
24
+
25
+ @pytest.mark.parametrize("cls,args,path,expect", [
26
+ (FastAFSearch, {"query": "x402"}, "/v1/serp", {"query": "x402", "results": "5"}),
27
+ (FastAFNews, {"query": "ai", "country": "it"}, "/v1/news", {"query": "ai", "country": "it"}),
28
+ (FastAFJobs, {"query": "python"}, "/v1/jobs", {"query": "python"}),
29
+ (FastAFAmazon, {"asin": "B0TEST"}, "/v1/amazon", {"asin": "B0TEST"}),
30
+ (FastAFEbay, {"url": "https://www.ebay.com/sch/i.html?_nkw=x"}, "/v1/ebay", {}),
31
+ (FastAFPageToMarkdown, {"url": "https://example.com"}, "/v1/scrape", {"formats": "markdown"}),
32
+ ])
33
+ def test_tools_call_endpoint(cls, args, path, expect):
34
+ out = json.loads(cls(client=_client()).invoke(args))
35
+ assert out["path"] == path
36
+ for k, v in expect.items():
37
+ assert out["params"][k] == v
38
+ assert CALLS[0].headers["authorization"] == "Bearer test-key"
39
+
40
+
41
+ def test_amazon_requires_query_or_asin():
42
+ with pytest.raises(Exception):
43
+ FastAFAmazon(client=_client()).invoke({})
44
+
45
+
46
+ def test_missing_key(monkeypatch):
47
+ monkeypatch.delenv("FASTAF_API_KEY", raising=False)
48
+ with pytest.raises(ValueError, match="FASTAF_API_KEY"):
49
+ FastAFSearch().invoke({"query": "x"})
50
+
51
+
52
+ def test_env_key(monkeypatch):
53
+ monkeypatch.setenv("FASTAF_API_KEY", "env-key")
54
+ assert FastAFSearch()._get_client() is not None
55
+
56
+
57
+ def test_key_not_in_repr():
58
+ assert "secret" not in repr(FastAFSearch(api_key="secret"))
59
+
60
+
61
+ def test_get_all_tools_names_unique():
62
+ names = [t.name for t in get_all_tools(api_key="k")]
63
+ assert len(set(names)) == 6
64
+
65
+
66
+ def test_tool_schema():
67
+ schema = FastAFSearch(api_key="k").tool_call_schema.model_json_schema()
68
+ assert "query" in schema["properties"]