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.
- langchain_cloro-0.1.0/.env.example +3 -0
- langchain_cloro-0.1.0/.gitignore +64 -0
- langchain_cloro-0.1.0/LICENSE +21 -0
- langchain_cloro-0.1.0/Makefile +28 -0
- langchain_cloro-0.1.0/PKG-INFO +166 -0
- langchain_cloro-0.1.0/README.md +150 -0
- langchain_cloro-0.1.0/langchain_cloro/__init__.py +8 -0
- langchain_cloro-0.1.0/langchain_cloro/_utilities.py +55 -0
- langchain_cloro-0.1.0/langchain_cloro/py.typed +0 -0
- langchain_cloro-0.1.0/langchain_cloro/tools.py +119 -0
- langchain_cloro-0.1.0/pyproject.toml +118 -0
- langchain_cloro-0.1.0/tests/__init__.py +0 -0
- langchain_cloro-0.1.0/tests/unit_tests/__init__.py +0 -0
- langchain_cloro-0.1.0/tests/unit_tests/test_tools.py +145 -0
|
@@ -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,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
|