langchain-coordinode 0.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- langchain_coordinode-0.4.0/.gitignore +23 -0
- langchain_coordinode-0.4.0/PKG-INFO +175 -0
- langchain_coordinode-0.4.0/README.md +153 -0
- langchain_coordinode-0.4.0/langchain_coordinode/__init__.py +6 -0
- langchain_coordinode-0.4.0/langchain_coordinode/_version.py +24 -0
- langchain_coordinode-0.4.0/langchain_coordinode/graph.py +185 -0
- langchain_coordinode-0.4.0/pyproject.toml +43 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Generated proto stubs — run `make proto` to regenerate
|
|
2
|
+
coordinode/_proto/
|
|
3
|
+
|
|
4
|
+
# Build artifacts
|
|
5
|
+
dist/
|
|
6
|
+
*.egg-info/
|
|
7
|
+
__pycache__/
|
|
8
|
+
*.pyc
|
|
9
|
+
*.pyo
|
|
10
|
+
.pytest_cache/
|
|
11
|
+
.ruff_cache/
|
|
12
|
+
.mypy_cache/
|
|
13
|
+
|
|
14
|
+
# Virtual envs
|
|
15
|
+
.venv/
|
|
16
|
+
venv/
|
|
17
|
+
env/
|
|
18
|
+
|
|
19
|
+
# Version files generated by hatch-vcs
|
|
20
|
+
coordinode/_version.py
|
|
21
|
+
langchain-coordinode/langchain_coordinode/_version.py
|
|
22
|
+
llama-index-coordinode/llama_index/graph_stores/coordinode/_version.py
|
|
23
|
+
GAPS.md
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: langchain-coordinode
|
|
3
|
+
Version: 0.4.0
|
|
4
|
+
Summary: LangChain integration for CoordiNode — GraphStore backed by graph + vector + full-text
|
|
5
|
+
Project-URL: Homepage, https://github.com/structured-world/coordinode
|
|
6
|
+
Project-URL: Repository, https://github.com/structured-world/coordinode-python
|
|
7
|
+
Author-email: "structured.world" <dev@structured.world>
|
|
8
|
+
License: Apache-2.0
|
|
9
|
+
Keywords: coordinode,graph,graphrag,langchain,rag
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
18
|
+
Requires-Python: >=3.11
|
|
19
|
+
Requires-Dist: coordinode>=0.3.0a1
|
|
20
|
+
Requires-Dist: langchain-community>=0.2.0
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
|
|
23
|
+
# langchain-coordinode
|
|
24
|
+
|
|
25
|
+
[](https://pypi.org/project/langchain-coordinode/)
|
|
26
|
+
[](https://pypi.org/project/langchain-coordinode/)
|
|
27
|
+
[](https://github.com/structured-world/coordinode-python/blob/main/LICENSE)
|
|
28
|
+
[](https://github.com/structured-world/coordinode-python/actions/workflows/ci.yml)
|
|
29
|
+
|
|
30
|
+
[LangChain](https://python.langchain.com/) integration for [CoordiNode](https://github.com/structured-world/coordinode) — `GraphStore` implementation and `GraphCypherQAChain` support for GraphRAG pipelines.
|
|
31
|
+
|
|
32
|
+
## Installation
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
pip install langchain-coordinode
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
uv add langchain-coordinode
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Requirements
|
|
43
|
+
|
|
44
|
+
- Python 3.11+
|
|
45
|
+
- Running CoordiNode instance
|
|
46
|
+
|
|
47
|
+
## Quick Start
|
|
48
|
+
|
|
49
|
+
### GraphCypherQAChain — Question Answering over a Knowledge Graph
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
from langchain_coordinode import CoordinodeGraph
|
|
53
|
+
from langchain.chains import GraphCypherQAChain
|
|
54
|
+
from langchain_openai import ChatOpenAI
|
|
55
|
+
|
|
56
|
+
# Connect to CoordiNode
|
|
57
|
+
graph = CoordinodeGraph("localhost:7080")
|
|
58
|
+
|
|
59
|
+
# Build a QA chain that generates and executes Cypher queries
|
|
60
|
+
chain = GraphCypherQAChain.from_llm(
|
|
61
|
+
ChatOpenAI(model="gpt-4o-mini"),
|
|
62
|
+
graph=graph,
|
|
63
|
+
verbose=True,
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
result = chain.invoke({"query": "What concepts are related to attention mechanisms?"})
|
|
67
|
+
print(result["result"])
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Schema Inspection
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
from langchain_coordinode import CoordinodeGraph
|
|
74
|
+
|
|
75
|
+
graph = CoordinodeGraph("localhost:7080")
|
|
76
|
+
|
|
77
|
+
# Refresh schema from database
|
|
78
|
+
graph.refresh_schema()
|
|
79
|
+
|
|
80
|
+
# Schema string used by the LLM to generate Cypher
|
|
81
|
+
print(graph.schema)
|
|
82
|
+
# Node properties: Person (name: String, age: Integer), Concept (name: String) ...
|
|
83
|
+
# Relationships: (Person)-[:KNOWS]->(Person), (Document)-[:ABOUT]->(Concept) ...
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Direct Cypher Queries
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
from langchain_coordinode import CoordinodeGraph
|
|
90
|
+
|
|
91
|
+
graph = CoordinodeGraph("localhost:7080")
|
|
92
|
+
|
|
93
|
+
# Returns List[Dict[str, Any]]
|
|
94
|
+
result = graph.query(
|
|
95
|
+
"MATCH (n:Person)-[:KNOWS]->(m) WHERE n.name = $name RETURN m.name AS colleague",
|
|
96
|
+
params={"name": "Alice"},
|
|
97
|
+
)
|
|
98
|
+
for row in result:
|
|
99
|
+
print(row["colleague"])
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### LLMGraphTransformer — Extract Knowledge from Text
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
from langchain_community.graphs.graph_document import GraphDocument
|
|
106
|
+
from langchain_openai import ChatOpenAI
|
|
107
|
+
from langchain_experimental.graph_transformers import LLMGraphTransformer
|
|
108
|
+
from langchain_coordinode import CoordinodeGraph
|
|
109
|
+
from langchain_core.documents import Document
|
|
110
|
+
|
|
111
|
+
graph = CoordinodeGraph("localhost:7080")
|
|
112
|
+
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
|
|
113
|
+
|
|
114
|
+
transformer = LLMGraphTransformer(llm=llm)
|
|
115
|
+
|
|
116
|
+
docs = [Document(page_content="Alice knows Bob. Bob works at Acme Corp.")]
|
|
117
|
+
graph_docs = transformer.convert_to_graph_documents(docs)
|
|
118
|
+
|
|
119
|
+
# Store extracted entities and relationships
|
|
120
|
+
graph.add_graph_documents(graph_docs)
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Connection Options
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
# host:port string
|
|
127
|
+
graph = CoordinodeGraph("localhost:7080")
|
|
128
|
+
|
|
129
|
+
# TLS
|
|
130
|
+
graph = CoordinodeGraph("db.example.com:7443", tls=True)
|
|
131
|
+
|
|
132
|
+
# Custom timeout
|
|
133
|
+
graph = CoordinodeGraph("localhost:7080", timeout=60.0)
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## API Reference
|
|
137
|
+
|
|
138
|
+
### `CoordinodeGraph`
|
|
139
|
+
|
|
140
|
+
| Method | Description |
|
|
141
|
+
|--------|-------------|
|
|
142
|
+
| `query(query, params)` | Execute Cypher, returns `List[Dict[str, Any]]` |
|
|
143
|
+
| `refresh_schema()` | Reload node/relationship schema from database |
|
|
144
|
+
| `add_graph_documents(docs)` | Batch MERGE nodes + relationships from `GraphDocument` list |
|
|
145
|
+
| `schema` | Schema string for LLM context |
|
|
146
|
+
|
|
147
|
+
## Related Packages
|
|
148
|
+
|
|
149
|
+
| Package | Description |
|
|
150
|
+
|---------|-------------|
|
|
151
|
+
| [`coordinode`](https://pypi.org/project/coordinode/) | Core gRPC client |
|
|
152
|
+
| [`llama-index-graph-stores-coordinode`](https://pypi.org/project/llama-index-graph-stores-coordinode/) | LlamaIndex `PropertyGraphStore` |
|
|
153
|
+
|
|
154
|
+
## Links
|
|
155
|
+
|
|
156
|
+
- [Source](https://github.com/structured-world/coordinode-python)
|
|
157
|
+
- [CoordiNode server](https://github.com/structured-world/coordinode)
|
|
158
|
+
- [LangChain docs](https://python.langchain.com/docs/integrations/graphs/)
|
|
159
|
+
- [Issues](https://github.com/structured-world/coordinode-python/issues)
|
|
160
|
+
|
|
161
|
+
## Support
|
|
162
|
+
|
|
163
|
+
<div align="center">
|
|
164
|
+
|
|
165
|
+
[](https://github.com/sponsors/structured-world)
|
|
166
|
+
|
|
167
|
+
**USDT (TRC-20):** `TFDsezHa1cBkoeZT5q2T49Wp66K8t2DmdA`
|
|
168
|
+
|
|
169
|
+
[GitHub Sponsors](https://github.com/sponsors/structured-world) · [Open Collective](https://opencollective.com/structured-world)
|
|
170
|
+
|
|
171
|
+
</div>
|
|
172
|
+
|
|
173
|
+
## License
|
|
174
|
+
|
|
175
|
+
Apache-2.0
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# langchain-coordinode
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/langchain-coordinode/)
|
|
4
|
+
[](https://pypi.org/project/langchain-coordinode/)
|
|
5
|
+
[](https://github.com/structured-world/coordinode-python/blob/main/LICENSE)
|
|
6
|
+
[](https://github.com/structured-world/coordinode-python/actions/workflows/ci.yml)
|
|
7
|
+
|
|
8
|
+
[LangChain](https://python.langchain.com/) integration for [CoordiNode](https://github.com/structured-world/coordinode) — `GraphStore` implementation and `GraphCypherQAChain` support for GraphRAG pipelines.
|
|
9
|
+
|
|
10
|
+
## Installation
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
pip install langchain-coordinode
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
uv add langchain-coordinode
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Requirements
|
|
21
|
+
|
|
22
|
+
- Python 3.11+
|
|
23
|
+
- Running CoordiNode instance
|
|
24
|
+
|
|
25
|
+
## Quick Start
|
|
26
|
+
|
|
27
|
+
### GraphCypherQAChain — Question Answering over a Knowledge Graph
|
|
28
|
+
|
|
29
|
+
```python
|
|
30
|
+
from langchain_coordinode import CoordinodeGraph
|
|
31
|
+
from langchain.chains import GraphCypherQAChain
|
|
32
|
+
from langchain_openai import ChatOpenAI
|
|
33
|
+
|
|
34
|
+
# Connect to CoordiNode
|
|
35
|
+
graph = CoordinodeGraph("localhost:7080")
|
|
36
|
+
|
|
37
|
+
# Build a QA chain that generates and executes Cypher queries
|
|
38
|
+
chain = GraphCypherQAChain.from_llm(
|
|
39
|
+
ChatOpenAI(model="gpt-4o-mini"),
|
|
40
|
+
graph=graph,
|
|
41
|
+
verbose=True,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
result = chain.invoke({"query": "What concepts are related to attention mechanisms?"})
|
|
45
|
+
print(result["result"])
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Schema Inspection
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
from langchain_coordinode import CoordinodeGraph
|
|
52
|
+
|
|
53
|
+
graph = CoordinodeGraph("localhost:7080")
|
|
54
|
+
|
|
55
|
+
# Refresh schema from database
|
|
56
|
+
graph.refresh_schema()
|
|
57
|
+
|
|
58
|
+
# Schema string used by the LLM to generate Cypher
|
|
59
|
+
print(graph.schema)
|
|
60
|
+
# Node properties: Person (name: String, age: Integer), Concept (name: String) ...
|
|
61
|
+
# Relationships: (Person)-[:KNOWS]->(Person), (Document)-[:ABOUT]->(Concept) ...
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Direct Cypher Queries
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
from langchain_coordinode import CoordinodeGraph
|
|
68
|
+
|
|
69
|
+
graph = CoordinodeGraph("localhost:7080")
|
|
70
|
+
|
|
71
|
+
# Returns List[Dict[str, Any]]
|
|
72
|
+
result = graph.query(
|
|
73
|
+
"MATCH (n:Person)-[:KNOWS]->(m) WHERE n.name = $name RETURN m.name AS colleague",
|
|
74
|
+
params={"name": "Alice"},
|
|
75
|
+
)
|
|
76
|
+
for row in result:
|
|
77
|
+
print(row["colleague"])
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### LLMGraphTransformer — Extract Knowledge from Text
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
from langchain_community.graphs.graph_document import GraphDocument
|
|
84
|
+
from langchain_openai import ChatOpenAI
|
|
85
|
+
from langchain_experimental.graph_transformers import LLMGraphTransformer
|
|
86
|
+
from langchain_coordinode import CoordinodeGraph
|
|
87
|
+
from langchain_core.documents import Document
|
|
88
|
+
|
|
89
|
+
graph = CoordinodeGraph("localhost:7080")
|
|
90
|
+
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
|
|
91
|
+
|
|
92
|
+
transformer = LLMGraphTransformer(llm=llm)
|
|
93
|
+
|
|
94
|
+
docs = [Document(page_content="Alice knows Bob. Bob works at Acme Corp.")]
|
|
95
|
+
graph_docs = transformer.convert_to_graph_documents(docs)
|
|
96
|
+
|
|
97
|
+
# Store extracted entities and relationships
|
|
98
|
+
graph.add_graph_documents(graph_docs)
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Connection Options
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
# host:port string
|
|
105
|
+
graph = CoordinodeGraph("localhost:7080")
|
|
106
|
+
|
|
107
|
+
# TLS
|
|
108
|
+
graph = CoordinodeGraph("db.example.com:7443", tls=True)
|
|
109
|
+
|
|
110
|
+
# Custom timeout
|
|
111
|
+
graph = CoordinodeGraph("localhost:7080", timeout=60.0)
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## API Reference
|
|
115
|
+
|
|
116
|
+
### `CoordinodeGraph`
|
|
117
|
+
|
|
118
|
+
| Method | Description |
|
|
119
|
+
|--------|-------------|
|
|
120
|
+
| `query(query, params)` | Execute Cypher, returns `List[Dict[str, Any]]` |
|
|
121
|
+
| `refresh_schema()` | Reload node/relationship schema from database |
|
|
122
|
+
| `add_graph_documents(docs)` | Batch MERGE nodes + relationships from `GraphDocument` list |
|
|
123
|
+
| `schema` | Schema string for LLM context |
|
|
124
|
+
|
|
125
|
+
## Related Packages
|
|
126
|
+
|
|
127
|
+
| Package | Description |
|
|
128
|
+
|---------|-------------|
|
|
129
|
+
| [`coordinode`](https://pypi.org/project/coordinode/) | Core gRPC client |
|
|
130
|
+
| [`llama-index-graph-stores-coordinode`](https://pypi.org/project/llama-index-graph-stores-coordinode/) | LlamaIndex `PropertyGraphStore` |
|
|
131
|
+
|
|
132
|
+
## Links
|
|
133
|
+
|
|
134
|
+
- [Source](https://github.com/structured-world/coordinode-python)
|
|
135
|
+
- [CoordiNode server](https://github.com/structured-world/coordinode)
|
|
136
|
+
- [LangChain docs](https://python.langchain.com/docs/integrations/graphs/)
|
|
137
|
+
- [Issues](https://github.com/structured-world/coordinode-python/issues)
|
|
138
|
+
|
|
139
|
+
## Support
|
|
140
|
+
|
|
141
|
+
<div align="center">
|
|
142
|
+
|
|
143
|
+
[](https://github.com/sponsors/structured-world)
|
|
144
|
+
|
|
145
|
+
**USDT (TRC-20):** `TFDsezHa1cBkoeZT5q2T49Wp66K8t2DmdA`
|
|
146
|
+
|
|
147
|
+
[GitHub Sponsors](https://github.com/sponsors/structured-world) · [Open Collective](https://opencollective.com/structured-world)
|
|
148
|
+
|
|
149
|
+
</div>
|
|
150
|
+
|
|
151
|
+
## License
|
|
152
|
+
|
|
153
|
+
Apache-2.0
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# file generated by vcs-versioning
|
|
2
|
+
# don't change, don't track in version control
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"__version__",
|
|
7
|
+
"__version_tuple__",
|
|
8
|
+
"version",
|
|
9
|
+
"version_tuple",
|
|
10
|
+
"__commit_id__",
|
|
11
|
+
"commit_id",
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
version: str
|
|
15
|
+
__version__: str
|
|
16
|
+
__version_tuple__: tuple[int | str, ...]
|
|
17
|
+
version_tuple: tuple[int | str, ...]
|
|
18
|
+
commit_id: str | None
|
|
19
|
+
__commit_id__: str | None
|
|
20
|
+
|
|
21
|
+
__version__ = version = '0.4.0'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 4, 0)
|
|
23
|
+
|
|
24
|
+
__commit_id__ = commit_id = None
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
"""CoordinodeGraph — LangChain GraphStore backed by CoordiNode."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from langchain_community.graphs.graph_store import GraphStore
|
|
9
|
+
|
|
10
|
+
from coordinode import CoordinodeClient
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class CoordinodeGraph(GraphStore):
|
|
14
|
+
"""LangChain `GraphStore` backed by CoordiNode.
|
|
15
|
+
|
|
16
|
+
Supports ``GraphCypherQAChain`` and any LangChain component that works
|
|
17
|
+
with a ``GraphStore``.
|
|
18
|
+
|
|
19
|
+
Example::
|
|
20
|
+
|
|
21
|
+
from langchain_coordinode import CoordinodeGraph
|
|
22
|
+
from langchain.chains import GraphCypherQAChain
|
|
23
|
+
from langchain_openai import ChatOpenAI
|
|
24
|
+
|
|
25
|
+
graph = CoordinodeGraph("localhost:7080")
|
|
26
|
+
chain = GraphCypherQAChain.from_llm(
|
|
27
|
+
ChatOpenAI(model="gpt-4o-mini"),
|
|
28
|
+
graph=graph,
|
|
29
|
+
verbose=True,
|
|
30
|
+
)
|
|
31
|
+
result = chain.invoke("What concepts are related to machine learning?")
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
addr: CoordiNode gRPC address, e.g. ``"localhost:7080"``.
|
|
35
|
+
database: Database name (reserved for future multi-db support).
|
|
36
|
+
timeout: Per-request gRPC deadline in seconds.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
def __init__(
|
|
40
|
+
self,
|
|
41
|
+
addr: str = "localhost:7080",
|
|
42
|
+
*,
|
|
43
|
+
database: str | None = None,
|
|
44
|
+
timeout: float = 30.0,
|
|
45
|
+
) -> None:
|
|
46
|
+
self._client = CoordinodeClient(addr, timeout=timeout)
|
|
47
|
+
self._schema: str | None = None
|
|
48
|
+
self._structured_schema: dict[str, Any] | None = None
|
|
49
|
+
|
|
50
|
+
# ── GraphStore interface ──────────────────────────────────────────────
|
|
51
|
+
|
|
52
|
+
@property
|
|
53
|
+
def schema(self) -> str:
|
|
54
|
+
"""Return cached schema string (refreshed by `refresh_schema`)."""
|
|
55
|
+
if self._schema is None:
|
|
56
|
+
self.refresh_schema()
|
|
57
|
+
return self._schema or ""
|
|
58
|
+
|
|
59
|
+
@property
|
|
60
|
+
def structured_schema(self) -> dict[str, Any]:
|
|
61
|
+
"""Return structured schema dict (refreshed by `refresh_schema`)."""
|
|
62
|
+
if self._structured_schema is None:
|
|
63
|
+
self.refresh_schema()
|
|
64
|
+
return self._structured_schema or {}
|
|
65
|
+
|
|
66
|
+
def refresh_schema(self) -> None:
|
|
67
|
+
"""Fetch current schema from CoordiNode."""
|
|
68
|
+
text = self._client.get_schema_text()
|
|
69
|
+
self._schema = text
|
|
70
|
+
structured = _parse_schema(text)
|
|
71
|
+
# Augment with relationship triples (start_label, type, end_label) via
|
|
72
|
+
# Cypher — get_schema_text() only lists edge types without direction.
|
|
73
|
+
try:
|
|
74
|
+
rows = self._client.cypher(
|
|
75
|
+
"MATCH (a)-[r]->(b) RETURN DISTINCT labels(a)[0] AS src, type(r) AS rel, labels(b)[0] AS dst"
|
|
76
|
+
)
|
|
77
|
+
structured["relationships"] = [
|
|
78
|
+
{"start": row["src"], "type": row["rel"], "end": row["dst"]}
|
|
79
|
+
for row in rows
|
|
80
|
+
if row.get("src") and row.get("rel") and row.get("dst")
|
|
81
|
+
]
|
|
82
|
+
except Exception: # noqa: BLE001
|
|
83
|
+
pass # Graph may have no relationships yet; structured["relationships"] stays []
|
|
84
|
+
self._structured_schema = structured
|
|
85
|
+
|
|
86
|
+
def query(
|
|
87
|
+
self,
|
|
88
|
+
query: str,
|
|
89
|
+
params: dict[str, Any] | None = None,
|
|
90
|
+
) -> list[dict[str, Any]]:
|
|
91
|
+
"""Run a Cypher query and return rows as dicts.
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
query: OpenCypher query string.
|
|
95
|
+
params: Optional query parameters.
|
|
96
|
+
|
|
97
|
+
Returns:
|
|
98
|
+
List of row dicts (column name → value).
|
|
99
|
+
"""
|
|
100
|
+
# cypher() returns List[Dict[str, Any]] directly — column name → value.
|
|
101
|
+
return self._client.cypher(query, params=params or {})
|
|
102
|
+
|
|
103
|
+
# ── Lifecycle ─────────────────────────────────────────────────────────
|
|
104
|
+
|
|
105
|
+
def close(self) -> None:
|
|
106
|
+
"""Close the underlying gRPC connection."""
|
|
107
|
+
self._client.close()
|
|
108
|
+
|
|
109
|
+
def __enter__(self) -> CoordinodeGraph:
|
|
110
|
+
return self
|
|
111
|
+
|
|
112
|
+
def __exit__(self, *args: Any) -> None:
|
|
113
|
+
self.close()
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
# ── Schema parser ─────────────────────────────────────────────────────────
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _parse_schema(schema_text: str) -> dict[str, Any]:
|
|
120
|
+
"""Convert CoordiNode schema text into LangChain's structured format.
|
|
121
|
+
|
|
122
|
+
LangChain's ``GraphCypherQAChain`` expects::
|
|
123
|
+
|
|
124
|
+
{
|
|
125
|
+
"node_props": {"Label": [{"property": "name", "type": "STRING"}, ...]},
|
|
126
|
+
"rel_props": {"TYPE": [{"property": "weight", "type": "FLOAT"}, ...]},
|
|
127
|
+
"relationships": [{"start": "A", "type": "REL", "end": "B"}, ...],
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
CoordiNode's schema text format (from ``get_schema_text()``)::
|
|
131
|
+
|
|
132
|
+
Node labels:
|
|
133
|
+
- Person (properties: name: STRING, age: INT64)
|
|
134
|
+
- Company
|
|
135
|
+
|
|
136
|
+
Edge types:
|
|
137
|
+
- KNOWS (properties: since: INT64)
|
|
138
|
+
- WORKS_FOR
|
|
139
|
+
|
|
140
|
+
We parse inline ``(properties: ...)`` lists on each bullet line.
|
|
141
|
+
For reliable structured access use the gRPC ``SchemaService`` directly.
|
|
142
|
+
"""
|
|
143
|
+
node_props: dict[str, list[dict[str, str]]] = {}
|
|
144
|
+
rel_props: dict[str, list[dict[str, str]]] = {}
|
|
145
|
+
relationships: list[dict[str, str]] = []
|
|
146
|
+
|
|
147
|
+
in_nodes = False
|
|
148
|
+
in_rels = False
|
|
149
|
+
|
|
150
|
+
for line in schema_text.splitlines():
|
|
151
|
+
stripped = line.strip()
|
|
152
|
+
if not stripped:
|
|
153
|
+
continue
|
|
154
|
+
|
|
155
|
+
if stripped.lower().startswith("node labels"):
|
|
156
|
+
in_nodes, in_rels = True, False
|
|
157
|
+
continue
|
|
158
|
+
# Accept both "Edge types:" (current format) and "Relationship types:" (legacy)
|
|
159
|
+
if stripped.lower().startswith("edge types") or stripped.lower().startswith("relationship types"):
|
|
160
|
+
in_nodes, in_rels = False, True
|
|
161
|
+
continue
|
|
162
|
+
|
|
163
|
+
if (in_nodes or in_rels) and (stripped.startswith("-") or stripped.startswith("*")):
|
|
164
|
+
# Extract name (part before optional "(properties: ...)")
|
|
165
|
+
name = stripped.lstrip("-* ").split("(")[0].strip()
|
|
166
|
+
if not name:
|
|
167
|
+
continue
|
|
168
|
+
# Parse inline properties: "- Label (properties: prop1: TYPE, prop2: TYPE)"
|
|
169
|
+
props: list[dict[str, str]] = []
|
|
170
|
+
m = re.search(r"\(properties:\s*([^)]+)\)", stripped)
|
|
171
|
+
if m:
|
|
172
|
+
for prop_str in m.group(1).split(","):
|
|
173
|
+
kv = prop_str.strip().split(":", 1)
|
|
174
|
+
if len(kv) == 2:
|
|
175
|
+
props.append({"property": kv[0].strip(), "type": kv[1].strip()})
|
|
176
|
+
if in_nodes:
|
|
177
|
+
node_props[name] = props
|
|
178
|
+
else:
|
|
179
|
+
rel_props[name] = props
|
|
180
|
+
|
|
181
|
+
return {
|
|
182
|
+
"node_props": node_props,
|
|
183
|
+
"rel_props": rel_props,
|
|
184
|
+
"relationships": relationships,
|
|
185
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling", "hatch-vcs"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "langchain-coordinode"
|
|
7
|
+
dynamic = ["version"]
|
|
8
|
+
description = "LangChain integration for CoordiNode — GraphStore backed by graph + vector + full-text"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
|
+
license = { text = "Apache-2.0" }
|
|
12
|
+
authors = [{ name = "structured.world", email = "dev@structured.world" }]
|
|
13
|
+
keywords = ["langchain", "graph", "rag", "graphrag", "coordinode"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 3 - Alpha",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"License :: OSI Approved :: Apache Software License",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Programming Language :: Python :: 3.11",
|
|
20
|
+
"Programming Language :: Python :: 3.12",
|
|
21
|
+
"Programming Language :: Python :: 3.13",
|
|
22
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
23
|
+
]
|
|
24
|
+
dependencies = [
|
|
25
|
+
"coordinode>=0.3.0a1",
|
|
26
|
+
"langchain-community>=0.2.0",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
[project.urls]
|
|
30
|
+
Homepage = "https://github.com/structured-world/coordinode"
|
|
31
|
+
Repository = "https://github.com/structured-world/coordinode-python"
|
|
32
|
+
|
|
33
|
+
[tool.hatch.version]
|
|
34
|
+
source = "vcs"
|
|
35
|
+
tag-pattern = "v(?P<version>.*)"
|
|
36
|
+
fallback-version = "0.0.0"
|
|
37
|
+
raw-options = {root = ".."}
|
|
38
|
+
|
|
39
|
+
[tool.hatch.build.hooks.vcs]
|
|
40
|
+
version-file = "langchain_coordinode/_version.py"
|
|
41
|
+
|
|
42
|
+
[tool.hatch.build.targets.wheel]
|
|
43
|
+
packages = ["langchain_coordinode"]
|