langchain-reranker 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,72 @@
1
+ Metadata-Version: 2.3
2
+ Name: langchain-reranker
3
+ Version: 0.1.0
4
+ Summary: Add init_reranker to the LangChain ecosystem
5
+ Keywords: langchain,reranker,rerank,rag,retrieval
6
+ Author: Ashitaka-Laputa-IV
7
+ Author-email: Ashitaka-Laputa-IV <Ashitaka-Laputa-IV@outlook.com>
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.13
12
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
13
+ Requires-Dist: langchain-core>=1.0
14
+ Requires-Python: >=3.13
15
+ Description-Content-Type: text/markdown
16
+
17
+ # langchain-reranker
18
+
19
+ Add `init_reranker` to the LangChain ecosystem, following the same conventions as
20
+ `init_chat_model` and `init_embeddings`.
21
+
22
+ ## Installation
23
+
24
+ ```bash
25
+ pip install langchain-reranker
26
+ ```
27
+
28
+ Provider integration packages are optional and installed on demand, e.g.
29
+ `pip install langchain-cohere`.
30
+
31
+ ## Usage
32
+
33
+ ```python
34
+ from langchain_reranker.rerankers import init_reranker
35
+
36
+ reranker = init_reranker("cohere:rerank-v3.5")
37
+
38
+ compressed = reranker.compress_documents(documents, query="what is langchain?")
39
+ ```
40
+
41
+ `init_reranker` returns a `BaseDocumentCompressor`, so it plugs directly into
42
+ existing retrieval pipelines such as a contextual compression retriever.
43
+
44
+ ```python
45
+ reranker = init_reranker(model="jina-reranker-v2-base-multilingual", provider="jina")
46
+ ```
47
+
48
+ ## Custom rerankers
49
+
50
+ Implement `rerank` and inherit `compress_documents` for free.
51
+
52
+ ```python
53
+ from langchain_reranker.rerankers import BaseReranker, RerankResult
54
+
55
+
56
+ class MyReranker(BaseReranker):
57
+ model: str = "my-reranker"
58
+
59
+ def rerank(self, query, documents, top_n=None):
60
+ scores = my_api(query, [doc.page_content for doc in documents])
61
+ ranked = sorted(zip(documents, scores), key=lambda pair: -pair[1])
62
+ return [
63
+ RerankResult(index=i, relevance_score=float(score), document=doc)
64
+ for i, (doc, score) in enumerate(ranked)
65
+ ]
66
+ ```
67
+
68
+ ## Local cross encoders
69
+
70
+ ```python
71
+ reranker = init_reranker("huggingface:BAAI/bge-reranker-v2-m3")
72
+ ```
@@ -0,0 +1,56 @@
1
+ # langchain-reranker
2
+
3
+ Add `init_reranker` to the LangChain ecosystem, following the same conventions as
4
+ `init_chat_model` and `init_embeddings`.
5
+
6
+ ## Installation
7
+
8
+ ```bash
9
+ pip install langchain-reranker
10
+ ```
11
+
12
+ Provider integration packages are optional and installed on demand, e.g.
13
+ `pip install langchain-cohere`.
14
+
15
+ ## Usage
16
+
17
+ ```python
18
+ from langchain_reranker.rerankers import init_reranker
19
+
20
+ reranker = init_reranker("cohere:rerank-v3.5")
21
+
22
+ compressed = reranker.compress_documents(documents, query="what is langchain?")
23
+ ```
24
+
25
+ `init_reranker` returns a `BaseDocumentCompressor`, so it plugs directly into
26
+ existing retrieval pipelines such as a contextual compression retriever.
27
+
28
+ ```python
29
+ reranker = init_reranker(model="jina-reranker-v2-base-multilingual", provider="jina")
30
+ ```
31
+
32
+ ## Custom rerankers
33
+
34
+ Implement `rerank` and inherit `compress_documents` for free.
35
+
36
+ ```python
37
+ from langchain_reranker.rerankers import BaseReranker, RerankResult
38
+
39
+
40
+ class MyReranker(BaseReranker):
41
+ model: str = "my-reranker"
42
+
43
+ def rerank(self, query, documents, top_n=None):
44
+ scores = my_api(query, [doc.page_content for doc in documents])
45
+ ranked = sorted(zip(documents, scores), key=lambda pair: -pair[1])
46
+ return [
47
+ RerankResult(index=i, relevance_score=float(score), document=doc)
48
+ for i, (doc, score) in enumerate(ranked)
49
+ ]
50
+ ```
51
+
52
+ ## Local cross encoders
53
+
54
+ ```python
55
+ reranker = init_reranker("huggingface:BAAI/bge-reranker-v2-m3")
56
+ ```
@@ -0,0 +1,32 @@
1
+ [project]
2
+ name = "langchain-reranker"
3
+ version = "0.1.0"
4
+ description = "Add init_reranker to the LangChain ecosystem"
5
+ readme = "README.md"
6
+ requires-python = ">=3.13"
7
+ keywords = [
8
+ "langchain",
9
+ "reranker",
10
+ "rerank",
11
+ "rag",
12
+ "retrieval",
13
+ ]
14
+ classifiers = [
15
+ "Development Status :: 3 - Alpha",
16
+ "Intended Audience :: Developers",
17
+ "Programming Language :: Python :: 3",
18
+ "Programming Language :: Python :: 3.13",
19
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
20
+ ]
21
+ dependencies = ["langchain-core>=1.0"]
22
+
23
+ [[project.authors]]
24
+ name = "Ashitaka-Laputa-IV"
25
+ email = "Ashitaka-Laputa-IV@outlook.com"
26
+
27
+ [dependency-groups]
28
+ dev = ["pytest>=8"]
29
+
30
+ [build-system]
31
+ requires = ["uv_build>=0.12.23,<0.13.0"]
32
+ build-backend = "uv_build"
@@ -0,0 +1,29 @@
1
+ [project]
2
+ name = "langchain-reranker"
3
+ version = "0.1.0"
4
+ description = "Add init_reranker to the LangChain ecosystem"
5
+ readme = "README.md"
6
+ authors = [
7
+ { name = "Ashitaka-Laputa-IV", email = "Ashitaka-Laputa-IV@outlook.com" }
8
+ ]
9
+ requires-python = ">=3.13"
10
+ keywords = ["langchain", "reranker", "rerank", "rag", "retrieval"]
11
+ classifiers = [
12
+ "Development Status :: 3 - Alpha",
13
+ "Intended Audience :: Developers",
14
+ "Programming Language :: Python :: 3",
15
+ "Programming Language :: Python :: 3.13",
16
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
17
+ ]
18
+ dependencies = [
19
+ "langchain-core>=1.0",
20
+ ]
21
+
22
+ [dependency-groups]
23
+ dev = [
24
+ "pytest>=8",
25
+ ]
26
+
27
+ [build-system]
28
+ requires = ["uv_build>=0.12.23,<0.13.0"]
29
+ build-backend = "uv_build"
@@ -0,0 +1,3 @@
1
+ """Main entrypoint into LangChain rerankers."""
2
+
3
+ __version__ = "0.1.0"
@@ -0,0 +1,15 @@
1
+ """Entrypoint to using rerankers in LangChain."""
2
+
3
+ from langchain_reranker.rerankers.base import (
4
+ BaseReranker,
5
+ CrossEncoderReranker,
6
+ RerankResult,
7
+ init_reranker,
8
+ )
9
+
10
+ __all__ = [
11
+ "BaseReranker",
12
+ "CrossEncoderReranker",
13
+ "RerankResult",
14
+ "init_reranker",
15
+ ]
@@ -0,0 +1,419 @@
1
+ """Factory function and base class for rerankers."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import functools
6
+ import importlib
7
+ from abc import ABC, abstractmethod
8
+ from collections.abc import Callable, Sequence
9
+ from typing import Any, ClassVar, override
10
+
11
+ from langchain_core.callbacks import Callbacks
12
+ from langchain_core.cross_encoders import BaseCrossEncoder
13
+ from langchain_core.documents import Document
14
+ from langchain_core.documents.compressor import BaseDocumentCompressor
15
+ from pydantic import BaseModel, ConfigDict
16
+
17
+
18
+ class RerankResult(BaseModel):
19
+ """A single reranked document together with its relevance score."""
20
+
21
+ index: int
22
+ """Index of the document in the input sequence."""
23
+
24
+ relevance_score: float
25
+ """Relevance score assigned by the reranker. Higher means more relevant."""
26
+
27
+ document: Document
28
+ """The reranked document."""
29
+
30
+
31
+ class BaseReranker(BaseDocumentCompressor, ABC):
32
+ """Base class for rerankers.
33
+
34
+ Subclasses implement [`rerank`][langchain_reranker.rerankers.BaseReranker.rerank].
35
+ The inherited `compress_documents` method is implemented in terms of `rerank`, so a
36
+ reranker can be used anywhere a
37
+ [`BaseDocumentCompressor`][langchain_core.documents.compressor.BaseDocumentCompressor]
38
+ is expected (for example inside a contextual compression retriever).
39
+ """
40
+
41
+ model: str = ""
42
+ """Name of the reranking model."""
43
+
44
+ top_n: int | None = None
45
+ """Number of documents to return. If `None`, all documents are returned."""
46
+
47
+ @abstractmethod
48
+ def rerank(
49
+ self,
50
+ query: str,
51
+ documents: Sequence[Document],
52
+ top_n: int | None = None,
53
+ ) -> list[RerankResult]:
54
+ """Rerank `documents` by relevance to `query`.
55
+
56
+ Args:
57
+ query: The query to rank the documents against.
58
+ documents: The documents to rerank.
59
+ top_n: Number of results to return. Defaults to `self.top_n`.
60
+
61
+ Returns:
62
+ The reranked results, ordered from most to least relevant.
63
+ """
64
+
65
+ @override
66
+ def compress_documents(
67
+ self,
68
+ documents: Sequence[Document],
69
+ query: str,
70
+ callbacks: Callbacks | None = None,
71
+ ) -> Sequence[Document]:
72
+ """Rerank documents and attach the relevance score to their metadata."""
73
+ _ = callbacks
74
+ return [
75
+ result.document.model_copy(
76
+ update={
77
+ "metadata": {
78
+ **result.document.metadata,
79
+ "relevance_score": result.relevance_score,
80
+ }
81
+ }
82
+ )
83
+ for result in self.rerank(query=query, documents=documents, top_n=self.top_n)
84
+ ]
85
+
86
+
87
+ class CrossEncoderReranker(BaseReranker):
88
+ """Reranker backed by a cross encoder.
89
+
90
+ Example:
91
+ ```python
92
+ from langchain_community.cross_encoders import HuggingFaceCrossEncoder
93
+ from langchain_reranker.rerankers import CrossEncoderReranker
94
+
95
+ cross_encoder = HuggingFaceCrossEncoder(model_name="BAAI/bge-reranker-base")
96
+ reranker = CrossEncoderReranker(cross_encoder=cross_encoder, top_n=3)
97
+ reranker.rerank(query="...", documents=documents)
98
+ ```
99
+ """
100
+
101
+ cross_encoder: BaseCrossEncoder
102
+ """Cross encoder used to score query-document pairs."""
103
+
104
+ model_config: ClassVar[ConfigDict] = ConfigDict(arbitrary_types_allowed=True)
105
+
106
+ @override
107
+ def rerank(
108
+ self,
109
+ query: str,
110
+ documents: Sequence[Document],
111
+ top_n: int | None = None,
112
+ ) -> list[RerankResult]:
113
+ """Score each document against the query and sort by relevance."""
114
+ scores = self.cross_encoder.score(
115
+ [(query, document.page_content) for document in documents]
116
+ )
117
+ results = [
118
+ RerankResult(
119
+ index=index,
120
+ relevance_score=float(score),
121
+ document=document,
122
+ )
123
+ for index, (document, score) in enumerate(
124
+ zip(documents, scores, strict=False)
125
+ )
126
+ ]
127
+ results.sort(key=lambda result: result.relevance_score, reverse=True)
128
+ limit = self.top_n if top_n is None else top_n
129
+ if limit is not None:
130
+ results = results[:limit]
131
+ return results
132
+
133
+
134
+ def _call(
135
+ cls: type[Any],
136
+ model: str,
137
+ **kwargs: Any,
138
+ ) -> BaseDocumentCompressor:
139
+ return cls(model=model, **kwargs)
140
+
141
+
142
+ def _call_cross_encoder(
143
+ cls: type[Any],
144
+ model: str,
145
+ *,
146
+ top_n: int | None = None,
147
+ **kwargs: Any,
148
+ ) -> BaseReranker:
149
+ return CrossEncoderReranker(
150
+ cross_encoder=cls(model_name=model, **kwargs),
151
+ model=model,
152
+ top_n=top_n,
153
+ )
154
+
155
+
156
+ _BUILTIN_PROVIDERS: dict[str, tuple[str, str, Callable[..., BaseDocumentCompressor]]] = {
157
+ "bedrock": ("langchain_aws", "AmazonBedrockRerank", _call),
158
+ "cohere": ("langchain_cohere", "CohereRerank", _call),
159
+ "dashscope": (
160
+ "langchain_community.document_compressors",
161
+ "DashScopeRerank",
162
+ _call,
163
+ ),
164
+ "flashrank": (
165
+ "langchain_community.document_compressors",
166
+ "FlashrankRerank",
167
+ _call,
168
+ ),
169
+ "huggingface": (
170
+ "langchain_huggingface",
171
+ "HuggingFaceCrossEncoder",
172
+ _call_cross_encoder,
173
+ ),
174
+ "infinity": (
175
+ "langchain_community.document_compressors",
176
+ "InfinityRerank",
177
+ _call,
178
+ ),
179
+ "jina": ("langchain_community.document_compressors", "JinaRerank", _call),
180
+ "local": (
181
+ "langchain_community.cross_encoders",
182
+ "HuggingFaceCrossEncoder",
183
+ _call_cross_encoder,
184
+ ),
185
+ "openvino": (
186
+ "langchain_community.document_compressors",
187
+ "OpenVINOReranker",
188
+ _call,
189
+ ),
190
+ "rankllm": (
191
+ "langchain_community.document_compressors",
192
+ "RankLLMRerank",
193
+ _call,
194
+ ),
195
+ "volcengine": (
196
+ "langchain_community.document_compressors",
197
+ "VolcengineRerank",
198
+ _call,
199
+ ),
200
+ "voyageai": ("langchain_voyageai", "VoyageAIRerank", _call),
201
+ }
202
+ """Registry mapping provider names to their import configuration.
203
+
204
+ Each entry maps a provider key to a tuple of:
205
+
206
+ - `module_path`: The Python module path containing the reranker class.
207
+ - `class_name`: The name of the reranker class to import.
208
+ - `creator_func`: A callable that instantiates the class with provided kwargs.
209
+
210
+ !!! note
211
+
212
+ This dict is not exhaustive of all providers supported by LangChain, but is
213
+ meant to cover the most popular ones and serve as a template for adding more
214
+ providers in the future. If a provider is not in this dict, it can still be
215
+ used with `init_reranker` as long as its integration package is installed, but
216
+ the provider key will not be inferred from the model name and must be
217
+ specified explicitly via the `provider` parameter.
218
+ """
219
+
220
+
221
+ @functools.lru_cache(maxsize=len(_BUILTIN_PROVIDERS))
222
+ def _get_reranker_class_creator(
223
+ provider: str,
224
+ ) -> Callable[..., BaseDocumentCompressor]:
225
+ """Return a factory that creates a reranker for the given provider.
226
+
227
+ This function is cached to avoid repeated module imports.
228
+
229
+ Args:
230
+ provider: The name of the provider (e.g., `'cohere'`, `'jina'`). Must be a
231
+ key in `_BUILTIN_PROVIDERS`.
232
+
233
+ Returns:
234
+ A callable that accepts model kwargs and returns a
235
+ `BaseDocumentCompressor` instance for the specified provider.
236
+
237
+ Raises:
238
+ ValueError: If the provider is not in `_BUILTIN_PROVIDERS`.
239
+ ImportError: If the provider's integration package is not installed.
240
+ """
241
+ if provider not in _BUILTIN_PROVIDERS:
242
+ msg = (
243
+ f"Provider '{provider}' is not supported.\n"
244
+ f"Supported providers and their required packages:\n"
245
+ f"{_get_provider_list()}"
246
+ )
247
+ raise ValueError(msg)
248
+
249
+ module_name, class_name, creator_func = _BUILTIN_PROVIDERS[provider]
250
+ try:
251
+ module = importlib.import_module(module_name)
252
+ except ImportError as e:
253
+ pkg = module_name.split(".", maxsplit=1)[0].replace("_", "-")
254
+ msg = f"Could not import {pkg} python package. Please install it with `pip install {pkg}`"
255
+ raise ImportError(msg) from e
256
+
257
+ cls = getattr(module, class_name)
258
+ return functools.partial(creator_func, cls=cls)
259
+
260
+
261
+ def _get_provider_list() -> str:
262
+ """Get formatted list of providers and their packages."""
263
+ return "\n".join(
264
+ f" - {p}: {pkg[0].split('.', maxsplit=1)[0].replace('_', '-')}"
265
+ for p, pkg in _BUILTIN_PROVIDERS.items()
266
+ )
267
+
268
+
269
+ def _parse_model_string(model_name: str) -> tuple[str, str]:
270
+ """Parse a model string into provider and model name components.
271
+
272
+ The model string should be in the format `'provider:model-name'`, where
273
+ provider is one of the supported providers.
274
+
275
+ Args:
276
+ model_name: A model string in the format `'provider:model-name'`.
277
+
278
+ Returns:
279
+ A tuple of `(provider, model_name)`.
280
+
281
+ Raises:
282
+ ValueError: If the model string is not in the correct format or the
283
+ provider is unsupported.
284
+ """
285
+ if ":" not in model_name:
286
+ msg = (
287
+ f"Invalid model format '{model_name}'.\n"
288
+ f"Model name must be in format 'provider:model-name'\n"
289
+ f"Example valid model strings:\n"
290
+ f" - cohere:rerank-v3.5\n"
291
+ f" - jina:jina-reranker-v2-base-multilingual\n"
292
+ f" - huggingface:BAAI/bge-reranker-v2-m3\n"
293
+ f"Supported providers: {_BUILTIN_PROVIDERS.keys()}"
294
+ )
295
+ raise ValueError(msg)
296
+
297
+ provider, model = model_name.split(":", 1)
298
+ provider = provider.lower().strip()
299
+ model = model.strip()
300
+
301
+ if provider not in _BUILTIN_PROVIDERS:
302
+ msg = (
303
+ f"Provider '{provider}' is not supported.\n"
304
+ f"Supported providers and their required packages:\n"
305
+ f"{_get_provider_list()}"
306
+ )
307
+ raise ValueError(msg)
308
+ if not model:
309
+ msg = "Model name cannot be empty"
310
+ raise ValueError(msg)
311
+ return provider, model
312
+
313
+
314
+ def _infer_model_and_provider(
315
+ model: str,
316
+ *,
317
+ provider: str | None = None,
318
+ ) -> tuple[str, str]:
319
+ if not model.strip():
320
+ msg = "Model name cannot be empty"
321
+ raise ValueError(msg)
322
+ if provider is None and ":" in model:
323
+ provider, model_name = _parse_model_string(model)
324
+ else:
325
+ model_name = model
326
+
327
+ if not provider:
328
+ msg = (
329
+ "Must specify either:\n"
330
+ "1. A model string in format 'provider:model-name'\n"
331
+ " Example: 'cohere:rerank-v3.5'\n"
332
+ "2. Or explicitly set provider from: "
333
+ f"{_BUILTIN_PROVIDERS.keys()}"
334
+ )
335
+ raise ValueError(msg)
336
+
337
+ if provider not in _BUILTIN_PROVIDERS:
338
+ msg = (
339
+ f"Provider '{provider}' is not supported.\n"
340
+ f"Supported providers and their required packages:\n"
341
+ f"{_get_provider_list()}"
342
+ )
343
+ raise ValueError(msg)
344
+ return provider, model_name
345
+
346
+
347
+ def init_reranker(
348
+ model: str,
349
+ *,
350
+ provider: str | None = None,
351
+ **kwargs: Any,
352
+ ) -> BaseDocumentCompressor:
353
+ """Initialize a reranker from a model name and optional provider.
354
+
355
+ !!! note
356
+
357
+ Requires the integration package for the chosen provider to be installed.
358
+
359
+ See the `provider` parameter below for specific package names
360
+ (e.g., `pip install langchain-cohere`).
361
+
362
+ Args:
363
+ model: The name of the model, e.g. `'cohere:rerank-v3.5'`.
364
+
365
+ You can also specify model and provider in a single argument using
366
+ the `'{provider}:{model}'` format, e.g. `'cohere:rerank-v3.5'`.
367
+ provider: The provider if not specified as part of the model arg.
368
+
369
+ Supported `provider` values and the corresponding integration package
370
+ are:
371
+
372
+ - `bedrock` -> `langchain-aws`
373
+ - `cohere` -> `langchain-cohere`
374
+ - `dashscope` -> `langchain-community`
375
+ - `flashrank` -> `langchain-community`
376
+ - `huggingface` -> `langchain-huggingface`
377
+ - `infinity` -> `langchain-community`
378
+ - `jina` -> `langchain-community`
379
+ - `local` -> `langchain-community` (sentence-transformers)
380
+ - `openvino` -> `langchain-community`
381
+ - `rankllm` -> `langchain-community`
382
+ - `volcengine` -> `langchain-community`
383
+ - `voyageai` -> `langchain-voyageai`
384
+
385
+ **kwargs: Additional model-specific parameters passed to the reranker.
386
+
387
+ Returns:
388
+ A `BaseDocumentCompressor` that reranks documents for a query.
389
+
390
+ Raises:
391
+ ValueError: If the provider is not supported or cannot be determined.
392
+ ImportError: If the required provider package is not installed.
393
+
394
+ ???+ example
395
+
396
+ ```python
397
+ # pip install langchain-reranker langchain-cohere
398
+
399
+ # Using a model string
400
+ reranker = init_reranker("cohere:rerank-v3.5")
401
+ reranker.compress_documents(documents, query="...")
402
+
403
+ # Using an explicit provider
404
+ reranker = init_reranker(model="jina-reranker-v2-base-multilingual", provider="jina")
405
+
406
+ # With additional parameters
407
+ reranker = init_reranker("cohere:rerank-v3.5", top_n=5)
408
+ ```
409
+ """
410
+ if not model:
411
+ providers = ", ".join(_BUILTIN_PROVIDERS.keys())
412
+ msg = f"Must specify model name. Supported providers are: {providers}"
413
+ raise ValueError(msg)
414
+
415
+ provider, model_name = _infer_model_and_provider(model, provider=provider)
416
+ return _get_reranker_class_creator(provider)(model=model_name, **kwargs)
417
+
418
+
419
+ __all__ = ["BaseReranker", "CrossEncoderReranker", "RerankResult", "init_reranker"]