rete-graph 0.2.2__tar.gz → 0.3.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.
- {rete_graph-0.2.2 → rete_graph-0.3.0}/Cargo.toml +2 -2
- {rete_graph-0.2.2 → rete_graph-0.3.0}/PKG-INFO +4 -2
- {rete_graph-0.2.2/clients/python → rete_graph-0.3.0}/README.md +3 -1
- {rete_graph-0.2.2 → rete_graph-0.3.0}/clients/python/Cargo.lock +2 -102
- {rete_graph-0.2.2 → rete_graph-0.3.0}/clients/python/Cargo.toml +4 -3
- {rete_graph-0.2.2 → rete_graph-0.3.0/clients/python}/README.md +3 -1
- rete_graph-0.3.0/clients/python/examples/agents/README.md +54 -0
- rete_graph-0.3.0/clients/python/examples/agents/langchain_agent.py +66 -0
- rete_graph-0.3.0/clients/python/examples/agents/pydantic_ai_agent.py +68 -0
- rete_graph-0.3.0/clients/python/examples/agents/rete_tools.py +161 -0
- {rete_graph-0.2.2 → rete_graph-0.3.0}/clients/python/src/lib.rs +14 -0
- {rete_graph-0.2.2 → rete_graph-0.3.0}/crates/rete-core/Cargo.toml +16 -6
- {rete_graph-0.2.2 → rete_graph-0.3.0}/crates/rete-core/README.md +4 -2
- {rete_graph-0.2.2 → rete_graph-0.3.0}/crates/rete-core/src/dict.rs +70 -19
- {rete_graph-0.2.2 → rete_graph-0.3.0}/crates/rete-core/src/dictionary.rs +9 -1
- {rete_graph-0.2.2 → rete_graph-0.3.0}/crates/rete-core/src/extbuild.rs +123 -73
- {rete_graph-0.2.2 → rete_graph-0.3.0}/crates/rete-core/src/file.rs +304 -47
- rete_graph-0.3.0/crates/rete-core/src/geo3.rs +227 -0
- {rete_graph-0.2.2 → rete_graph-0.3.0}/crates/rete-core/src/index.rs +202 -56
- {rete_graph-0.2.2 → rete_graph-0.3.0}/crates/rete-core/src/ingest.rs +15 -0
- {rete_graph-0.2.2 → rete_graph-0.3.0}/crates/rete-core/src/lib.rs +13 -2
- {rete_graph-0.2.2 → rete_graph-0.3.0}/crates/rete-core/src/sparql/aggregate.rs +209 -120
- {rete_graph-0.2.2 → rete_graph-0.3.0}/crates/rete-core/src/sparql/eval.rs +31 -21
- {rete_graph-0.2.2 → rete_graph-0.3.0}/crates/rete-core/src/sparql/expr.rs +73 -0
- {rete_graph-0.2.2 → rete_graph-0.3.0}/crates/rete-core/src/sparql/lower.rs +10 -0
- {rete_graph-0.2.2 → rete_graph-0.3.0}/crates/rete-core/src/sparql.rs +5 -0
- rete_graph-0.3.0/crates/rete-core/tests/properties.proptest-regressions +7 -0
- {rete_graph-0.2.2 → rete_graph-0.3.0}/crates/rete-core/tests/ranged.rs +91 -0
- {rete_graph-0.2.2 → rete_graph-0.3.0}/pyproject.toml +1 -1
- {rete_graph-0.2.2 → rete_graph-0.3.0}/python/rete_graph/__init__.py +3 -0
- {rete_graph-0.2.2 → rete_graph-0.3.0}/clients/python/.gitignore +0 -0
- {rete_graph-0.2.2 → rete_graph-0.3.0}/clients/python/examples/jupyterlite-build-tutorial.ipynb +0 -0
- {rete_graph-0.2.2 → rete_graph-0.3.0}/clients/python/examples/jupyterlite-demo.ipynb +0 -0
- {rete_graph-0.2.2 → rete_graph-0.3.0}/clients/python/examples/tutorial.ipynb +0 -0
- {rete_graph-0.2.2 → rete_graph-0.3.0}/clients/python/src/readers.rs +0 -0
- {rete_graph-0.2.2 → rete_graph-0.3.0}/clients/python/tests/conftest.py +0 -0
- {rete_graph-0.2.2 → rete_graph-0.3.0}/clients/python/tests/test_rete_graph.py +0 -0
- {rete_graph-0.2.2 → rete_graph-0.3.0}/crates/rete-core/LICENSE +0 -0
- {rete_graph-0.2.2 → rete_graph-0.3.0}/crates/rete-core/examples/coherence_bench.rs +0 -0
- {rete_graph-0.2.2 → rete_graph-0.3.0}/crates/rete-core/examples/parallel_bench.rs +0 -0
- {rete_graph-0.2.2 → rete_graph-0.3.0}/crates/rete-core/examples/qbench.rs +0 -0
- {rete_graph-0.2.2 → rete_graph-0.3.0}/crates/rete-core/src/bgp.rs +0 -0
- {rete_graph-0.2.2 → rete_graph-0.3.0}/crates/rete-core/src/block_cache.rs +0 -0
- {rete_graph-0.2.2 → rete_graph-0.3.0}/crates/rete-core/src/geo.rs +0 -0
- {rete_graph-0.2.2 → rete_graph-0.3.0}/crates/rete-core/src/header.rs +0 -0
- {rete_graph-0.2.2 → rete_graph-0.3.0}/crates/rete-core/src/meta.rs +0 -0
- {rete_graph-0.2.2 → rete_graph-0.3.0}/crates/rete-core/src/parallel.rs +0 -0
- {rete_graph-0.2.2 → rete_graph-0.3.0}/crates/rete-core/src/pyramid.rs +0 -0
- {rete_graph-0.2.2 → rete_graph-0.3.0}/crates/rete-core/src/reach.rs +0 -0
- {rete_graph-0.2.2 → rete_graph-0.3.0}/crates/rete-core/src/reader.rs +0 -0
- {rete_graph-0.2.2 → rete_graph-0.3.0}/crates/rete-core/src/reason.rs +0 -0
- {rete_graph-0.2.2 → rete_graph-0.3.0}/crates/rete-core/src/results.rs +0 -0
- {rete_graph-0.2.2 → rete_graph-0.3.0}/crates/rete-core/src/row.rs +0 -0
- {rete_graph-0.2.2 → rete_graph-0.3.0}/crates/rete-core/src/schema_pyramid.rs +0 -0
- {rete_graph-0.2.2 → rete_graph-0.3.0}/crates/rete-core/src/service.rs +0 -0
- {rete_graph-0.2.2 → rete_graph-0.3.0}/crates/rete-core/src/shacl.rs +0 -0
- {rete_graph-0.2.2 → rete_graph-0.3.0}/crates/rete-core/src/sparql/path.rs +0 -0
- {rete_graph-0.2.2 → rete_graph-0.3.0}/crates/rete-core/src/sparql/ql.rs +0 -0
- {rete_graph-0.2.2 → rete_graph-0.3.0}/crates/rete-core/src/terms.rs +0 -0
- {rete_graph-0.2.2 → rete_graph-0.3.0}/crates/rete-core/src/text_index.rs +0 -0
- {rete_graph-0.2.2 → rete_graph-0.3.0}/crates/rete-core/src/tiling.rs +0 -0
- {rete_graph-0.2.2 → rete_graph-0.3.0}/crates/rete-core/src/triples.rs +0 -0
- {rete_graph-0.2.2 → rete_graph-0.3.0}/crates/rete-core/src/varint.rs +0 -0
- {rete_graph-0.2.2 → rete_graph-0.3.0}/crates/rete-core/tests/community_eval.rs +0 -0
- {rete_graph-0.2.2 → rete_graph-0.3.0}/crates/rete-core/tests/compatibility_v1.rs +0 -0
- {rete_graph-0.2.2 → rete_graph-0.3.0}/crates/rete-core/tests/fixtures/v1/minimal.rete +0 -0
- {rete_graph-0.2.2 → rete_graph-0.3.0}/crates/rete-core/tests/fixtures/v1/source.nq +0 -0
- {rete_graph-0.2.2 → rete_graph-0.3.0}/crates/rete-core/tests/properties.rs +0 -0
- {rete_graph-0.2.2 → rete_graph-0.3.0}/crates/rete-core/tests/public_api.rs +0 -0
- {rete_graph-0.2.2 → rete_graph-0.3.0}/crates/rete-core/tests/robustness.rs +0 -0
- {rete_graph-0.2.2 → rete_graph-0.3.0}/crates/rete-core/tests/roundtrip.rs +0 -0
- {rete_graph-0.2.2 → rete_graph-0.3.0}/crates/rete-core/tests/service_federation.rs +0 -0
- {rete_graph-0.2.2 → rete_graph-0.3.0}/crates/rete-core/tests/shacl_core.rs +0 -0
- {rete_graph-0.2.2 → rete_graph-0.3.0}/crates/rete-core/tests/sparql_integration.rs +0 -0
- {rete_graph-0.2.2 → rete_graph-0.3.0}/crates/rete-core/tests/sparql_nondeterministic.rs +0 -0
|
@@ -14,10 +14,10 @@ members = [
|
|
|
14
14
|
# release gates (--locked checks, feature matrix, coverage floors) stay
|
|
15
15
|
# untouched by binding-only changes. Future clients (R, Go) follow the same
|
|
16
16
|
# pattern — clients/ consumes crates/, never the other way around.
|
|
17
|
-
exclude = ["fuzz", "clients/python", "clients/r/src/rust"]
|
|
17
|
+
exclude = ["fuzz", "clients/python", "clients/r/src/rust", "clients/java/ffi"]
|
|
18
18
|
|
|
19
19
|
[workspace.package]
|
|
20
|
-
version = "
|
|
20
|
+
version = "0.3.0"
|
|
21
21
|
edition = "2021"
|
|
22
22
|
license = "Apache-2.0"
|
|
23
23
|
repository = "https://github.com/caviri/rete"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: rete-graph
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Classifier: Development Status :: 4 - Beta
|
|
5
5
|
Classifier: Intended Audience :: Developers
|
|
6
6
|
Classifier: Intended Audience :: Science/Research
|
|
@@ -49,7 +49,9 @@ Wheels cover Linux (x86_64/aarch64), macOS, Windows — and, from 0.2.0,
|
|
|
49
49
|
graphs are queried over sync-XHR range requests, no server anywhere.
|
|
50
50
|
|
|
51
51
|
A runnable tour with captured outputs lives in
|
|
52
|
-
[`examples/tutorial.ipynb`](examples/tutorial.ipynb)
|
|
52
|
+
[`examples/tutorial.ipynb`](examples/tutorial.ipynb), and
|
|
53
|
+
[`examples/agents/`](examples/agents) turns a graph into agent tools for
|
|
54
|
+
LangChain and Pydantic AI.
|
|
53
55
|
|
|
54
56
|
## Use
|
|
55
57
|
|
|
@@ -21,7 +21,9 @@ Wheels cover Linux (x86_64/aarch64), macOS, Windows — and, from 0.2.0,
|
|
|
21
21
|
graphs are queried over sync-XHR range requests, no server anywhere.
|
|
22
22
|
|
|
23
23
|
A runnable tour with captured outputs lives in
|
|
24
|
-
[`examples/tutorial.ipynb`](examples/tutorial.ipynb)
|
|
24
|
+
[`examples/tutorial.ipynb`](examples/tutorial.ipynb), and
|
|
25
|
+
[`examples/agents/`](examples/agents) turns a graph into agent tools for
|
|
26
|
+
LangChain and Pydantic AI.
|
|
25
27
|
|
|
26
28
|
## Use
|
|
27
29
|
|
|
@@ -49,12 +49,6 @@ dependencies = [
|
|
|
49
49
|
"generic-array",
|
|
50
50
|
]
|
|
51
51
|
|
|
52
|
-
[[package]]
|
|
53
|
-
name = "bumpalo"
|
|
54
|
-
version = "3.20.3"
|
|
55
|
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
56
|
-
checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
|
|
57
|
-
|
|
58
52
|
[[package]]
|
|
59
53
|
name = "cc"
|
|
60
54
|
version = "1.2.67"
|
|
@@ -143,30 +137,6 @@ dependencies = [
|
|
|
143
137
|
"percent-encoding",
|
|
144
138
|
]
|
|
145
139
|
|
|
146
|
-
[[package]]
|
|
147
|
-
name = "futures-core"
|
|
148
|
-
version = "0.3.32"
|
|
149
|
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
150
|
-
checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d"
|
|
151
|
-
|
|
152
|
-
[[package]]
|
|
153
|
-
name = "futures-task"
|
|
154
|
-
version = "0.3.32"
|
|
155
|
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
156
|
-
checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393"
|
|
157
|
-
|
|
158
|
-
[[package]]
|
|
159
|
-
name = "futures-util"
|
|
160
|
-
version = "0.3.32"
|
|
161
|
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
162
|
-
checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6"
|
|
163
|
-
dependencies = [
|
|
164
|
-
"futures-core",
|
|
165
|
-
"futures-task",
|
|
166
|
-
"pin-project-lite",
|
|
167
|
-
"slab",
|
|
168
|
-
]
|
|
169
|
-
|
|
170
140
|
[[package]]
|
|
171
141
|
name = "generic-array"
|
|
172
142
|
version = "0.14.7"
|
|
@@ -184,10 +154,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
|
184
154
|
checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
|
|
185
155
|
dependencies = [
|
|
186
156
|
"cfg-if",
|
|
187
|
-
"js-sys",
|
|
188
157
|
"libc",
|
|
189
158
|
"wasi",
|
|
190
|
-
"wasm-bindgen",
|
|
191
159
|
]
|
|
192
160
|
|
|
193
161
|
[[package]]
|
|
@@ -335,17 +303,6 @@ dependencies = [
|
|
|
335
303
|
"libc",
|
|
336
304
|
]
|
|
337
305
|
|
|
338
|
-
[[package]]
|
|
339
|
-
name = "js-sys"
|
|
340
|
-
version = "0.3.103"
|
|
341
|
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
342
|
-
checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102"
|
|
343
|
-
dependencies = [
|
|
344
|
-
"cfg-if",
|
|
345
|
-
"futures-util",
|
|
346
|
-
"wasm-bindgen",
|
|
347
|
-
]
|
|
348
|
-
|
|
349
306
|
[[package]]
|
|
350
307
|
name = "libc"
|
|
351
308
|
version = "0.2.186"
|
|
@@ -481,12 +438,6 @@ version = "2.3.2"
|
|
|
481
438
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
482
439
|
checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
|
|
483
440
|
|
|
484
|
-
[[package]]
|
|
485
|
-
name = "pin-project-lite"
|
|
486
|
-
version = "0.2.17"
|
|
487
|
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
488
|
-
checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
|
|
489
|
-
|
|
490
441
|
[[package]]
|
|
491
442
|
name = "pkg-config"
|
|
492
443
|
version = "0.3.33"
|
|
@@ -651,7 +602,7 @@ checksum = "cab834c73d247e67f4fae452806d17d3c7501756d98c8808d7c9c7aa7d18f973"
|
|
|
651
602
|
|
|
652
603
|
[[package]]
|
|
653
604
|
name = "rete-core"
|
|
654
|
-
version = "
|
|
605
|
+
version = "0.3.0"
|
|
655
606
|
dependencies = [
|
|
656
607
|
"blake3",
|
|
657
608
|
"getrandom 0.2.17",
|
|
@@ -671,7 +622,7 @@ dependencies = [
|
|
|
671
622
|
|
|
672
623
|
[[package]]
|
|
673
624
|
name = "rete-python"
|
|
674
|
-
version = "0.
|
|
625
|
+
version = "0.3.0"
|
|
675
626
|
dependencies = [
|
|
676
627
|
"pyo3",
|
|
677
628
|
"rete-core",
|
|
@@ -813,12 +764,6 @@ version = "2.0.1"
|
|
|
813
764
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
814
765
|
checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
|
|
815
766
|
|
|
816
|
-
[[package]]
|
|
817
|
-
name = "slab"
|
|
818
|
-
version = "0.4.12"
|
|
819
|
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
820
|
-
checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
|
|
821
|
-
|
|
822
767
|
[[package]]
|
|
823
768
|
name = "smallvec"
|
|
824
769
|
version = "1.15.2"
|
|
@@ -994,51 +939,6 @@ version = "0.11.1+wasi-snapshot-preview1"
|
|
|
994
939
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
995
940
|
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
|
996
941
|
|
|
997
|
-
[[package]]
|
|
998
|
-
name = "wasm-bindgen"
|
|
999
|
-
version = "0.2.126"
|
|
1000
|
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1001
|
-
checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4"
|
|
1002
|
-
dependencies = [
|
|
1003
|
-
"cfg-if",
|
|
1004
|
-
"once_cell",
|
|
1005
|
-
"rustversion",
|
|
1006
|
-
"wasm-bindgen-macro",
|
|
1007
|
-
"wasm-bindgen-shared",
|
|
1008
|
-
]
|
|
1009
|
-
|
|
1010
|
-
[[package]]
|
|
1011
|
-
name = "wasm-bindgen-macro"
|
|
1012
|
-
version = "0.2.126"
|
|
1013
|
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1014
|
-
checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1"
|
|
1015
|
-
dependencies = [
|
|
1016
|
-
"quote",
|
|
1017
|
-
"wasm-bindgen-macro-support",
|
|
1018
|
-
]
|
|
1019
|
-
|
|
1020
|
-
[[package]]
|
|
1021
|
-
name = "wasm-bindgen-macro-support"
|
|
1022
|
-
version = "0.2.126"
|
|
1023
|
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1024
|
-
checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e"
|
|
1025
|
-
dependencies = [
|
|
1026
|
-
"bumpalo",
|
|
1027
|
-
"proc-macro2",
|
|
1028
|
-
"quote",
|
|
1029
|
-
"syn",
|
|
1030
|
-
"wasm-bindgen-shared",
|
|
1031
|
-
]
|
|
1032
|
-
|
|
1033
|
-
[[package]]
|
|
1034
|
-
name = "wasm-bindgen-shared"
|
|
1035
|
-
version = "0.2.126"
|
|
1036
|
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1037
|
-
checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24"
|
|
1038
|
-
dependencies = [
|
|
1039
|
-
"unicode-ident",
|
|
1040
|
-
]
|
|
1041
|
-
|
|
1042
942
|
[[package]]
|
|
1043
943
|
name = "webpki-roots"
|
|
1044
944
|
version = "0.26.11"
|
|
@@ -3,9 +3,10 @@
|
|
|
3
3
|
# maturin (pyproject.toml), and is published to PyPI, never to crates.io.
|
|
4
4
|
[package]
|
|
5
5
|
name = "rete-python"
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
|
|
6
|
+
# Kept in lockstep with pyproject.toml's [project] version, and with the engine's
|
|
7
|
+
# major.minor, by scripts/sync_versions.py (--check runs in CI). __version__ comes
|
|
8
|
+
# from here (CARGO_PKG_VERSION), the wheel metadata from pyproject.
|
|
9
|
+
version = "0.3.0"
|
|
9
10
|
edition = "2021"
|
|
10
11
|
license = "Apache-2.0"
|
|
11
12
|
repository = "https://github.com/caviri/rete"
|
|
@@ -21,7 +21,9 @@ Wheels cover Linux (x86_64/aarch64), macOS, Windows — and, from 0.2.0,
|
|
|
21
21
|
graphs are queried over sync-XHR range requests, no server anywhere.
|
|
22
22
|
|
|
23
23
|
A runnable tour with captured outputs lives in
|
|
24
|
-
[`examples/tutorial.ipynb`](examples/tutorial.ipynb)
|
|
24
|
+
[`examples/tutorial.ipynb`](examples/tutorial.ipynb), and
|
|
25
|
+
[`examples/agents/`](examples/agents) turns a graph into agent tools for
|
|
26
|
+
LangChain and Pydantic AI.
|
|
25
27
|
|
|
26
28
|
## Use
|
|
27
29
|
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Agent frameworks over a `.rete` graph
|
|
2
|
+
|
|
3
|
+
Runnable companions to the
|
|
4
|
+
[LangChain & Pydantic AI tutorial](https://caviri.github.io/rete/agent-frameworks.html).
|
|
5
|
+
Each script gives a model five tools over **one** `.rete` file — card, schema,
|
|
6
|
+
examples, entity lookup, SPARQL — and lets it answer questions by querying.
|
|
7
|
+
The file is read by byte range, local or remote, with no server in between.
|
|
8
|
+
|
|
9
|
+
| File | What it is |
|
|
10
|
+
| --- | --- |
|
|
11
|
+
| `rete_tools.py` | The tool layer. Framework-agnostic: five methods returning JSON, plus the system prompt that teaches the loop. |
|
|
12
|
+
| `pydantic_ai_agent.py` | [Pydantic AI](https://ai.pydantic.dev) agent — bound methods passed straight to `Agent(tools=[...])`. |
|
|
13
|
+
| `langchain_agent.py` | [LangChain](https://python.langchain.com) agent — the same methods via `StructuredTool.from_function` and `create_agent`. |
|
|
14
|
+
|
|
15
|
+
## Run
|
|
16
|
+
|
|
17
|
+
```sh
|
|
18
|
+
pip install rete-graph "pydantic-ai-slim[anthropic]" # ... or
|
|
19
|
+
pip install rete-graph langchain langchain-anthropic
|
|
20
|
+
|
|
21
|
+
export ANTHROPIC_API_KEY=...
|
|
22
|
+
python pydantic_ai_agent.py "How many laws are here? Show me three with their titles."
|
|
23
|
+
python langchain_agent.py "Which three laws are the oldest?"
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Both default to the published Spanish-legislation graph
|
|
27
|
+
(`https://data.graphplaza.com/boe/boe.rete`, 7 MB on a CDN). Point them
|
|
28
|
+
anywhere — including a private file that never leaves the machine:
|
|
29
|
+
|
|
30
|
+
```sh
|
|
31
|
+
RETE_GRAPH=/path/to/my-graph.rete python langchain_agent.py "What is in this graph?"
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Any OpenAI-compatible endpoint works instead of Anthropic (`pip install
|
|
35
|
+
langchain-openai` for the LangChain script):
|
|
36
|
+
|
|
37
|
+
```sh
|
|
38
|
+
export OPENAI_BASE_URL=https://your-endpoint/v1 OPENAI_API_KEY=... MODEL=...
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Each run prints the bytes and requests actually fetched, so the cost of the
|
|
42
|
+
agent's exploration stays visible:
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
[graph reads: {'fileLength': 6958628, 'bytes': 3065519, 'requests': 18}]
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Verified
|
|
49
|
+
|
|
50
|
+
Both scripts were run end to end against a live tool-calling model and the
|
|
51
|
+
remote `boe` graph: the agent reads the schema, copies the ELI IRIs out of it,
|
|
52
|
+
writes its own SPARQL, and answers from the rows. The MCP alternative —
|
|
53
|
+
pointing either framework at the hosted rete MCP server instead — is in the
|
|
54
|
+
same tutorial.
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"""A LangChain agent that answers questions from one `.rete` graph.
|
|
2
|
+
|
|
3
|
+
pip install rete-graph langchain langchain-anthropic
|
|
4
|
+
export ANTHROPIC_API_KEY=...
|
|
5
|
+
python langchain_agent.py "How many laws are in this graph, and which are the oldest?"
|
|
6
|
+
|
|
7
|
+
Defaults to the published Spanish-legislation graph (7 MB on a CDN, read by
|
|
8
|
+
byte range). Point it anywhere with RETE_GRAPH=/path/to/your.rete.
|
|
9
|
+
|
|
10
|
+
Any OpenAI-compatible endpoint works instead of Anthropic:
|
|
11
|
+
|
|
12
|
+
pip install langchain-openai
|
|
13
|
+
export OPENAI_BASE_URL=https://your-endpoint/v1 OPENAI_API_KEY=... MODEL=...
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import os
|
|
19
|
+
import sys
|
|
20
|
+
|
|
21
|
+
from langchain.agents import create_agent
|
|
22
|
+
from langchain_core.tools import StructuredTool
|
|
23
|
+
|
|
24
|
+
from rete_tools import SYSTEM_PROMPT, ReteGraphTools
|
|
25
|
+
|
|
26
|
+
GRAPH = os.environ.get("RETE_GRAPH", "https://data.graphplaza.com/boe/boe.rete")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def build_model():
|
|
30
|
+
"""An Anthropic model by default; an OpenAI-compatible endpoint if configured."""
|
|
31
|
+
model = os.environ.get("MODEL", "claude-sonnet-5")
|
|
32
|
+
base_url = os.environ.get("OPENAI_BASE_URL")
|
|
33
|
+
if not base_url:
|
|
34
|
+
from langchain.chat_models import init_chat_model
|
|
35
|
+
|
|
36
|
+
return init_chat_model(model, model_provider="anthropic")
|
|
37
|
+
|
|
38
|
+
from langchain_openai import ChatOpenAI
|
|
39
|
+
|
|
40
|
+
return ChatOpenAI(model=model, base_url=base_url, api_key=os.environ["OPENAI_API_KEY"])
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def rete_toolkit(source: str) -> tuple[ReteGraphTools, list[StructuredTool]]:
|
|
44
|
+
"""Bound methods → LangChain tools; name and description come from the method."""
|
|
45
|
+
tools = ReteGraphTools(source)
|
|
46
|
+
methods = [
|
|
47
|
+
tools.dataset_card,
|
|
48
|
+
tools.dataset_schema,
|
|
49
|
+
tools.example_queries,
|
|
50
|
+
tools.find_entities,
|
|
51
|
+
tools.sparql_query,
|
|
52
|
+
]
|
|
53
|
+
return tools, [StructuredTool.from_function(m, name=m.__name__) for m in methods]
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def main(question: str) -> None:
|
|
57
|
+
tools, toolkit = rete_toolkit(GRAPH)
|
|
58
|
+
agent = create_agent(build_model(), toolkit, system_prompt=SYSTEM_PROMPT)
|
|
59
|
+
|
|
60
|
+
result = agent.invoke({"messages": [{"role": "user", "content": question}]})
|
|
61
|
+
print(result["messages"][-1].content)
|
|
62
|
+
print(f"\n[graph reads: {tools.read_stats()}]")
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
if __name__ == "__main__":
|
|
66
|
+
main(" ".join(sys.argv[1:]) or "What is in this graph? Show me five example entities.")
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"""A Pydantic AI agent that answers questions from one `.rete` graph.
|
|
2
|
+
|
|
3
|
+
pip install rete-graph "pydantic-ai-slim[anthropic]"
|
|
4
|
+
export ANTHROPIC_API_KEY=...
|
|
5
|
+
python pydantic_ai_agent.py "How many laws are in this graph, and which are the oldest?"
|
|
6
|
+
|
|
7
|
+
Defaults to the published Spanish-legislation graph (7 MB on a CDN, read by
|
|
8
|
+
byte range). Point it anywhere with RETE_GRAPH=/path/to/your.rete.
|
|
9
|
+
|
|
10
|
+
Any OpenAI-compatible endpoint works instead of Anthropic:
|
|
11
|
+
|
|
12
|
+
export OPENAI_BASE_URL=https://your-endpoint/v1 OPENAI_API_KEY=... MODEL=...
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import asyncio
|
|
18
|
+
import os
|
|
19
|
+
import sys
|
|
20
|
+
|
|
21
|
+
from pydantic_ai import Agent
|
|
22
|
+
|
|
23
|
+
from rete_tools import SYSTEM_PROMPT, ReteGraphTools
|
|
24
|
+
|
|
25
|
+
GRAPH = os.environ.get("RETE_GRAPH", "https://data.graphplaza.com/boe/boe.rete")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def build_model():
|
|
29
|
+
"""An Anthropic model by default; an OpenAI-compatible endpoint if configured."""
|
|
30
|
+
model = os.environ.get("MODEL", "anthropic:claude-sonnet-5")
|
|
31
|
+
base_url = os.environ.get("OPENAI_BASE_URL")
|
|
32
|
+
if not base_url:
|
|
33
|
+
return model
|
|
34
|
+
|
|
35
|
+
from pydantic_ai.models.openai import OpenAIChatModel
|
|
36
|
+
from pydantic_ai.providers.openai import OpenAIProvider
|
|
37
|
+
|
|
38
|
+
return OpenAIChatModel(
|
|
39
|
+
model,
|
|
40
|
+
provider=OpenAIProvider(base_url=base_url, api_key=os.environ["OPENAI_API_KEY"]),
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
async def main(question: str) -> None:
|
|
45
|
+
tools = ReteGraphTools(GRAPH)
|
|
46
|
+
|
|
47
|
+
# Bound methods: pydantic-ai reads the signature for the schema and the
|
|
48
|
+
# docstring for the description, so the tools need no extra declaration.
|
|
49
|
+
agent = Agent(
|
|
50
|
+
build_model(),
|
|
51
|
+
system_prompt=SYSTEM_PROMPT,
|
|
52
|
+
tools=[
|
|
53
|
+
tools.dataset_card,
|
|
54
|
+
tools.dataset_schema,
|
|
55
|
+
tools.example_queries,
|
|
56
|
+
tools.find_entities,
|
|
57
|
+
tools.sparql_query,
|
|
58
|
+
],
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
result = await agent.run(question)
|
|
62
|
+
print(result.output)
|
|
63
|
+
print(f"\n[graph reads: {tools.read_stats()}]")
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
if __name__ == "__main__":
|
|
67
|
+
question = " ".join(sys.argv[1:]) or "What is in this graph? Show me five example entities."
|
|
68
|
+
asyncio.run(main(question))
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
"""A framework-agnostic tool surface over one `.rete` graph.
|
|
2
|
+
|
|
3
|
+
Five tools mirror the read loop the rete MCP server exposes — card → schema →
|
|
4
|
+
examples → find_entities → sparql_query — but run **in process**, against a
|
|
5
|
+
local path or an `https://` URL, with no server between the agent and the file.
|
|
6
|
+
|
|
7
|
+
Each method returns a JSON string, because that is what every agent framework
|
|
8
|
+
wants to hand a model. Results are trimmed: a real schema is hundreds of
|
|
9
|
+
kilobytes, which would swamp the context window without telling the model
|
|
10
|
+
anything it can act on.
|
|
11
|
+
|
|
12
|
+
Reads are byte ranges, so the cost of a tool call is bounded by the *query*,
|
|
13
|
+
not the file: `tools.read_stats()` reports the bytes and requests actually
|
|
14
|
+
fetched so far.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import json
|
|
20
|
+
from typing import Any, Dict, List
|
|
21
|
+
|
|
22
|
+
import rete_graph as rete
|
|
23
|
+
|
|
24
|
+
MAX_ROWS = 100 # rows handed back to the model
|
|
25
|
+
MAX_CLASSES = 40 # schema trimming — the full profile is far too big to prompt with
|
|
26
|
+
MAX_RELATIONS = 60
|
|
27
|
+
MAX_EXAMPLES = 8
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _json(obj: Any) -> str:
|
|
31
|
+
return json.dumps(obj, ensure_ascii=False, default=str)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _value(term: Any) -> Any:
|
|
35
|
+
"""A `Term` as a plain JSON value: typed literals become int/float/bool."""
|
|
36
|
+
try:
|
|
37
|
+
return term.to_python()
|
|
38
|
+
except Exception: # unusual datatype — keep the lexical form
|
|
39
|
+
return term.value
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class ReteGraphTools:
|
|
43
|
+
"""The five tools, bound to one graph.
|
|
44
|
+
|
|
45
|
+
>>> tools = ReteGraphTools("https://data.graphplaza.com/boe/boe.rete")
|
|
46
|
+
>>> tools.dataset_card()[:40]
|
|
47
|
+
'{"title": "BOE — Spanish consolidated l'
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
def __init__(self, source: str, *, max_rows: int = MAX_ROWS, **open_kwargs: Any):
|
|
51
|
+
self.source = source
|
|
52
|
+
self.max_rows = max_rows
|
|
53
|
+
self.graph = rete.open(source, **open_kwargs)
|
|
54
|
+
|
|
55
|
+
# -- discovery ---------------------------------------------------------
|
|
56
|
+
|
|
57
|
+
def dataset_card(self) -> str:
|
|
58
|
+
"""What this graph is: title, description, licence, size, vocabularies."""
|
|
59
|
+
card = self.graph.card() or {}
|
|
60
|
+
keep = (
|
|
61
|
+
"title",
|
|
62
|
+
"description",
|
|
63
|
+
"license",
|
|
64
|
+
"source",
|
|
65
|
+
"created",
|
|
66
|
+
"triple_count",
|
|
67
|
+
"quad_count",
|
|
68
|
+
"term_count",
|
|
69
|
+
"vocabularies",
|
|
70
|
+
"languages",
|
|
71
|
+
)
|
|
72
|
+
out: Dict[str, Any] = {k: card[k] for k in keep if card.get(k)}
|
|
73
|
+
out["info"] = self.graph.info()
|
|
74
|
+
return _json(out)
|
|
75
|
+
|
|
76
|
+
def dataset_schema(self) -> str:
|
|
77
|
+
"""The classes and subject→predicate→object relations actually present.
|
|
78
|
+
|
|
79
|
+
Copy these IRIs verbatim into queries; do not invent namespaces.
|
|
80
|
+
"""
|
|
81
|
+
schema = self.graph.schema()
|
|
82
|
+
classes = sorted(schema.get("classes", []), key=lambda c: -c[1])[:MAX_CLASSES]
|
|
83
|
+
relations = sorted(schema.get("relations", []), key=lambda r: -r[3])[:MAX_RELATIONS]
|
|
84
|
+
return _json(
|
|
85
|
+
{
|
|
86
|
+
"classes": [{"class": c[0], "instances": c[1]} for c in classes],
|
|
87
|
+
"relations": [
|
|
88
|
+
{"subject_class": r[0], "predicate": r[1], "object_class": r[2], "count": r[3]}
|
|
89
|
+
for r in relations
|
|
90
|
+
],
|
|
91
|
+
"note": (
|
|
92
|
+
f"top {len(classes)}/{len(schema.get('classes', []))} classes and "
|
|
93
|
+
f"{len(relations)}/{len(schema.get('relations', []))} relations, by count"
|
|
94
|
+
),
|
|
95
|
+
}
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
def example_queries(self) -> str:
|
|
99
|
+
"""Runnable SPARQL the dataset ships with — the graph's own idioms."""
|
|
100
|
+
examples = self.graph.examples() or []
|
|
101
|
+
return _json(
|
|
102
|
+
[
|
|
103
|
+
{"question": e.get("question") or e.get("title"), "sparql": e.get("sparql")}
|
|
104
|
+
for e in examples[:MAX_EXAMPLES]
|
|
105
|
+
]
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
# -- lookup ------------------------------------------------------------
|
|
109
|
+
|
|
110
|
+
def find_entities(self, text: str) -> str:
|
|
111
|
+
"""Resolve a name to entity IRIs. Use before querying for a named thing."""
|
|
112
|
+
hits: List[List[str]] = list(self.graph.prefix_search(text) or [])
|
|
113
|
+
if not hits:
|
|
114
|
+
# only present when the file was built with --text-index; [] otherwise
|
|
115
|
+
hits = list(self.graph.text_search(text) or [])
|
|
116
|
+
return _json([{"label": h[0], "iri": h[1]} for h in hits[:20]])
|
|
117
|
+
|
|
118
|
+
# -- query -------------------------------------------------------------
|
|
119
|
+
|
|
120
|
+
def sparql_query(self, query: str) -> str:
|
|
121
|
+
"""Run SPARQL 1.1 (SELECT / ASK / CONSTRUCT / DESCRIBE) over the graph.
|
|
122
|
+
|
|
123
|
+
A syntax or vocabulary mistake comes back as `{"error": ...}` with the
|
|
124
|
+
parser's message — read it and retry rather than giving up.
|
|
125
|
+
"""
|
|
126
|
+
try:
|
|
127
|
+
result = self.graph.query(query)
|
|
128
|
+
except (ValueError, RuntimeError) as exc:
|
|
129
|
+
return _json({"error": str(exc)})
|
|
130
|
+
|
|
131
|
+
if isinstance(result, bool):
|
|
132
|
+
return _json({"kind": "ask", "boolean": result})
|
|
133
|
+
if result and isinstance(result[0], tuple):
|
|
134
|
+
triples = [[_value(t) for t in row] for row in result[: self.max_rows]]
|
|
135
|
+
return _json(
|
|
136
|
+
{"kind": "graph", "triples": triples, "truncated": len(result) > self.max_rows}
|
|
137
|
+
)
|
|
138
|
+
rows = [{k: _value(v) for k, v in row.items()} for row in result[: self.max_rows]]
|
|
139
|
+
return _json({"kind": "table", "rows": rows, "truncated": len(result) > self.max_rows})
|
|
140
|
+
|
|
141
|
+
# -- observability -----------------------------------------------------
|
|
142
|
+
|
|
143
|
+
def read_stats(self) -> Dict[str, Any]:
|
|
144
|
+
"""Bytes and requests physically fetched so far — laziness, measured."""
|
|
145
|
+
return self.graph.stats()
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
SYSTEM_PROMPT = """\
|
|
149
|
+
You answer questions about ONE RDF knowledge graph by writing and running
|
|
150
|
+
SPARQL. Ground every answer in query results — never guess.
|
|
151
|
+
|
|
152
|
+
Method:
|
|
153
|
+
1. dataset_card, then dataset_schema. Copy class and predicate IRIs exactly
|
|
154
|
+
from the schema; never invent a namespace or a prefix.
|
|
155
|
+
2. example_queries shows how this particular graph is meant to be queried.
|
|
156
|
+
3. If the question names a specific thing, resolve it with find_entities first.
|
|
157
|
+
4. sparql_query, then read the rows and refine. Always use LIMIT (<= 200).
|
|
158
|
+
If a query errors, fix it from the message and try again.
|
|
159
|
+
|
|
160
|
+
Answer in prose, and state the SPARQL you ran.
|
|
161
|
+
"""
|
|
@@ -127,6 +127,15 @@ impl Graph {
|
|
|
127
127
|
}
|
|
128
128
|
Ok(())
|
|
129
129
|
}
|
|
130
|
+
|
|
131
|
+
/// The incompleteness verdict is PER CALL on this resident handle: reset
|
|
132
|
+
/// the sticky failure flags before evaluating, so one transient network
|
|
133
|
+
/// failure fails only the call it happened in — failed tiles/chunks are
|
|
134
|
+
/// never cached, so the next call simply retries them. (Same shape as the
|
|
135
|
+
/// browser engine's RemoteGraph entry points.)
|
|
136
|
+
fn fresh_verdict(&self) {
|
|
137
|
+
self.rete.reset_load_failures();
|
|
138
|
+
}
|
|
130
139
|
}
|
|
131
140
|
|
|
132
141
|
#[pymethods]
|
|
@@ -143,6 +152,7 @@ impl Graph {
|
|
|
143
152
|
graph: Option<&str>,
|
|
144
153
|
format: &str,
|
|
145
154
|
) -> PyResult<String> {
|
|
155
|
+
self.fresh_verdict();
|
|
146
156
|
let out = py
|
|
147
157
|
.allow_threads(|| -> Result<String, String> {
|
|
148
158
|
let shapes =
|
|
@@ -169,6 +179,7 @@ impl Graph {
|
|
|
169
179
|
/// QL entailment by query rewriting.
|
|
170
180
|
#[pyo3(signature = (query, *, reason=false))]
|
|
171
181
|
fn query(&self, py: Python<'_>, query: &str, reason: bool) -> PyResult<String> {
|
|
182
|
+
self.fresh_verdict();
|
|
172
183
|
let out = py
|
|
173
184
|
.allow_threads(|| {
|
|
174
185
|
if reason {
|
|
@@ -474,5 +485,8 @@ fn _rete(m: &Bound<'_, PyModule>) -> PyResult<()> {
|
|
|
474
485
|
m.add_function(wrap_pyfunction!(build, m)?)?;
|
|
475
486
|
m.add_function(wrap_pyfunction!(build_dataset, m)?)?;
|
|
476
487
|
m.add("__version__", env!("CARGO_PKG_VERSION"))?;
|
|
488
|
+
// The binding's version tracks the binding; this reports the engine actually
|
|
489
|
+
// compiled into the wheel, which is what "does my install support X?" means.
|
|
490
|
+
m.add("__engine_version__", rete_core::VERSION)?;
|
|
477
491
|
Ok(())
|
|
478
492
|
}
|
|
@@ -14,6 +14,12 @@ categories = ["database", "data-structures", "web-programming"]
|
|
|
14
14
|
publish = ["crates-io"]
|
|
15
15
|
include = ["src/**", "tests/**", "examples/**", "Cargo.toml", "README.md", "LICENSE"]
|
|
16
16
|
|
|
17
|
+
# docs.rs builds default features only, which would hide the `parallel` module.
|
|
18
|
+
# The builder is native Linux with a C toolchain, so `compression` + `rayon`
|
|
19
|
+
# both build there.
|
|
20
|
+
[package.metadata.docs.rs]
|
|
21
|
+
all-features = true
|
|
22
|
+
|
|
17
23
|
[dependencies]
|
|
18
24
|
thiserror.workspace = true
|
|
19
25
|
oxrdf.workspace = true
|
|
@@ -33,8 +39,11 @@ regex-lite = "0.1"
|
|
|
33
39
|
md-5 = "0.10"
|
|
34
40
|
sha1 = "0.10"
|
|
35
41
|
sha2 = "0.10"
|
|
36
|
-
# Randomness for the SPARQL RAND/UUID/STRUUID/BNODE built-ins. On
|
|
37
|
-
#
|
|
42
|
+
# Randomness for the SPARQL RAND/UUID/STRUUID/BNODE built-ins. On
|
|
43
|
+
# wasm32-unknown-unknown getrandom has no default backend: a *browser* consumer
|
|
44
|
+
# turns on the `wasm-js` feature below (crypto.getRandomValues); a non-browser
|
|
45
|
+
# wasm host (Chicory/WASI/Wasmtime) must supply its own via getrandom's `custom`
|
|
46
|
+
# backend. Native targets use the OS backend and need neither.
|
|
38
47
|
getrandom = "0.2"
|
|
39
48
|
zstd = { workspace = true, optional = true }
|
|
40
49
|
# Data-parallel query evaluator (prototype). Behind the `parallel` feature so it
|
|
@@ -54,6 +63,11 @@ compression = ["dep:zstd"]
|
|
|
54
63
|
# Prototype data-parallel query evaluator. NOT default; wasm must never enable it
|
|
55
64
|
# (wasm has no threads). Enables the `parallel` module + `rayon`.
|
|
56
65
|
parallel = ["dep:rayon"]
|
|
66
|
+
# getrandom's browser backend (crypto.getRandomValues, via js-sys/wasm-bindgen).
|
|
67
|
+
# Enable ONLY for a wasm build that runs in a browser — it makes the module
|
|
68
|
+
# import wasm-bindgen JS glue, which a non-browser wasm host (Chicory on the JVM,
|
|
69
|
+
# WASI) cannot link. Those hosts leave this off and register a `custom` backend.
|
|
70
|
+
wasm-js = ["getrandom/js"]
|
|
57
71
|
|
|
58
72
|
# The parallel benchmark only compiles with the `parallel` feature (it uses
|
|
59
73
|
# rayon + the feature-gated `parallel` module). Without the gate, a default
|
|
@@ -61,7 +75,3 @@ parallel = ["dep:rayon"]
|
|
|
61
75
|
[[example]]
|
|
62
76
|
name = "parallel_bench"
|
|
63
77
|
required-features = ["parallel"]
|
|
64
|
-
|
|
65
|
-
# On wasm, getrandom (pulled transitively via oxrdf) needs its browser backend.
|
|
66
|
-
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
|
67
|
-
getrandom = { version = "0.2", features = ["js"] }
|