langchain-crustapi 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.
@@ -0,0 +1,17 @@
1
+ from importlib import metadata
2
+ from typing import List
3
+
4
+ from langchain_crustapi.crustapi_search import CrustAPISearch
5
+ from langchain_crustapi._utilities import CrustAPISearchAPIWrapper
6
+
7
+ try:
8
+ __version__: str = metadata.version(__package__)
9
+ except metadata.PackageNotFoundError:
10
+ __version__ = ""
11
+ del metadata
12
+
13
+ __all__: List[str] = [
14
+ "CrustAPISearch",
15
+ "CrustAPISearchAPIWrapper",
16
+ "__version__",
17
+ ]
@@ -0,0 +1,109 @@
1
+ """Utility wrapper for the CrustAPI Google search endpoint."""
2
+
3
+ from typing import Any, Dict, Optional
4
+
5
+ import aiohttp
6
+ import requests
7
+ from langchain_core.utils import get_from_dict_or_env
8
+ from pydantic import BaseModel, ConfigDict, SecretStr, model_validator
9
+
10
+ CRUSTAPI_URL = "https://crustapi.com/v1/search"
11
+
12
+ # Google surfaces CrustAPI exposes through the single search endpoint.
13
+ SEARCH_TYPES = (
14
+ "web",
15
+ "news",
16
+ "maps",
17
+ "places",
18
+ "shopping",
19
+ "images",
20
+ "videos",
21
+ "scholar",
22
+ "patents",
23
+ )
24
+
25
+
26
+ class CrustAPISearchAPIWrapper(BaseModel):
27
+ """Wrapper around the CrustAPI search endpoint.
28
+
29
+ Reads the API key from the ``crustapi_api_key`` argument or the
30
+ ``CRUSTAPI_API_KEY`` environment variable. Get a free key at
31
+ https://crustapi.com (3,000 credits per month, no card).
32
+ """
33
+
34
+ crustapi_api_key: SecretStr
35
+ api_base_url: Optional[str] = None
36
+
37
+ model_config = ConfigDict(extra="forbid")
38
+
39
+ @model_validator(mode="before")
40
+ @classmethod
41
+ def validate_environment(cls, values: Dict) -> Any:
42
+ """Validate that the API key exists in the arguments or environment."""
43
+ values["crustapi_api_key"] = get_from_dict_or_env(
44
+ values, "crustapi_api_key", "CRUSTAPI_API_KEY"
45
+ )
46
+ return values
47
+
48
+ def _build_params(
49
+ self,
50
+ query: str,
51
+ search_type: str,
52
+ num: int,
53
+ gl: Optional[str],
54
+ hl: Optional[str],
55
+ location: Optional[str],
56
+ ) -> Dict[str, Any]:
57
+ if search_type not in SEARCH_TYPES:
58
+ raise ValueError(
59
+ f"Invalid search_type: {search_type}. "
60
+ f"Must be one of: {', '.join(SEARCH_TYPES)}"
61
+ )
62
+ params: Dict[str, Any] = {"type": search_type, "q": query, "num": num}
63
+ if gl:
64
+ params["gl"] = gl
65
+ if hl:
66
+ params["hl"] = hl
67
+ if location:
68
+ params["location"] = location
69
+ return params
70
+
71
+ def raw_results(
72
+ self,
73
+ query: str,
74
+ search_type: str = "web",
75
+ num: int = 10,
76
+ gl: Optional[str] = None,
77
+ hl: Optional[str] = None,
78
+ location: Optional[str] = None,
79
+ ) -> Dict:
80
+ """Run a search and return the raw JSON response."""
81
+ params = self._build_params(query, search_type, num, gl, hl, location)
82
+ headers = {"x-api-key": self.crustapi_api_key.get_secret_value()}
83
+ base_url = self.api_base_url or CRUSTAPI_URL
84
+ response = requests.get(base_url, params=params, headers=headers, timeout=30)
85
+ response.raise_for_status()
86
+ return response.json()
87
+
88
+ async def raw_results_async(
89
+ self,
90
+ query: str,
91
+ search_type: str = "web",
92
+ num: int = 10,
93
+ gl: Optional[str] = None,
94
+ hl: Optional[str] = None,
95
+ location: Optional[str] = None,
96
+ ) -> Dict:
97
+ """Run a search asynchronously and return the raw JSON response."""
98
+ params = self._build_params(query, search_type, num, gl, hl, location)
99
+ headers = {"x-api-key": self.crustapi_api_key.get_secret_value()}
100
+ base_url = self.api_base_url or CRUSTAPI_URL
101
+ async with aiohttp.ClientSession() as session:
102
+ async with session.get(
103
+ base_url,
104
+ params=params,
105
+ headers=headers,
106
+ timeout=aiohttp.ClientTimeout(total=30),
107
+ ) as response:
108
+ response.raise_for_status()
109
+ return await response.json()
@@ -0,0 +1,126 @@
1
+ """CrustAPI Google search tool for LangChain."""
2
+
3
+ from typing import Any, Dict, Literal, Optional, Type
4
+
5
+ from langchain_core.callbacks import (
6
+ AsyncCallbackManagerForToolRun,
7
+ CallbackManagerForToolRun,
8
+ )
9
+ from langchain_core.tools import BaseTool
10
+ from pydantic import BaseModel, Field
11
+
12
+ from langchain_crustapi._utilities import CrustAPISearchAPIWrapper
13
+
14
+
15
+ class CrustAPISearchInput(BaseModel):
16
+ """Input for the CrustAPI search tool."""
17
+
18
+ query: str = Field(description="Search query to look up on Google")
19
+
20
+
21
+ class CrustAPISearch(BaseTool): # type: ignore[override]
22
+ """Search Google through CrustAPI and get clean, structured JSON.
23
+
24
+ One tool covers multiple Google surfaces via ``search_type``: ``web``
25
+ (default), ``news``, ``maps``, ``places``, ``shopping``, ``images``,
26
+ ``videos``, ``scholar``, and ``patents``. You only pay for successful
27
+ results, and there is a free tier with no card.
28
+
29
+ Setup:
30
+ Install ``langchain-crustapi`` and set the ``CRUSTAPI_API_KEY``
31
+ environment variable with a key from https://crustapi.com.
32
+
33
+ .. code-block:: bash
34
+
35
+ pip install langchain-crustapi
36
+ export CRUSTAPI_API_KEY="your-key"
37
+
38
+ Instantiate:
39
+ .. code-block:: python
40
+
41
+ from langchain_crustapi import CrustAPISearch
42
+
43
+ tool = CrustAPISearch(max_results=5)
44
+
45
+ Invoke:
46
+ .. code-block:: python
47
+
48
+ tool.invoke({"query": "best crm software"})
49
+ """
50
+
51
+ name: str = "crustapi_search"
52
+ description: str = (
53
+ "Search Google via CrustAPI and return clean, structured JSON. "
54
+ "Useful for current web results, local businesses (maps), news, "
55
+ "shopping, images, videos, scholar, and patents. Input is a search query."
56
+ )
57
+ args_schema: Type[BaseModel] = CrustAPISearchInput
58
+
59
+ search_type: Literal[
60
+ "web",
61
+ "news",
62
+ "maps",
63
+ "places",
64
+ "shopping",
65
+ "images",
66
+ "videos",
67
+ "scholar",
68
+ "patents",
69
+ ] = "web"
70
+ """Which Google surface to query."""
71
+
72
+ max_results: int = 10
73
+ """Maximum number of results to return."""
74
+
75
+ gl: Optional[str] = None
76
+ """Two-letter country code, e.g. ``us``."""
77
+
78
+ hl: Optional[str] = None
79
+ """Two-letter language code, e.g. ``en``."""
80
+
81
+ location: Optional[str] = None
82
+ """City or region for local results, e.g. ``Austin, TX``."""
83
+
84
+ api_wrapper: CrustAPISearchAPIWrapper = Field(default_factory=CrustAPISearchAPIWrapper) # type: ignore[arg-type]
85
+
86
+ def __init__(self, **kwargs: Any) -> None:
87
+ # Let callers pass crustapi_api_key / api_base_url straight through to the wrapper.
88
+ if "api_wrapper" not in kwargs:
89
+ wrapper_kwargs = {}
90
+ if "crustapi_api_key" in kwargs:
91
+ wrapper_kwargs["crustapi_api_key"] = kwargs.pop("crustapi_api_key")
92
+ if "api_base_url" in kwargs:
93
+ wrapper_kwargs["api_base_url"] = kwargs.pop("api_base_url")
94
+ if wrapper_kwargs:
95
+ kwargs["api_wrapper"] = CrustAPISearchAPIWrapper(**wrapper_kwargs)
96
+ super().__init__(**kwargs)
97
+
98
+ def _run(
99
+ self,
100
+ query: str,
101
+ run_manager: Optional[CallbackManagerForToolRun] = None,
102
+ ) -> Dict[str, Any]:
103
+ """Run the search."""
104
+ return self.api_wrapper.raw_results(
105
+ query=query,
106
+ search_type=self.search_type,
107
+ num=self.max_results,
108
+ gl=self.gl,
109
+ hl=self.hl,
110
+ location=self.location,
111
+ )
112
+
113
+ async def _arun(
114
+ self,
115
+ query: str,
116
+ run_manager: Optional[AsyncCallbackManagerForToolRun] = None,
117
+ ) -> Dict[str, Any]:
118
+ """Run the search asynchronously."""
119
+ return await self.api_wrapper.raw_results_async(
120
+ query=query,
121
+ search_type=self.search_type,
122
+ num=self.max_results,
123
+ gl=self.gl,
124
+ hl=self.hl,
125
+ location=self.location,
126
+ )
File without changes
@@ -0,0 +1,70 @@
1
+ Metadata-Version: 2.4
2
+ Name: langchain-crustapi
3
+ Version: 0.1.0
4
+ Summary: An integration package connecting CrustAPI (Google search data) and LangChain
5
+ License: MIT
6
+ License-File: LICENSE
7
+ Keywords: langchain,crustapi,google,search,serp,maps,tools
8
+ Requires-Python: >=3.9,<4.0
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.9
12
+ Classifier: Programming Language :: Python :: 3.10
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Classifier: Programming Language :: Python :: 3.14
17
+ Requires-Dist: aiohttp (>=3.9,<4.0)
18
+ Requires-Dist: langchain-core (>=0.3,<2.0)
19
+ Requires-Dist: requests (>=2.32,<3.0)
20
+ Project-URL: Repository, https://github.com/crustapi/langchain-crustapi
21
+ Description-Content-Type: text/markdown
22
+
23
+ # langchain-crustapi
24
+
25
+ This package connects [CrustAPI](https://crustapi.com) to LangChain, giving your chains and agents clean, structured Google data from one endpoint: web search, Maps, News, Shopping, Images, Videos, Places, Scholar, and Patents. You only pay for successful results, and there's a free tier with no card.
26
+
27
+ ## Installation
28
+
29
+ ```bash
30
+ pip install -U langchain-crustapi
31
+ export CRUSTAPI_API_KEY="your-key" # free at https://crustapi.com
32
+ ```
33
+
34
+ ## Usage
35
+
36
+ ```python
37
+ from langchain_crustapi import CrustAPISearch
38
+
39
+ # Web search (default)
40
+ tool = CrustAPISearch(max_results=5)
41
+ tool.invoke({"query": "best crm software"})
42
+
43
+ # Pick a Google surface
44
+ maps = CrustAPISearch(search_type="maps", location="Austin, TX")
45
+ maps.invoke({"query": "dentists"})
46
+
47
+ news = CrustAPISearch(search_type="news")
48
+ news.invoke({"query": "openai"})
49
+ ```
50
+
51
+ Bind it to an agent like any other LangChain tool:
52
+
53
+ ```python
54
+ from langchain.chat_models import init_chat_model
55
+ from langchain_crustapi import CrustAPISearch
56
+
57
+ llm = init_chat_model("gpt-4o-mini")
58
+ agent_llm = llm.bind_tools([CrustAPISearch()])
59
+ ```
60
+
61
+ ## Parameters
62
+
63
+ - `search_type`: the Google surface — `web` (default), `news`, `maps`, `places`, `shopping`, `images`, `videos`, `scholar`, `patents`.
64
+ - `max_results`: number of results to return (default `10`).
65
+ - `gl`: two-letter country code, e.g. `us`.
66
+ - `hl`: two-letter language code, e.g. `en`.
67
+ - `location`: city or region for local results, e.g. `Austin, TX`.
68
+
69
+ The API key is read from the `CRUSTAPI_API_KEY` environment variable, or you can pass `crustapi_api_key` when constructing the tool.
70
+
@@ -0,0 +1,8 @@
1
+ langchain_crustapi/__init__.py,sha256=gfZUEx3ylzgwTDaU6ABYUtcfZa0PjCRfkXY0DE-7T8s,415
2
+ langchain_crustapi/_utilities.py,sha256=Lw-b8tlmJubhsXyEA0wlQLm2BwTL3xJccWjCQJ6lbgw,3495
3
+ langchain_crustapi/crustapi_search.py,sha256=NpP0ZGnfjwwmwTtYNfqhYpnXVu9luZQ9cZReafFIWDI,3970
4
+ langchain_crustapi/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
+ langchain_crustapi-0.1.0.dist-info/METADATA,sha256=J3wtfIaiRHQ0mSlDqOf-b4CPywhQzsPkA0EzekIqNkw,2492
6
+ langchain_crustapi-0.1.0.dist-info/WHEEL,sha256=eY7nduwzv-ldUxpzbRlxwvC693Hg6PX8bWDjEHjZ_dk,88
7
+ langchain_crustapi-0.1.0.dist-info/licenses/LICENSE,sha256=cKqmPndkGSxDfZY6_4-YSIjKVMi_bPN7x_je_OEoZUg,1070
8
+ langchain_crustapi-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: poetry-core 2.4.1
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 COLDWIRES LLC
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.