langchain-oceanbase 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_oceanbase-0.1.0/LICENSE +21 -0
- langchain_oceanbase-0.1.0/PKG-INFO +55 -0
- langchain_oceanbase-0.1.0/README.md +33 -0
- langchain_oceanbase-0.1.0/langchain_oceanbase/__init__.py +15 -0
- langchain_oceanbase-0.1.0/langchain_oceanbase/py.typed +0 -0
- langchain_oceanbase-0.1.0/langchain_oceanbase/vectorstores.py +724 -0
- langchain_oceanbase-0.1.0/pyproject.toml +74 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 LangChain, Inc.
|
|
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,55 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: langchain-oceanbase
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: An integration package connecting OceanBase and LangChain
|
|
5
|
+
License: MIT
|
|
6
|
+
Author: shanhaikang.shk
|
|
7
|
+
Author-email: shanhaikang.shk@oceanbase.com
|
|
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
|
+
Requires-Dist: langchain-core (>=0.3.15,<0.4.0)
|
|
17
|
+
Requires-Dist: pyobvector (>=0.1.17,<0.2.0)
|
|
18
|
+
Project-URL: Release Notes, https://github.com/langchain-ai/langchain/releases?q=tag%3A%22oceanbase%3D%3D0%22&expanded=true
|
|
19
|
+
Project-URL: Source Code, https://github.com/langchain-ai/langchain/tree/master/libs/partners/oceanbase
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
|
|
22
|
+
# langchain-oceanbase
|
|
23
|
+
|
|
24
|
+
This package contains the LangChain integration with OceanBase.
|
|
25
|
+
|
|
26
|
+
[OceanBase Database](https://github.com/oceanbase/oceanbase) is a distributed relational database.
|
|
27
|
+
It is developed entirely by Ant Group. The OceanBase Database is built on a common server cluster.
|
|
28
|
+
Based on the Paxos protocol and its distributed structure, the OceanBase Database provides high availability and linear scalability.
|
|
29
|
+
|
|
30
|
+
OceanBase currently has the ability to store vectors. Users can easily perform the following operations with SQL:
|
|
31
|
+
|
|
32
|
+
- Create a table containing vector type fields;
|
|
33
|
+
- Create a vector index table based on the HNSW algorithm;
|
|
34
|
+
- Perform vector approximate nearest neighbor queries;
|
|
35
|
+
- ...
|
|
36
|
+
|
|
37
|
+
## Installation
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
pip install -U langchain-oceanbase
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
We recommend using Docker to deploy OceanBase:
|
|
44
|
+
|
|
45
|
+
```shell
|
|
46
|
+
docker run --name=ob433 -e MODE=slim -p 2881:2881 -d oceanbase/oceanbase-ce:4.3.3.0-100000132024100711
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
[More methods to deploy OceanBase cluster](https://github.com/oceanbase/oceanbase-doc/blob/V4.3.1/en-US/400.deploy/500.deploy-oceanbase-database-community-edition/100.deployment-overview.md)
|
|
50
|
+
|
|
51
|
+
### Usage
|
|
52
|
+
|
|
53
|
+
For a more detailed walkthrough of the OceanBase Wrapper, see [this notebook](./docs/vectorstores.ipynb)
|
|
54
|
+
|
|
55
|
+
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# langchain-oceanbase
|
|
2
|
+
|
|
3
|
+
This package contains the LangChain integration with OceanBase.
|
|
4
|
+
|
|
5
|
+
[OceanBase Database](https://github.com/oceanbase/oceanbase) is a distributed relational database.
|
|
6
|
+
It is developed entirely by Ant Group. The OceanBase Database is built on a common server cluster.
|
|
7
|
+
Based on the Paxos protocol and its distributed structure, the OceanBase Database provides high availability and linear scalability.
|
|
8
|
+
|
|
9
|
+
OceanBase currently has the ability to store vectors. Users can easily perform the following operations with SQL:
|
|
10
|
+
|
|
11
|
+
- Create a table containing vector type fields;
|
|
12
|
+
- Create a vector index table based on the HNSW algorithm;
|
|
13
|
+
- Perform vector approximate nearest neighbor queries;
|
|
14
|
+
- ...
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
pip install -U langchain-oceanbase
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
We recommend using Docker to deploy OceanBase:
|
|
23
|
+
|
|
24
|
+
```shell
|
|
25
|
+
docker run --name=ob433 -e MODE=slim -p 2881:2881 -d oceanbase/oceanbase-ce:4.3.3.0-100000132024100711
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
[More methods to deploy OceanBase cluster](https://github.com/oceanbase/oceanbase-doc/blob/V4.3.1/en-US/400.deploy/500.deploy-oceanbase-database-community-edition/100.deployment-overview.md)
|
|
29
|
+
|
|
30
|
+
### Usage
|
|
31
|
+
|
|
32
|
+
For a more detailed walkthrough of the OceanBase Wrapper, see [this notebook](./docs/vectorstores.ipynb)
|
|
33
|
+
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from importlib import metadata
|
|
2
|
+
|
|
3
|
+
from langchain_oceanbase.vectorstores import OceanbaseVectorStore
|
|
4
|
+
|
|
5
|
+
try:
|
|
6
|
+
__version__ = metadata.version(__package__)
|
|
7
|
+
except metadata.PackageNotFoundError:
|
|
8
|
+
# Case where package metadata is not available.
|
|
9
|
+
__version__ = ""
|
|
10
|
+
del metadata # optional, avoids polluting the results of dir(__package__)
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
"OceanbaseVectorStore",
|
|
14
|
+
"__version__",
|
|
15
|
+
]
|
|
File without changes
|
|
@@ -0,0 +1,724 @@
|
|
|
1
|
+
"""Oceanbase vector stores."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import logging
|
|
7
|
+
import math
|
|
8
|
+
import traceback
|
|
9
|
+
import uuid
|
|
10
|
+
from typing import Any, Callable, Iterable, List, Optional, Sequence, Tuple
|
|
11
|
+
|
|
12
|
+
import numpy as np
|
|
13
|
+
from langchain_core.documents import Document
|
|
14
|
+
from langchain_core.embeddings import Embeddings
|
|
15
|
+
from langchain_core.vectorstores import VectorStore
|
|
16
|
+
from pyobvector import VECTOR, ObVecClient # type: ignore
|
|
17
|
+
from sqlalchemy import JSON, Column, String, Table, func, text
|
|
18
|
+
from sqlalchemy.dialects.mysql import LONGTEXT
|
|
19
|
+
|
|
20
|
+
logger = logging.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
DEFAULT_OCEANBASE_CONNECTION = {
|
|
23
|
+
"host": "localhost",
|
|
24
|
+
"port": "2881",
|
|
25
|
+
"user": "root@test",
|
|
26
|
+
"password": "",
|
|
27
|
+
"db_name": "test",
|
|
28
|
+
}
|
|
29
|
+
DEFAULT_OCEANBASE_VECTOR_TABLE_NAME = "langchain_vector"
|
|
30
|
+
DEFAULT_OCEANBASE_HNSW_BUILD_PARAM = {"M": 16, "efConstruction": 256}
|
|
31
|
+
DEFAULT_OCEANBASE_HNSW_SEARCH_PARAM = {"efSearch": 64}
|
|
32
|
+
OCEANBASE_SUPPORTED_VECTOR_INDEX_TYPE = "HNSW"
|
|
33
|
+
DEFAULT_OCEANBASE_VECTOR_METRIC_TYPE = "l2"
|
|
34
|
+
|
|
35
|
+
DEFAULT_METADATA_FIELD = "metadata"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _euclidean_similarity(distance: float) -> float:
|
|
39
|
+
return 1.0 - distance / math.sqrt(2)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _neg_inner_product_similarity(distance: float) -> float:
|
|
43
|
+
return -distance
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class OceanbaseVectorStore(VectorStore):
|
|
47
|
+
"""Oceanbase vector store integration.
|
|
48
|
+
|
|
49
|
+
Setup:
|
|
50
|
+
Install ``langchain-oceanbase`` and deploy a standalone OceanBase server with docker.
|
|
51
|
+
|
|
52
|
+
.. code-block:: bash
|
|
53
|
+
|
|
54
|
+
pip install -U langchain-oceanbase
|
|
55
|
+
docker run --name=ob433 -e MODE=mini -e OB_SERVER_IP=127.0.0.1 -p 2881:2881 -d quay.io/oceanbase/oceanbase-ce:4.3.3.1-101000012024102216
|
|
56
|
+
|
|
57
|
+
More methods to deploy OceanBase cluster:
|
|
58
|
+
https://github.com/oceanbase/oceanbase-doc/blob/V4.3.1/en-US/400.deploy/500.deploy-oceanbase-database-community-edition/100.deployment-overview.md
|
|
59
|
+
|
|
60
|
+
Key init args — indexing params:
|
|
61
|
+
vidx_metric_type: str
|
|
62
|
+
Metric method of distance between vectors.
|
|
63
|
+
This parameter takes values in `l2` and `inner_product`. Defaults to `l2`.
|
|
64
|
+
vidx_algo_params: Optional[dict]
|
|
65
|
+
Which index params to use. Now OceanBase supports HNSW only.
|
|
66
|
+
Refer to `DEFAULT_OCEANBASE_HNSW_BUILD_PARAM` for example.
|
|
67
|
+
drop_old: bool
|
|
68
|
+
Whether to drop the current table. Defaults to False.
|
|
69
|
+
primary_field: str
|
|
70
|
+
Name of the primary key column. Defaults to "id".
|
|
71
|
+
vector_field: str
|
|
72
|
+
Name of the vector column. Defaults to "embedding".
|
|
73
|
+
text_field: str
|
|
74
|
+
Name of the text column. Defaults to "document".
|
|
75
|
+
metadata_field: Optional[str]
|
|
76
|
+
Name of the metadata column. Defaults to "metadata".
|
|
77
|
+
When `metadata_field` is specified, the document's metadata will store as json.
|
|
78
|
+
vidx_name: str
|
|
79
|
+
Name of the vector index table.
|
|
80
|
+
partitions: ObPartition
|
|
81
|
+
Partition strategy of table.
|
|
82
|
+
Refer to `pyobvector`'s documentation for more examples.
|
|
83
|
+
extra_columns: Optional[List[Column]]
|
|
84
|
+
Extra sqlalchemy columns to add to the table.
|
|
85
|
+
|
|
86
|
+
Key init args — client params:
|
|
87
|
+
embedding_function: Embeddings
|
|
88
|
+
Function used to embed the text.
|
|
89
|
+
table_name: str
|
|
90
|
+
Which table name to use. Defaults to "langchain_vector".
|
|
91
|
+
connection_args: Optional[dict[str, any]]
|
|
92
|
+
The connection args used for this class comes in the form of a dict. Refer to
|
|
93
|
+
`DEFAULT_OCEANBASE_CONNECTION` for example.
|
|
94
|
+
|
|
95
|
+
Instantiate:
|
|
96
|
+
.. code-block:: python
|
|
97
|
+
|
|
98
|
+
from langchain_oceanbase.vectorstores import OceanbaseVectorStore
|
|
99
|
+
from langchain_community.embeddings import DashScopeEmbeddings
|
|
100
|
+
|
|
101
|
+
DASHSCOPE_API = os.environ.get("DASHSCOPE_API_KEY", "")
|
|
102
|
+
connection_args = {
|
|
103
|
+
"host": "127.0.0.1",
|
|
104
|
+
"port": "2881",
|
|
105
|
+
"user": "root@test",
|
|
106
|
+
"password": "",
|
|
107
|
+
"db_name": "test",
|
|
108
|
+
}
|
|
109
|
+
embeddings = DashScopeEmbeddings(
|
|
110
|
+
model="text-embedding-v1", dashscope_api_key=DASHSCOPE_API
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
vector_store = OceanbaseVectorStore(
|
|
114
|
+
embedding_function=embeddings,
|
|
115
|
+
table_name="langchain_vector",
|
|
116
|
+
connection_args=connection_args,
|
|
117
|
+
vidx_metric_type="l2",
|
|
118
|
+
drop_old=True,
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
Add Documents:
|
|
122
|
+
.. code-block:: python
|
|
123
|
+
|
|
124
|
+
from langchain_core.documents import Document
|
|
125
|
+
|
|
126
|
+
document_1 = Document(page_content="foo", metadata={"baz": "bar"})
|
|
127
|
+
document_2 = Document(page_content="thud", metadata={"bar": "baz"})
|
|
128
|
+
document_3 = Document(page_content="i will be deleted :(")
|
|
129
|
+
|
|
130
|
+
documents = [document_1, document_2, document_3]
|
|
131
|
+
ids = ["1", "2", "3"]
|
|
132
|
+
vector_store.add_documents(documents=documents, ids=ids)
|
|
133
|
+
|
|
134
|
+
Delete Documents:
|
|
135
|
+
.. code-block:: python
|
|
136
|
+
|
|
137
|
+
vector_store.delete(ids=["3"])
|
|
138
|
+
|
|
139
|
+
Search:
|
|
140
|
+
.. code-block:: python
|
|
141
|
+
|
|
142
|
+
results = vector_store.similarity_search(query="thud",k=1)
|
|
143
|
+
for doc in results:
|
|
144
|
+
print(f"* {doc.page_content} [{doc.metadata}]")
|
|
145
|
+
|
|
146
|
+
Search with filter:
|
|
147
|
+
.. code-block:: python
|
|
148
|
+
|
|
149
|
+
results = vector_store.similarity_search(query="thud",k=1,filter={"bar": "baz"})
|
|
150
|
+
for doc in results:
|
|
151
|
+
print(f"* {doc.page_content} [{doc.metadata}]")
|
|
152
|
+
|
|
153
|
+
Search with score:
|
|
154
|
+
.. code-block:: python
|
|
155
|
+
|
|
156
|
+
results = vector_store.similarity_search_with_score(query="qux",k=1)
|
|
157
|
+
for doc, score in results:
|
|
158
|
+
print(f"* [SIM={score:3f}] {doc.page_content} [{doc.metadata}]")
|
|
159
|
+
|
|
160
|
+
Use as Retriever:
|
|
161
|
+
.. code-block:: python
|
|
162
|
+
|
|
163
|
+
retriever = vector_store.as_retriever(
|
|
164
|
+
search_kwargs={"k": 1, "fetch_k": 2, "lambda_mult": 0.5},
|
|
165
|
+
)
|
|
166
|
+
retriever.invoke("thud")
|
|
167
|
+
|
|
168
|
+
""" # noqa: E501
|
|
169
|
+
|
|
170
|
+
def __init__( # type: ignore[no-untyped-def]
|
|
171
|
+
self,
|
|
172
|
+
embedding_function: Embeddings,
|
|
173
|
+
table_name: str = DEFAULT_OCEANBASE_VECTOR_TABLE_NAME,
|
|
174
|
+
connection_args: Optional[dict[str, Any]] = None,
|
|
175
|
+
vidx_metric_type: str = DEFAULT_OCEANBASE_VECTOR_METRIC_TYPE,
|
|
176
|
+
vidx_algo_params: Optional[dict] = None,
|
|
177
|
+
drop_old: bool = False,
|
|
178
|
+
*,
|
|
179
|
+
primary_field: str = "id",
|
|
180
|
+
vector_field: str = "embedding",
|
|
181
|
+
text_field: str = "document",
|
|
182
|
+
metadata_field: Optional[str] = DEFAULT_METADATA_FIELD,
|
|
183
|
+
vidx_name: str = "vidx",
|
|
184
|
+
partitions: Optional[Any] = None,
|
|
185
|
+
extra_columns: Optional[List[Column]] = None,
|
|
186
|
+
normalize: bool = False,
|
|
187
|
+
embedding_dim: Optional[int] = None,
|
|
188
|
+
**kwargs,
|
|
189
|
+
):
|
|
190
|
+
"""Initialize the OceanBase vector store."""
|
|
191
|
+
|
|
192
|
+
self.embedding_function = embedding_function
|
|
193
|
+
self.table_name = table_name
|
|
194
|
+
self.connection_args = (
|
|
195
|
+
connection_args
|
|
196
|
+
if connection_args is not None
|
|
197
|
+
else DEFAULT_OCEANBASE_CONNECTION
|
|
198
|
+
)
|
|
199
|
+
self.extra_columns = extra_columns
|
|
200
|
+
self.normalize = normalize
|
|
201
|
+
self._create_client(**kwargs)
|
|
202
|
+
assert self.obvector is not None
|
|
203
|
+
|
|
204
|
+
self.vidx_metric_type = vidx_metric_type.lower()
|
|
205
|
+
if self.vidx_metric_type not in ("l2", "inner_product"):
|
|
206
|
+
raise ValueError(
|
|
207
|
+
"`vidx_metric_type` should be set in `l2`/`inner_product`."
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
self.vidx_algo_params = (
|
|
211
|
+
vidx_algo_params
|
|
212
|
+
if vidx_algo_params is not None
|
|
213
|
+
else DEFAULT_OCEANBASE_HNSW_BUILD_PARAM
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
self.drop_old = drop_old
|
|
217
|
+
self.primary_field = primary_field
|
|
218
|
+
self.vector_field = vector_field
|
|
219
|
+
self.text_field = text_field
|
|
220
|
+
self.metadata_field = metadata_field or DEFAULT_METADATA_FIELD
|
|
221
|
+
self.vidx_name = vidx_name
|
|
222
|
+
self.partition = partitions
|
|
223
|
+
self.hnsw_ef_search = -1
|
|
224
|
+
|
|
225
|
+
if self.drop_old:
|
|
226
|
+
self.obvector.drop_table_if_exist(self.table_name)
|
|
227
|
+
|
|
228
|
+
if not self.obvector.check_table_exists(self.table_name):
|
|
229
|
+
if embedding_dim is not None:
|
|
230
|
+
self._create_table_with_index_by_embedding_dim(embedding_dim)
|
|
231
|
+
self._load_table()
|
|
232
|
+
else:
|
|
233
|
+
self._load_table()
|
|
234
|
+
|
|
235
|
+
@property
|
|
236
|
+
def embeddings(self) -> Embeddings:
|
|
237
|
+
return self.embedding_function
|
|
238
|
+
|
|
239
|
+
def _create_client(self, **kwargs): # type: ignore[no-untyped-def]
|
|
240
|
+
host = self.connection_args.get("host", "localhost")
|
|
241
|
+
port = self.connection_args.get("port", "2881")
|
|
242
|
+
user = self.connection_args.get("user", "root@test")
|
|
243
|
+
password = self.connection_args.get("password", "")
|
|
244
|
+
db_name = self.connection_args.get("db_name", "test")
|
|
245
|
+
|
|
246
|
+
self.obvector = ObVecClient(
|
|
247
|
+
uri=host + ":" + port,
|
|
248
|
+
user=user,
|
|
249
|
+
password=password,
|
|
250
|
+
db_name=db_name,
|
|
251
|
+
**kwargs,
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
def _load_table(self) -> None:
|
|
255
|
+
table = Table(
|
|
256
|
+
self.table_name,
|
|
257
|
+
self.obvector.metadata_obj,
|
|
258
|
+
autoload_with=self.obvector.engine,
|
|
259
|
+
)
|
|
260
|
+
column_names = [column.name for column in table.columns]
|
|
261
|
+
optional_len = len(self.extra_columns or []) + 1
|
|
262
|
+
assert len(column_names) == (3 + optional_len)
|
|
263
|
+
|
|
264
|
+
logging.info(f"load exist table with {column_names} columns")
|
|
265
|
+
self.primary_field = column_names[0]
|
|
266
|
+
self.vector_field = column_names[1]
|
|
267
|
+
self.text_field = column_names[2]
|
|
268
|
+
self.metadata_field = column_names[3]
|
|
269
|
+
|
|
270
|
+
def _create_table_with_index_by_embedding_dim(self, dim: int) -> None:
|
|
271
|
+
cols = [
|
|
272
|
+
Column(
|
|
273
|
+
self.primary_field, String(4096), primary_key=True, autoincrement=False
|
|
274
|
+
),
|
|
275
|
+
Column(self.vector_field, VECTOR(dim)),
|
|
276
|
+
Column(self.text_field, LONGTEXT),
|
|
277
|
+
Column(self.metadata_field, JSON),
|
|
278
|
+
]
|
|
279
|
+
if self.extra_columns is not None:
|
|
280
|
+
cols.extend(self.extra_columns)
|
|
281
|
+
|
|
282
|
+
vidx_params = self.obvector.prepare_index_params()
|
|
283
|
+
vidx_params.add_index(
|
|
284
|
+
field_name=self.vector_field,
|
|
285
|
+
index_type=OCEANBASE_SUPPORTED_VECTOR_INDEX_TYPE,
|
|
286
|
+
index_name=self.vidx_name,
|
|
287
|
+
metric_type=self.vidx_metric_type,
|
|
288
|
+
params=self.vidx_algo_params,
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
self.obvector.create_table_with_index_params(
|
|
292
|
+
table_name=self.table_name,
|
|
293
|
+
columns=cols,
|
|
294
|
+
indexes=None,
|
|
295
|
+
vidxs=vidx_params,
|
|
296
|
+
partitions=self.partition,
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
def _create_table_with_index(self, embeddings: list) -> None:
|
|
300
|
+
if self.obvector.check_table_exists(self.table_name):
|
|
301
|
+
self._load_table()
|
|
302
|
+
return
|
|
303
|
+
|
|
304
|
+
dim = len(embeddings[0])
|
|
305
|
+
self._create_table_with_index_by_embedding_dim(dim)
|
|
306
|
+
|
|
307
|
+
def _parse_metric_type_str_to_dist_func(self) -> Any:
|
|
308
|
+
if self.vidx_metric_type == "l2":
|
|
309
|
+
return func.l2_distance
|
|
310
|
+
if self.vidx_metric_type == "cosine":
|
|
311
|
+
return func.cosine_distance
|
|
312
|
+
if self.vidx_metric_type == "inner_product":
|
|
313
|
+
return func.negative_inner_product
|
|
314
|
+
raise ValueError(f"Invalid vector index metric type: {self.vidx_metric_type}")
|
|
315
|
+
|
|
316
|
+
def _normalize(self, vector: List[float]) -> List[float]:
|
|
317
|
+
arr = np.array(vector)
|
|
318
|
+
norm = np.linalg.norm(arr)
|
|
319
|
+
arr = arr / norm
|
|
320
|
+
return arr.tolist()
|
|
321
|
+
|
|
322
|
+
def add_texts(
|
|
323
|
+
self,
|
|
324
|
+
texts: Iterable[str],
|
|
325
|
+
metadatas: Optional[List[dict]] = None,
|
|
326
|
+
batch_size: int = 1000,
|
|
327
|
+
*,
|
|
328
|
+
ids: Optional[List[str]] = None,
|
|
329
|
+
extras: Optional[List[dict]] = None,
|
|
330
|
+
partition_name: Optional[str] = None,
|
|
331
|
+
**kwargs: Any,
|
|
332
|
+
) -> List[str]:
|
|
333
|
+
"""Insert text data into OceanBase.
|
|
334
|
+
|
|
335
|
+
Inserting data when the table has not be created yet will result
|
|
336
|
+
in creating a new table. The data of the first record decides
|
|
337
|
+
the schema of the new table, the dim is extracted from the first
|
|
338
|
+
embedding.
|
|
339
|
+
|
|
340
|
+
Args:
|
|
341
|
+
texts (Iterable[str]): The texts to embed. OceanBase use a `LONGTEXT`
|
|
342
|
+
type column to hold the data.
|
|
343
|
+
metadatas (Optional[List[dict]]): Metadata dicts attached to each of
|
|
344
|
+
the texts. Defaults to None.
|
|
345
|
+
batch_size (int, optional): Batch size to use for insertion.
|
|
346
|
+
Defaults to 1000.
|
|
347
|
+
ids (Optional[List[str]]): List of text ids.
|
|
348
|
+
extras (Optional[List[dict]]): Extra data to store in the table.
|
|
349
|
+
partition_name (Optional[str]): The partition name to insert data into.
|
|
350
|
+
|
|
351
|
+
Raises:
|
|
352
|
+
Exception: Failure to add texts
|
|
353
|
+
|
|
354
|
+
Returns:
|
|
355
|
+
List[str]: The resulting ids for each inserted element.
|
|
356
|
+
"""
|
|
357
|
+
texts = list(texts)
|
|
358
|
+
|
|
359
|
+
try:
|
|
360
|
+
embeddings = self.embedding_function.embed_documents(texts)
|
|
361
|
+
except NotImplementedError:
|
|
362
|
+
embeddings = [self.embedding_function.embed_query(x) for x in texts]
|
|
363
|
+
|
|
364
|
+
total_count = len(embeddings)
|
|
365
|
+
if total_count == 0:
|
|
366
|
+
return []
|
|
367
|
+
|
|
368
|
+
self._create_table_with_index(embeddings)
|
|
369
|
+
|
|
370
|
+
if ids is None:
|
|
371
|
+
ids = [str(uuid.uuid4()) for _ in texts]
|
|
372
|
+
|
|
373
|
+
if not metadatas:
|
|
374
|
+
metadatas = [{} for _ in texts]
|
|
375
|
+
|
|
376
|
+
extra_data = extras or [{} for _ in texts]
|
|
377
|
+
|
|
378
|
+
pks: list[str] = []
|
|
379
|
+
for i in range(0, total_count, batch_size):
|
|
380
|
+
data = [
|
|
381
|
+
{
|
|
382
|
+
self.primary_field: id,
|
|
383
|
+
self.vector_field: (
|
|
384
|
+
embedding if not self.normalize else self._normalize(embedding)
|
|
385
|
+
),
|
|
386
|
+
self.text_field: text,
|
|
387
|
+
self.metadata_field: metadata,
|
|
388
|
+
**extra,
|
|
389
|
+
}
|
|
390
|
+
for id, embedding, text, metadata, extra in zip(
|
|
391
|
+
ids[i : i + batch_size],
|
|
392
|
+
embeddings[i : i + batch_size],
|
|
393
|
+
texts[i : i + batch_size],
|
|
394
|
+
metadatas[i : i + batch_size],
|
|
395
|
+
extra_data[i : i + batch_size],
|
|
396
|
+
)
|
|
397
|
+
]
|
|
398
|
+
try:
|
|
399
|
+
self.obvector.upsert(
|
|
400
|
+
table_name=self.table_name,
|
|
401
|
+
data=data,
|
|
402
|
+
partition_name=(partition_name or ""),
|
|
403
|
+
)
|
|
404
|
+
pks.extend(ids[i : i + batch_size])
|
|
405
|
+
except Exception:
|
|
406
|
+
traceback.print_exc()
|
|
407
|
+
logger.error(
|
|
408
|
+
f"Failed to insert batch starting at entity:[{i}, {i + batch_size})"
|
|
409
|
+
)
|
|
410
|
+
return pks
|
|
411
|
+
|
|
412
|
+
def delete( # type: ignore[no-untyped-def]
|
|
413
|
+
self, ids: Optional[List[str]] = None, fltr: Optional[str] = None, **kwargs
|
|
414
|
+
) -> Optional[bool]:
|
|
415
|
+
"""Delete by vector ID or boolean expression.
|
|
416
|
+
|
|
417
|
+
Args:
|
|
418
|
+
ids (Optional[List[str]]): List of ids to delete.
|
|
419
|
+
fltr (Optional[str]): Boolean filter that specifies the entities to delete.
|
|
420
|
+
"""
|
|
421
|
+
self.obvector.delete(
|
|
422
|
+
table_name=self.table_name,
|
|
423
|
+
ids=ids,
|
|
424
|
+
where_clause=([text(fltr)] if fltr is not None else None),
|
|
425
|
+
)
|
|
426
|
+
return None
|
|
427
|
+
|
|
428
|
+
def get_by_ids(self, ids: Sequence[str], /) -> list[Document]:
|
|
429
|
+
"""Get entities by vector ID.
|
|
430
|
+
|
|
431
|
+
Args:
|
|
432
|
+
ids (Optional[List[str]]): List of ids to get.
|
|
433
|
+
|
|
434
|
+
Returns:
|
|
435
|
+
List[Document]: Document results for search.
|
|
436
|
+
"""
|
|
437
|
+
res = self.obvector.get(
|
|
438
|
+
table_name=self.table_name,
|
|
439
|
+
ids=ids,
|
|
440
|
+
output_column_name=[
|
|
441
|
+
self.text_field,
|
|
442
|
+
self.metadata_field,
|
|
443
|
+
self.primary_field,
|
|
444
|
+
],
|
|
445
|
+
)
|
|
446
|
+
return [
|
|
447
|
+
Document(
|
|
448
|
+
id=r[2],
|
|
449
|
+
page_content=r[0],
|
|
450
|
+
metadata=(
|
|
451
|
+
json.loads(r[1])
|
|
452
|
+
if isinstance(r[1], str) or isinstance(r[1], bytes)
|
|
453
|
+
else r[1]
|
|
454
|
+
),
|
|
455
|
+
)
|
|
456
|
+
for r in res.fetchall()
|
|
457
|
+
]
|
|
458
|
+
|
|
459
|
+
def similarity_search(
|
|
460
|
+
self,
|
|
461
|
+
query: str,
|
|
462
|
+
k: int = 10,
|
|
463
|
+
param: Optional[dict] = None,
|
|
464
|
+
fltr: Optional[str] = None,
|
|
465
|
+
**kwargs: Any,
|
|
466
|
+
) -> list[Document]:
|
|
467
|
+
"""Perform a similarity search against the query string.
|
|
468
|
+
|
|
469
|
+
Args:
|
|
470
|
+
query (str): The text to search.
|
|
471
|
+
k (int, optional): How many results to return. Defaults to 10.
|
|
472
|
+
param (Optional[dict]): The search params for the index type.
|
|
473
|
+
Defaults to None. Refer to `DEFAULT_OCEANBASE_HNSW_SEARCH_PARAM`
|
|
474
|
+
for example.
|
|
475
|
+
fltr (Optional[str]): Boolean filter. Defaults to None.
|
|
476
|
+
|
|
477
|
+
Returns:
|
|
478
|
+
List[Document]: Document results for search.
|
|
479
|
+
"""
|
|
480
|
+
if k < 0:
|
|
481
|
+
return []
|
|
482
|
+
|
|
483
|
+
query_vector = self.embedding_function.embed_query(query)
|
|
484
|
+
return self.similarity_search_by_vector(
|
|
485
|
+
embedding=query_vector, k=k, param=param, fltr=fltr, **kwargs
|
|
486
|
+
)
|
|
487
|
+
|
|
488
|
+
def similarity_search_with_score(
|
|
489
|
+
self,
|
|
490
|
+
query: str,
|
|
491
|
+
k: int = 10,
|
|
492
|
+
param: Optional[dict] = None,
|
|
493
|
+
fltr: Optional[str] = None,
|
|
494
|
+
**kwargs: Any,
|
|
495
|
+
) -> List[Tuple[Document, float]]:
|
|
496
|
+
"""Perform a search on a query string and return results with score.
|
|
497
|
+
|
|
498
|
+
Args:
|
|
499
|
+
query (str): The text being searched.
|
|
500
|
+
k (int, optional): How many results to return. Defaults to 10.
|
|
501
|
+
param (Optional[dict]): The search params for the index type.
|
|
502
|
+
Defaults to None. Refer to `DEFAULT_OCEANBASE_HNSW_SEARCH_PARAM`
|
|
503
|
+
for example.
|
|
504
|
+
fltr (Optional[str]): Boolean filter. Defaults to None.
|
|
505
|
+
|
|
506
|
+
Returns:
|
|
507
|
+
List[Tuple[Document, float]]: Document results with score for search.
|
|
508
|
+
"""
|
|
509
|
+
if k < 0:
|
|
510
|
+
return []
|
|
511
|
+
|
|
512
|
+
query_vector = self.embedding_function.embed_query(query)
|
|
513
|
+
return self.similarity_search_with_score_by_vector(
|
|
514
|
+
embedding=query_vector, k=k, param=param, fltr=fltr, **kwargs
|
|
515
|
+
)
|
|
516
|
+
|
|
517
|
+
def similarity_search_by_vector(
|
|
518
|
+
self,
|
|
519
|
+
embedding: List[float],
|
|
520
|
+
k: int = 4,
|
|
521
|
+
param: Optional[dict] = None,
|
|
522
|
+
fltr: Optional[str] = None,
|
|
523
|
+
**kwargs: Any,
|
|
524
|
+
) -> List[Document]:
|
|
525
|
+
"""Perform a similarity search against the query string.
|
|
526
|
+
|
|
527
|
+
Args:
|
|
528
|
+
embedding (List[float]): The embedding vector to search.
|
|
529
|
+
k (int, optional): How many results to return. Defaults to 10.
|
|
530
|
+
param (Optional[dict]): The search params for the index type.
|
|
531
|
+
Defaults to None. Refer to `DEFAULT_OCEANBASE_HNSW_SEARCH_PARAM`
|
|
532
|
+
for example.
|
|
533
|
+
fltr (Optional[str]): Boolean filter. Defaults to None.
|
|
534
|
+
|
|
535
|
+
Returns:
|
|
536
|
+
List[Document]: Document results for search.
|
|
537
|
+
"""
|
|
538
|
+
if k < 0:
|
|
539
|
+
return []
|
|
540
|
+
|
|
541
|
+
search_param = (
|
|
542
|
+
param if param is not None else DEFAULT_OCEANBASE_HNSW_SEARCH_PARAM
|
|
543
|
+
)
|
|
544
|
+
ef_search = search_param.get(
|
|
545
|
+
"efSearch", DEFAULT_OCEANBASE_HNSW_SEARCH_PARAM["efSearch"]
|
|
546
|
+
)
|
|
547
|
+
if ef_search != self.hnsw_ef_search:
|
|
548
|
+
self.obvector.set_ob_hnsw_ef_search(ef_search)
|
|
549
|
+
self.hnsw_ef_search = ef_search
|
|
550
|
+
|
|
551
|
+
res = self.obvector.ann_search(
|
|
552
|
+
table_name=self.table_name,
|
|
553
|
+
vec_data=(embedding if not self.normalize else self._normalize(embedding)),
|
|
554
|
+
vec_column_name=self.vector_field,
|
|
555
|
+
distance_func=self._parse_metric_type_str_to_dist_func(),
|
|
556
|
+
topk=k,
|
|
557
|
+
output_column_names=[
|
|
558
|
+
self.text_field,
|
|
559
|
+
self.metadata_field,
|
|
560
|
+
self.primary_field,
|
|
561
|
+
],
|
|
562
|
+
where_clause=([text(fltr)] if fltr is not None else None),
|
|
563
|
+
**kwargs,
|
|
564
|
+
)
|
|
565
|
+
return [
|
|
566
|
+
Document(
|
|
567
|
+
id=r[2],
|
|
568
|
+
page_content=r[0],
|
|
569
|
+
metadata=json.loads(r[1]),
|
|
570
|
+
)
|
|
571
|
+
for r in res.fetchall()
|
|
572
|
+
]
|
|
573
|
+
|
|
574
|
+
def similarity_search_with_score_by_vector(
|
|
575
|
+
self,
|
|
576
|
+
embedding: List[float],
|
|
577
|
+
k: int = 10,
|
|
578
|
+
param: Optional[dict] = None,
|
|
579
|
+
fltr: Optional[str] = None,
|
|
580
|
+
**kwargs: Any,
|
|
581
|
+
) -> List[Tuple[Document, float]]:
|
|
582
|
+
"""Perform a search on a query string and return results with score.
|
|
583
|
+
|
|
584
|
+
Args:
|
|
585
|
+
embedding (List[float]): The embedding vector being searched.
|
|
586
|
+
k (int, optional): The amount of results to return. Defaults to 10.
|
|
587
|
+
param (Optional[dict]): The search params for the index type.
|
|
588
|
+
Defaults to None. Refer to `DEFAULT_OCEANBASE_HNSW_SEARCH_PARAM`
|
|
589
|
+
for example.
|
|
590
|
+
fltr (Optional[str]): Boolean filter. Defaults to None.
|
|
591
|
+
|
|
592
|
+
Returns:
|
|
593
|
+
List[Tuple[Document, float]]: Document results with score for search.
|
|
594
|
+
"""
|
|
595
|
+
if k < 0:
|
|
596
|
+
return []
|
|
597
|
+
|
|
598
|
+
search_param = (
|
|
599
|
+
param if param is not None else DEFAULT_OCEANBASE_HNSW_SEARCH_PARAM
|
|
600
|
+
)
|
|
601
|
+
ef_search = search_param.get(
|
|
602
|
+
"efSearch", DEFAULT_OCEANBASE_HNSW_SEARCH_PARAM["efSearch"]
|
|
603
|
+
)
|
|
604
|
+
if ef_search != self.hnsw_ef_search:
|
|
605
|
+
self.obvector.set_ob_hnsw_ef_search(ef_search)
|
|
606
|
+
self.hnsw_ef_search = ef_search
|
|
607
|
+
|
|
608
|
+
res = self.obvector.ann_search(
|
|
609
|
+
table_name=self.table_name,
|
|
610
|
+
vec_data=(embedding if not self.normalize else self._normalize(embedding)),
|
|
611
|
+
vec_column_name=self.vector_field,
|
|
612
|
+
distance_func=self._parse_metric_type_str_to_dist_func(),
|
|
613
|
+
with_dist=True,
|
|
614
|
+
topk=k,
|
|
615
|
+
output_column_names=[
|
|
616
|
+
self.text_field,
|
|
617
|
+
self.metadata_field,
|
|
618
|
+
self.primary_field,
|
|
619
|
+
],
|
|
620
|
+
where_clause=([text(fltr)] if fltr is not None else None),
|
|
621
|
+
**kwargs,
|
|
622
|
+
)
|
|
623
|
+
return [
|
|
624
|
+
(
|
|
625
|
+
Document(
|
|
626
|
+
id=r[2],
|
|
627
|
+
page_content=r[0],
|
|
628
|
+
metadata=json.loads(r[1]),
|
|
629
|
+
),
|
|
630
|
+
r[3],
|
|
631
|
+
)
|
|
632
|
+
for r in res.fetchall()
|
|
633
|
+
]
|
|
634
|
+
|
|
635
|
+
def max_marginal_relevance_search(
|
|
636
|
+
self,
|
|
637
|
+
query: str,
|
|
638
|
+
k: int = 4,
|
|
639
|
+
fetch_k: int = 20,
|
|
640
|
+
lambda_mult: float = 0.5,
|
|
641
|
+
**kwargs: Any,
|
|
642
|
+
) -> List[Document]:
|
|
643
|
+
raise NotImplementedError
|
|
644
|
+
|
|
645
|
+
def max_marginal_relevance_search_by_vector(
|
|
646
|
+
self,
|
|
647
|
+
embedding: List[float],
|
|
648
|
+
k: int = 4,
|
|
649
|
+
fetch_k: int = 20,
|
|
650
|
+
lambda_mult: float = 0.5,
|
|
651
|
+
**kwargs: Any,
|
|
652
|
+
) -> List[Document]:
|
|
653
|
+
raise NotImplementedError
|
|
654
|
+
|
|
655
|
+
@classmethod
|
|
656
|
+
def from_texts(
|
|
657
|
+
cls,
|
|
658
|
+
texts: list[str],
|
|
659
|
+
embedding: Embeddings,
|
|
660
|
+
metadatas: Optional[list[dict]] = None,
|
|
661
|
+
table_name: str = DEFAULT_OCEANBASE_VECTOR_TABLE_NAME,
|
|
662
|
+
connection_args: Optional[dict[str, Any]] = None,
|
|
663
|
+
vidx_metric_type: str = DEFAULT_OCEANBASE_VECTOR_METRIC_TYPE,
|
|
664
|
+
vidx_algo_params: Optional[dict] = None,
|
|
665
|
+
drop_old: bool = False,
|
|
666
|
+
*,
|
|
667
|
+
ids: Optional[List[str]] = None,
|
|
668
|
+
extra_columns: Optional[List[Column]] = None,
|
|
669
|
+
normalize: bool = False,
|
|
670
|
+
extras: Optional[List[dict]] = None,
|
|
671
|
+
**kwargs: Any,
|
|
672
|
+
) -> "OceanbaseVectorStore":
|
|
673
|
+
"""Create a OceanBase table, indexes it with HNSW, and insert data.
|
|
674
|
+
|
|
675
|
+
Args:
|
|
676
|
+
texts (List[str]): Text data.
|
|
677
|
+
embedding (Embeddings): Embedding function.
|
|
678
|
+
metadatas (Optional[List[dict]]): Metadata for each text if it exists.
|
|
679
|
+
Defaults to None.
|
|
680
|
+
table_name (str): Table name to use. Defaults to "langchain_vector".
|
|
681
|
+
connection_args (Optional[dict[str, Any]]): Refer to
|
|
682
|
+
`DEFAULT_OCEANBASE_CONNECTION` for example.
|
|
683
|
+
vidx_metric_type (str): Metric method of distance between vectors.
|
|
684
|
+
This parameter takes values in `l2` and `inner_product`.
|
|
685
|
+
Defaults to `l2`.
|
|
686
|
+
vidx_algo_params (Optional[dict]): Which index params to use. Now OceanBase
|
|
687
|
+
supports HNSW only. Refer to `DEFAULT_OCEANBASE_HNSW_BUILD_PARAM`
|
|
688
|
+
for example.
|
|
689
|
+
drop_old (bool): Whether to drop the current table. Defaults
|
|
690
|
+
to False.
|
|
691
|
+
ids (Optional[List[str]]): List of text ids. Defaults to None.
|
|
692
|
+
extra_columns (Optional[List[Column]]): Extra columns to add to the table.
|
|
693
|
+
extras (Optional[List[dict]]): Extra data to insert. Defaults to None.
|
|
694
|
+
|
|
695
|
+
Returns:
|
|
696
|
+
OceanBase: OceanBase Vector Store
|
|
697
|
+
"""
|
|
698
|
+
oceanbase = cls(
|
|
699
|
+
embedding_function=embedding,
|
|
700
|
+
table_name=table_name,
|
|
701
|
+
connection_args=connection_args,
|
|
702
|
+
vidx_metric_type=vidx_metric_type,
|
|
703
|
+
vidx_algo_params=vidx_algo_params,
|
|
704
|
+
drop_old=drop_old,
|
|
705
|
+
extra_columns=extra_columns,
|
|
706
|
+
normalize=normalize,
|
|
707
|
+
**kwargs,
|
|
708
|
+
)
|
|
709
|
+
oceanbase.add_texts(texts, metadatas, ids=ids, extras=extras)
|
|
710
|
+
return oceanbase
|
|
711
|
+
|
|
712
|
+
def _select_relevance_score_fn(self) -> Callable[[float], float]:
|
|
713
|
+
"""
|
|
714
|
+
Select the relevance score function based on the distance strategy.
|
|
715
|
+
"""
|
|
716
|
+
if self.vidx_metric_type == "inner_product":
|
|
717
|
+
return _neg_inner_product_similarity
|
|
718
|
+
elif self.vidx_metric_type == "l2":
|
|
719
|
+
return _euclidean_similarity
|
|
720
|
+
else:
|
|
721
|
+
raise ValueError(
|
|
722
|
+
"No supported normalization function"
|
|
723
|
+
f" for distance_strategy of {self.vidx_metric_type}."
|
|
724
|
+
)
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["poetry-core>=1.0.0"]
|
|
3
|
+
build-backend = "poetry.core.masonry.api"
|
|
4
|
+
|
|
5
|
+
[tool.poetry]
|
|
6
|
+
name = "langchain-oceanbase"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "An integration package connecting OceanBase and LangChain"
|
|
9
|
+
authors = ["shanhaikang.shk <shanhaikang.shk@oceanbase.com>"]
|
|
10
|
+
readme = "README.md"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
|
|
13
|
+
[tool.mypy]
|
|
14
|
+
disallow_untyped_defs = "True"
|
|
15
|
+
|
|
16
|
+
[tool.poetry.urls]
|
|
17
|
+
"Source Code" = "https://github.com/langchain-ai/langchain/tree/master/libs/partners/oceanbase"
|
|
18
|
+
"Release Notes" = "https://github.com/langchain-ai/langchain/releases?q=tag%3A%22oceanbase%3D%3D0%22&expanded=true"
|
|
19
|
+
|
|
20
|
+
[tool.poetry.dependencies]
|
|
21
|
+
python = ">=3.9,<4.0"
|
|
22
|
+
langchain-core = "^0.3.15"
|
|
23
|
+
pyobvector = "^0.1.17"
|
|
24
|
+
|
|
25
|
+
[tool.ruff.lint]
|
|
26
|
+
select = ["E", "F", "I", "T201"]
|
|
27
|
+
|
|
28
|
+
[tool.coverage.run]
|
|
29
|
+
omit = ["tests/*"]
|
|
30
|
+
|
|
31
|
+
[tool.pytest.ini_options]
|
|
32
|
+
addopts = "--strict-markers --strict-config --durations=5"
|
|
33
|
+
markers = [
|
|
34
|
+
"compile: mark placeholder test used to compile integration tests without running them",
|
|
35
|
+
]
|
|
36
|
+
asyncio_mode = "auto"
|
|
37
|
+
|
|
38
|
+
[tool.poetry.group.test]
|
|
39
|
+
optional = true
|
|
40
|
+
|
|
41
|
+
[tool.poetry.group.codespell]
|
|
42
|
+
optional = true
|
|
43
|
+
|
|
44
|
+
[tool.poetry.group.test_integration]
|
|
45
|
+
optional = true
|
|
46
|
+
|
|
47
|
+
[tool.poetry.group.lint]
|
|
48
|
+
optional = true
|
|
49
|
+
|
|
50
|
+
[tool.poetry.group.dev]
|
|
51
|
+
optional = true
|
|
52
|
+
|
|
53
|
+
[tool.poetry.group.dev.dependencies]
|
|
54
|
+
ipykernel = "^6.29.5"
|
|
55
|
+
|
|
56
|
+
[tool.poetry.group.test.dependencies]
|
|
57
|
+
pytest = "^7.4.3"
|
|
58
|
+
pytest-asyncio = "^0.23.2"
|
|
59
|
+
pytest-socket = "^0.7.0"
|
|
60
|
+
pytest-watcher = "^0.3.4"
|
|
61
|
+
langchain-tests = "^0.3.5"
|
|
62
|
+
langchain-community = "^0.3.12"
|
|
63
|
+
dashscope = "^1.20.14"
|
|
64
|
+
|
|
65
|
+
[tool.poetry.group.codespell.dependencies]
|
|
66
|
+
codespell = "^2.2.6"
|
|
67
|
+
|
|
68
|
+
[tool.poetry.group.test_integration.dependencies]
|
|
69
|
+
|
|
70
|
+
[tool.poetry.group.lint.dependencies]
|
|
71
|
+
ruff = "^0.5"
|
|
72
|
+
|
|
73
|
+
[tool.poetry.group.typing.dependencies]
|
|
74
|
+
mypy = "^1.10"
|