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:
Derek Tapley
2026-08-26 18:29:22 +05:00
committed by GitHub
co-authored by Cursor
parent 8a990c8bf5
commit f0aa581318
14 changed files with 1469 additions and 14 deletions
+11
View File
@@ -9,6 +9,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
### Added
- **First-class LangChain integration** (closes #963; recreates #969)
- New `pip install semantica[langchain]` extra (`langchain-core>=0.3.0`), included in the `all` bundle
- `integrations/langchain/SemanticaRetriever` — LangChain `BaseRetriever` that seeds from `HybridSearch` then walks graph edges (`hops=2` default) for GraphRAG-style retrieval; falls back to `ContextGraph.query` when hybrid search is unavailable
- `integrations/langchain/SemanticaVectorStore` — LangChain `VectorStore` adapter over `HybridSearch` (`add_texts`, `similarity_search`, `similarity_search_with_score`, `from_texts`)
- `integrations/langchain/SemanticaKGTool` / `SemanticaDecisionTool``BaseTool` subclasses with Pydantic `args_schema` (`semantica_query_graph`, `semantica_query_decisions`); `build()` returns the tool, or `None` when langchain-core is absent
- Retriever and VectorStore read HybridSearch nested `metadata` (`content`, `node_id`, `node_type`) rather than top-level fields that HybridSearch does not set
- All adapters remain importable without langchain-core (`LANGCHAIN_AVAILABLE` flag)
- Docs: `docs/integrations/langchain.md`, README native-integration matrix, and `docs.json` nav entry
## [0.6.6] - 2026-08-20
### Added
+8 -12
View File
@@ -87,7 +87,7 @@ Semantica sits underneath your LLM, vector store, and agent framework as a deter
- **Graph Analytics:** Centrality, community detection, link prediction, and shortest-path queries over the graph you just built
- **Polyglot Graph Storage:** Native RDF (embedded Oxigraph, Blazegraph, Apache Jena, Eclipse RDF4J via SPARQL) and Labeled Property Graphs (Neo4j, FalkorDB, Apache AGE, AWS Neptune via Cypher), plus vector stores, all swappable without touching your code
- **Visualization:** Explore any graph, ontology, or timeline in an interactive browser workbench
- **Drop-in Integrations:** Native Agno and CrewAI support, a full-featured MCP server, a comprehensive CLI, a REST API, and plugins across major editors
- **Drop-in Integrations:** Native Agno, CrewAI, and LangChain support, a full-featured MCP server, a comprehensive CLI, a REST API, and plugins across major editors
---
@@ -1188,7 +1188,7 @@ Start with `semantica`, verify with `doctor`, build a graph, and explore the com
## Integrations
Native plugin bundles for Claude Code, Cursor, Codex, Windsurf, Cline, Continue, VS Code, and OpenClaw; a full-featured MCP server for any MCP-compatible client; a comprehensive REST API; and first-class Agno and CrewAI support for agentic frameworks. Every major LLM provider is already supported via `semantica.llms` and LiteLLM: OpenAI, Anthropic, Gemini, Mistral, Llama, Groq, Cohere, Azure, Bedrock, Ollama, DeepSeek, HuggingFace, and more.
Native plugin bundles for Claude Code, Cursor, Codex, Windsurf, Cline, Continue, VS Code, and OpenClaw; a full-featured MCP server for any MCP-compatible client; a comprehensive REST API; and first-class Agno, CrewAI, and LangChain support for agentic frameworks. Every major LLM provider is already supported via `semantica.llms` and LiteLLM: OpenAI, Anthropic, Gemini, Mistral, Llama, Groq, Cohere, Azure, Bedrock, Ollama, DeepSeek, HuggingFace, and more.
MCP setup takes 30 seconds — see [MCP Server](#mcp-server) below.
@@ -1307,17 +1307,17 @@ MCP setup takes 30 seconds — see [MCP Server](#mcp-server) below.
<strong>CrewAI</strong><br/>
<sub>First-class · <code>pip install semantica[crewai]</code></sub>
</td>
<td align="center" width="12.5%">
<a href="https://github.com/langchain-ai/langchain"><img src="https://github.com/langchain-ai.png?size=120" alt="LangChain" width="48" height="48" /></a><br/>
<strong>LangChain</strong><br/>
<sub>First-class · <code>pip install semantica[langchain]</code></sub>
</td>
</tr>
<tr>
<th colspan="8" align="left">Already Supported via REST API &amp; MCP</th>
</tr>
<tr>
<td align="center" width="12.5%">
<a href="https://github.com/langchain-ai/langchain"><img src="https://github.com/langchain-ai.png?size=120" alt="LangChain" width="48" height="48" /></a><br/>
<strong>LangChain</strong><br/>
<sub>REST API · MCP</sub>
</td>
<td align="center" width="12.5%">
<a href="https://github.com/langchain-ai/langgraph"><img src="https://github.com/langchain-ai.png?size=120" alt="LangGraph" width="48" height="48" /></a><br/>
<strong>LangGraph</strong><br/>
<sub>REST API · MCP</sub>
@@ -1348,11 +1348,6 @@ MCP setup takes 30 seconds — see [MCP Server](#mcp-server) below.
</tr>
<tr>
<td align="center" width="12.5%">
<a href="https://github.com/langchain-ai/langchain"><img src="https://github.com/langchain-ai.png?size=120" alt="LangChain" width="48" height="48" /></a><br/>
<strong>LangChain</strong><br/>
<sub>Dedicated toolkit</sub>
</td>
<td align="center" width="12.5%">
<a href="https://github.com/run-llama/llama_index"><img src="https://github.com/run-llama.png?size=120" alt="LlamaIndex" width="48" height="48" /></a><br/>
<strong>LlamaIndex</strong><br/>
<sub>Dedicated toolkit</sub>
@@ -1511,6 +1506,7 @@ pip install semantica[all] # everything
```bash
pip install semantica[agno] # Agno multi-agent integration
pip install semantica[crewai] # CrewAI integration
pip install semantica[langchain] # LangChain / LangGraph integration
pip install semantica[llm-litellm] # OpenAI, Anthropic, Gemini, Mistral, Llama, Groq, Cohere, Bedrock, Ollama, DeepSeek, and more
pip install semantica[graph-neo4j] # Neo4j graph store (LPG)
pip install semantica[graph-falkordb] # FalkorDB graph store (LPG)
+1
View File
@@ -103,6 +103,7 @@
"pages": [
"integrations/agno",
"integrations/crewai",
"integrations/langchain",
"integrations/docling",
"integrations/snowflake",
"integrations/databricks"
+81
View File
@@ -0,0 +1,81 @@
---
title: "LangChain Integration"
description: "Drop Semantica into LangChain / LangGraph pipelines via a GraphRAG retriever, VectorStore adapter, and agent tools."
icon: "link"
---
> Three drop-in adapters that bring Semantica's context graph and hybrid search into LangChain chains and LangGraph agents.
## Installation
```bash
pip install "semantica[langchain]"
```
Requires `langchain-core >= 0.3`. If langchain-core is not installed, the integration still imports — every class carries the full Semantica API and degrades gracefully (`build()` returns `None`; branch on `LANGCHAIN_AVAILABLE`).
## Components at a Glance
- **SemanticaRetriever** — `BaseRetriever`: hybrid-search seeds retrieval, then graph edges are walked `hops` steps (default 2) for GraphRAG-style results.
- **SemanticaVectorStore** — `VectorStore`: `add_texts` / `similarity_search` / `similarity_search_with_score` / `from_texts` over `HybridSearch`.
- **SemanticaKGTool** / **SemanticaDecisionTool**`BaseTool` subclasses: `semantica_query_graph` and `semantica_query_decisions` for LangGraph / tool-calling agents.
## Component Details
<Tabs>
<Tab title="SemanticaRetriever">
Hybrid search seeds retrieval; then graph edges are walked `hops` steps so results go beyond flat vector similarity. If hybrid search is omitted or fails, the retriever falls back to a `ContextGraph.query` keyword scan.
```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)
from langchain.chains import RetrievalQA
qa = RetrievalQA.from_chain_type(llm=llm, retriever=retriever)
```
</Tab>
<Tab title="SemanticaVectorStore">
Drop-in `VectorStore` for RetrievalQA / LCEL chains. `from_texts` requires a pre-configured `hybrid` instance.
```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)
```
`add_texts` delegates to a Semantica vector store with `add_documents` (pass `vector_store=` to `HybridSearch` or to `SemanticaVectorStore`).
</Tab>
<Tab title="Agent tools">
Instances are LangChain `BaseTool`s and can be passed to an agent directly.
`.build()` returns the tool, or `None` when langchain-core is absent.
```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)
```
| Tool | Description |
| :------ | :------------- |
| `semantica_query_graph` | Keyword / NL query over the shared context graph |
| `semantica_query_decisions` | Search the recorded decision log |
</Tab>
</Tabs>
+1 -1
View File
@@ -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.
"""
+67
View File
@@ -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`.
+48
View File
@@ -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"
+216
View File
@@ -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 []
+133
View File
@@ -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)
+143
View File
@@ -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
+2 -1
View File
@@ -206,6 +206,7 @@ agno = ["agno>=1.0.0"]
# needed (it pulls vulnerable transitive deps like chromadb) and would only
# duplicate the prebuilt tooling users can install separately.
crewai = ["crewai>=0.80.0"]
langchain = ["langchain-core>=0.3.0"]
# ---- File Watching ----
watch = ["watchdog>=6.0.0"]
@@ -253,7 +254,7 @@ explorer-lite = [
# dependency-audit/security gates. Install it explicitly via ``semantica[crewai]``.
all = [
"semantica[dev,viz,infra,cloud,monitoring,watch,llm-all,models-huggingface,split-all,graph-all,tripletstore-oxigraph,vectorstore-all,parse-docling,ingest-parquet,ingest-arrow,shacl,explorer]",
"semantica[dev,viz,infra,cloud,monitoring,watch,llm-all,models-huggingface,split-all,graph-all,tripletstore-oxigraph,vectorstore-all,parse-docling,ingest-parquet,ingest-arrow,shacl,agno]"
"semantica[dev,viz,infra,cloud,monitoring,watch,llm-all,models-huggingface,split-all,graph-all,tripletstore-oxigraph,vectorstore-all,parse-docling,ingest-parquet,ingest-arrow,shacl,agno,langchain]"
]
# ---------------- ENTRYPOINTS ----------------
+435
View File
@@ -2191,6 +2191,10 @@ jsonlines==4.0.0 \
--hash=sha256:0c6d2c09117550c089995247f605ae4cf77dd1533041d366351f6f298822ea74 \
--hash=sha256:185b334ff2ca5a91362993f42e83588a360cf95ce4b71a73548502bda52a7c55
# via docling-ibm-models
jsonpatch==1.33 \
--hash=sha256:0ae28c0cd062bbd8b8ecc26d7d164fbbea9652a1a3693f3b956c1eae5145dade \
--hash=sha256:9fcd4009c41e6d12348b4a0ff2563ba56a2923a7dfee731d004e212e1ee5030c
# via langchain-core
jsonpickle==4.1.2 \
--hash=sha256:7ffe34426bc797684dbf1dc84185558bd864cd25b1ff5fb01b7405e392d0a937 \
--hash=sha256:8afed18aa189fd81e2e833b426bb4af485594921f0b1d36c2001fc5637a2f210
@@ -2419,6 +2423,18 @@ kombu==5.6.2 \
--hash=sha256:8060497058066c6f5aed7c26d7cd0d3b574990b09de842a8c5aaed0b92cc5a55 \
--hash=sha256:efcfc559da324d41d61ca311b0c64965ea35b4c55cc04ee36e55386145dace93
# via celery
langchain-core==1.5.6 \
--hash=sha256:b5f73bd9688c457b31ec73657a0ad56948f889fae27acee79286e9c285632ee6 \
--hash=sha256:d6cf37bf695ecc22cddeb8461a684e353190b2ce430d99eb22bc11c0c7c00ea5
# via semantica (pyproject.toml)
langchain-protocol==0.0.18 \
--hash=sha256:70b53a86fbf9cedc863555effe44da192ab02d556ddbf2cf95b8873adcf41b5a \
--hash=sha256:ec3e11782f1ed0c9db38e5a9ed01b0e7a0d3fba406faa8aef6594b73c56a63e6
# via langchain-core
langsmith==0.11.0 \
--hash=sha256:7339f90e6fd9a1a009445b5084a7a0e56a8b6f17305ee5d7e8c5e7582217854f \
--hash=sha256:e87a3929915936c066b3fa3283ec3f3f0013e2ef7f98a443a7fbe3fab8e784a3
# via langchain-core
lark==1.3.1 \
--hash=sha256:b426a7a6d6d53189d318f2b6236ab5d6429eaf09259f1ca33eb716eed10d2905 \
--hash=sha256:c629b661023a014c37da873b4ff58a817398d12635d3bbb2c5a03be7fe5d1e12
@@ -5483,6 +5499,10 @@ requests==2.34.2 \
# rapidocr
# spacy
# tiktoken
requests-toolbelt==1.0.0 \
--hash=sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6 \
--hash=sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06
# via langsmith
rfc3339-validator==0.1.4 \
--hash=sha256:138a2abdf93304ad60530167e51d2dfb9549521a836871b88d7f4695d0022f6b \
--hash=sha256:24f6ec1eda14ef823da9e36ec7113124b39c04d50a4d3d3a3c2859577e7791fa
@@ -6634,6 +6654,104 @@ urllib3==2.7.0 \
# pinecone-client
# qdrant-client
# requests
uuid-utils==0.17.0 \
--hash=sha256:03815cea572c8a693cab5475b9d750cc161470961c7defa27e9286cad62f38f5 \
--hash=sha256:04452640d8b6920c480c16e5afe91ff896d236e0c972830f9247e0898d38c803 \
--hash=sha256:09a55b7a5ae764985cb46467496a1787678d0a1400356157a080ad95b1a36869 \
--hash=sha256:0ab4a66e7a035ad6625cfc1fbdb34f5c2d25a80ae1ef4bfee458ea2036333c6d \
--hash=sha256:0bc4c431ccd59c764080ceb43b126043325fe17861b87759d026a0cdd8423bb2 \
--hash=sha256:0f3729e839209f3457d0d8b6a35a376fdf65577a5aecaf4cc3587d3305759ba6 \
--hash=sha256:0fcca4e838af9ac9243b3358d7c14afa4dca286a87781124c272d6c4cad9c968 \
--hash=sha256:1019476b6bdc047216ef7414be5babe0fa5ccfde977c0cac4fd6c75ddec66ff7 \
--hash=sha256:14dc2f46abb1091260c0d203fcbdf4e045042cc07e49183fd3b255904b95eb70 \
--hash=sha256:1776a80d16369999b21627028cc5dbce819be83e1e079fdd7a51b587d2916db9 \
--hash=sha256:1edf2f8732e4ed95bd7b65f2658f4aa072efaaff321144f4e0d4bf6a22709263 \
--hash=sha256:1fd6f0e8a162dc0e9255b6aebe3cd175e76c33202f1bf39da9e6294b93db0099 \
--hash=sha256:21c79b61ff750abcf057163dd764ccb6196cde7a26cda1b31b45cd97769e03b3 \
--hash=sha256:220b52746d99e11964badac3c0869016e0c24bafb70a7dd5c2c072a6be3da9cc \
--hash=sha256:237722b6581bb5b4eb4cefbcbe5c6e2980a440aabe781fbe50ebf1cb71eee4cc \
--hash=sha256:239d8a281fe10bae33205b5d43185834d556b18434e0a113b5dc1dfb2fd97e91 \
--hash=sha256:29179ffb7b317239b6d6afb100d14c439c728770460718280b9c0a42d2561ec2 \
--hash=sha256:2db386941cfdecdd0b5a8ceeed5cf7479c83d1730dcf64a48d43cfa018cc3310 \
--hash=sha256:2dd4a21baaac9a88486f0dd166c5793feb101a0bb9f006f2c401657fff5a1343 \
--hash=sha256:309a35f12d99dde19032bc2259cda6431c85eeac0879134dc777cc3087d7e1cb \
--hash=sha256:3150d836290c88f1d26eb59c4db280d87417dd3bfaadd2889c77416c8f0ff6fa \
--hash=sha256:32abaafc8e91928b3d9f4d82e42d2094041e38ad6bb964066faadff28e4162f1 \
--hash=sha256:32df1944808877702ceea398c103881c09a679bb672a215e01c2a84231266bf9 \
--hash=sha256:344f7c755e280ea0ba6aeb08022190d867a80000b1715cacded54fc4b5633607 \
--hash=sha256:351462debd866f1f25e4d4f5c7fac89525b52151f0102a1bdfe94a999b046f5f \
--hash=sha256:375cde148430d60a4a07c03abaa0774c4fddfdd90de99b4ba02f24088bc9d750 \
--hash=sha256:387cf7437c94ddec08651a0f1081381299c7075bc48a6251d8922bf39973378a \
--hash=sha256:3dac0ad0cd9a2818d1775215365a4e8c2f8ada215529dd26f3f8cceeb67a6988 \
--hash=sha256:405233a5f625b3d995648f4647fa6befa4567cf3f74e1f6b9837e16f7310f0e0 \
--hash=sha256:4134353bfe3026ddab8e886002dc52bc5a0ab04611aabb0eaae23c32e6e57f64 \
--hash=sha256:42275ebd0e8e74e32cdbfb8bd88fc99576567d51d54a508020611fd8f4f463a0 \
--hash=sha256:4441600447d340ae103a353f01dbcd22ff680e5ee1a22988efe8d7b791d8fdb3 \
--hash=sha256:46a73cacdf512f473a81f65dbf84186e08cfe6e9118fa582b6c6b33a8288a30d \
--hash=sha256:4bf4d9cd1e80e73922073b9b27c143bedeb109d65f94cd12712e2c87118f2b7d \
--hash=sha256:4e2ac1c0b56f2c91b6f158e29ed96b1503223fe8aa6e79b1be1dc55bd8a5131c \
--hash=sha256:52db0e471d3d2632d35445af352591f40a8f32959a412981d9f51e068bb9514b \
--hash=sha256:53ce348ef4c6e98c02c19c522af01334fe94476ce9af0db8c4482f9f142ae9c1 \
--hash=sha256:5641071337eb11d61a001ea08793bf72216f3241f0a433ed2764804b2a3e3cc7 \
--hash=sha256:5670c52a438e21483ce715776144914a4e2a2a5c62d9dee15f8a3e90cf128ae6 \
--hash=sha256:56aa6488b931246fae11924e4bd0e2b32677e63945eecb71c29e3c2ca0dc3131 \
--hash=sha256:570db214f6d8507587a8faa968a3fe65e957daeb7bc48b27dc7f69bc3ecdd6f1 \
--hash=sha256:58838921e377791ef22c64cc92141bfae030f43651ff9272f0f28a208a9e6a5a \
--hash=sha256:589d9da7de8fa7f739bb970ac4632c9a268213117d634e1c4a58c1c1e821ca05 \
--hash=sha256:5a4370089c8b2e42f1db51d76408c7fa8eaa2934bf854d17983d16179c07c098 \
--hash=sha256:622cdde768300591ac79bfcd7bb3468e4b191b1105d5dbfe8d87c39d8f63dd46 \
--hash=sha256:673d89cc434cc9b97a0b4cf61272f6fca70a81f64eb0afbface2a0d9f77f06cd \
--hash=sha256:6a019a31bc4db89a0903a3e4f6b218571f3a6ff0ad4b3d3fe1c8f91a05ff6e3e \
--hash=sha256:6c142bd0cb4dba31c10babe00d59f7ef6460f0ef55eaa9c1a9da270684af996a \
--hash=sha256:6f29689a76fe7a49cbd629a794d0ec1eab48814e323a00a146a741b0195bde68 \
--hash=sha256:75d7411e8eb9259764dd60310738540649057cda4509b4af14b36b7f663bfeb0 \
--hash=sha256:793229621e1ad6cac55f015cfa9f4eff102accbc3da25d607b91c6b0bec167fb \
--hash=sha256:7a49f47ac26df3e431c56b825c1bae8e6d3d591fdbb7438c227cc9845a7e3d73 \
--hash=sha256:7b9044ce4acbf392d4b3a503fe377641f4deff82e6c341c36ef27af0dea76cdf \
--hash=sha256:7c89359affecebe2e39e6a116d069b363c936511a9572b308402489a26957d89 \
--hash=sha256:84ed3a2d5cd3ae6db87af20bfed3331116195ba4757ad7177fc8f12c1bbce2a9 \
--hash=sha256:89a0980d49683c00539c59cd9f46b1908c538e6b5b0a48ad12187bb856d0f391 \
--hash=sha256:8b72c2002202038666bf647f9a790906214c7c11cd0d6efef77b7d07bef3034a \
--hash=sha256:8eb3e5caca8d3a6f72ea4cce024583f989f6f2e9186f98800213fff0176e8bcc \
--hash=sha256:9082e709014946b1f6e96ae6ecd93652efca2d2a6a3ab67dbe151c8b4bf193a4 \
--hash=sha256:9205068badf453d2f0821fd5d340389b4679992d7ff79d4f3e5608996dd1b287 \
--hash=sha256:9472a8de37faf8bd216c628e0e68c8f6bef730d3ba0a5060f3b0fa460c992ac2 \
--hash=sha256:967955620df45e6cffe2e9950cb9903cb455649396f896b26b04363a91a5054b \
--hash=sha256:975c17da26c5b9d46c336b03c52a057ac28378d6f9d98b58d32a038589bb3912 \
--hash=sha256:981cc10163988defea96e8d6c507df151eab8f483e7df9ae543d5a41a4be073b \
--hash=sha256:98c88d3edd08e7245562e9815996dbc6f0bd4745e1c76462f24af5ae4e187dd1 \
--hash=sha256:9a91c4814c7150a4d798da691b7804eacd78c4b84fb392a60fa0de21341861eb \
--hash=sha256:9e311f908d2f842fca4c7dcebc4f10306b8089b204ef04cf6704b4332c9ff6ff \
--hash=sha256:9e753e81457241e2200c56a898e268e8fa25796271af0489c608f24d8e631eed \
--hash=sha256:a46bedc273b6f58f11dee816ff74999625ef8d007890f411b7a4975bf1c89330 \
--hash=sha256:abb5667a36119019b3fa320c4d10c21ebccfcc87c8a739e6a0056cee7f48dde2 \
--hash=sha256:b3131a82d0c7611f0aa480a6d36929e001a3f54ba0fc029a8118a5863cce513c \
--hash=sha256:b5d11cccba076a32321ef1380dea956821f0b51794ef59df64e58fb1cd543aae \
--hash=sha256:b6c5d2d71e1f17329150ad9427d27f4a3f29a01792e7ecdc64a98ac5368fc4d5 \
--hash=sha256:b776c7fc8755c7de06dd5a22b47c40ae84f67d13277ebb233cc84933ba4dcbcd \
--hash=sha256:c00d182e31034250690f417b9068b78eab423c10d76766664e82d9860c340479 \
--hash=sha256:c351737e2e65497c7200ab4ffb8af97e9f48be6488309abdd265fe08d66ee92f \
--hash=sha256:c4f845166b09acc65c5213a35551a7f81c17fa010ab467229b5813f79d17fe13 \
--hash=sha256:c589f5023d471ce75dd2cce61acb25ed6347e562041588a1a366808f22d7176c \
--hash=sha256:cee808b405e9095506f4e4e89924bec7ea77eac3129b6fe36eda04364b3b343b \
--hash=sha256:d11a7bc1e02da8984d32e6de9e0826c6edac00eac17de270f372bf32f9a0af63 \
--hash=sha256:d27c531edb8d1f38ca2eddaa1fa24913a460aeb721f2efd4ef42a124ce94e354 \
--hash=sha256:d2d9a63a9e6f2416ace8c109043a9280d6b34f34bb2e5421903e149403db40a6 \
--hash=sha256:d561a4c5747a1e6c7fa7c49a0292e78b4e8c456332caa084fc7abad8de828652 \
--hash=sha256:d63010803d7c368963bbe6f7ec379593e76dd581d7db0f29118d88713c9e0354 \
--hash=sha256:dd741c73440b328f937dc53b344ecadc46bc4f0cec0333a8f42b55f3468ce7ec \
--hash=sha256:de1064663aa7c839286488a319d2b3b478ca5ab5b2091ade888ed0eeca11a98a \
--hash=sha256:e252db239eb41c32248e096e0d170bce5896a4fd3405556362bc3dd83d912206 \
--hash=sha256:e288a06cbbbcd01b44386e767985c9e21d2ad9bf59829aa7058d9a2a494804ab \
--hash=sha256:e59b60a0a4cb7541480e02090d37dc2df3b72df4c2e776fff64ce3a4e3dd4637 \
--hash=sha256:e671b2322ef09106ecb1ca0f4c398b134d5e2c1f80d7a4f3336847a3072c0e94 \
--hash=sha256:e7b04935a79c03c41ad08d0a5f390aac968bfb561f1268897bc5b0f077971efd \
--hash=sha256:f7e9b8728ba07a3cb2f29d5aa1a266c2664eb8ef0fd43afa34627c92f7fac8f0 \
--hash=sha256:f9b093cb3b6c9d6233ef45a05cab064d2aa0a8cb3c5777084c9e20fcb77c2371 \
--hash=sha256:fae8b282f0cb22a5de222999f7723f4e5ec04f6fcdf4aaef879b5b36625ae2b0
# via
# langchain-core
# langsmith
uvicorn==0.52.1 \
--hash=sha256:112ec661814189acbccd3f7b86460147cc065fc92c0821afa78918780e4354dd \
--hash=sha256:e4403f9d93188cf9d1088e9f40e3acd12630e2df8675316704379a7fc20fff6a
@@ -7097,6 +7215,222 @@ xlsxwriter==3.2.9 \
--hash=sha256:254b1c37a368c444eac6e2f867405cc9e461b0ed97a3233b2ac1e574efb4140c \
--hash=sha256:9a5db42bc5dff014806c58a20b9eae7322a134abb6fce3c92c181bfb275ec5b3
# via python-pptx
xxhash==4.0.1 \
--hash=sha256:0163b5d259de23ae9e07b7eabf435ce4704f6f205589a2b154e6af4be985ce1b \
--hash=sha256:03600a8987849b2bef7be795a60a6052b635c63fa98b718b08ca5ee823691cfc \
--hash=sha256:04f9a24de11a6647666d5302fd73d6a5224ce50ddc965fb0bb44cee736e6bd7c \
--hash=sha256:06713a5aaf1d0905c5579416c020c02e42b3ceb931e86c7d3b7fb85403dee3f3 \
--hash=sha256:06d7fbd609503c3be5e65cdb6bb2f040d6a98574404e2e1d5c60815c97fff4aa \
--hash=sha256:0718ad66f4ded2411f8e62bdba549ee71e313a2d26ef5060ca3fdbf29897dd3c \
--hash=sha256:08ed8da18cd4fd0a6a5d6a444852d8fbd0e565388a74a4937085451b5f1a312a \
--hash=sha256:09f9feb118966cc6650e1806205d577eae7ca394aa6acf349a0b62a94bbeb329 \
--hash=sha256:0ab851b45c70d4992be7cdeeee16f97a0b677408c758c4b1efb1cfe8030bfd37 \
--hash=sha256:0b1082fd0f089ce9098ed77aad8b777b5d156f8ac601c69cab73811822b8ef07 \
--hash=sha256:0b20a06454b34f1531fc677c54efe2ecdec691ef9224f7fa919bf2c1363f7ff1 \
--hash=sha256:0b42a5a26607e4b2409fea174773a66f2dff9dfdbf2c1a851bb7b804e2c97535 \
--hash=sha256:101aa300de6ceef3d9c77569706330d8921fc45dd82bceed2084f1e9f2557a24 \
--hash=sha256:1216f7ba5683f17a89eb7dcb4bc50a0b743dfe1902278d7b3d0786f538118433 \
--hash=sha256:1642907941ee4b75aacc3db688af52ea02ca2305ab22af7ee686ed726b332684 \
--hash=sha256:168dd6b51725a222abc722832e56624d15a63fc2e8249021509c93f1063913f6 \
--hash=sha256:1749f0688020209fe0d357ce1e1cd9ec9c6161ed0405ea949d24581c4c43fa91 \
--hash=sha256:1b3cccf75eeb5b01639b2feadb042a8e07889293b7ca72fa2985e7dcb64763cf \
--hash=sha256:1b50223d92df94d54e1a31469335a2c74b16692e6c1cb726f1e6949514458706 \
--hash=sha256:1bc591533fc975614f7e13594daee76af96b8e1fbcf8de76c8773858fa9e7cea \
--hash=sha256:1c2200b98a805351cb3142ae4e1fdcc9e91b5e20f5d30d4862b0b96f92558f4e \
--hash=sha256:1c7c642a0f79c3e3cf2965475507574d3d1a50ec71060039d60cb87358667cb2 \
--hash=sha256:1ee523f51718e41753f04f7102bb4dc55a18d2ea5cbaceef8ec7ca08571bd428 \
--hash=sha256:1f3346c5c287ac3c7f38b20380f55e8768230e7252af59fabcf3b87ab21e4256 \
--hash=sha256:2194bf96d5f3d4e0cb65deba370ec83dda3edfba42155f9384190ed5e51ea5e2 \
--hash=sha256:237b8f63a2a0fcfb1ffc06e21dad23add44e6d354b2b014364a1d41e419a4dee \
--hash=sha256:23a4376b4a3183cb50d4d2a3179f887a7773cc695eb2c908e551bec3221b8c60 \
--hash=sha256:247ece770647c0aef080561fa996f9774b4dadce2d0c42eeb98229db7dcf820d \
--hash=sha256:2696bbac613f6880fed60316c298bf3091d4f8eee3ae2e9466f70bb76204fb0c \
--hash=sha256:26fe6238c2d5b11ed5063b9bf4eb290624b004fd074688da6bb079bd564f10d7 \
--hash=sha256:2d52dc7c33c1b83082b707f6b7814dc76d2faaa2ea62bd9c5fab4b36f83c087f \
--hash=sha256:2df3ca8757dc381e75e90a4d7995a6324f58a923c7145220a7b2c0231f66fddc \
--hash=sha256:303121aab4b7f898058582d7962ea79d9e26e2379d7b6d8743f70f2671674481 \
--hash=sha256:3088dadbffa33c29e0518578430a7dff2e901a212e487aefa5faaa0dc06dad34 \
--hash=sha256:31d86f9e81f3e84e00131ac7c54caf5119ae4ddd82c09c31cff597c813ce1ee2 \
--hash=sha256:3358097d333d40657569ec1121e21043dd7d0efa10aead1b50e8b4fa83077d7b \
--hash=sha256:33e270d302c95ec426dfa0f5a4e16bff2ab8d7b8a46faa4746affb05e684ac77 \
--hash=sha256:33fd538191f47071deef6b1f676535e2aa770f1fd150ae4cc75a34c9e930be3d \
--hash=sha256:348c8f288dc961d6bbd1985c8152a3ed7a85c95df00e82320f0c5215d922a399 \
--hash=sha256:349775ac30372b344d2338b2a168c0a1312a644194da25b8bec476d55761a128 \
--hash=sha256:34ed93e20bfd98d722b902121643791eeb4b1641871e2dc63d0d4c2d93f187df \
--hash=sha256:37f667dee0f867c42894b34e2a6fe26bf195c0ea4683d9d2b713db023f242c3a \
--hash=sha256:3891efe3d7a531ce6da0a4a50a99dd41c75b8fd4ca19d73c86431b4db5c305f0 \
--hash=sha256:38c3d22129a6958846a3098d68bc8e661704461c0be4793ae28836e4690c8478 \
--hash=sha256:3c2445edafc300cc40feb6a25a8356a971c30cd0bf47b5349c2ad74c508343b1 \
--hash=sha256:3f68fe400ceec235f3e4a4b02a28c2fd2d283584a193223c921dd4c48f1d0754 \
--hash=sha256:3fb1d30d4b6d6e2c4a08e5ac6fffdb2b572d2cfcca15a5509cf4e7a1350f955c \
--hash=sha256:41e579025a6e13a99e6d71e39c9cfc621a0dcdbbf19106325e145fa858f2d794 \
--hash=sha256:421b94f3ba7067958d02e38960d987756347aa150df06df11aa68ae1af78c619 \
--hash=sha256:427b62d62d4f967fbb10b82a3813e4875c2a6e7e7634739f17265b650c7f65a6 \
--hash=sha256:436e11b4dd966afe5f7f665e4cc4c5485ffe3ceb42f25a22e1701d236abf1853 \
--hash=sha256:43bcf2a871f28f16135545415cab3ec43904d4c80425a64598a9e6cebfb2b5ba \
--hash=sha256:43e5f9169e73d0f0db33b5f6b8554bcce69ac278c966daf83d5eb4eb2f13829f \
--hash=sha256:440c401e146ce64bdb3beb8ff0c84677b6f21307c28a34779071cecee5d4d70c \
--hash=sha256:44ab12e8cd17d4f001769f00ad465208b4bcb897ed29e65f058f74466b57a98f \
--hash=sha256:4528cf80ebbbf57d40edfb31521ae265daa6dd636d615b1cf0ac86209579e59d \
--hash=sha256:45e88111ebe331de478ef8d4293efbe88f3cf8b863386c9a2357136b838e1af0 \
--hash=sha256:4741d42d59e4e5fa1a86c17ab9c27dc8ea459c700d91b6742fdb9138d9a516cb \
--hash=sha256:4751f1d7eecae6b2d2a773630f1a7248f125c9a92a456694d03c15bceffc9d68 \
--hash=sha256:488ca5c5e28ef56ec4bbb12f835b3f1cbecc5f3510062e70117bc6594851932a \
--hash=sha256:4972332c079d6aad69c4620a68d015a4ecb33141583f70d642cf9edf6a713763 \
--hash=sha256:4a252fb862b0ae2590587e625f47a0e03da05cf0205e8830b67b6596c06038b1 \
--hash=sha256:4a76345f5aceb4ec404918edf9c7f2b5507db864dc0d7455982009ac0890b57b \
--hash=sha256:4af350bc3f329970c0e3a59af84a8a30998bf8a9167eb50cd48e59baaa1d7bec \
--hash=sha256:4bbf3ff651e0f1a19beb5d0f48e0874a9bad2482a588c9d214c96ef1fff1cd9c \
--hash=sha256:4e5141543c7f7fe3087500bbb4ac2845cb528a980aa91f8f1e661e2292ff4a5d \
--hash=sha256:4f5e5c6df4b703afcbe9352d238a51efd97c3b91fdc3a2052e40fdacb1e7505f \
--hash=sha256:515a822c73abbf6a0b7c70976d9662be342835c9d78b8dc7c023411f39c35dbc \
--hash=sha256:554f87034635bcec47c5d72447bf3db7e02da1bf493a0ada010db28a76f891c6 \
--hash=sha256:567cbc630302a46a8ecfd943b309ccf5372bb3718f1f3762d452df30f033bcf0 \
--hash=sha256:57d7fa8f23908d173001c21a9e82bfc6ad997d1b6c270fb121812b7ed158891c \
--hash=sha256:5adf927dca8c47fde7e683fe69efdd81bc865c4db1fb6bb00b391e2b6185207b \
--hash=sha256:5b7875ac1a2edcb691f27642b8b94b904baa6bcecb7d79c72df2228ba8cb5c51 \
--hash=sha256:5b7979f71d06ae45a769de0699900a246d8cb632db1e8bfdc79ec019063a503c \
--hash=sha256:5c2d525a3afabcd8e3549d85fc7e111fde6bc302d06a1893fe73adb79823415e \
--hash=sha256:5dc434c946012e6d8a72b10f970ea30755b718251dd7591dbfdabafd3bcb21bc \
--hash=sha256:5f1ea31d61bcd2cd2f3ec4ca80a64187bbd7948f490b63cf0dcbc6e717b4c1e9 \
--hash=sha256:62198213fc3e0c56e567894b318ba45834e007d065f84ba6dc9165d21546fc56 \
--hash=sha256:63aa52659bc32bb9bd7cb5caf523b4d14429a477762cfac886132d687c1f80fc \
--hash=sha256:649f2682c090cca1ac4037866381f3652eaacbd56e5178030f4ce1325b8f945b \
--hash=sha256:67e57b834e07ed973cee7b6da1548ff28a56458d77696fd2a5f397f340694848 \
--hash=sha256:684160b3c0a9b62c6f0de90f44e11dc5d8643dcfa18a5856b45fb1c47478bb71 \
--hash=sha256:6a8c5ce76b94ba49f3be8a8f2611abc6564210702c72ac9e237ca2bebfd17794 \
--hash=sha256:6a9f98af872355e0c02439e48583958eee00e60b928bb20476460d9d40cb7b4e \
--hash=sha256:6c45258a37fc22721395c09927cb982d3e7a83607cab15be7e2416501bd3a330 \
--hash=sha256:6cbf4e21ef0890804b5bb9ad25c48f9c127758d7f6c66bef374efcacc63c738a \
--hash=sha256:6cf633df84d80a1668fcf61e330791dae46825e395549e7d34f376411e75088a \
--hash=sha256:6efb8f21cc136c79b3e5bb747c8682d37916fb202cdbbc32182de5c4e47f821f \
--hash=sha256:70129ebb8f20e1ac1da58b78ed381624bd689a43a9a7366560bd8fabea145105 \
--hash=sha256:704381264b36a18b9c62ecbabe2e71d0fc58c77c129c15355c989b10bf05b6b0 \
--hash=sha256:7236be540d6be9ce448d98b940dd26ddf70ca41012e8a14a53fd9354cefe4e8d \
--hash=sha256:72f34834518157a75e7090f328ee7a16c70c804cfc7c694fa069cc888e9fc03e \
--hash=sha256:74379a577a9f3b6afbdedf1b90e5c7764467051977f18a326d7d607336d743bd \
--hash=sha256:74a164e8b63f1e9cf35c9a7809d082b033d1a00e7375d5d814415436e7867e57 \
--hash=sha256:760de77279e9cf9c81d012ce0705cba13afccee9b09c480f17d778c8c5cefae8 \
--hash=sha256:764b32d52d15b8b95ac8160e540772fa1adeb611fe40bffaeb42e7bf98279e44 \
--hash=sha256:79a3203aadf39637869dfea1185227d8452844d78b837e54fb1117b4d34ba5c3 \
--hash=sha256:7c343ee174d417a44d0c3355602c0cbbfa52a04d1bbbf1723378c7d2c8f60626 \
--hash=sha256:7e27dbed5c4ba033919e4b4ed8dc14e029e91d14a93cd9f920d25277c7df6781 \
--hash=sha256:81507a68ba84c55241fb61cce1469f473a5da4205fc8ef6f698e5948eea8dd88 \
--hash=sha256:81664268dba92e037b740ecf37fa02f1cab4a391f93f28e35792b3341c60648f \
--hash=sha256:839f58c5bd9989875be0fd28446dbf32cace2c2cd8bf2f6762acdc38a95cd1aa \
--hash=sha256:83b8c2013edb5dc1f9e7268b6496130705bc48d79c86bb8817b3d210b81a5513 \
--hash=sha256:84df5f8da574caadbc0cb1b8866ecc2368cc941f0cd05f677756c802f370dafa \
--hash=sha256:8580aab306888224074c7edeec734de0c3c5ccde65b2da4e6c9a5e28f7c0a1bd \
--hash=sha256:85bdd40cb505a11e0ca04191711266c5fd696ed786ae83849955e457774edc96 \
--hash=sha256:85e402dab0f9acd3604539747c6fcc57dc188a18af6ab07eb8189351cd32466c \
--hash=sha256:863f3d3b44110f7243e86cf994aa5c5d88f2348b6e84ab4402fadadfbf9f7da7 \
--hash=sha256:86b2b12bec60c678ed8f5cca0258ad93a8928ebddb6ca7732f0875afe1451d1a \
--hash=sha256:87aa309a93bd5ec13f14309a305ff4e9bf74c5363fc46c264c0a22edfd5b0670 \
--hash=sha256:87cbdec1a7dd930079671a60b249f3ca4e773e6fbd0676e21e36fdc9dd0f3b00 \
--hash=sha256:87da13df72c5612771cd905a8b121e0bfea62d7659b1c92198736eb722220e83 \
--hash=sha256:88d87719fe6bddf117238b341c5db851f8e96ba68ad9832b450e4a43dc60b37f \
--hash=sha256:8b4477edc03091f51f5309406d230851c23cf4822029e3bf40b8df53093fff1c \
--hash=sha256:8b99ebaf9e816ac5069423b1367ee7e8078fbcebcf62545506bb0608d2f4f468 \
--hash=sha256:8ba782ca3bf1e81492611152b9a0d5264971339e95e34d69de0ac2c926be496d \
--hash=sha256:8bcba9456242ebf180a04d9443812fd85ffe6bd12bda464dd116fcece8886ff3 \
--hash=sha256:8c9fe122444e129881afd1d4d1c7ac0d3ce2d91b68c2b40173b6025ff1c31f9a \
--hash=sha256:8ec4777d92fd61a5c8fdeddab894fd65bea301a8092fb5419ec6472aa4d458d7 \
--hash=sha256:90cb2a1c9cc503a054a19612b48ff6e8e47805f618bdb3224a07568aad03a37e \
--hash=sha256:9283d9dd6b44acad35118e2976fc763a065509e4118debdb61916ec322ed17b9 \
--hash=sha256:94ac8a6b8c47951173f0b67bf862bcb971bf24e493b9fbbdb0e010cbbc7d9f54 \
--hash=sha256:96d8de55029d42251945531f6aa7590c32b48163c66a43bf29d8657d7446a377 \
--hash=sha256:96dedccfb09a73a25751053a183159b88f4ee75f388df8166040c152ac0531c6 \
--hash=sha256:9761ff4a0ffa583fe850731ad24fe82c88cccb7a2294727db0955f3279a4cb3f \
--hash=sha256:97b455de3e8b1b0b1e4594cb61a468992563f03ca264062fbb0a66b393c01d90 \
--hash=sha256:97b94fb29abf21f5f0bde15f7dbdd3a4aa2dc59f37026adc7b4bee8563b84375 \
--hash=sha256:99054b838b74d8d3995ea0d410976ae967c46207ae22d6ddfc535e809197dab9 \
--hash=sha256:99166cc98637e8bf550cda2aab07f4f1d5f899c45fbd721801aeabcc9d404824 \
--hash=sha256:9a51b061d54cda8b83e62c44458bfbf0dabbef9b975dd9649952ba5076b9f349 \
--hash=sha256:9b1dddc257279417d93c9e59420d49ef90aece90d7a01996db3aade74b0281b1 \
--hash=sha256:9c3c4b9aa9a27196b921197f7daf9e6c1412739df06a99cfa6e923879362eff6 \
--hash=sha256:a14578102a6081465aec9cf73c76c3cd3f79f0709bdb3b8ae7ab0b54c9d8b089 \
--hash=sha256:a16a3fa6936e36bb1414d16a6bd012c9033e5161b68b426805b61d895392437d \
--hash=sha256:a33de7633c948ab2dc144af370a66e7e7af29b425dcd0f7e4f59689fb9391b53 \
--hash=sha256:a43418e1a90b4809a9caf64aeb8b0696e3e1f300a323acc1e6ee2f93ae319fcf \
--hash=sha256:a4553d36cc0b7fce1f35ba8a94dfd775aa3ed12f5eab2dc3b46ac75a0706b0bb \
--hash=sha256:a5b21b42a01a343096a1c018d35e9b7aec9c7065dda53ae8da071e37478b2cea \
--hash=sha256:a65785e653573fcd1e33062760ab4c3c3440e8e910765018e4b6ed4ad07b54a0 \
--hash=sha256:a6671a8f6ea4f2101ce11fab5023a2e59391cff249fc3928cecb69d971525fd5 \
--hash=sha256:a69e8946e4902ea11fc1c557740cdbfe7d75c78fcc5e4324ff89a696a634357d \
--hash=sha256:a6e3653df1a70b8ac4191216324242e4be2bca18c9a7c10934e1bd56dc7ca15e \
--hash=sha256:a865d2d470220e659220fdb59d5b6c4422802d8d6098e1324bc4d12444798914 \
--hash=sha256:a949b072ea59c6eca0811ccd9e95133cc50d2afda8d464b5b077c78f78efa269 \
--hash=sha256:aa6ccc7f31018484d652cf52db020003433f3c9fa83189c028bd807d2adde503 \
--hash=sha256:ac0f291ab6485bd71f33941f9b92771318332a05d505460b41e893a549caadc0 \
--hash=sha256:acb31ecdd1a97fab5cd39a84ee9f515e727d319f796fec48703b8339b9998360 \
--hash=sha256:acf52474b2494ef66dc7e0fb6d5e2b50c18313039ad4d275fbf9f9907c804bc5 \
--hash=sha256:ad889d58361a26ba75f5d6a1a0da08ed4950ec4ac8a6da86e1c5ce1b95ccb43f \
--hash=sha256:adbd48b30e3f82c89fb2b3e6a87cdd28d113b190a5ed0ee2dee286323ee9a621 \
--hash=sha256:af05a3f650220a6c59fa0ad2410249f2d2470a05225807c378fb67458693f8df \
--hash=sha256:b3662719007e059abde7eddacf8517142ba076ddc7b30c807260e57d28c3c191 \
--hash=sha256:b3bece52127ac20044311ee73567f9f0893b5de64f9028aecc90cc740cfd525a \
--hash=sha256:b4c8842fb19d78b5e8c2a52baf4c8357658cc56c62bc822b86ce0f942f28e286 \
--hash=sha256:b659fad79c99b0238c7ad7e9d7dbf4eebfea9097c2dba65fa0a4d18a25b29a2f \
--hash=sha256:b6c1f9c59bbe593f88a0aad30be4150f15bd57bd64efb95feeabcb8e563f1ecd \
--hash=sha256:bdd16718b63aa3ebd68aabb79021a40e47c81374852d41a306b9453141bbcbee \
--hash=sha256:bf430c587f447a554c53768ad76b9846fe7c5632180ef6f69c4fce8b0552fbd0 \
--hash=sha256:bfed61996d618eb90d6eaae0178002e3466a28b06bfc557a7a3a7266378d8c5a \
--hash=sha256:c09ada495567c9c9a8156c5ebcfb93be7fece0755062d738c972dcbecd0d84b5 \
--hash=sha256:c0e6ccc2b19ec8a726b2e26062ac71ea63e15500d6bf85910e42481844fdffc1 \
--hash=sha256:c101180495cb4ba3617b279a944345c53a5e73b0c150053d1fa8d8af32de9579 \
--hash=sha256:c10b9206753b64aa791b35b201485477525b26fdec5bf86e8364c388a03e2592 \
--hash=sha256:c3074db513c81f764053e3da079312ecf85a50d8350c71f4cc0105d9662a9e6c \
--hash=sha256:c30dd1af66a820820398b26e0d74e7a9aa43cae705924f23ed828cd8e5c26c3d \
--hash=sha256:c57963970d359a72262f7fe6be88f945e2334d4bc41462b7f08c37b0abf35ca6 \
--hash=sha256:c6301d92545c591ad31c3e050aa40a5f8a4c16413f1f9e6f9322c6f0f9d2b736 \
--hash=sha256:c6370189e8e66b7e608f533b939a9de092ddca6cce084ca0d3d414d2ed5b5d59 \
--hash=sha256:c6fc415b5568bd9accc7187f1729a99707330c0a67a8b9f93c1149ed573ed75d \
--hash=sha256:c7484fea54964edd417cc3a104d5180562514aa7c4e2a2bc26d776ef0c4cb4a1 \
--hash=sha256:cba763d84b06bda2c38d5185dee76f1b9dfdc0789e96e476d9e10005526d0788 \
--hash=sha256:cd878d32f5c6cbce9783f8d6897561fb772211edba9dde49d85672b88ed45276 \
--hash=sha256:ce6d5cc94a50291d080259a126cbf1e9ba4ac861e6429d2f3cdbb1474f51945d \
--hash=sha256:d0d24a4f3fb63852cd09af46ae4b7a4d00cc8b8615a046dca543786e728d1056 \
--hash=sha256:d1e0d1ea6e44f51808a9e8469c8afdebcdf6fa23d1ea524a0303d57d23919712 \
--hash=sha256:d54b8ae068af532c8cdf56abb9e09a60fbe7b10792444c9c27987bb6d3b450fa \
--hash=sha256:d55bf4ef10eb09b8b6866790e083d26d087d84caa3cc0946ba87c3ca7ecaf7b7 \
--hash=sha256:d9f3848ffaf010bdbabdbf4c25641fa258b6227ff27bc74a4d06edef521a4873 \
--hash=sha256:da0264844a09b538c894e5eff25313d941deb4dedec2131b98418a71a3c9944e \
--hash=sha256:da544672efd9ad76077928a3e6c5d894e52ce82d3bf14002db4a1bf17d1a36a2 \
--hash=sha256:daade8936c4deaaf7b01561324ce438ba4f885d717e9adc62b4d67212ad7d7bd \
--hash=sha256:dd649663ddeafbfd4734eb8abae921dd5baa1242f20bda54e8bc927369ccded4 \
--hash=sha256:deca2a30d983d240b8375ec2ee0a4288e72042827fc61df2f7671f8467e4cb2f \
--hash=sha256:e259bb7e1e2d8de6b35f430f5c7220b1c0ebf3962d1ba7ec7545980d5931edb8 \
--hash=sha256:e3996ff9b6f99180357024336bf5749a8ad6476a9a2523e535c5212b995b12a2 \
--hash=sha256:e3eba72f9bb84fe696516f4cbca68d3d74a376157e68bacddbb7f2516af61523 \
--hash=sha256:e4296fcc790876a8b0f297edc83d3b088457b774d8f67b4636807f8a2ec69a79 \
--hash=sha256:e53926e76131a74e79cc0b39fa712c227875f180afc68646bd1e1d8a17e60313 \
--hash=sha256:e681a6fc7e4f715252b9b5acfb30536ec7dd1f75033a32dc617e6fa95af1a3fd \
--hash=sha256:e71b34978e77868cbf2d18c5206a4603f9c644dd7181bec5643bd40141d3b8c5 \
--hash=sha256:e8cda075b10bb3917b002c74a04f9e02b7d13b5bf732571404d51c52b11c7329 \
--hash=sha256:e90b4bcf1d9eb1010fdaee7c9209fb667e74c0684f3ba17f9032bd7319da90c9 \
--hash=sha256:e961093277ff9d42addb9dad5614dfb7800ccba07c245c39c8e9b4daa35d160c \
--hash=sha256:e9701c073bd062fb6bf6be51b47186ad15f1e87feedf4ea07198e0333ec068dc \
--hash=sha256:e998cb3685b92101ec5de0fb4d9485cf01e50bc418211955c55d98064664cf4c \
--hash=sha256:ea5ecf800b45bdb34afe05a1d0dae1f8ea02a290e50636dccd399063f6b180f8 \
--hash=sha256:ec1a470c6db94ac4589c203921e89ac1bc13e796a8b1784d8135e1893559cd3b \
--hash=sha256:edccc2ec58435a580f96a48a3ccae8cd0a480824119165dd90108718ad81ae6e \
--hash=sha256:f00330ac7e24769e2032203f2b01794d670916b0c1799fd261340f1af9499875 \
--hash=sha256:f09ee747e2a5f876cc5ad56947734811828335e13b403dd8ea1e06d77a9dd48d \
--hash=sha256:f18732adcc271741bd651c3e56fa519d8a237d2cccda01fe3afb226bf87f783b \
--hash=sha256:f1b603d0686c99fa0879f104a74e7db58367634c6e50ba827bee9aa095e23205 \
--hash=sha256:f33cf0baa91eccd2cb7b62bf00f10c2264ef578b71dd33a12962e71a36eb4d32 \
--hash=sha256:f3e1a44af01b6692de0ec6caba5f0bf93ceb36896e02b7fc00952c6ea7ef39e1 \
--hash=sha256:f484ed57bb3e4142f9d6439568658c38be5f94b702ba00a1ff32c69783b6c66d \
--hash=sha256:f5d031f35962e5483a613214e61f09fe24ab523062c3646d592dc16c4a217451 \
--hash=sha256:f6247f5e23ee94f2557ac9dab738a336f607c6ff476fcf66ca70c3aef5eee15a \
--hash=sha256:f7db035447a0ac8959aa230c5d36545ecf9f547413eb1711c0ca6f0ba1418925 \
--hash=sha256:f83295394d34e1287e5b30fcc496c13b92cf886a131f3dae5444e38da8757efb \
--hash=sha256:fac4832b638000106207bc44e44b9616a6a416aaee56c62b01d61f3705e49f58 \
--hash=sha256:fb59a0dd61fb2ad481c03fda399d78ce57dab6bb62c2c8fdb446a7ba4754b89a \
--hash=sha256:fc737c05ca2d48e5dcdbbb249314df3fc6c2a0be6da8b0aa28e13d72afaad7cd \
--hash=sha256:ff48915bf1871a1f19f74c11834c6329443d306cedc0c05fe7fe617810422a80 \
--hash=sha256:ffa44b4c7c5d0ffa31356b4428659516c0e47647825c74079a296b3857b6d99d
# via langsmith
yarl==1.24.5 \
--hash=sha256:0055afc45e864b92729ac7600e2d102c17bef060647e74bca75fa84d66b9ff36 \
--hash=sha256:0465ec8cedc2349b97a6b595ace64084a50c6e839eca40aa0626f38b8350e331 \
@@ -7207,3 +7541,104 @@ zipp==4.1.0 \
--hash=sha256:25ad4e16390cd314347dd8f1de67a2ac538ae658ed4ab9db16029c07c188e97f \
--hash=sha256:4cb57381f544315db7688e976e922a2b18cdb513d21cc194eb42232ba2a3e602
# via importlib-metadata
zstandard==0.25.0 \
--hash=sha256:011d388c76b11a0c165374ce660ce2c8efa8e5d87f34996aa80f9c0816698b64 \
--hash=sha256:01582723b3ccd6939ab7b3a78622c573799d5d8737b534b86d0e06ac18dbde4a \
--hash=sha256:05353cef599a7b0b98baca9b068dd36810c3ef0f42bf282583f438caf6ddcee3 \
--hash=sha256:05df5136bc5a011f33cd25bc9f506e7426c0c9b3f9954f056831ce68f3b6689f \
--hash=sha256:06acb75eebeedb77b69048031282737717a63e71e4ae3f77cc0c3b9508320df6 \
--hash=sha256:07b527a69c1e1c8b5ab1ab14e2afe0675614a09182213f21a0717b62027b5936 \
--hash=sha256:0bbc9a0c65ce0eea3c34a691e3c4b6889f5f3909ba4822ab385fab9057099431 \
--hash=sha256:0be7622c37c183406f3dbf0cba104118eb16a4ea7359eeb5752f0794882fc250 \
--hash=sha256:106281ae350e494f4ac8a80470e66d1fe27e497052c8d9c3b95dc4cf1ade81aa \
--hash=sha256:10ef2a79ab8e2974e2075fb984e5b9806c64134810fac21576f0668e7ea19f8f \
--hash=sha256:1673b7199bbe763365b81a4f3252b8e80f44c9e323fc42940dc8843bfeaf9851 \
--hash=sha256:172de1f06947577d3a3005416977cce6168f2261284c02080e7ad0185faeced3 \
--hash=sha256:181eb40e0b6a29b3cd2849f825e0fa34397f649170673d385f3598ae17cca2e9 \
--hash=sha256:1869da9571d5e94a85a5e8d57e4e8807b175c9e4a6294e3b66fa4efb074d90f6 \
--hash=sha256:19796b39075201d51d5f5f790bf849221e58b48a39a5fc74837675d8bafc7362 \
--hash=sha256:1cd5da4d8e8ee0e88be976c294db744773459d51bb32f707a0f166e5ad5c8649 \
--hash=sha256:1f3689581a72eaba9131b1d9bdbfe520ccd169999219b41000ede2fca5c1bfdb \
--hash=sha256:1f830a0dac88719af0ae43b8b2d6aef487d437036468ef3c2ea59c51f9d55fd5 \
--hash=sha256:223415140608d0f0da010499eaa8ccdb9af210a543fac54bce15babbcfc78439 \
--hash=sha256:22a06c5df3751bb7dc67406f5374734ccee8ed37fc5981bf1ad7041831fa1137 \
--hash=sha256:22a086cff1b6ceca18a8dd6096ec631e430e93a8e70a9ca5efa7561a00f826fa \
--hash=sha256:23ebc8f17a03133b4426bcc04aabd68f8236eb78c3760f12783385171b0fd8bd \
--hash=sha256:25f8f3cd45087d089aef5ba3848cd9efe3ad41163d3400862fb42f81a3a46701 \
--hash=sha256:2b6bd67528ee8b5c5f10255735abc21aa106931f0dbaf297c7be0c886353c3d0 \
--hash=sha256:2e54296a283f3ab5a26fc9b8b5d4978ea0532f37b231644f367aa588930aa043 \
--hash=sha256:3756b3e9da9b83da1796f8809dd57cb024f838b9eeafde28f3cb472012797ac1 \
--hash=sha256:37daddd452c0ffb65da00620afb8e17abd4adaae6ce6310702841760c2c26860 \
--hash=sha256:3a39c94ad7866160a4a46d772e43311a743c316942037671beb264e395bdd611 \
--hash=sha256:3b870ce5a02d4b22286cf4944c628e0f0881b11b3f14667c1d62185a99e04f53 \
--hash=sha256:3c83b0188c852a47cd13ef3bf9209fb0a77fa5374958b8c53aaa699398c6bd7b \
--hash=sha256:4203ce3b31aec23012d3a4cf4a2ed64d12fea5269c49aed5e4c3611b938e4088 \
--hash=sha256:457ed498fc58cdc12fc48f7950e02740d4f7ae9493dd4ab2168a47c93c31298e \
--hash=sha256:474d2596a2dbc241a556e965fb76002c1ce655445e4e3bf38e5477d413165ffa \
--hash=sha256:4b14abacf83dfb5c25eb4e4a79520de9e7e205f72c9ee7702f91233ae57d33a2 \
--hash=sha256:4b6d83057e713ff235a12e73916b6d356e3084fd3d14ced499d84240f3eecee0 \
--hash=sha256:4d441506e9b372386a5271c64125f72d5df6d2a8e8a2a45a0ae09b03cb781ef7 \
--hash=sha256:4f187a0bb61b35119d1926aee039524d1f93aaf38a9916b8c4b78ac8514a0aaf \
--hash=sha256:51526324f1b23229001eb3735bc8c94f9c578b1bd9e867a0a646a3b17109f388 \
--hash=sha256:53e08b2445a6bc241261fea89d065536f00a581f02535f8122eba42db9375530 \
--hash=sha256:53f94448fe5b10ee75d246497168e5825135d54325458c4bfffbaafabcc0a577 \
--hash=sha256:5a56ba0db2d244117ed744dfa8f6f5b366e14148e00de44723413b2f3938a902 \
--hash=sha256:5f1ad7bf88535edcf30038f6919abe087f606f62c00a87d7e33e7fc57cb69fcc \
--hash=sha256:5f5e4c2a23ca271c218ac025bd7d635597048b366d6f31f420aaeb715239fc98 \
--hash=sha256:6a573a35693e03cf1d67799fd01b50ff578515a8aeadd4595d2a7fa9f3ec002a \
--hash=sha256:6c0e5a65158a7946e7a7affa6418878ef97ab66636f13353b8502d7ea03c8097 \
--hash=sha256:6dffecc361d079bb48d7caef5d673c88c8988d3d33fb74ab95b7ee6da42652ea \
--hash=sha256:7030defa83eef3e51ff26f0b7bfb229f0204b66fe18e04359ce3474ac33cbc09 \
--hash=sha256:7149623bba7fdf7e7f24312953bcf73cae103db8cae49f8154dd1eadc8a29ecb \
--hash=sha256:72d35d7aa0bba323965da807a462b0966c91608ef3a48ba761678cb20ce5d8b7 \
--hash=sha256:75ffc32a569fb049499e63ce68c743155477610532da1eb38e7f24bf7cd29e74 \
--hash=sha256:7713e1179d162cf5c7906da876ec2ccb9c3a9dcbdffef0cc7f70c3667a205f0b \
--hash=sha256:78228d8a6a1c177a96b94f7e2e8d012c55f9c760761980da16ae7546a15a8e9b \
--hash=sha256:7b3c3a3ab9daa3eed242d6ecceead93aebbb8f5f84318d82cee643e019c4b73b \
--hash=sha256:809c5bcb2c67cd0ed81e9229d227d4ca28f82d0f778fc5fea624a9def3963f91 \
--hash=sha256:81dad8d145d8fd981b2962b686b2241d3a1ea07733e76a2f15435dfb7fb60150 \
--hash=sha256:85304a43f4d513f5464ceb938aa02c1e78c2943b29f44a750b48b25ac999a049 \
--hash=sha256:89c4b48479a43f820b749df49cd7ba2dbc2b1b78560ecb5ab52985574fd40b27 \
--hash=sha256:8e735494da3db08694d26480f1493ad2cf86e99bdd53e8e9771b2752a5c0246a \
--hash=sha256:913cbd31a400febff93b564a23e17c3ed2d56c064006f54efec210d586171c00 \
--hash=sha256:9174f4ed06f790a6869b41cba05b43eeb9a35f8993c4422ab853b705e8112bbd \
--hash=sha256:9300d02ea7c6506f00e627e287e0492a5eb0371ec1670ae852fefffa6164b072 \
--hash=sha256:933b65d7680ea337180733cf9e87293cc5500cc0eb3fc8769f4d3c88d724ec5c \
--hash=sha256:9654dbc012d8b06fc3d19cc825af3f7bf8ae242226df5f83936cb39f5fdc846c \
--hash=sha256:98750a309eb2f020da61e727de7d7ba3c57c97cf6213f6f6277bb7fb42a8e065 \
--hash=sha256:99c0c846e6e61718715a3c9437ccc625de26593fea60189567f0118dc9db7512 \
--hash=sha256:a1a4ae2dec3993a32247995bdfe367fc3266da832d82f8438c8570f989753de1 \
--hash=sha256:a3f79487c687b1fc69f19e487cd949bf3aae653d181dfb5fde3bf6d18894706f \
--hash=sha256:a4089a10e598eae6393756b036e0f419e8c1d60f44a831520f9af41c14216cf2 \
--hash=sha256:a51ff14f8017338e2f2e5dab738ce1ec3b5a851f23b18c1ae1359b1eecbee6df \
--hash=sha256:a5a419712cf88862a45a23def0ae063686db3d324cec7edbe40509d1a79a0aab \
--hash=sha256:a9ec8c642d1ec73287ae3e726792dd86c96f5681eb8df274a757bf62b750eae7 \
--hash=sha256:aaf21ba8fb76d102b696781bddaa0954b782536446083ae3fdaa6f16b25a1c4b \
--hash=sha256:ab85470ab54c2cb96e176f40342d9ed41e58ca5733be6a893b730e7af9c40550 \
--hash=sha256:b9af1fe743828123e12b41dd8091eca1074d0c1569cc42e6e1eee98027f2bbd0 \
--hash=sha256:bfc4e20784722098822e3eee42b8e576b379ed72cca4a7cb856ae733e62192ea \
--hash=sha256:bfd06b1c5584b657a2892a6014c2f4c20e0db0208c159148fa78c65f7e0b0277 \
--hash=sha256:c19bcdd826e95671065f8692b5a4aa95c52dc7a02a4c5a0cac46deb879a017a2 \
--hash=sha256:c2ba942c94e0691467ab901fc51b6f2085ff48f2eea77b1a48240f011e8247c7 \
--hash=sha256:c8e167d5adf59476fa3e37bee730890e389410c354771a62e3c076c86f9f7778 \
--hash=sha256:ca54090275939dc8ec5dea2d2afb400e0f83444b2fc24e07df7fdef677110859 \
--hash=sha256:d7541afd73985c630bafcd6338d2518ae96060075f9463d7dc14cfb33514383d \
--hash=sha256:d8c56bb4e6c795fc77d74d8e8b80846e1fb8292fc0b5060cd8131d522974b751 \
--hash=sha256:da469dc041701583e34de852d8634703550348d5822e66a0c827d39b05365b12 \
--hash=sha256:daab68faadb847063d0c56f361a289c4f268706b598afbf9ad113cbe5c38b6b2 \
--hash=sha256:e05ab82ea7753354bb054b92e2f288afb750e6b439ff6ca78af52939ebbc476d \
--hash=sha256:e09bb6252b6476d8d56100e8147b803befa9a12cea144bbe629dd508800d1ad0 \
--hash=sha256:e29f0cf06974c899b2c188ef7f783607dbef36da4c242eb6c82dcd8b512855e3 \
--hash=sha256:e59fdc271772f6686e01e1b3b74537259800f57e24280be3f29c8a0deb1904dd \
--hash=sha256:e7360eae90809efd19b886e59a09dad07da4ca9ba096752e61a2e03c8aca188e \
--hash=sha256:e96594a5537722fdfb79951672a2a63aec5ebfb823e7560586f7484819f2a08f \
--hash=sha256:ea9d54cc3d8064260114a0bbf3479fc4a98b21dffc89b3459edd506b69262f6e \
--hash=sha256:ec996f12524f88e151c339688c3897194821d7f03081ab35d31d1e12ec975e94 \
--hash=sha256:f27662e4f7dbf9f9c12391cb37b4c4c3cb90ffbd3b1fb9284dadbbb8935fa708 \
--hash=sha256:f373da2c1757bb7f1acaf09369cdc1d51d84131e50d5fa9863982fd626466313 \
--hash=sha256:f5aeea11ded7320a84dcdd62a3d95b5186834224a9e55b92ccae35d21a8b63d4 \
--hash=sha256:f604efd28f239cc21b3adb53eb061e2a205dc164be408e553b41ba2ffe0ca15c \
--hash=sha256:f67e8f1a324a900e75b5e28ffb152bcac9fbed1cc7b43f99cd90f395c4375344 \
--hash=sha256:fd7a5004eb1980d3cefe26b2685bcb0b17989901a70a1040d1ac86f1d898c551 \
--hash=sha256:ffef5a74088f1e09947aecf91011136665152e0b4b359c42be3373897fb39b01
# via langsmith
@@ -0,0 +1,92 @@
"""
Graceful-degradation tests for the LangChain integration.
Runs the adapters in a fresh subprocess with langchain-core hidden, so the
object-base path is proven even when this env has langchain-core installed.
"""
from __future__ import annotations
import os
import subprocess
import sys
from types import SimpleNamespace
import pytest
from integrations.langchain import (
LANGCHAIN_AVAILABLE,
SemanticaDecisionTool,
SemanticaKGTool,
)
REPO_ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__))))
_SCRIPT = r"""
import sys
from types import SimpleNamespace
class _BlockLangchain:
def find_spec(self, fullname, path=None, target=None):
if fullname == "langchain_core" or fullname.startswith("langchain_core."):
raise ImportError("langchain_core blocked for degradation test")
return None
sys.meta_path.insert(0, _BlockLangchain())
for name in list(sys.modules):
if name == "langchain_core" or name.startswith("langchain_core."):
del sys.modules[name]
from integrations.langchain.retriever import LANGCHAIN_AVAILABLE as RET_AVAIL
from integrations.langchain.vectorstore import (
LANGCHAIN_AVAILABLE as VS_AVAIL,
SemanticaVectorStore,
)
from integrations.langchain.tools import (
LANGCHAIN_AVAILABLE as TOOL_AVAIL,
SemanticaKGTool,
SemanticaDecisionTool,
)
from integrations.langchain.retriever import SemanticaRetriever, _get_document
assert RET_AVAIL is False and VS_AVAIL is False and TOOL_AVAIL is False
retriever = SemanticaRetriever(graph=SimpleNamespace(), hops=2)
assert retriever.hops == 2
store = SemanticaVectorStore(hybrid=SimpleNamespace(), tags=["x"])
assert store.hybrid is not None
graph = SimpleNamespace(query=lambda q, limit=10: [{"q": q, "limit": limit}])
assert SemanticaKGTool(graph).build() is None
assert SemanticaDecisionTool(graph).build() is None
try:
_get_document(page_content="x")
raise SystemExit("expected RuntimeError from _get_document")
except RuntimeError as exc:
assert "langchain-core" in str(exc)
print("DEGRADATION_OK")
"""
def test_importable_and_functional_without_langchain():
result = subprocess.run(
[sys.executable, "-c", _SCRIPT],
cwd=REPO_ROOT,
capture_output=True,
text=True,
timeout=60,
)
assert result.returncode == 0, (
f"subprocess failed:\nSTDOUT:\n{result.stdout}\nSTDERR:\n{result.stderr}"
)
assert "DEGRADATION_OK" in result.stdout
@pytest.mark.skipif(LANGCHAIN_AVAILABLE, reason="langchain-core is installed")
def test_tools_build_returns_none_without_langchain():
graph = SimpleNamespace()
assert SemanticaKGTool(graph).build() is None
assert SemanticaDecisionTool(graph).build() is None
@@ -0,0 +1,231 @@
"""
Tests for integrations/langchain.
Adapter behavior is always exercised (hit parsing, seed/fallback, tool JSON).
LangChain-present paths use pytest.importorskip; degradation without
langchain-core is covered in test_degradation.py via a subprocess so it still
runs when langchain-core is installed in this env.
"""
from __future__ import annotations
import json
from types import SimpleNamespace
from unittest.mock import MagicMock
import pytest
from integrations.langchain import (
LANGCHAIN_AVAILABLE,
SemanticaDecisionTool,
SemanticaKGTool,
SemanticaRetriever,
SemanticaVectorStore,
)
from integrations.langchain.retriever import _hit_content, _hit_id, _hit_type
from integrations.langchain.tools import QueryDecisionsInput, QueryGraphInput
# HybridSearch.search() returns {id, score, distance, metadata} — content lives
# inside metadata, and id is a vector id, not a graph node id.
_HYBRID_HIT = {
"id": "vec_0",
"score": 0.91,
"distance": 0.09,
"metadata": {
"node_id": "alice",
"content": "Alice is a developer",
"node_type": "person",
"source": "graph",
},
}
def test_exports_exist():
assert callable(SemanticaRetriever)
assert callable(SemanticaVectorStore)
assert callable(SemanticaKGTool)
assert callable(SemanticaDecisionTool)
def test_version():
from integrations.langchain import __version__
assert __version__ == "0.1.0"
# ---------------------------------------------------------------------------
# Hit parsing (the Qodo high-severity finding)
# ---------------------------------------------------------------------------
def test_hit_id_prefers_metadata_node_id_over_vector_id():
assert _hit_id(_HYBRID_HIT) == "alice"
assert _hit_id({"node_id": "n1"}) == "n1"
assert _hit_id({"id": "n2"}) == "n2"
def test_hit_id_unwraps_context_graph_query_shape():
hit = {
"node": {
"id": "alice",
"type": "person",
"properties": {"content": "Alice"},
},
"score": 1.0,
"content": "Alice is a developer",
}
assert _hit_id(hit) == "alice"
assert _hit_content(hit) == "Alice is a developer"
assert _hit_type(hit) == "person"
def test_hit_content_and_type_read_nested_metadata():
assert _hit_content(_HYBRID_HIT) == "Alice is a developer"
assert _hit_type(_HYBRID_HIT) == "person"
assert _hit_content({"id": "x"}) == ""
# ---------------------------------------------------------------------------
# Retriever
# ---------------------------------------------------------------------------
def test_empty_seed_returns_empty():
graph = MagicMock()
graph.query.return_value = []
retriever = SemanticaRetriever(graph=graph, top_k=5)
assert retriever._seed_results("query") == []
assert retriever.hops == 2
def test_seed_uses_hybrid_when_provided():
graph = MagicMock()
hybrid = MagicMock()
hybrid.search.return_value = [_HYBRID_HIT]
retriever = SemanticaRetriever(graph=graph, hybrid=hybrid)
results = retriever._seed_results("query")
assert len(results) == 1
hybrid.search.assert_called_once_with("query", k=10)
def test_graph_fallback_when_hybrid_fails():
graph = MagicMock()
graph.query.return_value = [{"node_id": "n1", "content": "c1"}]
hybrid = MagicMock()
hybrid.search.side_effect = RuntimeError("down")
retriever = SemanticaRetriever(graph=graph, hybrid=hybrid)
results = retriever._seed_results("query")
assert len(results) == 1
graph.query.assert_called_once()
def test_retriever_reads_hybrid_metadata_and_expands_by_node_id():
pytest.importorskip("langchain_core")
graph = MagicMock()
graph.get_neighbors.return_value = [
{
"id": "bob",
"type": "person",
"content": "Bob reports to Alice",
"weight": 0.8,
}
]
hybrid = MagicMock()
hybrid.search.return_value = [_HYBRID_HIT]
retriever = SemanticaRetriever(graph=graph, hybrid=hybrid)
docs = retriever._get_relevant_documents("Alice")
assert docs[0].page_content == "Alice is a developer"
assert docs[0].metadata["node_id"] == "alice"
assert docs[0].metadata["source"] == "graph"
graph.get_neighbors.assert_called_once_with("alice", hops=2)
assert [d.metadata["node_id"] for d in docs] == ["alice", "bob"]
# ---------------------------------------------------------------------------
# VectorStore
# ---------------------------------------------------------------------------
def test_add_texts_delegates_to_vector_store():
vs = MagicMock()
vs.add_documents.return_value = ["id1"]
store = SemanticaVectorStore(hybrid=MagicMock(), vector_store=vs)
assert store.add_texts(["hello"]) == ["id1"]
vs.add_documents.assert_called_once()
def test_add_texts_raises_without_vector_store():
store = SemanticaVectorStore(hybrid=SimpleNamespace(vector_store=None))
with pytest.raises(ValueError):
store.add_texts(["hello"])
def test_from_texts_requires_hybrid_kwarg():
with pytest.raises(ValueError):
SemanticaVectorStore.from_texts(["hello"], embedding=None)
def test_vectorstore_reads_hybrid_metadata():
pytest.importorskip("langchain_core")
hybrid = MagicMock()
hybrid.search.return_value = [_HYBRID_HIT]
store = SemanticaVectorStore(hybrid=hybrid)
docs = store.similarity_search("Alice", k=1)
assert docs[0].page_content == "Alice is a developer"
assert docs[0].metadata["node_id"] == "alice"
assert docs[0].metadata["source"] == "graph"
pairs = store.similarity_search_with_score("Alice", k=1)
assert pairs[0][0].page_content == "Alice is a developer"
assert pairs[0][1] == pytest.approx(0.91)
# ---------------------------------------------------------------------------
# Tools — JSON payload + BaseTool contract
# ---------------------------------------------------------------------------
def test_kg_tool_returns_full_valid_json():
graph = MagicMock()
graph.query.return_value = [{"content": "x" * 5000, "id": i} for i in range(3)]
raw = SemanticaKGTool(graph)._run("q", limit=3)
parsed = json.loads(raw)
assert len(parsed) == 3
assert len(parsed[0]["content"]) == 5000
graph.query.assert_called_once_with("q", limit=3)
def test_tool_errors_are_json():
graph = MagicMock()
graph.query.side_effect = RuntimeError("boom")
assert json.loads(SemanticaKGTool(graph)._run("q")) == {"error": "boom"}
assert json.loads(SemanticaDecisionTool(graph)._run("q")) == {"error": "boom"}
def test_decision_tool_empty_category_uses_insights():
graph = MagicMock()
graph.get_decision_insights.return_value = {"n": 0}
assert json.loads(SemanticaDecisionTool(graph)._run("")) == {"n": 0}
def test_tools_are_base_tools_with_args_schema():
pytest.importorskip("langchain_core")
from langchain_core.tools import BaseTool
graph = MagicMock()
graph.query.return_value = [{"hit": True}]
kg = SemanticaKGTool(graph)
dec = SemanticaDecisionTool(graph)
assert isinstance(kg, BaseTool)
assert isinstance(dec, BaseTool)
assert kg.args_schema is QueryGraphInput
assert dec.args_schema is QueryDecisionsInput
assert kg.build() is kg
parsed = json.loads(kg.invoke({"query": "Alice", "limit": 5}))
assert parsed == [{"hit": True}]
@pytest.mark.skipif(not LANGCHAIN_AVAILABLE, reason="langchain-core not installed")
def test_kg_tool_invoke_with_context_graph():
pytest.importorskip("langchain_core")
try:
from semantica.context import ContextGraph
except ImportError:
pytest.skip("ContextGraph import requires optional core deps")
graph = ContextGraph()
graph.add_node(node_id="alice", node_type="person", content="Alice is a developer")
result = SemanticaKGTool(graph).invoke({"query": "Alice", "limit": 5})
assert "Alice" in result
json.loads(result)