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