diff --git a/.gitattributes b/.gitattributes
index a6344aac8c09253b3b630fb776ae94478aa0275b..c4cb545ce9c9bbbff6607aa2f92b4a57a0851402 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
*.zip filter=lfs diff=lfs merge=lfs -text
*.zst filter=lfs diff=lfs merge=lfs -text
*tfevents* filter=lfs diff=lfs merge=lfs -text
+*.json filter=lfs diff=lfs merge=lfs -text
diff --git "a/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\223\320\236\320\241\320\242 \320\240 15.301-2016.json" "b/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\223\320\236\320\241\320\242 \320\240 15.301-2016.json"
new file mode 100644
index 0000000000000000000000000000000000000000..0a98817f9c5787b2348bd0ae00aef200d23944f5
--- /dev/null
+++ "b/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\223\320\236\320\241\320\242 \320\240 15.301-2016.json"
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:f2f6c7c92eed2998fc29f31c0be84a854b17ee5116a5888208675347e8512d2d
+size 81835
diff --git "a/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\223\320\236\320\241\320\242 \320\240 50.01.01-2017.json" "b/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\223\320\236\320\241\320\242 \320\240 50.01.01-2017.json"
new file mode 100644
index 0000000000000000000000000000000000000000..7ef5fe4e98b2f537118778d88ad089e5e566c587
--- /dev/null
+++ "b/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\223\320\236\320\241\320\242 \320\240 50.01.01-2017.json"
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:3b6f65be6f0de290f5e137d84699fee0fa2f485dcfcc57359f4e143e2bb220db
+size 11330
diff --git "a/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\223\320\236\320\241\320\242 \320\240 50.02.01-2017.json" "b/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\223\320\236\320\241\320\242 \320\240 50.02.01-2017.json"
new file mode 100644
index 0000000000000000000000000000000000000000..2bc26bf17741a40c13649e5dc1500cc3724c8797
--- /dev/null
+++ "b/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\223\320\236\320\241\320\242 \320\240 50.02.01-2017.json"
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:a850856da6d28cea6b431ac52b209a575b63d241470aa72c5fd3a9530457e1e2
+size 45964
diff --git "a/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\223\320\236\320\241\320\242 \320\240 50.02.02-2017.json" "b/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\223\320\236\320\241\320\242 \320\240 50.02.02-2017.json"
new file mode 100644
index 0000000000000000000000000000000000000000..a6b6b2c5f65b6a9cfb04bbfac863b83c9cb8fbf3
--- /dev/null
+++ "b/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\223\320\236\320\241\320\242 \320\240 50.02.02-2017.json"
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:d5963e21b67900994b8e2452415180d17c6ea9fea58edd0e41a469270e4bd057
+size 20704
diff --git "a/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\223\320\236\320\241\320\242 \320\240 50.03.01-2017.json" "b/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\223\320\236\320\241\320\242 \320\240 50.03.01-2017.json"
new file mode 100644
index 0000000000000000000000000000000000000000..9447149e148deba5afe6f38e0f31433c953d6b38
--- /dev/null
+++ "b/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\223\320\236\320\241\320\242 \320\240 50.03.01-2017.json"
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:68b9a7b7d1b9daf7aa5a43e5d434ca65c19add00c4c5e383f2216e70526e3a33
+size 51409
diff --git "a/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\223\320\236\320\241\320\242 \320\240 50.04.01-2018.json" "b/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\223\320\236\320\241\320\242 \320\240 50.04.01-2018.json"
new file mode 100644
index 0000000000000000000000000000000000000000..4f0873664da2cd650b90b7c2112e25d713ff9180
--- /dev/null
+++ "b/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\223\320\236\320\241\320\242 \320\240 50.04.01-2018.json"
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:b27bb03894a5f4c4a15fac4aac8dd71b1feb817f9a8329486d153400a278a289
+size 54329
diff --git "a/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\223\320\236\320\241\320\242 \320\240 50.04.02-2018.json" "b/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\223\320\236\320\241\320\242 \320\240 50.04.02-2018.json"
new file mode 100644
index 0000000000000000000000000000000000000000..7e788c475a9e67b4152c74e42d80abdaa2045306
--- /dev/null
+++ "b/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\223\320\236\320\241\320\242 \320\240 50.04.02-2018.json"
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:bebd3729ca3bbbe6eeea109835eb4e94739313c456d7f68bcaa184952c197109
+size 49158
diff --git "a/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\223\320\236\320\241\320\242 \320\240 50.04.03-2018.json" "b/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\223\320\236\320\241\320\242 \320\240 50.04.03-2018.json"
new file mode 100644
index 0000000000000000000000000000000000000000..a737d1a45e2bffa585eb9fb983e3480200b66815
--- /dev/null
+++ "b/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\223\320\236\320\241\320\242 \320\240 50.04.03-2018.json"
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:1ebeeda9ff35c1c8ae884d41a60d391b546569eac4189830d7d50054a7cb0489
+size 80710
diff --git "a/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\223\320\236\320\241\320\242 \320\240 50.04.04-2018.json" "b/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\223\320\236\320\241\320\242 \320\240 50.04.04-2018.json"
new file mode 100644
index 0000000000000000000000000000000000000000..46226efdfcc866a6a7cb9e0bb722a820dcac7cfa
--- /dev/null
+++ "b/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\223\320\236\320\241\320\242 \320\240 50.04.04-2018.json"
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:6c29f846cd75f532ec9962149b526790625144aaae6f26f2bf7dace64df5a099
+size 49777
diff --git "a/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\223\320\236\320\241\320\242 \320\240 50.04.06-2018.json" "b/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\223\320\236\320\241\320\242 \320\240 50.04.06-2018.json"
new file mode 100644
index 0000000000000000000000000000000000000000..a1ae1e02a203b5d9e9583b9267fdeb1030fafc60
--- /dev/null
+++ "b/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\223\320\236\320\241\320\242 \320\240 50.04.06-2018.json"
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:1a24655e89c9bae0a064a2c5e2e12f34a87f8634faa756d2cad8bcc562dc9100
+size 207454
diff --git "a/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\223\320\236\320\241\320\242 \320\240 50.04.07-2022.json" "b/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\223\320\236\320\241\320\242 \320\240 50.04.07-2022.json"
new file mode 100644
index 0000000000000000000000000000000000000000..2c1a5e47cb417a1a802419059b303ddf38bb962a
--- /dev/null
+++ "b/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\223\320\236\320\241\320\242 \320\240 50.04.07-2022.json"
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:f679c5946b9eff6ddaa6aae19a3bfbd5d94ba974ee2581f4e443f0bbd9c85427
+size 219984
diff --git "a/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\223\320\236\320\241\320\242 \320\240 50.04.08-2019.json" "b/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\223\320\236\320\241\320\242 \320\240 50.04.08-2019.json"
new file mode 100644
index 0000000000000000000000000000000000000000..cec82d25fda7c71516cdae8b3daa8ef80e677c63
--- /dev/null
+++ "b/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\223\320\236\320\241\320\242 \320\240 50.04.08-2019.json"
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:05dcf7ed980c035bba06d7739d87fe4e776025ca2a5a97d7e12a96dc2f921cb5
+size 125444
diff --git "a/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\223\320\236\320\241\320\242 \320\240 50.05.01-2018.json" "b/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\223\320\236\320\241\320\242 \320\240 50.05.01-2018.json"
new file mode 100644
index 0000000000000000000000000000000000000000..6144dbc6f88c649c2ea769c2baec14e35c208971
--- /dev/null
+++ "b/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\223\320\236\320\241\320\242 \320\240 50.05.01-2018.json"
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:85e7256045b17f08d3ebaca6c699d37c9d80fd63a55777988a92b8fbef46169b
+size 249610
diff --git "a/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\223\320\236\320\241\320\242 \320\240 50.05.15-2018.json" "b/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\223\320\236\320\241\320\242 \320\240 50.05.15-2018.json"
new file mode 100644
index 0000000000000000000000000000000000000000..5e0d64d9bc53e08555d3a198f163c3222954b439
--- /dev/null
+++ "b/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\223\320\236\320\241\320\242 \320\240 50.05.15-2018.json"
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:95e9cc3b1eeac4842951b7834399d50207664a698707dcc37a1ff8fc4ed6b523
+size 24965
diff --git "a/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\223\320\236\320\241\320\242 \320\240 50.05.17-2019.json" "b/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\223\320\236\320\241\320\242 \320\240 50.05.17-2019.json"
new file mode 100644
index 0000000000000000000000000000000000000000..a884e4ffd3941b67555672544e6b4a652ecccc40
--- /dev/null
+++ "b/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\223\320\236\320\241\320\242 \320\240 50.05.17-2019.json"
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:9fb86c635203310e3986f2fc0f5e4bf27dc72a53976140eafec4a27453b4132a
+size 104256
diff --git "a/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\223\320\236\320\241\320\242 \320\240 50.05.18-2019.json" "b/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\223\320\236\320\241\320\242 \320\240 50.05.18-2019.json"
new file mode 100644
index 0000000000000000000000000000000000000000..8d314360eb03ee8422d851eb8d7803e853d7f427
--- /dev/null
+++ "b/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\223\320\236\320\241\320\242 \320\240 50.05.18-2019.json"
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:57e113aaa6c9f17ad8251ec7f72edfc063a3804ea1302d58152676bd46a47faa
+size 49933
diff --git "a/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\223\320\236\320\241\320\242 \320\240 50.05.22-2020.json" "b/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\223\320\236\320\241\320\242 \320\240 50.05.22-2020.json"
new file mode 100644
index 0000000000000000000000000000000000000000..f65af5a87bc73977959ceec71df8ecf9d8f478f5
--- /dev/null
+++ "b/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\223\320\236\320\241\320\242 \320\240 50.05.22-2020.json"
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:40958f6e8242408d66fe04a4d53cbff348b3301b777370c934f73cce440c6b2c
+size 18866
diff --git "a/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\223\320\236\320\241\320\242 \320\240 50.05.23-2020.json" "b/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\223\320\236\320\241\320\242 \320\240 50.05.23-2020.json"
new file mode 100644
index 0000000000000000000000000000000000000000..cad2cdeaca1f904302eb28cb2424e7a4aae879e0
--- /dev/null
+++ "b/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\223\320\236\320\241\320\242 \320\240 50.05.23-2020.json"
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:feb1adf97406bb406c19011ec1184aa06f6c4177acbf91dc9cb6f7ca7555d234
+size 97943
diff --git "a/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\223\320\236\320\241\320\242 \320\240 50.05.24-2020.json" "b/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\223\320\236\320\241\320\242 \320\240 50.05.24-2020.json"
new file mode 100644
index 0000000000000000000000000000000000000000..9e42bb7e05f05cbf08642d542d8fc4d5e2fc12bd
--- /dev/null
+++ "b/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\223\320\236\320\241\320\242 \320\240 50.05.24-2020.json"
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:45b41060f8fe3237feca055aa8767dbe275c69423aad1f4778395b658a6b7974
+size 20252
diff --git "a/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\223\320\236\320\241\320\242 \320\240 50.05.25-2020.json" "b/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\223\320\236\320\241\320\242 \320\240 50.05.25-2020.json"
new file mode 100644
index 0000000000000000000000000000000000000000..d260c9f41c78c21b4acaeec7f55df32769743eb0
--- /dev/null
+++ "b/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\223\320\236\320\241\320\242 \320\240 50.05.25-2020.json"
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:80ffdb0290dda00d67c771b3f95f62ed428d45198d2bed741a92c66573dc4cd4
+size 30896
diff --git "a/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\223\320\236\320\241\320\242 \320\240 50.06.01-2017.json" "b/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\223\320\236\320\241\320\242 \320\240 50.06.01-2017.json"
new file mode 100644
index 0000000000000000000000000000000000000000..4b3fb1ce141af16c3aa82d7a197c607aae254dba
--- /dev/null
+++ "b/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\223\320\236\320\241\320\242 \320\240 50.06.01-2017.json"
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:db848ceed112799291d9c886156fab084deeceab5dbe2d9fe18f996ec0e04f0b
+size 185158
diff --git "a/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\223\320\236\320\241\320\242 \320\240 50.07.01-2017.json" "b/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\223\320\236\320\241\320\242 \320\240 50.07.01-2017.json"
new file mode 100644
index 0000000000000000000000000000000000000000..82709e4fa8c3b3270ed4a1164d42adf479ed005f
--- /dev/null
+++ "b/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\223\320\236\320\241\320\242 \320\240 50.07.01-2017.json"
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:4a5dd25dbd74b9898a040524d487582b01bc81bad27b8c03b6a180fcef809f06
+size 244213
diff --git "a/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\223\320\236\320\241\320\242 \320\240 50.08.01-2023.json" "b/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\223\320\236\320\241\320\242 \320\240 50.08.01-2023.json"
new file mode 100644
index 0000000000000000000000000000000000000000..54e4aa22c533de4f32c72c50b28ed79a091d46f4
--- /dev/null
+++ "b/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\223\320\236\320\241\320\242 \320\240 50.08.01-2023.json"
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:5492b213601a3d6a016f84e19769645bc4df6c5782a1d1e12b8dec6958af0a40
+size 109068
diff --git "a/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\223\320\236\320\241\320\242 \320\240 50.08.02-2024.json" "b/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\223\320\236\320\241\320\242 \320\240 50.08.02-2024.json"
new file mode 100644
index 0000000000000000000000000000000000000000..28ece822eb03be7a5619b0dd780c8f8554515a9d
--- /dev/null
+++ "b/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\223\320\236\320\241\320\242 \320\240 50.08.02-2024.json"
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:943fb3657d1153e0755883102b697d604eda57a3b22dd3b2c344e6af868a605f
+size 76123
diff --git "a/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\223\320\236\320\241\320\242 \320\240 50.08.03-2017.json" "b/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\223\320\236\320\241\320\242 \320\240 50.08.03-2017.json"
new file mode 100644
index 0000000000000000000000000000000000000000..8c60b821fcf4ab586ba637f2fc82e21295635d8b
--- /dev/null
+++ "b/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\223\320\236\320\241\320\242 \320\240 50.08.03-2017.json"
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:b18af0c48691618c03635ad967f57f44c22a8cfa51729cd2087f5fc9bcc04c5f
+size 51168
diff --git "a/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\223\320\236\320\241\320\242 \320\240 50.08.04-2022.json" "b/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\223\320\236\320\241\320\242 \320\240 50.08.04-2022.json"
new file mode 100644
index 0000000000000000000000000000000000000000..313528cfad2ddfd44ecc3cb52773a24c6ba0662b
--- /dev/null
+++ "b/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\223\320\236\320\241\320\242 \320\240 50.08.04-2022.json"
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:f2aca1bbe29b1b13ae799b82d18c4f8c9038578be9bd4c083bc3a5f476e1133f
+size 98441
diff --git "a/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\223\320\236\320\241\320\242 \320\240 50.08.06-2017.json" "b/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\223\320\236\320\241\320\242 \320\240 50.08.06-2017.json"
new file mode 100644
index 0000000000000000000000000000000000000000..f3ff2a808d09902aad880d10c9a77272362e5b2e
--- /dev/null
+++ "b/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\223\320\236\320\241\320\242 \320\240 50.08.06-2017.json"
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:afee799bf84bcf37f3db38701a140e5828f0eb058e96b549366ef0ab85071bcf
+size 26220
diff --git "a/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\223\320\236\320\241\320\242 \320\240 59023.1-2020.json" "b/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\223\320\236\320\241\320\242 \320\240 59023.1-2020.json"
new file mode 100644
index 0000000000000000000000000000000000000000..8a2ff76ed5c564d799c55ec08dec84694f8b8df5
--- /dev/null
+++ "b/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\223\320\236\320\241\320\242 \320\240 59023.1-2020.json"
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:3fdfff6017ce1e890bd03e120ac69ec6d08ae360e1a342a03e6ab8d7c0c123fc
+size 39939
diff --git "a/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\223\320\236\320\241\320\242 \320\240 59023.2-2020.json" "b/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\223\320\236\320\241\320\242 \320\240 59023.2-2020.json"
new file mode 100644
index 0000000000000000000000000000000000000000..5073e8c934e152b714240b3250fac474ef09633c
--- /dev/null
+++ "b/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\223\320\236\320\241\320\242 \320\240 59023.2-2020.json"
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:bdfbf23558ce977b54279a14ca08206f847dc9fa8b3328bb53dea85f5f0ebddd
+size 34537
diff --git "a/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\223\320\236\320\241\320\242 \320\240 59023.3-2020.json" "b/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\223\320\236\320\241\320\242 \320\240 59023.3-2020.json"
new file mode 100644
index 0000000000000000000000000000000000000000..e5b9068199e439d45db30de78345e856e54e3c19
--- /dev/null
+++ "b/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\223\320\236\320\241\320\242 \320\240 59023.3-2020.json"
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:936f044c2660bae390f5cb7465aa5acbe4baa3a08003255dc3145adf7bca6290
+size 29645
diff --git "a/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\223\320\236\320\241\320\242 \320\240 59023.4-2020.json" "b/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\223\320\236\320\241\320\242 \320\240 59023.4-2020.json"
new file mode 100644
index 0000000000000000000000000000000000000000..fbdd4b09bee90c8a942b766a4fed2417bf790d1a
--- /dev/null
+++ "b/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\223\320\236\320\241\320\242 \320\240 59023.4-2020.json"
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:36dd410912c4297e65835814857ed4c72750c0a4bdd7bda919119f72f322c199
+size 23634
diff --git "a/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\223\320\236\320\241\320\242 \320\240 59023.5-2020 .json" "b/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\223\320\236\320\241\320\242 \320\240 59023.5-2020 .json"
new file mode 100644
index 0000000000000000000000000000000000000000..c3a7acd6d8395cbc5988128d4e759dd61f0fa011
--- /dev/null
+++ "b/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\223\320\236\320\241\320\242 \320\240 59023.5-2020 .json"
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:db3376625361940fa108f7a53599f946b57217d5acc34f451b480b837f0bc953
+size 51908
diff --git "a/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\223\320\236\320\241\320\242 \320\240 59023.6-2020.json" "b/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\223\320\236\320\241\320\242 \320\240 59023.6-2020.json"
new file mode 100644
index 0000000000000000000000000000000000000000..8d19eb946a55cd9e9a5eaffefa8b5d2aa548cd96
--- /dev/null
+++ "b/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\223\320\236\320\241\320\242 \320\240 59023.6-2020.json"
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:b50c8f4651b6299696dffaf085308479d1e9411ad4ce53adab9452bc6de5e5ae
+size 31437
diff --git "a/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\234\320\243 1.1.4.01.1422-2019.json" "b/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\234\320\243 1.1.4.01.1422-2019.json"
new file mode 100644
index 0000000000000000000000000000000000000000..aeb6732fa781966317d8847257393fc5ca404cce
--- /dev/null
+++ "b/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\234\320\243 1.1.4.01.1422-2019.json"
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:3a310a57f9f025707fdbedfbc7e4c77110f841b687caf3fa6c7a0456272551f3
+size 103409
diff --git "a/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\234\320\243_1.2.3.07.0057-2018.json" "b/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\234\320\243_1.2.3.07.0057-2018.json"
new file mode 100644
index 0000000000000000000000000000000000000000..f3f3d43cb496a7730da7adb99f547da76cea40d6
--- /dev/null
+++ "b/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\234\320\243_1.2.3.07.0057-2018.json"
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:175678654efff7c3cda5bb6c291dd2c80afe3772e896ae8287f056f0cae6fcef
+size 587898
diff --git "a/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\235\320\237-068-05.json" "b/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\235\320\237-068-05.json"
new file mode 100644
index 0000000000000000000000000000000000000000..e998667138165165d50f5c9e808e3b6301a1dd74
--- /dev/null
+++ "b/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\235\320\237-068-05.json"
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:d76dd3c23d7e0ed44fb1c1486c256bc70ea3624dd12e129b20f7f3c9f0fe99e8
+size 290085
diff --git "a/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\235\320\237-071-18.json" "b/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\235\320\237-071-18.json"
new file mode 100644
index 0000000000000000000000000000000000000000..e9e1f3d7f6f8cfe8869ab4bebf31b5fc9c7211e1
--- /dev/null
+++ "b/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\235\320\237-071-18.json"
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:30bcdabaf0793f17e9bd2da3d6431a6f857d91783ddbaab8024517d7154a19fb
+size 128889
diff --git "a/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\235\320\237-089-15.json" "b/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\235\320\237-089-15.json"
new file mode 100644
index 0000000000000000000000000000000000000000..f2effd527f121ad5a3b8d51429dbaeada4246093
--- /dev/null
+++ "b/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\235\320\237-089-15.json"
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:fc3506862873de08f67d762ec8603c72d4357f1b399468b342985097ba1dfd74
+size 298564
diff --git "a/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\235\320\237-104-18.json" "b/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\235\320\237-104-18.json"
new file mode 100644
index 0000000000000000000000000000000000000000..c01a709d91801df7f39f3d372988049ea6732126
--- /dev/null
+++ "b/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\235\320\237-104-18.json"
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:9336db60d9fe721017de8668fbc2f6d2f16c25cd9e9351003f8b51d03153f981
+size 280667
diff --git "a/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\235\320\237-105-18.json" "b/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\235\320\237-105-18.json"
new file mode 100644
index 0000000000000000000000000000000000000000..11c6700bb6103e7b872be02dcca6e3bf2e77b40d
--- /dev/null
+++ "b/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 1/\320\235\320\237-105-18.json"
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:9331087951ba32462682e4d9136700a547882b5bb8d8409b8fef95e5f68ba95f
+size 242379
diff --git "a/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 2/\320\223\320\236\320\241\320\242 \320\240 50.05.02-2022.json" "b/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 2/\320\223\320\236\320\241\320\242 \320\240 50.05.02-2022.json"
new file mode 100644
index 0000000000000000000000000000000000000000..a1754aa7ed4bc33c32e687cb326eaea81b6c2d4b
--- /dev/null
+++ "b/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 2/\320\223\320\236\320\241\320\242 \320\240 50.05.02-2022.json"
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:f3b67f002b9590f9fbd0aea1191909d28551ab634bf1d5537a3ec05f6c709020
+size 490366
diff --git "a/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 2/\320\223\320\236\320\241\320\242 \320\240 50.05.03-2022.json" "b/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 2/\320\223\320\236\320\241\320\242 \320\240 50.05.03-2022.json"
new file mode 100644
index 0000000000000000000000000000000000000000..f4c2dd3744a85dd1a25db32a7e4c163e92de3b98
--- /dev/null
+++ "b/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 2/\320\223\320\236\320\241\320\242 \320\240 50.05.03-2022.json"
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:e2e782baef2cce948ea3c9ec3f87df11c854bc83900d9892eda6a7d6e966c7d8
+size 149861
diff --git "a/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 2/\320\223\320\236\320\241\320\242 \320\240 50.05.04-2022.json" "b/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 2/\320\223\320\236\320\241\320\242 \320\240 50.05.04-2022.json"
new file mode 100644
index 0000000000000000000000000000000000000000..c0bb8c6227acb1ca16aeaaeada51723fbd520872
--- /dev/null
+++ "b/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 2/\320\223\320\236\320\241\320\242 \320\240 50.05.04-2022.json"
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:71e4e173c39dbf93d1f64da779212e04f34959933d6495581899716c049e20ec
+size 156473
diff --git "a/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 2/\320\223\320\236\320\241\320\242 \320\240 50.05.05-2018.json" "b/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 2/\320\223\320\236\320\241\320\242 \320\240 50.05.05-2018.json"
new file mode 100644
index 0000000000000000000000000000000000000000..65b395d16d345bbf0dafe7c673173fb91307adbf
--- /dev/null
+++ "b/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 2/\320\223\320\236\320\241\320\242 \320\240 50.05.05-2018.json"
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:d68219a0337a12c529bc8b5a2ef8000ca350df77385732e594796a666679bf22
+size 126584
diff --git "a/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 2/\320\223\320\236\320\241\320\242 \320\240 50.05.06-2018.json" "b/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 2/\320\223\320\236\320\241\320\242 \320\240 50.05.06-2018.json"
new file mode 100644
index 0000000000000000000000000000000000000000..5e25d923157c2bc5537560ca568cbe25cfdeb5ae
--- /dev/null
+++ "b/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 2/\320\223\320\236\320\241\320\242 \320\240 50.05.06-2018.json"
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:8ecff9866781a4ff8453d003ff28e1846cf8bda115d304e4c99163bdf32a63ae
+size 185296
diff --git "a/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 2/\320\223\320\236\320\241\320\242 \320\240 50.05.07-2018.json" "b/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 2/\320\223\320\236\320\241\320\242 \320\240 50.05.07-2018.json"
new file mode 100644
index 0000000000000000000000000000000000000000..fddc9b5ac54659cbe18f82b1e3cb569a7b50465c
--- /dev/null
+++ "b/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 2/\320\223\320\236\320\241\320\242 \320\240 50.05.07-2018.json"
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:76f551c42a5174e3588511edd7a0e9c7143d21ec912d57f0663b4ebaa8d127a7
+size 112897
diff --git "a/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 2/\320\223\320\236\320\241\320\242 \320\240 50.05.08-2018.json" "b/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 2/\320\223\320\236\320\241\320\242 \320\240 50.05.08-2018.json"
new file mode 100644
index 0000000000000000000000000000000000000000..300d6269486b0eedec0a0472b3396fcd7638043f
--- /dev/null
+++ "b/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 2/\320\223\320\236\320\241\320\242 \320\240 50.05.08-2018.json"
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:e735b5b0074724fabda11201bcfd06a7c6416c60a2975bf33f6af87e9bfdd5d2
+size 93422
diff --git "a/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 2/\320\223\320\236\320\241\320\242 \320\240 50.05.09-2018.json" "b/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 2/\320\223\320\236\320\241\320\242 \320\240 50.05.09-2018.json"
new file mode 100644
index 0000000000000000000000000000000000000000..42930ac56f62c82aadb90b59179a223bf852a06b
--- /dev/null
+++ "b/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 2/\320\223\320\236\320\241\320\242 \320\240 50.05.09-2018.json"
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:f0e7755756acc6e3f0ff421f8cef6e56a3d0bee9728caadabc3d8012c9198d6d
+size 125282
diff --git "a/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 2/\320\223\320\236\320\241\320\242 \320\240 50.05.10-2018.json" "b/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 2/\320\223\320\236\320\241\320\242 \320\240 50.05.10-2018.json"
new file mode 100644
index 0000000000000000000000000000000000000000..fc59e503f693c48c72568157b89f544a3c3111c5
--- /dev/null
+++ "b/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 2/\320\223\320\236\320\241\320\242 \320\240 50.05.10-2018.json"
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:2fd2ee242c823495462c8c83ce0c2ef4fc334ee22825cebe3adc9d23aa1eefe1
+size 129276
diff --git "a/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 2/\320\223\320\236\320\241\320\242 \320\240 50.05.11-2018.json" "b/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 2/\320\223\320\236\320\241\320\242 \320\240 50.05.11-2018.json"
new file mode 100644
index 0000000000000000000000000000000000000000..a4b08c3c8e015263dba2f6d5418d484d8b8b3757
--- /dev/null
+++ "b/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 2/\320\223\320\236\320\241\320\242 \320\240 50.05.11-2018.json"
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:b539e42e31732b61f5fa81f19e116099fc5ccc0adc6a7642a3c8ee78a3388c13
+size 103990
diff --git "a/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 2/\320\223\320\236\320\241\320\242 \320\240 50.05.14-2019.json" "b/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 2/\320\223\320\236\320\241\320\242 \320\240 50.05.14-2019.json"
new file mode 100644
index 0000000000000000000000000000000000000000..237c0c86e122ce47310b5ac0f23e23c9cc2f0511
--- /dev/null
+++ "b/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 2/\320\223\320\236\320\241\320\242 \320\240 50.05.14-2019.json"
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:c66e01fd55a18880f0cab3a4f5fecc1190d8e18d017cb643b0c5debf5a893664
+size 59653
diff --git "a/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 2/\320\223\320\236\320\241\320\242 \320\240 50.05.16-2018.json" "b/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 2/\320\223\320\236\320\241\320\242 \320\240 50.05.16-2018.json"
new file mode 100644
index 0000000000000000000000000000000000000000..6ca5961d0a54c82cab7a6626fbbc63327fc618b3
--- /dev/null
+++ "b/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 2/\320\223\320\236\320\241\320\242 \320\240 50.05.16-2018.json"
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:bdfd2dd1e47d8540a10871ff8e433e8a16a9ec40e10dd9fcaa5d6529bc09d4b4
+size 115057
diff --git "a/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 2/\320\223\320\236\320\241\320\242 \320\240 50.08.07-2017.json" "b/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 2/\320\223\320\236\320\241\320\242 \320\240 50.08.07-2017.json"
new file mode 100644
index 0000000000000000000000000000000000000000..6114f81622680b76623651a915548c16cd1e3856
--- /dev/null
+++ "b/JSON/\320\237\321\200\320\270\320\276\321\200\320\270\321\202\320\265\321\202 2/\320\223\320\236\320\241\320\242 \320\240 50.08.07-2017.json"
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:dc90732e7ce515ed4ad03cd3b87c79056487f06940b39c11aa1cdf75c3fddc78
+size 24904
diff --git a/app.py b/app.py
index f71a9e6240d84b41efd5d5851e981e2b561ecb20..a683db9f9ac6d60599f398adfebed29caf31495d 100644
--- a/app.py
+++ b/app.py
@@ -1,499 +1,81 @@
import gradio as gr
-from huggingface_hub import hf_hub_download
-import faiss
-import pandas as pd
import os
-import json
-from llama_index.core import Document, VectorStoreIndex, Settings
-from llama_index.embeddings.huggingface import HuggingFaceEmbedding
-from llama_index.llms.google_genai import GoogleGenAI
-from llama_index.core.query_engine import RetrieverQueryEngine
-from llama_index.core.retrievers import VectorIndexRetriever
-from llama_index.core.response_synthesizers import get_response_synthesizer, ResponseMode
-from llama_index.core.prompts import PromptTemplate
-import time
import sys
+import logging
from config import *
+from documents_prep import DocumentsPreparation
+from index_retriever import IndexRetriever
+from chat_handler import ChatHandler
REPO_ID = "MrSimple01/AIEXP_RAG_FILES"
-faiss_index_filename = "cleaned_faiss_index.index"
-chunks_filename = "processed_chunks.csv"
-download_dir = "rag_files"
-table_data_dir = "Табличные данные_JSON"
HF_TOKEN = os.getenv('HF_TOKEN')
-GOOGLE_API_KEY = os.getenv('GOOGLE_API_KEY')
-CUSTOM_PROMPT_NEW = """
-Вы являетесь высокоспециализированным Ассистентом для анализа документов (AIEXP). Ваша цель - предоставлять точные, корректные и контекстно релевантные ответы на основе анализа нормативной документации (НД). Все ваши ответы должны основываться исключительно на предоставленном контексте без использования внешних знаний или предположений.
+logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
+logger = logging.getLogger(__name__)
-КРИТИЧЕСКИ ВАЖНО: ВСЕ ОТВЕТЫ ДОЛЖНЫ БЫТЬ ТОЛЬКО НА РУССКОМ ЯЗЫКЕ! НИКОГДА НЕ ОТВЕЧАЙТЕ НА АНГЛИЙСКОМ!
-
-История чата:
-{chat_history}
-
-
-ИНСТРУКЦИИ ПО ОБРАБОТКЕ КОНТЕКСТА:
-
-1. АНАЛИЗ ТАБЛИЧНЫХ ДАННЫХ:
- - Если в контексте есть информация начинающаяся с "Таблица", внимательно изучите её содержимое
- - Извлекайте данные из строк с заголовками и данными таблицы
- - Указывайте номер и название таблицы при ответе
- - Структурируйте ответ на основе табличных данных
-
-2. ОПРЕДЕЛЕНИЕ ТИПА ЗАДАЧИ:
-Проанализируйте запрос пользователя и определите тип задачи:
-
-1. КРАТКОЕ САММАРИ (ключевые слова: "кратко", "суммировать", "резюме", "основные моменты", "в двух словах"):
- - Предоставьте структурированное резюме запрашиваемого раздела/пункта
- - Выделите ключевые требования, процедуры или положения
- - Используйте нумерованный список для лучшей читаемости
- - Сохраняйте терминологию НД
-
-2. ПОИСК ДОКУМЕНТА И ПУНКТА (ключевые слова: "найти", "где", "какой документ", "в каком разделе", "ссылка"):
- - Укажите конкретный документ и его структурное расположение
- - Предоставьте точные номера разделов/подразделов/пунктов
- - Процитируйте релевантные фрагменты
- - Если найдено несколько документов, перечислите все с указанием специфики каждого
-
-3. ПРОВЕРКА КОРРЕКТНОСТИ (ключевые слова: "правильно ли", "соответствует ли", "проверить", "корректно", "нарушение"):
- - Сопоставьте предоставленную информацию с требованиями НД
- - Четко укажите: "СООТВЕТСТВУЕТ" или "НЕ СООТВЕТСТВУЕТ"
- - Перечислите конкретные требования НД
- - Укажите выявленные расхождения или подтвердите соответствие
- - Процитируйте релевантные пункты НД
-
-4. ПЛАН ДЕЙСТВИЙ (ключевые слова: "план", "алгоритм", "последовательность", "как действовать", "пошагово"):
- - Создайте пронумерованный пошаговый план
- - Каждый шаг должен содержать ссылку на соответствующий пункт НД
- - Укажите необходимые документы или формы
- - Добавьте временные рамки, если они указаны в НД
- - Выделите критические требования или ограничения
-
-5. УТОЧНЯЮЩИЕ ВОПРОСЫ (ключевые слова: "что это значит", "что означает", "объясните", "расскажите подробнее"):
- - Используйте историю чата для понимания контекста
- - Если вопрос относится к предыдущему обсуждению, опирайтесь на него
- - Предоставьте подробное объяснение на основе НД
- - Если контекст неясен, попросите уточнения
-
-ПРАВИЛА ФОРМИРОВАНИЯ ОТВЕТОВ:
-
-1. ОБЯЗАТЕЛЬНОЕ УКАЗАНИЕ ИСТОЧНИКОВ:
- - Для каждого ответа указывайте: "Согласно [Название документа], раздел [X], пункт [X.X]: [Ваш ответ]"
- - В конце ответа добавляйте: "Подробнее об этом можно узнать в документе [Название документа], раздел [X]."
- - При отсутствии точного раздела: "Согласно документу [Название]: [Ваш ответ]"
-
-2. СТРОГОЕ СЛЕДОВАНИЕ КОНТЕКСТУ:
- - Если информация не найдена: "Информация по вашему запросу не была найдена в нормативной документации."
- - НЕ используйте английский язык ни при каких обстоятельствах
- - Используйте историю чата для понимания контекста вопросов
-
-3. ИСПОЛЬЗОВАНИЕ ТЕРМИНОЛОГИИ НД:
- - Применяйте официальную терминологию из документов
- - Сохраняйте оригинальные формулировки ключевых требований
- - При необходимости разъясняйте специальные термины на основе НД
-
-4. СТРУКТУРИРОВАНИЕ ОТВЕТОВ:
- - Основной ответ на русском языке
- - Указание источника
- - Дополнительная информация о документе
-
-Контекст: {context_str}
-
-Вопрос: {query_str}
-
-Ответ (ТОЛЬКО НА РУССКОМ ЯЗЫКЕ):
-"""
-
-
-query_engine = None
-chunks_df = None
-chat_history = []
+doc_prep = None
+index_retriever = None
+chat_handler = None
def log_message(message):
+ logger.info(message)
print(message, flush=True)
sys.stdout.flush()
-def table_to_document(table_json):
- document_id = table_json.get("document_id") or table_json.get("document", "unknown")
-
- metadata = {
- "document_id": document_id,
- "section": table_json.get("section", ""),
- "table_number": table_json.get("table_number", ""),
- "table_title": table_json.get("table_title", ""),
- }
-
- description = table_json.get("table_description", "")
- headers = table_json.get("headers", [])
-
- table_text = f"ТАБЛИЦА: {table_json.get('table_number', '')} - {table_json.get('table_title', '')}\n"
- table_text += f"ДОКУМЕНТ: {document_id}\n"
- table_text += f"РАЗДЕЛ: {table_json.get('section', '')}\n"
-
- if description:
- table_text += f"ОПИСАНИЕ: {description}\n"
-
- if headers:
- table_text += f"ЗАГОЛОВКИ ТАБЛИЦЫ: {' | '.join(headers)}\n"
-
- data = table_json.get("data", [])
- if data:
- table_text += "ДАННЫЕ ТАБЛИЦЫ:\n"
- for i, row in enumerate(data):
- if isinstance(row, dict):
- row_str = " | ".join([f"{k}: {v}" for k,v in row.items()])
- table_text += f"Строка {i+1}: {row_str}\n"
-
- return Document(text=table_text, metadata=metadata)
-
-def download_table_data():
- log_message("📥 Загрузка табличных данных...")
+def initialize_system():
+ global doc_prep, index_retriever, chat_handler
- from huggingface_hub import list_repo_files
-
- table_files = []
try:
- files = list_repo_files(repo_id=REPO_ID, repo_type="dataset", token=HF_TOKEN)
- for file in files:
- if file.startswith(table_data_dir) and file.endswith('.json'):
- table_files.append(file)
+ log_message("Запуск инициализации системы AIEXP")
- log_message(f"📊 Найдено {len(table_files)} JSON файлов с таблицами")
+ doc_prep = DocumentsPreparation(REPO_ID, HF_TOKEN)
+ index_retriever = IndexRetriever(config=sys.modules[__name__])
- table_documents = []
- for file_path in table_files:
- try:
- log_message(f"🔄 Обработка файла: {file_path}")
-
- local_path = hf_hub_download(
- repo_id=REPO_ID,
- filename=file_path,
- local_dir=download_dir,
- repo_type="dataset",
- token=HF_TOKEN
- )
-
- with open(local_path, 'r', encoding='utf-8') as f:
- table_data = json.load(f)
-
- log_message(f"📋 Структура JSON: {list(table_data.keys()) if isinstance(table_data, dict) else 'Список'}")
-
- if isinstance(table_data, dict):
- if 'sheets' in table_data:
- for sheet in table_data['sheets']:
- doc = table_to_document(sheet)
- table_documents.append(doc)
- log_message(f"✅ Создан документ из таблицы: {sheet.get('table_number', 'unknown')}")
- else:
- doc = table_to_document(table_data)
- table_documents.append(doc)
- log_message(f"✅ Создан документ из JSON объекта")
- elif isinstance(table_data, list):
- for table_json in table_data:
- doc = table_to_document(table_json)
- table_documents.append(doc)
- log_message(f"✅ Создан документ из элемента списка")
-
- except Exception as e:
- log_message(f"❌ Ошибка обработки файла {file_path}: {str(e)}")
- continue
+ log_message("Подготовка документов")
+ all_documents = doc_prep.prepare_all_documents()
- log_message(f"✅ Создано {len(table_documents)} документов из таблиц")
- return table_documents
+ if not all_documents:
+ log_message("Не удалось загрузить документы")
+ return False
- except Exception as e:
- log_message(f"❌ Ошибка загрузки табличных данных: {str(e)}")
- return []
-
-
-def improve_query_with_history(question, chat_history_list):
- try:
- log_message("🔄 Улучшение запроса с учетом истории...")
+ log_message("Инициализация моделей и индекса")
+ if not index_retriever.initialize_models(all_documents):
+ log_message("Не удалось инициализировать модели")
+ return False
- if not chat_history_list:
- log_message("📝 История чата пуста, используем оригинальный запрос")
- return question
+ chat_handler = ChatHandler(index_retriever)
- history_context = ""
- for i, (user_msg, bot_msg) in enumerate(chat_history_list[-3:], 1):
- history_context += f"Вопрос {i}: {user_msg}\nОтвет {i}: {bot_msg[:200]}...\n\n"
-
- improvement_prompt = f"""Проанализируй историю диалога и улучши текущий запрос пользователя.
-
-ИСТОРИЯ ДИАЛОГА:
-{history_context}
-
-ТЕКУЩИЙ ЗАПРОС: {question}
-
-ПРАВИЛА:
-1. Если запрос неполный или ссылается на предыдущий контекст (например: "что это", "о чем это", "объясни это"), дополни его информацией из истории
-2. Если запрос самодостаточный, верни его без изменений
-3. Сохраняй ключевые термины и названия документов из истории
-4. Отвечай только улучшенным запросом без дополнительных пояснений
-
-УЛУЧШЕННЫЙ ЗАПРОС:"""
-
- from llama_index.llms.google_genai import GoogleGenAI
- llm = GoogleGenAI(model="gemini-2.0-flash", api_key=GOOGLE_API_KEY)
-
- improved_query = llm.complete(improvement_prompt).text.strip()
- log_message(f"✨ Улучшенный запрос: {improved_query}")
-
- return improved_query
-
- except Exception as e:
- log_message(f"❌ Ошибка улучшения запроса: {str(e)}")
- return question
-
-
-def format_chat_history():
- if not chat_history:
- return "История чата пуста."
-
- history_text = ""
- for i, (user_msg, bot_msg) in enumerate(chat_history[-5:], 1):
- history_text += f"Сообщение {i}:\nПользователь: {user_msg}\nАссистент: {bot_msg}\n\n"
-
- return history_text
-
-
-def answer_question(question, history):
- global query_engine, chunks_df, chat_history
-
- if query_engine is None:
- return history + [["", "❌ Система не инициализирована"]], ""
-
- try:
- start_time = time.time()
-
- log_message(f"🔍 Получен вопрос: {question}")
- log_message(f"📜 История чата: {len(chat_history)} сообщений")
-
- # Улучшаем запрос с учетом истории
- improved_question = improve_query_with_history(question, chat_history)
- log_message(f"🎯 Обработка улучшенного запроса: {improved_question}")
-
- # Форматируем историю чата для промпта
- chat_history_text = format_chat_history()
- log_message(f"📝 Сформированная история для промпта: {len(chat_history_text)} символов")
-
- log_message("🔎 Поиск релевантных чанков...")
- retrieved_nodes = query_engine.retriever.retrieve(improved_question)
- log_message(f"📊 Найдено {len(retrieved_nodes)} релевантных чанков")
-
- # Логируем найденные чанки
- for i, node in enumerate(retrieved_nodes[:3]):
- log_message(f"📄 Чанк {i+1}: {node.text[:100]}...")
- log_message(f"🏷️ Метаданные: {node.metadata}")
-
- log_message("🤖 Отправка запроса в LLM...")
-
- # Создаем контекст с историей чата
- query_with_context = f"""
-История чата:
-{chat_history_text}
-
-Текущий вопрос: {question}
-"""
-
- response = query_engine.query(query_with_context)
-
- end_time = time.time()
- processing_time = end_time - start_time
-
- bot_response = response.response
- log_message(f"✅ Получен ответ: {bot_response[:100]}...")
-
- # Проверяем, что ответ на русском языке
- if any(english_word in bot_response.lower() for english_word in ['i am sorry', 'i cannot', 'the query', 'this request']):
- log_message("⚠️ Обнаружен ответ на английском языке, форсируем русский ответ")
-
- # Принудительно запрашиваем ответ на русском
- russian_prompt = f"""
- ВАЖНО: Отвечай ТОЛЬКО на русском языке!
-
- Вопрос: {question}
- История: {chat_history_text}
- Контекст: {retrieved_nodes[0].text if retrieved_nodes else 'Нет контекста'}
-
- Если информации недостаточно для ответа, скажи: "Недостаточно информации для ответа на ваш вопрос в предоставленной документации."
-
- Ответ на русском языке:
- """
-
- from llama_index.llms.google_genai import GoogleGenAI
- llm = GoogleGenAI(model="gemini-2.0-flash", api_key=GOOGLE_API_KEY)
- bot_response = llm.complete(russian_prompt).text.strip()
- log_message(f"🔄 Исправленный ответ на русском: {bot_response[:100]}...")
-
- # Обновляем историю чата
- chat_history.append((question, bot_response))
-
- if len(chat_history) > 10:
- chat_history = chat_history[-10:]
-
- log_message(f"💾 История чата обновлена. Всего сообщений: {len(chat_history)}")
-
- sources_html = generate_sources_html(retrieved_nodes)
-
- response_with_time = f"{bot_response}\n\n⏱️ Время обработки: {processing_time:.2f} сек"
-
- history.append([question, response_with_time])
-
- return history, sources_html
-
- except Exception as e:
- error_msg = f"❌ Ошибка обработки вопроса: {str(e)}"
- log_message(f"❌ Ошибка: {str(e)}")
- history.append([question, error_msg])
- return history, ""
-
-
-def initialize_models():
- global query_engine, chunks_df
-
- try:
- log_message("🔄 Инициализация системы...")
- os.makedirs(download_dir, exist_ok=True)
-
- log_message("📥 Загрузка основных файлов...")
- faiss_index_path = hf_hub_download(
- repo_id=REPO_ID,
- filename=faiss_index_filename,
- local_dir=download_dir,
- repo_type="dataset",
- token=HF_TOKEN
- )
-
- chunks_csv_path = hf_hub_download(
- repo_id=REPO_ID,
- filename=chunks_filename,
- local_dir=download_dir,
- repo_type="dataset",
- token=HF_TOKEN
- )
-
- log_message("📚 Загрузка индекса и данных...")
- index_faiss = faiss.read_index(faiss_index_path)
- chunks_df = pd.read_csv(chunks_csv_path)
- log_message(f"📄 Загружено {len(chunks_df)} основных чанков")
- log_message(f"📋 Колонки в chunks_df: {list(chunks_df.columns)}")
-
- table_documents = download_table_data()
-
- log_message("🤖 Настройка моделей...")
- embed_model = HuggingFaceEmbedding(model_name="sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2")
- llm = GoogleGenAI(model="gemini-2.0-flash", api_key=GOOGLE_API_KEY)
-
- Settings.embed_model = embed_model
- Settings.llm = llm
-
- text_column = None
- for col in chunks_df.columns:
- if 'text' in col.lower() or 'content' in col.lower() or 'chunk' in col.lower():
- text_column = col
- break
-
- if text_column is None:
- text_column = chunks_df.columns[0]
-
- log_message(f"📝 Используется колонка для текста: {text_column}")
-
- documents = []
- for i, (_, row) in enumerate(chunks_df.iterrows()):
- doc = Document(
- text=str(row[text_column]),
- metadata={
- "chunk_id": row.get('chunk_id', i),
- "document_id": row.get('document_id', 'unknown')
- }
- )
- documents.append(doc)
-
- documents.extend(table_documents)
- log_message(f"📋 Всего создано {len(documents)} документов ({len(chunks_df)} чанков + {len(table_documents)} таблиц)")
-
- log_message("🔍 Построение векторного индекса...")
- vector_index = VectorStoreIndex.from_documents(documents)
-
- retriever = VectorIndexRetriever(
- index=vector_index,
- similarity_top_k=20,
- similarity_cutoff=0.7
- )
-
- custom_prompt_template = PromptTemplate(CUSTOM_PROMPT_NEW)
- response_synthesizer = get_response_synthesizer(
- response_mode=ResponseMode.TREE_SUMMARIZE,
- text_qa_template=custom_prompt_template
- )
-
- query_engine = RetrieverQueryEngine(
- retriever=retriever,
- response_synthesizer=response_synthesizer
- )
-
- log_message("✅ Система успешно инициализирована!")
+ log_message("Система успешно инициализирована")
return True
except Exception as e:
- log_message(f"❌ Ошибка инициализации: {str(e)}")
+ log_message(f"Ошибка инициализации системы: {str(e)}")
return False
-def generate_sources_html(nodes):
- html = "
"
- html += "
📚 Источники:
"
-
- unique_docs = {}
- for node in nodes:
- metadata = node.metadata if hasattr(node, 'metadata') else {}
- doc_id = metadata.get('document_id', 'unknown')
- if doc_id not in unique_docs:
- unique_docs[doc_id] = []
- unique_docs[doc_id].append(node)
-
- for doc_id, doc_nodes in unique_docs.items():
- if doc_id == 'unknown' or doc_id == 'Раздел документа':
- continue
-
- file_link = None
- if chunks_df is not None and 'file_link' in chunks_df.columns:
- doc_rows = chunks_df[chunks_df['document_id'] == doc_id]
- if not doc_rows.empty:
- file_link = doc_rows.iloc[0]['file_link']
-
- html += f"
"
- html += f"
📄 {doc_id}
"
-
- if file_link:
- html += f"
🔗 Ссылка на документ"
-
- table_nodes = [node for node in doc_nodes if 'table_number' in node.metadata]
- if table_nodes:
- for node in table_nodes[:3]:
- metadata = node.metadata
- table_num = metadata.get('table_number', '')
- table_title = metadata.get('table_title', 'Без названия')
- if table_num and table_title != 'Без названия':
- html += f"
📊 {table_num}: {table_title}
"
-
- html += "
"
-
- html += "
"
- return html
+def handle_question(question):
+ if chat_handler is None:
+ return "Система не инициализирована", ""
+ return chat_handler.answer_question(question)
-def clear_chat():
- global chat_history
- chat_history = []
- log_message("🗑️ История чата очищена")
- return [], ""
+def handle_model_switch(model_name):
+ if index_retriever is None:
+ return "Система не инициализирована"
+ return index_retriever.switch_model(model_name)
-def handle_submit(message, history):
- if not message.strip():
- return history, ""
-
- updated_history, sources = answer_question(message, history)
- return updated_history, sources
+def get_current_model_status():
+ if index_retriever is None:
+ return "Система не инициализирована"
+ return f"Текущая модель: {index_retriever.get_current_model()}"
+
+def get_chat_history_html():
+ if chat_handler is None:
+ return "История недоступна"
+ return chat_handler.get_history_html()
+
+def clear_chat_history():
+ if chat_handler is not None:
+ chat_handler.clear_history()
+ return "История очищена"
def create_demo_interface():
with gr.Blocks(title="AIEXP - AI Expert для нормативной документации", theme=gr.themes.Soft()) as demo:
@@ -504,64 +86,141 @@ def create_demo_interface():
## Инструмент для работы с нормативной документацией
""")
- with gr.Tab("💬 Чат с документами"):
+ with gr.Tab("🏠 Поиск по нормативным документам"):
gr.Markdown("### Задайте вопрос по нормативной документации")
with gr.Row():
with gr.Column(scale=2):
- chatbot = gr.Chatbot(
- label="Диалог с AIEXP",
- height=500,
- show_copy_button=True
+ model_dropdown = gr.Dropdown(
+ choices=list(AVAILABLE_MODELS.keys()),
+ value=DEFAULT_MODEL,
+ label="🤖 Выберите языковую модель",
+ info="Выберите модель для генерации ответов"
)
-
- with gr.Row():
- msg = gr.Textbox(
- label="Ваш вопрос",
- placeholder="Введите вопрос по нормативным документам...",
- lines=2,
- scale=4
- )
- send_btn = gr.Button("📤 Отправить", variant="primary", scale=1)
-
- with gr.Row():
- clear_btn = gr.Button("🗑️ Очистить чат", variant="secondary")
+ with gr.Column(scale=1):
+ switch_btn = gr.Button("🔄 Переключить модель", variant="secondary")
+ model_status = gr.Textbox(
+ value=get_current_model_status(),
+ label="Статус модели",
+ interactive=False
+ )
+
+ with gr.Row():
+ with gr.Column(scale=3):
+ question_input = gr.Textbox(
+ label="Ваш вопрос к базе знаний",
+ placeholder="Введите вопрос по нормативным документам...",
+ lines=3
+ )
+ ask_btn = gr.Button("🔍 Найти ответ", variant="primary", size="lg")
gr.Examples(
examples=[
+ "О чем этот рисунок: ГОСТ Р 50.04.07-2022 Приложение Л. Л.1.5 Рисунок Л.2",
+ "Л.9 Формула в ГОСТ Р 50.04.07 - 2022 что и о чем там?",
"Какой стандарт устанавливает порядок признания протоколов испытаний продукции в области использования атомной энергии?",
"Кто несет ответственность за организацию и проведение признания протоколов испытаний продукции?",
- "В каких случаях могут быть признаны протоколы испытаний, проведенные лабораториями, не включенными в перечисления?",
+ "В каких случаях могут быть признаны протоколы испытаний, проведенные лабораториями?",
],
- inputs=msg
+ inputs=question_input
+ )
+
+ with gr.Row():
+ with gr.Column(scale=2):
+ answer_output = gr.HTML(
+ label="",
+ value=f"Здесь появится ответ на ваш вопрос...
Текущая модель: {DEFAULT_MODEL}
",
)
with gr.Column(scale=1):
sources_output = gr.HTML(
- label="Источники",
+ label="",
value="Здесь появятся источники...
",
)
+
+ with gr.Tab("📊 История чата"):
+ gr.Markdown("### История ваших вопросов и ответов")
- def submit_message(message, history):
- return handle_submit(message, history)
+ with gr.Row():
+ refresh_history_btn = gr.Button("🔄 Обновить историю", variant="secondary")
+ clear_history_btn = gr.Button("🗑️ Очистить историю", variant="secondary")
- msg.submit(submit_message, [msg, chatbot], [chatbot, sources_output]).then(
- lambda: "", None, msg
+ history_output = gr.HTML(
+ value="История пуста
",
)
- send_btn.click(submit_message, [msg, chatbot], [chatbot, sources_output]).then(
- lambda: "", None, msg
+ history_status = gr.Textbox(
+ label="Статус",
+ interactive=False,
+ visible=False
)
+
+ with gr.Tab("ℹ️ Информация о системе"):
+ gr.Markdown("""
+ ### О системе AIEXP
+
+ **AIEXP (Artificial Intelligence Expert)** - это интеллектуальная система для работы с нормативной документацией.
+
+ #### Возможности:
+ - 🔍 Поиск информации в нормативных документах
+ - 📊 Работа с таблицами и изображениями
+ - 🤖 Поддержка различных языковых моделей
+ - 📈 Гибридный поиск с переранжировкой
+ - 📝 История диалогов
+
+ #### Поддерживаемые типы данных:
+ - **Текстовые документы** - разделы и подразделы нормативных актов
+ - **Таблицы** - структурированные данные в табличном формате
+ - **Изображения** - описания и метаданные изображений
- clear_btn.click(clear_chat, outputs=[chatbot, sources_output])
+ #### Технические характеристики:
+ - Векторный поиск на основе sentence-transformers
+ - BM25 поиск для точного совпадения терминов
+ - Cross-encoder переранжировка результатов
+ - Поддержка Google Gemini и OpenAI моделей
+ """)
+
+ switch_btn.click(
+ fn=handle_model_switch,
+ inputs=[model_dropdown],
+ outputs=[model_status]
+ )
+
+ ask_btn.click(
+ fn=handle_question,
+ inputs=[question_input],
+ outputs=[answer_output, sources_output]
+ )
+
+ question_input.submit(
+ fn=handle_question,
+ inputs=[question_input],
+ outputs=[answer_output, sources_output]
+ )
+
+ refresh_history_btn.click(
+ fn=get_chat_history_html,
+ outputs=[history_output]
+ )
+
+ clear_history_btn.click(
+ fn=clear_chat_history,
+ outputs=[history_status]
+ ).then(
+ fn=get_chat_history_html,
+ outputs=[history_output]
+ ).then(
+ fn=lambda: gr.update(visible=False),
+ outputs=[history_status]
+ )
return demo
if __name__ == "__main__":
- log_message("🚀 Запуск AIEXP - AI Expert для нормативной документации")
+ log_message("Запуск AIEXP - AI Expert для нормативной документации")
- if initialize_models():
- log_message("🌟 Запуск веб-интерфейса...")
+ if initialize_system():
+ log_message("Запуск веб-интерфейса")
demo = create_demo_interface()
demo.launch(
server_name="0.0.0.0",
@@ -570,5 +229,5 @@ if __name__ == "__main__":
debug=False
)
else:
- log_message("❌ Невозможно запустить приложение из-за ошибки инициализации")
+ log_message("Невозможно запустить приложение из-за ошибки инициализации")
sys.exit(1)
\ No newline at end of file
diff --git a/app_1.py b/app_1.py
new file mode 100644
index 0000000000000000000000000000000000000000..f71a9e6240d84b41efd5d5851e981e2b561ecb20
--- /dev/null
+++ b/app_1.py
@@ -0,0 +1,574 @@
+import gradio as gr
+from huggingface_hub import hf_hub_download
+import faiss
+import pandas as pd
+import os
+import json
+from llama_index.core import Document, VectorStoreIndex, Settings
+from llama_index.embeddings.huggingface import HuggingFaceEmbedding
+from llama_index.llms.google_genai import GoogleGenAI
+from llama_index.core.query_engine import RetrieverQueryEngine
+from llama_index.core.retrievers import VectorIndexRetriever
+from llama_index.core.response_synthesizers import get_response_synthesizer, ResponseMode
+from llama_index.core.prompts import PromptTemplate
+import time
+import sys
+from config import *
+
+REPO_ID = "MrSimple01/AIEXP_RAG_FILES"
+faiss_index_filename = "cleaned_faiss_index.index"
+chunks_filename = "processed_chunks.csv"
+download_dir = "rag_files"
+table_data_dir = "Табличные данные_JSON"
+HF_TOKEN = os.getenv('HF_TOKEN')
+GOOGLE_API_KEY = os.getenv('GOOGLE_API_KEY')
+
+CUSTOM_PROMPT_NEW = """
+Вы являетесь высокоспециализированным Ассистентом для анализа документов (AIEXP). Ваша цель - предоставлять точные, корректные и контекстно релевантные ответы на основе анализа нормативной документации (НД). Все ваши ответы должны основываться исключительно на предоставленном контексте без использования внешних знаний или предположений.
+
+КРИТИЧЕСКИ ВАЖНО: ВСЕ ОТВЕТЫ ДОЛЖНЫ БЫТЬ ТОЛЬКО НА РУССКОМ ЯЗЫКЕ! НИКОГДА НЕ ОТВЕЧАЙТЕ НА АНГЛИЙСКОМ!
+
+История чата:
+{chat_history}
+
+
+ИНСТРУКЦИИ ПО ОБРАБОТКЕ КОНТЕКСТА:
+
+1. АНАЛИЗ ТАБЛИЧНЫХ ДАННЫХ:
+ - Если в контексте есть информация начинающаяся с "Таблица", внимательно изучите её содержимое
+ - Извлекайте данные из строк с заголовками и данными таблицы
+ - Указывайте номер и название таблицы при ответе
+ - Структурируйте ответ на основе табличных данных
+
+2. ОПРЕДЕЛЕНИЕ ТИПА ЗАДАЧИ:
+Проанализируйте запрос пользователя и определите тип задачи:
+
+1. КРАТКОЕ САММАРИ (ключевые слова: "кратко", "суммировать", "резюме", "основные моменты", "в двух словах"):
+ - Предоставьте структурированное резюме запрашиваемого раздела/пункта
+ - Выделите ключевые требования, процедуры или положения
+ - Используйте нумерованный список для лучшей читаемости
+ - Сохраняйте терминологию НД
+
+2. ПОИСК ДОКУМЕНТА И ПУНКТА (ключевые слова: "найти", "где", "какой документ", "в каком разделе", "ссылка"):
+ - Укажите конкретный документ и его структурное расположение
+ - Предоставьте точные номера разделов/подразделов/пунктов
+ - Процитируйте релевантные фрагменты
+ - Если найдено несколько документов, перечислите все с указанием специфики каждого
+
+3. ПРОВЕРКА КОРРЕКТНОСТИ (ключевые слова: "правильно ли", "соответствует ли", "проверить", "корректно", "нарушение"):
+ - Сопоставьте предоставленную информацию с требованиями НД
+ - Четко укажите: "СООТВЕТСТВУЕТ" или "НЕ СООТВЕТСТВУЕТ"
+ - Перечислите конкретные требования НД
+ - Укажите выявленные расхождения или подтвердите соответствие
+ - Процитируйте релевантные пункты НД
+
+4. ПЛАН ДЕЙСТВИЙ (ключевые слова: "план", "алгоритм", "последовательность", "как действовать", "пошагово"):
+ - Создайте пронумерованный пошаговый план
+ - Каждый шаг должен содержать ссылку на соответствующий пункт НД
+ - Укажите необходимые документы или формы
+ - Добавьте временные рамки, если они указаны в НД
+ - Выделите критические требования или ограничения
+
+5. УТОЧНЯЮЩИЕ ВОПРОСЫ (ключевые слова: "что это значит", "что означает", "объясните", "расскажите подробнее"):
+ - Используйте историю чата для понимания контекста
+ - Если вопрос относится к предыдущему обсуждению, опирайтесь на него
+ - Предоставьте подробное объяснение на основе НД
+ - Если контекст неясен, попросите уточнения
+
+ПРАВИЛА ФОРМИРОВАНИЯ ОТВЕТОВ:
+
+1. ОБЯЗАТЕЛЬНОЕ УКАЗАНИЕ ИСТОЧНИКОВ:
+ - Для каждого ответа указывайте: "Согласно [Название документа], раздел [X], пункт [X.X]: [Ваш ответ]"
+ - В конце ответа добавляйте: "Подробнее об этом можно узнать в документе [Название документа], раздел [X]."
+ - При отсутствии точного раздела: "Согласно документу [Название]: [Ваш ответ]"
+
+2. СТРОГОЕ СЛЕДОВАНИЕ КОНТЕКСТУ:
+ - Если информация не найдена: "Информация по вашему запросу не была найдена в нормативной документации."
+ - НЕ используйте английский язык ни при каких обстоятельствах
+ - Используйте историю чата для понимания контекста вопросов
+
+3. ИСПОЛЬЗОВАНИЕ ТЕРМИНОЛОГИИ НД:
+ - Применяйте официальную терминологию из документов
+ - Сохраняйте оригинальные формулировки ключевых требований
+ - При необходимости разъясняйте специальные термины на основе НД
+
+4. СТРУКТУРИРОВАНИЕ ОТВЕТОВ:
+ - Основной ответ на русском языке
+ - Указание источника
+ - Дополнительная информация о документе
+
+Контекст: {context_str}
+
+Вопрос: {query_str}
+
+Ответ (ТОЛЬКО НА РУССКОМ ЯЗЫКЕ):
+"""
+
+
+query_engine = None
+chunks_df = None
+chat_history = []
+
+def log_message(message):
+ print(message, flush=True)
+ sys.stdout.flush()
+
+def table_to_document(table_json):
+ document_id = table_json.get("document_id") or table_json.get("document", "unknown")
+
+ metadata = {
+ "document_id": document_id,
+ "section": table_json.get("section", ""),
+ "table_number": table_json.get("table_number", ""),
+ "table_title": table_json.get("table_title", ""),
+ }
+
+ description = table_json.get("table_description", "")
+ headers = table_json.get("headers", [])
+
+ table_text = f"ТАБЛИЦА: {table_json.get('table_number', '')} - {table_json.get('table_title', '')}\n"
+ table_text += f"ДОКУМЕНТ: {document_id}\n"
+ table_text += f"РАЗДЕЛ: {table_json.get('section', '')}\n"
+
+ if description:
+ table_text += f"ОПИСАНИЕ: {description}\n"
+
+ if headers:
+ table_text += f"ЗАГОЛОВКИ ТАБЛИЦЫ: {' | '.join(headers)}\n"
+
+ data = table_json.get("data", [])
+ if data:
+ table_text += "ДАННЫЕ ТАБЛИЦЫ:\n"
+ for i, row in enumerate(data):
+ if isinstance(row, dict):
+ row_str = " | ".join([f"{k}: {v}" for k,v in row.items()])
+ table_text += f"Строка {i+1}: {row_str}\n"
+
+ return Document(text=table_text, metadata=metadata)
+
+def download_table_data():
+ log_message("📥 Загрузка табличных данных...")
+
+ from huggingface_hub import list_repo_files
+
+ table_files = []
+ try:
+ files = list_repo_files(repo_id=REPO_ID, repo_type="dataset", token=HF_TOKEN)
+ for file in files:
+ if file.startswith(table_data_dir) and file.endswith('.json'):
+ table_files.append(file)
+
+ log_message(f"📊 Найдено {len(table_files)} JSON файлов с таблицами")
+
+ table_documents = []
+ for file_path in table_files:
+ try:
+ log_message(f"🔄 Обработка файла: {file_path}")
+
+ local_path = hf_hub_download(
+ repo_id=REPO_ID,
+ filename=file_path,
+ local_dir=download_dir,
+ repo_type="dataset",
+ token=HF_TOKEN
+ )
+
+ with open(local_path, 'r', encoding='utf-8') as f:
+ table_data = json.load(f)
+
+ log_message(f"📋 Структура JSON: {list(table_data.keys()) if isinstance(table_data, dict) else 'Список'}")
+
+ if isinstance(table_data, dict):
+ if 'sheets' in table_data:
+ for sheet in table_data['sheets']:
+ doc = table_to_document(sheet)
+ table_documents.append(doc)
+ log_message(f"✅ Создан документ из таблицы: {sheet.get('table_number', 'unknown')}")
+ else:
+ doc = table_to_document(table_data)
+ table_documents.append(doc)
+ log_message(f"✅ Создан документ из JSON объекта")
+ elif isinstance(table_data, list):
+ for table_json in table_data:
+ doc = table_to_document(table_json)
+ table_documents.append(doc)
+ log_message(f"✅ Создан документ из элемента списка")
+
+ except Exception as e:
+ log_message(f"❌ Ошибка обработки файла {file_path}: {str(e)}")
+ continue
+
+ log_message(f"✅ Создано {len(table_documents)} документов из таблиц")
+ return table_documents
+
+ except Exception as e:
+ log_message(f"❌ Ошибка загрузки табличных данных: {str(e)}")
+ return []
+
+
+def improve_query_with_history(question, chat_history_list):
+ try:
+ log_message("🔄 Улучшение запроса с учетом истории...")
+
+ if not chat_history_list:
+ log_message("📝 История чата пуста, используем оригинальный запрос")
+ return question
+
+ history_context = ""
+ for i, (user_msg, bot_msg) in enumerate(chat_history_list[-3:], 1):
+ history_context += f"Вопрос {i}: {user_msg}\nОтвет {i}: {bot_msg[:200]}...\n\n"
+
+ improvement_prompt = f"""Проанализируй историю диалога и улучши текущий запрос пользователя.
+
+ИСТОРИЯ ДИАЛОГА:
+{history_context}
+
+ТЕКУЩИЙ ЗАПРОС: {question}
+
+ПРАВИЛА:
+1. Если запрос неполный или ссылается на предыдущий контекст (например: "что это", "о чем это", "объясни это"), дополни его информацией из истории
+2. Если запрос самодостаточный, верни его без изменений
+3. Сохраняй ключевые термины и названия документов из истории
+4. Отвечай только улучшенным запросом без дополнительных пояснений
+
+УЛУЧШЕННЫЙ ЗАПРОС:"""
+
+ from llama_index.llms.google_genai import GoogleGenAI
+ llm = GoogleGenAI(model="gemini-2.0-flash", api_key=GOOGLE_API_KEY)
+
+ improved_query = llm.complete(improvement_prompt).text.strip()
+ log_message(f"✨ Улучшенный запрос: {improved_query}")
+
+ return improved_query
+
+ except Exception as e:
+ log_message(f"❌ Ошибка улучшения запроса: {str(e)}")
+ return question
+
+
+def format_chat_history():
+ if not chat_history:
+ return "История чата пуста."
+
+ history_text = ""
+ for i, (user_msg, bot_msg) in enumerate(chat_history[-5:], 1):
+ history_text += f"Сообщение {i}:\nПользователь: {user_msg}\nАссистент: {bot_msg}\n\n"
+
+ return history_text
+
+
+def answer_question(question, history):
+ global query_engine, chunks_df, chat_history
+
+ if query_engine is None:
+ return history + [["", "❌ Система не инициализирована"]], ""
+
+ try:
+ start_time = time.time()
+
+ log_message(f"🔍 Получен вопрос: {question}")
+ log_message(f"📜 История чата: {len(chat_history)} сообщений")
+
+ # Улучшаем запрос с учетом истории
+ improved_question = improve_query_with_history(question, chat_history)
+ log_message(f"🎯 Обработка улучшенного запроса: {improved_question}")
+
+ # Форматируем историю чата для промпта
+ chat_history_text = format_chat_history()
+ log_message(f"📝 Сформированная история для промпта: {len(chat_history_text)} символов")
+
+ log_message("🔎 Поиск релевантных чанков...")
+ retrieved_nodes = query_engine.retriever.retrieve(improved_question)
+ log_message(f"📊 Найдено {len(retrieved_nodes)} релевантных чанков")
+
+ # Логируем найденные чанки
+ for i, node in enumerate(retrieved_nodes[:3]):
+ log_message(f"📄 Чанк {i+1}: {node.text[:100]}...")
+ log_message(f"🏷️ Метаданные: {node.metadata}")
+
+ log_message("🤖 Отправка запроса в LLM...")
+
+ # Создаем контекст с историей чата
+ query_with_context = f"""
+История чата:
+{chat_history_text}
+
+Текущий вопрос: {question}
+"""
+
+ response = query_engine.query(query_with_context)
+
+ end_time = time.time()
+ processing_time = end_time - start_time
+
+ bot_response = response.response
+ log_message(f"✅ Получен ответ: {bot_response[:100]}...")
+
+ # Проверяем, что ответ на русском языке
+ if any(english_word in bot_response.lower() for english_word in ['i am sorry', 'i cannot', 'the query', 'this request']):
+ log_message("⚠️ Обнаружен ответ на английском языке, форсируем русский ответ")
+
+ # Принудительно запрашиваем ответ на русском
+ russian_prompt = f"""
+ ВАЖНО: Отвечай ТОЛЬКО на русском языке!
+
+ Вопрос: {question}
+ История: {chat_history_text}
+ Контекст: {retrieved_nodes[0].text if retrieved_nodes else 'Нет контекста'}
+
+ Если информации недостаточно для ответа, скажи: "Недостаточно информации для ответа на ваш вопрос в предоставленной документации."
+
+ Ответ на русском языке:
+ """
+
+ from llama_index.llms.google_genai import GoogleGenAI
+ llm = GoogleGenAI(model="gemini-2.0-flash", api_key=GOOGLE_API_KEY)
+ bot_response = llm.complete(russian_prompt).text.strip()
+ log_message(f"🔄 Исправленный ответ на русском: {bot_response[:100]}...")
+
+ # Обновляем историю чата
+ chat_history.append((question, bot_response))
+
+ if len(chat_history) > 10:
+ chat_history = chat_history[-10:]
+
+ log_message(f"💾 История чата обновлена. Всего сообщений: {len(chat_history)}")
+
+ sources_html = generate_sources_html(retrieved_nodes)
+
+ response_with_time = f"{bot_response}\n\n⏱️ Время обработки: {processing_time:.2f} сек"
+
+ history.append([question, response_with_time])
+
+ return history, sources_html
+
+ except Exception as e:
+ error_msg = f"❌ Ошибка обработки вопроса: {str(e)}"
+ log_message(f"❌ Ошибка: {str(e)}")
+ history.append([question, error_msg])
+ return history, ""
+
+
+def initialize_models():
+ global query_engine, chunks_df
+
+ try:
+ log_message("🔄 Инициализация системы...")
+ os.makedirs(download_dir, exist_ok=True)
+
+ log_message("📥 Загрузка основных файлов...")
+ faiss_index_path = hf_hub_download(
+ repo_id=REPO_ID,
+ filename=faiss_index_filename,
+ local_dir=download_dir,
+ repo_type="dataset",
+ token=HF_TOKEN
+ )
+
+ chunks_csv_path = hf_hub_download(
+ repo_id=REPO_ID,
+ filename=chunks_filename,
+ local_dir=download_dir,
+ repo_type="dataset",
+ token=HF_TOKEN
+ )
+
+ log_message("📚 Загрузка индекса и данных...")
+ index_faiss = faiss.read_index(faiss_index_path)
+ chunks_df = pd.read_csv(chunks_csv_path)
+ log_message(f"📄 Загружено {len(chunks_df)} основных чанков")
+ log_message(f"📋 Колонки в chunks_df: {list(chunks_df.columns)}")
+
+ table_documents = download_table_data()
+
+ log_message("🤖 Настройка моделей...")
+ embed_model = HuggingFaceEmbedding(model_name="sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2")
+ llm = GoogleGenAI(model="gemini-2.0-flash", api_key=GOOGLE_API_KEY)
+
+ Settings.embed_model = embed_model
+ Settings.llm = llm
+
+ text_column = None
+ for col in chunks_df.columns:
+ if 'text' in col.lower() or 'content' in col.lower() or 'chunk' in col.lower():
+ text_column = col
+ break
+
+ if text_column is None:
+ text_column = chunks_df.columns[0]
+
+ log_message(f"📝 Используется колонка для текста: {text_column}")
+
+ documents = []
+ for i, (_, row) in enumerate(chunks_df.iterrows()):
+ doc = Document(
+ text=str(row[text_column]),
+ metadata={
+ "chunk_id": row.get('chunk_id', i),
+ "document_id": row.get('document_id', 'unknown')
+ }
+ )
+ documents.append(doc)
+
+ documents.extend(table_documents)
+ log_message(f"📋 Всего создано {len(documents)} документов ({len(chunks_df)} чанков + {len(table_documents)} таблиц)")
+
+ log_message("🔍 Построение векторного индекса...")
+ vector_index = VectorStoreIndex.from_documents(documents)
+
+ retriever = VectorIndexRetriever(
+ index=vector_index,
+ similarity_top_k=20,
+ similarity_cutoff=0.7
+ )
+
+ custom_prompt_template = PromptTemplate(CUSTOM_PROMPT_NEW)
+ response_synthesizer = get_response_synthesizer(
+ response_mode=ResponseMode.TREE_SUMMARIZE,
+ text_qa_template=custom_prompt_template
+ )
+
+ query_engine = RetrieverQueryEngine(
+ retriever=retriever,
+ response_synthesizer=response_synthesizer
+ )
+
+ log_message("✅ Система успешно инициализирована!")
+ return True
+
+ except Exception as e:
+ log_message(f"❌ Ошибка инициализации: {str(e)}")
+ return False
+
+def generate_sources_html(nodes):
+ html = ""
+ html += "
📚 Источники:
"
+
+ unique_docs = {}
+ for node in nodes:
+ metadata = node.metadata if hasattr(node, 'metadata') else {}
+ doc_id = metadata.get('document_id', 'unknown')
+ if doc_id not in unique_docs:
+ unique_docs[doc_id] = []
+ unique_docs[doc_id].append(node)
+
+ for doc_id, doc_nodes in unique_docs.items():
+ if doc_id == 'unknown' or doc_id == 'Раздел документа':
+ continue
+
+ file_link = None
+ if chunks_df is not None and 'file_link' in chunks_df.columns:
+ doc_rows = chunks_df[chunks_df['document_id'] == doc_id]
+ if not doc_rows.empty:
+ file_link = doc_rows.iloc[0]['file_link']
+
+ html += f"
"
+ html += f"
📄 {doc_id}
"
+
+ if file_link:
+ html += f"
🔗 Ссылка на документ"
+
+ table_nodes = [node for node in doc_nodes if 'table_number' in node.metadata]
+ if table_nodes:
+ for node in table_nodes[:3]:
+ metadata = node.metadata
+ table_num = metadata.get('table_number', '')
+ table_title = metadata.get('table_title', 'Без названия')
+ if table_num and table_title != 'Без названия':
+ html += f"
📊 {table_num}: {table_title}
"
+
+ html += "
"
+
+ html += "
"
+ return html
+
+def clear_chat():
+ global chat_history
+ chat_history = []
+ log_message("🗑️ История чата очищена")
+ return [], ""
+
+def handle_submit(message, history):
+ if not message.strip():
+ return history, ""
+
+ updated_history, sources = answer_question(message, history)
+ return updated_history, sources
+
+def create_demo_interface():
+ with gr.Blocks(title="AIEXP - AI Expert для нормативной документации", theme=gr.themes.Soft()) as demo:
+
+ gr.Markdown("""
+ # AIEXP - Artificial Intelligence Expert
+
+ ## Инструмент для работы с нормативной документацией
+ """)
+
+ with gr.Tab("💬 Чат с документами"):
+ gr.Markdown("### Задайте вопрос по нормативной документации")
+
+ with gr.Row():
+ with gr.Column(scale=2):
+ chatbot = gr.Chatbot(
+ label="Диалог с AIEXP",
+ height=500,
+ show_copy_button=True
+ )
+
+ with gr.Row():
+ msg = gr.Textbox(
+ label="Ваш вопрос",
+ placeholder="Введите вопрос по нормативным документам...",
+ lines=2,
+ scale=4
+ )
+ send_btn = gr.Button("📤 Отправить", variant="primary", scale=1)
+
+ with gr.Row():
+ clear_btn = gr.Button("🗑️ Очистить чат", variant="secondary")
+
+ gr.Examples(
+ examples=[
+ "Какой стандарт устанавливает порядок признания протоколов испытаний продукции в области использования атомной энергии?",
+ "Кто несет ответственность за организацию и проведение признания протоколов испытаний продукции?",
+ "В каких случаях могут быть признаны протоколы испытаний, проведенные лабораториями, не включенными в перечисления?",
+ ],
+ inputs=msg
+ )
+
+ with gr.Column(scale=1):
+ sources_output = gr.HTML(
+ label="Источники",
+ value="Здесь появятся источники...
",
+ )
+
+ def submit_message(message, history):
+ return handle_submit(message, history)
+
+ msg.submit(submit_message, [msg, chatbot], [chatbot, sources_output]).then(
+ lambda: "", None, msg
+ )
+
+ send_btn.click(submit_message, [msg, chatbot], [chatbot, sources_output]).then(
+ lambda: "", None, msg
+ )
+
+ clear_btn.click(clear_chat, outputs=[chatbot, sources_output])
+
+ return demo
+
+if __name__ == "__main__":
+ log_message("🚀 Запуск AIEXP - AI Expert для нормативной документации")
+
+ if initialize_models():
+ log_message("🌟 Запуск веб-интерфейса...")
+ demo = create_demo_interface()
+ demo.launch(
+ server_name="0.0.0.0",
+ server_port=7860,
+ share=True,
+ debug=False
+ )
+ else:
+ log_message("❌ Невозможно запустить приложение из-за ошибки инициализации")
+ sys.exit(1)
\ No newline at end of file
diff --git a/app_main for 0.py b/app_main for 0.py
new file mode 100644
index 0000000000000000000000000000000000000000..42d7ae852bce2ff650eb8ee8adaf2df2d5d0f741
--- /dev/null
+++ b/app_main for 0.py
@@ -0,0 +1,576 @@
+import gradio as gr
+from huggingface_hub import hf_hub_download, list_repo_files
+import faiss
+import pandas as pd
+import os
+import json
+from llama_index.core import Document, VectorStoreIndex, Settings
+from llama_index.embeddings.huggingface import HuggingFaceEmbedding
+from llama_index.llms.google_genai import GoogleGenAI
+from llama_index.llms.openai import OpenAI
+from llama_index.core.query_engine import RetrieverQueryEngine
+from llama_index.core.retrievers import VectorIndexRetriever
+from llama_index.core.response_synthesizers import get_response_synthesizer, ResponseMode
+from llama_index.core.prompts import PromptTemplate
+from llama_index.retrievers.bm25 import BM25Retriever
+from sentence_transformers import CrossEncoder
+from llama_index.core.retrievers import QueryFusionRetriever
+import time
+import sys
+import logging
+from config import *
+
+REPO_ID = "MrSimple01/AIEXP_RAG_FILES"
+faiss_index_filename = "cleaned_faiss_index.index"
+chunks_filename = "processed_chunks.csv"
+table_data_dir = "Табличные данные_JSON"
+image_data_dir = "Изображения"
+download_dir = "rag_files"
+HF_TOKEN = os.getenv('HF_TOKEN')
+
+# Global variables
+query_engine = None
+chunks_df = None
+reranker = None
+vector_index = None
+current_model = DEFAULT_MODEL
+
+logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
+logger = logging.getLogger(__name__)
+
+def log_message(message):
+ logger.info(message)
+ print(message, flush=True)
+ sys.stdout.flush()
+
+def get_llm_model(model_name):
+ """Get LLM model instance based on model name"""
+ try:
+ model_config = AVAILABLE_MODELS.get(model_name)
+ if not model_config:
+ log_message(f"Модель {model_name} не найдена, использую модель по умолчанию")
+ model_config = AVAILABLE_MODELS[DEFAULT_MODEL]
+
+ if not model_config.get("api_key"):
+ raise Exception(f"API ключ не найден для модели {model_name}")
+
+ if model_config["provider"] == "google":
+ return GoogleGenAI(
+ model=model_config["model_name"],
+ api_key=model_config["api_key"]
+ )
+ elif model_config["provider"] == "openai":
+ return OpenAI(
+ model=model_config["model_name"],
+ api_key=model_config["api_key"]
+ )
+ else:
+ raise Exception(f"Неподдерживаемый провайдер: {model_config['provider']}")
+
+ except Exception as e:
+ log_message(f"Ошибка создания модели {model_name}: {str(e)}")
+ # Fallback to default Google model
+ return GoogleGenAI(model="gemini-2.0-flash", api_key=GOOGLE_API_KEY)
+
+def switch_model(model_name):
+ """Switch to a different LLM model"""
+ global query_engine, current_model
+
+ try:
+ log_message(f"Переключение на модель: {model_name}")
+
+ # Create new LLM instance
+ new_llm = get_llm_model(model_name)
+ Settings.llm = new_llm
+
+ # Recreate query engine with new model
+ if vector_index is not None:
+ recreate_query_engine()
+ current_model = model_name
+ log_message(f"Модель успешно переключена на: {model_name}")
+ return f"✅ Модель переключена на: {model_name}"
+ else:
+ return "❌ Ошибка: система не инициализирована"
+
+ except Exception as e:
+ error_msg = f"Ошибка переключения модели: {str(e)}"
+ log_message(error_msg)
+ return f"❌ {error_msg}"
+
+def recreate_query_engine():
+ """Recreate query engine with current settings"""
+ global query_engine
+
+ try:
+ # Create BM25 retriever
+ bm25_retriever = BM25Retriever.from_defaults(
+ docstore=vector_index.docstore,
+ similarity_top_k=15
+ )
+
+ # Create vector retriever
+ vector_retriever = VectorIndexRetriever(
+ index=vector_index,
+ similarity_top_k=20,
+ similarity_cutoff=0.5
+ )
+
+ # Create hybrid retriever
+ hybrid_retriever = QueryFusionRetriever(
+ [vector_retriever, bm25_retriever],
+ similarity_top_k=30,
+ num_queries=1
+ )
+
+ # Create response synthesizer
+ custom_prompt_template = PromptTemplate(CUSTOM_PROMPT)
+ response_synthesizer = get_response_synthesizer(
+ response_mode=ResponseMode.TREE_SUMMARIZE,
+ text_qa_template=custom_prompt_template
+ )
+
+ # Create new query engine
+ query_engine = RetrieverQueryEngine(
+ retriever=hybrid_retriever,
+ response_synthesizer=response_synthesizer
+ )
+
+ log_message("Query engine успешно пересоздан")
+
+ except Exception as e:
+ log_message(f"Ошибка пересоздания query engine: {str(e)}")
+ raise
+
+def table_to_document(table_data, document_id=None):
+ content = ""
+ if isinstance(table_data, dict):
+ doc_id = document_id or table_data.get('document_id', table_data.get('document', 'Неизвестно'))
+
+ table_num = table_data.get('table_number', 'Неизвестно')
+ table_title = table_data.get('table_title', 'Неизвестно')
+ section = table_data.get('section', 'Неизвестно')
+
+ content += f"Таблица: {table_num}\n"
+ content += f"Название: {table_title}\n"
+ content += f"Документ: {doc_id}\n"
+ content += f"Раздел: {section}\n"
+
+ if 'data' in table_data and isinstance(table_data['data'], list):
+ for row in table_data['data']:
+ if isinstance(row, dict):
+ row_text = " | ".join([f"{k}: {v}" for k, v in row.items()])
+ content += f"{row_text}\n"
+
+ return Document(
+ text=content,
+ metadata={
+ "type": "table",
+ "table_number": table_data.get('table_number', 'unknown'),
+ "table_title": table_data.get('table_title', 'unknown'),
+ "document_id": doc_id or table_data.get('document_id', table_data.get('document', 'unknown')),
+ "section": table_data.get('section', 'unknown')
+ }
+ )
+
+def download_table_data():
+ log_message("Начинаю загрузку табличных данных")
+
+ table_files = []
+ try:
+ files = list_repo_files(repo_id=REPO_ID, repo_type="dataset", token=HF_TOKEN)
+ for file in files:
+ if file.startswith(table_data_dir) and file.endswith('.json'):
+ table_files.append(file)
+
+ log_message(f"Найдено {len(table_files)} JSON файлов с таблицами")
+
+ table_documents = []
+ for file_path in table_files:
+ try:
+ log_message(f"Обрабатываю файл: {file_path}")
+ local_path = hf_hub_download(
+ repo_id=REPO_ID,
+ filename=file_path,
+ local_dir='',
+ repo_type="dataset",
+ token=HF_TOKEN
+ )
+
+ with open(local_path, 'r', encoding='utf-8') as f:
+ table_data = json.load(f)
+
+ if isinstance(table_data, dict):
+ document_id = table_data.get('document', 'unknown')
+
+ if 'sheets' in table_data:
+ for sheet in table_data['sheets']:
+ sheet['document'] = document_id
+ doc = table_to_document(sheet, document_id)
+ table_documents.append(doc)
+ else:
+ doc = table_to_document(table_data, document_id)
+ table_documents.append(doc)
+ elif isinstance(table_data, list):
+ for table_json in table_data:
+ doc = table_to_document(table_json)
+ table_documents.append(doc)
+
+ except Exception as e:
+ log_message(f"Ошибка обработки файла {file_path}: {str(e)}")
+ continue
+
+ log_message(f"Создано {len(table_documents)} документов из таблиц")
+ return table_documents
+
+ except Exception as e:
+ log_message(f"Ошибка загрузки табличных данных: {str(e)}")
+ return []
+
+def download_image_data():
+ log_message("Начинаю загрузку данных изображений")
+
+ image_files = []
+ try:
+ files = list_repo_files(repo_id=REPO_ID, repo_type="dataset", token=HF_TOKEN)
+ for file in files:
+ if file.startswith(image_data_dir) and file.endswith('.csv'):
+ image_files.append(file)
+
+ log_message(f"Найдено {len(image_files)} CSV файлов с изображениями")
+
+ image_documents = []
+ for file_path in image_files:
+ try:
+ log_message(f"Обрабатываю файл изображений: {file_path}")
+ local_path = hf_hub_download(
+ repo_id=REPO_ID,
+ filename=file_path,
+ local_dir='',
+ repo_type="dataset",
+ token=HF_TOKEN
+ )
+
+ df = pd.read_csv(local_path)
+ log_message(f"Загружено {len(df)} записей изображений из файла {file_path}")
+
+ for _, row in df.iterrows():
+ content = f"Изображение: {row.get('№ Изображения', 'Неизвестно')}\n"
+ content += f"Название: {row.get('Название изображения', 'Неизвестно')}\n"
+ content += f"Описание: {row.get('Описание изображение', 'Неизвестно')}\n"
+ content += f"Документ: {row.get('Обозначение документа', 'Неизвестно')}\n"
+ content += f"Раздел: {row.get('Раздел документа', 'Неизвестно')}\n"
+ content += f"Файл: {row.get('Файл изображения', 'Неизвестно')}\n"
+
+ doc = Document(
+ text=content,
+ metadata={
+ "type": "image",
+ "image_number": row.get('№ Изображения', 'unknown'),
+ "document_id": row.get('Обозначение документа', 'unknown'),
+ "file_path": row.get('Файл изображения', 'unknown'),
+ "section": row.get('Раздел документа', 'unknown')
+ }
+ )
+ image_documents.append(doc)
+
+ except Exception as e:
+ log_message(f"Ошибка обработки файла {file_path}: {str(e)}")
+ continue
+
+ log_message(f"Создано {len(image_documents)} документов из изображений")
+ return image_documents
+
+ except Exception as e:
+ log_message(f"Ошибка загрузки данных изображений: {str(e)}")
+ return []
+
+def initialize_models():
+ global query_engine, chunks_df, reranker, vector_index, current_model
+
+ try:
+ log_message("Инициализация системы")
+ os.makedirs(download_dir, exist_ok=True)
+
+ log_message("Загружаю основные файлы")
+ chunks_csv_path = hf_hub_download(
+ repo_id=REPO_ID,
+ filename=chunks_filename,
+ local_dir=download_dir,
+ repo_type="dataset",
+ token=HF_TOKEN
+ )
+
+ log_message("Загружаю данные чанков")
+ chunks_df = pd.read_csv(chunks_csv_path)
+ log_message(f"Загружено {len(chunks_df)} чанков")
+
+ log_message("Инициализирую модели")
+ embed_model = HuggingFaceEmbedding(model_name="sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2")
+ llm = get_llm_model(current_model)
+
+ log_message("Инициализирую переранкер")
+ reranker = CrossEncoder('cross-encoder/ms-marco-MiniLM-L-12-v2')
+
+ Settings.embed_model = embed_model
+ Settings.llm = llm
+
+ text_column = None
+ for col in chunks_df.columns:
+ if 'text' in col.lower() or 'content' in col.lower() or 'chunk' in col.lower():
+ text_column = col
+ break
+
+ if text_column is None:
+ text_column = chunks_df.columns[0]
+
+ log_message(f"Использую колонку: {text_column}")
+
+ log_message("Создаю документы из чанков")
+ documents = []
+ for i, (_, row) in enumerate(chunks_df.iterrows()):
+ doc = Document(
+ text=str(row[text_column]),
+ metadata={
+ "chunk_id": row.get('chunk_id', i),
+ "document_id": row.get('document_id', 'unknown'),
+ "type": "text"
+ }
+ )
+ documents.append(doc)
+
+ log_message(f"Создано {len(documents)} текстовых документов")
+
+ log_message("Добавляю табличные данные")
+ table_documents = download_table_data()
+ documents.extend(table_documents)
+
+ log_message("Добавляю данные изображений")
+ image_documents = download_image_data()
+ documents.extend(image_documents)
+
+ log_message(f"Всего документов: {len(documents)}")
+
+ log_message("Строю векторный индекс")
+ vector_index = VectorStoreIndex.from_documents(documents)
+
+ # Create query engine
+ recreate_query_engine()
+
+ log_message(f"Система успешно инициализирована с моделью: {current_model}")
+ return True
+
+ except Exception as e:
+ log_message(f"Ошибка инициализации: {str(e)}")
+ return False
+
+def rerank_nodes(query, nodes, top_k=10):
+ if not nodes or not reranker:
+ return nodes[:top_k]
+
+ try:
+ log_message(f"Переранжирую {len(nodes)} узлов")
+
+ pairs = []
+ for node in nodes:
+ pairs.append([query, node.text])
+
+ scores = reranker.predict(pairs)
+
+ scored_nodes = list(zip(nodes, scores))
+ scored_nodes.sort(key=lambda x: x[1], reverse=True)
+
+ reranked_nodes = [node for node, score in scored_nodes[:top_k]]
+ log_message(f"Возвращаю топ-{len(reranked_nodes)} переранжированных узлов")
+
+ return reranked_nodes
+ except Exception as e:
+ log_message(f"Ошибка переранжировки: {str(e)}")
+ return nodes[:top_k]
+
+def answer_question(question):
+ global query_engine, chunks_df, current_model
+
+ if query_engine is None:
+ return "Система не инициализирована
", ""
+
+ try:
+ log_message(f"Получен вопрос: {question}")
+ log_message(f"Используется модель: {current_model}")
+ start_time = time.time()
+
+ log_message("Извлекаю релевантные узлы")
+ retrieved_nodes = query_engine.retriever.retrieve(question)
+ log_message(f"Извлечено {len(retrieved_nodes)} узлов")
+
+ log_message("Применяю переранжировку")
+ reranked_nodes = rerank_nodes(question, retrieved_nodes, top_k=10)
+
+ log_message(f"Отправляю запрос в LLM с {len(reranked_nodes)} узлами")
+ response = query_engine.query(question)
+
+ end_time = time.time()
+ processing_time = end_time - start_time
+
+ log_message(f"Обработка завершена за {processing_time:.2f} секунд")
+
+ sources_html = generate_sources_html(reranked_nodes)
+
+ answer_with_time = f"""
+
Ответ (Модель: {current_model}):
+
{response.response}
+
+ Время обработки: {processing_time:.2f} секунд
+
+
"""
+
+ return answer_with_time, sources_html
+
+ except Exception as e:
+ log_message(f"Ошибка обработки вопроса: {str(e)}")
+ error_msg = f"Ошибка обработки вопроса: {str(e)}
"
+ return error_msg, ""
+
+def generate_sources_html(nodes):
+ html = ""
+ html += "
Источники:
"
+
+ for i, node in enumerate(nodes):
+ metadata = node.metadata if hasattr(node, 'metadata') else {}
+ doc_type = metadata.get('type', 'text')
+ doc_id = metadata.get('document_id', 'unknown')
+
+ html += f"
"
+
+ if doc_type == 'text':
+ html += f"
📄 {doc_id}
"
+ elif doc_type == 'table':
+ table_num = metadata.get('table_number', 'unknown')
+ if table_num and table_num != 'unknown':
+ if not table_num.startswith('№'):
+ table_num = f"№{table_num}"
+ html += f"
📊 Таблица {table_num} - {doc_id}
"
+ else:
+ html += f"
📊 Таблица - {doc_id}
"
+ elif doc_type == 'image':
+ image_num = metadata.get('image_number', 'unknown')
+ section = metadata.get('section', '')
+ if image_num and image_num != 'unknown':
+ if not str(image_num).startswith('№'):
+ image_num = f"№{image_num}"
+ html += f"
🖼️ Изображение {image_num} - {doc_id} ({section})
"
+ else:
+ html += f"
🖼️ Изображение - {doc_id} ({section})
"
+
+ if chunks_df is not None and 'file_link' in chunks_df.columns and doc_type == 'text':
+ doc_rows = chunks_df[chunks_df['document_id'] == doc_id]
+ if not doc_rows.empty:
+ file_link = doc_rows.iloc[0]['file_link']
+ html += f"
🔗 Ссылка на документ"
+
+ html += "
"
+
+ html += "
"
+ return html
+
+def create_demo_interface():
+ with gr.Blocks(title="AIEXP - AI Expert для нормативной документации", theme=gr.themes.Soft()) as demo:
+
+ gr.Markdown("""
+ # AIEXP - Artificial Intelligence Expert
+
+ ## Инструмент для работы с нормативной документацией
+ """)
+
+ with gr.Tab("🏠 Поиск по нормативным документам"):
+ gr.Markdown("### Задайте вопрос по нормативной документации")
+
+ # Model selection section
+ with gr.Row():
+ with gr.Column(scale=2):
+ model_dropdown = gr.Dropdown(
+ choices=list(AVAILABLE_MODELS.keys()),
+ value=current_model,
+ label="🤖 Выберите языковую модель",
+ info="Выберите модель для генерации ответов"
+ )
+ with gr.Column(scale=1):
+ switch_btn = gr.Button("🔄 Переключить модель", variant="secondary")
+ model_status = gr.Textbox(
+ value=f"Текущая модель: {current_model}",
+ label="Статус модели",
+ interactive=False
+ )
+
+ with gr.Row():
+ with gr.Column(scale=3):
+ question_input = gr.Textbox(
+ label="Ваш вопрос к базе знаний",
+ placeholder="Введите вопрос по нормативным документам...",
+ lines=3
+ )
+ ask_btn = gr.Button("🔍 Найти ответ", variant="primary", size="lg")
+
+ gr.Examples(
+ examples=[
+ "О чем этот рисунок: ГОСТ Р 50.04.07-2022 Приложение Л. Л.1.5 Рисунок Л.2",
+ "Л.9 Формула в ГОСТ Р 50.04.07 - 2022 что и о чем там?",
+ "Какой стандарт устанавливает порядок признания протоколов испытаний продукции в области использования атомной энергии?",
+ "Кто несет ответственность за организацию и проведение признания протоколов испытаний продукции?",
+ "В каких случаях могут быть признаны протоколы испытаний, проведенные лабораториями?",
+ ],
+ inputs=question_input
+ )
+
+ with gr.Row():
+ with gr.Column(scale=2):
+ answer_output = gr.HTML(
+ label="",
+ value=f"Здесь появится ответ на ваш вопрос...
Текущая модель: {current_model}
",
+ )
+
+ with gr.Column(scale=1):
+ sources_output = gr.HTML(
+ label="",
+ value="Здесь появятся источники...
",
+ )
+
+ # Event handlers
+ def update_model_status(new_model):
+ result = switch_model(new_model)
+ return result
+
+ switch_btn.click(
+ fn=update_model_status,
+ inputs=[model_dropdown],
+ outputs=[model_status]
+ )
+
+ ask_btn.click(
+ fn=answer_question,
+ inputs=[question_input],
+ outputs=[answer_output, sources_output]
+ )
+
+ question_input.submit(
+ fn=answer_question,
+ inputs=[question_input],
+ outputs=[answer_output, sources_output]
+ )
+
+ return demo
+
+if __name__ == "__main__":
+ log_message("Запуск AIEXP - AI Expert для нормативной документации")
+
+ if initialize_models():
+ log_message("Запуск веб-интерфейса")
+ demo = create_demo_interface()
+ demo.launch(
+ server_name="0.0.0.0",
+ server_port=7860,
+ share=True,
+ debug=False
+ )
+ else:
+ log_message("Невозможно запустить приложение из-за ошибки инициализации")
+ sys.exit(1)
\ No newline at end of file
diff --git a/chat_handler.py b/chat_handler.py
new file mode 100644
index 0000000000000000000000000000000000000000..7a4da46d639f03be42e4e14325917515cd3733c4
--- /dev/null
+++ b/chat_handler.py
@@ -0,0 +1,158 @@
+import time
+import logging
+
+logger = logging.getLogger(__name__)
+
+def log_message(message):
+ logger.info(message)
+ print(message, flush=True)
+
+class ChatHandler:
+ def __init__(self, index_retriever):
+ self.index_retriever = index_retriever
+ self.chat_history = []
+
+ def format_section_path(self, metadata):
+ parts = []
+
+ section_id = metadata.get('section_id')
+ if section_id and section_id != 'Unknown':
+ parts.append(section_id)
+
+ subsection_id = metadata.get('subsection_id')
+ if subsection_id and subsection_id != 'Unknown':
+ parts.append(subsection_id)
+
+ sub_subsection_id = metadata.get('sub_subsection_id')
+ if sub_subsection_id and sub_subsection_id != 'Unknown':
+ parts.append(sub_subsection_id)
+
+ sub_sub_subsection_id = metadata.get('sub_sub_subsection_id')
+ if sub_sub_subsection_id and sub_sub_subsection_id != 'Unknown':
+ parts.append(sub_sub_subsection_id)
+
+ return " → ".join(parts) if parts else "Основной раздел"
+
+ def generate_sources_html(self, nodes):
+ html = ""
+ html += "
Источники:
"
+
+ for i, node in enumerate(nodes):
+ metadata = node.metadata if hasattr(node, 'metadata') else {}
+ doc_type = metadata.get('type', 'text')
+ doc_id = metadata.get('document_id', 'unknown')
+
+ html += f"
"
+
+ if doc_type == 'text':
+ section_path = self.format_section_path(metadata)
+ document_name = metadata.get('document_name', doc_id)
+ level = metadata.get('level', 'section')
+
+ html += f"
📄 {doc_id}
"
+ html += f"
{document_name}
"
+ html += f"
📍 {section_path}
"
+ html += f"
Уровень: {level}
"
+
+ elif doc_type == 'table':
+ table_num = metadata.get('table_number', 'unknown')
+ section = metadata.get('section', '')
+ if table_num and table_num != 'unknown':
+ if not table_num.startswith('№'):
+ table_num = f"№{table_num}"
+ html += f"
📊 Таблица {table_num} - {doc_id}
"
+ else:
+ html += f"
📊 Таблица - {doc_id}
"
+ if section:
+ html += f"
📍 {section}
"
+
+ elif doc_type == 'image':
+ image_num = metadata.get('image_number', 'unknown')
+ section = metadata.get('section', '')
+ if image_num and image_num != 'unknown':
+ if not str(image_num).startswith('№'):
+ image_num = f"№{image_num}"
+ html += f"
🖼️ Изображение {image_num} - {doc_id}
"
+ else:
+ html += f"
🖼️ Изображение - {doc_id}
"
+ if section:
+ html += f"
📍 {section}
"
+
+ html += "
"
+
+ html += "
"
+ return html
+
+ def answer_question(self, question):
+ if not self.index_retriever.is_initialized():
+ return "Система не инициализирована
", ""
+
+ try:
+ log_message(f"Получен вопрос: {question}")
+ current_model = self.index_retriever.get_current_model()
+ log_message(f"Используется модель: {current_model}")
+ start_time = time.time()
+
+ retrieved_nodes = self.index_retriever.retrieve_nodes(question)
+
+ if not retrieved_nodes:
+ return "Не удалось найти релевантные документы
", ""
+
+ log_message(f"Отправляю запрос в LLM с {len(retrieved_nodes)} узлами")
+ response = self.index_retriever.query_engine.query(question)
+
+ end_time = time.time()
+ processing_time = end_time - start_time
+
+ log_message(f"Обработка завершена за {processing_time:.2f} секунд")
+
+ self.chat_history.append({
+ "question": question,
+ "answer": response.response,
+ "model": current_model,
+ "processing_time": processing_time,
+ "nodes_count": len(retrieved_nodes)
+ })
+
+ sources_html = self.generate_sources_html(retrieved_nodes)
+
+ answer_with_time = f"""
+
Ответ (Модель: {current_model}):
+
{response.response}
+
+ Время обработки: {processing_time:.2f} секунд | Источников: {len(retrieved_nodes)}
+
+
"""
+
+ return answer_with_time, sources_html
+
+ except Exception as e:
+ log_message(f"Ошибка обработки вопроса: {str(e)}")
+ error_msg = f"Ошибка обработки вопроса: {str(e)}
"
+ return error_msg, ""
+
+ def get_chat_history(self):
+ return self.chat_history
+
+ def clear_history(self):
+ self.chat_history = []
+ log_message("История чата очищена")
+
+ def get_history_html(self):
+ if not self.chat_history:
+ return "История пуста
"
+
+ html = ""
+ html += "
История чата:
"
+
+ for i, entry in enumerate(reversed(self.chat_history[-10:])):
+ html += f"
"
+ html += f"
Вопрос {len(self.chat_history) - i}:
"
+ html += f"
{entry['question']}
"
+ html += f"
Ответ ({entry['model']}):
"
+ html += f"
{entry['answer'][:300]}{'...' if len(entry['answer']) > 300 else ''}
"
+ html += f"
Время: {entry['processing_time']:.2f}с | Источников: {entry['nodes_count']}
"
+ html += "
"
+
+ html += "
"
+ return html
\ No newline at end of file
diff --git a/config.py b/config.py
index b27c1d3e0b79e62b697427dd3ca0e0a9eecb9172..e34c7b279c6992aa0e1c341f40c6d233daa6cc3e 100644
--- a/config.py
+++ b/config.py
@@ -1,226 +1,121 @@
import os
EMBEDDING_MODEL = "sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2"
-RETRIEVER_TOP_K = 25
+RETRIEVER_TOP_K = 15
SIMILARITY_THRESHOLD = 0.7
RAG_FILES_DIR = "rag_files"
-PROCESSED_DATA_FILE = "rag_files/processed_chunks.csv"
+PROCESSED_DATA_FILE = "processed_chunks.csv"
-REPO_ID = "MrSimple01/AIEXP_RAG_FILES"
-faiss_index_filename = "faiss_index.index"
-chunks_filename = "processed_chunks.csv"
-
-extracted_files = 'cleaned_extracted_tokens.csv'
-
-download_dir = "rag_files"
-HF_TOKEN = os.getenv('HF_TOKEN')
GOOGLE_API_KEY = os.getenv('GOOGLE_API_KEY')
+OPENAI_API_KEY = os.getenv('OPENAI_API_KEY')
+HF_REPO_ID = "MrSimple01/AIEXP_RAG_FILES"
+HF_TOKEN = os.getenv('HF_TOKEN')
+
+# Available models configuration
+AVAILABLE_MODELS = {
+ "Gemini 2.5 Flash": {
+ "provider": "google",
+ "model_name": "gemini-2.5-flash",
+ "api_key": GOOGLE_API_KEY
+ },
+ "Gemini 2.5 Pro": {
+ "provider": "google",
+ "model_name": "gemini-2.5-pro",
+ "api_key": GOOGLE_API_KEY
+ },
+ "GPT-4o": {
+ "provider": "openai",
+ "model_name": "gpt-4o",
+ "api_key": OPENAI_API_KEY
+ },
+ "GPT-4o Mini": {
+ "provider": "openai",
+ "model_name": "gpt-4o-mini",
+ "api_key": OPENAI_API_KEY
+ },
+ "GPT-5": {
+ "provider": "openai",
+ "model_name": "gpt-5",
+ "api_key": OPENAI_API_KEY
+ }
+}
+
+DEFAULT_MODEL = "Gemini 2.5 Flash"
CHUNK_SIZE = 1024
CHUNK_OVERLAP = 256
-
CUSTOM_PROMPT = """
-You are a highly specialized Document Analysis Assistant (AIEXP). Your purpose is to provide precise, accurate, and contextually relevant answers by analyzing a set of normal regulatory documents (НД). Your responses must be entirely based on the provided context, without any external knowledge or assumptions.
-
-**Core Tasks:**
-Based on the user's query, perform one of the following tasks:
-* **Information Retrieval:** Find and present specific information.
-* **Summarization:** Provide a concise summary of a document or a section.
-* **Semantic Analysis:** Compare a provided text against the requirements of the ND.
-* **Action Planning:** Create a step-by-step plan based on ND requirements.
-
-**Strict Rules for Response Generation:**
-1. Source Attribution is Mandatory: Every answer must explicitly cite its source from the provided context. Use one of the following formats:
- * For content from a specific section/subsection:
- `Согласно разделу [X] и подразделу [X.X]: [Ваш ответ]`
- * For content that is not part of a specific subsection (e.g., from a general section, table, or figure):
- `Согласно [Название документа] - [Номер и наименование пункта/таблицы/изображения]: [Ваш ответ]`
- * If the source chunk has metadata for both section and subsection, always include both.
- * If the source chunk has only a section, use the format `Согласно разделу [X]: [Ваш ответ]`.
-2. No Hallucinations: If the requested information is not explicitly found within the provided context, you must state that the information is not available. **Do not** attempt to infer, guess, or create a response. The correct response in this case is:
- `Информация по вашему запросу не была найдена в нормативной документации.`
-3. Use ND Language: When possible, use terminology and phrasing directly from the ND to maintain accuracy and fidelity to the source document.
-4. Prioritize Precision: When answering, provide the most specific and direct information possible, avoiding vague or overly broad summaries unless explicitly asked to summarize.
-
-**Context:**
-{context_str}
-
-**Question:**
-{query_str}
-
-**Answer:**
-"""
-
-
-# CUSTOM_PROMPT_NEW = """
-# Вы являетесь высокоспециализированным Ассистентом для анализа документов (AIEXP). Ваша цель - предоставлять точные, корректные и контекстно релевантные ответы на основе анализа нормативной документации (НД). Все ваши ответы должны основываться исключительно на предоставленном контексте без использования внешних знаний или предположений.
-
-# ОБЯЗАТЕЛЬНАЯ ЗАДАЧА - ИЗВЛЕЧЕНИЕ СТРУКТУРНОЙ ИНФОРМАЦИИ:
-# Для каждого ответа ОБЯЗАТЕЛЬНО определите и укажите:
-# 1. НОМЕР РАЗДЕЛА/ПОДРАЗДЕЛА/ПУНКТА (например: "3.1", "4.2.3", "Приложение А.1")
-# 2. НАЗВАНИЕ РАЗДЕЛА/ПОДРАЗДЕЛА/ПУНКТА (например: "Общие требования", "Процедура испытаний")
-# 3. НАЗВАНИЕ ДОКУМЕНТА-ИСТОЧНИКА
-# 4. ССЫЛКУ НА ДОКУМЕНТ (если доступна в метаданных)
-
-# ФОРМАТ ОБЯЗАТЕЛЬНОГО УКАЗАНИЯ ИСТОЧНИКОВ:
-# - **📍 Источник**: [Название документа]
-# - **📄 Раздел**: [Номер] - [Название раздела/подраздела]
-# - **🔗 Ссылка**: [ссылка на документ, если доступна]
-
-# ИНСТРУКЦИИ ПО ИЗВЛЕЧЕНИЮ СТРУКТУРНОЙ ИНФОРМАЦИИ:
-# 1. Ищите в тексте паттерны нумерации: "1.", "1.1.", "3.2.4.", "Приложение А", "Таблица 1", "Рисунок 2"
-# 2. Находите заголовки разделов после номеров (обычно выделены или идут сразу после номера)
-# 3. Если номер раздела не найден, ищите контекстные указания: "в данном разделе", "настоящий пункт"
-# 4. При отсутствии явной нумерации указывайте: "Раздел не определен"
-
-# ОПРЕДЕЛЕНИЕ ТИПА ЗАДАЧИ:
-# Проанализируйте запрос пользователя и определите тип задачи:
-
-# 1. КРАТКОЕ САММАРИ (ключевые слова: "кратко", "суммировать", "резюме", "основные моменты", "в двух словах"):
-# - Предоставьте структурированное резюме запрашиваемого раздела/пункта
-# - Выделите ключевые требования, процедуры или положения
-# - Используйте нумерованный список для лучшей читаемости
-# - Сохраняйте терминологию НД
-
-# 2. ПОИСК ДОКУМЕНТА И ПУНКТА (ключевые слова: "найти", "где", "какой документ", "в каком разделе", "ссылка"):
-# - Укажите конкретный документ и его структурное расположение
-# - Предоставьте точные номера разделов/подразделов/пунктов с их названиями
-# - Процитируйте релевантные фрагменты
-# - Если найдено несколько документов, перечислите все с указанием специфики каждого
-
-# 3. ПРОВЕРКА КОРРЕКТНОСТИ (ключевые слова: "правильно ли", "соответствует ли", "проверить", "корректно", "нарушение"):
-# - Сопоставьте предоставленную информацию с требованиями НД
-# - Четко укажите: "СООТВЕТСТВУЕТ" или "НЕ СООТВЕТСТВУЕТ"
-# - Перечислите конкретные требования НД с указанием разделов
-# - Укажите выявленные расхождения или подтвердите соответствие
-# - Процитируйте релевантные пункты НД с их номерами и названиями
-
-# 4. ПЛАН ДЕЙСТВИЙ (ключевые слова: "план", "алгоритм", "последовательность", "как действовать", "пошагово"):
-# - Создайте пронумерованный пошаговый план
-# - Каждый шаг должен содержать ссылку на соответствующий пункт НД с номером и названием
-# - Укажите необходимые документы или формы
-# - Добавьте временные рамки, если они указаны в НД
-# - Выделите критические требования или ограничения
-
-# ПРАВИЛА ФОРМИРОВАНИЯ ОТВЕТОВ:
-
-# 1. ОБЯЗАТЕЛЬНОЕ УКАЗАНИЕ ИСТОЧНИКОВ С СТРУКТУРНОЙ ИНФОРМАЦИЕЙ:
-# Начинайте каждый ответ с блока источников в формате:
-
-# **📍 Источник**: [Название документа]
-# **📄 Раздел**: [Номер] - [Название раздела/подраздела]
-# **🔗 Ссылка**: [ссылка, если доступна]
-
-# Затем продолжайте основной ответ.
-
-# 2. В ТЕКСТЕ ОТВЕТА используйте конкретные ссылки:
-# - "Согласно пункту 3.1 'Общие требования': [Ваш ответ]"
-# - "В разделе 4.2 'Процедура испытаний' указано: [Ваш ответ]"
-# - "Приложение А.1 'Формы документов' содержит: [Ваш ответ]"
-
-# 3. СТРОГОЕ СЛЕДОВАНИЕ КОНТЕКСТУ:
-# - Если информация не найдена: "Информация по вашему запросу не была найдена в нормативной документации."
-# - Если структурная информация не определена: "**📄 Раздел**: Не определен в предоставленном контексте"
-# - Не делайте предположений или выводов за пределами предоставленного контекста
-
-# 4. ИСПОЛЬЗОВАНИЕ ТЕРМИНОЛОГИИ НД:
-# - Применяйте официальную терминологию из документов
-# - Сохраняйте оригинальные формулировки ключевых требований
-# - При необходимости разъясняйте специальные термины на основе НД
-
-# 5. СТРУКТУРИРОВАНИЕ ОТВЕТОВ:
-# - Всегда начинайте с блока источников
-# - Для саммари: используйте маркированные или нумерованные списки
-# - Для проверки: четкая структура "Требование → Соответствие/Несоответствие"
-# - Для планов: пронумерованные шаги с подзадачами при необходимости
-# - Для поиска: указание полной иерархии документа
-
-# 6. ДОПОЛНИТЕЛЬНЫЕ РЕКОМЕНДАЦИИ:
-# - При множественных релевантных источниках - укажите все с их структурной информацией
-# - Выделяйте критически важные требования
-# - Указывайте альтернативные процедуры, если они предусмотрены НД
-# - Если в одном ответе используется информация из разных разделов, указывайте все релевантные разделы
-
-# ПРИМЕРЫ ПРАВИЛЬНОГО ФОРМАТА ОТВЕТА:
-
-# **📍 Источник**: ГОСТ Р 58771-2019
-# **📄 Раздел**: 4.2 - Требования к испытательным лабораториям
-# **🔗 Ссылка**: [ссылка на документ]
-
-# Согласно пункту 4.2 "Требования к испытательным лабораториям", лаборатория должна соответствовать следующим критериям:
-# 1. Наличие аккредитации...
-# 2. Квалифицированный персонал...
-
-# Контекст: {context_str}
-
-# Вопрос: {query_str}
-
-# Ответ:
-# """
-
-CUSTOM_PROMPT_NEW = """
-Вы являетесь высокоспециализированным Ассистентом для анализа документов (AIEXP). Ваша цель - предоставлять точные, корректные и контекстно релевантные ответы на основе анализа нормативной документации (НД). Все ваши ответы должны основываться исключительно на предоставленном контексте без использования внешних знаний или предположений.
-
-ОПРЕДЕЛЕНИЕ ТИПА ЗАДАЧИ:
-Проанализируйте запрос пользователя и определите тип задачи:
-
-1. КРАТКОЕ САММАРИ (ключевые слова: "кратко", "суммировать", "резюме", "основные моменты", "в двух словах"):
- - Предоставьте структурированное резюме запрашиваемого раздела/пункта
- - Выделите ключевые требования, процедуры или положения
- - Используйте нумерованный список для лучшей читаемости
- - Сохраняйте терминологию НД
-
-2. ПОИСК ДОКУМЕНТА И ПУНКТА (ключевые слова: "найти", "где", "какой документ", "в каком разделе", "ссылка"):
- - Укажите конкретный документ и его структурное расположение
- - Предоставьте точные номера разделов/подразделов/пунктов
- - Процитируйте релевантные фрагменты
- - Если найдено несколько документов, перечислите все с указанием специфики каждого
-
-3. ПРОВЕРКА КОРРЕКТНОСТИ (ключевые слова: "правильно ли", "соответствует ли", "проверить", "корректно", "нарушение"):
- - Сопоставьте предоставленную информацию с требованиями НД
- - Четко укажите: "СООТВЕТСТВУЕТ" или "НЕ СООТВЕТСТВУЕТ"
- - Перечислите конкретные требования НД
- - Укажите выявленные расхождения или подтвердите соответствие
- - Процитируйте релевантные пункты НД
-
-4. ПЛАН ДЕЙСТВИЙ (ключевые слова: "план", "алгоритм", "последовательность", "как действовать", "пошагово"):
- - Создайте пронумерованный пошаговый план
- - Каждый шаг должен содержать ссылку на соответствующий пункт НД
- - Укажите необходимые документы или формы
- - Добавьте временные рамки, если они указаны в НД
- - Выделите критические требования или ограничения
+Вы являетесь высокоспециализированным Ассистентом для анализа нормативных документов (AIEXP). Ваша цель - предоставлять точные, корректные и контекстно релевантные ответы исключительно на основе предоставленного контекста из нормативной документации.
+
+ПРАВИЛА АНАЛИЗА ЗАПРОСА:
+
+1. ПРЯМЫЕ ВОПРОСЫ БЕЗ ДОКУМЕНТАЛЬНОГО КОНТЕКСТА:
+ Если пользователь задает вопрос типа "В каких случаях могут быть признаны протоколы испытаний?" без предоставления дополнительных документов, найдите соответствующую информацию в доступном контексте и предоставьте полный ответ с указанием источников.
+
+2. ОПРЕДЕЛЕНИЕ ТИПА ЗАДАЧИ:
+
+ а) ПОИСК И ОТВЕТ НА ВОПРОС (ключевые слова: "в каких случаях", "когда", "кто", "что", "как", "почему"):
+ - Найдите релевантную информацию в контексте
+ - Предоставьте развернутый ответ
+ - Обязательно укажите конкретные документы и разделы
+ - Процитируйте ключевые положения
+
+ б) КРАТКОЕ САММАРИ (ключевые слова: "кратко", "суммировать", "резюме", "основные моменты"):
+ - Предоставьте структурированное резюме
+ - Выделите ключевые требования
+ - Используйте нумерованный список
+
+ в) ПОИСК ДОКУМЕНТА И ПУНКТА (ключевые слова: "найти", "где", "какой документ", "в каком разделе"):
+ - Укажите конкретный документ и структурное расположение
+ - Предоставьте точные номера разделов/пунктов
+
+ г) ПРОВЕРКА КОРРЕКТНОСТИ (ключевые слова: "правильно ли", "соответствует ли", "проверить"):
+ - Четко укажите: "СООТВЕТСТВУЕТ" или "НЕ СООТВЕТСТВУЕТ"
+ - Перечислите конкретные требования
+
+ д) ПЛАН ДЕЙСТВИЙ (ключевые слова: "план", "алгоритм", "пошагово"):
+ - Создайте пронумерованный план
+ - Укажите ссылки на соответствующие пункты НД
ПРАВИЛА ФОРМИРОВАНИЯ ОТВЕТОВ:
1. ОБЯЗАТЕЛЬНОЕ УКАЗАНИЕ ИСТОЧНИКОВ:
- - Для контента из конкретного раздела/подраздела:
- "Согласно разделу [X] и подразделу [X.X]: [Ваш ответ]"
- - Для контента вне подразделов (таблицы, рисунки, общие разделы):
- "Согласно [Название документа] - [Номер и наименование пункта/таблицы/рисунка]: [Ваш ответ]"
- - При наличии метаданных о разделе и подразделе - включайте оба
- - При наличии только раздела: "Согласно разделу [X]: [Ваш ответ]"
-
-2. СТРОГОЕ СЛЕДОВАНИЕ КОНТЕКСТУ:
- - Если информация не найдена: "Информация по вашему запросу не была найдена в нормативной документации."
- - Не делайте предположений или выводов за пределами предоставленного контекста
+ - Всегда указывайте конкретный документ (ГОСТ, раздел, пункт)
+ - Формат: "Согласно [Документ], раздел [X], пункт [X.X]: [информация]"
+ - При цитировании: используйте кавычки и точные ссылки
+
+2. СТРУКТУРА ОТВЕТА:
+ - Начинайте с прямого ответа на вопрос
+ - Затем указывайте нормативные основания
+ - Завершайте ссылками на конкретные документы и разделы
+
+3. РАБОТА С КОНТЕКСТОМ:
+ - Если информация найдена в контексте - предоставьте полный ответ
+ - Если информация не найдена: "Информация по вашему запросу не найдена в доступной нормативной документации"
+ - Не делайте предположений за пределами контекста
- Не используйте общие знания
-3. ИСПОЛЬЗОВАНИЕ ТЕРМИНОЛОГИИ НД:
- - Применяйте официальную терминологию из документов
- - Сохраняйте оригинальные формулировки ключевых требований
- - При необходимости разъясняйте специальные термины на основе НД
-
-4. СТРУКТУРИРОВАНИЕ ОТВЕТОВ:
- - Для саммари: используйте маркированные или нумерованные списки
- - Для проверки: четкая структура "Требование → Соответствие/Несоответствие"
- - Для планов: пронумерованные шаги с подзадачами при необходимости
- - Для поиска: указание иерархии документа
+4. ТЕРМИНОЛОГИЯ И ЦИТИРОВАНИЕ:
+ - Сохраняйте официальную терминологию НД
+ - Цитируйте точные формулировки ключевых требований
+ - При множественных источниках - укажите все релевантные
-5. ДОПОЛНИТЕЛЬНЫЕ РЕКОМЕНДАЦИИ:
- - При множественных релевантных источниках - укажите все
+5. ФОРМАТИРОВАНИЕ:
+ - Для перечислений: используйте нумерованные списки
- Выделяйте критически важные требования
- - Указывайте альтернативные процедуры, если они предусмотрены НД
+ - Структурируйте ответ логически
+
+ПРИМЕРЫ ПРАВИЛЬНОГО ФОРМАТИРОВАНИЯ:
+
+Вопрос: "В каких случаях могут быть признаны протоколы испытаний?"
+Ответ: "Протоколы испытаний могут быть признаны в следующих случаях:
+
+1. Если они проведены испытательными лабораториями (центрами), аккредитованными в области использования атомной энергии (ГОСТ Р 50.08.04-2022, раздел 6 )
+2. Если они проведены лабораториями, аккредитованными национальным органом Российской Федерации по аккредитации (ГОСТ Р 50.08.04-2022, пункт 4.1)
+3. Если лаборатории прошли оценку состояния измерений
+
+Также допускается признание результатов испытаний, выполненных испытательными центрами (лабораториями), аккредитованными в национальных системах аккредитации страны изготовителя (ГОСТ Р 50.04.08-2019)."
Контекст: {context_str}
diff --git a/config_1.py b/config_1.py
new file mode 100644
index 0000000000000000000000000000000000000000..b27c1d3e0b79e62b697427dd3ca0e0a9eecb9172
--- /dev/null
+++ b/config_1.py
@@ -0,0 +1,230 @@
+import os
+
+EMBEDDING_MODEL = "sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2"
+RETRIEVER_TOP_K = 25
+SIMILARITY_THRESHOLD = 0.7
+RAG_FILES_DIR = "rag_files"
+PROCESSED_DATA_FILE = "rag_files/processed_chunks.csv"
+
+REPO_ID = "MrSimple01/AIEXP_RAG_FILES"
+faiss_index_filename = "faiss_index.index"
+chunks_filename = "processed_chunks.csv"
+
+extracted_files = 'cleaned_extracted_tokens.csv'
+
+download_dir = "rag_files"
+HF_TOKEN = os.getenv('HF_TOKEN')
+GOOGLE_API_KEY = os.getenv('GOOGLE_API_KEY')
+
+CHUNK_SIZE = 1024
+CHUNK_OVERLAP = 256
+
+
+CUSTOM_PROMPT = """
+You are a highly specialized Document Analysis Assistant (AIEXP). Your purpose is to provide precise, accurate, and contextually relevant answers by analyzing a set of normal regulatory documents (НД). Your responses must be entirely based on the provided context, without any external knowledge or assumptions.
+
+**Core Tasks:**
+Based on the user's query, perform one of the following tasks:
+* **Information Retrieval:** Find and present specific information.
+* **Summarization:** Provide a concise summary of a document or a section.
+* **Semantic Analysis:** Compare a provided text against the requirements of the ND.
+* **Action Planning:** Create a step-by-step plan based on ND requirements.
+
+**Strict Rules for Response Generation:**
+1. Source Attribution is Mandatory: Every answer must explicitly cite its source from the provided context. Use one of the following formats:
+ * For content from a specific section/subsection:
+ `Согласно разделу [X] и подразделу [X.X]: [Ваш ответ]`
+ * For content that is not part of a specific subsection (e.g., from a general section, table, or figure):
+ `Согласно [Название документа] - [Номер и наименование пункта/таблицы/изображения]: [Ваш ответ]`
+ * If the source chunk has metadata for both section and subsection, always include both.
+ * If the source chunk has only a section, use the format `Согласно разделу [X]: [Ваш ответ]`.
+2. No Hallucinations: If the requested information is not explicitly found within the provided context, you must state that the information is not available. **Do not** attempt to infer, guess, or create a response. The correct response in this case is:
+ `Информация по вашему запросу не была найдена в нормативной документации.`
+3. Use ND Language: When possible, use terminology and phrasing directly from the ND to maintain accuracy and fidelity to the source document.
+4. Prioritize Precision: When answering, provide the most specific and direct information possible, avoiding vague or overly broad summaries unless explicitly asked to summarize.
+
+**Context:**
+{context_str}
+
+**Question:**
+{query_str}
+
+**Answer:**
+"""
+
+
+# CUSTOM_PROMPT_NEW = """
+# Вы являетесь высокоспециализированным Ассистентом для анализа документов (AIEXP). Ваша цель - предоставлять точные, корректные и контекстно релевантные ответы на основе анализа нормативной документации (НД). Все ваши ответы должны основываться исключительно на предоставленном контексте без использования внешних знаний или предположений.
+
+# ОБЯЗАТЕЛЬНАЯ ЗАДАЧА - ИЗВЛЕЧЕНИЕ СТРУКТУРНОЙ ИНФОРМАЦИИ:
+# Для каждого ответа ОБЯЗАТЕЛЬНО определите и укажите:
+# 1. НОМЕР РАЗДЕЛА/ПОДРАЗДЕЛА/ПУНКТА (например: "3.1", "4.2.3", "Приложение А.1")
+# 2. НАЗВАНИЕ РАЗДЕЛА/ПОДРАЗДЕЛА/ПУНКТА (например: "Общие требования", "Процедура испытаний")
+# 3. НАЗВАНИЕ ДОКУМЕНТА-ИСТОЧНИКА
+# 4. ССЫЛКУ НА ДОКУМЕНТ (если доступна в метаданных)
+
+# ФОРМАТ ОБЯЗАТЕЛЬНОГО УКАЗАНИЯ ИСТОЧНИКОВ:
+# - **📍 Источник**: [Название документа]
+# - **📄 Раздел**: [Номер] - [Название раздела/подраздела]
+# - **🔗 Ссылка**: [ссылка на документ, если доступна]
+
+# ИНСТРУКЦИИ ПО ИЗВЛЕЧЕНИЮ СТРУКТУРНОЙ ИНФОРМАЦИИ:
+# 1. Ищите в тексте паттерны нумерации: "1.", "1.1.", "3.2.4.", "Приложение А", "Таблица 1", "Рисунок 2"
+# 2. Находите заголовки разделов после номеров (обычно выделены или идут сразу после номера)
+# 3. Если номер раздела не найден, ищите контекстные указания: "в данном разделе", "настоящий пункт"
+# 4. При отсутствии явной нумерации указывайте: "Раздел не определен"
+
+# ОПРЕДЕЛЕНИЕ ТИПА ЗАДАЧИ:
+# Проанализируйте запрос пользователя и определите тип задачи:
+
+# 1. КРАТКОЕ САММАРИ (ключевые слова: "кратко", "суммировать", "резюме", "основные моменты", "в двух словах"):
+# - Предоставьте структурированное резюме запрашиваемого раздела/пункта
+# - Выделите ключевые требования, процедуры или положения
+# - Используйте нумерованный список для лучшей читаемости
+# - Сохраняйте терминологию НД
+
+# 2. ПОИСК ДОКУМЕНТА И ПУНКТА (ключевые слова: "найти", "где", "какой документ", "в каком разделе", "ссылка"):
+# - Укажите конкретный документ и его структурное расположение
+# - Предоставьте точные номера разделов/подразделов/пунктов с их названиями
+# - Процитируйте релевантные фрагменты
+# - Если найдено несколько документов, перечислите все с указанием специфики каждого
+
+# 3. ПРОВЕРКА КОРРЕКТНОСТИ (ключевые слова: "правильно ли", "соответствует ли", "проверить", "корректно", "нарушение"):
+# - Сопоставьте предоставленную информацию с требованиями НД
+# - Четко укажите: "СООТВЕТСТВУЕТ" или "НЕ СООТВЕТСТВУЕТ"
+# - Перечислите конкретные требования НД с указанием разделов
+# - Укажите выявленные расхождения или подтвердите соответствие
+# - Процитируйте релевантные пункты НД с их номерами и названиями
+
+# 4. ПЛАН ДЕЙСТВИЙ (ключевые слова: "план", "алгоритм", "последовательность", "как действовать", "пошагово"):
+# - Создайте пронумерованный пошаговый план
+# - Каждый шаг должен содержать ссылку на соответствующий пункт НД с номером и названием
+# - Укажите необходимые документы или формы
+# - Добавьте временные рамки, если они указаны в НД
+# - Выделите критические требования или ограничения
+
+# ПРАВИЛА ФОРМИРОВАНИЯ ОТВЕТОВ:
+
+# 1. ОБЯЗАТЕЛЬНОЕ УКАЗАНИЕ ИСТОЧНИКОВ С СТРУКТУРНОЙ ИНФОРМАЦИЕЙ:
+# Начинайте каждый ответ с блока источников в формате:
+
+# **📍 Источник**: [Название документа]
+# **📄 Раздел**: [Номер] - [Название раздела/подраздела]
+# **🔗 Ссылка**: [ссылка, если доступна]
+
+# Затем продолжайте основной ответ.
+
+# 2. В ТЕКСТЕ ОТВЕТА используйте конкретные ссылки:
+# - "Согласно пункту 3.1 'Общие требования': [Ваш ответ]"
+# - "В разделе 4.2 'Процедура испытаний' указано: [Ваш ответ]"
+# - "Приложение А.1 'Формы документов' содержит: [Ваш ответ]"
+
+# 3. СТРОГОЕ СЛЕДОВАНИЕ КОНТЕКСТУ:
+# - Если информация не найдена: "Информация по вашему запросу не была найдена в нормативной документации."
+# - Если структурная информация не определена: "**📄 Раздел**: Не определен в предоставленном контексте"
+# - Не делайте предположений или выводов за пределами предоставленного контекста
+
+# 4. ИСПОЛЬЗОВАНИЕ ТЕРМИНОЛОГИИ НД:
+# - Применяйте официальную терминологию из документов
+# - Сохраняйте оригинальные формулировки ключевых требований
+# - При необходимости разъясняйте специальные термины на основе НД
+
+# 5. СТРУКТУРИРОВАНИЕ ОТВЕТОВ:
+# - Всегда начинайте с блока источников
+# - Для саммари: используйте маркированные или нумерованные списки
+# - Для проверки: четкая структура "Требование → Соответствие/Несоответствие"
+# - Для планов: пронумерованные шаги с подзадачами при необходимости
+# - Для поиска: указание полной иерархии документа
+
+# 6. ДОПОЛНИТЕЛЬНЫЕ РЕКОМЕНДАЦИИ:
+# - При множественных релевантных источниках - укажите все с их структурной информацией
+# - Выделяйте критически важные требования
+# - Указывайте альтернативные процедуры, если они предусмотрены НД
+# - Если в одном ответе используется информация из разных разделов, указывайте все релевантные разделы
+
+# ПРИМЕРЫ ПРАВИЛЬНОГО ФОРМАТА ОТВЕТА:
+
+# **📍 Источник**: ГОСТ Р 58771-2019
+# **📄 Раздел**: 4.2 - Требования к испытательным лабораториям
+# **🔗 Ссылка**: [ссылка на документ]
+
+# Согласно пункту 4.2 "Требования к испытательным лабораториям", лаборатория должна соответствовать следующим критериям:
+# 1. Наличие аккредитации...
+# 2. Квалифицированный персонал...
+
+# Контекст: {context_str}
+
+# Вопрос: {query_str}
+
+# Ответ:
+# """
+
+CUSTOM_PROMPT_NEW = """
+Вы являетесь высокоспециализированным Ассистентом для анализа документов (AIEXP). Ваша цель - предоставлять точные, корректные и контекстно релевантные ответы на основе анализа нормативной документации (НД). Все ваши ответы должны основываться исключительно на предоставленном контексте без использования внешних знаний или предположений.
+
+ОПРЕДЕЛЕНИЕ ТИПА ЗАДАЧИ:
+Проанализируйте запрос пользователя и определите тип задачи:
+
+1. КРАТКОЕ САММАРИ (ключевые слова: "кратко", "суммировать", "резюме", "основные моменты", "в двух словах"):
+ - Предоставьте структурированное резюме запрашиваемого раздела/пункта
+ - Выделите ключевые требования, процедуры или положения
+ - Используйте нумерованный список для лучшей читаемости
+ - Сохраняйте терминологию НД
+
+2. ПОИСК ДОКУМЕНТА И ПУНКТА (ключевые слова: "найти", "где", "какой документ", "в каком разделе", "ссылка"):
+ - Укажите конкретный документ и его структурное расположение
+ - Предоставьте точные номера разделов/подразделов/пунктов
+ - Процитируйте релевантные фрагменты
+ - Если найдено несколько документов, перечислите все с указанием специфики каждого
+
+3. ПРОВЕРКА КОРРЕКТНОСТИ (ключевые слова: "правильно ли", "соответствует ли", "проверить", "корректно", "нарушение"):
+ - Сопоставьте предоставленную информацию с требованиями НД
+ - Четко укажите: "СООТВЕТСТВУЕТ" или "НЕ СООТВЕТСТВУЕТ"
+ - Перечислите конкретные требования НД
+ - Укажите выявленные расхождения или подтвердите соответствие
+ - Процитируйте релевантные пункты НД
+
+4. ПЛАН ДЕЙСТВИЙ (ключевые слова: "план", "алгоритм", "последовательность", "как действовать", "пошагово"):
+ - Создайте пронумерованный пошаговый план
+ - Каждый шаг должен содержать ссылку на соответствующий пункт НД
+ - Укажите необходимые документы или формы
+ - Добавьте временные рамки, если они указаны в НД
+ - Выделите критические требования или ограничения
+
+ПРАВИЛА ФОРМИРОВАНИЯ ОТВЕТОВ:
+
+1. ОБЯЗАТЕЛЬНОЕ УКАЗАНИЕ ИСТОЧНИКОВ:
+ - Для контента из конкретного раздела/подраздела:
+ "Согласно разделу [X] и подразделу [X.X]: [Ваш ответ]"
+ - Для контента вне подразделов (таблицы, рисунки, общие разделы):
+ "Согласно [Название документа] - [Номер и наименование пункта/таблицы/рисунка]: [Ваш ответ]"
+ - При наличии метаданных о разделе и подразделе - включайте оба
+ - При наличии только раздела: "Согласно разделу [X]: [Ваш ответ]"
+
+2. СТРОГОЕ СЛЕДОВАНИЕ КОНТЕКСТУ:
+ - Если информация не найдена: "Информация по вашему запросу не была найдена в нормативной документации."
+ - Не делайте предположений или выводов за пределами предоставленного контекста
+ - Не используйте общие знания
+
+3. ИСПОЛЬЗОВАНИЕ ТЕРМИНОЛОГИИ НД:
+ - Применяйте официальную терминологию из документов
+ - Сохраняйте оригинальные формулировки ключевых требований
+ - При необходимости разъясняйте специальные термины на основе НД
+
+4. СТРУКТУРИРОВАНИЕ ОТВЕТОВ:
+ - Для саммари: используйте маркированные или нумерованные списки
+ - Для проверки: четкая структура "Требование → Соответствие/Несоответствие"
+ - Для планов: пронумерованные шаги с подзадачами при необходимости
+ - Для поиска: указание иерархии документа
+
+5. ДОПОЛНИТЕЛЬНЫЕ РЕКОМЕНДАЦИИ:
+ - При множественных релевантных источниках - укажите все
+ - Выделяйте критически важные требования
+ - Указывайте альтернативные процедуры, если они предусмотрены НД
+
+Контекст: {context_str}
+
+Вопрос: {query_str}
+
+Ответ:
+"""
\ No newline at end of file
diff --git a/documents_prep.py b/documents_prep.py
new file mode 100644
index 0000000000000000000000000000000000000000..47728d3ae9de98a7b250847ccc55edad5adf7a25
--- /dev/null
+++ b/documents_prep.py
@@ -0,0 +1,303 @@
+import json
+import pandas as pd
+import os
+from huggingface_hub import hf_hub_download, list_repo_files
+from llama_index.core import Document
+import logging
+
+logger = logging.getLogger(__name__)
+
+def log_message(message):
+ logger.info(message)
+ print(message, flush=True)
+
+class DocumentsPreparation:
+ def __init__(self, repo_id, hf_token):
+ self.repo_id = repo_id
+ self.hf_token = hf_token
+ self.json_files_dir = "JSON"
+ self.table_data_dir = "Табличные данные_JSON"
+ self.image_data_dir = "Изображения"
+ self.download_dir = "rag_files"
+
+ def extract_text_from_json(self, data, document_id, document_name):
+ documents = []
+
+ if 'sections' in data:
+ for section in data['sections']:
+ section_id = section.get('section_id', 'Unknown')
+ section_text = section.get('section_text', '')
+
+ if section_text.strip():
+ doc = Document(
+ text=section_text,
+ metadata={
+ "type": "text",
+ "document_id": document_id,
+ "document_name": document_name,
+ "section_id": section_id,
+ "level": "section"
+ }
+ )
+ documents.append(doc)
+
+ if 'subsections' in section:
+ for subsection in section['subsections']:
+ subsection_id = subsection.get('subsection_id', 'Unknown')
+ subsection_text = subsection.get('subsection_text', '')
+
+ if subsection_text.strip():
+ doc = Document(
+ text=subsection_text,
+ metadata={
+ "type": "text",
+ "document_id": document_id,
+ "document_name": document_name,
+ "section_id": section_id,
+ "subsection_id": subsection_id,
+ "level": "subsection"
+ }
+ )
+ documents.append(doc)
+
+ if 'sub_subsections' in subsection:
+ for sub_subsection in subsection['sub_subsections']:
+ sub_subsection_id = sub_subsection.get('sub_subsection_id', 'Unknown')
+ sub_subsection_text = sub_subsection.get('sub_subsection_text', '')
+
+ if sub_subsection_text.strip():
+ doc = Document(
+ text=sub_subsection_text,
+ metadata={
+ "type": "text",
+ "document_id": document_id,
+ "document_name": document_name,
+ "section_id": section_id,
+ "subsection_id": subsection_id,
+ "sub_subsection_id": sub_subsection_id,
+ "level": "sub_subsection"
+ }
+ )
+ documents.append(doc)
+
+ if 'sub_sub_subsections' in sub_subsection:
+ for sub_sub_subsection in sub_subsection['sub_sub_subsections']:
+ sub_sub_subsection_id = sub_sub_subsection.get('sub_sub_subsection_id', 'Unknown')
+ sub_sub_subsection_text = sub_sub_subsection.get('sub_sub_subsection_text', '')
+
+ if sub_sub_subsection_text.strip():
+ doc = Document(
+ text=sub_sub_subsection_text,
+ metadata={
+ "type": "text",
+ "document_id": document_id,
+ "document_name": document_name,
+ "section_id": section_id,
+ "subsection_id": subsection_id,
+ "sub_subsection_id": sub_subsection_id,
+ "sub_sub_subsection_id": sub_sub_subsection_id,
+ "level": "sub_sub_subsection"
+ }
+ )
+ documents.append(doc)
+
+ return documents
+
+ def load_json_documents(self):
+ log_message("Начинаю загрузку JSON документов")
+
+ try:
+ files = list_repo_files(repo_id=self.repo_id, repo_type="dataset", token=self.hf_token)
+ json_files = [f for f in files if f.startswith(self.json_files_dir) and f.endswith('.json')]
+
+ log_message(f"Найдено {len(json_files)} JSON файлов")
+
+ all_documents = []
+
+ for file_path in json_files:
+ try:
+ log_message(f"Обрабатываю файл: {file_path}")
+ local_path = hf_hub_download(
+ repo_id=self.repo_id,
+ filename=file_path,
+ local_dir=self.download_dir,
+ repo_type="dataset",
+ token=self.hf_token
+ )
+
+ with open(local_path, 'r', encoding='utf-8') as f:
+ json_data = json.load(f)
+
+ document_metadata = json_data.get('document_metadata', {})
+ document_id = document_metadata.get('document_id', 'unknown')
+ document_name = document_metadata.get('document_name', 'unknown')
+
+ documents = self.extract_text_from_json(json_data, document_id, document_name)
+ all_documents.extend(documents)
+
+ log_message(f"Извлечено {len(documents)} документов из {file_path}")
+
+ except Exception as e:
+ log_message(f"Ошибка обработки файла {file_path}: {str(e)}")
+ continue
+
+ log_message(f"Всего создано {len(all_documents)} текстовых документов")
+ return all_documents
+
+ except Exception as e:
+ log_message(f"Ошибка загрузки JSON документов: {str(e)}")
+ return []
+
+ def table_to_document(self, table_data, document_id=None):
+ content = ""
+ if isinstance(table_data, dict):
+ doc_id = document_id or table_data.get('document_id', table_data.get('document', 'Неизвестно'))
+
+ table_num = table_data.get('table_number', 'Неизвестно')
+ table_title = table_data.get('table_title', 'Неизвестно')
+ section = table_data.get('section', 'Неизвестно')
+
+ content += f"Таблица: {table_num}\n"
+ content += f"Название: {table_title}\n"
+ content += f"Документ: {doc_id}\n"
+ content += f"Раздел: {section}\n"
+
+ if 'data' in table_data and isinstance(table_data['data'], list):
+ for row in table_data['data']:
+ if isinstance(row, dict):
+ row_text = " | ".join([f"{k}: {v}" for k, v in row.items()])
+ content += f"{row_text}\n"
+
+ return Document(
+ text=content,
+ metadata={
+ "type": "table",
+ "table_number": table_data.get('table_number', 'unknown'),
+ "table_title": table_data.get('table_title', 'unknown'),
+ "document_id": doc_id or table_data.get('document_id', table_data.get('document', 'unknown')),
+ "section": table_data.get('section', 'unknown')
+ }
+ )
+
+ def load_table_documents(self):
+ log_message("Начинаю загрузку табличных данных")
+
+ try:
+ files = list_repo_files(repo_id=self.repo_id, repo_type="dataset", token=self.hf_token)
+ table_files = [f for f in files if f.startswith(self.table_data_dir) and f.endswith('.json')]
+
+ log_message(f"Найдено {len(table_files)} JSON файлов с таблицами")
+
+ table_documents = []
+ for file_path in table_files:
+ try:
+ log_message(f"Обрабатываю файл: {file_path}")
+ local_path = hf_hub_download(
+ repo_id=self.repo_id,
+ filename=file_path,
+ local_dir=self.download_dir,
+ repo_type="dataset",
+ token=self.hf_token
+ )
+
+ with open(local_path, 'r', encoding='utf-8') as f:
+ table_data = json.load(f)
+
+ if isinstance(table_data, dict):
+ document_id = table_data.get('document', 'unknown')
+
+ if 'sheets' in table_data:
+ for sheet in table_data['sheets']:
+ sheet['document'] = document_id
+ doc = self.table_to_document(sheet, document_id)
+ table_documents.append(doc)
+ else:
+ doc = self.table_to_document(table_data, document_id)
+ table_documents.append(doc)
+ elif isinstance(table_data, list):
+ for table_json in table_data:
+ doc = self.table_to_document(table_json)
+ table_documents.append(doc)
+
+ except Exception as e:
+ log_message(f"Ошибка обработки файла {file_path}: {str(e)}")
+ continue
+
+ log_message(f"Создано {len(table_documents)} документов из таблиц")
+ return table_documents
+
+ except Exception as e:
+ log_message(f"Ошибка загрузки табличных данных: {str(e)}")
+ return []
+
+ def load_image_documents(self):
+ log_message("Начинаю загрузку данных изображений")
+
+ try:
+ files = list_repo_files(repo_id=self.repo_id, repo_type="dataset", token=self.hf_token)
+ image_files = [f for f in files if f.startswith(self.image_data_dir) and f.endswith('.csv')]
+
+ log_message(f"Найдено {len(image_files)} CSV файлов с изображениями")
+
+ image_documents = []
+ for file_path in image_files:
+ try:
+ log_message(f"Обрабатываю файл изображений: {file_path}")
+ local_path = hf_hub_download(
+ repo_id=self.repo_id,
+ filename=file_path,
+ local_dir=self.download_dir,
+ repo_type="dataset",
+ token=self.hf_token
+ )
+
+ df = pd.read_csv(local_path)
+ log_message(f"Загружено {len(df)} записей изображений из файла {file_path}")
+
+ for _, row in df.iterrows():
+ content = f"Изображение: {row.get('№ Изображения', 'Неизвестно')}\n"
+ content += f"Название: {row.get('Название изображения', 'Неизвестно')}\n"
+ content += f"Описание: {row.get('Описание изображение', 'Неизвестно')}\n"
+ content += f"Документ: {row.get('Обозначение документа', 'Неизвестно')}\n"
+ content += f"Раздел: {row.get('Раздел документа', 'Неизвестно')}\n"
+ content += f"Файл: {row.get('Файл изображения', 'Неизвестно')}\n"
+
+ doc = Document(
+ text=content,
+ metadata={
+ "type": "image",
+ "image_number": row.get('№ Изображения', 'unknown'),
+ "document_id": row.get('Обозначение документа', 'unknown'),
+ "file_path": row.get('Файл изображения', 'unknown'),
+ "section": row.get('Раздел документа', 'unknown')
+ }
+ )
+ image_documents.append(doc)
+
+ except Exception as e:
+ log_message(f"Ошибка обработки файла {file_path}: {str(e)}")
+ continue
+
+ log_message(f"Создано {len(image_documents)} документов из изображений")
+ return image_documents
+
+ except Exception as e:
+ log_message(f"Ошибка загрузки данных изображений: {str(e)}")
+ return []
+
+ def prepare_all_documents(self):
+ log_message("Подготовка всех документов")
+
+ all_documents = []
+
+ json_documents = self.load_json_documents()
+ all_documents.extend(json_documents)
+
+ table_documents = self.load_table_documents()
+ all_documents.extend(table_documents)
+
+ image_documents = self.load_image_documents()
+ all_documents.extend(image_documents)
+
+ log_message(f"Всего подготовлено {len(all_documents)} документов")
+ return all_documents
\ No newline at end of file
diff --git a/index_retriever.py b/index_retriever.py
new file mode 100644
index 0000000000000000000000000000000000000000..55083694ebded7421d2d25a03ba4e5cb065e79db
--- /dev/null
+++ b/index_retriever.py
@@ -0,0 +1,182 @@
+from llama_index.core import VectorStoreIndex, Settings
+from llama_index.embeddings.huggingface import HuggingFaceEmbedding
+from llama_index.llms.google_genai import GoogleGenAI
+from llama_index.llms.openai import OpenAI
+from llama_index.core.query_engine import RetrieverQueryEngine
+from llama_index.core.retrievers import VectorIndexRetriever
+from llama_index.core.response_synthesizers import get_response_synthesizer, ResponseMode
+from llama_index.core.prompts import PromptTemplate
+from llama_index.retrievers.bm25 import BM25Retriever
+from llama_index.core.retrievers import QueryFusionRetriever
+from sentence_transformers import CrossEncoder
+import logging
+
+logger = logging.getLogger(__name__)
+
+def log_message(message):
+ logger.info(message)
+ print(message, flush=True)
+
+class IndexRetriever:
+ def __init__(self, config):
+ self.config = config
+ self.vector_index = None
+ self.query_engine = None
+ self.reranker = None
+ self.current_model = config.DEFAULT_MODEL
+
+ def get_llm_model(self, model_name):
+ try:
+ model_config = self.config.AVAILABLE_MODELS.get(model_name)
+ if not model_config:
+ log_message(f"Модель {model_name} не найдена, использую модель по умолчанию")
+ model_config = self.config.AVAILABLE_MODELS[self.config.DEFAULT_MODEL]
+
+ if not model_config.get("api_key"):
+ raise Exception(f"API ключ не найден для модели {model_name}")
+
+ if model_config["provider"] == "google":
+ return GoogleGenAI(
+ model=model_config["model_name"],
+ api_key=model_config["api_key"]
+ )
+ elif model_config["provider"] == "openai":
+ return OpenAI(
+ model=model_config["model_name"],
+ api_key=model_config["api_key"]
+ )
+ else:
+ raise Exception(f"Неподдерживаемый провайдер: {model_config['provider']}")
+
+ except Exception as e:
+ log_message(f"Ошибка создания модели {model_name}: {str(e)}")
+ return GoogleGenAI(model="gemini-2.0-flash", api_key=self.config.GOOGLE_API_KEY)
+
+ def initialize_models(self, documents):
+ try:
+ log_message("Инициализация моделей и индекса")
+
+ embed_model = HuggingFaceEmbedding(model_name="sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2")
+ llm = self.get_llm_model(self.current_model)
+
+ log_message("Инициализирую переранкер")
+ self.reranker = CrossEncoder('cross-encoder/ms-marco-MiniLM-L-12-v2')
+
+ Settings.embed_model = embed_model
+ Settings.llm = llm
+
+ log_message(f"Строю векторный индекс из {len(documents)} документов")
+ self.vector_index = VectorStoreIndex.from_documents(documents)
+
+ self.create_query_engine()
+
+ log_message(f"Модели и индекс успешно инициализированы с моделью: {self.current_model}")
+ return True
+
+ except Exception as e:
+ log_message(f"Ошибка инициализации моделей: {str(e)}")
+ return False
+
+ def create_query_engine(self):
+ try:
+ bm25_retriever = BM25Retriever.from_defaults(
+ docstore=self.vector_index.docstore,
+ similarity_top_k=15
+ )
+
+ vector_retriever = VectorIndexRetriever(
+ index=self.vector_index,
+ similarity_top_k=20,
+ similarity_cutoff=0.5
+ )
+
+ hybrid_retriever = QueryFusionRetriever(
+ [vector_retriever, bm25_retriever],
+ similarity_top_k=30,
+ num_queries=1
+ )
+
+ custom_prompt_template = PromptTemplate(self.config.CUSTOM_PROMPT)
+ response_synthesizer = get_response_synthesizer(
+ response_mode=ResponseMode.TREE_SUMMARIZE,
+ text_qa_template=custom_prompt_template
+ )
+
+ self.query_engine = RetrieverQueryEngine(
+ retriever=hybrid_retriever,
+ response_synthesizer=response_synthesizer
+ )
+
+ log_message("Query engine успешно создан")
+
+ except Exception as e:
+ log_message(f"Ошибка создания query engine: {str(e)}")
+ raise
+
+ def switch_model(self, model_name):
+ try:
+ log_message(f"Переключение на модель: {model_name}")
+
+ new_llm = self.get_llm_model(model_name)
+ Settings.llm = new_llm
+
+ if self.vector_index is not None:
+ self.create_query_engine()
+ self.current_model = model_name
+ log_message(f"Модель успешно переключена на: {model_name}")
+ return f"✅ Модель переключена на: {model_name}"
+ else:
+ return "❌ Ошибка: система не инициализирована"
+
+ except Exception as e:
+ error_msg = f"Ошибка переключения модели: {str(e)}"
+ log_message(error_msg)
+ return f"❌ {error_msg}"
+
+ def rerank_nodes(self, query, nodes, top_k=10):
+ if not nodes or not self.reranker:
+ return nodes[:top_k]
+
+ try:
+ log_message(f"Переранжирую {len(nodes)} узлов")
+
+ pairs = []
+ for node in nodes:
+ pairs.append([query, node.text])
+
+ scores = self.reranker.predict(pairs)
+
+ scored_nodes = list(zip(nodes, scores))
+ scored_nodes.sort(key=lambda x: x[1], reverse=True)
+
+ reranked_nodes = [node for node, score in scored_nodes[:top_k]]
+ log_message(f"Возвращаю топ-{len(reranked_nodes)} переранжированных узлов")
+
+ return reranked_nodes
+ except Exception as e:
+ log_message(f"Ошибка переранжировки: {str(e)}")
+ return nodes[:top_k]
+
+ def retrieve_nodes(self, question):
+ if self.query_engine is None:
+ return []
+
+ try:
+ log_message(f"Извлекаю релевантные узлы для вопроса: {question}")
+ retrieved_nodes = self.query_engine.retriever.retrieve(question)
+ log_message(f"Извлечено {len(retrieved_nodes)} узлов")
+
+ log_message("Применяю переранжировку")
+ reranked_nodes = self.rerank_nodes(question, retrieved_nodes, top_k=10)
+
+ return reranked_nodes
+
+ except Exception as e:
+ log_message(f"Ошибка извлечения узлов: {str(e)}")
+ return []
+
+ def get_current_model(self):
+ return self.current_model
+
+ def is_initialized(self):
+ return self.query_engine is not None
\ No newline at end of file
diff --git a/requirements.txt b/requirements.txt
index 5340268a9e929e70cf242d0fbf337ff7db94e12d..42ef474686d98429dde4c6a3a9a20089ea1cfc84 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -11,4 +11,7 @@ llama-index-vector-stores-faiss
PyMuPDF
PyPDF2
python-docx
-openpyxl
\ No newline at end of file
+openpyxl
+llama-index-llms-openai
+llama-index-vector-stores-faiss
+llama-index-retrievers-bm25
\ No newline at end of file