mirror of
https://github.com/semantica-agi/semantica.git
synced 2026-08-29 04:26:20 +00:00
feat(integrations): add LangChain integration — retriever, vectorstor… (#1155)
* feat(integrations): add LangChain integration — retriever, vectorstore, tools Co-authored-by: Cursor <cursoragent@cursor.com> * fix(langchain): address Qodo review on HybridSearch hits and tools Read nested HybridSearch metadata so retriever/vectorstore Documents are not empty, make the agent tools real BaseTool subclasses, and stop slicing tool JSON into invalid payloads. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
"""
|
||||
Semantica Framework Integrations
|
||||
|
||||
Optional integration packages for agentic frameworks (Google ADK, Claude Agent SDK, Agno, etc.).
|
||||
Optional integration packages for agentic frameworks (Google ADK, Claude Agent SDK, Agno, CrewAI, LangChain, etc.).
|
||||
Each integration is self-contained, independently installable via extras_require, and maintains
|
||||
zero impact on core Semantica - keeping the semantic layer lean while maximizing ecosystem reach.
|
||||
"""
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
# Semantica × LangChain
|
||||
|
||||
Drop Semantica into existing LangChain / LangGraph pipelines: GraphRAG-style
|
||||
retrieval, a `VectorStore` adapter, and agent tools.
|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
pip install semantica[langchain]
|
||||
# or just the core adapter dependency:
|
||||
pip install langchain-core
|
||||
```
|
||||
|
||||
## Retriever (GraphRAG)
|
||||
|
||||
```python
|
||||
from integrations.langchain import SemanticaRetriever
|
||||
from semantica.context import ContextGraph
|
||||
from semantica.vector_store import HybridSearch
|
||||
|
||||
graph = ContextGraph()
|
||||
hybrid = HybridSearch()
|
||||
|
||||
retriever = SemanticaRetriever(graph=graph, hybrid=hybrid, hops=2, top_k=10)
|
||||
|
||||
# Use with any LangChain chain that accepts a retriever:
|
||||
from langchain.chains import RetrievalQA
|
||||
|
||||
qa = RetrievalQA.from_chain_type(llm=llm, retriever=retriever)
|
||||
```
|
||||
|
||||
Hybrid search seeds retrieval; then graph edges are walked `hops` steps so
|
||||
results go beyond flat vector similarity.
|
||||
|
||||
## VectorStore
|
||||
|
||||
```python
|
||||
from integrations.langchain import SemanticaVectorStore
|
||||
|
||||
store = SemanticaVectorStore(hybrid=hybrid)
|
||||
store.add_texts(["document one", "document two"], metadatas=[{"source": "a"}, {"source": "b"}])
|
||||
docs = store.similarity_search("document", k=2)
|
||||
docs, scores = store.similarity_search_with_score("document", k=2)
|
||||
```
|
||||
|
||||
## Agent tools (LangGraph / tool-calling agents)
|
||||
|
||||
```python
|
||||
from integrations.langchain import SemanticaKGTool, SemanticaDecisionTool
|
||||
from langgraph.prebuilt import create_react_agent
|
||||
|
||||
tools = [
|
||||
SemanticaKGTool(graph),
|
||||
SemanticaDecisionTool(graph),
|
||||
]
|
||||
agent = create_react_agent(model, tools)
|
||||
```
|
||||
|
||||
- `semantica_query_graph` — query the shared context graph (keyword / NL)
|
||||
- `semantica_query_decisions` — search the recorded decision log
|
||||
|
||||
## Compatibility
|
||||
|
||||
- Requires `langchain-core >= 0.3`.
|
||||
- All classes degrade gracefully when `langchain-core` is absent: they remain
|
||||
importable (carrying the full Semantica API), and `build()` returns `None`,
|
||||
so agents can branch on `LANGCHAIN_AVAILABLE`.
|
||||
@@ -0,0 +1,48 @@
|
||||
"""
|
||||
Semantica × LangChain Integration
|
||||
=================================
|
||||
|
||||
First-class integration between the Semantica semantic intelligence stack and
|
||||
the `LangChain <https://github.com/langchain-ai/langchain>`_ / LangGraph
|
||||
ecosystem.
|
||||
|
||||
Public surface
|
||||
--------------
|
||||
SemanticaRetriever — ``BaseRetriever`` with multi-hop GraphRAG (walks graph
|
||||
edges from hybrid-search hits)
|
||||
SemanticaVectorStore — ``VectorStore`` adapter over Semantica's hybrid search
|
||||
(drop-in for RetrievalQA / LCEL chains)
|
||||
SemanticaKGTool — ``BaseTool`` for querying the context graph
|
||||
SemanticaDecisionTool — ``BaseTool`` exposing the recorded decision log
|
||||
|
||||
Quick start
|
||||
-----------
|
||||
pip install semantica[langchain]
|
||||
|
||||
>>> from integrations.langchain import (
|
||||
... SemanticaRetriever,
|
||||
... SemanticaVectorStore,
|
||||
... SemanticaKGTool,
|
||||
... SemanticaDecisionTool,
|
||||
... )
|
||||
|
||||
Compatibility
|
||||
-------------
|
||||
Requires ``langchain-core >= 0.3``. All classes degrade gracefully when
|
||||
``langchain-core`` is not installed — they are still importable and carry the
|
||||
full Semantica API, but cannot be bound to LangChain chains/agents.
|
||||
"""
|
||||
|
||||
from .retriever import LANGCHAIN_AVAILABLE, SemanticaRetriever
|
||||
from .tools import SemanticaDecisionTool, SemanticaKGTool
|
||||
from .vectorstore import SemanticaVectorStore
|
||||
|
||||
__all__ = [
|
||||
"SemanticaRetriever",
|
||||
"SemanticaVectorStore",
|
||||
"SemanticaKGTool",
|
||||
"SemanticaDecisionTool",
|
||||
"LANGCHAIN_AVAILABLE",
|
||||
]
|
||||
|
||||
__version__ = "0.1.0"
|
||||
@@ -0,0 +1,216 @@
|
||||
"""
|
||||
SemanticaRetriever — LangChain ``BaseRetriever`` with multi-hop GraphRAG.
|
||||
|
||||
Hybrid search seeds the retrieval, then graph edges are walked for ``hops``
|
||||
steps so results go beyond flat vector similarity.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, List, Optional, Tuple
|
||||
|
||||
from semantica.utils.logging import get_logger
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Optional: LangChain core
|
||||
# ---------------------------------------------------------------------------
|
||||
LANGCHAIN_AVAILABLE = False
|
||||
LANGCHAIN_IMPORT_ERROR: Optional[str] = None
|
||||
|
||||
_BaseRetriever: Any = object
|
||||
_Document: Any = None
|
||||
|
||||
|
||||
def _get_document(**kwargs: Any) -> Any:
|
||||
"""Instantiate a langchain Document lazily (keeps the import optional)."""
|
||||
if _Document is None: # pragma: no cover - exercised only with langchain
|
||||
raise RuntimeError(LANGCHAIN_IMPORT_ERROR or "langchain-core not installed")
|
||||
return _Document(**kwargs)
|
||||
|
||||
|
||||
try:
|
||||
from langchain_core.documents import Document as _Document # type: ignore
|
||||
from langchain_core.retrievers import (
|
||||
BaseRetriever as _BaseRetriever, # type: ignore
|
||||
)
|
||||
|
||||
LANGCHAIN_AVAILABLE = True
|
||||
except ImportError: # pragma: no cover - exercised only without langchain
|
||||
LANGCHAIN_IMPORT_ERROR = (
|
||||
"langchain-core is not installed. Install with: pip install langchain-core"
|
||||
)
|
||||
logger.debug(LANGCHAIN_IMPORT_ERROR)
|
||||
|
||||
|
||||
def _hit_layers(hit: Dict[str, Any]) -> Tuple[Dict[str, Any], Dict[str, Any]]:
|
||||
"""Nested HybridSearch metadata and ContextGraph.query node, if present."""
|
||||
metadata = hit.get("metadata") if isinstance(hit.get("metadata"), dict) else {}
|
||||
node = hit.get("node") if isinstance(hit.get("node"), dict) else {}
|
||||
return metadata, node
|
||||
|
||||
|
||||
def _hit_id(hit: Dict[str, Any]) -> Optional[str]:
|
||||
"""Graph node id, preferring metadata over a HybridSearch vector id."""
|
||||
metadata, node = _hit_layers(hit)
|
||||
return (
|
||||
hit.get("node_id")
|
||||
or metadata.get("node_id")
|
||||
or node.get("id")
|
||||
or node.get("node_id")
|
||||
or hit.get("id")
|
||||
)
|
||||
|
||||
|
||||
def _hit_content(hit: Dict[str, Any], fallback: str = "") -> str:
|
||||
metadata, node = _hit_layers(hit)
|
||||
props = node.get("properties") if isinstance(node.get("properties"), dict) else {}
|
||||
return (
|
||||
hit.get("content")
|
||||
or hit.get("text")
|
||||
or metadata.get("content")
|
||||
or metadata.get("text")
|
||||
or props.get("content")
|
||||
or fallback
|
||||
)
|
||||
|
||||
|
||||
def _hit_type(hit: Dict[str, Any]) -> str:
|
||||
metadata, node = _hit_layers(hit)
|
||||
return (
|
||||
hit.get("node_type")
|
||||
or hit.get("type")
|
||||
or metadata.get("node_type")
|
||||
or metadata.get("type")
|
||||
or node.get("type")
|
||||
or node.get("node_type")
|
||||
or "node"
|
||||
)
|
||||
|
||||
|
||||
def _hit_score(hit: Dict[str, Any], default: float = 1.0) -> float:
|
||||
return float(hit.get("score") if hit.get("score") is not None else hit.get("distance") or default)
|
||||
|
||||
|
||||
class SemanticaRetriever(_BaseRetriever): # type: ignore[misc]
|
||||
"""GraphRAG-style retriever over a Semantica ``ContextGraph``.
|
||||
|
||||
Args:
|
||||
graph: A semantica.context.ContextGraph instance.
|
||||
hybrid: A semantica.vector_store.HybridSearch instance used to seed
|
||||
retrieval. If omitted, a best-effort keyword search on the graph
|
||||
is used.
|
||||
hops: Number of graph-edge expansion hops (default 2).
|
||||
top_k: Number of seed hits (default 10).
|
||||
"""
|
||||
|
||||
graph: Any
|
||||
hybrid: Any = None
|
||||
hops: int = 2
|
||||
top_k: int = 10
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
graph: Any,
|
||||
hybrid: Any = None,
|
||||
hops: int = 2,
|
||||
top_k: int = 10,
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
"""Explicit init so the retriever works with and without langchain."""
|
||||
if LANGCHAIN_AVAILABLE:
|
||||
# BaseRetriever is a Pydantic model: pass the declared fields
|
||||
# through so validation succeeds.
|
||||
super().__init__(
|
||||
graph=graph,
|
||||
hybrid=hybrid,
|
||||
hops=hops,
|
||||
top_k=top_k,
|
||||
**kwargs,
|
||||
)
|
||||
else:
|
||||
# Without langchain-core, BaseRetriever is a plain object
|
||||
super().__init__() # type: ignore[call-arg]
|
||||
self.graph = graph
|
||||
self.hybrid = hybrid
|
||||
self.hops = hops
|
||||
self.top_k = top_k
|
||||
|
||||
def _get_relevant_documents(self, query: str, **kwargs: Any) -> List[Any]:
|
||||
"""LangChain BaseRetriever entry point."""
|
||||
seed = self._seed_results(query)
|
||||
if not seed:
|
||||
return []
|
||||
|
||||
# Expand each seed node through the graph
|
||||
expanded: Dict[str, Dict[str, Any]] = {}
|
||||
for hit in seed:
|
||||
node_id = _hit_id(hit)
|
||||
if not node_id:
|
||||
continue
|
||||
metadata, _ = _hit_layers(hit)
|
||||
expanded[node_id] = {
|
||||
"content": _hit_content(hit, fallback=str(node_id)),
|
||||
"node_type": _hit_type(hit),
|
||||
"score": _hit_score(hit),
|
||||
"metadata": metadata,
|
||||
}
|
||||
try:
|
||||
neighbors = self.graph.get_neighbors(node_id, hops=self.hops)
|
||||
for neighbor in neighbors:
|
||||
nid = neighbor.get("node_id") or neighbor.get("id")
|
||||
if nid and nid not in expanded:
|
||||
expanded[nid] = {
|
||||
"content": neighbor.get("content")
|
||||
or neighbor.get("text")
|
||||
or neighbor.get("name")
|
||||
or str(nid),
|
||||
"node_type": neighbor.get("node_type")
|
||||
or neighbor.get("type")
|
||||
or "node",
|
||||
"score": float(neighbor.get("weight") or 0.5),
|
||||
"metadata": {},
|
||||
}
|
||||
except Exception as exc: # graph expansion is best-effort
|
||||
logger.debug("graph expansion failed for %s: %s", node_id, exc)
|
||||
|
||||
# Order: seed hits first (they have real scores), then neighbors.
|
||||
# Keep a deterministic id->payload list (sets are unordered — see Qodo).
|
||||
ordered_pairs: List[tuple] = []
|
||||
seen_ids = set()
|
||||
for hit in seed:
|
||||
nid = _hit_id(hit)
|
||||
if nid and nid in expanded and nid not in seen_ids:
|
||||
ordered_pairs.append((nid, expanded[nid]))
|
||||
seen_ids.add(nid)
|
||||
for nid, item in expanded.items():
|
||||
if nid not in seen_ids:
|
||||
ordered_pairs.append((nid, item))
|
||||
seen_ids.add(nid)
|
||||
|
||||
return [
|
||||
_get_document(
|
||||
page_content=item["content"],
|
||||
metadata={
|
||||
**item["metadata"],
|
||||
"node_id": nid,
|
||||
"node_type": item["node_type"],
|
||||
"score": item["score"],
|
||||
},
|
||||
)
|
||||
for nid, item in ordered_pairs
|
||||
]
|
||||
|
||||
def _seed_results(self, query: str) -> List[Dict[str, Any]]:
|
||||
"""Get seed results from hybrid search or a graph keyword scan."""
|
||||
if self.hybrid is not None:
|
||||
try:
|
||||
return self.hybrid.search(query, k=self.top_k)
|
||||
except Exception as exc:
|
||||
logger.debug("hybrid search failed, falling back: %s", exc)
|
||||
# Best-effort keyword scan over graph nodes (ContextGraph.query)
|
||||
try:
|
||||
return self.graph.query(query, limit=self.top_k)
|
||||
except Exception:
|
||||
return []
|
||||
@@ -0,0 +1,133 @@
|
||||
"""
|
||||
SemanticaKGTool / SemanticaDecisionTool — LangChain ``BaseTool`` adapters
|
||||
for LangChain / LangGraph agents.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from typing import Any, Optional, Type
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
|
||||
from semantica.utils.logging import get_logger
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Optional: LangChain core
|
||||
# ---------------------------------------------------------------------------
|
||||
LANGCHAIN_AVAILABLE = False
|
||||
LANGCHAIN_IMPORT_ERROR: Optional[str] = None
|
||||
|
||||
_BaseTool: Any = object
|
||||
|
||||
|
||||
try:
|
||||
from langchain_core.tools import BaseTool as _BaseTool # type: ignore
|
||||
|
||||
LANGCHAIN_AVAILABLE = True
|
||||
except ImportError: # pragma: no cover
|
||||
LANGCHAIN_IMPORT_ERROR = (
|
||||
"langchain-core is not installed. Install with: pip install langchain-core"
|
||||
)
|
||||
logger.debug(LANGCHAIN_IMPORT_ERROR)
|
||||
|
||||
|
||||
def _json(payload: Any) -> str:
|
||||
return json.dumps(payload, default=str, ensure_ascii=False)
|
||||
|
||||
|
||||
class QueryGraphInput(BaseModel):
|
||||
query: str = Field(..., description="Natural-language or keyword graph query")
|
||||
limit: int = Field(10, description="Maximum matching nodes to return")
|
||||
|
||||
|
||||
class QueryDecisionsInput(BaseModel):
|
||||
category: str = Field(
|
||||
"",
|
||||
description="Keyword to search recorded decisions; empty returns insights",
|
||||
)
|
||||
limit: int = Field(10, description="Maximum results when searching by keyword")
|
||||
|
||||
|
||||
class SemanticaKGTool(_BaseTool): # type: ignore[misc]
|
||||
"""LangChain tool for querying a Semantica ``ContextGraph``.
|
||||
|
||||
Args:
|
||||
graph: A semantica.context.ContextGraph instance.
|
||||
|
||||
Example:
|
||||
>>> tool = SemanticaKGTool(graph)
|
||||
>>> agent = create_react_agent(model, tools=[tool])
|
||||
"""
|
||||
|
||||
model_config = ConfigDict(arbitrary_types_allowed=True)
|
||||
|
||||
name: str = "semantica_query_graph"
|
||||
description: str = (
|
||||
"Query Semantica's shared context graph with a natural-language "
|
||||
"keyword query. Returns matching entities and relationships."
|
||||
)
|
||||
args_schema: Type[BaseModel] = QueryGraphInput
|
||||
graph: Any = None
|
||||
|
||||
def __init__(self, graph: Any = None, **kwargs: Any) -> None:
|
||||
if LANGCHAIN_AVAILABLE:
|
||||
super().__init__(graph=graph, **kwargs)
|
||||
else:
|
||||
super().__init__()
|
||||
self.graph = graph
|
||||
|
||||
def build(self) -> Any:
|
||||
"""Return this tool, or None if langchain-core is missing."""
|
||||
return self if LANGCHAIN_AVAILABLE else None
|
||||
|
||||
def _run(self, query: str, limit: int = 10, **kwargs: Any) -> str:
|
||||
try:
|
||||
return _json(self.graph.query(query, limit=limit))
|
||||
except Exception as exc:
|
||||
return _json({"error": str(exc)})
|
||||
|
||||
async def _arun(self, query: str, limit: int = 10, **kwargs: Any) -> str:
|
||||
return self._run(query, limit=limit)
|
||||
|
||||
|
||||
class SemanticaDecisionTool(_BaseTool): # type: ignore[misc]
|
||||
"""LangChain tool for searching Semantica's recorded decision log.
|
||||
|
||||
Args:
|
||||
graph: A semantica.context.ContextGraph instance.
|
||||
"""
|
||||
|
||||
model_config = ConfigDict(arbitrary_types_allowed=True)
|
||||
|
||||
name: str = "semantica_query_decisions"
|
||||
description: str = (
|
||||
"Search Semantica's recorded decision log with a keyword query. "
|
||||
"Returns decisions, rationale, and context."
|
||||
)
|
||||
args_schema: Type[BaseModel] = QueryDecisionsInput
|
||||
graph: Any = None
|
||||
|
||||
def __init__(self, graph: Any = None, **kwargs: Any) -> None:
|
||||
if LANGCHAIN_AVAILABLE:
|
||||
super().__init__(graph=graph, **kwargs)
|
||||
else:
|
||||
super().__init__()
|
||||
self.graph = graph
|
||||
|
||||
def build(self) -> Any:
|
||||
"""Return this tool, or None if langchain-core is missing."""
|
||||
return self if LANGCHAIN_AVAILABLE else None
|
||||
|
||||
def _run(self, category: str = "", limit: int = 10, **kwargs: Any) -> str:
|
||||
try:
|
||||
if category:
|
||||
return _json(self.graph.query(category, limit=limit))
|
||||
return _json(self.graph.get_decision_insights())
|
||||
except Exception as exc:
|
||||
return _json({"error": str(exc)})
|
||||
|
||||
async def _arun(self, category: str = "", limit: int = 10, **kwargs: Any) -> str:
|
||||
return self._run(category=category, limit=limit)
|
||||
@@ -0,0 +1,143 @@
|
||||
"""
|
||||
SemanticaVectorStore — LangChain ``VectorStore`` adapter over Semantica's
|
||||
hybrid search (``semantica.vector_store.HybridSearch``).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, Iterable, List, Optional
|
||||
|
||||
from semantica.utils.logging import get_logger
|
||||
|
||||
from .retriever import _hit_content, _hit_id, _hit_score, _hit_type, _hit_layers
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Optional: LangChain core
|
||||
# ---------------------------------------------------------------------------
|
||||
LANGCHAIN_AVAILABLE = False
|
||||
LANGCHAIN_IMPORT_ERROR: Optional[str] = None
|
||||
|
||||
_VectorStoreBase: Any = object
|
||||
_Document: Any = None
|
||||
|
||||
|
||||
def _make_document(**kwargs: Any) -> Any:
|
||||
if _Document is None: # pragma: no cover
|
||||
raise RuntimeError(LANGCHAIN_IMPORT_ERROR or "langchain-core not installed")
|
||||
return _Document(**kwargs)
|
||||
|
||||
|
||||
try:
|
||||
from langchain_core.documents import Document as _Document # type: ignore
|
||||
from langchain_core.vectorstores import (
|
||||
VectorStore as _VectorStoreBase, # type: ignore
|
||||
)
|
||||
|
||||
LANGCHAIN_AVAILABLE = True
|
||||
except ImportError: # pragma: no cover
|
||||
LANGCHAIN_IMPORT_ERROR = (
|
||||
"langchain-core is not installed. Install with: pip install langchain-core"
|
||||
)
|
||||
logger.debug(LANGCHAIN_IMPORT_ERROR)
|
||||
|
||||
|
||||
def _document_from_hit(hit: Dict[str, Any], include_score: bool = True) -> Any:
|
||||
metadata, _ = _hit_layers(hit)
|
||||
node_id = _hit_id(hit)
|
||||
doc_meta = {
|
||||
**metadata,
|
||||
"node_id": node_id,
|
||||
"node_type": _hit_type(hit),
|
||||
}
|
||||
if include_score:
|
||||
doc_meta["score"] = _hit_score(hit, default=0.0)
|
||||
return _make_document(
|
||||
page_content=_hit_content(hit),
|
||||
metadata=doc_meta,
|
||||
)
|
||||
|
||||
|
||||
class SemanticaVectorStore(_VectorStoreBase): # type: ignore[misc]
|
||||
"""Wrap Semantica hybrid search as a LangChain ``VectorStore``.
|
||||
|
||||
Args:
|
||||
hybrid: A semantica.vector_store.HybridSearch instance.
|
||||
vector_store: Optional Semantica vector store passed through to
|
||||
``HybridSearch.add_texts``.
|
||||
"""
|
||||
|
||||
hybrid: Any
|
||||
vector_store: Any = None
|
||||
|
||||
def __init__(self, hybrid: Any, vector_store: Any = None, **kwargs: Any) -> None:
|
||||
if LANGCHAIN_AVAILABLE:
|
||||
super().__init__(**kwargs)
|
||||
else:
|
||||
super().__init__()
|
||||
self.hybrid = hybrid
|
||||
self.vector_store = vector_store
|
||||
|
||||
# -- required VectorStore API ------------------------------------------
|
||||
def add_texts(
|
||||
self,
|
||||
texts: Iterable[str],
|
||||
metadatas: Optional[List[Dict[str, Any]]] = None,
|
||||
**kwargs: Any,
|
||||
) -> List[str]:
|
||||
"""Embed and store texts; return the generated IDs.
|
||||
|
||||
Delegates to the Semantica ``VectorStore.add_documents`` backing the
|
||||
HybridSearch instance (or to ``hybrid.vector_store`` if provided).
|
||||
"""
|
||||
if self.vector_store is not None:
|
||||
return self.vector_store.add_documents(
|
||||
list(texts), metadata=metadatas, **kwargs
|
||||
)
|
||||
vs = getattr(self.hybrid, "vector_store", None)
|
||||
if vs is not None and hasattr(vs, "add_documents"):
|
||||
return vs.add_documents(list(texts), metadata=metadatas, **kwargs)
|
||||
raise ValueError(
|
||||
"SemanticaVectorStore requires a Semantica vector store with "
|
||||
"add_documents (pass vector_store=... to the HybridSearch or to "
|
||||
"SemanticaVectorStore)"
|
||||
)
|
||||
|
||||
def similarity_search(self, query: str, k: int = 4, **kwargs: Any) -> List[Any]:
|
||||
"""Return documents most similar to the query."""
|
||||
return [_document_from_hit(hit) for hit in self.hybrid.search(query, k=k)]
|
||||
|
||||
def similarity_search_with_score(
|
||||
self, query: str, k: int = 4, **kwargs: Any
|
||||
) -> List[Any]:
|
||||
"""Return (document, score) pairs."""
|
||||
return [
|
||||
(
|
||||
_document_from_hit(hit, include_score=False),
|
||||
_hit_score(hit, default=0.0),
|
||||
)
|
||||
for hit in self.hybrid.search(query, k=k)
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def from_texts(
|
||||
cls,
|
||||
texts: List[str],
|
||||
embedding: Any = None,
|
||||
metadatas: Optional[List[Dict[str, Any]]] = None,
|
||||
**kwargs: Any,
|
||||
) -> "SemanticaVectorStore":
|
||||
"""Build a store from a list of texts (LangChain convention).
|
||||
|
||||
Requires a pre-configured ``hybrid`` instance passed via kwargs.
|
||||
"""
|
||||
hybrid = kwargs.pop("hybrid", None)
|
||||
if hybrid is None:
|
||||
raise ValueError(
|
||||
"SemanticaVectorStore.from_texts requires a 'hybrid' "
|
||||
"HybridSearch instance as a keyword argument"
|
||||
)
|
||||
store = cls(hybrid=hybrid, **kwargs)
|
||||
store.add_texts(texts, metadatas=metadatas)
|
||||
return store
|
||||
Reference in New Issue
Block a user