langchain-cloro 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,3 @@
1
+ # Cloro API Key
2
+ # Get your API key at https://cloro.dev
3
+ CLORO_API_KEY=your-api-key-here
@@ -0,0 +1,64 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ *.egg-info/
24
+ .installed.cfg
25
+ *.egg
26
+
27
+ # PyInstaller
28
+ *.manifest
29
+ *.spec
30
+
31
+ # Unit test / coverage reports
32
+ htmlcov/
33
+ .tox/
34
+ .coverage
35
+ .coverage.*
36
+ .cache
37
+ nosetests.xml
38
+ coverage.xml
39
+ *.cover
40
+ .pytest_cache/
41
+
42
+ # Environments
43
+ .env
44
+ .venv
45
+ env/
46
+ venv/
47
+ ENV/
48
+ env.bak/
49
+ venv.bak/
50
+
51
+ # IDEs
52
+ .vscode/
53
+ .idea/
54
+ *.swp
55
+ *.swo
56
+ *~
57
+
58
+ # OS
59
+ .DS_Store
60
+ Thumbs.db
61
+
62
+ # Project specific
63
+ *.log
64
+ uv.lock
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Cloro.dev
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.
@@ -0,0 +1,28 @@
1
+ .PHONY: help test lint format typing clean
2
+
3
+ help:
4
+ @echo "Available commands:"
5
+ @echo " make test - Run unit tests"
6
+ @echo " make lint - Run linter"
7
+ @echo " make format - Format code"
8
+ @echo " make typing - Run type checker"
9
+ @echo " make clean - Clean build artifacts"
10
+
11
+ test:
12
+ pytest tests/unit_tests -v
13
+
14
+ lint:
15
+ ruff check .
16
+
17
+ format:
18
+ ruff format .
19
+
20
+ typing:
21
+ mypy langchain_cloro
22
+
23
+ clean:
24
+ rm -rf build/
25
+ rm -rf dist/
26
+ rm -rf *.egg-info/
27
+ find . -type d -name __pycache__ -exec rm -rf {} +
28
+ find . -type f -name "*.pyc" -delete
@@ -0,0 +1,166 @@
1
+ Metadata-Version: 2.4
2
+ Name: langchain-cloro
3
+ Version: 0.1.0
4
+ Summary: An integration package connecting Cloro.dev and LangChain
5
+ Project-URL: Homepage, https://docs.cloro.dev
6
+ Project-URL: Documentation, https://docs.cloro.dev
7
+ Project-URL: Source, https://github.com/cloro-dev/langchain-cloro
8
+ Project-URL: Changelog, https://github.com/cloro-dev/langchain-cloro/releases
9
+ Project-URL: Repository, https://github.com/cloro-dev/langchain-cloro
10
+ License: MIT
11
+ License-File: LICENSE
12
+ Requires-Python: <4.0.0,>=3.10.0
13
+ Requires-Dist: httpx<1.0.0,>=0.25.0
14
+ Requires-Dist: langchain-core<1.0.0,>=0.1.0
15
+ Description-Content-Type: text/markdown
16
+
17
+ # langchain-cloro
18
+
19
+ This package contains the LangChain integration for [Cloro.dev](https://cloro.dev).
20
+
21
+ ## Installation
22
+
23
+ ```bash
24
+ pip install langchain-cloro
25
+ ```
26
+
27
+ ## Setup
28
+
29
+ You'll need a Cloro API key. Get one at [https://cloro.dev](https://cloro.dev).
30
+
31
+ Set the API key as an environment variable:
32
+
33
+ ```bash
34
+ export CLORO_API_KEY="your-api-key-here"
35
+ ```
36
+
37
+ Or pass it directly when initializing:
38
+
39
+ ```python
40
+ from langchain_cloro import CloroSearchRun
41
+
42
+ tool = CloroSearchRun(cloro_api_key="your-api-key-here")
43
+ ```
44
+
45
+ ## Usage
46
+
47
+ ### With an Agent
48
+
49
+ ```python
50
+ from langchain.agents import initialize_agent, AgentType
51
+ from langchain_openai import OpenAI
52
+ from langchain_cloro import CloroSearchRun
53
+
54
+ llm = OpenAI(temperature=0)
55
+ search = CloroSearchRun()
56
+
57
+ agent = initialize_agent(
58
+ [search],
59
+ llm,
60
+ agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION,
61
+ verbose=True,
62
+ )
63
+
64
+ agent.run("What are the latest developments in AI?")
65
+ ```
66
+
67
+ ### Direct Tool Usage
68
+
69
+ ```python
70
+ from langchain_cloro import CloroSearchRun
71
+
72
+ tool = CloroSearchRun()
73
+
74
+ # Simple search
75
+ results = tool.invoke({"query": "Python programming tips"})
76
+
77
+ # Search with custom parameters
78
+ results = tool.invoke({
79
+ "query": "machine learning tutorials",
80
+ "num_results": 5,
81
+ })
82
+ ```
83
+
84
+ ### With LCEL
85
+
86
+ ```python
87
+ from langchain_core.prompts import ChatPromptTemplate
88
+ from langchain_core.output_parsers import StrOutputParser
89
+ from langchain_openai import ChatOpenAI
90
+ from langchain_cloro import CloroSearchRun
91
+
92
+ search = CloroSearchRun()
93
+
94
+ prompt = ChatPromptTemplate.from_messages([
95
+ ("system", "Answer the question based on the search results:\n\n{results}"),
96
+ ("user", "{question}")
97
+ ])
98
+
99
+ chain = {
100
+ "results": lambda x: search.invoke({"query": x["question"]}),
101
+ "question": lambda x: x["question"]
102
+ } | prompt | ChatOpenAI() | StrOutputParser()
103
+
104
+ response = chain.invoke({"question": "What is LangChain?"})
105
+ print(response)
106
+ ```
107
+
108
+ ## API Reference
109
+
110
+ ### CloroSearchRun
111
+
112
+ Main search tool for Cloro.dev API.
113
+
114
+ **Parameters:**
115
+ - `cloro_api_key` (str, optional): Cloro API key. Defaults to `CLORO_API_KEY` environment variable.
116
+ - `timeout` (float, optional): Request timeout in seconds. Defaults to 10.0.
117
+
118
+ **Methods:**
119
+
120
+ ##### `_run`
121
+
122
+ Execute a search query.
123
+
124
+ ```python
125
+ _run(
126
+ query: str,
127
+ num_results: int = 10,
128
+ **kwargs: dict
129
+ ) -> str
130
+ ```
131
+
132
+ **Parameters:**
133
+ - `query` (str): The search query string.
134
+ - `num_results` (int): Number of results to return. Default: 10.
135
+ - `**kwargs`: Additional search parameters to pass to the Cloro API.
136
+
137
+ **Returns:**
138
+ - `str`: JSON string of search results.
139
+
140
+ ## Development
141
+
142
+ ### Running Tests
143
+
144
+ ```bash
145
+ # Unit tests
146
+ pytest tests/unit_tests
147
+
148
+ # Linting
149
+ ruff check .
150
+ ruff format .
151
+ ```
152
+
153
+ ## Contributing
154
+
155
+ This package follows LangChain's contribution guidelines. See:
156
+ - [LangChain Contributing Guide](https://docs.langchain.com/oss/python/contributing/overview)
157
+
158
+ ## License
159
+
160
+ MIT
161
+
162
+ ## Links
163
+
164
+ - Documentation: [https://docs.cloro.dev](https://docs.cloro.dev)
165
+ - Source: [https://github.com/cloro-dev/langchain-cloro](https://github.com/cloro-dev/langchain-cloro)
166
+ - Cloro API: [https://cloro.dev](https://cloro.dev)
@@ -0,0 +1,150 @@
1
+ # langchain-cloro
2
+
3
+ This package contains the LangChain integration for [Cloro.dev](https://cloro.dev).
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pip install langchain-cloro
9
+ ```
10
+
11
+ ## Setup
12
+
13
+ You'll need a Cloro API key. Get one at [https://cloro.dev](https://cloro.dev).
14
+
15
+ Set the API key as an environment variable:
16
+
17
+ ```bash
18
+ export CLORO_API_KEY="your-api-key-here"
19
+ ```
20
+
21
+ Or pass it directly when initializing:
22
+
23
+ ```python
24
+ from langchain_cloro import CloroSearchRun
25
+
26
+ tool = CloroSearchRun(cloro_api_key="your-api-key-here")
27
+ ```
28
+
29
+ ## Usage
30
+
31
+ ### With an Agent
32
+
33
+ ```python
34
+ from langchain.agents import initialize_agent, AgentType
35
+ from langchain_openai import OpenAI
36
+ from langchain_cloro import CloroSearchRun
37
+
38
+ llm = OpenAI(temperature=0)
39
+ search = CloroSearchRun()
40
+
41
+ agent = initialize_agent(
42
+ [search],
43
+ llm,
44
+ agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION,
45
+ verbose=True,
46
+ )
47
+
48
+ agent.run("What are the latest developments in AI?")
49
+ ```
50
+
51
+ ### Direct Tool Usage
52
+
53
+ ```python
54
+ from langchain_cloro import CloroSearchRun
55
+
56
+ tool = CloroSearchRun()
57
+
58
+ # Simple search
59
+ results = tool.invoke({"query": "Python programming tips"})
60
+
61
+ # Search with custom parameters
62
+ results = tool.invoke({
63
+ "query": "machine learning tutorials",
64
+ "num_results": 5,
65
+ })
66
+ ```
67
+
68
+ ### With LCEL
69
+
70
+ ```python
71
+ from langchain_core.prompts import ChatPromptTemplate
72
+ from langchain_core.output_parsers import StrOutputParser
73
+ from langchain_openai import ChatOpenAI
74
+ from langchain_cloro import CloroSearchRun
75
+
76
+ search = CloroSearchRun()
77
+
78
+ prompt = ChatPromptTemplate.from_messages([
79
+ ("system", "Answer the question based on the search results:\n\n{results}"),
80
+ ("user", "{question}")
81
+ ])
82
+
83
+ chain = {
84
+ "results": lambda x: search.invoke({"query": x["question"]}),
85
+ "question": lambda x: x["question"]
86
+ } | prompt | ChatOpenAI() | StrOutputParser()
87
+
88
+ response = chain.invoke({"question": "What is LangChain?"})
89
+ print(response)
90
+ ```
91
+
92
+ ## API Reference
93
+
94
+ ### CloroSearchRun
95
+
96
+ Main search tool for Cloro.dev API.
97
+
98
+ **Parameters:**
99
+ - `cloro_api_key` (str, optional): Cloro API key. Defaults to `CLORO_API_KEY` environment variable.
100
+ - `timeout` (float, optional): Request timeout in seconds. Defaults to 10.0.
101
+
102
+ **Methods:**
103
+
104
+ ##### `_run`
105
+
106
+ Execute a search query.
107
+
108
+ ```python
109
+ _run(
110
+ query: str,
111
+ num_results: int = 10,
112
+ **kwargs: dict
113
+ ) -> str
114
+ ```
115
+
116
+ **Parameters:**
117
+ - `query` (str): The search query string.
118
+ - `num_results` (int): Number of results to return. Default: 10.
119
+ - `**kwargs`: Additional search parameters to pass to the Cloro API.
120
+
121
+ **Returns:**
122
+ - `str`: JSON string of search results.
123
+
124
+ ## Development
125
+
126
+ ### Running Tests
127
+
128
+ ```bash
129
+ # Unit tests
130
+ pytest tests/unit_tests
131
+
132
+ # Linting
133
+ ruff check .
134
+ ruff format .
135
+ ```
136
+
137
+ ## Contributing
138
+
139
+ This package follows LangChain's contribution guidelines. See:
140
+ - [LangChain Contributing Guide](https://docs.langchain.com/oss/python/contributing/overview)
141
+
142
+ ## License
143
+
144
+ MIT
145
+
146
+ ## Links
147
+
148
+ - Documentation: [https://docs.cloro.dev](https://docs.cloro.dev)
149
+ - Source: [https://github.com/cloro-dev/langchain-cloro](https://github.com/cloro-dev/langchain-cloro)
150
+ - Cloro API: [https://cloro.dev](https://cloro.dev)
@@ -0,0 +1,8 @@
1
+ """LangChain integration for Cloro.dev Search API."""
2
+
3
+ from langchain_cloro.tools import CloroSearchRun, CloroSearchResults
4
+
5
+ __all__ = [
6
+ "CloroSearchRun",
7
+ "CloroSearchResults",
8
+ ]
@@ -0,0 +1,55 @@
1
+ """Utilities for Cloro integration."""
2
+
3
+ import os
4
+ from typing import Any
5
+
6
+ import httpx
7
+ from langchain_core.utils import convert_to_secret_str
8
+
9
+
10
+ def initialize_client(values: dict, *, timeout: float = 10.0) -> dict:
11
+ """Initialize the Cloro HTTP client.
12
+
13
+ Args:
14
+ values: The values dictionary containing configuration.
15
+ timeout: Request timeout in seconds. Default: 10.0
16
+
17
+ Returns:
18
+ The updated values dictionary with initialized client.
19
+ """
20
+ cloro_api_key = values.get("cloro_api_key") or os.environ.get("CLORO_API_KEY") or ""
21
+ values["cloro_api_key"] = convert_to_secret_str(cloro_api_key)
22
+
23
+ # Initialize httpx client with appropriate settings
24
+ values["client"] = httpx.Client(
25
+ base_url="https://api.cloro.dev",
26
+ headers={
27
+ "Authorization": f"Bearer {values['cloro_api_key'].get_secret_value()}",
28
+ "Content-Type": "application/json",
29
+ },
30
+ timeout=timeout,
31
+ )
32
+ return values
33
+
34
+
35
+ def build_search_params(
36
+ query: str,
37
+ num_results: int = 10,
38
+ **kwargs: dict,
39
+ ) -> dict[str, Any]:
40
+ """Build search parameters for the Cloro API.
41
+
42
+ Args:
43
+ query: The search query string.
44
+ num_results: Number of results to return. Default: 10
45
+ **kwargs: Additional search parameters.
46
+
47
+ Returns:
48
+ Dictionary of search parameters.
49
+ """
50
+ params = {
51
+ "query": query,
52
+ "num_results": num_results,
53
+ }
54
+ params.update(kwargs)
55
+ return params
File without changes
@@ -0,0 +1,119 @@
1
+ """Tool for the Cloro.dev Search API."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ import httpx
8
+ from langchain_core.callbacks import CallbackManagerForToolRun
9
+ from langchain_core.tools import BaseTool
10
+ from pydantic import Field, SecretStr, model_validator
11
+
12
+ from langchain_cloro._utilities import build_search_params, initialize_client
13
+
14
+
15
+ class CloroSearchRun(BaseTool):
16
+ r"""Cloro.dev Search tool.
17
+
18
+ Setup:
19
+ Install ``langchain-cloro`` and set environment variable ``CLORO_API_KEY``.
20
+
21
+ .. code-block:: bash
22
+
23
+ pip install -U langchain-cloro
24
+ export CLORO_API_KEY="your-api-key"
25
+
26
+ Instantiation:
27
+ .. code-block:: python
28
+
29
+ from langchain_cloro import CloroSearchRun
30
+
31
+ tool = CloroSearchRun()
32
+
33
+ Invocation with args:
34
+ .. code-block:: python
35
+
36
+ tool.invoke({"query": "latest AI news", "num_results": 5})
37
+
38
+ Invocation with ToolCall:
39
+ .. code-block:: python
40
+
41
+ tool.invoke(
42
+ {
43
+ "args": {"query": "python programming", "num_results": 3},
44
+ "id": "1",
45
+ "name": tool.name,
46
+ "type": "tool_call",
47
+ }
48
+ )
49
+ """
50
+
51
+ name: str = "cloro_search"
52
+ description: str = (
53
+ "A wrapper around Cloro.dev Search API. "
54
+ "Useful for when you need to answer questions about current events. "
55
+ "Input should be a search query. "
56
+ "Output is a JSON array of search results."
57
+ )
58
+ client: httpx.Client = Field(default=None) # type: ignore[assignment]
59
+ cloro_api_key: SecretStr = Field(default=SecretStr(""))
60
+ timeout: float = Field(default=10.0, description="Request timeout in seconds")
61
+
62
+ @model_validator(mode="before")
63
+ @classmethod
64
+ def validate_environment(cls, values: dict) -> Any:
65
+ """Validate the environment and initialize the client.
66
+
67
+ Args:
68
+ values: The values dictionary containing configuration.
69
+
70
+ Returns:
71
+ The validated and updated values dictionary.
72
+ """
73
+ return initialize_client(values, timeout=values.get("timeout", 10.0))
74
+
75
+ def _run(
76
+ self,
77
+ query: str,
78
+ num_results: int = 10,
79
+ run_manager: CallbackManagerForToolRun | None = None,
80
+ **kwargs: dict,
81
+ ) -> str:
82
+ """Use the Cloro tool to search.
83
+
84
+ Args:
85
+ query: The search query string.
86
+ num_results: Number of search results to return (default: 10).
87
+ run_manager: The run manager for callbacks.
88
+ **kwargs: Additional search parameters to pass to the API.
89
+
90
+ Returns:
91
+ JSON string of search results or error message.
92
+
93
+ Raises:
94
+ httpx.HTTPError: If the API request fails.
95
+ """
96
+ try:
97
+ params = build_search_params(
98
+ query=query,
99
+ num_results=num_results,
100
+ **kwargs,
101
+ )
102
+
103
+ response = self.client.get(
104
+ "/search",
105
+ params=params,
106
+ )
107
+ response.raise_for_status()
108
+
109
+ results = response.json()
110
+ return str(results)
111
+
112
+ except httpx.HTTPError as e:
113
+ return f"Error searching with Cloro: {e!s}"
114
+ except Exception as e:
115
+ return f"Unexpected error: {e!s}"
116
+
117
+
118
+ # Alias for backwards compatibility
119
+ CloroSearchResults = CloroSearchRun
@@ -0,0 +1,118 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ authors = []
7
+ license = { text = "MIT" }
8
+ requires-python = ">=3.10.0,<4.0.0"
9
+ dependencies = [
10
+ "langchain-core>=0.1.0,<1.0.0",
11
+ "httpx>=0.25.0,<1.0.0",
12
+ ]
13
+ name = "langchain-cloro"
14
+ version = "0.1.0"
15
+ description = "An integration package connecting Cloro.dev and LangChain"
16
+ readme = "README.md"
17
+
18
+ [project.urls]
19
+ Homepage = "https://docs.cloro.dev"
20
+ Documentation = "https://docs.cloro.dev"
21
+ Source = "https://github.com/cloro-dev/langchain-cloro"
22
+ Changelog = "https://github.com/cloro-dev/langchain-cloro/releases"
23
+ Repository = "https://github.com/cloro-dev/langchain-cloro"
24
+
25
+ [dependency-groups]
26
+ test = [
27
+ "pytest>=7.3.0,<8.0.0",
28
+ "pytest-mock>=3.10.0,<4.0.0",
29
+ "pytest-watcher>=0.3.4,<1.0.0",
30
+ "pytest-asyncio>=0.21.1,<1.0.0",
31
+ "freezegun>=1.2.2,<2.0.0",
32
+ "langchain-core",
33
+ ]
34
+ lint = ["ruff>=0.13.1,<0.14.0"]
35
+ dev = ["langchain-core"]
36
+ typing = [
37
+ "mypy>=1.10.0,<2.0.0",
38
+ "langchain-core",
39
+ ]
40
+
41
+ [tool.mypy]
42
+ disallow_untyped_defs = "True"
43
+
44
+ [tool.ruff.format]
45
+ docstring-code-format = true
46
+
47
+ [tool.ruff.lint]
48
+ select = [
49
+ "A", # flake8-builtins
50
+ "ASYNC", # flake8-async
51
+ "C4", # flake8-comprehensions
52
+ "COM", # flake8-commas
53
+ "D", # pydocstyle
54
+ "E", # pycodestyle error
55
+ "EM", # flake8-errmsg
56
+ "F", # pyflakes
57
+ "FA", # flake8-future-annotations
58
+ "FBT", # flake8-boolean-trap
59
+ "FLY", # flake8-flynt
60
+ "I", # isort
61
+ "ICN", # flake8-import-conventions
62
+ "INT", # flake8-gettext
63
+ "ISC", # isort-comprehensions
64
+ "PGH", # pygrep-hooks
65
+ "PIE", # flake8-pie
66
+ "PERF", # flake8-perf
67
+ "PYI", # flake8-pyi
68
+ "Q", # flake8-quotes
69
+ "RET", # flake8-return
70
+ "RSE", # flake8-rst-docstrings
71
+ "RUF", # ruff
72
+ "S", # flake8-bandit
73
+ "SLF", # flake8-self
74
+ "SLOT", # flake8-slots
75
+ "SIM", # flake8-simplify
76
+ "T10", # flake8-debugger
77
+ "T20", # flake8-print
78
+ "TID", # flake8-tidy-imports
79
+ "UP", # pyupgrade
80
+ "W", # pycodestyle warning
81
+ "YTT", # flake8-2020
82
+ ]
83
+ ignore = [
84
+ "COM812", # Messes with the formatter
85
+ "ISC001", # Messes with the formatter
86
+ "PERF203", # Rarely useful
87
+ "S112", # Rarely useful
88
+ "RUF012", # Doesn't play well with Pydantic
89
+ "SLF001", # Private member access
90
+ ]
91
+ unfixable = ["B028"] # People should intentionally tune the stacklevel
92
+
93
+ [tool.coverage.run]
94
+ omit = ["tests/*"]
95
+
96
+ [tool.pytest.ini_options]
97
+ addopts = "--snapshot-warn-unused --strict-markers --strict-config --durations=5"
98
+ markers = [
99
+ "requires: mark tests as requiring a specific library",
100
+ "compile: mark placeholder test used to compile integration tests without running them",
101
+ ]
102
+ asyncio_mode = "auto"
103
+
104
+ [tool.ruff.lint.pydocstyle]
105
+ convention = "google"
106
+ ignore-var-parameters = true # ignore missing documentation for *args and **kwargs parameters
107
+
108
+ [tool.ruff.lint.flake8-tidy-imports]
109
+ ban-relative-imports = "all"
110
+
111
+ [tool.ruff.lint.extend-per-file-ignores]
112
+ "tests/**/*.py" = [
113
+ "S101", # Tests need assertions
114
+ "S311", # Standard pseudo-random generators are not suitable for cryptographic purposes
115
+ ]
116
+ "scripts/*.py" = [
117
+ "INP001", # Not a package
118
+ ]
File without changes
File without changes
@@ -0,0 +1,145 @@
1
+ """Unit tests for Cloro tools."""
2
+
3
+ from unittest.mock import MagicMock, patch
4
+
5
+ import pytest
6
+ from langchain_cloro.tools import CloroSearchRun
7
+
8
+
9
+ class TestCloroSearchRun:
10
+ """Test CloroSearchRun tool."""
11
+
12
+ @patch("langchain_cloro.tools.initialize_client")
13
+ def test_initialization_with_api_key(self, mock_init: MagicMock) -> None:
14
+ """Test initialization with API key."""
15
+ mock_init.return_value = {
16
+ "cloro_api_key": "test-key",
17
+ "client": MagicMock(),
18
+ }
19
+
20
+ tool = CloroSearchRun(cloro_api_key="test-api-key")
21
+
22
+ assert tool.name == "cloro_search"
23
+ assert "Cloro.dev Search API" in tool.description
24
+ mock_init.assert_called_once()
25
+
26
+ @patch("langchain_cloro.tools.initialize_client")
27
+ def test_run_success(self, mock_init: MagicMock) -> None:
28
+ """Test successful search execution."""
29
+ mock_client = MagicMock()
30
+ mock_response = MagicMock()
31
+ mock_response.json.return_value = {"results": [{"title": "Test Result"}]}
32
+ mock_response.raise_for_status = MagicMock()
33
+ mock_client.get.return_value = mock_response
34
+
35
+ mock_init.return_value = {
36
+ "cloro_api_key": "test-key",
37
+ "client": mock_client,
38
+ }
39
+
40
+ tool = CloroSearchRun(cloro_api_key="test-api-key")
41
+ result = tool._run(query="test query", num_results=5)
42
+
43
+ # Verify the API call was made correctly
44
+ mock_client.get.assert_called_once()
45
+ call_args = mock_client.get.call_args
46
+ assert call_args[0][0] == "/search"
47
+ assert "params" in call_args[1]
48
+ assert call_args[1]["params"]["query"] == "test query"
49
+ assert call_args[1]["params"]["num_results"] == 5
50
+
51
+ # Verify result
52
+ assert "Test Result" in result
53
+
54
+ @patch("langchain_cloro.tools.initialize_client")
55
+ def test_run_with_http_error(self, mock_init: MagicMock) -> None:
56
+ """Test handling of HTTP errors."""
57
+ import httpx
58
+
59
+ mock_client = MagicMock()
60
+ mock_client.get.side_effect = httpx.HTTPStatusError(
61
+ "404 Not Found", request=MagicMock(), response=MagicMock()
62
+ )
63
+
64
+ mock_init.return_value = {
65
+ "cloro_api_key": "test-key",
66
+ "client": mock_client,
67
+ }
68
+
69
+ tool = CloroSearchRun(cloro_api_key="test-api-key")
70
+ result = tool._run(query="test query")
71
+
72
+ assert "Error searching with Cloro" in result
73
+
74
+ @patch("langchain_cloro.tools.initialize_client")
75
+ def test_run_with_unexpected_error(self, mock_init: MagicMock) -> None:
76
+ """Test handling of unexpected errors."""
77
+ mock_client = MagicMock()
78
+ mock_client.get.side_effect = Exception("Unexpected error")
79
+
80
+ mock_init.return_value = {
81
+ "cloro_api_key": "test-key",
82
+ "client": mock_client,
83
+ }
84
+
85
+ tool = CloroSearchRun(cloro_api_key="test-api-key")
86
+ result = tool._run(query="test query")
87
+
88
+ assert "Unexpected error" in result
89
+
90
+ @patch("langchain_cloro.tools.initialize_client")
91
+ def test_run_with_additional_params(self, mock_init: MagicMock) -> None:
92
+ """Test search with additional parameters."""
93
+ mock_client = MagicMock()
94
+ mock_response = MagicMock()
95
+ mock_response.json.return_value = {"results": []}
96
+ mock_response.raise_for_status = MagicMock()
97
+ mock_client.get.return_value = mock_response
98
+
99
+ mock_init.return_value = {
100
+ "cloro_api_key": "test-key",
101
+ "client": mock_client,
102
+ }
103
+
104
+ tool = CloroSearchRun(cloro_api_key="test-api-key")
105
+ result = tool._run(
106
+ query="test query",
107
+ num_results=10,
108
+ domain="example.com",
109
+ language="en",
110
+ )
111
+
112
+ # Verify additional params were passed
113
+ call_args = mock_client.get.call_args
114
+ assert call_args[1]["params"]["domain"] == "example.com"
115
+ assert call_args[1]["params"]["language"] == "en"
116
+
117
+ @patch("langchain_cloro.tools.initialize_client")
118
+ def test_default_timeout(self, mock_init: MagicMock) -> None:
119
+ """Test default timeout is set correctly."""
120
+ mock_init.return_value = {
121
+ "cloro_api_key": "test-key",
122
+ "client": MagicMock(),
123
+ }
124
+
125
+ tool = CloroSearchRun(cloro_api_key="test-api-key")
126
+
127
+ # Check that initialize_client was called with default timeout
128
+ mock_init.assert_called_once()
129
+ call_kwargs = mock_init.call_args[1]
130
+ assert call_kwargs["timeout"] == 10.0
131
+
132
+ @patch("langchain_cloro.tools.initialize_client")
133
+ def test_custom_timeout(self, mock_init: MagicMock) -> None:
134
+ """Test custom timeout can be set."""
135
+ mock_init.return_value = {
136
+ "cloro_api_key": "test-key",
137
+ "client": MagicMock(),
138
+ }
139
+
140
+ tool = CloroSearchRun(cloro_api_key="test-api-key", timeout=30.0)
141
+
142
+ # Check that initialize_client was called with custom timeout
143
+ mock_init.assert_called_once()
144
+ call_kwargs = mock_init.call_args[1]
145
+ assert call_kwargs["timeout"] == 30.0