mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-27 21:08:00 +00:00
Merge pull request #113 from sanil-23/issue-60-ingestion-relex
Add GLiNER relex ingestion pipeline (#60)
This commit is contained in:
Generated
+178
-149
@@ -35,7 +35,7 @@ version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0"
|
||||
dependencies = [
|
||||
"crypto-common",
|
||||
"crypto-common 0.1.7",
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
@@ -178,15 +178,6 @@ dependencies = [
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ar_archive_writer"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7eb93bbb63b9c227414f6eb3a0adfddca591a8ce1e9b60661bb08969b87e340b"
|
||||
dependencies = [
|
||||
"object 0.37.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "archery"
|
||||
version = "1.2.2"
|
||||
@@ -376,9 +367,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aws-lc-sys"
|
||||
version = "0.39.0"
|
||||
version = "0.39.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fa7e52a4c5c547c741610a2c6f123f3881e409b714cd27e6798ef020c514f0a"
|
||||
checksum = "83a25cf98105baa966497416dbd42565ce3a8cf8dbfd59803ec9ad46f3126399"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"cmake",
|
||||
@@ -559,21 +550,21 @@ version = "0.10.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe"
|
||||
dependencies = [
|
||||
"digest",
|
||||
"digest 0.10.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "blake3"
|
||||
version = "1.8.3"
|
||||
version = "1.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2468ef7d57b3fb7e16b576e8377cdbde2320c60e1491e961d11da40fc4f02a2d"
|
||||
checksum = "4d2d5991425dfd0785aed03aedcf0b321d61975c9b5b3689c774a2610ae0b51e"
|
||||
dependencies = [
|
||||
"arrayref",
|
||||
"arrayvec",
|
||||
"cc",
|
||||
"cfg-if",
|
||||
"constant_time_eq",
|
||||
"cpufeatures 0.2.17",
|
||||
"cpufeatures 0.3.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -585,6 +576,15 @@ dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cdd35008169921d80bc60d3d0ab416eecb028c4cd653352907921d95084790be"
|
||||
dependencies = [
|
||||
"hybrid-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block-padding"
|
||||
version = "0.3.3"
|
||||
@@ -763,23 +763,13 @@ dependencies = [
|
||||
"phf 0.12.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "chumsky"
|
||||
version = "0.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8eebd66744a15ded14960ab4ccdbfb51ad3b81f51f3f04a80adac98c985396c9"
|
||||
dependencies = [
|
||||
"hashbrown 0.14.5",
|
||||
"stacker",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cipher"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
|
||||
dependencies = [
|
||||
"crypto-common",
|
||||
"crypto-common 0.1.7",
|
||||
"inout",
|
||||
"zeroize",
|
||||
]
|
||||
@@ -851,6 +841,12 @@ dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cmov"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "de0758edba32d61d1fd9f4d69491b47604b91ee2f7e6b33de7e54ca4ebe55dc3"
|
||||
|
||||
[[package]]
|
||||
name = "cobs"
|
||||
version = "0.5.1"
|
||||
@@ -939,6 +935,12 @@ version = "0.9.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
|
||||
|
||||
[[package]]
|
||||
name = "const-oid"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c"
|
||||
|
||||
[[package]]
|
||||
name = "const_panic"
|
||||
version = "0.2.15"
|
||||
@@ -1111,6 +1113,15 @@ dependencies = [
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77727bb15fa921304124b128af125e7e3b968275d1b108b379190264f4423710"
|
||||
dependencies = [
|
||||
"hybrid-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "csv"
|
||||
version = "1.4.0"
|
||||
@@ -1141,6 +1152,15 @@ dependencies = [
|
||||
"cipher",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ctutils"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1005a6d4446f5120ef475ad3d2af2b30c49c2c9c6904258e3bb30219bebed5e4"
|
||||
dependencies = [
|
||||
"cmov",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "curve25519-dalek"
|
||||
version = "4.1.3"
|
||||
@@ -1150,7 +1170,7 @@ dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures 0.2.17",
|
||||
"curve25519-dalek-derive",
|
||||
"digest",
|
||||
"digest 0.10.7",
|
||||
"fiat-crypto",
|
||||
"rustc_version",
|
||||
"serde",
|
||||
@@ -1324,7 +1344,7 @@ version = "0.7.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb"
|
||||
dependencies = [
|
||||
"const-oid",
|
||||
"const-oid 0.9.6",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
@@ -1440,11 +1460,23 @@ version = "0.10.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
|
||||
dependencies = [
|
||||
"block-buffer",
|
||||
"crypto-common",
|
||||
"block-buffer 0.10.4",
|
||||
"crypto-common 0.1.7",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4850db49bf08e663084f7fb5c87d202ef91a3907271aff24a94eb97ff039153c"
|
||||
dependencies = [
|
||||
"block-buffer 0.12.0",
|
||||
"const-oid 0.10.2",
|
||||
"crypto-common 0.2.1",
|
||||
"ctutils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "directories"
|
||||
version = "6.0.0"
|
||||
@@ -1578,7 +1610,7 @@ dependencies = [
|
||||
"ed25519",
|
||||
"rand_core 0.6.4",
|
||||
"serde",
|
||||
"sha2",
|
||||
"sha2 0.10.9",
|
||||
"subtle",
|
||||
"zeroize",
|
||||
]
|
||||
@@ -1736,7 +1768,7 @@ dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"csv",
|
||||
"deku",
|
||||
"md-5",
|
||||
"md-5 0.10.6",
|
||||
"parse_int",
|
||||
"regex",
|
||||
"serde",
|
||||
@@ -1759,12 +1791,12 @@ dependencies = [
|
||||
"gimli",
|
||||
"libc",
|
||||
"log",
|
||||
"md-5",
|
||||
"md-5 0.10.6",
|
||||
"miette",
|
||||
"nix 0.30.1",
|
||||
"object 0.38.1",
|
||||
"serde",
|
||||
"sha2",
|
||||
"sha2 0.10.9",
|
||||
"strum",
|
||||
"thiserror 2.0.18",
|
||||
]
|
||||
@@ -2264,10 +2296,6 @@ name = "hashbrown"
|
||||
version = "0.14.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"allocator-api2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
@@ -2411,7 +2439,7 @@ version = "0.12.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7"
|
||||
dependencies = [
|
||||
"hmac",
|
||||
"hmac 0.12.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2420,7 +2448,16 @@ version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
|
||||
dependencies = [
|
||||
"digest",
|
||||
"digest 0.10.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hmac"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6303bc9732ae41b04cb554b844a762b4115a61bfaa81e3e83050991eeb56863f"
|
||||
dependencies = [
|
||||
"digest 0.11.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2519,6 +2556,15 @@ version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
|
||||
|
||||
[[package]]
|
||||
name = "hybrid-array"
|
||||
version = "0.4.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a79f2aff40c18ab8615ddc5caa9eb5b96314aef18fe5823090f204ad988e813"
|
||||
dependencies = [
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper"
|
||||
version = "1.8.1"
|
||||
@@ -2957,9 +3003,9 @@ checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2"
|
||||
|
||||
[[package]]
|
||||
name = "iri-string"
|
||||
version = "0.7.11"
|
||||
version = "0.7.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d8e7418f59cc01c88316161279a7f665217ae316b388e58a0d10e29f54f1e5eb"
|
||||
checksum = "25e659a4bb38e810ebc252e53b5814ff908a8c58c2a9ce2fae1bbec24cbf4e20"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"serde",
|
||||
@@ -3040,10 +3086,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.91"
|
||||
version = "0.3.93"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c"
|
||||
checksum = "797146bb2677299a1eb6b7b50a890f4c361b29ef967addf5b2fa45dae1bb6d7d"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"futures-util",
|
||||
"once_cell",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
@@ -3117,12 +3165,11 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
|
||||
|
||||
[[package]]
|
||||
name = "lettre"
|
||||
version = "0.11.19"
|
||||
version = "0.11.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e13e10e8818f8b2a60f52cb127041d388b89f3a96a62be9ceaffa22262fef7f"
|
||||
checksum = "471816f3e24b85e820dee02cde962379ea1a669e5242f19c61bcbcffedf4c4fb"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"chumsky",
|
||||
"email-encoding",
|
||||
"email_address",
|
||||
"fastrand",
|
||||
@@ -3252,12 +3299,12 @@ dependencies = [
|
||||
"indexmap",
|
||||
"itoa",
|
||||
"log",
|
||||
"md-5",
|
||||
"md-5 0.10.6",
|
||||
"nom 8.0.0",
|
||||
"nom_locate",
|
||||
"rand 0.9.2",
|
||||
"rangemap",
|
||||
"sha2",
|
||||
"sha2 0.10.9",
|
||||
"stringprep",
|
||||
"thiserror 2.0.18",
|
||||
"ttf-parser",
|
||||
@@ -3476,7 +3523,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_html_form",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
"sha2 0.10.9",
|
||||
"tempfile",
|
||||
"thiserror 2.0.18",
|
||||
"tokio",
|
||||
@@ -3559,7 +3606,7 @@ dependencies = [
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"hkdf",
|
||||
"hmac",
|
||||
"hmac 0.12.1",
|
||||
"itertools 0.14.0",
|
||||
"js_option",
|
||||
"matrix-sdk-common",
|
||||
@@ -3569,7 +3616,7 @@ dependencies = [
|
||||
"ruma",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
"sha2 0.10.9",
|
||||
"subtle",
|
||||
"thiserror 2.0.18",
|
||||
"time",
|
||||
@@ -3604,7 +3651,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde-wasm-bindgen",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
"sha2 0.10.9",
|
||||
"thiserror 2.0.18",
|
||||
"tokio",
|
||||
"tracing",
|
||||
@@ -3652,13 +3699,13 @@ dependencies = [
|
||||
"blake3",
|
||||
"chacha20poly1305",
|
||||
"getrandom 0.2.17",
|
||||
"hmac",
|
||||
"hmac 0.12.1",
|
||||
"pbkdf2",
|
||||
"rand 0.8.5",
|
||||
"rmp-serde",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
"sha2 0.10.9",
|
||||
"thiserror 2.0.18",
|
||||
"zeroize",
|
||||
]
|
||||
@@ -3719,7 +3766,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"digest",
|
||||
"digest 0.10.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "md-5"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69b6441f590336821bb897fb28fc622898ccceb1d6cea3fde5ea86b090c4de98"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"digest 0.11.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4117,7 +4174,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_path_to_error",
|
||||
"sha2",
|
||||
"sha2 0.10.9",
|
||||
"thiserror 1.0.69",
|
||||
"url",
|
||||
]
|
||||
@@ -4228,7 +4285,7 @@ dependencies = [
|
||||
"futures-util",
|
||||
"glob",
|
||||
"hex",
|
||||
"hmac",
|
||||
"hmac 0.12.1",
|
||||
"hostname",
|
||||
"landlock",
|
||||
"lettre",
|
||||
@@ -4239,8 +4296,6 @@ dependencies = [
|
||||
"nu-ansi-term 0.46.0",
|
||||
"nusb 0.2.3",
|
||||
"once_cell",
|
||||
"openssl",
|
||||
"openssl-sys",
|
||||
"opentelemetry",
|
||||
"opentelemetry-otlp",
|
||||
"opentelemetry_sdk",
|
||||
@@ -4265,7 +4320,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde-big-array",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
"sha2 0.10.9",
|
||||
"shellexpand",
|
||||
"socketioxide",
|
||||
"sysinfo",
|
||||
@@ -4327,15 +4382,6 @@ version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe"
|
||||
|
||||
[[package]]
|
||||
name = "openssl-src"
|
||||
version = "300.5.5+3.5.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f1787d533e03597a7934fd0a765f0d28e94ecc5fb7789f8053b1e699a56f709"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openssl-sys"
|
||||
version = "0.9.112"
|
||||
@@ -4344,7 +4390,6 @@ checksum = "57d55af3b3e226502be1526dfdba67ab0e9c96fc293004e79576b2b9edb0dbdb"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"openssl-src",
|
||||
"pkg-config",
|
||||
"vcpkg",
|
||||
]
|
||||
@@ -4511,8 +4556,8 @@ version = "0.12.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2"
|
||||
dependencies = [
|
||||
"digest",
|
||||
"hmac",
|
||||
"digest 0.10.7",
|
||||
"hmac 0.12.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4752,9 +4797,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "postgres"
|
||||
version = "0.19.12"
|
||||
version = "0.19.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e7c48ece1c6cda0db61b058c1721378da76855140e9214339fa1317decacb176"
|
||||
checksum = "aacf632d0554ff75f58183694f41dc8999c8a3a43a386994d0ec2d034f1dfbe1"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"fallible-iterator 0.2.0",
|
||||
@@ -4766,27 +4811,27 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "postgres-protocol"
|
||||
version = "0.6.10"
|
||||
version = "0.6.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3ee9dd5fe15055d2b6806f4736aa0c9637217074e224bbec46d4041b91bb9491"
|
||||
checksum = "56201207dac53e2f38e848e31b4b91616a6bb6e0c7205b77718994a7f49e70fc"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"byteorder",
|
||||
"bytes",
|
||||
"fallible-iterator 0.2.0",
|
||||
"hmac",
|
||||
"md-5",
|
||||
"hmac 0.13.0",
|
||||
"md-5 0.11.0",
|
||||
"memchr",
|
||||
"rand 0.9.2",
|
||||
"sha2",
|
||||
"rand 0.10.0",
|
||||
"sha2 0.11.0",
|
||||
"stringprep",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "postgres-types"
|
||||
version = "0.2.12"
|
||||
version = "0.2.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "54b858f82211e84682fecd373f68e1ceae642d8d751a1ebd13f33de6257b3e20"
|
||||
checksum = "8dc729a129e682e8d24170cd30ae1aa01b336b096cbb56df6d534ffec133d186"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"chrono",
|
||||
@@ -5035,16 +5080,6 @@ dependencies = [
|
||||
"thiserror 1.0.69",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "psm"
|
||||
version = "0.1.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3852766467df634d74f0b2d7819bf8dc483a0eb2e3b0f50f756f9cfe8b0d18d8"
|
||||
dependencies = [
|
||||
"ar_archive_writer",
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pulldown-cmark"
|
||||
version = "0.13.3"
|
||||
@@ -5675,7 +5710,7 @@ dependencies = [
|
||||
"rand 0.8.5",
|
||||
"ruma-common",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
"sha2 0.10.9",
|
||||
"thiserror 2.0.18",
|
||||
]
|
||||
|
||||
@@ -5695,9 +5730,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustc-hash"
|
||||
version = "2.1.1"
|
||||
version = "2.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
|
||||
checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe"
|
||||
|
||||
[[package]]
|
||||
name = "rustc_version"
|
||||
@@ -6122,7 +6157,7 @@ checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures 0.2.17",
|
||||
"digest",
|
||||
"digest 0.10.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6133,7 +6168,18 @@ checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures 0.2.17",
|
||||
"digest",
|
||||
"digest 0.10.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha2"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures 0.3.0",
|
||||
"digest 0.11.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6317,19 +6363,6 @@ version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
|
||||
|
||||
[[package]]
|
||||
name = "stacker"
|
||||
version = "0.1.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08d74a23609d509411d10e2176dc2a4346e3b4aea2e7b1869f19fdedbc71c013"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"psm",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "static_assertions"
|
||||
version = "1.1.0"
|
||||
@@ -6750,9 +6783,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tokio-postgres"
|
||||
version = "0.7.16"
|
||||
version = "0.7.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dcea47c8f71744367793f16c2db1f11cb859d28f436bdb4ca9193eb1f787ee42"
|
||||
checksum = "4dd8df5ef180f6364759a6f00f7aadda4fbbac86cdee37480826a6ff9f3574ce"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"byteorder",
|
||||
@@ -6767,7 +6800,7 @@ dependencies = [
|
||||
"pin-project-lite",
|
||||
"postgres-protocol",
|
||||
"postgres-types",
|
||||
"rand 0.9.2",
|
||||
"rand 0.10.0",
|
||||
"socket2",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
@@ -6898,7 +6931,7 @@ dependencies = [
|
||||
"toml_datetime 1.1.0+spec-1.1.0",
|
||||
"toml_parser",
|
||||
"toml_writer",
|
||||
"winnow 1.0.0",
|
||||
"winnow 1.0.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6928,7 +6961,7 @@ dependencies = [
|
||||
"indexmap",
|
||||
"toml_datetime 1.1.0+spec-1.1.0",
|
||||
"toml_parser",
|
||||
"winnow 1.0.0",
|
||||
"winnow 1.0.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6937,7 +6970,7 @@ version = "1.1.0+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2334f11ee363607eb04df9b8fc8a13ca1715a72ba8662a26ac285c98aabb4011"
|
||||
dependencies = [
|
||||
"winnow 1.0.0",
|
||||
"winnow 1.0.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -7309,7 +7342,7 @@ version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea"
|
||||
dependencies = [
|
||||
"crypto-common",
|
||||
"crypto-common 0.1.7",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
@@ -7477,14 +7510,14 @@ dependencies = [
|
||||
"ed25519-dalek",
|
||||
"getrandom 0.2.17",
|
||||
"hkdf",
|
||||
"hmac",
|
||||
"hmac 0.12.1",
|
||||
"matrix-pickle",
|
||||
"prost 0.13.5",
|
||||
"rand 0.8.5",
|
||||
"serde",
|
||||
"serde_bytes",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
"sha2 0.10.9",
|
||||
"subtle",
|
||||
"thiserror 2.0.18",
|
||||
"x25519-dalek",
|
||||
@@ -7536,7 +7569,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde-big-array",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
"sha2 0.10.9",
|
||||
"thiserror 2.0.18",
|
||||
"wa-rs-binary",
|
||||
"wa-rs-libsignal",
|
||||
@@ -7574,7 +7607,7 @@ dependencies = [
|
||||
"flate2",
|
||||
"hex",
|
||||
"hkdf",
|
||||
"hmac",
|
||||
"hmac 0.12.1",
|
||||
"log",
|
||||
"md5",
|
||||
"once_cell",
|
||||
@@ -7586,7 +7619,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde-big-array",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
"sha2 0.10.9",
|
||||
"thiserror 2.0.18",
|
||||
"typed-builder",
|
||||
"wa-rs-appstate",
|
||||
@@ -7627,14 +7660,14 @@ dependencies = [
|
||||
"ghash",
|
||||
"hex",
|
||||
"hkdf",
|
||||
"hmac",
|
||||
"hmac 0.12.1",
|
||||
"itertools 0.14.0",
|
||||
"log",
|
||||
"prost 0.14.3",
|
||||
"rand 0.9.2",
|
||||
"serde",
|
||||
"sha1",
|
||||
"sha2",
|
||||
"sha2 0.10.9",
|
||||
"subtle",
|
||||
"thiserror 2.0.18",
|
||||
"uuid",
|
||||
@@ -7656,7 +7689,7 @@ dependencies = [
|
||||
"prost 0.14.3",
|
||||
"rand 0.9.2",
|
||||
"rand_core 0.10.0",
|
||||
"sha2",
|
||||
"sha2 0.10.9",
|
||||
"thiserror 2.0.18",
|
||||
"wa-rs-binary",
|
||||
"wa-rs-libsignal",
|
||||
@@ -7761,9 +7794,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.114"
|
||||
version = "0.2.116"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e"
|
||||
checksum = "7dc0882f7b5bb01ae8c5215a1230832694481c1a4be062fd410e12ea3da5b631"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
@@ -7774,23 +7807,19 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-futures"
|
||||
version = "0.4.64"
|
||||
version = "0.4.66"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e9c5522b3a28661442748e09d40924dfb9ca614b21c00d3fd135720e48b67db8"
|
||||
checksum = "19280959e2844181895ef62f065c63e0ca07ece4771b53d89bfdb967d97cbf05"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"futures-util",
|
||||
"js-sys",
|
||||
"once_cell",
|
||||
"wasm-bindgen",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.114"
|
||||
version = "0.2.116"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6"
|
||||
checksum = "75973d3066e01d035dbedaad2864c398df42f8dd7b1ea057c35b8407c015b537"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
@@ -7798,9 +7827,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.114"
|
||||
version = "0.2.116"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3"
|
||||
checksum = "91af5e4be765819e0bcfee7322c14374dc821e35e72fa663a830bbc7dc199eac"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"proc-macro2",
|
||||
@@ -7811,9 +7840,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.114"
|
||||
version = "0.2.116"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16"
|
||||
checksum = "c9bf0406a78f02f336bf1e451799cca198e8acde4ffa278f0fb20487b150a633"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
@@ -7885,9 +7914,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "web-sys"
|
||||
version = "0.3.91"
|
||||
version = "0.3.93"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "854ba17bb104abfb26ba36da9729addc7ce7f06f5c0f90f3c391f8461cca21f9"
|
||||
checksum = "749466a37ee189057f54748b200186b59a03417a117267baf3fd89cecc9fb837"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
@@ -8363,9 +8392,9 @@ checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945"
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "1.0.0"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a90e88e4667264a994d34e6d1ab2d26d398dcdca8b7f52bec8668957517fc7d8"
|
||||
checksum = "09dac053f1cd375980747450bfc7250c264eaae0583872e845c0c7cd578872b5"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
@@ -8546,18 +8575,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.8.47"
|
||||
version = "0.8.48"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "efbb2a062be311f2ba113ce66f697a4dc589f85e78a4aea276200804cea0ed87"
|
||||
checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9"
|
||||
dependencies = [
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.8.47"
|
||||
version = "0.8.48"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0e8bc7269b54418e7aeeef514aa68f8690b8c0489a06b0136e5f57c4c5ccab89"
|
||||
checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
||||
@@ -84,8 +84,6 @@ opentelemetry_sdk = { version = "0.31", default-features = false, features = ["t
|
||||
opentelemetry-otlp = { version = "0.31", default-features = false, features = ["trace", "metrics", "http-proto", "reqwest-client", "reqwest-rustls-webpki-roots"] }
|
||||
tokio-stream = { version = "0.1.18", features = ["full"] }
|
||||
url = "2"
|
||||
openssl = "0.10"
|
||||
openssl-sys = { version = "0.9", features = ["vendored"] }
|
||||
socketioxide = { version = "0.15", features = ["extensions"] }
|
||||
|
||||
matrix-sdk = { version = "0.16", optional = true, default-features = false, features = ["e2e-encryption", "rustls-tls", "markdown"] }
|
||||
|
||||
+17
-9
@@ -388,15 +388,23 @@ pub fn decrypt_handoff_blob(b64_ciphertext: &str, key_str: &str) -> Result<Strin
|
||||
let tag = &combined[16..32];
|
||||
let ciphertext = &combined[32..];
|
||||
|
||||
let plain = openssl::symm::decrypt_aead(
|
||||
openssl::symm::Cipher::aes_256_gcm(),
|
||||
&key,
|
||||
Some(iv),
|
||||
&[],
|
||||
ciphertext,
|
||||
tag,
|
||||
)
|
||||
.map_err(|e| anyhow::anyhow!("AES-GCM decrypt failed: {e}"))?;
|
||||
// aes-gcm expects ciphertext || tag
|
||||
let mut ct_with_tag = Vec::with_capacity(ciphertext.len() + tag.len());
|
||||
ct_with_tag.extend_from_slice(ciphertext);
|
||||
ct_with_tag.extend_from_slice(tag);
|
||||
|
||||
use aes_gcm::aead::generic_array::typenum::U16;
|
||||
use aes_gcm::aead::{Aead, KeyInit};
|
||||
use aes_gcm::aes::Aes256;
|
||||
use aes_gcm::AesGcm;
|
||||
type Aes256Gcm16 = AesGcm<Aes256, U16>;
|
||||
|
||||
let cipher =
|
||||
Aes256Gcm16::new_from_slice(&key).map_err(|e| anyhow::anyhow!("invalid AES key: {e}"))?;
|
||||
let nonce = aes_gcm::aead::generic_array::GenericArray::from_slice(iv);
|
||||
let plain = cipher
|
||||
.decrypt(nonce, ct_with_tag.as_ref())
|
||||
.map_err(|e| anyhow::anyhow!("AES-GCM decrypt failed: {e}"))?;
|
||||
|
||||
String::from_utf8(plain).context("handoff plaintext is not UTF-8")
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,10 +1,16 @@
|
||||
pub mod chunker;
|
||||
pub mod embeddings;
|
||||
pub mod ingestion;
|
||||
pub mod ops;
|
||||
pub(crate) mod relex;
|
||||
pub mod rpc_models;
|
||||
pub mod store;
|
||||
pub mod traits;
|
||||
|
||||
pub use ingestion::{
|
||||
ExtractedEntity, ExtractedRelation, ExtractionMode, MemoryIngestionConfig,
|
||||
MemoryIngestionRequest, MemoryIngestionResult, DEFAULT_GLINER_RELEX_MODEL,
|
||||
};
|
||||
pub use ops as rpc;
|
||||
pub use ops::*;
|
||||
pub use rpc_models::*;
|
||||
|
||||
@@ -668,6 +668,9 @@ pub async fn memory_init(
|
||||
let client = Arc::new(MemoryClient::from_workspace_dir(workspace_dir.clone())?);
|
||||
*lock_memory_client_state()? = Some(client);
|
||||
let memory_dir = workspace_dir.join("memory");
|
||||
tokio::spawn(async {
|
||||
let _ = super::relex::warm_default_bundle().await;
|
||||
});
|
||||
Ok(envelope(
|
||||
MemoryInitResponse {
|
||||
initialized: true,
|
||||
|
||||
@@ -0,0 +1,916 @@
|
||||
use std::env;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
use std::sync::OnceLock;
|
||||
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use futures_util::TryStreamExt;
|
||||
use glob::glob;
|
||||
use ndarray::{Array, Array2, Array3, Array4, Ix2, Ix3, Ix4};
|
||||
use ort::{
|
||||
session::{builder::GraphOptimizationLevel, Session},
|
||||
value::Tensor,
|
||||
};
|
||||
use parking_lot::Mutex;
|
||||
use serde::Deserialize;
|
||||
use sha2::{Digest, Sha256};
|
||||
use tokenizers::Tokenizer;
|
||||
use tokio::io::AsyncWriteExt;
|
||||
use tokio::sync::Mutex as AsyncMutex;
|
||||
|
||||
use crate::openhuman::memory::DEFAULT_GLINER_RELEX_MODEL;
|
||||
|
||||
const DEFAULT_EXPORTED_RELEX_DIR: &str =
|
||||
"_tmp/gliner-export/artifacts/gliner-relex-large-v0.5-onnx";
|
||||
const DEFAULT_MANAGED_RELEX_DIR: &str = ".openhuman/models/gliner-relex-large-v0.5-onnx";
|
||||
const DEFAULT_RELEX_RELEASE_BASE_URL: &str =
|
||||
"https://github.com/sanil-23/GLiNER/releases/download/tinyhumans-gliner-relex-v0.5-onnx.1";
|
||||
const MODEL_FILE_NAME: &str = "model_quantized.onnx";
|
||||
const FALLBACK_MODEL_FILE_NAME: &str = "model.onnx";
|
||||
const TOKENIZER_FILE_NAME: &str = "tokenizer.json";
|
||||
const TOKENIZER_CONFIG_FILE_NAME: &str = "tokenizer_config.json";
|
||||
const GLINER_CONFIG_FILE_NAME: &str = "gliner_config.json";
|
||||
#[cfg(target_os = "windows")]
|
||||
const ORT_DYLIB_FILE_NAME: &str = "onnxruntime.dll";
|
||||
#[cfg(target_os = "macos")]
|
||||
const ORT_DYLIB_FILE_NAME: &str = "libonnxruntime.dylib";
|
||||
#[cfg(target_os = "linux")]
|
||||
const ORT_DYLIB_FILE_NAME: &str = "libonnxruntime.so";
|
||||
#[cfg(target_os = "linux")]
|
||||
const ORT_SHARED_PROVIDER_FILE_NAME: &str = "libonnxruntime_providers_shared.so";
|
||||
|
||||
struct BundleAsset {
|
||||
remote_name: &'static str,
|
||||
local_name: &'static str,
|
||||
sha256: &'static str,
|
||||
}
|
||||
|
||||
const CORE_BUNDLE_ASSETS: &[BundleAsset] = &[
|
||||
BundleAsset {
|
||||
remote_name: MODEL_FILE_NAME,
|
||||
local_name: MODEL_FILE_NAME,
|
||||
sha256: "7D4B8D35750D0AEC35DA0EB1EDFE33076C6958B8CD6EEC4560C59822536C9AEF",
|
||||
},
|
||||
BundleAsset {
|
||||
remote_name: TOKENIZER_FILE_NAME,
|
||||
local_name: TOKENIZER_FILE_NAME,
|
||||
sha256: "0FD23B86F1BACEE52F4485FCD4441B923132302BED55BC5E081172CA013E7654",
|
||||
},
|
||||
BundleAsset {
|
||||
remote_name: TOKENIZER_CONFIG_FILE_NAME,
|
||||
local_name: TOKENIZER_CONFIG_FILE_NAME,
|
||||
sha256: "3157274603C17459B0589DBB6818A47714D780718A6D0EB505C10347C466F2CD",
|
||||
},
|
||||
BundleAsset {
|
||||
remote_name: GLINER_CONFIG_FILE_NAME,
|
||||
local_name: GLINER_CONFIG_FILE_NAME,
|
||||
sha256: "FF6D7FEFD65F721515A3822BB074F2A36EC9B66AC75DAA400E2465FFE52F02BA",
|
||||
},
|
||||
];
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
const PLATFORM_BUNDLE_ASSETS: &[BundleAsset] = &[BundleAsset {
|
||||
remote_name: ORT_DYLIB_FILE_NAME,
|
||||
local_name: ORT_DYLIB_FILE_NAME,
|
||||
sha256: "EF720FC44A4EA48626BFE1EBD29642DE20222D7F104A509EA305D9F3CB3B7850",
|
||||
}];
|
||||
#[cfg(target_os = "macos")]
|
||||
const PLATFORM_BUNDLE_ASSETS: &[BundleAsset] = &[BundleAsset {
|
||||
remote_name: ORT_DYLIB_FILE_NAME,
|
||||
local_name: ORT_DYLIB_FILE_NAME,
|
||||
sha256: "285C8CD1E53856507B9B2E38EE9AFFC69AA6E90AC30F8670DC8195710CA14B77",
|
||||
}];
|
||||
#[cfg(target_os = "linux")]
|
||||
const PLATFORM_BUNDLE_ASSETS: &[BundleAsset] = &[
|
||||
BundleAsset {
|
||||
remote_name: ORT_DYLIB_FILE_NAME,
|
||||
local_name: ORT_DYLIB_FILE_NAME,
|
||||
sha256: "13AB8084954FA4A47C777880180B90810D6020F021441395712B48A75B74C68B",
|
||||
},
|
||||
BundleAsset {
|
||||
remote_name: ORT_SHARED_PROVIDER_FILE_NAME,
|
||||
local_name: ORT_SHARED_PROVIDER_FILE_NAME,
|
||||
sha256: "086EC1D5388F64153D9C63470D126693DB9A182C8CE236D3A1119068471B8A0D",
|
||||
},
|
||||
];
|
||||
#[cfg(not(any(target_os = "windows", target_os = "macos", target_os = "linux")))]
|
||||
const PLATFORM_BUNDLE_ASSETS: &[BundleAsset] = &[];
|
||||
|
||||
const ENTITY_LABELS: &[&str] = &[
|
||||
"person",
|
||||
"organization",
|
||||
"project",
|
||||
"product",
|
||||
"tool",
|
||||
"topic",
|
||||
"work item",
|
||||
"mode",
|
||||
"place",
|
||||
"room",
|
||||
"date",
|
||||
];
|
||||
|
||||
const RELATION_LABELS: &[&str] = &[
|
||||
"owns",
|
||||
"uses",
|
||||
"works on",
|
||||
"responsible for",
|
||||
"reviews",
|
||||
"works for",
|
||||
"depends on",
|
||||
"prefers",
|
||||
"has deadline",
|
||||
"communicates with",
|
||||
"investigates",
|
||||
"evaluates",
|
||||
"north of",
|
||||
"south of",
|
||||
"east of",
|
||||
"west of",
|
||||
"avoids",
|
||||
];
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct RelexEntity {
|
||||
pub name: String,
|
||||
pub entity_type: String,
|
||||
pub confidence: f32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct RelexRelation {
|
||||
pub subject: String,
|
||||
pub subject_type: String,
|
||||
pub predicate: String,
|
||||
pub object: String,
|
||||
pub object_type: String,
|
||||
pub confidence: f32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub(crate) struct RelexExtraction {
|
||||
pub entities: Vec<RelexEntity>,
|
||||
pub relations: Vec<RelexRelation>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct RelexRuntime {
|
||||
tokenizer: Tokenizer,
|
||||
session: Mutex<Session>,
|
||||
config: RelexBundleConfig,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
struct RelexBundleConfig {
|
||||
#[serde(default = "default_ent_token")]
|
||||
ent_token: String,
|
||||
#[serde(default = "default_rel_token")]
|
||||
rel_token: String,
|
||||
#[serde(default = "default_sep_token")]
|
||||
sep_token: String,
|
||||
#[serde(default = "default_max_width")]
|
||||
max_width: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct PromptBatch {
|
||||
input_ids: Array2<i64>,
|
||||
attention_mask: Array2<i64>,
|
||||
words_mask: Array2<i64>,
|
||||
text_lengths: Array2<i64>,
|
||||
num_words: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct TokenSlice {
|
||||
start: usize,
|
||||
end: usize,
|
||||
text: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct DecodedSpan {
|
||||
start: usize,
|
||||
end: usize,
|
||||
text: String,
|
||||
class_name: String,
|
||||
probability: f32,
|
||||
}
|
||||
|
||||
fn default_ent_token() -> String {
|
||||
"<<ENT>>".to_string()
|
||||
}
|
||||
|
||||
fn default_rel_token() -> String {
|
||||
"<<REL>>".to_string()
|
||||
}
|
||||
|
||||
fn default_sep_token() -> String {
|
||||
"<<SEP>>".to_string()
|
||||
}
|
||||
|
||||
fn default_max_width() -> usize {
|
||||
12
|
||||
}
|
||||
|
||||
pub(crate) async fn runtime(model_name: &str) -> Option<Arc<RelexRuntime>> {
|
||||
if !uses_default_bundle(model_name) {
|
||||
return load_runtime_for_model(model_name).await.ok();
|
||||
}
|
||||
|
||||
static DEFAULT_RUNTIME: OnceLock<Mutex<Option<Arc<RelexRuntime>>>> = OnceLock::new();
|
||||
static DEFAULT_RUNTIME_BOOTSTRAP: OnceLock<AsyncMutex<()>> = OnceLock::new();
|
||||
|
||||
let runtime_cell = DEFAULT_RUNTIME.get_or_init(|| Mutex::new(None));
|
||||
if let Some(runtime) = runtime_cell.lock().clone() {
|
||||
return Some(runtime);
|
||||
}
|
||||
|
||||
let _guard = DEFAULT_RUNTIME_BOOTSTRAP
|
||||
.get_or_init(|| AsyncMutex::new(()))
|
||||
.lock()
|
||||
.await;
|
||||
|
||||
if let Some(runtime) = runtime_cell.lock().clone() {
|
||||
return Some(runtime);
|
||||
}
|
||||
|
||||
let runtime = load_default_runtime().await.ok().map(Arc::new)?;
|
||||
*runtime_cell.lock() = Some(runtime.clone());
|
||||
Some(runtime)
|
||||
}
|
||||
|
||||
pub(crate) async fn warm_default_bundle() -> Option<Arc<RelexRuntime>> {
|
||||
runtime(DEFAULT_GLINER_RELEX_MODEL).await
|
||||
}
|
||||
|
||||
impl RelexRuntime {
|
||||
pub(crate) fn extract(
|
||||
&self,
|
||||
text: &str,
|
||||
entity_threshold: f32,
|
||||
relation_threshold: f32,
|
||||
) -> Result<RelexExtraction> {
|
||||
let tokens = split_whitespace_tokens(text);
|
||||
if tokens.is_empty() {
|
||||
return Ok(RelexExtraction::default());
|
||||
}
|
||||
|
||||
let prompt = encode_prompt(
|
||||
&self.tokenizer,
|
||||
&self.config,
|
||||
&tokens,
|
||||
ENTITY_LABELS,
|
||||
RELATION_LABELS,
|
||||
)?;
|
||||
let (span_idx, span_mask) = make_spans_tensors(prompt.num_words, self.config.max_width);
|
||||
|
||||
let inputs = ort::inputs! {
|
||||
"input_ids" => Tensor::from_array(prompt.input_ids.clone())?,
|
||||
"attention_mask" => Tensor::from_array(prompt.attention_mask.clone())?,
|
||||
"words_mask" => Tensor::from_array(prompt.words_mask.clone())?,
|
||||
"text_lengths" => Tensor::from_array(prompt.text_lengths.clone())?,
|
||||
"span_idx" => Tensor::from_array(span_idx.clone())?,
|
||||
"span_mask" => Tensor::from_array(span_mask.clone())?,
|
||||
};
|
||||
|
||||
let mut session = self.session.lock();
|
||||
let outputs = session.run(inputs)?;
|
||||
let logits = extract_f32_4d(outputs.get("logits").context("missing logits output")?)?;
|
||||
|
||||
let spans = decode_entity_spans(
|
||||
&logits,
|
||||
text,
|
||||
&tokens,
|
||||
ENTITY_LABELS,
|
||||
self.config.max_width,
|
||||
entity_threshold,
|
||||
);
|
||||
let entities = spans
|
||||
.iter()
|
||||
.map(|span| RelexEntity {
|
||||
name: span.text.clone(),
|
||||
entity_type: normalize_entity_label(&span.class_name).to_string(),
|
||||
confidence: span.probability,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut relations = Vec::new();
|
||||
let rel_idx = outputs.get("rel_idx").map(extract_i64_3d).transpose()?;
|
||||
let rel_logits = outputs.get("rel_logits").map(extract_f32_3d).transpose()?;
|
||||
let rel_mask = outputs.get("rel_mask").map(extract_bool_2d).transpose()?;
|
||||
|
||||
if let (Some(rel_idx), Some(rel_logits), Some(rel_mask)) = (rel_idx, rel_logits, rel_mask) {
|
||||
let rel_pairs = rel_idx.index_axis(ndarray::Axis(0), 0);
|
||||
let rel_scores = rel_logits.index_axis(ndarray::Axis(0), 0);
|
||||
let rel_valid = rel_mask.index_axis(ndarray::Axis(0), 0);
|
||||
|
||||
for pair_idx in 0..rel_valid.shape()[0] {
|
||||
if !rel_valid[[pair_idx]] {
|
||||
continue;
|
||||
}
|
||||
let head_idx = rel_pairs[[pair_idx, 0]];
|
||||
let tail_idx = rel_pairs[[pair_idx, 1]];
|
||||
if head_idx < 0 || tail_idx < 0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
let head_idx = head_idx as usize;
|
||||
let tail_idx = tail_idx as usize;
|
||||
if head_idx >= spans.len() || tail_idx >= spans.len() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let head = &spans[head_idx];
|
||||
let tail = &spans[tail_idx];
|
||||
let class_count = rel_scores.shape()[1].min(RELATION_LABELS.len());
|
||||
for class_idx in 0..class_count {
|
||||
let probability = sigmoid(rel_scores[[pair_idx, class_idx]]);
|
||||
if probability < relation_threshold {
|
||||
continue;
|
||||
}
|
||||
relations.push(RelexRelation {
|
||||
subject: head.text.clone(),
|
||||
subject_type: normalize_entity_label(&head.class_name).to_string(),
|
||||
predicate: normalize_relation_label(RELATION_LABELS[class_idx]),
|
||||
object: tail.text.clone(),
|
||||
object_type: normalize_entity_label(&tail.class_name).to_string(),
|
||||
confidence: probability,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(RelexExtraction {
|
||||
entities,
|
||||
relations,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn uses_default_bundle(model_name: &str) -> bool {
|
||||
model_name.trim().is_empty()
|
||||
|| model_name == DEFAULT_GLINER_RELEX_MODEL
|
||||
|| model_name == default_bundle_dir().to_string_lossy()
|
||||
|| model_name == default_managed_bundle_dir().to_string_lossy()
|
||||
}
|
||||
|
||||
async fn load_default_runtime() -> Result<RelexRuntime> {
|
||||
let bundle_dir = resolve_bundle_dir(DEFAULT_GLINER_RELEX_MODEL)
|
||||
.await
|
||||
.ok_or_else(|| anyhow!("relex bundle directory not found"))?;
|
||||
load_runtime_from_bundle_dir(&bundle_dir)
|
||||
}
|
||||
|
||||
async fn load_runtime_for_model(model_name: &str) -> Result<Arc<RelexRuntime>> {
|
||||
let bundle_dir = resolve_bundle_dir(model_name)
|
||||
.await
|
||||
.ok_or_else(|| anyhow!("relex bundle directory not found"))?;
|
||||
load_runtime_from_bundle_dir(&bundle_dir).map(Arc::new)
|
||||
}
|
||||
|
||||
fn load_runtime_from_bundle_dir(bundle_dir: &Path) -> Result<RelexRuntime> {
|
||||
ensure_ort_dylib_path(bundle_dir);
|
||||
|
||||
let tokenizer_path = bundle_dir.join(TOKENIZER_FILE_NAME);
|
||||
let model_path = model_file_path(bundle_dir)
|
||||
.ok_or_else(|| anyhow!("model file not found in {}", bundle_dir.display()))?;
|
||||
let config_path = bundle_dir.join(GLINER_CONFIG_FILE_NAME);
|
||||
|
||||
let tokenizer = Tokenizer::from_file(&tokenizer_path).map_err(|err| {
|
||||
anyhow!(
|
||||
"failed to load tokenizer from {}: {err}",
|
||||
tokenizer_path.display()
|
||||
)
|
||||
})?;
|
||||
let config = serde_json::from_slice::<RelexBundleConfig>(
|
||||
&std::fs::read(&config_path)
|
||||
.with_context(|| format!("failed to read {}", config_path.display()))?,
|
||||
)
|
||||
.with_context(|| format!("failed to parse {}", config_path.display()))?;
|
||||
|
||||
let session = Session::builder()?
|
||||
.with_optimization_level(GraphOptimizationLevel::Level3)?
|
||||
.commit_from_file(&model_path)
|
||||
.with_context(|| format!("failed to load model {}", model_path.display()))?;
|
||||
|
||||
Ok(RelexRuntime {
|
||||
tokenizer,
|
||||
session: Mutex::new(session),
|
||||
config,
|
||||
})
|
||||
}
|
||||
|
||||
async fn resolve_bundle_dir(model_name: &str) -> Option<PathBuf> {
|
||||
if let Ok(path) = env::var("OPENHUMAN_GLINER_RELEX_DIR") {
|
||||
let bundle_dir = PathBuf::from(path);
|
||||
if bundle_complete(&bundle_dir) {
|
||||
return Some(bundle_dir);
|
||||
}
|
||||
}
|
||||
|
||||
let requested = PathBuf::from(model_name);
|
||||
if requested.is_absolute() || model_name.contains('/') || model_name.contains('\\') {
|
||||
if requested.is_dir() && bundle_complete(&requested) {
|
||||
return Some(requested);
|
||||
}
|
||||
if requested.is_file()
|
||||
&& requested
|
||||
.file_name()
|
||||
.is_some_and(|name| name == FALLBACK_MODEL_FILE_NAME || name == MODEL_FILE_NAME)
|
||||
{
|
||||
return requested.parent().map(Path::to_path_buf);
|
||||
}
|
||||
}
|
||||
|
||||
let managed_dir = default_managed_bundle_dir();
|
||||
if managed_bundle_complete(&managed_dir) {
|
||||
return Some(managed_dir);
|
||||
}
|
||||
|
||||
let bundle_dir = default_bundle_dir();
|
||||
if bundle_complete(&bundle_dir) {
|
||||
return Some(bundle_dir);
|
||||
}
|
||||
|
||||
if uses_default_bundle(model_name) {
|
||||
if ensure_managed_bundle(&managed_dir).await.is_ok() && bundle_complete(&managed_dir) {
|
||||
return Some(managed_dir);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn default_bundle_dir() -> PathBuf {
|
||||
Path::new(env!("CARGO_MANIFEST_DIR"))
|
||||
.parent()
|
||||
.unwrap_or_else(|| Path::new(env!("CARGO_MANIFEST_DIR")))
|
||||
.join(DEFAULT_EXPORTED_RELEX_DIR)
|
||||
}
|
||||
|
||||
fn default_managed_bundle_dir() -> PathBuf {
|
||||
if let Ok(path) = env::var("OPENHUMAN_GLINER_RELEX_CACHE_DIR") {
|
||||
return PathBuf::from(path);
|
||||
}
|
||||
|
||||
directories::UserDirs::new()
|
||||
.map(|dirs| dirs.home_dir().join(DEFAULT_MANAGED_RELEX_DIR))
|
||||
.unwrap_or_else(|| PathBuf::from(DEFAULT_MANAGED_RELEX_DIR))
|
||||
}
|
||||
|
||||
fn bundle_complete(bundle_dir: &Path) -> bool {
|
||||
bundle_dir.join(TOKENIZER_FILE_NAME).exists()
|
||||
&& bundle_dir.join(GLINER_CONFIG_FILE_NAME).exists()
|
||||
&& model_file_path(bundle_dir).is_some()
|
||||
}
|
||||
|
||||
fn managed_bundle_complete(bundle_dir: &Path) -> bool {
|
||||
bundle_complete(bundle_dir)
|
||||
&& PLATFORM_BUNDLE_ASSETS
|
||||
.iter()
|
||||
.all(|asset| bundle_dir.join(asset.local_name).exists())
|
||||
}
|
||||
|
||||
fn model_file_path(bundle_dir: &Path) -> Option<PathBuf> {
|
||||
for file_name in [MODEL_FILE_NAME, FALLBACK_MODEL_FILE_NAME] {
|
||||
let candidate = bundle_dir.join(file_name);
|
||||
if candidate.exists() {
|
||||
return Some(candidate);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn ensure_ort_dylib_path(bundle_dir: &Path) {
|
||||
if env::var_os("ORT_DYLIB_PATH").is_some() {
|
||||
return;
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "windows", target_os = "macos"))]
|
||||
{
|
||||
let bundled = bundle_dir.join(ORT_DYLIB_FILE_NAME);
|
||||
if bundled.exists() {
|
||||
env::set_var("ORT_DYLIB_PATH", bundled);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(lib_path) = env::var_os("ORT_LIB_LOCATION") {
|
||||
let candidate = PathBuf::from(lib_path);
|
||||
if candidate.is_file() {
|
||||
env::set_var("ORT_DYLIB_PATH", candidate);
|
||||
return;
|
||||
}
|
||||
let runtime_lib = candidate.join(ORT_DYLIB_FILE_NAME);
|
||||
if runtime_lib.exists() {
|
||||
env::set_var("ORT_DYLIB_PATH", runtime_lib);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
let Some(user_profile) = env::var_os("USERPROFILE") else {
|
||||
return;
|
||||
};
|
||||
let pattern = PathBuf::from(user_profile)
|
||||
.join("AppData/Local/uv/cache/archive-v0/*/onnxruntime/capi/onnxruntime.dll")
|
||||
.to_string_lossy()
|
||||
.replace('\\', "/");
|
||||
if let Ok(paths) = glob(&pattern) {
|
||||
for candidate in paths.flatten() {
|
||||
if candidate.exists() {
|
||||
env::set_var("ORT_DYLIB_PATH", &candidate);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
for candidate in [
|
||||
"/usr/lib/x86_64-linux-gnu/libonnxruntime.so",
|
||||
"/usr/local/lib/libonnxruntime.so",
|
||||
"/usr/lib/libonnxruntime.so",
|
||||
] {
|
||||
let candidate = PathBuf::from(candidate);
|
||||
if candidate.exists() {
|
||||
env::set_var("ORT_DYLIB_PATH", &candidate);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn ensure_managed_bundle(bundle_dir: &Path) -> Result<()> {
|
||||
static MANAGED_BUNDLE_BOOTSTRAP: OnceLock<AsyncMutex<()>> = OnceLock::new();
|
||||
let _guard = MANAGED_BUNDLE_BOOTSTRAP
|
||||
.get_or_init(|| AsyncMutex::new(()))
|
||||
.lock()
|
||||
.await;
|
||||
|
||||
if managed_bundle_complete(bundle_dir) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
tokio::fs::create_dir_all(bundle_dir)
|
||||
.await
|
||||
.with_context(|| format!("failed to create {}", bundle_dir.display()))?;
|
||||
|
||||
let client = crate::openhuman::config::build_runtime_proxy_client("memory.relex");
|
||||
let base_url = env::var("OPENHUMAN_GLINER_RELEX_BASE_URL")
|
||||
.ok()
|
||||
.filter(|value| !value.trim().is_empty())
|
||||
.unwrap_or_else(|| DEFAULT_RELEX_RELEASE_BASE_URL.to_string());
|
||||
|
||||
for asset in CORE_BUNDLE_ASSETS
|
||||
.iter()
|
||||
.chain(PLATFORM_BUNDLE_ASSETS.iter())
|
||||
{
|
||||
let target = bundle_dir.join(asset.local_name);
|
||||
download_asset_if_needed(&client, &base_url, asset, &target).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn download_asset_if_needed(
|
||||
client: &reqwest::Client,
|
||||
base_url: &str,
|
||||
asset: &BundleAsset,
|
||||
target: &Path,
|
||||
) -> Result<()> {
|
||||
if file_matches_sha256(target, asset.sha256).await? {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let url = format!(
|
||||
"{}/{}",
|
||||
base_url.trim_end_matches('/'),
|
||||
asset.remote_name.trim_start_matches('/')
|
||||
);
|
||||
let tmp = target.with_extension("download");
|
||||
if let Some(parent) = target.parent() {
|
||||
tokio::fs::create_dir_all(parent)
|
||||
.await
|
||||
.with_context(|| format!("failed to create {}", parent.display()))?;
|
||||
}
|
||||
|
||||
let response = client
|
||||
.get(&url)
|
||||
.send()
|
||||
.await
|
||||
.with_context(|| format!("failed to start relex asset download {url}"))?;
|
||||
if !response.status().is_success() {
|
||||
return Err(anyhow!(
|
||||
"failed to download relex asset {}, status {}",
|
||||
asset.remote_name,
|
||||
response.status()
|
||||
));
|
||||
}
|
||||
|
||||
let mut file = tokio::fs::File::create(&tmp)
|
||||
.await
|
||||
.with_context(|| format!("failed to create {}", tmp.display()))?;
|
||||
let mut stream = response.bytes_stream();
|
||||
while let Some(chunk) = stream
|
||||
.try_next()
|
||||
.await
|
||||
.with_context(|| format!("download stream error for {}", asset.remote_name))?
|
||||
{
|
||||
file.write_all(&chunk)
|
||||
.await
|
||||
.with_context(|| format!("failed writing {}", tmp.display()))?;
|
||||
}
|
||||
file.flush()
|
||||
.await
|
||||
.with_context(|| format!("failed flushing {}", tmp.display()))?;
|
||||
|
||||
if !asset.sha256.is_empty() && !file_matches_sha256(&tmp, asset.sha256).await? {
|
||||
let _ = tokio::fs::remove_file(&tmp).await;
|
||||
return Err(anyhow!(
|
||||
"checksum mismatch for downloaded relex asset {}",
|
||||
asset.remote_name
|
||||
));
|
||||
}
|
||||
|
||||
tokio::fs::rename(&tmp, target)
|
||||
.await
|
||||
.with_context(|| format!("failed to finalize {}", target.display()))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn file_matches_sha256(path: &Path, expected: &str) -> Result<bool> {
|
||||
if expected.is_empty() {
|
||||
return Ok(path.exists());
|
||||
}
|
||||
if !path.exists() {
|
||||
return Ok(false);
|
||||
}
|
||||
let bytes = tokio::fs::read(path)
|
||||
.await
|
||||
.with_context(|| format!("failed to read {}", path.display()))?;
|
||||
let actual = hex::encode(Sha256::digest(bytes));
|
||||
Ok(actual.eq_ignore_ascii_case(expected))
|
||||
}
|
||||
|
||||
fn split_whitespace_tokens(text: &str) -> Vec<TokenSlice> {
|
||||
let mut tokens = Vec::new();
|
||||
let mut current_start: Option<usize> = None;
|
||||
|
||||
for (idx, ch) in text.char_indices() {
|
||||
if ch.is_whitespace() {
|
||||
if let Some(start) = current_start.take() {
|
||||
tokens.push(TokenSlice {
|
||||
start,
|
||||
end: idx,
|
||||
text: text[start..idx].to_string(),
|
||||
});
|
||||
}
|
||||
} else if current_start.is_none() {
|
||||
current_start = Some(idx);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(start) = current_start {
|
||||
tokens.push(TokenSlice {
|
||||
start,
|
||||
end: text.len(),
|
||||
text: text[start..].to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
tokens
|
||||
}
|
||||
|
||||
fn encode_prompt(
|
||||
tokenizer: &Tokenizer,
|
||||
config: &RelexBundleConfig,
|
||||
tokens: &[TokenSlice],
|
||||
entity_labels: &[&str],
|
||||
relation_labels: &[&str],
|
||||
) -> Result<PromptBatch> {
|
||||
let mut prompt_words = Vec::new();
|
||||
for label in entity_labels {
|
||||
prompt_words.push(config.ent_token.clone());
|
||||
prompt_words.push((*label).to_string());
|
||||
}
|
||||
prompt_words.push(config.sep_token.clone());
|
||||
for label in relation_labels {
|
||||
prompt_words.push(config.rel_token.clone());
|
||||
prompt_words.push((*label).to_string());
|
||||
}
|
||||
prompt_words.push(config.sep_token.clone());
|
||||
|
||||
let mut words = prompt_words.clone();
|
||||
words.extend(tokens.iter().map(|token| token.text.clone()));
|
||||
|
||||
let mut encoded_words = Vec::with_capacity(words.len());
|
||||
let mut total_tokens = 2usize;
|
||||
let mut prompt_subtokens = 0usize;
|
||||
|
||||
for (index, word) in words.iter().enumerate() {
|
||||
let encoding = tokenizer
|
||||
.encode(word.as_str(), false)
|
||||
.map_err(|err| anyhow!("failed to tokenize prompt word `{word}`: {err}"))?;
|
||||
let ids = encoding.get_ids().to_vec();
|
||||
if index < prompt_words.len() {
|
||||
prompt_subtokens += ids.len();
|
||||
}
|
||||
total_tokens += ids.len();
|
||||
encoded_words.push(ids);
|
||||
}
|
||||
|
||||
let text_offset = prompt_subtokens + 1;
|
||||
let mut input_ids = vec![0_i64; total_tokens];
|
||||
let mut attention_mask = vec![0_i64; total_tokens];
|
||||
let mut words_mask = vec![0_i64; total_tokens];
|
||||
|
||||
let mut cursor = 0usize;
|
||||
input_ids[cursor] = 1;
|
||||
attention_mask[cursor] = 1;
|
||||
cursor += 1;
|
||||
|
||||
let mut word_id = 0_i64;
|
||||
for ids in encoded_words {
|
||||
for (token_index, token_id) in ids.iter().enumerate() {
|
||||
input_ids[cursor] = i64::from(*token_id);
|
||||
attention_mask[cursor] = 1;
|
||||
if cursor >= text_offset && token_index == 0 {
|
||||
words_mask[cursor] = word_id;
|
||||
}
|
||||
cursor += 1;
|
||||
}
|
||||
if cursor >= text_offset {
|
||||
word_id += 1;
|
||||
}
|
||||
}
|
||||
|
||||
input_ids[cursor] = 2;
|
||||
attention_mask[cursor] = 1;
|
||||
|
||||
Ok(PromptBatch {
|
||||
input_ids: Array2::from_shape_vec((1, total_tokens), input_ids)?,
|
||||
attention_mask: Array2::from_shape_vec((1, total_tokens), attention_mask)?,
|
||||
words_mask: Array2::from_shape_vec((1, total_tokens), words_mask)?,
|
||||
text_lengths: Array2::from_shape_vec((1, 1), vec![tokens.len() as i64])?,
|
||||
num_words: tokens.len(),
|
||||
})
|
||||
}
|
||||
|
||||
fn make_spans_tensors(num_words: usize, max_width: usize) -> (Array3<i64>, Array2<bool>) {
|
||||
let num_spans = num_words * max_width;
|
||||
let mut span_idx = Array3::<i64>::zeros((1, num_spans, 2));
|
||||
let mut span_mask = Array2::<bool>::from_elem((1, num_spans), false);
|
||||
|
||||
for start in 0..num_words {
|
||||
let actual_max_width = max_width.min(num_words.saturating_sub(start));
|
||||
for width in 0..actual_max_width {
|
||||
let dim = start * max_width + width;
|
||||
span_idx[[0, dim, 0]] = start as i64;
|
||||
span_idx[[0, dim, 1]] = (start + width) as i64;
|
||||
span_mask[[0, dim]] = true;
|
||||
}
|
||||
}
|
||||
|
||||
(span_idx, span_mask)
|
||||
}
|
||||
|
||||
fn decode_entity_spans(
|
||||
logits: &Array4<f32>,
|
||||
text: &str,
|
||||
tokens: &[TokenSlice],
|
||||
entity_labels: &[&str],
|
||||
max_width: usize,
|
||||
threshold: f32,
|
||||
) -> Vec<DecodedSpan> {
|
||||
let mut spans = Vec::new();
|
||||
let num_words = tokens.len();
|
||||
let width_count = logits.shape().get(2).copied().unwrap_or_default() as usize;
|
||||
let class_count = logits.shape().get(3).copied().unwrap_or_default() as usize;
|
||||
|
||||
for start in 0..num_words {
|
||||
let actual_max_width = max_width
|
||||
.min(width_count)
|
||||
.min(num_words.saturating_sub(start));
|
||||
for width in 0..actual_max_width {
|
||||
let end_word = start + width;
|
||||
if end_word >= num_words {
|
||||
continue;
|
||||
}
|
||||
for class_idx in 0..class_count.min(entity_labels.len()) {
|
||||
let probability = sigmoid(logits[[0, start, width, class_idx]]);
|
||||
if probability < threshold {
|
||||
continue;
|
||||
}
|
||||
let start_offset = tokens[start].start;
|
||||
let end_offset = tokens[end_word].end;
|
||||
spans.push(DecodedSpan {
|
||||
start: start_offset,
|
||||
end: end_offset,
|
||||
text: text[start_offset..end_offset].to_string(),
|
||||
class_name: entity_labels[class_idx].to_string(),
|
||||
probability,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
spans.sort_unstable_by_key(|span| (span.start, span.end));
|
||||
greedy_filter(spans)
|
||||
}
|
||||
|
||||
fn extract_f32_4d(value: &ort::value::DynValue) -> Result<Array4<f32>> {
|
||||
let (shape, data) = value.try_extract_tensor::<f32>()?;
|
||||
Ok(Array::from_shape_vec(shape.to_ixdyn(), data.to_vec())?.into_dimensionality::<Ix4>()?)
|
||||
}
|
||||
|
||||
fn extract_f32_3d(value: &ort::value::DynValue) -> Result<Array3<f32>> {
|
||||
let (shape, data) = value.try_extract_tensor::<f32>()?;
|
||||
Ok(Array::from_shape_vec(shape.to_ixdyn(), data.to_vec())?.into_dimensionality::<Ix3>()?)
|
||||
}
|
||||
|
||||
fn extract_i64_3d(value: &ort::value::DynValue) -> Result<Array3<i64>> {
|
||||
let (shape, data) = value.try_extract_tensor::<i64>()?;
|
||||
Ok(Array::from_shape_vec(shape.to_ixdyn(), data.to_vec())?.into_dimensionality::<Ix3>()?)
|
||||
}
|
||||
|
||||
fn extract_bool_2d(value: &ort::value::DynValue) -> Result<Array2<bool>> {
|
||||
let (shape, data) = value.try_extract_tensor::<bool>()?;
|
||||
Ok(Array::from_shape_vec(shape.to_ixdyn(), data.to_vec())?.into_dimensionality::<Ix2>()?)
|
||||
}
|
||||
|
||||
fn greedy_filter(spans: Vec<DecodedSpan>) -> Vec<DecodedSpan> {
|
||||
if spans.is_empty() {
|
||||
return spans;
|
||||
}
|
||||
|
||||
let mut selected = Vec::with_capacity(spans.len());
|
||||
let mut previous = 0usize;
|
||||
let mut next = 1usize;
|
||||
|
||||
while next < spans.len() {
|
||||
let left = &spans[previous];
|
||||
let right = &spans[next];
|
||||
if disjoint(left, right) {
|
||||
selected.push(left.clone());
|
||||
previous = next;
|
||||
} else if left.probability < right.probability {
|
||||
previous = next;
|
||||
}
|
||||
next += 1;
|
||||
}
|
||||
|
||||
selected.push(spans[previous].clone());
|
||||
selected
|
||||
}
|
||||
|
||||
fn disjoint(left: &DecodedSpan, right: &DecodedSpan) -> bool {
|
||||
right.start >= left.end || right.end <= left.start
|
||||
}
|
||||
|
||||
fn normalize_entity_label(label: &str) -> &'static str {
|
||||
match label {
|
||||
"person" => "PERSON",
|
||||
"organization" => "ORGANIZATION",
|
||||
"project" => "PROJECT",
|
||||
"product" => "PRODUCT",
|
||||
"tool" => "TOOL",
|
||||
"topic" => "TOPIC",
|
||||
"work item" => "WORK_ITEM",
|
||||
"mode" => "MODE",
|
||||
"place" => "PLACE",
|
||||
"room" => "ROOM",
|
||||
"date" => "DATE",
|
||||
_ => "TOPIC",
|
||||
}
|
||||
}
|
||||
|
||||
fn normalize_relation_label(label: &str) -> String {
|
||||
match label {
|
||||
"owns" => "owns".to_string(),
|
||||
"uses" => "uses".to_string(),
|
||||
"works on" => "works_on".to_string(),
|
||||
"responsible for" => "responsible_for".to_string(),
|
||||
"reviews" => "reviews".to_string(),
|
||||
"works for" => "works_for".to_string(),
|
||||
"depends on" => "depends_on".to_string(),
|
||||
"prefers" => "prefers".to_string(),
|
||||
"has deadline" => "has_deadline".to_string(),
|
||||
"communicates with" => "communicates_with".to_string(),
|
||||
"investigates" => "investigates".to_string(),
|
||||
"evaluates" => "evaluates".to_string(),
|
||||
"north of" => "north_of".to_string(),
|
||||
"south of" => "south_of".to_string(),
|
||||
"east of" => "east_of".to_string(),
|
||||
"west of" => "west_of".to_string(),
|
||||
"avoids" => "avoids".to_string(),
|
||||
_ => label.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
fn sigmoid(value: f32) -> f32 {
|
||||
1.0 / (1.0 + (-value).exp())
|
||||
}
|
||||
@@ -3,6 +3,7 @@ use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::openhuman::memory::embeddings::{self, EmbeddingProvider};
|
||||
use crate::openhuman::memory::ingestion::{MemoryIngestionRequest, MemoryIngestionResult};
|
||||
use crate::openhuman::memory::store::types::{
|
||||
NamespaceDocumentInput, NamespaceMemoryHit, NamespaceRetrievalContext,
|
||||
};
|
||||
@@ -45,6 +46,13 @@ impl MemoryClient {
|
||||
self.inner.upsert_document(input).await
|
||||
}
|
||||
|
||||
pub async fn ingest_doc(
|
||||
&self,
|
||||
request: MemoryIngestionRequest,
|
||||
) -> Result<MemoryIngestionResult, String> {
|
||||
self.inner.ingest_document(request).await
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn store_skill_sync(
|
||||
&self,
|
||||
|
||||
Vendored
+25
@@ -0,0 +1,25 @@
|
||||
# Ingestion Fixtures
|
||||
|
||||
These fixtures are plain-text source samples for memory ingestion tests.
|
||||
|
||||
They are intentionally written as raw strings rather than strongly typed JSON so
|
||||
future ingestion tests can exercise the same path used for real imported text.
|
||||
|
||||
Current fixtures:
|
||||
|
||||
- `gmail_thread_example.txt`
|
||||
Gmail-like thread with headers, quoted replies, task ownership, dates, and
|
||||
durable user/project facts.
|
||||
|
||||
- `notion_page_example.txt`
|
||||
Notion-like project page with sections, bullet lists, decisions, owners,
|
||||
milestones, and operating notes.
|
||||
|
||||
Suggested test usage:
|
||||
|
||||
- Load fixture text as a string.
|
||||
- Pass it through chunking and extraction.
|
||||
- Assert that ingestion can recover:
|
||||
- entities such as people, tools, projects, and dates
|
||||
- relations such as ownership, dependencies, and responsibilities
|
||||
- durable memory facts such as preferences, deadlines, and decisions
|
||||
@@ -0,0 +1,85 @@
|
||||
From: Sanil Jain <sanil@tinyhumans.ai>
|
||||
To: Asha Mehta <asha@tinyhumans.ai>, Ravi Kulkarni <ravi@tinyhumans.ai>
|
||||
Cc: OpenHuman Core <core@tinyhumans.ai>
|
||||
Subject: Re: Memory integration plan for OpenHuman desktop
|
||||
Date: Tue, 12 Mar 2026 09:14:00 +0530
|
||||
Thread-Id: memory-integration-2026-03
|
||||
|
||||
Hi Asha and Ravi,
|
||||
|
||||
Quick summary after today's sync:
|
||||
|
||||
1. We should keep JSON-RPC as the transport for the desktop core.
|
||||
2. The memory layer in the Rust core should use namespace as the main scope key.
|
||||
3. We do not need user_id in the local storage contract for the current desktop runtime.
|
||||
4. The frontend can adapt to richer result payloads as long as they still arrive inside JSON-RPC result.
|
||||
|
||||
Current work items:
|
||||
- Ravi owns the Rust memory API alignment for list, delete, query, and recall.
|
||||
- Asha owns the Neocortex v2 ingestion experiment using the GLiNER relex model.
|
||||
- Sanil will review response models so they follow the Neocortex API style.
|
||||
|
||||
Important project facts:
|
||||
- Project name: OpenHuman
|
||||
- Subproject: memory-layer-completion
|
||||
- Target milestone: March 22, 2026
|
||||
- Preferred embedding model for local experiments: text-embedding-3-small
|
||||
- Preferred extraction mode to try first: sentence
|
||||
|
||||
Known constraints:
|
||||
- The desktop app is local-first.
|
||||
- Core RPC currently binds to localhost only.
|
||||
- We should avoid introducing user_id into every memory request unless we later support multi-user or remote runtimes.
|
||||
|
||||
Action items:
|
||||
- Ravi: draft typed request/response structs for memory.query_namespace and memory.recall_namespace by Friday.
|
||||
- Asha: prepare two ingestion fixtures, one Gmail-like and one Notion-like, with enough structure to test entity and relation extraction.
|
||||
- Sanil: decide whether memory.init becomes a no-op compatibility method or is removed from the frontend wrappers.
|
||||
|
||||
One durable preference to remember:
|
||||
I prefer keeping the memory core simple first and delaying graph traversal until after ingestion and recall are stable.
|
||||
|
||||
Thanks,
|
||||
Sanil
|
||||
|
||||
---
|
||||
|
||||
From: Asha Mehta <asha@tinyhumans.ai>
|
||||
To: Sanil Jain <sanil@tinyhumans.ai>, Ravi Kulkarni <ravi@tinyhumans.ai>
|
||||
Subject: Re: Memory integration plan for OpenHuman desktop
|
||||
Date: Tue, 12 Mar 2026 08:41:00 +0530
|
||||
|
||||
Agreed.
|
||||
|
||||
For the Neocortex donor path, I reviewed the neocortex_v2 extractor again:
|
||||
- It uses a single GLiNER relex model.
|
||||
- It supports sentence-level and chunk-level extraction.
|
||||
- It adds recipient and spatial relation heuristics.
|
||||
|
||||
I think we should preserve those heuristics when we port the ingestion flow into OpenHuman.
|
||||
|
||||
Also, please record this:
|
||||
- Ravi prefers narrower worker ownership to avoid merge conflicts.
|
||||
- I prefer evaluation fixtures that include dates, owners, and product decisions.
|
||||
|
||||
Regards,
|
||||
Asha
|
||||
|
||||
---
|
||||
|
||||
From: Ravi Kulkarni <ravi@tinyhumans.ai>
|
||||
To: Sanil Jain <sanil@tinyhumans.ai>, Asha Mehta <asha@tinyhumans.ai>
|
||||
Subject: Re: Memory integration plan for OpenHuman desktop
|
||||
Date: Tue, 12 Mar 2026 08:09:00 +0530
|
||||
|
||||
One more note before I start:
|
||||
|
||||
- I will treat namespace as mandatory for memory query and recall.
|
||||
- I will treat memory file APIs as optional until the core contract settles.
|
||||
- I want the Gmail importer to preserve subject, sender, recipients, and sent_at metadata.
|
||||
|
||||
Dependency note:
|
||||
- The frontend wrapper work depends on finalizing the result shape from the Rust core.
|
||||
- The ingestion evaluation can run in parallel once the storage mapping is clear.
|
||||
|
||||
Ravi
|
||||
+132
@@ -0,0 +1,132 @@
|
||||
# OpenHuman Memory Layer Roadmap
|
||||
|
||||
Workspace: tinyhumans / engineering
|
||||
Owner: Sanil Jain
|
||||
Last edited: 2026-03-14
|
||||
Status: In Progress
|
||||
Tags: memory, rust-core, ingestion, neocortex
|
||||
|
||||
## Overview
|
||||
|
||||
This page tracks the work needed to complete the OpenHuman memory layer in the Rust core.
|
||||
|
||||
The current direction is:
|
||||
- keep JSON-RPC as the transport
|
||||
- use namespace as the storage and retrieval scope key
|
||||
- avoid requiring user_id in local memory APIs
|
||||
- adopt Neocortex-style typed request and response models inside JSON-RPC result
|
||||
|
||||
## Core Decisions
|
||||
|
||||
### Decision 1: Transport
|
||||
We will keep JSON-RPC 2.0 as the transport for the desktop core.
|
||||
|
||||
### Decision 2: Scope
|
||||
Namespace is the primary logical partition for local memory.
|
||||
Examples:
|
||||
- conversations
|
||||
- conscious
|
||||
- skill-gmail
|
||||
- skill-notion
|
||||
|
||||
### Decision 3: Ingestion donor
|
||||
We will use neocortex_v2 as the donor path for better memory extraction.
|
||||
Important features to preserve:
|
||||
- joint entity and relation extraction
|
||||
- sentence-level extraction option
|
||||
- relation constraints
|
||||
- recipient relation synthesis
|
||||
- spatial relation synthesis
|
||||
|
||||
## Deliverables
|
||||
|
||||
### Thread 0: Contract
|
||||
Owner: Sanil Jain
|
||||
Deliverables:
|
||||
- final memory RPC names
|
||||
- request and response model table
|
||||
- decision on memory.init
|
||||
- decision on file APIs
|
||||
|
||||
### Thread 1: Core Memory Domain
|
||||
Owner: Ravi Kulkarni
|
||||
Deliverables:
|
||||
- stable document storage semantics
|
||||
- stable namespace list and document list behavior
|
||||
- stable query and recall behavior
|
||||
- clarified graph and KV scope
|
||||
|
||||
### Thread 3: Ingestion
|
||||
Owner: Asha Mehta
|
||||
Deliverables:
|
||||
- extraction adapter plan
|
||||
- mapping into memory_docs, vector_chunks, and graph_namespace
|
||||
- sample-data evaluation
|
||||
|
||||
## Current Data Model Notes
|
||||
|
||||
### Documents
|
||||
Documents should preserve:
|
||||
- document_id
|
||||
- namespace
|
||||
- title
|
||||
- content
|
||||
- metadata
|
||||
- created_at
|
||||
- updated_at
|
||||
|
||||
### Graph facts
|
||||
Graph storage should capture facts like:
|
||||
- Ravi works_on memory-layer-completion
|
||||
- Asha evaluates neocortex_v2
|
||||
- OpenHuman uses JSON-RPC
|
||||
- memory-layer-completion depends_on API-contract
|
||||
|
||||
### Durable preferences
|
||||
Examples of durable user or team memory:
|
||||
- Sanil prefers core-first delivery over UI-first delivery.
|
||||
- Ravi prefers strict ownership boundaries for parallel agents.
|
||||
- Asha prefers evaluation fixtures with realistic semi-structured text.
|
||||
|
||||
## Milestones
|
||||
|
||||
### Milestone A
|
||||
Name: Core contract locked
|
||||
Due date: 2026-03-18
|
||||
Success criteria:
|
||||
- final RPC method names agreed
|
||||
- JSON-RPC transport explicitly retained
|
||||
- response envelope strategy documented
|
||||
|
||||
### Milestone B
|
||||
Name: Core memory operational
|
||||
Due date: 2026-03-22
|
||||
Success criteria:
|
||||
- list, delete, query, and recall work in Rust
|
||||
- stable outputs exist for frontend adaptation
|
||||
|
||||
### Milestone C
|
||||
Name: Ingestion quality baseline
|
||||
Due date: 2026-03-26
|
||||
Success criteria:
|
||||
- Gmail-like and Notion-like fixtures ingest successfully
|
||||
- extracted entities and relations are reviewed manually
|
||||
|
||||
## Risks
|
||||
|
||||
- The frontend currently expects raw values for some memory methods.
|
||||
- neocortex_v2 preserves duplicate relation evidence, while OpenHuman may prefer aggregation.
|
||||
- If we do not define request and response models early, parallel agents may diverge.
|
||||
|
||||
## Testing Notes
|
||||
|
||||
Use these sample source types for ingestion tests:
|
||||
- Gmail thread as raw imported message text
|
||||
- Notion page as raw exported document text
|
||||
|
||||
Assertions should check for:
|
||||
- person names
|
||||
- project names
|
||||
- ownership relations
|
||||
- deadlines and dates
|
||||
- decisions and preferences
|
||||
Reference in New Issue
Block a user