From 40c33007bd15c7000c22e2b5c37b5ed3e8fb20cf Mon Sep 17 00:00:00 2001 From: tpoisonooo Date: Wed, 28 Aug 2024 17:06:07 +0800 Subject: [PATCH 1/3] update --- huixiangdou/gradio.py | 4 +- huixiangdou/service/llm_server_hybrid.py | 2 +- .../huixiangdou/.github/ISSUE_TEMPLATE/bug.md | 16 + .../.github/ISSUE_TEMPLATE/others.md | 6 + .../.github/scripts/doc_link_checker.py | 89 ++ .../huixiangdou/.github/workflows/lint.yml | 24 + .../huixiangdou/.github/workflows/release.yml | 36 + repodir/huixiangdou/.gitignore | 59 ++ repodir/huixiangdou/.pre-commit-config.yaml | 60 ++ repodir/huixiangdou/.pylintrc | 621 ++++++++++++ repodir/huixiangdou/.readthedocs.yaml | 32 + repodir/huixiangdou/LICENSE | 28 + repodir/huixiangdou/README.md | 422 +++++++++ repodir/huixiangdou/README_zh.md | 425 +++++++++ repodir/huixiangdou/android/.gitignore | 18 + repodir/huixiangdou/android/.idea/.gitignore | 3 + .../android/.idea/assetWizardSettings.xml | 38 + .../.idea/caches/build_file_checksums.ser | Bin 0 -> 584 bytes .../android/.idea/codeStyles/Project.xml | 116 +++ .../.idea/codeStyles/codeStyleConfig.xml | 5 + .../.idea/copyright/profiles_settings.xml | 3 + .../huixiangdou/android/.idea/dbnavigator.xml | 458 +++++++++ .../.idea/deploymentTargetDropDown.xml | 10 + .../android/.idea/dictionaries/caochang.xml | 3 + .../huixiangdou/android/.idea/encodings.xml | 6 + repodir/huixiangdou/android/.idea/gradle.xml | 20 + .../inspectionProfiles/Project_Default.xml | 6 + .../android/.idea/jarRepositories.xml | 60 ++ .../.idea/kotlinCodeInsightSettings.xml | 6 + repodir/huixiangdou/android/.idea/kotlinc.xml | 9 + .../android/.idea/markdown-navigator.xml | 78 ++ .../markdown-navigator/profiles_settings.xml | 3 + .../huixiangdou/android/.idea/migrations.xml | 10 + repodir/huixiangdou/android/.idea/misc.xml | 58 ++ repodir/huixiangdou/android/.idea/modules.xml | 12 + repodir/huixiangdou/android/.idea/vcs.xml | 7 + repodir/huixiangdou/android/.travis.yml | 28 + repodir/huixiangdou/android/LICENSE | 674 ++++++++++++++ repodir/huixiangdou/android/README.md | 10 + repodir/huixiangdou/android/build.gradle | 39 + .../android/buildsystem/debug.keystore | Bin 0 -> 1268 bytes .../android/buildsystem/default.properties | 23 + repodir/huixiangdou/android/demo/.gitignore | 1 + repodir/huixiangdou/android/demo/build.gradle | 48 + .../android/demo/proguard-rules.pro | 21 + .../demo/ExampleInstrumentedTest.kt | 23 + .../android/demo/src/main/AndroidManifest.xml | 36 + .../grabredenvelope/demo/MainActivity.kt | 63 ++ .../grabredenvelope/demo/SendEmojiService.kt | 404 ++++++++ .../demo/SharedPreferenceHelper.kt | 27 + .../grabredenvelope/demo/WechatConstants.kt | 30 + .../drawable-v24/ic_launcher_foreground.xml | 34 + .../res/drawable/ic_launcher_background.xml | 171 ++++ .../demo/src/main/res/drawable/logo.xml | 14 + .../src/main/res/layout/activity_main.xml | 91 ++ .../res/mipmap-anydpi-v26/ic_launcher.xml | 5 + .../mipmap-anydpi-v26/ic_launcher_round.xml | 5 + .../demo/src/main/res/values/colors.xml | 6 + .../demo/src/main/res/values/strings.xml | 16 + .../demo/src/main/res/values/styles.xml | 11 + .../src/main/res/xml/sendemoji_service.xml | 11 + .../demo/src/main/res/xml/wechat_service.xml | 12 + .../grabredenvelope/demo/ExampleUnitTest.kt | 16 + repodir/huixiangdou/android/gradle.properties | 24 + .../android/gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 53637 bytes .../gradle/wrapper/gradle-wrapper.properties | 6 + repodir/huixiangdou/android/gradlew | 160 ++++ repodir/huixiangdou/android/gradlew.bat | 90 ++ repodir/huixiangdou/android/settings.gradle | 1 + repodir/huixiangdou/app.py | 11 + repodir/huixiangdou/config-2G.ini | 200 ++++ repodir/huixiangdou/config-advanced.ini | 138 +++ .../huixiangdou/config-alignment-example.ini | 98 ++ repodir/huixiangdou/config-cpu.ini | 212 +++++ repodir/huixiangdou/config-multimodal.ini | 109 +++ repodir/huixiangdou/config-wkteam-example.ini | 177 ++++ repodir/huixiangdou/config.ini | 212 +++++ repodir/huixiangdou/docs/en/.readthedocs.yaml | 17 + repodir/huixiangdou/docs/en/Makefile | 20 + .../docs/en/_static/css/readthedocs.css | 62 ++ .../docs/en/_static/image/logo.svg | 24 + .../docs/en/_static/image/logo_icon.svg | 1 + .../huixiangdou/docs/en/_static/js/custom.js | 10 + .../huixiangdou/docs/en/_templates/404.html | 18 + .../docs/en/_templates/autosummary/class.rst | 13 + .../docs/en/_templates/callable.rst | 14 + repodir/huixiangdou/docs/en/conf.py | 221 +++++ repodir/huixiangdou/docs/en/cp_origin_docs.sh | 11 + .../huixiangdou/docs/en/doc_architecture.md | 77 ++ repodir/huixiangdou/docs/en/doc_full_dev.md | 79 ++ .../docs/en/doc_knowledge_graph.md | 67 ++ repodir/huixiangdou/docs/en/doctuils.conf | 2 + repodir/huixiangdou/docs/en/index.rst | 52 ++ repodir/huixiangdou/docs/figures/convert.py | 13 + .../huixiangdou/docs/figures/huixiangdou.png | Bin 0 -> 28293 bytes .../docs/figures/lark-add-ability.png | Bin 0 -> 48568 bytes .../huixiangdou/docs/figures/lark-arch.jpg | Bin 0 -> 14590 bytes .../docs/figures/lark-bot-add-callback.png | Bin 0 -> 63469 bytes .../docs/figures/lark-bot-reply.png | Bin 0 -> 166208 bytes .../huixiangdou/docs/figures/lark-bot-sub.png | Bin 0 -> 148577 bytes .../docs/figures/lark-create-app.png | Bin 0 -> 51107 bytes .../docs/figures/lark-create-corp.png | Bin 0 -> 72853 bytes .../docs/figures/lark-switch-corp.png | Bin 0 -> 29330 bytes .../docs/figures/wechat-android-example.jpg | Bin 0 -> 31971 bytes .../docs/figures/wechat-android-homepage.jpg | Bin 0 -> 199593 bytes .../docs/figures/wechat-dingdong.png | Bin 0 -> 101980 bytes .../docs/figures/wechat-puppet-log.png | Bin 0 -> 198356 bytes .../docs/figures/wechat-run-state.jpg | Bin 0 -> 759853 bytes .../docs/figures/wechat-wkteam.jpg | Bin 0 -> 20124 bytes repodir/huixiangdou/docs/zh/.readthedocs.yaml | 17 + repodir/huixiangdou/docs/zh/Makefile | 20 + .../docs/zh/_static/css/readthedocs.css | 62 ++ .../docs/zh/_static/image/logo.svg | 24 + .../docs/zh/_static/image/logo_icon.svg | 1 + .../huixiangdou/docs/zh/_static/js/custom.js | 10 + .../huixiangdou/docs/zh/_templates/404.html | 18 + .../docs/zh/_templates/autosummary/class.rst | 13 + .../docs/zh/_templates/callable.rst | 14 + repodir/huixiangdou/docs/zh/conf.py | 221 +++++ repodir/huixiangdou/docs/zh/cp_origin_docs.sh | 11 + .../huixiangdou/docs/zh/doc_add_lark_group.md | 156 ++++ .../docs/zh/doc_add_wechat_accessibility.md | 63 ++ .../docs/zh/doc_add_wechat_commercial.md | 129 +++ .../docs/zh/doc_add_wechat_group.md | 150 +++ .../huixiangdou/docs/zh/doc_architecture.md | 76 ++ repodir/huixiangdou/docs/zh/doc_full_dev.md | 78 ++ .../docs/zh/doc_knowledge_graph.md | 63 ++ .../docs/zh/doc_rag_annotate_sft_data.md | 84 ++ .../docs/zh/doc_send_only_lark_group.md | 22 + repodir/huixiangdou/docs/zh/doctuils.conf | 2 + repodir/huixiangdou/docs/zh/index.rst | 47 + repodir/huixiangdou/evaluation/README.md | 88 ++ repodir/huixiangdou/evaluation/README_zh.md | 88 ++ .../rejection/build_fs_and_filter.py | 323 +++++++ .../evaluation/rejection/gt_bad.txt | 1 + .../evaluation/rejection/gt_good.txt | 1 + .../evaluation/rejection/kg_filter.py | 108 +++ .../huixiangdou/evaluation/rejection/plot.py | 105 +++ .../evaluation/rejection/plot_example.png | Bin 0 -> 202921 bytes .../evaluation/rerank/step0_clean_queries.py | 56 ++ .../rerank/step1_create_candidates.py | 164 ++++ repodir/huixiangdou/huixiangdou-inside.md | 7 + repodir/huixiangdou/huixiangdou/__init__.py | 12 + .../huixiangdou/frontend/__init__.py | 6 + .../huixiangdou/huixiangdou/frontend/lark.py | 102 ++ .../huixiangdou/frontend/lark_group.py | 193 ++++ .../huixiangdou/frontend/wechat.py | 880 ++++++++++++++++++ repodir/huixiangdou/huixiangdou/gradio.py | 238 +++++ repodir/huixiangdou/huixiangdou/main.py | 213 +++++ .../huixiangdou/primitive/__init__.py | 16 + .../huixiangdou/primitive/chunk.py | 46 + .../huixiangdou/primitive/embedder.py | 117 +++ .../huixiangdou/primitive/faiss.py | 202 ++++ .../huixiangdou/primitive/file_operation.py | 261 ++++++ .../huixiangdou/primitive/llm_reranker.py | 181 ++++ .../huixiangdou/primitive/query.py | 61 ++ .../huixiangdou/huixiangdou/primitive/rpm.py | 38 + .../huixiangdou/primitive/splitter.py | 647 +++++++++++++ repodir/huixiangdou/huixiangdou/rag.py | 138 +++ repodir/huixiangdou/huixiangdou/server.py | 141 +++ .../huixiangdou/service/__init__.py | 16 + .../huixiangdou/huixiangdou/service/config.py | 30 + .../huixiangdou/service/feature_store.py | 404 ++++++++ .../huixiangdou/huixiangdou/service/helper.py | 377 ++++++++ repodir/huixiangdou/huixiangdou/service/kg.py | 507 ++++++++++ .../huixiangdou/service/llm_client.py | 221 +++++ .../huixiangdou/service/llm_server_hybrid.py | 602 ++++++++++++ .../huixiangdou/service/parallel_pipeline.py | 355 +++++++ .../huixiangdou/huixiangdou/service/prompt.py | 47 + .../huixiangdou/service/retriever.py | 312 +++++++ .../huixiangdou/service/serial_pipeline.py | 525 +++++++++++ .../huixiangdou/service/session.py | 54 ++ .../huixiangdou/service/sg_search.py | 206 ++++ .../huixiangdou/service/web_search.py | 363 ++++++++ repodir/huixiangdou/huixiangdou/version.py | 28 + repodir/huixiangdou/logs/work.txt | 1 + repodir/huixiangdou/requirements.txt | 37 + repodir/huixiangdou/requirements/cpu.txt | 35 + repodir/huixiangdou/requirements/docs.txt | 11 + .../huixiangdou/requirements/lark-group.txt | 4 + .../huixiangdou/requirements/multimodal.txt | 7 + repodir/huixiangdou/requirements/sft.txt | 2 + repodir/huixiangdou/resource/2405.02817.pdf | Bin 0 -> 213152 bytes repodir/huixiangdou/resource/HuixiangDou.pdf | Bin 0 -> 297302 bytes .../huixiangdou/resource/bad_questions.json | 39 + .../resource/figures/inside-middleware.png | Bin 0 -> 87948 bytes .../resource/figures/inside-mmpose.jpg | Bin 0 -> 228489 bytes .../resource/figures/inside-ncnn-group.jpg | Bin 0 -> 32655 bytes .../resource/figures/lark-example.png | Bin 0 -> 59152 bytes .../huixiangdou/resource/figures/wechat.jpg | Bin 0 -> 107842 bytes .../huixiangdou/resource/good_questions.json | 35 + repodir/huixiangdou/resource/logo_black.svg | 24 + repodir/huixiangdou/resource/logo_blue.svg | 19 + .../resource/rag_example_input.json | 87 ++ .../resource/rag_example_output.json | 72 ++ repodir/huixiangdou/setup.py | 70 ++ repodir/huixiangdou/sft/README.md | 75 ++ .../sft/axolotl_configs/lora-4B.yml | 68 ++ .../sft/axolotl_configs/qwen2-lora-0.5B.yaml | 61 ++ .../sft/axolotl_configs/qwen2-lora-1.8B.yaml | 61 ++ .../sft/axolotl_configs/qwen2-lora-14B.yaml | 62 ++ .../sft/axolotl_configs/qwen2-lora-32B.yaml | 62 ++ .../qwen2-lora-4B-loraplus-epoch4.yaml | 62 ++ .../sft/axolotl_configs/qwen2-lora-4B.yaml | 61 ++ .../sft/axolotl_configs/qwen2-lora-7B.yaml | 62 ++ .../axolotl_configs/qwen2-moe-lora-2.7B.yaml | 62 ++ .../sft/axolotl_configs/qwen2-moe-lora.yaml | 64 ++ .../sft/axolotl_configs/qwen2-moe-qlora.yaml | 64 ++ repodir/huixiangdou/sft/convert_to_alpaca.py | 99 ++ .../huixiangdou/sft/reconstruct_check_llm.py | 69 ++ .../sft/reconstruct_filter_annotate.py | 481 ++++++++++ .../sft/reconstruct_wechat_group.py | 520 +++++++++++ repodir/huixiangdou/tests/__init__.py | 0 repodir/huixiangdou/tests/data.json | 7 + repodir/huixiangdou/tests/git-clone.sh | 22 + repodir/huixiangdou/tests/test_alles_apin.py | 27 + repodir/huixiangdou/tests/test_bce.py | 67 ++ repodir/huixiangdou/tests/test_benepar.py | 11 + .../huixiangdou/tests/test_bge_reranker.py | 33 + .../tests/test_build_milvus_and_filter.py | 292 ++++++ .../tests/test_clear_kimi_files.py | 12 + repodir/huixiangdou/tests/test_dataclass.py | 14 + repodir/huixiangdou/tests/test_deepseek.py | 27 + .../tests/test_get_issue_comment_pipeline.py | 177 ++++ .../tests/test_hf_import_accelerate.py | 8 + .../tests/test_intention_prompt.py | 39 + repodir/huixiangdou/tests/test_internlm2.py | 25 + repodir/huixiangdou/tests/test_kimi.py | 48 + repodir/huixiangdou/tests/test_kimi_cr.py | 119 +++ .../huixiangdou/tests/test_kimi_passkey.py | 107 +++ repodir/huixiangdou/tests/test_llm_client.py | 68 ++ repodir/huixiangdou/tests/test_m3.py | 19 + .../tests/test_milvus_hybrid_retrieval.py | 156 ++++ repodir/huixiangdou/tests/test_neo4j.py | 28 + repodir/huixiangdou/tests/test_openai.py | 64 ++ repodir/huixiangdou/tests/test_optimum_st.py | 122 +++ .../huixiangdou/tests/test_post_android.py | 52 ++ repodir/huixiangdou/tests/test_pyppeteer.py | 27 + .../huixiangdou/tests/test_query_gradio.py | 76 ++ repodir/huixiangdou/tests/test_qwen_react.py | 245 +++++ repodir/huixiangdou/tests/test_relative.py | 38 + repodir/huixiangdou/tests/test_reranker.py | 139 +++ repodir/huixiangdou/tests/test_splitter.py | 68 ++ repodir/huixiangdou/tests/test_step1_llm.py | 120 +++ repodir/huixiangdou/tests/test_time.py | 10 + repodir/huixiangdou/tests/test_visual_bge.py | 16 + repodir/huixiangdou/tests/test_yi.py | 22 + repodir/huixiangdou/tests/test_yulan.py | 46 + .../unittest/primitive/test_dataclass.py | 23 + .../unittest/primitive/test_embedder.py | 25 + .../unittest/primitive/test_faiss.py | 37 + .../unittest/primitive/test_reranker.py | 17 + .../unittest/primitive/test_splitter.py | 121 +++ .../unittest/service/daily_smoke.py | 81 ++ .../unittest/service/test_llm_client.py | 23 + .../unittest/service/test_llm_server_local.py | 63 ++ .../service/test_llm_server_remote.py | 221 +++++ .../unittest/service/test_sg_search.py | 59 ++ .../unittest/service/test_web_search.py | 76 ++ repodir/huixiangdou/web/README.md | 118 +++ repodir/huixiangdou/web/__init__.py | 0 repodir/huixiangdou/web/api/__init__.py | 0 repodir/huixiangdou/web/api/access.py | 11 + repodir/huixiangdou/web/api/chat.py | 33 + repodir/huixiangdou/web/api/integrate.py | 25 + repodir/huixiangdou/web/api/message.py | 18 + repodir/huixiangdou/web/api/qalib.py | 50 + repodir/huixiangdou/web/api/statistic.py | 10 + repodir/huixiangdou/web/config/__init__.py | 0 repodir/huixiangdou/web/config/env.py | 86 ++ repodir/huixiangdou/web/config/logging.py | 34 + repodir/huixiangdou/web/constant/__init__.py | 0 .../huixiangdou/web/constant/biz_constant.py | 66 ++ .../huixiangdou/web/front-end/.eslintignore | 6 + .../huixiangdou/web/front-end/.eslintrc.cjs | 91 ++ repodir/huixiangdou/web/front-end/.gitignore | 23 + repodir/huixiangdou/web/front-end/.npmrc | 2 + .../front-end/dist/assets/bean1-002ba51d.png | Bin 0 -> 4432 bytes .../front-end/dist/assets/logo-af340389.png | Bin 0 -> 28293 bytes .../huixiangdou/web/front-end/dist/index.html | 39 + .../huixiangdou/web/front-end/dist/logo.png | Bin 0 -> 26656 bytes .../web/front-end/env/.env.development | 2 + .../web/front-end/env/.env.production | 1 + .../web/front-end/env/.env.staging | 1 + repodir/huixiangdou/web/front-end/index.html | 29 + .../huixiangdou/web/front-end/mock/db.json | 27 + .../huixiangdou/web/front-end/package.json | 60 ++ .../huixiangdou/web/front-end/public/logo.png | Bin 0 -> 26656 bytes repodir/huixiangdou/web/front-end/readme.md | 26 + .../web/front-end/scripts/alias.ts | 20 + .../web/front-end/scripts/import-to-cdn.ts | 12 + .../web/front-end/scripts/index.ts | 3 + .../web/front-end/scripts/proxy.ts | 13 + .../web/front-end/scripts/utils.ts | 3 + repodir/huixiangdou/web/front-end/src/app.tsx | 16 + .../web/front-end/src/assets/imgs/bean.png | Bin 0 -> 28293 bytes .../web/front-end/src/assets/imgs/bean1.png | Bin 0 -> 4432 bytes .../web/front-end/src/assets/imgs/logo.png | Bin 0 -> 28293 bytes .../src/components/button/button.module.less | 18 + .../src/components/button/button.tsx | 34 + .../components-portal/components-portal.tsx | 7 + .../copy-code/copy-code.module.less | 22 + .../src/components/copy-code/copy-code.tsx | 29 + .../global-lang/global-lang-context.ts | 11 + .../components/global-lang/global-lang.tsx | 31 + .../src/components/global-lang/index.tsx | 2 + .../src/components/header/header.module.less | 19 + .../src/components/header/header.tsx | 38 + .../components/notification/emoji-wrapper.tsx | 21 + .../notification/notification.module.less | 136 +++ .../components/notification/notification.tsx | 47 + .../notification/use-notification.tsx | 45 + .../src/components/upload-item/index.tsx | 4 + .../upload-item/upload-item.module.less | 18 + .../components/upload-item/upload-item.tsx | 65 ++ .../src/components/upload/delete-btn.tsx | 49 + .../front-end/src/components/upload/index.tsx | 4 + .../src/components/upload/upload.module.less | 167 ++++ .../src/components/upload/upload.tsx | 340 +++++++ .../web/front-end/src/config/auth.ts | 45 + .../web/front-end/src/config/base-url.ts | 22 + .../front-end/src/config/change-page-gray.ts | 90 ++ .../web/front-end/src/config/index.ts | 3 + .../web/front-end/src/config/log.ts | 13 + .../web/front-end/src/hooks/useLocale.ts | 16 + .../web/front-end/src/interceptors/request.ts | 44 + .../front-end/src/interceptors/response.ts | 95 ++ .../header-container-layout.module.less | 4 + .../header-container-layout.tsx | 18 + .../web/front-end/src/locales/en-US.ts | 11 + .../src/locales/en-US/bean-detail.ts | 62 ++ .../front-end/src/locales/en-US/components.ts | 27 + .../web/front-end/src/locales/en-US/home.ts | 24 + .../front-end/src/locales/en-US/welcome.ts | 6 + .../web/front-end/src/locales/index.ts | 7 + .../web/front-end/src/locales/zh-CN.ts | 11 + .../src/locales/zh-CN/bean-detail.ts | 67 ++ .../front-end/src/locales/zh-CN/components.ts | 28 + .../web/front-end/src/locales/zh-CN/home.ts | 24 + .../front-end/src/locales/zh-CN/welcome.ts | 6 + .../huixiangdou/web/front-end/src/main.tsx | 13 + .../pages/bean-detail/bean-detail.module.less | 80 ++ .../src/pages/bean-detail/bean-detail.tsx | 207 ++++ .../components/chat/chat.module.less | 151 +++ .../bean-detail/components/chat/chat.tsx | 299 ++++++ .../bean-detail/components/chat/index.tsx | 4 + .../components/example/example.module.less | 11 + .../components/example/example.tsx | 115 +++ .../bean-detail/components/example/index.tsx | 4 + .../import-docs/import-docs.module.less | 3 + .../components/import-docs/import-docs.tsx | 57 ++ .../components/import-docs/index.tsx | 4 + .../components/integrate-feishu/index.tsx | 4 + .../integrate-feishu.module.less | 41 + .../integrate-feishu/integrate-feishu.tsx | 148 +++ .../integrate-wechat.module.less | 8 + .../integrate-wechat/integrate-wechat.tsx | 52 ++ .../components/toggle-search/index.tsx | 4 + .../toggle-search/toggle-search.module.less | 29 + .../toggle-search/toggle-search.tsx | 98 ++ .../front-end/src/pages/home/home.module.less | 138 +++ .../web/front-end/src/pages/home/home.tsx | 161 ++++ .../web/front-end/src/routes/index.tsx | 36 + .../web/front-end/src/services/home.ts | 207 ++++ .../web/front-end/src/services/user.ts | 54 ++ .../web/front-end/src/styles/index.less | 2 + .../web/front-end/src/styles/mixins.less | 0 .../web/front-end/src/styles/variables.less | 19 + .../huixiangdou/web/front-end/src/types.d.ts | 14 + .../web/front-end/src/utils/ajax.ts | 114 +++ .../web/front-end/src/utils/mlog.ts | 68 ++ .../web/front-end/src/utils/utils.ts | 191 ++++ .../web/front-end/src/vite-env.d.ts | 14 + .../huixiangdou/web/front-end/tsconfig.json | 75 ++ .../web/front-end/tsconfig.node.json | 10 + .../huixiangdou/web/front-end/vite.config.ts | 65 ++ repodir/huixiangdou/web/main.py | 107 +++ .../huixiangdou/web/middleware/__init__.py | 0 repodir/huixiangdou/web/middleware/token.py | 64 ++ repodir/huixiangdou/web/model/__init__.py | 0 repodir/huixiangdou/web/model/access.py | 11 + repodir/huixiangdou/web/model/base.py | 22 + repodir/huixiangdou/web/model/chat.py | 74 ++ repodir/huixiangdou/web/model/huixiangdou.py | 65 ++ repodir/huixiangdou/web/model/integrate.py | 13 + repodir/huixiangdou/web/model/qalib.py | 74 ++ repodir/huixiangdou/web/model/statistic.py | 12 + repodir/huixiangdou/web/mq/__init__.py | 0 repodir/huixiangdou/web/mq/hxd_task.py | 34 + repodir/huixiangdou/web/orm/__init__.py | 0 repodir/huixiangdou/web/orm/redis.py | 23 + .../huixiangdou/web/proxy/config-template.ini | 94 ++ repodir/huixiangdou/web/proxy/logs/work.txt | 0 repodir/huixiangdou/web/proxy/main.py | 395 ++++++++ repodir/huixiangdou/web/proxy/test.py | 126 +++ repodir/huixiangdou/web/proxy/traslate.txt | 76 ++ repodir/huixiangdou/web/proxy/web_worker.py | 346 +++++++ repodir/huixiangdou/web/requirements.txt | 12 + repodir/huixiangdou/web/scheduler/__init__.py | 0 .../web/scheduler/huixiangdou_task.py | 171 ++++ repodir/huixiangdou/web/service/__init__.py | 0 repodir/huixiangdou/web/service/access.py | 140 +++ repodir/huixiangdou/web/service/agent.py | 389 ++++++++ repodir/huixiangdou/web/service/cache.py | 165 ++++ repodir/huixiangdou/web/service/chat.py | 175 ++++ repodir/huixiangdou/web/service/message.py | 23 + repodir/huixiangdou/web/service/qalib.py | 421 +++++++++ repodir/huixiangdou/web/service/statistic.py | 34 + repodir/huixiangdou/web/tools/README.md | 7 + .../huixiangdou/web/tools/dump_redis_query.py | 45 + .../web/tools/get_puyu_model_list.py | 15 + .../web/tools/update_fs_max_len.py | 28 + repodir/huixiangdou/web/util/__init__.py | 0 repodir/huixiangdou/web/util/image.py | 20 + repodir/huixiangdou/web/util/log.py | 29 + repodir/huixiangdou/web/util/str.py | 73 ++ repodir/huixiangdou/web/util/time_util.py | 6 + repodir/huixiangdou/web/web-architecture.png | Bin 0 -> 37624 bytes 418 files changed, 30286 insertions(+), 3 deletions(-) create mode 100644 repodir/huixiangdou/.github/ISSUE_TEMPLATE/bug.md create mode 100644 repodir/huixiangdou/.github/ISSUE_TEMPLATE/others.md create mode 100644 repodir/huixiangdou/.github/scripts/doc_link_checker.py create mode 100644 repodir/huixiangdou/.github/workflows/lint.yml create mode 100644 repodir/huixiangdou/.github/workflows/release.yml create mode 100644 repodir/huixiangdou/.gitignore create mode 100644 repodir/huixiangdou/.pre-commit-config.yaml create mode 100644 repodir/huixiangdou/.pylintrc create mode 100644 repodir/huixiangdou/.readthedocs.yaml create mode 100644 repodir/huixiangdou/LICENSE create mode 100644 repodir/huixiangdou/README.md create mode 100644 repodir/huixiangdou/README_zh.md create mode 100644 repodir/huixiangdou/android/.gitignore create mode 100644 repodir/huixiangdou/android/.idea/.gitignore create mode 100644 repodir/huixiangdou/android/.idea/assetWizardSettings.xml create mode 100644 repodir/huixiangdou/android/.idea/caches/build_file_checksums.ser create mode 100644 repodir/huixiangdou/android/.idea/codeStyles/Project.xml create mode 100644 repodir/huixiangdou/android/.idea/codeStyles/codeStyleConfig.xml create mode 100644 repodir/huixiangdou/android/.idea/copyright/profiles_settings.xml create mode 100644 repodir/huixiangdou/android/.idea/dbnavigator.xml create mode 100644 repodir/huixiangdou/android/.idea/deploymentTargetDropDown.xml create mode 100644 repodir/huixiangdou/android/.idea/dictionaries/caochang.xml create mode 100644 repodir/huixiangdou/android/.idea/encodings.xml create mode 100644 repodir/huixiangdou/android/.idea/gradle.xml create mode 100644 repodir/huixiangdou/android/.idea/inspectionProfiles/Project_Default.xml create mode 100644 repodir/huixiangdou/android/.idea/jarRepositories.xml create mode 100644 repodir/huixiangdou/android/.idea/kotlinCodeInsightSettings.xml create mode 100644 repodir/huixiangdou/android/.idea/kotlinc.xml create mode 100644 repodir/huixiangdou/android/.idea/markdown-navigator.xml create mode 100644 repodir/huixiangdou/android/.idea/markdown-navigator/profiles_settings.xml create mode 100644 repodir/huixiangdou/android/.idea/migrations.xml create mode 100644 repodir/huixiangdou/android/.idea/misc.xml create mode 100644 repodir/huixiangdou/android/.idea/modules.xml create mode 100644 repodir/huixiangdou/android/.idea/vcs.xml create mode 100644 repodir/huixiangdou/android/.travis.yml create mode 100644 repodir/huixiangdou/android/LICENSE create mode 100644 repodir/huixiangdou/android/README.md create mode 100644 repodir/huixiangdou/android/build.gradle create mode 100644 repodir/huixiangdou/android/buildsystem/debug.keystore create mode 100644 repodir/huixiangdou/android/buildsystem/default.properties create mode 100644 repodir/huixiangdou/android/demo/.gitignore create mode 100644 repodir/huixiangdou/android/demo/build.gradle create mode 100644 repodir/huixiangdou/android/demo/proguard-rules.pro create mode 100644 repodir/huixiangdou/android/demo/src/androidTest/java/com/carlos/grabredenvelope/demo/ExampleInstrumentedTest.kt create mode 100644 repodir/huixiangdou/android/demo/src/main/AndroidManifest.xml create mode 100644 repodir/huixiangdou/android/demo/src/main/java/com/carlos/grabredenvelope/demo/MainActivity.kt create mode 100644 repodir/huixiangdou/android/demo/src/main/java/com/carlos/grabredenvelope/demo/SendEmojiService.kt create mode 100644 repodir/huixiangdou/android/demo/src/main/java/com/carlos/grabredenvelope/demo/SharedPreferenceHelper.kt create mode 100644 repodir/huixiangdou/android/demo/src/main/java/com/carlos/grabredenvelope/demo/WechatConstants.kt create mode 100644 repodir/huixiangdou/android/demo/src/main/res/drawable-v24/ic_launcher_foreground.xml create mode 100644 repodir/huixiangdou/android/demo/src/main/res/drawable/ic_launcher_background.xml create mode 100644 repodir/huixiangdou/android/demo/src/main/res/drawable/logo.xml create mode 100644 repodir/huixiangdou/android/demo/src/main/res/layout/activity_main.xml create mode 100644 repodir/huixiangdou/android/demo/src/main/res/mipmap-anydpi-v26/ic_launcher.xml create mode 100644 repodir/huixiangdou/android/demo/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml create mode 100644 repodir/huixiangdou/android/demo/src/main/res/values/colors.xml create mode 100644 repodir/huixiangdou/android/demo/src/main/res/values/strings.xml create mode 100644 repodir/huixiangdou/android/demo/src/main/res/values/styles.xml create mode 100644 repodir/huixiangdou/android/demo/src/main/res/xml/sendemoji_service.xml create mode 100644 repodir/huixiangdou/android/demo/src/main/res/xml/wechat_service.xml create mode 100644 repodir/huixiangdou/android/demo/src/test/java/com/carlos/grabredenvelope/demo/ExampleUnitTest.kt create mode 100644 repodir/huixiangdou/android/gradle.properties create mode 100644 repodir/huixiangdou/android/gradle/wrapper/gradle-wrapper.jar create mode 100644 repodir/huixiangdou/android/gradle/wrapper/gradle-wrapper.properties create mode 100644 repodir/huixiangdou/android/gradlew create mode 100644 repodir/huixiangdou/android/gradlew.bat create mode 100644 repodir/huixiangdou/android/settings.gradle create mode 100644 repodir/huixiangdou/app.py create mode 100644 repodir/huixiangdou/config-2G.ini create mode 100644 repodir/huixiangdou/config-advanced.ini create mode 100644 repodir/huixiangdou/config-alignment-example.ini create mode 100644 repodir/huixiangdou/config-cpu.ini create mode 100644 repodir/huixiangdou/config-multimodal.ini create mode 100644 repodir/huixiangdou/config-wkteam-example.ini create mode 100644 repodir/huixiangdou/config.ini create mode 100644 repodir/huixiangdou/docs/en/.readthedocs.yaml create mode 100644 repodir/huixiangdou/docs/en/Makefile create mode 100644 repodir/huixiangdou/docs/en/_static/css/readthedocs.css create mode 100644 repodir/huixiangdou/docs/en/_static/image/logo.svg create mode 100644 repodir/huixiangdou/docs/en/_static/image/logo_icon.svg create mode 100644 repodir/huixiangdou/docs/en/_static/js/custom.js create mode 100644 repodir/huixiangdou/docs/en/_templates/404.html create mode 100644 repodir/huixiangdou/docs/en/_templates/autosummary/class.rst create mode 100644 repodir/huixiangdou/docs/en/_templates/callable.rst create mode 100644 repodir/huixiangdou/docs/en/conf.py create mode 100755 repodir/huixiangdou/docs/en/cp_origin_docs.sh create mode 100644 repodir/huixiangdou/docs/en/doc_architecture.md create mode 100644 repodir/huixiangdou/docs/en/doc_full_dev.md create mode 100644 repodir/huixiangdou/docs/en/doc_knowledge_graph.md create mode 100644 repodir/huixiangdou/docs/en/doctuils.conf create mode 100644 repodir/huixiangdou/docs/en/index.rst create mode 100644 repodir/huixiangdou/docs/figures/convert.py create mode 100644 repodir/huixiangdou/docs/figures/huixiangdou.png create mode 100644 repodir/huixiangdou/docs/figures/lark-add-ability.png create mode 100644 repodir/huixiangdou/docs/figures/lark-arch.jpg create mode 100644 repodir/huixiangdou/docs/figures/lark-bot-add-callback.png create mode 100644 repodir/huixiangdou/docs/figures/lark-bot-reply.png create mode 100644 repodir/huixiangdou/docs/figures/lark-bot-sub.png create mode 100644 repodir/huixiangdou/docs/figures/lark-create-app.png create mode 100644 repodir/huixiangdou/docs/figures/lark-create-corp.png create mode 100644 repodir/huixiangdou/docs/figures/lark-switch-corp.png create mode 100644 repodir/huixiangdou/docs/figures/wechat-android-example.jpg create mode 100644 repodir/huixiangdou/docs/figures/wechat-android-homepage.jpg create mode 100644 repodir/huixiangdou/docs/figures/wechat-dingdong.png create mode 100644 repodir/huixiangdou/docs/figures/wechat-puppet-log.png create mode 100644 repodir/huixiangdou/docs/figures/wechat-run-state.jpg create mode 100644 repodir/huixiangdou/docs/figures/wechat-wkteam.jpg create mode 100644 repodir/huixiangdou/docs/zh/.readthedocs.yaml create mode 100644 repodir/huixiangdou/docs/zh/Makefile create mode 100644 repodir/huixiangdou/docs/zh/_static/css/readthedocs.css create mode 100644 repodir/huixiangdou/docs/zh/_static/image/logo.svg create mode 100644 repodir/huixiangdou/docs/zh/_static/image/logo_icon.svg create mode 100644 repodir/huixiangdou/docs/zh/_static/js/custom.js create mode 100644 repodir/huixiangdou/docs/zh/_templates/404.html create mode 100644 repodir/huixiangdou/docs/zh/_templates/autosummary/class.rst create mode 100644 repodir/huixiangdou/docs/zh/_templates/callable.rst create mode 100644 repodir/huixiangdou/docs/zh/conf.py create mode 100755 repodir/huixiangdou/docs/zh/cp_origin_docs.sh create mode 100644 repodir/huixiangdou/docs/zh/doc_add_lark_group.md create mode 100644 repodir/huixiangdou/docs/zh/doc_add_wechat_accessibility.md create mode 100644 repodir/huixiangdou/docs/zh/doc_add_wechat_commercial.md create mode 100644 repodir/huixiangdou/docs/zh/doc_add_wechat_group.md create mode 100644 repodir/huixiangdou/docs/zh/doc_architecture.md create mode 100644 repodir/huixiangdou/docs/zh/doc_full_dev.md create mode 100644 repodir/huixiangdou/docs/zh/doc_knowledge_graph.md create mode 100644 repodir/huixiangdou/docs/zh/doc_rag_annotate_sft_data.md create mode 100644 repodir/huixiangdou/docs/zh/doc_send_only_lark_group.md create mode 100644 repodir/huixiangdou/docs/zh/doctuils.conf create mode 100644 repodir/huixiangdou/docs/zh/index.rst create mode 100644 repodir/huixiangdou/evaluation/README.md create mode 100644 repodir/huixiangdou/evaluation/README_zh.md create mode 100644 repodir/huixiangdou/evaluation/rejection/build_fs_and_filter.py create mode 100644 repodir/huixiangdou/evaluation/rejection/gt_bad.txt create mode 100644 repodir/huixiangdou/evaluation/rejection/gt_good.txt create mode 100644 repodir/huixiangdou/evaluation/rejection/kg_filter.py create mode 100644 repodir/huixiangdou/evaluation/rejection/plot.py create mode 100644 repodir/huixiangdou/evaluation/rejection/plot_example.png create mode 100644 repodir/huixiangdou/evaluation/rerank/step0_clean_queries.py create mode 100644 repodir/huixiangdou/evaluation/rerank/step1_create_candidates.py create mode 100644 repodir/huixiangdou/huixiangdou-inside.md create mode 100644 repodir/huixiangdou/huixiangdou/__init__.py create mode 100644 repodir/huixiangdou/huixiangdou/frontend/__init__.py create mode 100644 repodir/huixiangdou/huixiangdou/frontend/lark.py create mode 100644 repodir/huixiangdou/huixiangdou/frontend/lark_group.py create mode 100644 repodir/huixiangdou/huixiangdou/frontend/wechat.py create mode 100644 repodir/huixiangdou/huixiangdou/gradio.py create mode 100755 repodir/huixiangdou/huixiangdou/main.py create mode 100644 repodir/huixiangdou/huixiangdou/primitive/__init__.py create mode 100644 repodir/huixiangdou/huixiangdou/primitive/chunk.py create mode 100644 repodir/huixiangdou/huixiangdou/primitive/embedder.py create mode 100644 repodir/huixiangdou/huixiangdou/primitive/faiss.py create mode 100644 repodir/huixiangdou/huixiangdou/primitive/file_operation.py create mode 100644 repodir/huixiangdou/huixiangdou/primitive/llm_reranker.py create mode 100644 repodir/huixiangdou/huixiangdou/primitive/query.py create mode 100644 repodir/huixiangdou/huixiangdou/primitive/rpm.py create mode 100644 repodir/huixiangdou/huixiangdou/primitive/splitter.py create mode 100644 repodir/huixiangdou/huixiangdou/rag.py create mode 100644 repodir/huixiangdou/huixiangdou/server.py create mode 100644 repodir/huixiangdou/huixiangdou/service/__init__.py create mode 100644 repodir/huixiangdou/huixiangdou/service/config.py create mode 100644 repodir/huixiangdou/huixiangdou/service/feature_store.py create mode 100644 repodir/huixiangdou/huixiangdou/service/helper.py create mode 100644 repodir/huixiangdou/huixiangdou/service/kg.py create mode 100644 repodir/huixiangdou/huixiangdou/service/llm_client.py create mode 100644 repodir/huixiangdou/huixiangdou/service/llm_server_hybrid.py create mode 100644 repodir/huixiangdou/huixiangdou/service/parallel_pipeline.py create mode 100644 repodir/huixiangdou/huixiangdou/service/prompt.py create mode 100644 repodir/huixiangdou/huixiangdou/service/retriever.py create mode 100644 repodir/huixiangdou/huixiangdou/service/serial_pipeline.py create mode 100644 repodir/huixiangdou/huixiangdou/service/session.py create mode 100644 repodir/huixiangdou/huixiangdou/service/sg_search.py create mode 100644 repodir/huixiangdou/huixiangdou/service/web_search.py create mode 100644 repodir/huixiangdou/huixiangdou/version.py create mode 100644 repodir/huixiangdou/logs/work.txt create mode 100644 repodir/huixiangdou/requirements.txt create mode 100644 repodir/huixiangdou/requirements/cpu.txt create mode 100644 repodir/huixiangdou/requirements/docs.txt create mode 100644 repodir/huixiangdou/requirements/lark-group.txt create mode 100644 repodir/huixiangdou/requirements/multimodal.txt create mode 100644 repodir/huixiangdou/requirements/sft.txt create mode 100644 repodir/huixiangdou/resource/2405.02817.pdf create mode 100644 repodir/huixiangdou/resource/HuixiangDou.pdf create mode 100644 repodir/huixiangdou/resource/bad_questions.json create mode 100644 repodir/huixiangdou/resource/figures/inside-middleware.png create mode 100644 repodir/huixiangdou/resource/figures/inside-mmpose.jpg create mode 100644 repodir/huixiangdou/resource/figures/inside-ncnn-group.jpg create mode 100644 repodir/huixiangdou/resource/figures/lark-example.png create mode 100644 repodir/huixiangdou/resource/figures/wechat.jpg create mode 100644 repodir/huixiangdou/resource/good_questions.json create mode 100644 repodir/huixiangdou/resource/logo_black.svg create mode 100644 repodir/huixiangdou/resource/logo_blue.svg create mode 100644 repodir/huixiangdou/resource/rag_example_input.json create mode 100644 repodir/huixiangdou/resource/rag_example_output.json create mode 100644 repodir/huixiangdou/setup.py create mode 100644 repodir/huixiangdou/sft/README.md create mode 100644 repodir/huixiangdou/sft/axolotl_configs/lora-4B.yml create mode 100644 repodir/huixiangdou/sft/axolotl_configs/qwen2-lora-0.5B.yaml create mode 100644 repodir/huixiangdou/sft/axolotl_configs/qwen2-lora-1.8B.yaml create mode 100644 repodir/huixiangdou/sft/axolotl_configs/qwen2-lora-14B.yaml create mode 100644 repodir/huixiangdou/sft/axolotl_configs/qwen2-lora-32B.yaml create mode 100644 repodir/huixiangdou/sft/axolotl_configs/qwen2-lora-4B-loraplus-epoch4.yaml create mode 100644 repodir/huixiangdou/sft/axolotl_configs/qwen2-lora-4B.yaml create mode 100644 repodir/huixiangdou/sft/axolotl_configs/qwen2-lora-7B.yaml create mode 100644 repodir/huixiangdou/sft/axolotl_configs/qwen2-moe-lora-2.7B.yaml create mode 100644 repodir/huixiangdou/sft/axolotl_configs/qwen2-moe-lora.yaml create mode 100644 repodir/huixiangdou/sft/axolotl_configs/qwen2-moe-qlora.yaml create mode 100644 repodir/huixiangdou/sft/convert_to_alpaca.py create mode 100644 repodir/huixiangdou/sft/reconstruct_check_llm.py create mode 100644 repodir/huixiangdou/sft/reconstruct_filter_annotate.py create mode 100644 repodir/huixiangdou/sft/reconstruct_wechat_group.py create mode 100644 repodir/huixiangdou/tests/__init__.py create mode 100644 repodir/huixiangdou/tests/data.json create mode 100644 repodir/huixiangdou/tests/git-clone.sh create mode 100644 repodir/huixiangdou/tests/test_alles_apin.py create mode 100644 repodir/huixiangdou/tests/test_bce.py create mode 100644 repodir/huixiangdou/tests/test_benepar.py create mode 100644 repodir/huixiangdou/tests/test_bge_reranker.py create mode 100644 repodir/huixiangdou/tests/test_build_milvus_and_filter.py create mode 100644 repodir/huixiangdou/tests/test_clear_kimi_files.py create mode 100644 repodir/huixiangdou/tests/test_dataclass.py create mode 100644 repodir/huixiangdou/tests/test_deepseek.py create mode 100644 repodir/huixiangdou/tests/test_get_issue_comment_pipeline.py create mode 100644 repodir/huixiangdou/tests/test_hf_import_accelerate.py create mode 100644 repodir/huixiangdou/tests/test_intention_prompt.py create mode 100644 repodir/huixiangdou/tests/test_internlm2.py create mode 100644 repodir/huixiangdou/tests/test_kimi.py create mode 100644 repodir/huixiangdou/tests/test_kimi_cr.py create mode 100644 repodir/huixiangdou/tests/test_kimi_passkey.py create mode 100644 repodir/huixiangdou/tests/test_llm_client.py create mode 100644 repodir/huixiangdou/tests/test_m3.py create mode 100644 repodir/huixiangdou/tests/test_milvus_hybrid_retrieval.py create mode 100644 repodir/huixiangdou/tests/test_neo4j.py create mode 100644 repodir/huixiangdou/tests/test_openai.py create mode 100644 repodir/huixiangdou/tests/test_optimum_st.py create mode 100644 repodir/huixiangdou/tests/test_post_android.py create mode 100644 repodir/huixiangdou/tests/test_pyppeteer.py create mode 100644 repodir/huixiangdou/tests/test_query_gradio.py create mode 100644 repodir/huixiangdou/tests/test_qwen_react.py create mode 100644 repodir/huixiangdou/tests/test_relative.py create mode 100644 repodir/huixiangdou/tests/test_reranker.py create mode 100644 repodir/huixiangdou/tests/test_splitter.py create mode 100644 repodir/huixiangdou/tests/test_step1_llm.py create mode 100644 repodir/huixiangdou/tests/test_time.py create mode 100644 repodir/huixiangdou/tests/test_visual_bge.py create mode 100644 repodir/huixiangdou/tests/test_yi.py create mode 100644 repodir/huixiangdou/tests/test_yulan.py create mode 100644 repodir/huixiangdou/unittest/primitive/test_dataclass.py create mode 100644 repodir/huixiangdou/unittest/primitive/test_embedder.py create mode 100644 repodir/huixiangdou/unittest/primitive/test_faiss.py create mode 100644 repodir/huixiangdou/unittest/primitive/test_reranker.py create mode 100644 repodir/huixiangdou/unittest/primitive/test_splitter.py create mode 100644 repodir/huixiangdou/unittest/service/daily_smoke.py create mode 100644 repodir/huixiangdou/unittest/service/test_llm_client.py create mode 100644 repodir/huixiangdou/unittest/service/test_llm_server_local.py create mode 100644 repodir/huixiangdou/unittest/service/test_llm_server_remote.py create mode 100644 repodir/huixiangdou/unittest/service/test_sg_search.py create mode 100644 repodir/huixiangdou/unittest/service/test_web_search.py create mode 100644 repodir/huixiangdou/web/README.md create mode 100644 repodir/huixiangdou/web/__init__.py create mode 100644 repodir/huixiangdou/web/api/__init__.py create mode 100644 repodir/huixiangdou/web/api/access.py create mode 100644 repodir/huixiangdou/web/api/chat.py create mode 100644 repodir/huixiangdou/web/api/integrate.py create mode 100644 repodir/huixiangdou/web/api/message.py create mode 100644 repodir/huixiangdou/web/api/qalib.py create mode 100644 repodir/huixiangdou/web/api/statistic.py create mode 100644 repodir/huixiangdou/web/config/__init__.py create mode 100644 repodir/huixiangdou/web/config/env.py create mode 100644 repodir/huixiangdou/web/config/logging.py create mode 100644 repodir/huixiangdou/web/constant/__init__.py create mode 100644 repodir/huixiangdou/web/constant/biz_constant.py create mode 100644 repodir/huixiangdou/web/front-end/.eslintignore create mode 100644 repodir/huixiangdou/web/front-end/.eslintrc.cjs create mode 100644 repodir/huixiangdou/web/front-end/.gitignore create mode 100644 repodir/huixiangdou/web/front-end/.npmrc create mode 100644 repodir/huixiangdou/web/front-end/dist/assets/bean1-002ba51d.png create mode 100644 repodir/huixiangdou/web/front-end/dist/assets/logo-af340389.png create mode 100644 repodir/huixiangdou/web/front-end/dist/index.html create mode 100644 repodir/huixiangdou/web/front-end/dist/logo.png create mode 100644 repodir/huixiangdou/web/front-end/env/.env.development create mode 100644 repodir/huixiangdou/web/front-end/env/.env.production create mode 100644 repodir/huixiangdou/web/front-end/env/.env.staging create mode 100644 repodir/huixiangdou/web/front-end/index.html create mode 100644 repodir/huixiangdou/web/front-end/mock/db.json create mode 100644 repodir/huixiangdou/web/front-end/package.json create mode 100644 repodir/huixiangdou/web/front-end/public/logo.png create mode 100644 repodir/huixiangdou/web/front-end/readme.md create mode 100644 repodir/huixiangdou/web/front-end/scripts/alias.ts create mode 100644 repodir/huixiangdou/web/front-end/scripts/import-to-cdn.ts create mode 100644 repodir/huixiangdou/web/front-end/scripts/index.ts create mode 100644 repodir/huixiangdou/web/front-end/scripts/proxy.ts create mode 100644 repodir/huixiangdou/web/front-end/scripts/utils.ts create mode 100644 repodir/huixiangdou/web/front-end/src/app.tsx create mode 100644 repodir/huixiangdou/web/front-end/src/assets/imgs/bean.png create mode 100644 repodir/huixiangdou/web/front-end/src/assets/imgs/bean1.png create mode 100644 repodir/huixiangdou/web/front-end/src/assets/imgs/logo.png create mode 100644 repodir/huixiangdou/web/front-end/src/components/button/button.module.less create mode 100644 repodir/huixiangdou/web/front-end/src/components/button/button.tsx create mode 100644 repodir/huixiangdou/web/front-end/src/components/components-portal/components-portal.tsx create mode 100644 repodir/huixiangdou/web/front-end/src/components/copy-code/copy-code.module.less create mode 100644 repodir/huixiangdou/web/front-end/src/components/copy-code/copy-code.tsx create mode 100644 repodir/huixiangdou/web/front-end/src/components/global-lang/global-lang-context.ts create mode 100644 repodir/huixiangdou/web/front-end/src/components/global-lang/global-lang.tsx create mode 100644 repodir/huixiangdou/web/front-end/src/components/global-lang/index.tsx create mode 100644 repodir/huixiangdou/web/front-end/src/components/header/header.module.less create mode 100644 repodir/huixiangdou/web/front-end/src/components/header/header.tsx create mode 100644 repodir/huixiangdou/web/front-end/src/components/notification/emoji-wrapper.tsx create mode 100644 repodir/huixiangdou/web/front-end/src/components/notification/notification.module.less create mode 100644 repodir/huixiangdou/web/front-end/src/components/notification/notification.tsx create mode 100644 repodir/huixiangdou/web/front-end/src/components/notification/use-notification.tsx create mode 100644 repodir/huixiangdou/web/front-end/src/components/upload-item/index.tsx create mode 100644 repodir/huixiangdou/web/front-end/src/components/upload-item/upload-item.module.less create mode 100644 repodir/huixiangdou/web/front-end/src/components/upload-item/upload-item.tsx create mode 100644 repodir/huixiangdou/web/front-end/src/components/upload/delete-btn.tsx create mode 100644 repodir/huixiangdou/web/front-end/src/components/upload/index.tsx create mode 100644 repodir/huixiangdou/web/front-end/src/components/upload/upload.module.less create mode 100755 repodir/huixiangdou/web/front-end/src/components/upload/upload.tsx create mode 100644 repodir/huixiangdou/web/front-end/src/config/auth.ts create mode 100644 repodir/huixiangdou/web/front-end/src/config/base-url.ts create mode 100644 repodir/huixiangdou/web/front-end/src/config/change-page-gray.ts create mode 100644 repodir/huixiangdou/web/front-end/src/config/index.ts create mode 100644 repodir/huixiangdou/web/front-end/src/config/log.ts create mode 100644 repodir/huixiangdou/web/front-end/src/hooks/useLocale.ts create mode 100644 repodir/huixiangdou/web/front-end/src/interceptors/request.ts create mode 100644 repodir/huixiangdou/web/front-end/src/interceptors/response.ts create mode 100644 repodir/huixiangdou/web/front-end/src/layouts/header-container-layout/header-container-layout.module.less create mode 100644 repodir/huixiangdou/web/front-end/src/layouts/header-container-layout/header-container-layout.tsx create mode 100644 repodir/huixiangdou/web/front-end/src/locales/en-US.ts create mode 100644 repodir/huixiangdou/web/front-end/src/locales/en-US/bean-detail.ts create mode 100755 repodir/huixiangdou/web/front-end/src/locales/en-US/components.ts create mode 100644 repodir/huixiangdou/web/front-end/src/locales/en-US/home.ts create mode 100644 repodir/huixiangdou/web/front-end/src/locales/en-US/welcome.ts create mode 100644 repodir/huixiangdou/web/front-end/src/locales/index.ts create mode 100644 repodir/huixiangdou/web/front-end/src/locales/zh-CN.ts create mode 100644 repodir/huixiangdou/web/front-end/src/locales/zh-CN/bean-detail.ts create mode 100755 repodir/huixiangdou/web/front-end/src/locales/zh-CN/components.ts create mode 100644 repodir/huixiangdou/web/front-end/src/locales/zh-CN/home.ts create mode 100644 repodir/huixiangdou/web/front-end/src/locales/zh-CN/welcome.ts create mode 100644 repodir/huixiangdou/web/front-end/src/main.tsx create mode 100644 repodir/huixiangdou/web/front-end/src/pages/bean-detail/bean-detail.module.less create mode 100644 repodir/huixiangdou/web/front-end/src/pages/bean-detail/bean-detail.tsx create mode 100644 repodir/huixiangdou/web/front-end/src/pages/bean-detail/components/chat/chat.module.less create mode 100644 repodir/huixiangdou/web/front-end/src/pages/bean-detail/components/chat/chat.tsx create mode 100644 repodir/huixiangdou/web/front-end/src/pages/bean-detail/components/chat/index.tsx create mode 100644 repodir/huixiangdou/web/front-end/src/pages/bean-detail/components/example/example.module.less create mode 100644 repodir/huixiangdou/web/front-end/src/pages/bean-detail/components/example/example.tsx create mode 100644 repodir/huixiangdou/web/front-end/src/pages/bean-detail/components/example/index.tsx create mode 100644 repodir/huixiangdou/web/front-end/src/pages/bean-detail/components/import-docs/import-docs.module.less create mode 100644 repodir/huixiangdou/web/front-end/src/pages/bean-detail/components/import-docs/import-docs.tsx create mode 100644 repodir/huixiangdou/web/front-end/src/pages/bean-detail/components/import-docs/index.tsx create mode 100644 repodir/huixiangdou/web/front-end/src/pages/bean-detail/components/integrate-feishu/index.tsx create mode 100644 repodir/huixiangdou/web/front-end/src/pages/bean-detail/components/integrate-feishu/integrate-feishu.module.less create mode 100644 repodir/huixiangdou/web/front-end/src/pages/bean-detail/components/integrate-feishu/integrate-feishu.tsx create mode 100644 repodir/huixiangdou/web/front-end/src/pages/bean-detail/components/integrate-wechat/integrate-wechat.module.less create mode 100644 repodir/huixiangdou/web/front-end/src/pages/bean-detail/components/integrate-wechat/integrate-wechat.tsx create mode 100644 repodir/huixiangdou/web/front-end/src/pages/bean-detail/components/toggle-search/index.tsx create mode 100644 repodir/huixiangdou/web/front-end/src/pages/bean-detail/components/toggle-search/toggle-search.module.less create mode 100644 repodir/huixiangdou/web/front-end/src/pages/bean-detail/components/toggle-search/toggle-search.tsx create mode 100755 repodir/huixiangdou/web/front-end/src/pages/home/home.module.less create mode 100755 repodir/huixiangdou/web/front-end/src/pages/home/home.tsx create mode 100644 repodir/huixiangdou/web/front-end/src/routes/index.tsx create mode 100644 repodir/huixiangdou/web/front-end/src/services/home.ts create mode 100644 repodir/huixiangdou/web/front-end/src/services/user.ts create mode 100644 repodir/huixiangdou/web/front-end/src/styles/index.less create mode 100644 repodir/huixiangdou/web/front-end/src/styles/mixins.less create mode 100644 repodir/huixiangdou/web/front-end/src/styles/variables.less create mode 100644 repodir/huixiangdou/web/front-end/src/types.d.ts create mode 100644 repodir/huixiangdou/web/front-end/src/utils/ajax.ts create mode 100644 repodir/huixiangdou/web/front-end/src/utils/mlog.ts create mode 100644 repodir/huixiangdou/web/front-end/src/utils/utils.ts create mode 100644 repodir/huixiangdou/web/front-end/src/vite-env.d.ts create mode 100644 repodir/huixiangdou/web/front-end/tsconfig.json create mode 100644 repodir/huixiangdou/web/front-end/tsconfig.node.json create mode 100644 repodir/huixiangdou/web/front-end/vite.config.ts create mode 100644 repodir/huixiangdou/web/main.py create mode 100644 repodir/huixiangdou/web/middleware/__init__.py create mode 100644 repodir/huixiangdou/web/middleware/token.py create mode 100644 repodir/huixiangdou/web/model/__init__.py create mode 100644 repodir/huixiangdou/web/model/access.py create mode 100644 repodir/huixiangdou/web/model/base.py create mode 100644 repodir/huixiangdou/web/model/chat.py create mode 100644 repodir/huixiangdou/web/model/huixiangdou.py create mode 100644 repodir/huixiangdou/web/model/integrate.py create mode 100644 repodir/huixiangdou/web/model/qalib.py create mode 100644 repodir/huixiangdou/web/model/statistic.py create mode 100644 repodir/huixiangdou/web/mq/__init__.py create mode 100644 repodir/huixiangdou/web/mq/hxd_task.py create mode 100644 repodir/huixiangdou/web/orm/__init__.py create mode 100644 repodir/huixiangdou/web/orm/redis.py create mode 100644 repodir/huixiangdou/web/proxy/config-template.ini create mode 100644 repodir/huixiangdou/web/proxy/logs/work.txt create mode 100644 repodir/huixiangdou/web/proxy/main.py create mode 100644 repodir/huixiangdou/web/proxy/test.py create mode 100644 repodir/huixiangdou/web/proxy/traslate.txt create mode 100644 repodir/huixiangdou/web/proxy/web_worker.py create mode 100644 repodir/huixiangdou/web/requirements.txt create mode 100644 repodir/huixiangdou/web/scheduler/__init__.py create mode 100644 repodir/huixiangdou/web/scheduler/huixiangdou_task.py create mode 100644 repodir/huixiangdou/web/service/__init__.py create mode 100644 repodir/huixiangdou/web/service/access.py create mode 100644 repodir/huixiangdou/web/service/agent.py create mode 100644 repodir/huixiangdou/web/service/cache.py create mode 100644 repodir/huixiangdou/web/service/chat.py create mode 100644 repodir/huixiangdou/web/service/message.py create mode 100644 repodir/huixiangdou/web/service/qalib.py create mode 100644 repodir/huixiangdou/web/service/statistic.py create mode 100644 repodir/huixiangdou/web/tools/README.md create mode 100644 repodir/huixiangdou/web/tools/dump_redis_query.py create mode 100644 repodir/huixiangdou/web/tools/get_puyu_model_list.py create mode 100644 repodir/huixiangdou/web/tools/update_fs_max_len.py create mode 100644 repodir/huixiangdou/web/util/__init__.py create mode 100644 repodir/huixiangdou/web/util/image.py create mode 100644 repodir/huixiangdou/web/util/log.py create mode 100644 repodir/huixiangdou/web/util/str.py create mode 100644 repodir/huixiangdou/web/util/time_util.py create mode 100644 repodir/huixiangdou/web/web-architecture.png diff --git a/huixiangdou/gradio.py b/huixiangdou/gradio.py index 24d1986d..bf5bba78 100644 --- a/huixiangdou/gradio.py +++ b/huixiangdou/gradio.py @@ -29,10 +29,10 @@ def parse_args(): type=str, default='workdir', help='Working directory.') - parser.add_argument('--pipeline-count', type=int, default=2, help='Support user choosing all pipeline types.') + parser.add_argument('--pipeline-count', type=int, default=1, help='Support user choosing all pipeline types.') parser.add_argument( '--config_path', - default='config.ini', + default='config-cpu.ini', type=str, help='SerialPipeline configuration path. Default value is config.ini') parser.add_argument('--standalone', diff --git a/huixiangdou/service/llm_server_hybrid.py b/huixiangdou/service/llm_server_hybrid.py index 8fdad72a..e3978b60 100644 --- a/huixiangdou/service/llm_server_hybrid.py +++ b/huixiangdou/service/llm_server_hybrid.py @@ -208,7 +208,7 @@ def __init__(self, "deepseek": "deepseek-chat", "zhipuai": "glm-4", "puyu": "internlm2-latest", - "siliconcloud": "alibaba/Qwen1.5-110B-Chat" + "siliconcloud": "internlm/internlm2_5-20b-chat" } async def call_kimi(self, prompt:str, history:List[Tuple], remote_api_key:str, model:str): diff --git a/repodir/huixiangdou/.github/ISSUE_TEMPLATE/bug.md b/repodir/huixiangdou/.github/ISSUE_TEMPLATE/bug.md new file mode 100644 index 00000000..e1b37428 --- /dev/null +++ b/repodir/huixiangdou/.github/ISSUE_TEMPLATE/bug.md @@ -0,0 +1,16 @@ +--- +name: 🐛 bug issue +about: submit a bug report +_+ +--- + +## error log | 日志或报错信息 | ログ + +## context | 编译/运行环境 | バックグラウンド + +## how to reproduce | 复现步骤 | 再現方法 + +1. +2. +3. + +## more | 其他 | その他 diff --git a/repodir/huixiangdou/.github/ISSUE_TEMPLATE/others.md b/repodir/huixiangdou/.github/ISSUE_TEMPLATE/others.md new file mode 100644 index 00000000..cc930053 --- /dev/null +++ b/repodir/huixiangdou/.github/ISSUE_TEMPLATE/others.md @@ -0,0 +1,6 @@ +--- +name: 📝 others +about: discussion, suggestion and question +--- + +## detail | 详细描述 | 詳細な説明 diff --git a/repodir/huixiangdou/.github/scripts/doc_link_checker.py b/repodir/huixiangdou/.github/scripts/doc_link_checker.py new file mode 100644 index 00000000..95bb15ec --- /dev/null +++ b/repodir/huixiangdou/.github/scripts/doc_link_checker.py @@ -0,0 +1,89 @@ +# Copyright (c) MegFlow. All rights reserved. +# /bin/python3 + +import argparse +import os +import re + + +def make_parser(): + parser = argparse.ArgumentParser('Doc link checker') + parser.add_argument('--http', + default=False, + type=bool, + help='check http or not ') + parser.add_argument('--target', + default='./docs', + type=str, + help='the directory or file to check') + return parser + + +pattern = re.compile(r'\[.*?\]\(.*?\)') + + +def analyze_doc(home, path): + print('analyze {}'.format(path)) + problem_list = [] + code_block = 0 + with open(path, encoding='utf8') as f: + lines = f.readlines() + for line in lines: + line = line.strip() + if line.startswith('```'): + code_block = 1 - code_block + + if code_block > 0: + continue + + if '[' in line and ']' in line and '(' in line and ')' in line: + all = pattern.findall(line) + for item in all: + # skip ![]() + if item.find('[') == item.find(']') - 1: + continue + + # process the case [text()]() + offset = item.find('](') + if offset == -1: + continue + item = item[offset:] + start = item.find('(') + end = item.find(')') + ref = item[start + 1:end] + + if ref.startswith('http') or ref.startswith('#'): + continue + if '.md#' in ref: + ref = ref[ref.find('#'):] + fullpath = os.path.join(home, ref) + if not os.path.exists(fullpath): + # raise ValueError(fullpath) + problem_list.append(ref) + else: + continue + if len(problem_list) > 0: + print(f'{path}:') + for item in problem_list: + print(f'\t {item}') + print('\n') + raise Exception('found link error') + + +def traverse(target): + if os.path.isfile(target): + analyze_doc(os.path.dirname(target), target) + return + for home, dirs, files in os.walk(target): + for filename in files: + if filename.endswith('.md'): + path = os.path.join(home, filename) + if os.path.islink(path) is False: + if 'copy_' in path: + continue + analyze_doc(home, path) + + +if __name__ == '__main__': + args = make_parser().parse_args() + traverse(args.target) diff --git a/repodir/huixiangdou/.github/workflows/lint.yml b/repodir/huixiangdou/.github/workflows/lint.yml new file mode 100644 index 00000000..5c760602 --- /dev/null +++ b/repodir/huixiangdou/.github/workflows/lint.yml @@ -0,0 +1,24 @@ +name: Check markdown local file link available + +on: + push: + branches: + - main + pull_request: + +jobs: + lint: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v2 + - name: Set up Python 3.9 + uses: actions/setup-python@v2 + with: + python-version: 3.9 + - name: Check doc link + run: | + python .github/scripts/doc_link_checker.py --target README_zh.md + python .github/scripts/doc_link_checker.py --target README.md + python -m pip install pylint interrogate + pylint huixiangdou || true + interrogate huixiangdou -v || true diff --git a/repodir/huixiangdou/.github/workflows/release.yml b/repodir/huixiangdou/.github/workflows/release.yml new file mode 100644 index 00000000..b40cb66e --- /dev/null +++ b/repodir/huixiangdou/.github/workflows/release.yml @@ -0,0 +1,36 @@ +name: Publish Python 🐍 distributions 📦 to PyPI + +on: + push: + tags: + - '*' + +jobs: + build-n-publish: + name: Build and publish Python 🐍 distributions 📦 to PyPI + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: '3.9' + - name: Install pypa/build + run: >- + python -m + pip install + build + --user + - name: Build a binary wheel and a source tarball + run: >- + python -m + build + --sdist + --wheel + --outdir dist/ + - name: Publish distribution 📦 to PyPI + if: startsWith(github.ref, 'refs/tags') + uses: pypa/gh-action-pypi-publish@release/v1 + with: + user: __token__ + password: ${{ secrets.pypi_password }} diff --git a/repodir/huixiangdou/.gitignore b/repodir/huixiangdou/.gitignore new file mode 100644 index 00000000..336f3f30 --- /dev/null +++ b/repodir/huixiangdou/.gitignore @@ -0,0 +1,59 @@ +models/ +repodir/ +workdir/ +write_toml.py +modeling_internlm2.py +config.ini +config-template.ini +logs/ +logs/work.txt +server.log +**/__pycache__ +pk/ +badcase.txt +config.bak +config.ini +resource/prompt.txt +build/ +dist/ +huixiangdou.egg-info/ +commit.id +resource/wechat_questions.json +.eggs/ +feature_stores/ +web/qa +redis.conf +nohup.out +*.pyc +start-web.sh +web/proxy/config-template.ini +web/env.sh +sft-data +config-alignment.ini +logs/work.txt +web/tools/query.jsonl +query.jsonl +web/tools/groups/ +tests/history_recv_send.txt +web/tools/chat.txt +web/tools/filter.jsonl +unittest/token.json +config.test +wkteam/ +config-wechat.ini +sft/groups/ +evaluation/queries/ +candidates/ +evaluation/candidates.zip +evaluation/feature_stores.zip +evaluation/query.log +bceodir/ +odir/ +repodir-full/ +web.log +workdir-full/ +evaluation/rejection/gt_bad.txt +evaluation/rejection/gt_good.txt +workdir832/ +workdir.bak/ +workdir-20240729-kg-included/ diff --git a/repodir/huixiangdou/.pre-commit-config.yaml b/repodir/huixiangdou/.pre-commit-config.yaml new file mode 100644 index 00000000..b66ed82d --- /dev/null +++ b/repodir/huixiangdou/.pre-commit-config.yaml @@ -0,0 +1,60 @@ +repos: + - repo: https://github.com/PyCQA/flake8 + rev: 4.0.1 + hooks: + - id: flake8 + exclude: ^(__init__.py)$ + args: ["--max-line-length=79", "--exclude=service/__init__.py", "--exclude=tests/*", "--exclude=android/*"] + - repo: https://github.com/PyCQA/isort + rev: 5.11.5 + hooks: + - id: isort + - repo: https://github.com/pre-commit/mirrors-yapf + rev: v0.32.0 + hooks: + - id: yapf + name: yapf + description: 'Formatter for Python code' + entry: yapf + language: python + args: ['-i', '--style={based_on_style: pep8, column_limit: 79}'] + + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.2.0 + hooks: + - id: trailing-whitespace + - id: check-yaml + - id: end-of-file-fixer + - id: requirements-txt-fixer + - id: double-quote-string-fixer + - id: check-merge-conflict + - id: fix-encoding-pragma + args: ["--remove"] + - id: mixed-line-ending + args: ["--fix=lf"] + - repo: https://github.com/executablebooks/mdformat + rev: 0.7.9 + hooks: + - id: mdformat + args: ["--number"] + additional_dependencies: + - mdformat-openmmlab + - mdformat_frontmatter + - linkify-it-py + - repo: https://github.com/codespell-project/codespell + rev: v2.1.0 + hooks: + - id: codespell + args: ["--skip=third_party/*,*.ipynb,*.proto"] + + - repo: https://github.com/myint/docformatter + rev: v1.4 + hooks: + - id: docformatter + args: ["--in-place", "--wrap-descriptions", "79"] + + - repo: https://github.com/open-mmlab/pre-commit-hooks + rev: v0.4.1 + hooks: + - id: check-copyright + args: ["huixiangdou"] diff --git a/repodir/huixiangdou/.pylintrc b/repodir/huixiangdou/.pylintrc new file mode 100644 index 00000000..d7a39be8 --- /dev/null +++ b/repodir/huixiangdou/.pylintrc @@ -0,0 +1,621 @@ +[MASTER] + +# A comma-separated list of package or module names from where C extensions may +# be loaded. Extensions are loading into the active Python interpreter and may +# run arbitrary code. +extension-pkg-whitelist= + +# Specify a score threshold to be exceeded before program exits with error. +fail-under=10.0 + +# Add files or directories to the blacklist. They should be base names, not +# paths. +ignore=CVS,configs + +# Add files or directories matching the regex patterns to the blacklist. The +# regex matches against base names, not paths. +ignore-patterns= + +# Python code to execute, usually for sys.path manipulation such as +# pygtk.require(). +#init-hook= + +# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the +# number of processors available to use. +jobs=1 + +# Control the amount of potential inferred values when inferring a single +# object. This can help the performance when dealing with large functions or +# complex, nested conditions. +limit-inference-results=100 + +# List of plugins (as comma separated values of python module names) to load, +# usually to register additional checkers. +load-plugins= + +# Pickle collected data for later comparisons. +persistent=yes + +# When enabled, pylint would attempt to guess common misconfiguration and emit +# user-friendly hints instead of false-positive error messages. +suggestion-mode=yes + +# Allow loading of arbitrary C extensions. Extensions are imported into the +# active Python interpreter and may run arbitrary code. +unsafe-load-any-extension=no + + +[MESSAGES CONTROL] + +# Only show warnings with the listed confidence levels. Leave empty to show +# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED. +confidence= + +# Disable the message, report, category or checker with the given id(s). You +# can either give multiple identifiers separated by comma (,) or put this +# option multiple times (only on the command line, not in the configuration +# file where it should appear only once). You can also use "--disable=all" to +# disable everything first and then reenable specific checks. For example, if +# you want to run only the similarities checker, you can use "--disable=all +# --enable=similarities". If you want to run only the classes checker, but have +# no Warning level messages displayed, use "--disable=all --enable=classes +# --disable=W". +disable=print-statement, + parameter-unpacking, + unpacking-in-except, + old-raise-syntax, + backtick, + long-suffix, + old-ne-operator, + old-octal-literal, + import-star-module-level, + non-ascii-bytes-literal, + raw-checker-failed, + bad-inline-option, + locally-disabled, + file-ignored, + suppressed-message, + useless-suppression, + deprecated-pragma, + use-symbolic-message-instead, + apply-builtin, + basestring-builtin, + buffer-builtin, + cmp-builtin, + coerce-builtin, + execfile-builtin, + file-builtin, + long-builtin, + raw_input-builtin, + reduce-builtin, + standarderror-builtin, + unicode-builtin, + xrange-builtin, + coerce-method, + delslice-method, + getslice-method, + setslice-method, + no-absolute-import, + old-division, + dict-iter-method, + dict-view-method, + next-method-called, + metaclass-assignment, + indexing-exception, + raising-string, + reload-builtin, + oct-method, + hex-method, + nonzero-method, + cmp-method, + input-builtin, + round-builtin, + intern-builtin, + unichr-builtin, + map-builtin-not-iterating, + zip-builtin-not-iterating, + range-builtin-not-iterating, + filter-builtin-not-iterating, + using-cmp-argument, + eq-without-hash, + div-method, + idiv-method, + rdiv-method, + exception-message-attribute, + invalid-str-codec, + sys-max-int, + bad-python3-import, + deprecated-string-function, + deprecated-str-translate-call, + deprecated-itertools-function, + deprecated-types-field, + next-method-defined, + dict-items-not-iterating, + dict-keys-not-iterating, + dict-values-not-iterating, + deprecated-operator-function, + deprecated-urllib-function, + xreadlines-attribute, + deprecated-sys-function, + exception-escape, + comprehension-escape, + no-member, + invalid-name, + too-many-branches, + wrong-import-order, + too-many-arguments, + missing-function-docstring, + missing-module-docstring, + too-many-locals, + too-few-public-methods, + abstract-method, + broad-except, + too-many-nested-blocks, + too-many-instance-attributes, + missing-class-docstring, + duplicate-code, + not-callable, + protected-access, + dangerous-default-value, + no-name-in-module, + logging-fstring-interpolation, + super-init-not-called, + redefined-builtin, + attribute-defined-outside-init, + arguments-differ, + cyclic-import, + bad-super-call, + too-many-statements + +# Enable the message, report, category or checker with the given id(s). You can +# either give multiple identifier separated by comma (,) or put this option +# multiple time (only on the command line, not in the configuration file where +# it should appear only once). See also the "--disable" option for examples. +enable=c-extension-no-member + + +[REPORTS] + +# Python expression which should return a score less than or equal to 10. You +# have access to the variables 'error', 'warning', 'refactor', and 'convention' +# which contain the number of messages in each category, as well as 'statement' +# which is the total number of statements analyzed. This score is used by the +# global evaluation report (RP0004). +evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) + +# Template used to display messages. This is a python new-style format string +# used to format the message information. See doc for all details. +#msg-template= + +# Set the output format. Available formats are text, parseable, colorized, json +# and msvs (visual studio). You can also give a reporter class, e.g. +# mypackage.mymodule.MyReporterClass. +output-format=text + +# Tells whether to display a full report or only the messages. +reports=no + +# Activate the evaluation score. +score=yes + + +[REFACTORING] + +# Maximum number of nested blocks for function / method body +max-nested-blocks=5 + +# Complete name of functions that never returns. When checking for +# inconsistent-return-statements if a never returning function is called then +# it will be considered as an explicit return statement and no message will be +# printed. +never-returning-functions=sys.exit + + +[TYPECHECK] + +# List of decorators that produce context managers, such as +# contextlib.contextmanager. Add to this list to register other decorators that +# produce valid context managers. +contextmanager-decorators=contextlib.contextmanager + +# List of members which are set dynamically and missed by pylint inference +# system, and so shouldn't trigger E1101 when accessed. Python regular +# expressions are accepted. +generated-members= + +# Tells whether missing members accessed in mixin class should be ignored. A +# mixin class is detected if its name ends with "mixin" (case insensitive). +ignore-mixin-members=yes + +# Tells whether to warn about missing members when the owner of the attribute +# is inferred to be None. +ignore-none=yes + +# This flag controls whether pylint should warn about no-member and similar +# checks whenever an opaque object is returned when inferring. The inference +# can return multiple potential results while evaluating a Python object, but +# some branches might not be evaluated, which results in partial inference. In +# that case, it might be useful to still emit no-member and other checks for +# the rest of the inferred objects. +ignore-on-opaque-inference=yes + +# List of class names for which member attributes should not be checked (useful +# for classes with dynamically set attributes). This supports the use of +# qualified names. +ignored-classes=optparse.Values,thread._local,_thread._local + +# List of module names for which member attributes should not be checked +# (useful for modules/projects where namespaces are manipulated during runtime +# and thus existing member attributes cannot be deduced by static analysis). It +# supports qualified module names, as well as Unix pattern matching. +ignored-modules= + +# Show a hint with possible names when a member name was not found. The aspect +# of finding the hint is based on edit distance. +missing-member-hint=yes + +# The minimum edit distance a name should have in order to be considered a +# similar match for a missing member name. +missing-member-hint-distance=1 + +# The total number of similar names that should be taken in consideration when +# showing a hint for a missing member. +missing-member-max-choices=1 + +# List of decorators that change the signature of a decorated function. +signature-mutators= + + +[SPELLING] + +# Limits count of emitted suggestions for spelling mistakes. +max-spelling-suggestions=4 + +# Spelling dictionary name. Available dictionaries: none. To make it work, +# install the python-enchant package. +spelling-dict= + +# List of comma separated words that should not be checked. +spelling-ignore-words= + +# A path to a file that contains the private dictionary; one word per line. +spelling-private-dict-file= + +# Tells whether to store unknown words to the private dictionary (see the +# --spelling-private-dict-file option) instead of raising a message. +spelling-store-unknown-words=no + + +[LOGGING] + +# The type of string formatting that logging methods do. `old` means using % +# formatting, `new` is for `{}` formatting. +logging-format-style=old + +# Logging modules to check that the string format arguments are in logging +# function parameter format. +logging-modules=logging + + +[VARIABLES] + +# List of additional names supposed to be defined in builtins. Remember that +# you should avoid defining new builtins when possible. +additional-builtins= + +# Tells whether unused global variables should be treated as a violation. +allow-global-unused-variables=yes + +# List of strings which can identify a callback function by name. A callback +# name must start or end with one of those strings. +callbacks=cb_, + _cb + +# A regular expression matching the name of dummy variables (i.e. expected to +# not be used). +dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ + +# Argument names that match this expression will be ignored. Default to name +# with leading underscore. +ignored-argument-names=_.*|^ignored_|^unused_ + +# Tells whether we should check for unused import in __init__ files. +init-import=no + +# List of qualified module names which can have objects that can redefine +# builtins. +redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io + + +[FORMAT] + +# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. +expected-line-ending-format= + +# Regexp for a line that is allowed to be longer than the limit. +ignore-long-lines=^\s*(# )??$ + +# Number of spaces of indent required inside a hanging or continued line. +indent-after-paren=4 + +# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 +# tab). +indent-string=' ' + +# Maximum number of characters on a single line. +max-line-length=100 + +# Maximum number of lines in a module. +max-module-lines=1000 + +# Allow the body of a class to be on the same line as the declaration if body +# contains single statement. +single-line-class-stmt=no + +# Allow the body of an if to be on the same line as the test if there is no +# else. +single-line-if-stmt=no + + +[STRING] + +# This flag controls whether inconsistent-quotes generates a warning when the +# character used as a quote delimiter is used inconsistently within a module. +check-quote-consistency=no + +# This flag controls whether the implicit-str-concat should generate a warning +# on implicit string concatenation in sequences defined over several lines. +check-str-concat-over-line-jumps=no + + +[SIMILARITIES] + +# Ignore comments when computing similarities. +ignore-comments=yes + +# Ignore docstrings when computing similarities. +ignore-docstrings=yes + +# Ignore imports when computing similarities. +ignore-imports=no + +# Minimum lines number of a similarity. +min-similarity-lines=4 + + +[MISCELLANEOUS] + +# List of note tags to take in consideration, separated by a comma. +notes=FIXME, + XXX, + TODO + +# Regular expression of note tags to take in consideration. +#notes-rgx= + + +[BASIC] + +# Naming style matching correct argument names. +argument-naming-style=snake_case + +# Regular expression matching correct argument names. Overrides argument- +# naming-style. +#argument-rgx= + +# Naming style matching correct attribute names. +attr-naming-style=snake_case + +# Regular expression matching correct attribute names. Overrides attr-naming- +# style. +#attr-rgx= + +# Bad variable names which should always be refused, separated by a comma. +bad-names=foo, + bar, + baz, + toto, + tutu, + tata + +# Bad variable names regexes, separated by a comma. If names match any regex, +# they will always be refused +bad-names-rgxs= + +# Naming style matching correct class attribute names. +class-attribute-naming-style=any + +# Regular expression matching correct class attribute names. Overrides class- +# attribute-naming-style. +#class-attribute-rgx= + +# Naming style matching correct class names. +class-naming-style=PascalCase + +# Regular expression matching correct class names. Overrides class-naming- +# style. +#class-rgx= + +# Naming style matching correct constant names. +const-naming-style=UPPER_CASE + +# Regular expression matching correct constant names. Overrides const-naming- +# style. +#const-rgx= + +# Minimum line length for functions/classes that require docstrings, shorter +# ones are exempt. +docstring-min-length=-1 + +# Naming style matching correct function names. +function-naming-style=snake_case + +# Regular expression matching correct function names. Overrides function- +# naming-style. +#function-rgx= + +# Good variable names which should always be accepted, separated by a comma. +good-names=i, + j, + k, + ex, + Run, + _, + x, + y, + w, + h, + a, + b + +# Good variable names regexes, separated by a comma. If names match any regex, +# they will always be accepted +good-names-rgxs= + +# Include a hint for the correct naming format with invalid-name. +include-naming-hint=no + +# Naming style matching correct inline iteration names. +inlinevar-naming-style=any + +# Regular expression matching correct inline iteration names. Overrides +# inlinevar-naming-style. +#inlinevar-rgx= + +# Naming style matching correct method names. +method-naming-style=snake_case + +# Regular expression matching correct method names. Overrides method-naming- +# style. +#method-rgx= + +# Naming style matching correct module names. +module-naming-style=snake_case + +# Regular expression matching correct module names. Overrides module-naming- +# style. +#module-rgx= + +# Colon-delimited sets of names that determine each other's naming style when +# the name regexes allow several styles. +name-group= + +# Regular expression which should only match function or class names that do +# not require a docstring. +no-docstring-rgx=^_ + +# List of decorators that produce properties, such as abc.abstractproperty. Add +# to this list to register other decorators that produce valid properties. +# These decorators are taken in consideration only for invalid-name. +property-classes=abc.abstractproperty + +# Naming style matching correct variable names. +variable-naming-style=snake_case + +# Regular expression matching correct variable names. Overrides variable- +# naming-style. +#variable-rgx= + + +[DESIGN] + +# Maximum number of arguments for function / method. +max-args=5 + +# Maximum number of attributes for a class (see R0902). +max-attributes=7 + +# Maximum number of boolean expressions in an if statement (see R0916). +max-bool-expr=5 + +# Maximum number of branch for function / method body. +max-branches=12 + +# Maximum number of locals for function / method body. +max-locals=15 + +# Maximum number of parents for a class (see R0901). +max-parents=7 + +# Maximum number of public methods for a class (see R0904). +max-public-methods=20 + +# Maximum number of return / yield for function / method body. +max-returns=6 + +# Maximum number of statements in function / method body. +max-statements=50 + +# Minimum number of public methods for a class (see R0903). +min-public-methods=2 + + +[IMPORTS] + +# List of modules that can be imported at any level, not just the top level +# one. +allow-any-import-level= + +# Allow wildcard imports from modules that define __all__. +allow-wildcard-with-all=no + +# Analyse import fallback blocks. This can be used to support both Python 2 and +# 3 compatible code, which means that the block might have code that exists +# only in one or another interpreter, leading to false positives when analysed. +analyse-fallback-blocks=no + +# Deprecated modules which should not be used, separated by a comma. +deprecated-modules=optparse,tkinter.tix + +# Create a graph of external dependencies in the given file (report RP0402 must +# not be disabled). +ext-import-graph= + +# Create a graph of every (i.e. internal and external) dependencies in the +# given file (report RP0402 must not be disabled). +import-graph= + +# Create a graph of internal dependencies in the given file (report RP0402 must +# not be disabled). +int-import-graph= + +# Force import order to recognize a module as part of the standard +# compatibility libraries. +known-standard-library= + +# Force import order to recognize a module as part of a third party library. +known-third-party=enchant + +# Couples of modules and preferred modules, separated by a comma. +preferred-modules= + + +[CLASSES] + +# List of method names used to declare (i.e. assign) instance attributes. +defining-attr-methods=__init__, + __new__, + setUp, + __post_init__ + +# List of member names, which should be excluded from the protected access +# warning. +exclude-protected=_asdict, + _fields, + _replace, + _source, + _make + +# List of valid names for the first argument in a class method. +valid-classmethod-first-arg=cls + +# List of valid names for the first argument in a metaclass class method. +valid-metaclass-classmethod-first-arg=cls + + +[EXCEPTIONS] + +# Exceptions that will emit a warning when being caught. Defaults to +# "BaseException, Exception". +overgeneral-exceptions=BaseException, + Exception diff --git a/repodir/huixiangdou/.readthedocs.yaml b/repodir/huixiangdou/.readthedocs.yaml new file mode 100644 index 00000000..f89fc906 --- /dev/null +++ b/repodir/huixiangdou/.readthedocs.yaml @@ -0,0 +1,32 @@ +# .readthedocs.yaml +# Read the Docs configuration file +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +# Required +version: 2 + +# Set the OS, Python version and other tools you might need +build: + os: ubuntu-22.04 + tools: + python: "3.12" + # You can also specify other tool versions: + # nodejs: "19" + # rust: "1.64" + # golang: "1.19" + +# Build documentation in the "docs/" directory with Sphinx +sphinx: + configuration: docs/conf.py + +# Optionally build your docs in additional formats such as PDF and ePub +# formats: +# - pdf +# - epub + +# Optional but recommended, declare the Python requirements required +# to build your documentation +# See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html +# python: +# install: +# - requirements: docs/requirements.txt diff --git a/repodir/huixiangdou/LICENSE b/repodir/huixiangdou/LICENSE new file mode 100644 index 00000000..b2122312 --- /dev/null +++ b/repodir/huixiangdou/LICENSE @@ -0,0 +1,28 @@ +BSD 3-Clause License + +Copyright (c) 2024, tpoisonooo + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/repodir/huixiangdou/README.md b/repodir/huixiangdou/README.md new file mode 100644 index 00000000..834ee143 --- /dev/null +++ b/repodir/huixiangdou/README.md @@ -0,0 +1,422 @@ +English | [简体中文](README_zh.md) + +
+ + + +
+ + Wechat + + + + Readthedocs + + + YouTube + + + BiliBili + + + discord + + + Arxiv + + + Arxiv + +
+ +
+ +HuixiangDou is a **professional knowledge assistant** based on LLM. + +Advantages: + +1. Design three-stage pipelines of preprocess, rejection and response + * `chat_in_group` copes with **group chat** scenario, answer user questions without message flooding, see [2401.08772](https://arxiv.org/abs/2401.08772), [2405.02817](https://arxiv.org/abs/2405.02817), [Hybrid Retrieval](./docs/en/doc_knowledge_graph.md) and [Precision Report](./evaluation/) + * `chat_with_repo` for **real-time streaming** chat +2. No training required, with CPU-only, 2G, 10G, 20G and 80G configuration +3. Offers a complete suite of Web, Android, and pipeline source code, industrial-grade and commercially viable + +Check out the [scenes in which HuixiangDou are running](./huixiangdou-inside.md) and join [WeChat Group](resource/figures/wechat.jpg) to try AI assistant inside. + +If this helps you, please give it a star ⭐ + +# 🔆 New Features + +Our Web version has been released to [OpenXLab](https://openxlab.org.cn/apps/detail/tpoisonooo/huixiangdou-web), where you can create knowledge base, update positive and negative examples, turn on web search, test chat, and integrate into Feishu/WeChat groups. See [BiliBili](https://www.bilibili.com/video/BV1S2421N7mn) and [YouTube](https://www.youtube.com/watch?v=ylXrT-Tei-Y) ! + +- \[2024/08\] `chat_with_repo` [pipeline](./huixiangdou/service/parallel_pipeline.py) 👍 +- \[2024/07\] Image and text retrieval & Removal of `langchain` 👍 +- \[2024/07\] [Hybrid Knowledge Graph and Dense Retrieval](./docs/en/doc_knowledge_graph.md) improve 1.7% F1 score 🎯 +- \[2024/06\] [Evaluation of chunksize, splitter, and text2vec model](./evaluation) 🎯 +- \[2024/05\] [wkteam WeChat access](./docs/zh/doc_add_wechat_commercial.md), parsing image & URL, support coreference resolution +- \[2024/05\] [SFT LLM on NLP task, F1 increased by 29%](./sft/) 🎯 + + + + + + + + +
🤗LoRA-Qwen1.5-14BLoRA-Qwen1.5-32Balpaca dataarXiv
+- \[2024/04\] [RAG Annotation SFT Q&A Data and Examples](./docs/zh/doc_rag_annotate_sft_data.md) +- \[2024/04\] Release [Web Front and Back End Service Source Code](./web) 👍 +- \[2024/03\] New [Personal WeChat Integration](./docs/zh/doc_add_wechat_accessibility.md) and [**Prebuilt APK**](https://github.com/InternLM/HuixiangDou/releases/download/v0.1.0rc1/huixiangdou-20240508.apk) ! +- \[2024/02\] \[Experimental Feature\] [WeChat Group](https://github.com/InternLM/HuixiangDou/blob/main/resource/figures/wechat.jpg) Integration of multimodal to achieve OCR + +# 📖 Support Status + + + + + + + + + + + + + + + + + + + + + + + +
+ LLM + + File Format + + Retrieval Method + + Integration + + Preprocessing +
+ +- [InternLM2/InternLM2.5](https://github.com/InternLM/InternLM) +- [Qwen/Qwen2](https://github.com/QwenLM/Qwen2) +- [puyu](https://internlm.openxlab.org.cn/) +- [StepFun](https://platform.stepfun.com) +- [KIMI](https://kimi.moonshot.cn) +- [DeepSeek](https://www.deepseek.com) +- [GLM (ZHIPU)](https://www.zhipuai.cn) +- [SiliconCloud](https://siliconflow.cn/zh-cn/siliconcloud) +- [Xi-Api](https://api.xi-ai.cn) + + + +- pdf +- word +- excel +- ppt +- html +- markdown +- txt + + + +- [Knowledge Graph](./docs/en/doc_knowledge_graph.md) +- [Internet Search](./huixiangdou/service/web_search.py) +- [SourceGraph](https://sourcegraph.com) +- Image and text (only markdown) + + + +- WeChat([android](./docs/zh/doc_add_wechat_accessibility.md)/[wkteam](./docs/zh/doc_add_wechat_commercial.md)) +- Lark +- [OpenXLab Web](https://openxlab.org.cn/apps/detail/tpoisonooo/huixiangdou-web) +- [Gradio Demo](./huixiangdou/gradio.py) +- [HTTP Server](./huixiangdou/server.py) + + + +- [Coreference Resolution](https://arxiv.org/abs/2405.02817) + +
+ +# 📦 Hardware Requirements + +The following are the GPU memory requirements for different features, the difference lies only in whether the **options are turned on**. + +| Configuration Example | GPU mem Requirements | Description | Verified on Linux | +| :----------------------------------------------: | :------------------: | :-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------: | +| [config-cpu.ini](./config-cpu.ini) | - | Use [siliconcloud](https://siliconflow.cn/) API
for text only | ![](https://img.shields.io/badge/x86-passed-blue?style=for-the-badge) | +| [config-2G.ini](./config-2G.ini) | 2GB | Use openai API (such as [kimi](https://kimi.moonshot.cn), [deepseek](https://platform.deepseek.com/usage) and [stepfun](https://platform.stepfun.com/) to search for text only | ![](https://img.shields.io/badge/1660ti%206G-passed-blue?style=for-the-badge) | +| [config-multimodal.ini](./config-multimodal.ini) | 10GB | Use openai API for LLM, image and text retrieval | ![](https://img.shields.io/badge/3090%2024G-passed-blue?style=for-the-badge) | +| \[Standard Edition\] [config.ini](./config.ini) | 19GB | Local deployment of LLM, single modality | ![](https://img.shields.io/badge/3090%2024G-passed-blue?style=for-the-badge) | +| [config-advanced.ini](./config-advanced.ini) | 80GB | local LLM, anaphora resolution, single modality, practical for WeChat group | ![](https://img.shields.io/badge/A100%2080G-passed-blue?style=for-the-badge) | + +# 🔥 Running the Standard Edition + +We take the standard edition (local running LLM, text retrieval) as an introduction example. Other versions are just different in configuration options. + +## I. Download and install dependencies + +[Click to agree to the BCE model agreement](https://huggingface.co/maidalun1020/bce-embedding-base_v1), log in huggingface + +```shell +huggingface-cli login +``` + +Install dependencies + +```bash +# parsing `word` format requirements +apt update +apt install python-dev libxml2-dev libxslt1-dev antiword unrtf poppler-utils pstotext tesseract-ocr flac ffmpeg lame libmad0 libsox-fmt-mp3 sox libjpeg-dev swig libpulse-dev +# python requirements +pip install -r requirements.txt +# For python3.8, install faiss-gpu instead of faiss +``` + +## II. Create knowledge base and ask questions + +Use mmpose documents to build the mmpose knowledge base and filtering questions. If you have your own documents, just put them under `repodir`. + +Copy and execute all the following commands (including the '#' symbol). + +```shell +# Download the knowledge base, we only take the documents of mmpose as an example. You can put any of your own documents under `repodir` +cd HuixiangDou +mkdir repodir +git clone https://github.com/open-mmlab/mmpose --depth=1 repodir/mmpose + +# Save the features of repodir to workdir, and update the positive and negative example thresholds into `config.ini` +mkdir workdir +python3 -m huixiangdou.service.feature_store +``` + +After running, test with `python3 -m huixiangdou.main --standalone`. At this time, reply to mmpose related questions (related to the knowledge base), while not responding to weather questions. + +```bash +python3 -m huixiangdou.main --standalone + ++---------------------------+---------+----------------------------+-----------------+ +| Query | State | Part of Reply | References | ++===========================+=========+============================+=================+ +| How to install mmpose? | success | To install mmpose, plea.. | installation.md | +-------------------------------------------------------------------------------------- +| How is the weather today? | unrelated.. | .. | | ++-----------------------+---------+--------------------------------+-----------------+ +🔆 Input your question here, type `bye` for exit: +.. +``` + +> \[!NOTE\] +> +>
+> If restarting LLM every time is too slow, first python3 -m huixiangdou.service.llm_server_hybrid; then open a new window, and each time only execute python3 -m huixiangdou.main without restarting LLM. +>
+ +
+ +💡 Also run a simple Web UI with `gradio`: + +```bash +python3 -m huixiangdou.gradio +``` + + + +Or run a server to listen 23333, default pipeline is `chat_with_repo`: +```bash +python3 -m huixiangdou.server + +# test async API +curl -X POST http://127.0.0.1:23333/huixiangdou_stream -H "Content-Type: application/json" -d '{"text": "how to install mmpose","image": ""}' +# cURL sync API +curl -X POST http://127.0.0.1:23333/huixiangdou_inference -H "Content-Type: application/json" -d '{"text": "how to install mmpose","image": ""}' +``` + + +Please update the `repodir` documents, [good_questions](./resource/good_questions.json) and [bad_questions](./resource/bad_questions.json), and try your own domain knowledge (medical, financial, power, etc.). + +## III. Integration into Feishu, WeChat group + +- [**One-way** sending to Feishu group](./docs/zh/doc_send_only_lark_group.md) +- [**Two-way** Feishu group receiving and sending, recalling](./docs/zh/doc_add_lark_group.md) +- [Personal WeChat Android access](./docs/zh/doc_add_wechat_accessibility.md) +- [Personal WeChat wkteam access](./docs/zh/doc_add_wechat_commercial.md) + +## IV. Deploy web front and back end + +We provide `typescript` front-end and `python` back-end source code: + +- Multi-tenant management supported +- Zero programming access to Feishu and WeChat +- k8s friendly + +Same as [OpenXlab APP](https://openxlab.org.cn/apps/detail/tpoisonooo/huixiangdou-web), please read the [web deployment document](./web/README.md). + +# 🍴 Other Configurations + +## **CPU-only Edition** + +If there is no GPU available, model inference can be completed using the [siliconcloud](https://siliconflow.cn/) API. + +Taking docker miniconda+Python3.11 as an example, install CPU dependencies and run: + +```bash +# Start container +docker run -v /path/to/huixiangdou:/huixiangdou -p 7860:7860 -p 23333:23333 -it continuumio/miniconda3 /bin/bash +# Install dependencies +apt update +apt install python-dev libxml2-dev libxslt1-dev antiword unrtf poppler-utils pstotext tesseract-ocr flac ffmpeg lame libmad0 libsox-fmt-mp3 sox libjpeg-dev swig libpulse-dev +python3 -m pip install -r requirements-cpu.txt +# Establish knowledge base +python3 -m huixiangdou.service.feature_store --config_path config-cpu.ini +# Q&A test +python3 -m huixiangdou.main --standalone --config_path config-cpu.ini +# gradio UI +python3 -m huixiangdou.gradio --config_path config-cpu.ini +``` + +If you find the installation too slow, a pre-installed image is provided in [Docker Hub](https://hub.docker.com/repository/docker/tpoisonooo/huixiangdou/tags). Simply replace it when starting the docker. + +## **2G Cost-effective Edition** + +If your GPU mem exceeds 1.8G, or you pursue cost-effectiveness. This configuration discards the local LLM and uses remote LLM instead, which is the same as the standard edition. + +Take `siliconcloud` as an example, fill in the API TOKEN applied from the [official website](https://siliconflow.cn/) into `config-2G.ini` + +```toml +# config-2G.ini +[llm] +enable_local = 0 # Turn off local LLM +enable_remote = 1 # Only use remote +.. +remote_type = "siliconcloud" # Choose siliconcloud +remote_api_key = "YOUR-API-KEY-HERE" # Your API key +remote_llm_model = "alibaba/Qwen1.5-110B-Chat" +``` + +> \[!NOTE\] +> +>
+> Each Q&A scenario requires calling the LLM 7 times at worst, subject to the free user RPM limit, you can modify the rpm parameter in config.ini +>
+ +Execute the following to get the Q&A results + +```shell +python3 -m huixiangdou.main --standalone --config-path config-2G.ini # Start all services at once +``` + +## **10G Multimodal Edition** + +If you have 10G GPU mem, you can further support image and text retrieval. Just modify the model used in config.ini. + +```toml +# config-multimodal.ini +# !!! Download `https://huggingface.co/BAAI/bge-visualized/blob/main/Visualized_m3.pth` to `bge-m3` folder !!! +embedding_model_path = "BAAI/bge-m3" +reranker_model_path = "BAAI/bge-reranker-v2-minicpm-layerwise" +``` + +Note: + +- You need to manually download [Visualized_m3.pth](https://huggingface.co/BAAI/bge-visualized/blob/main/Visualized_m3.pth) to the [bge-m3](https://huggingface.co/BAAI/bge-m3) directory +- Install FlagEmbedding on main branch, we have made [bugfix](https://github.com/FlagOpen/FlagEmbedding/commit/3f84da0796d5badc3ad519870612f1f18ff0d1d3). [Here](https://github.com/FlagOpen/FlagEmbedding/blob/master/FlagEmbedding/visual/eva_clip/bpe_simple_vocab_16e6.txt.gz) you can download `bpe_simple_vocab_16e6.txt.gz` +- Install [requirements/multimodal.txt](./requirements/multimodal.txt) + +Run gradio to test, see the image and text retrieval result [here](https://github.com/InternLM/HuixiangDou/pull/326). + +```bash +python3 tests/test_query_gradio.py +``` + +## **80G Complete Edition** + +The "HuiXiangDou" in the WeChat experience group has enabled all features: + +- Serper search and SourceGraph search enhancement +- Group chat images, WeChat public account parsing +- Text coreference resolution +- Hybrid LLM +- Knowledge base is related to openmmlab's 12 repositories (1700 documents), refusing small talk + +Please read the following topics: + +- [Hybrid knowledge graph and dense retrieval](./docs/en/doc_knowledge_graph.md) +- [Refer to config-advanced.ini configuration to improve effects](./docs/en/doc_full_dev.md) +- [Group chat scenario anaphora resolution training](./sft) +- [Use wkteam WeChat access, integrate images, public account parsing, and anaphora resolution](./docs/zh/doc_add_wechat_commercial.md) +- [Use rag.py to annotate SFT training data](./docs/zh/doc_rag_annotate_sft_data.md) + +## **Android Tools** + +Contributors have provided [Android tools](./android) to interact with WeChat. The solution is based on system-level APIs, and in principle, it can control any UI (not limited to communication software). + +# 🛠️ FAQ + +1. What if the robot is too cold/too chatty? + + - Fill in the questions that should be answered in the real scenario into `resource/good_questions.json`, and fill the ones that should be rejected into `resource/bad_questions.json`. + - Adjust the theme content in `repodir` to ensure that the markdown documents in the main library do not contain irrelevant content. + + Re-run `feature_store` to update thresholds and feature libraries. + + ⚠️ You can directly modify `reject_throttle` in config.ini. Generally speaking, 0.5 is a high value; 0.2 is too low. + +2. Launch is normal, but out of memory during runtime? + + LLM long text based on transformers structure requires more memory. At this time, kv cache quantization needs to be done on the model, such as [lmdeploy quantization description](https://github.com/InternLM/lmdeploy/blob/main/docs/en/quantization). Then use docker to independently deploy Hybrid LLM Service. + +3. How to access other local LLM / After access, the effect is not ideal? + + - Open [hybrid llm service](./huixiangdou/service/llm_server_hybrid.py), add a new LLM inference implementation. + - Refer to [test_intention_prompt and test data](./tests/test_intention_prompt.py), adjust prompt and threshold for the new model, and update them into [prompt.py](./huixiangdou/service/prompt.py). + +4. What if the response is too slow/request always fails? + + - Refer to [hybrid llm service](./huixiangdou/service/llm_server_hybrid.py) to add exponential backoff and retransmission. + - Replace local LLM with an inference framework such as [lmdeploy](https://github.com/internlm/lmdeploy), instead of the native huggingface/transformers. + +5. What if the GPU memory is too low? + + At this time, it is impossible to run local LLM, and only remote LLM can be used in conjunction with text2vec to execute the pipeline. Please make sure that `config.ini` only uses remote LLM and turn off local LLM. + + +# 🍀 Acknowledgements + +- [KIMI](https://kimi.moonshot.cn/): Long text LLM, supports direct file upload +- [FlagEmbedding](https://github.com/FlagOpen/FlagEmbedding): BAAI RAG group +- [BCEmbedding](https://github.com/netease-youdao/BCEmbedding): Chinese-English bilingual feature model +- [Langchain-ChatChat](https://github.com/chatchat-space/Langchain-Chatchat): Application of Langchain and ChatGLM +- [GrabRedEnvelope](https://github.com/xbdcc/GrabRedEnvelope): WeChat red packet grab + +# 📝 Citation + +````shell +@misc{kong2024huixiangdou, + title={HuiXiangDou: Overcoming Group Chat Scenarios with LLM-based Technical Assistance}, + author={Huanjun Kong and Songyang Zhang and Jiaying Li and Min Xiao and Jun Xu and Kai Chen}, + year={2024}, + eprint={2401.08772}, + archivePrefix={arXiv}, + primaryClass={cs.CL} +} + +@misc{kong2024huixiangdoucr, + title={HuiXiangDou-CR: Coreference Resolution in Group Chats}, + author={Huanjun Kong}, + year={2024}, + eprint={2405.02817}, + archivePrefix={arXiv}, + primaryClass={cs.CL} +}``` +```` diff --git a/repodir/huixiangdou/README_zh.md b/repodir/huixiangdou/README_zh.md new file mode 100644 index 00000000..7b5429c8 --- /dev/null +++ b/repodir/huixiangdou/README_zh.md @@ -0,0 +1,425 @@ +[English](README.md) | 简体中文 + +
+ + +
+ + Wechat + + + + Readthedocs + + + YouTube + + + BiliBili + + + discord + + + Arxiv + + + Arxiv + +
+ +
+ +茴香豆是一个基于 LLM 的专业知识助手,优势: + +1. 设计预处理、拒答、响应三阶段 pipeline: + * `chat_in_group` 群聊场景,解答问题时不会消息泛滥。见 [2401.08772](https://arxiv.org/abs/2401.08772),[2405.02817](https://arxiv.org/abs/2405.02817),[混合检索](./docs/zh/doc_knowledge_graph.md)和[业务数据精度测试](./evaluation) + * `chat_with_repo` 实时聊天场景,响应更快 +2. 无需训练适用各行业,提供 CPU-only、2G、10G、20G、80G 规格配置 +3. 提供一整套前后端 web、android、算法源码,工业级开源可商用 + +查看[茴香豆已运行在哪些场景](./huixiangdou-inside.md);加入[微信群](resource/figures/wechat.jpg)直接体验群聊助手效果。 + +如果对你有用,麻烦 star 一下⭐ + +# 🔆 新功能 + +茴香豆 Web 版已发布到 [OpenXLab](https://openxlab.org.cn/apps/detail/tpoisonooo/huixiangdou-web),可以创建自己的知识库、更新正反例、开关网络搜索,聊天测试效果后,集成到飞书/微信群。 + +Web 版视频教程见 [BiliBili](https://www.bilibili.com/video/BV1S2421N7mn) 和 [YouTube](https://www.youtube.com/watch?v=ylXrT-Tei-Y)。 + +- \[2024/08\] `chat_with_repo` [pipeline](./huixiangdou/service/parallel_pipeline.py) +- \[2024/07\] 图文检索 & 移除 `langchain` 👍 +- \[2024/07\] [混合知识图谱和稠密检索,F1 提升 1.7%](./docs/zh/doc_knowledge_graph.md) 🎯 +- \[2024/06\] [评估 chunksize,splitter 和 text2vec 模型](./evaluation) 🎯 +- \[2024/05\] [wkteam 微信接入](./docs/zh/doc_add_wechat_commercial.md),整合图片&公众号解析、集成指代消歧 +- \[2024/05\] [SFT LLM 处理 NLP 任务,F1 提升 29%](./sft/) 🎯 + + + + + + + + +
🤗LoRA-Qwen1.5-14BLoRA-Qwen1.5-32Balpaca 数据arXiv
+- \[2024/04\] 实现 [RAG 标注 SFT 问答数据和样例](./docs/zh/doc_rag_annotate_sft_data.md) +- \[2024/04\] 发布 [web 前后端服务源码](./web) 👍 +- \[2024/03\] 新的[个人微信集成方法](./docs/zh/doc_add_wechat_accessibility.md)和[**预编译 apk**](https://github.com/InternLM/HuixiangDou/releases/download/v0.1.0rc1/huixiangdou-20240508.apk) ! +- \[2024/02\] \[实验功能\] [微信群](https://github.com/InternLM/HuixiangDou/blob/main/resource/figures/wechat.jpg) 集成多模态以实现 OCR + +# 📖 支持情况 + + + + + + + + + + + + + + + + + + + + + + +
+ LLM + + 文件格式 + + 检索方法 + + 接入方法 + + 预处理 +
+ +- [InternLM2/InternLM2.5](https://github.com/InternLM/InternLM) +- [Qwen/Qwen2](https://github.com/QwenLM/Qwen2) +- [浦语](https://internlm.openxlab.org.cn/) +- [StepFun](https://platform.stepfun.com) +- [KIMI](https://kimi.moonshot.cn) +- [DeepSeek](https://www.deepseek.com) +- [GLM (ZHIPU)](https://www.zhipuai.cn) +- [SiliconCloud](https://siliconflow.cn/zh-cn/siliconcloud) +- [Xi-Api](https://api.xi-ai.cn) + + + +- pdf +- word +- excel +- ppt +- html +- markdown +- txt + + + +- [知识图谱](./docs/zh/doc_knowledge_graph.md) +- [联网搜索](./huixiangdou/service/web_search.py) +- [SourceGraph](https://sourcegraph.com) +- 图文混合(仅 markdown) + + + +- 微信([android](./docs/zh/doc_add_wechat_accessibility.md)/[wkteam](./docs/zh/doc_add_wechat_commercial.md)) +- 飞书 +- [OpenXLab Web](https://openxlab.org.cn/apps/detail/tpoisonooo/huixiangdou-web) +- [Gradio Demo](./huixiangdou/gradio.py) +- [HTTP Server](./huixiangdou/server.py) + + + +- [指代消歧](https://arxiv.org/abs/2405.02817) + +
+ +# 📦 硬件要求 + +以下是不同特性所需显存,区别仅在**配置选项是否开启**。 + +| 配置示例 | 显存需求 | 描述 | Linux 系统已验证设备 | +| :----------------------------------------------: | :------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------: | +| [config-cpu.ini](./config-cpu.ini) | - | 用 [siliconcloud](https://siliconflow.cn/) API
仅检索文本 | ![](https://img.shields.io/badge/x86-passed-blue?style=for-the-badge) | +| [config-2G.ini](./config-2G.ini) | 2GB | 用 openai API(如 [kimi](https://kimi.moonshot.cn)、[deepseek](https://platform.deepseek.com/usage) 和 [stepfun](https://platform.stepfun.com/))
仅检索文本 | ![](https://img.shields.io/badge/1660ti%206G-passed-blue?style=for-the-badge) | +| [config-multimodal.ini](./config-multimodal.ini) | 10GB | 用 openai API 做 LLM,图文检索 | ![](https://img.shields.io/badge/3090%2024G-passed-blue?style=for-the-badge) | +| 【标准版】[config.ini](./config.ini) | 19GB | 本地部署 LLM,单模态 | ![](https://img.shields.io/badge/3090%2024G-passed-blue?style=for-the-badge) | +| [config-advanced.ini](./config-advanced.ini) | 80GB | 本地 LLM,指代消歧,单模态,微信群实用 | ![](https://img.shields.io/badge/A100%2080G-passed-blue?style=for-the-badge) | + +# 🔥 运行标准版 + +我们以标准版(本地运行 LLM,纯文本检索)为例,介绍 HuixiangDou 功能。其他版本仅仅是配置选项不同。 + +## 一、下载模型,安装依赖 + +首先[点击同意 BCE 模型协议](https://huggingface.co/maidalun1020/bce-embedding-base_v1),命令行登录 huggingface + +```shell +huggingface-cli login +``` + +安装依赖 + +```bash +# parsing `word` format requirements +apt update +apt install python-dev libxml2-dev libxslt1-dev antiword unrtf poppler-utils pstotext tesseract-ocr flac ffmpeg lame libmad0 libsox-fmt-mp3 sox libjpeg-dev swig libpulse-dev +# python requirements +pip install -r requirements.txt +# python3.8 安装 faiss-gpu 而不是 faiss +``` + +## 二、创建知识库,执行测试 + +我们将用 mmpose 的文档构建 mmpose 知识库,过滤问题。如有自己的文档,放入 `repodir` 下即可。 + +复制下面所有命令(包含 '#' 符号)建立知识库。 + +```shell +# 下载知识库,我们仅以 mmpose 的文档为例。repodir下可以放任何自己的文档 +cd HuixiangDou +mkdir repodir +git clone https://github.com/open-mmlab/mmpose --depth=1 repodir/mmpose + +# 把 repodir 的特征保存到 workdir,把正反例阈值更新进 `config.ini` +mkdir workdir +python3 -m huixiangdou.service.feature_store +``` + +结束后执行 `python3 -m huixiangdou.main --standalone`,此时回复 mmpose 相关问题(和知识库相关),同时不响应天气问题。 + +```bash +python3 -m huixiangdou.main --standalone + ++-----------------------+---------+--------------------------------+-----------------+ +| Query | State | Part of Reply | References | ++=======================+=========+================================+=================+ +| 请问如何安装 mmpose ? | success | 要安装 mmpose,请按照以下步骤操作..| installation.md | +-------------------------------------------------------------------------------------- +| 今天天气如何? | unrelated| .. | | ++-----------------------+---------+--------------------------------+-----------------+ +🔆 Input your question here, type `bye` for exit: +.. +``` + +> \[!NOTE\] +> +>
+> 如果每次重启 LLM 太慢,先 python3 -m huixiangdou.service.llm_server_hybrid;然后开新窗口,每次只执行 python3 -m huixiangdou.main 不重启 LLM。 +>
+ +
+ +💡 也可以启动 `gradio` 搭建一个简易的 Web UI,默认绑定 7860 端口: + +```bash +python3 -m huixiangdou.gradio +# 若已单独运行 `llm_server_hybrid.py`,可以 +# python3 -m huixiangdou.gradio --no-standalone +``` + + + +或者启动服务端,监听 23333 端口。默认使用 `chat_with_repo` pipeline: +```bash +python3 -m huixiangdou.server + +# cURL 测试状态回调接口 +curl -X POST http://127.0.0.1:23333/huixiangdou_stream -H "Content-Type: application/json" -d '{"text": "how to install mmpose","image": ""}' +# cURL 测试同步接口 +curl -X POST http://127.0.0.1:23333/huixiangdou_inference -H "Content-Type: application/json" -d '{"text": "how to install mmpose","image": ""}' +``` + +请调整 `repodir` 文档、[good_questions](./resource/good_questions.json) 和 [bad_questions](./resource/bad_questions.json),尝试自己的领域知识(医疗,金融,电力等)。 + +## 三、集成到飞书、微信群 + +- [**单向**发送到飞书群](./docs/zh/doc_send_only_lark_group.md) +- [**双向**飞书群收发、撤回](./docs/zh/doc_add_lark_group.md) +- [个微 android 接入](./docs/zh/doc_add_wechat_accessibility.md) +- [个微 wkteam 接入](./docs/zh/doc_add_wechat_commercial.md) + +## 四、WEB 前后端部署,零编程集成飞书微信 + +我们提供了完整的 typescript 前端和 python 后端服务源码: + +- 支持多租户管理 +- 零编程接入飞书、微信群 +- 架构松散,适合 k8s + +效果同 [OpenXlab APP](https://openxlab.org.cn/apps/detail/tpoisonooo/huixiangdou-web) ,请阅读 [web 部署文档](./web/README.md)。 + +# 🍴 其他配置 + +## **纯 CPU 版** + +若没有 GPU,可以使用 [siliconcloud](https://siliconflow.cn/) API 完成模型推理。 + +以 docker miniconda+Python3.11 为例,安装 cpu 依赖,运行: + +```bash +# 启动容器 +docker run -v /path/to/huixiangdou:/huixiangdou -p 7860:7860 -p 23333:23333 -it continuumio/miniconda3 /bin/bash +# 装依赖 +apt update +apt install python-dev libxml2-dev libxslt1-dev antiword unrtf poppler-utils pstotext tesseract-ocr flac ffmpeg lame libmad0 libsox-fmt-mp3 sox libjpeg-dev swig libpulse-dev +python3 -m pip install -r requirements-cpu.txt +# 建立知识库 +python3 -m huixiangdou.service.feature_store --config_path config-cpu.ini +# 问答测试 +python3 -m huixiangdou.main --standalone --config_path config-cpu.ini +# gradio UI +python3 -m huixiangdou.gradio --config_path config-cpu.ini +``` + +如果装依赖太慢,[dockerhub 里](https://hub.docker.com/repository/docker/tpoisonooo/huixiangdou/tags)提供了安装好依赖的镜像,docker 启动时替换即可。 + +## **2G 实惠版** + +如果你的显存超过 1.8G,或追求性价比。此配置扔掉了本地 LLM,使用 remote LLM 代替,其他和标准版相同。 + +以 siliconcloud 为例,把[官网申请](https://siliconflow.cn/zh-cn/siliconcloud) 的 API TOKEN 填入 `config-2G.ini` + +```toml +[llm] +enable_local = 0 # 关掉本地 LLM +enable_remote = 1 # 只用远程 +.. +remote_type = "siliconcloud" # 选择 siliconcloud +remote_api_key = "YOUR-API-KEY-HERE" # 填 API key +remote_llm_model = "alibaba/Qwen1.5-110B-Chat" +``` + +> \[!NOTE\] +> +>
+> 每次问答最坏情况要调用 7 次 LLM,受免费用户 RPM 限制,可修改 config.ini 中 rpm 参数 +>
+ +执行命令获取问答结果 + +```shell +python3 -m huixiangdou.main --standalone --config-path config-2G.ini # 一次启动所有服务 +``` + +## **10G 多模态版** + +如果你有 10G 显存,那么可以进一步支持图文检索。仅需修改 config.ini 使用的模型。 + +```toml +# config-multimodal.ini +# !!! Download `https://huggingface.co/BAAI/bge-visualized/blob/main/Visualized_m3.pth` to `bge-m3` folder !!! +embedding_model_path = "BAAI/bge-m3" +reranker_model_path = "BAAI/bge-reranker-v2-minicpm-layerwise" +``` + +需要注意: + +- 先下载 [bge-m3](https://huggingface.co/BAAI/bge-m3),然后把 [Visualized_m3.pth](https://huggingface.co/BAAI/bge-visualized/blob/main/Visualized_m3.pth) 放进 `bge-m3` 目录 +- FlagEmbedding 需要安装 master 最新版,我们做了 [bugfix](https://github.com/FlagOpen/FlagEmbedding/commit/3f84da0796d5badc3ad519870612f1f18ff0d1d3);[这里](https://github.com/FlagOpen/FlagEmbedding/blob/master/FlagEmbedding/visual/eva_clip/bpe_simple_vocab_16e6.txt.gz)可以下载 BGE 打包漏掉的 `bpe_simple_vocab_16e6.txt.gz` +- 安装 [requirements/multimodal.txt](./requirements/multimodal.txt) + +运行 gradio 测试,图文检索效果见[这里](https://github.com/InternLM/HuixiangDou/pull/326). + +```bash +python3 tests/test_query_gradio.py +``` + +## **80G 完整版** + +微信体验群里的 “茴香豆” 开启了全部功能: + +- Serper 搜索及 SourceGraph 搜索增强 +- 群聊图片、微信公众号解析 +- 文本指代消歧 +- 混合 LLM +- 知识库为 openmmlab 相关的 12 个 repo(1700 个文档),拒绝闲聊 + +请阅读以下话题: + +- [混合**知识图谱**和稠密检索提升精度](./docs/zh/doc_knowledge_graph.md) +- [参照 config-advanced.ini 配置提升效果](./docs/zh/doc_full_dev.md) +- [群聊场景指代消歧训练](./sft) +- [使用 wkteam 微信接入,整合图片、公众号解析和指代消歧](./docs/zh/doc_add_wechat_commercial.md) +- [使用 rag.py 标注 SFT 训练数据](./docs/zh/doc_rag_annotate_sft_data.md) + +## **移动端** + +贡献者提供了[android工具](./android) 完成微信接入。方案基于系统层 API,原理上可以控制任何 UI(不限于通讯软件)。 + +# 🛠️ FAQ + +1. 机器人太高冷/太嘴碎怎么办? + + - 把真实场景中,应该回答的问题填入`resource/good_questions.json`,应该拒绝的填入`resource/bad_questions.json` + - 调整 `repodir` 中的文档,确保不包含场景无关内容 + + 重新执行 `feature_store` 来更新阈值和特征库。 + + ⚠️ 如果你足够自信,也可以直接修改 config.ini 的 `reject_throttle` 数值,一般来说 0.5 是很高的值;0.2 过低。 + +2. 启动正常,但运行期间显存 OOM 怎么办? + + 基于 transformers 结构的 LLM 长文本需要更多显存,此时需要对模型做 kv cache 量化,如 [lmdeploy 量化说明](https://github.com/InternLM/lmdeploy/blob/main/docs/zh_cn/quantization)。然后使用 docker 独立部署 Hybrid LLM Service。 + +3. 如何接入其他 local LLM / 接入后效果不理想怎么办? + + - 打开 [hybrid llm service](./huixiangdou/service/llm_server_hybrid.py),增加新的 LLM 推理实现 + - 参照 [test_intention_prompt 和测试数据](./tests/test_intention_prompt.py),针对新模型调整 prompt 和阈值,更新到 [prompt.py](./huixiangdou/service/prompt.py) + +4. 响应太慢/网络请求总是失败怎么办? + + - 参考 [hybrid llm service](./huixiangdou/service/llm_server_hybrid.py) 增加指数退避重传 + - local LLM 替换为 [lmdeploy](https://github.com/internlm/lmdeploy) 等推理框架,而非原生的 huggingface/transformers + +5. 机器配置低,GPU 显存不足怎么办? + + 此时无法运行 local LLM,只能用 remote LLM 配合 text2vec 执行 pipeline。请确保 `config.ini` 只使用 remote LLM,关闭 local LLM + +6. 报错 `(500, 'Internal Server Error')`,意为 standalone 模式启动的 LLM 服务没访问到。按如下方式定位 + + - 执行 `python3 -m huixiangdou.service.llm_server_hybrid` 确定 LLM 服务无报错,监听的端口和配置一致。检查结束后按 ctrl-c 关掉。 + - 检查 `config.ini` 中各种 TOKEN 书写正确。 + + +# 🍀 致谢 + +- [KIMI](https://kimi.moonshot.cn/): 长文本 LLM,支持直接上传文件 +- [FlagEmbedding](https://github.com/FlagOpen/FlagEmbedding): BAAI RAG 组 +- [BCEmbedding](https://github.com/netease-youdao/BCEmbedding): 中英双语特征模型 +- [Langchain-ChatChat](https://github.com/chatchat-space/Langchain-Chatchat): Langchain 和 ChatGLM 的应用 +- [GrabRedEnvelope](https://github.com/xbdcc/GrabRedEnvelope): 微信抢红包 + +# 📝 引用 + +```shell +@misc{kong2024huixiangdou, + title={HuixiangDou: Overcoming Group Chat Scenarios with LLM-based Technical Assistance}, + author={Huanjun Kong and Songyang Zhang and Jiaying Li and Min Xiao and Jun Xu and Kai Chen}, + year={2024}, + eprint={2401.08772}, + archivePrefix={arXiv}, + primaryClass={cs.CL} +} + +@misc{kong2024huixiangdoucr, + title={HuixiangDou-CR: Coreference Resolution in Group Chats}, + author={Huanjun Kong}, + year={2024}, + eprint={2405.02817}, + archivePrefix={arXiv}, + primaryClass={cs.CL} +} +``` diff --git a/repodir/huixiangdou/android/.gitignore b/repodir/huixiangdou/android/.gitignore new file mode 100644 index 00000000..c2e82418 --- /dev/null +++ b/repodir/huixiangdou/android/.gitignore @@ -0,0 +1,18 @@ +*.iml +.gradle +/local.properties +/.idea/workspace.xml +/.idea/libraries +.DS_Store +/build + +/captures +/buildsystem/keystore.properties +/buildsystem/qianghongbao.jks +/xbd +/app/src/production +app/src/main/java/com/carlos/grabredenvelope/local +/apk + +sentry.properties +/.idea/compiler.xml diff --git a/repodir/huixiangdou/android/.idea/.gitignore b/repodir/huixiangdou/android/.idea/.gitignore new file mode 100644 index 00000000..26d33521 --- /dev/null +++ b/repodir/huixiangdou/android/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/repodir/huixiangdou/android/.idea/assetWizardSettings.xml b/repodir/huixiangdou/android/.idea/assetWizardSettings.xml new file mode 100644 index 00000000..6b96aeb8 --- /dev/null +++ b/repodir/huixiangdou/android/.idea/assetWizardSettings.xml @@ -0,0 +1,38 @@ + + + + + + \ No newline at end of file diff --git a/repodir/huixiangdou/android/.idea/caches/build_file_checksums.ser b/repodir/huixiangdou/android/.idea/caches/build_file_checksums.ser new file mode 100644 index 0000000000000000000000000000000000000000..5f5a6f276f147794c39fcae41667498631d4cfff GIT binary patch literal 584 zcmZ4UmVvdnh`~NNKUXg?FQq6yGexf?KR>5fFEb@IQ7^qHF(oHeub?PDD>b=9F91S2 zm1gFoxMk*~I%lLNXBU^|7Q2L-Ts|(GuF1r}nf0c10Sz`0Dm>V`KVi>5yc%=qnjBeaBp zCkfnA7XCubz)rRzg2&_{$hhAHwqoIZLS?u7z| z-6EOS59}#n5X7T;v%bvy6PF^N%(|gcaAKD3Z=mXw)ZBc1?A|eX!*FfG{{z=}%Qpz@ h;Bb=!dMB}<0Ec3~8RmsSrE*Jt%!;_hJjq3|0swUY%0d7D literal 0 HcmV?d00001 diff --git a/repodir/huixiangdou/android/.idea/codeStyles/Project.xml b/repodir/huixiangdou/android/.idea/codeStyles/Project.xml new file mode 100644 index 00000000..681f41ae --- /dev/null +++ b/repodir/huixiangdou/android/.idea/codeStyles/Project.xml @@ -0,0 +1,116 @@ + + + + + + + +
+ + + + xmlns:android + + ^$ + + + +
+
+ + + + xmlns:.* + + ^$ + + + BY_NAME + +
+
+ + + + .*:id + + http://schemas.android.com/apk/res/android + + + +
+
+ + + + .*:name + + http://schemas.android.com/apk/res/android + + + +
+
+ + + + name + + ^$ + + + +
+
+ + + + style + + ^$ + + + +
+
+ + + + .* + + ^$ + + + BY_NAME + +
+
+ + + + .* + + http://schemas.android.com/apk/res/android + + + ANDROID_ATTRIBUTE_ORDER + +
+
+ + + + .* + + .* + + + BY_NAME + +
+
+
+
+
+
\ No newline at end of file diff --git a/repodir/huixiangdou/android/.idea/codeStyles/codeStyleConfig.xml b/repodir/huixiangdou/android/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 00000000..44b35dda --- /dev/null +++ b/repodir/huixiangdou/android/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/repodir/huixiangdou/android/.idea/copyright/profiles_settings.xml b/repodir/huixiangdou/android/.idea/copyright/profiles_settings.xml new file mode 100644 index 00000000..e7bedf33 --- /dev/null +++ b/repodir/huixiangdou/android/.idea/copyright/profiles_settings.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/repodir/huixiangdou/android/.idea/dbnavigator.xml b/repodir/huixiangdou/android/.idea/dbnavigator.xml new file mode 100644 index 00000000..efc9bd2d --- /dev/null +++ b/repodir/huixiangdou/android/.idea/dbnavigator.xml @@ -0,0 +1,458 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/repodir/huixiangdou/android/.idea/deploymentTargetDropDown.xml b/repodir/huixiangdou/android/.idea/deploymentTargetDropDown.xml new file mode 100644 index 00000000..81d2a465 --- /dev/null +++ b/repodir/huixiangdou/android/.idea/deploymentTargetDropDown.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/repodir/huixiangdou/android/.idea/dictionaries/caochang.xml b/repodir/huixiangdou/android/.idea/dictionaries/caochang.xml new file mode 100644 index 00000000..5748c236 --- /dev/null +++ b/repodir/huixiangdou/android/.idea/dictionaries/caochang.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/repodir/huixiangdou/android/.idea/encodings.xml b/repodir/huixiangdou/android/.idea/encodings.xml new file mode 100644 index 00000000..97626ba4 --- /dev/null +++ b/repodir/huixiangdou/android/.idea/encodings.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/repodir/huixiangdou/android/.idea/gradle.xml b/repodir/huixiangdou/android/.idea/gradle.xml new file mode 100644 index 00000000..ac689d1d --- /dev/null +++ b/repodir/huixiangdou/android/.idea/gradle.xml @@ -0,0 +1,20 @@ + + + + + + + \ No newline at end of file diff --git a/repodir/huixiangdou/android/.idea/inspectionProfiles/Project_Default.xml b/repodir/huixiangdou/android/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 00000000..5eb36df7 --- /dev/null +++ b/repodir/huixiangdou/android/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/repodir/huixiangdou/android/.idea/jarRepositories.xml b/repodir/huixiangdou/android/.idea/jarRepositories.xml new file mode 100644 index 00000000..aa37b4dc --- /dev/null +++ b/repodir/huixiangdou/android/.idea/jarRepositories.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/repodir/huixiangdou/android/.idea/kotlinCodeInsightSettings.xml b/repodir/huixiangdou/android/.idea/kotlinCodeInsightSettings.xml new file mode 100644 index 00000000..90505828 --- /dev/null +++ b/repodir/huixiangdou/android/.idea/kotlinCodeInsightSettings.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/repodir/huixiangdou/android/.idea/kotlinc.xml b/repodir/huixiangdou/android/.idea/kotlinc.xml new file mode 100644 index 00000000..dd185e22 --- /dev/null +++ b/repodir/huixiangdou/android/.idea/kotlinc.xml @@ -0,0 +1,9 @@ + + + + + + + \ No newline at end of file diff --git a/repodir/huixiangdou/android/.idea/markdown-navigator.xml b/repodir/huixiangdou/android/.idea/markdown-navigator.xml new file mode 100644 index 00000000..076726f8 --- /dev/null +++ b/repodir/huixiangdou/android/.idea/markdown-navigator.xml @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/repodir/huixiangdou/android/.idea/markdown-navigator/profiles_settings.xml b/repodir/huixiangdou/android/.idea/markdown-navigator/profiles_settings.xml new file mode 100644 index 00000000..57927c5a --- /dev/null +++ b/repodir/huixiangdou/android/.idea/markdown-navigator/profiles_settings.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/repodir/huixiangdou/android/.idea/migrations.xml b/repodir/huixiangdou/android/.idea/migrations.xml new file mode 100644 index 00000000..f8051a6f --- /dev/null +++ b/repodir/huixiangdou/android/.idea/migrations.xml @@ -0,0 +1,10 @@ + + + + + + \ No newline at end of file diff --git a/repodir/huixiangdou/android/.idea/misc.xml b/repodir/huixiangdou/android/.idea/misc.xml new file mode 100644 index 00000000..4e41ce57 --- /dev/null +++ b/repodir/huixiangdou/android/.idea/misc.xml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/repodir/huixiangdou/android/.idea/modules.xml b/repodir/huixiangdou/android/.idea/modules.xml new file mode 100644 index 00000000..20fee022 --- /dev/null +++ b/repodir/huixiangdou/android/.idea/modules.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/repodir/huixiangdou/android/.idea/vcs.xml b/repodir/huixiangdou/android/.idea/vcs.xml new file mode 100644 index 00000000..62bd7a01 --- /dev/null +++ b/repodir/huixiangdou/android/.idea/vcs.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/repodir/huixiangdou/android/.travis.yml b/repodir/huixiangdou/android/.travis.yml new file mode 100644 index 00000000..af768f87 --- /dev/null +++ b/repodir/huixiangdou/android/.travis.yml @@ -0,0 +1,28 @@ +language: android +sudo: false # 为了开启基于容器的 Travis CI 任务,让编译效率更高 +android: + components: + # Uncomment the lines below if you want to + # use the latest revision of Android SDK Tools + # - platform-tools + # - tools + # The BuildTools version used by your project + - build-tools-28.0.3 + # The SDK version used to compile your project + - android-28 + # Additional components + - extra-google-google_play_services + - extra-google-m2repository + - extra-android-m2repository + - addon-google_apis-google-19 + # Specify at least one system image, + # if you need to run emulator(s) during your tests + - sys-img-armeabi-v7a-android-19 + - sys-img-x86-android-17 + +before_script: + - mkdir "$ANDROID_HOME/licenses" || true + - echo "24333f8a63b6825ea9c5514f83c2829b004d1fee" > "$ANDROID_HOME/licenses/android-sdk-license" + +script: + - ./gradlew assembleDev diff --git a/repodir/huixiangdou/android/LICENSE b/repodir/huixiangdou/android/LICENSE new file mode 100644 index 00000000..f288702d --- /dev/null +++ b/repodir/huixiangdou/android/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/repodir/huixiangdou/android/README.md b/repodir/huixiangdou/android/README.md new file mode 100644 index 00000000..55aecd74 --- /dev/null +++ b/repodir/huixiangdou/android/README.md @@ -0,0 +1,10 @@ +# 茴香豆 Android 辅助 + +这是基于 [抢红包 app](https://github.com/xbdcc/GrabRedEnvelope) 软件的二次开发。 + +* 移除抢红包功能,重新用于 LLM RAG chat +* 它基于 android 系统 API 工作,原理上可以控制所有 UI(不只是即时通讯软件),风险自行承担 + +# License + +注意软件使用 [GPL 协议](LICENSE)。 diff --git a/repodir/huixiangdou/android/build.gradle b/repodir/huixiangdou/android/build.gradle new file mode 100644 index 00000000..a55d6be4 --- /dev/null +++ b/repodir/huixiangdou/android/build.gradle @@ -0,0 +1,39 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. +buildscript { + ext.kotlin_version = '1.7.20' + repositories { + google() + jcenter() + maven { url 'https://jitpack.io' } + } + dependencies { + classpath 'com.android.tools.build:gradle:7.3.1' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath 'org.greenrobot:greendao-gradle-plugin:3.3.0' // add plugin + classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" + + classpath 'io.sentry:sentry-android-gradle-plugin:3.0.1' + + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } +} +allprojects { + repositories { + google() + jcenter() + maven { url 'https://jitpack.io' } + + maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' } + maven{ url 'https://maven.aliyun.com/repository/public'} + + maven { url "https://kotlin.bintray.com/kotlinx" } + maven { url 'https://dl.bintray.com/xbdcc/maven' } + } +} +task clean(type: Delete) { + delete rootProject.buildDir +} + + + diff --git a/repodir/huixiangdou/android/buildsystem/debug.keystore b/repodir/huixiangdou/android/buildsystem/debug.keystore new file mode 100644 index 0000000000000000000000000000000000000000..b2ccfae5ad11aefa3aaf98fa2b2643b6f3b6a15f GIT binary patch literal 1268 zcmezO_TO6u1_mY|W&~sY#JrTE{LGY;)TGk%?9@u2xcTlQCo_R+b{jM?Z8zX!}5Yr$U)fq5l$85d_6-VR}1_|?$Q*{7sUvCpQ}aQbtJEq4ry4vNY67YUwS`qsk~YBQAzjqI?q3!jm?Z)rN5lsuu&)Nd)~sy zr%P*`^caqFylJT^J-@$GD7RX@*yEK)>8cNmdm?lyn^v4knY2MW>!Ylo+NRWoiiejp z+io{j2sL-_zrZqc`7ER0KV8djR2^D+aUN&czO3^xw@OPn^j0h{_;61`fAQ+8$M&Dr z4Ri6367k(Ncj9{e2ikkN`Wb$fi^nK?YFCQ7Gsr%x%IKHi)Sj50MLo_vyZqmBzs}bC zOn1BW9+ju7ZDjfsm>17EY0{&*Fy;UK9KScuL%BFQzEr1d)7X^t@2foT-2NuErip6j zye1_ccR%vXE&1&KoBFZuLJ!vbJF|^{gZvYQ&%$!Txi?JoO*AXkzH)Nhed5T=pQ^Wf z=gcr*75u`e#1dR%KE2o{lk0%`J9TXl#(6V?+vb>REIT!$E8y`2@i^5pEn6S{k~#P~ z!u4OVht**j``?7u(b{D9CbftJl^5Gg$mu>_b71`i(b~OJH)P!}*(`d? zd|H!CYv)b=`@%tvHsCY>_fsX8pBU zxBjW2`TYaC&$T=|xl_RAp4%_ep9$yoZOe^d^EtC9JJX^0^1_?f^2_X>n=Ht5{R&Ro ztPy&q2A04S{nDU`@hK4dEnsG1WMX3RKYRG30WTY;R+~rLcV1w!W@RuiH{>?pWMd9x zVH0Kw4K@@2CVU`=OPJjel0gi`3`9U8%)-1-5d{}eo-vRU=QT7kFf%YUurM|>FpU!D zH8C}Lx&of8d2BtzDlesD1uZo3Kr`{KHx~miHiPY7JL}2=G`4p8Rr!3GQ^|Yjq_6w_&ME9uLNwA&J$GHs+~n@5s9}9e zOIB?*UojIiBLgF{^MFCf40M;p&I4*|k6KJ(IwrU&@M2^987s91apK1{7cE=GIQz_( zzf+@lFNsYP>FY8Iy_@uLa@v$tk`6Qdw)j5~S?D@_#});x(@M)&cpO#MKHHcmm!%tc zq$}1%c!D=0>(@N|23UVqeJ$Ha@c2YM0oW&E& literal 0 HcmV?d00001 diff --git a/repodir/huixiangdou/android/buildsystem/default.properties b/repodir/huixiangdou/android/buildsystem/default.properties new file mode 100644 index 00000000..b7b8e4c8 --- /dev/null +++ b/repodir/huixiangdou/android/buildsystem/default.properties @@ -0,0 +1,23 @@ +# keystore +keyAlias= androiddebugkey +keyPassword= android +storeFile= ../buildsystem/debug.keystore +storePassword= android + +# other +JPUSH_APPKEY = + +#测试的 +UMENG_APPKEY_DEV = +#正式的 +UMENG_APPKEY = + +BUGLY_KEY_DEV = +BUGLY_KEY = + +#sentry +SENTRY_DSN_DEV = +SENTRY_DSN = + + + diff --git a/repodir/huixiangdou/android/demo/.gitignore b/repodir/huixiangdou/android/demo/.gitignore new file mode 100644 index 00000000..796b96d1 --- /dev/null +++ b/repodir/huixiangdou/android/demo/.gitignore @@ -0,0 +1 @@ +/build diff --git a/repodir/huixiangdou/android/demo/build.gradle b/repodir/huixiangdou/android/demo/build.gradle new file mode 100644 index 00000000..a06b4d62 --- /dev/null +++ b/repodir/huixiangdou/android/demo/build.gradle @@ -0,0 +1,48 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +android { + compileSdkVersion 33 + + defaultConfig { + applicationId "com.carlos.grabredenvelope.demo" + minSdkVersion 18 + targetSdkVersion 33 + versionCode 1 + versionName "1.0" + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + lint { + disable 'GoogleAppIndexingWarning' + } + namespace 'com.carlos.grabredenvelope.demo' + +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation 'com.github.xbdcc:cutils:0.0.18' + testImplementation 'junit:junit:4.12' + androidTestImplementation 'androidx.test:runner:1.1.1' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' + + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.0.1' + implementation 'com.squareup.okhttp3:okhttp:4.9.0' + implementation 'com.google.code.gson:gson:2.8.9' + implementation 'com.google.android.material:material:1.2.0' +} + + +task hello { + doLast { + println 'Hello world' + } +} \ No newline at end of file diff --git a/repodir/huixiangdou/android/demo/proguard-rules.pro b/repodir/huixiangdou/android/demo/proguard-rules.pro new file mode 100644 index 00000000..f1b42451 --- /dev/null +++ b/repodir/huixiangdou/android/demo/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/repodir/huixiangdou/android/demo/src/androidTest/java/com/carlos/grabredenvelope/demo/ExampleInstrumentedTest.kt b/repodir/huixiangdou/android/demo/src/androidTest/java/com/carlos/grabredenvelope/demo/ExampleInstrumentedTest.kt new file mode 100644 index 00000000..1d42cd1f --- /dev/null +++ b/repodir/huixiangdou/android/demo/src/androidTest/java/com/carlos/grabredenvelope/demo/ExampleInstrumentedTest.kt @@ -0,0 +1,23 @@ +package com.carlos.grabredenvelope.demo + +import androidx.test.InstrumentationRegistry +import androidx.test.runner.AndroidJUnit4 +import org.junit.Assert.assertEquals +import org.junit.Test +import org.junit.runner.RunWith + + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getTargetContext() + assertEquals("com.carlos.grabredenvelope.demo", appContext.packageName) + } +} diff --git a/repodir/huixiangdou/android/demo/src/main/AndroidManifest.xml b/repodir/huixiangdou/android/demo/src/main/AndroidManifest.xml new file mode 100644 index 00000000..7479a6e2 --- /dev/null +++ b/repodir/huixiangdou/android/demo/src/main/AndroidManifest.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/repodir/huixiangdou/android/demo/src/main/java/com/carlos/grabredenvelope/demo/MainActivity.kt b/repodir/huixiangdou/android/demo/src/main/java/com/carlos/grabredenvelope/demo/MainActivity.kt new file mode 100644 index 00000000..407f80ac --- /dev/null +++ b/repodir/huixiangdou/android/demo/src/main/java/com/carlos/grabredenvelope/demo/MainActivity.kt @@ -0,0 +1,63 @@ +package com.carlos.grabredenvelope.demo + +import android.content.Intent +import android.os.Bundle +import android.provider.Settings +import android.text.Editable +import android.widget.Button +import android.widget.EditText +import android.widget.Switch +import android.widget.Toast +import com.carlos.cutils.base.CBaseAccessibilityActivity +import com.google.android.material.switchmaterial.SwitchMaterial + +class MainActivity : CBaseAccessibilityActivity() { + + private lateinit var btn_jump: Button + private lateinit var btn_url: Button + private lateinit var et_url: EditText + private lateinit var sw_debug: SwitchMaterial + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_main) + initView() + } + + private fun initView() { + btn_jump = findViewById(R.id.btn_jump) + et_url = findViewById(R.id.et_url) + sw_debug = findViewById(R.id.sw_debug) + + var helper = SharedPreferenceHelper(applicationContext) + + var default_url = et_url.text.toString() + var get_saved_url: String = helper.getString("URL", default_url) + et_url.text = Editable.Factory.getInstance().newEditable(get_saved_url) + + btn_jump.setOnClickListener { + var debug = sw_debug.isChecked() + helper.saveBoolean("DEBUG", debug) + + var url: String = helper.getString("URL", "") + if (url.isEmpty()) { + Toast.makeText(this@MainActivity, "请确认 URL 正确,点击确定!", Toast.LENGTH_LONG).show() + } else { + startActivity(Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS)) + Toast.makeText(this@MainActivity, "找到(茴香豆)开启或关闭。", Toast.LENGTH_LONG) + .show() + } + + } + + btn_url = findViewById(R.id.btn_url) + btn_url.setOnClickListener { + var url = et_url.text.toString() + var helper = SharedPreferenceHelper(applicationContext) + helper.saveString("URL", url) + + Toast.makeText(this@MainActivity, "修改成功", Toast.LENGTH_LONG) + .show() + } + } +} diff --git a/repodir/huixiangdou/android/demo/src/main/java/com/carlos/grabredenvelope/demo/SendEmojiService.kt b/repodir/huixiangdou/android/demo/src/main/java/com/carlos/grabredenvelope/demo/SendEmojiService.kt new file mode 100644 index 00000000..698a80fb --- /dev/null +++ b/repodir/huixiangdou/android/demo/src/main/java/com/carlos/grabredenvelope/demo/SendEmojiService.kt @@ -0,0 +1,404 @@ +package com.carlos.grabredenvelope.demo + +import android.accessibilityservice.AccessibilityService +import android.content.Context +import android.content.pm.PackageManager +import android.graphics.Rect +import android.os.Bundle +import android.os.Handler +import android.os.Looper +import android.util.Log +import android.view.accessibility.AccessibilityEvent +import android.view.accessibility.AccessibilityNodeInfo +import android.widget.EditText +import android.widget.RelativeLayout +import android.widget.TextView +import android.widget.Toast +import com.carlos.cutils.util.LogUtils +import com.carlos.grabredenvelope.demo.WechatConstants.RES_ID_EDIT_TEXT +import com.carlos.grabredenvelope.demo.WechatConstants.RES_ID_GROUP_NAME +import com.carlos.grabredenvelope.demo.WechatConstants.RES_ID_USER_CONTENT +import com.carlos.grabredenvelope.demo.WechatConstants.RES_ID_USER_HEADER +import com.carlos.grabredenvelope.demo.WechatConstants.RES_ID_USER_NAME +import com.carlos.grabredenvelope.demo.WechatConstants.RES_ID_USER_RL +import com.google.gson.Gson +import kotlinx.coroutines.withTimeout +import okhttp3.Call +import okhttp3.Callback +import okhttp3.MediaType +import okhttp3.MediaType.Companion.toMediaTypeOrNull +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.RequestBody.Companion.toRequestBody +import okhttp3.Response +import java.io.IOException +import java.util.concurrent.ExecutorService +import java.util.concurrent.Executors +import java.util.concurrent.TimeUnit +import kotlin.math.abs + +data class Query (val type: String, val content: String) +data class UserInfo (val query_id: String, val groupname: String, val username: String, val query: Query) +data class RSP (val code: Int, val state:String, val text:String, val references: ArrayList) +data class QueryResponsePair(val req: UserInfo, val rsp:RSP) +data class ChatResponse(val msg: String, val msgCode: Int, val data: ArrayList) + +class NoDoubleClick(val timeout: Int) { + var time = 0L + + fun pass(): Boolean { + var now = System.currentTimeMillis() + if (time == 0L) { + time = now + return true + } + + if (now - time > this.timeout) { + time = now + return true + } + return false + } +} + +class SendEmojiService : AccessibilityService() { + private var groupname: String = "" + private var lastusername:String = "" + private var lastcontent:String = "" + private var firststart = 0L + private var send_throttle = NoDoubleClick(4000) + private val WECHAT_PACKAGE = "com.tencent.mm" + private lateinit var executor: ExecutorService + private lateinit var httpclient: OkHttpClient + private var handler = Handler(Looper.getMainLooper()) + private var send_text:String = "" + private var asked_questions: ArrayList = ArrayList() + + override fun onCreate() { + super.onCreate() + LogUtils.d("onCreate") + } + + override fun onServiceConnected() { + super.onServiceConnected() + LogUtils.d("onServiceConnected") + httpclient = OkHttpClient() + executor = Executors.newSingleThreadExecutor(); + } + + override fun onAccessibilityEvent(event: AccessibilityEvent?) { + if (event == null) return + WechatConstants.setVersion(getAppVersionName(baseContext, WECHAT_PACKAGE)) + + when (event.eventType) { + AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED -> { + LogUtils.d("通知改变:$event") + } + AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED -> { + LogUtils.d("界面改变:$event") + } + AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED -> { + LogUtils.d("内容改变:$event") + // extract latest event in framelayout + parseSendMessage(event) + } + } + } + + private fun getAppVersionName(context: Context, packageName: String = context.packageName) = try { + context.packageManager.getPackageInfo(packageName, 0).versionName + } catch (e: PackageManager.NameNotFoundException) { + e.printStackTrace() + "" + } + + private fun chat_with_server() { + var userInfo = UserInfo(query_id = groupname, groupname = groupname, username = lastusername, Query(type = "text", content = lastcontent)) + var userJsonStr = Gson().toJson(userInfo) + + var helper = SharedPreferenceHelper(applicationContext) + var post_url:String = helper.getString("URL", "") + + executor.execute { + val client = OkHttpClient.Builder().connectTimeout(20, TimeUnit.SECONDS).writeTimeout(40, TimeUnit.SECONDS).readTimeout(180, TimeUnit.SECONDS).build() + // Define the JSON media type + val JSON :MediaType? = "application/json; charset=utf-8".toMediaTypeOrNull() + + // Create the request body + val requestBody = userJsonStr.toRequestBody(JSON) + + // Build the request + val request = Request.Builder() + .url(post_url) + .post(requestBody) + .build() + + client.newCall(request).enqueue(object : Callback{ + override fun onFailure(call: Call, e: IOException) { + Log.e("msg", e.toString()) + } + + override fun onResponse(call: Call, response: Response) { + Log.d("msg resp code", response.code.toString()) + + if (response.isSuccessful) { + // loop and wait resp + var retry: Int = 2 + while (retry > 0){ + retry -= 1 + + if (!send_text.equals("")) { + var input_and_send = send_text + send_text = "" + // got set send text + Log.d("msg will send", input_and_send) + handler.post { + var nodeInfo = rootInActiveWindow.findAccessibilityNodeInfosByViewId(RES_ID_EDIT_TEXT) + if (nodeInfo.size > 0) { + for (et in nodeInfo) { + if (et.className == EditText::class.java.name) { + var args = Bundle() + args.putCharSequence(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, input_and_send) + et.performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, args) + } + } + } + } + // sleep + Thread.sleep(2000) + // then click send button + if (!send_throttle.pass()) { + Log.d("msg", "click too more") + return + } + handler.post { + var do_send = click_send() + Log.d("msg", "action send?") + Log.d("msg", do_send.toString()) + } + break + } + parseRecvMessage() + Thread.sleep(5000) + } + if (retry == 0) { + Log.e("msg", "outdate") + } + + } else { + Log.d("msg", response.toString()) + } + } + }) + } + } + + private fun build_reply_text(reply: ChatResponse): String { + var helper = SharedPreferenceHelper(applicationContext) + var debug: Boolean = helper.getBoolean("DEBUG", true) + + var sb: StringBuilder = StringBuilder() + for (item in reply.data) { + if (debug) { + sb.append(item.req.query.content) + sb.append("\n---\n") + sb.append(item.rsp.code) + sb.append("\n") + sb.append(item.rsp.state) + sb.append("\n") + sb.append(item.rsp.text) + } else if (item.rsp.code == 0) { + sb.append(item.req.query.content) + sb.append("\n------\n") + sb.append(item.rsp.text) + } + } + return sb.toString() + } + + private fun parseRecvMessage() { + // poll message from server + var userInfo = UserInfo(query_id = groupname, groupname = groupname, username = lastusername, Query(type = "poll", content = "")) + var userJsonStr = Gson().toJson(userInfo) + + var helper = SharedPreferenceHelper(applicationContext) + var url:String = helper.getString("URL", "") + var post_url:String = url ?: "" + + if (!send_text.equals("")) { + return + } + + executor.execute { + val client = OkHttpClient() + // Define the JSON media type + val JSON :MediaType? = "application/json; charset=utf-8".toMediaTypeOrNull() + + // Create the request body + val requestBody = userJsonStr.toRequestBody(JSON) + + // Build the request + val request = Request.Builder() + .url(post_url) + .post(requestBody) + .build() + + client.newCall(request).enqueue(object : Callback{ + override fun onFailure(call: Call, e: IOException) { + Log.e("msg", e.toString()) + } + + override fun onResponse(call: Call, response: Response) { + Log.d("msg resp code", response.code.toString()) + + if (response.isSuccessful) { + var reply_text: String = response.body?.string() ?: "response.body?" + Log.d("msg parse recv", reply_text) + try { + var reply: ChatResponse = Gson().fromJson(reply_text, ChatResponse::class.java) + if (reply.data.size > 0){ + send_text = build_reply_text(reply) + } + } catch (e: Exception){ + Log.e("msg", e.toString()) + } + + } else { + Log.e("msg", response.toString()) + } + } + }) + } + } + + private fun parseWindow() { + // 声明参数 + var username = "" + // 获取所有的 rl,看里面的头像位置,头像是 imageview 且为正方形 + var nodeInfo = rootInActiveWindow.findAccessibilityNodeInfosByViewId(RES_ID_USER_RL) + if (nodeInfo.size < 1){ + Log.d("msg", "no rl found, return") + return + } + var lastTop = -1 + + for (rl in nodeInfo) { + if (rl.className != RelativeLayout::class.java.name) { + continue + } + + var rlBound = Rect(0,0,0,0) + rl.getBoundsInScreen(rlBound) + if (rlBound.top <= lastTop) { + // 只处理最下面的 RL + continue + } + + var headers = rl.findAccessibilityNodeInfosByViewId(RES_ID_USER_HEADER) + if (headers.size < 1) { + continue + } + lastTop = rlBound.top + + var header = headers[0] + var bound = Rect(0,0,0,0) + header.getBoundsInScreen(bound) + if (abs(bound.right - bound.left) == abs(bound.bottom - bound.top) && bound.left < 300) { + // 正方形是头像 + // 头像尺寸显然在屏幕左边,是发送者 + // 获取发送者的名字、发的内容 + var names = rl.findAccessibilityNodeInfosByViewId(RES_ID_USER_NAME) + if (names.size > 0) { + var tv = names[0] + if (tv.className == TextView::class.java.name){ + lastusername = tv.text.toString() + } + } + + + var contents = rl.findAccessibilityNodeInfosByViewId(RES_ID_USER_CONTENT) + if (contents.size > 0) { + var tv = contents[0] + if (tv.className == TextView::class.java.name){ + lastcontent = tv.text.toString() + } + } + } + } + } + + private fun parseSendMessage(event: AccessibilityEvent) { + var classname = event.className.toString() + var packagename = event.packageName.toString() + if (classname != "android.widget.LinearLayout" && classname != "android.widget.FrameLayout") return + if (packagename != "com.tencent.mm") return + + if (firststart == 0L) { + firststart = System.currentTimeMillis() + return + } + if (System.currentTimeMillis() - firststart < 1000 * 2) { + // 启动 2 秒内不处理,等 view 稳定 + Log.d("msg", "skip first 2 seconds") + return + } + + if (rootInActiveWindow == null) { + Log.d("msg", "rootInActiveWindow is null") + return + } + // fetch group name + var nodeInfo = rootInActiveWindow.findAccessibilityNodeInfosByViewId(RES_ID_GROUP_NAME) + for (tv in nodeInfo) { + if (tv.className == TextView::class.java.name) { + var content = tv.text.toString() + groupname = content + break + } + } + + // 解析发送者和发送内容 + parseWindow() + + nodeInfo = rootInActiveWindow.findAccessibilityNodeInfosByViewId(RES_ID_USER_NAME) + if (nodeInfo.size < 1) { + // 单聊 + // 单聊没有 RES_ID_USERNAME,需要把 groupname assign 成 username + lastusername = groupname + } + // 检查要发的内容是否跟之前重复,重复的句子不会请求服务器 + Log.d("send", groupname) + Log.d("send", lastusername) + Log.d("send", lastcontent) + + for (question in asked_questions) { + if (question.equals(lastcontent)) { + Log.w("msg", "this question already asked") + return + } + } + asked_questions.add(lastcontent) + Toast.makeText(application.applicationContext, lastcontent, Toast.LENGTH_SHORT) + chat_with_server() + } + + private fun click_send(): Boolean { + var do_send = false + val accessibilityNodeInfos = + rootInActiveWindow?.findAccessibilityNodeInfosByText("发送") ?: return false + for (accessibilityNodeInfo in accessibilityNodeInfos) { + do_send = true + accessibilityNodeInfo.performAction(AccessibilityNodeInfo.ACTION_CLICK) + } + return do_send + } + + override fun onInterrupt() { + LogUtils.d("onInterrupt") + } + + override fun onDestroy() { + super.onDestroy() + LogUtils.d("onDestroy") + } +} diff --git a/repodir/huixiangdou/android/demo/src/main/java/com/carlos/grabredenvelope/demo/SharedPreferenceHelper.kt b/repodir/huixiangdou/android/demo/src/main/java/com/carlos/grabredenvelope/demo/SharedPreferenceHelper.kt new file mode 100644 index 00000000..9feeecda --- /dev/null +++ b/repodir/huixiangdou/android/demo/src/main/java/com/carlos/grabredenvelope/demo/SharedPreferenceHelper.kt @@ -0,0 +1,27 @@ +package com.carlos.grabredenvelope.demo + +import android.content.Context +import android.content.SharedPreferences + +class SharedPreferenceHelper(context: Context) { + private val NAME = "huixiangdou" + private val sharedPreferences: SharedPreferences = context.getSharedPreferences(NAME, Context.MODE_PRIVATE) + + fun saveString(key: String, value: String) { + val editor = sharedPreferences.edit() + editor.putString(key, value) + editor.commit() + } + + fun saveBoolean(key: String, value: Boolean) { + val editor = sharedPreferences.edit() + editor.putBoolean(key, value) + editor.commit() + } + + fun getBoolean(key: String, defaultValue: Boolean): Boolean = + sharedPreferences.getBoolean(key, defaultValue) + + fun getString(key: String, defaultValue: String): String = + sharedPreferences.getString(key, defaultValue)!! +} \ No newline at end of file diff --git a/repodir/huixiangdou/android/demo/src/main/java/com/carlos/grabredenvelope/demo/WechatConstants.kt b/repodir/huixiangdou/android/demo/src/main/java/com/carlos/grabredenvelope/demo/WechatConstants.kt new file mode 100644 index 00000000..4a5a500a --- /dev/null +++ b/repodir/huixiangdou/android/demo/src/main/java/com/carlos/grabredenvelope/demo/WechatConstants.kt @@ -0,0 +1,30 @@ +package com.carlos.grabredenvelope.demo + +import android.util.Log +import com.carlos.cutils.util.LogUtils + +/** + * Created by Carlos on 2019-05-29. + */ +object WechatConstants { + + var RES_ID_GROUP_NAME = "com.tencent.mm:id/obn" // 群名 + var RES_ID_USER_NAME = "com.tencent.mm:id/brc" // 发消息的人 + var RES_ID_USER_CONTENT = "com.tencent.mm:id/bkl" // 发的文本内容 + var RES_ID_EDIT_TEXT = "com.tencent.mm:id/bkk" // 消息输入框 + // 从 8.0.48 开始调整判断逻辑,根据头像坐标定位谁是发送者 + var RES_ID_USER_RL = "com.tencent.mm:id/bn1" // 发消息的 rl + var RES_ID_USER_HEADER = "com.tencent.mm:id/bk1" // 头像 + + fun setVersion(version: String) { + LogUtils.d("version:$version") + if (version == "8.0.47" || version == "8.0.48" || version == "8.0.49") { + RES_ID_GROUP_NAME = "com.tencent.mm:id/obn" + RES_ID_USER_NAME = "com.tencent.mm:id/brc" + RES_ID_USER_CONTENT = "com.tencent.mm:id/bkl" + RES_ID_EDIT_TEXT = "com.tencent.mm:id/bkk" + } else { + Log.w("msg", "unknown version, maybe incompatible") + } + } +} \ No newline at end of file diff --git a/repodir/huixiangdou/android/demo/src/main/res/drawable-v24/ic_launcher_foreground.xml b/repodir/huixiangdou/android/demo/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 00000000..485bbd37 --- /dev/null +++ b/repodir/huixiangdou/android/demo/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + diff --git a/repodir/huixiangdou/android/demo/src/main/res/drawable/ic_launcher_background.xml b/repodir/huixiangdou/android/demo/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 00000000..7ca09099 --- /dev/null +++ b/repodir/huixiangdou/android/demo/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,171 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/repodir/huixiangdou/android/demo/src/main/res/drawable/logo.xml b/repodir/huixiangdou/android/demo/src/main/res/drawable/logo.xml new file mode 100644 index 00000000..e0b72a6b --- /dev/null +++ b/repodir/huixiangdou/android/demo/src/main/res/drawable/logo.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + diff --git a/repodir/huixiangdou/android/demo/src/main/res/layout/activity_main.xml b/repodir/huixiangdou/android/demo/src/main/res/layout/activity_main.xml new file mode 100644 index 00000000..1882dc67 --- /dev/null +++ b/repodir/huixiangdou/android/demo/src/main/res/layout/activity_main.xml @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/repodir/huixiangdou/android/demo/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/repodir/huixiangdou/android/demo/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 00000000..ddbf047d --- /dev/null +++ b/repodir/huixiangdou/android/demo/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/repodir/huixiangdou/android/demo/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/repodir/huixiangdou/android/demo/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 00000000..ddbf047d --- /dev/null +++ b/repodir/huixiangdou/android/demo/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/repodir/huixiangdou/android/demo/src/main/res/values/colors.xml b/repodir/huixiangdou/android/demo/src/main/res/values/colors.xml new file mode 100644 index 00000000..69b22338 --- /dev/null +++ b/repodir/huixiangdou/android/demo/src/main/res/values/colors.xml @@ -0,0 +1,6 @@ + + + #008577 + #00574B + #D81B60 + diff --git a/repodir/huixiangdou/android/demo/src/main/res/values/strings.xml b/repodir/huixiangdou/android/demo/src/main/res/values/strings.xml new file mode 100644 index 00000000..1ec8ecd9 --- /dev/null +++ b/repodir/huixiangdou/android/demo/src/main/res/values/strings.xml @@ -0,0 +1,16 @@ + + 茴香豆 Android 助手 + 这是 [茴香豆](https://github.com/internlm/huixiangdou) 的 Android 部分。\n开启后停留在微信对话界面,将读最新消息,调用大语言模型自动回复主题相关内容。如果是主题无关的闲聊,则不处理。\n如果对你有用,请 star 一下! + 抢微信红包 + 茴香豆 LLM RAG 回复 + + 如果对你有用,请给 https://github.com/internlm/huixiangdou 点个 star,这对我们真的很重要qaq + 第一步:打开 openxlab.org.cn 应用中心,搜索“茴香豆”,创建知识库直接获取回调地址;或自行部署开源版茴香豆得到服务器地址。\n输入框里是个可用的地址,仅仅用于调试 app 是否正常,并不会真的回答问题。 + http://139.224.198.162:18443/api/v1/message/v1/wechat/Qlyq + 确定 + 第二步:点击下方按钮进入辅助功能,找到(茴香豆)开启或关闭 + 第三步:直接进入微信(注意 github 文档中微信版本要求)聊天界面,请对方发个消息被动扫描屏幕、或上滑聊天框主动触发扫描。\n群聊或单聊都支持。\n注意不要关闭本应用,它默认后台运行。 + tips:助手只回答知识库相关话题,碰到无关闲聊会跳过。adb log 可以看到完整处理日志。 + 点我 + 调试模式,默认开启。\n确认功能正常后需关闭,否则收到啥都响应 + diff --git a/repodir/huixiangdou/android/demo/src/main/res/values/styles.xml b/repodir/huixiangdou/android/demo/src/main/res/values/styles.xml new file mode 100644 index 00000000..1c56072b --- /dev/null +++ b/repodir/huixiangdou/android/demo/src/main/res/values/styles.xml @@ -0,0 +1,11 @@ + + + + + + diff --git a/repodir/huixiangdou/android/demo/src/main/res/xml/sendemoji_service.xml b/repodir/huixiangdou/android/demo/src/main/res/xml/sendemoji_service.xml new file mode 100644 index 00000000..5d54eb1e --- /dev/null +++ b/repodir/huixiangdou/android/demo/src/main/res/xml/sendemoji_service.xml @@ -0,0 +1,11 @@ + \ No newline at end of file diff --git a/repodir/huixiangdou/android/demo/src/main/res/xml/wechat_service.xml b/repodir/huixiangdou/android/demo/src/main/res/xml/wechat_service.xml new file mode 100644 index 00000000..790361aa --- /dev/null +++ b/repodir/huixiangdou/android/demo/src/main/res/xml/wechat_service.xml @@ -0,0 +1,12 @@ + \ No newline at end of file diff --git a/repodir/huixiangdou/android/demo/src/test/java/com/carlos/grabredenvelope/demo/ExampleUnitTest.kt b/repodir/huixiangdou/android/demo/src/test/java/com/carlos/grabredenvelope/demo/ExampleUnitTest.kt new file mode 100644 index 00000000..8c413ef3 --- /dev/null +++ b/repodir/huixiangdou/android/demo/src/test/java/com/carlos/grabredenvelope/demo/ExampleUnitTest.kt @@ -0,0 +1,16 @@ +package com.carlos.grabredenvelope.demo + +import org.junit.Assert.assertEquals +import org.junit.Test + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest { + @Test + fun addition_isCorrect() { + assertEquals(4, 2 + 2) + } +} diff --git a/repodir/huixiangdou/android/gradle.properties b/repodir/huixiangdou/android/gradle.properties new file mode 100644 index 00000000..2cf0d728 --- /dev/null +++ b/repodir/huixiangdou/android/gradle.properties @@ -0,0 +1,24 @@ +# Project-wide Gradle settings. + +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. + +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html + +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +# Default value: -Xmx10248m -XX:MaxPermSize=256m +# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 + +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true +android.useAndroidX=true +# Automatically convert third-party libraries to use AndroidX +android.enableJetifier=true +# Kotlin code style for this project: "official" or "obsolete": +kotlin.code.style=official + diff --git a/repodir/huixiangdou/android/gradle/wrapper/gradle-wrapper.jar b/repodir/huixiangdou/android/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..05ef575b0cd0173fc735f2857ce4bd594ce4f6bd GIT binary patch literal 53637 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 t9m@SZ!E*7&jsr?!t7TL-WYI4eM@gAug8 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 \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/repodir/huixiangdou/android/gradlew.bat b/repodir/huixiangdou/android/gradlew.bat new file mode 100644 index 00000000..8a0b282a --- /dev/null +++ b/repodir/huixiangdou/android/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/repodir/huixiangdou/android/settings.gradle b/repodir/huixiangdou/android/settings.gradle new file mode 100644 index 00000000..971067f9 --- /dev/null +++ b/repodir/huixiangdou/android/settings.gradle @@ -0,0 +1 @@ +include ':demo' \ No newline at end of file diff --git a/repodir/huixiangdou/app.py b/repodir/huixiangdou/app.py new file mode 100644 index 00000000..ea9377f3 --- /dev/null +++ b/repodir/huixiangdou/app.py @@ -0,0 +1,11 @@ +# This is a start-up file for deploying HuixiangDou-WEB on OpenXLab-APPs(https://openxlab.org.cn/apps) +# Some environment variables need to be set before starting up: +# JWT_SECRET= +# REDIS_HOST= +# REDIS_PASSWORD= +# SERVER_PORT=7860 (when deploy on OpenXLab-APPs, this SERVER_PORT should be 7860) + +import os + +# launch the HuixiangDou-WEB +os.system('python -m web.main') diff --git a/repodir/huixiangdou/config-2G.ini b/repodir/huixiangdou/config-2G.ini new file mode 100644 index 00000000..4686bd7a --- /dev/null +++ b/repodir/huixiangdou/config-2G.ini @@ -0,0 +1,200 @@ +[feature_store] +# `feature_store.py` use this throttle to distinct `good_questions` and `bad_questions` +reject_throttle = -1.0 +# text2vec model path, support local relative path and huggingface model format. +# also support local path, model_path = "/path/to/your/text2vec-model" +embedding_model_path = "maidalun1020/bce-embedding-base_v1" +reranker_model_path = "maidalun1020/bce-reranker-base_v1" +work_dir = "workdir" + +[web_search] +engine = "serper" +# web search engine support ddgs and serper +# For ddgs, see https://pypi.org/project/duckduckgo-search +# For serper, check https://serper.dev/api-key to get a free API key +serper_x_api_key = "YOUR-API-KEY-HERE" +domain_partial_order = ["openai.com", "pytorch.org", "readthedocs.io", "nvidia.com", "stackoverflow.com", "juejin.cn", "zhuanlan.zhihu.com", "www.cnblogs.com"] +save_dir = "logs/web_search_result" + +[llm] +enable_local = 0 +enable_remote = 1 +# hybrid llm service address +client_url = "http://127.0.0.1:8888/inference" + +[llm.server] +# local LLM configuration +# support "internlm/internlm2-chat-7b" and "qwen/qwen-7b-chat-int8" +# support local path, for example +# local_llm_path = "/path/to/your/internlm2" + +local_llm_path = "internlm/internlm2-chat-7b" +local_llm_max_text_length = 3000 +# llm server listen port +local_llm_bind_port = 8888 + +# remote LLM service configuration +# support "gpt", "kimi", "deepseek", "zhipuai", "step", "internlm", "xi-api" and "alles-apin" +# support "siliconcloud", see https://siliconflow.cn/zh-cn/siliconcloud +# xi-api and alles-apin is chinese gpt proxy +# for internlm, see https://internlm.intern-ai.org.cn/api/document + +remote_type = "siliconcloud" +remote_api_key = "YOUR-API-KEY-HERE" +# max text length for remote LLM. +# use 128000 for kimi, 192000 for gpt/xi-api, 16000 for deepseek, 128000 for zhipuai, 40000 for internlm2 +remote_llm_max_text_length = 40000 +# openai API model type, support model list: +# "auto" for kimi. To save money, we auto select model name by prompt length. +# "auto" for step to save money, see https://platform.stepfun.com/ +# "gpt-4-0613" for gpt/xi-api, +# "deepseek-chat" for deepseek, +# "glm-4" for zhipuai, +# "gpt-4-1106-preview" for alles-apin or OpenAOE +# "internlm2-latest" for internlm +# for example "alibaba/Qwen1.5-110B-Chat", see https://siliconflow.readme.io/reference/chat-completions-1 +remote_llm_model = "alibaba/Qwen1.5-110B-Chat" +# request per minute +rpm = 500 + +[coreference_resolution] +base_url = 'http://127.0.0.1:9999/v1' +api_key = 'token-abc123' + +[worker] +# enable web search or not +enable_web_search = 1 +# enable search enhancement or not +enable_sg_search = 0 +# enable coreference resolution in `PreprocNode` +enable_cr = 0 +save_path = "logs/work.txt" + +[worker.time] +enable = 0 +start = "00:00:00" +end = "23:59:59" +has_weekday = 1 + +[sg_search] +# download `src` from https://github.com/sourcegraph/src-cli#installation +binary_src_path = "/usr/local/bin/src" +src_access_token = "YOUR-SRC-ACCESS-TOKEN" + +# add your repo here, we just take opencompass and lmdeploy as example +[sg_search.opencompass] +github_repo_id = "open-compass/opencompass" +introduction = "用于评测大型语言模型(LLM). 它提供了完整的开源可复现的评测框架,支持大语言模型、多模态模型的一站式评测,基于分布式技术,对大参数量模型亦能实现高效评测。评测方向汇总为知识、语言、理解、推理、考试五大能力维度,整合集纳了超过70个评测数据集,合计提供了超过40万个模型评测问题,并提供长文本、安全、代码3类大模型特色技术能力评测。" +# introduction = "For evaluating Large Language Models (LLMs). It provides a fully open-source, reproducible evaluation framework, supporting one-stop evaluation for large language models and multimodal models. Based on distributed technology, it can efficiently evaluate models with a large number of parameters. The evaluation directions are summarized in five capability dimensions: knowledge, language, understanding, reasoning, and examination. It integrates and collects more than 70 evaluation datasets, providing in total over 400,000 model evaluation questions. Additionally, it offers evaluations for three types of capabilities specific to large models: long text, security, and coding." + +[sg_search.lmdeploy] +github_repo_id = "internlm/lmdeploy" +introduction = "lmdeploy 是一个用于压缩、部署和服务 LLM(Large Language Model)的工具包。是一个服务端场景下,transformer 结构 LLM 部署工具,支持 GPU 服务端部署,速度有保障,支持 Tensor Parallel,多并发优化,功能全面,包括模型转换、缓存历史会话的 cache feature 等. 它还提供了 WebUI、命令行和 gRPC 客户端接入。" +# introduction = "lmdeploy is a toolkit for compressing, deploying, and servicing Large Language Models (LLMs). It is a deployment tool for transformer-structured LLMs in server-side scenarios, supporting GPU server-side deployment, ensuring speed, and supporting Tensor Parallel along with optimizations for multiple concurrent processes. It offers comprehensive features including model conversion, cache features for caching historical sessions and more. Additionally, it provides access via WebUI, command line, and gRPC clients." +# add your repo here, we just take opencompass and lmdeploy as example + +[sg_search.mmpose] +github_repo_id = "open-mmlab/mmpose" +introduction = "MMPose is an open-source toolbox for pose estimation based on PyTorch" + +[sg_search.mmdetection] +github_repo_id = "open-mmlab/mmdetection" +introduction = "MMDetection is an open source object detection toolbox based on PyTorch." + +[sg_search.huixiangdou] +github_repo_id = "internlm/huixiangdou" +introduction = "茴香豆是一个基于 LLM 的群聊知识助手。设计拒答、响应两阶段 pipeline 应对群聊场景,解答问题同时不会消息泛滥。" + +[sg_search.xtuner] +github_repo_id = "internlm/xtuner" +introduction = "XTuner is an efficient, flexible and full-featured toolkit for fine-tuning large models." + +[sg_search.mmyolo] +github_repo_id = "open-mmlab/mmyolo" +introduction = "OpenMMLab YOLO series toolbox and benchmark. Implemented RTMDet, RTMDet-Rotated,YOLOv5, YOLOv6, YOLOv7, YOLOv8,YOLOX, PPYOLOE, etc." + +[sg_search.Amphion] +github_repo_id = "open-mmlab/Amphion" +introduction = "Amphion is a toolkit for Audio, Music, and Speech Generation. Its purpose is to support reproducible research and help junior researchers and engineers get started in the field of audio, music, and speech generation research and development." + +[sg_search.mmcv] +github_repo_id = "open-mmlab/mmcv" +introduction = "MMCV is a foundational library for computer vision research and it provides image/video processing, image and annotation visualization, image transformation, various CNN architectures and high-quality implementation of common CPU and CUDA ops" + +[frontend] +# chat group assistant type, support "lark_group", "wechat_personal", "wechat_wkteam" and "none" +# for "lark_group", open https://open.feishu.cn/document/home/introduction-to-custom-app-development/self-built-application-development-process to create one +# for "wechat_personal", read ./docs/add_wechat_group_zh.md to setup gateway +# for "wkteam", see https://wkteam.cn/ +type = "none" + +# for "lark", it is chat group webhook url, send reply to group, for example "https://open.feishu.cn/open-apis/bot/v2/hook/xxxxxxxxxxxxxxx" +# for "lark_group", it is the url to fetch chat group message, for example "http://101.133.161.20:6666/fetch", `101.133.161.20` is your own public IPv4 addr +# for "wechat_personal", it is useless +webhook_url = "https://open.feishu.cn/open-apis/bot/v2/hook/xxxxxxxxxxxxxxx" + +# when a new group chat message is received, should it be processed immediately or wait for 18 seconds in case the user hasn't finished speaking? +# support "immediate" +message_process_policy = "immediate" + +[frontend.lark_group] +# "lark_group" configuration examples, use your own app_id and secret !!! +app_id = "cli_a53a34dcb778500e" +app_secret = "2ajhg1ixSvlNm1bJkH4tJhPfTCsGGHT1" +encrypt_key = "abc" +verification_token = "def" + +[frontend.wechat_personal] +# "wechat_personal" listen port +bind_port = 9527 + +[frontend.wechat_wkteam] +# wechat message callback server ip +callback_ip = "101.133.161.11" +callback_port = 9528 + +# public redis config +redis_host = "101.133.161.11" +redis_port = "6380" +redis_passwd = "hxd123" + +# wkteam +account = "" +password = "" +# !!! `proxy` is very import parameter, it's your account location +# 1:北京 2:天津 3:上海 4:重庆 5:河北 +# 6:山西 7:江苏 8:浙江 9:安徽 10:福建 +# 11:江西 12:山东 13:河南 14:湖北 15:湖南 +# 16:广东 17:海南 18:四川 20:陕西 +# bad proxy would cause account deactivation !!! +proxy = -1 + +# save dir +dir = "wkteam" + +# 群号和介绍 +# 茴香豆相关 +[frontend.wechat_wkteam.43925126702] +name = "茴香豆群(大暑)" +introduction = "github https://github.com/InternLM/HuixiangDou 用户体验群" + +[frontend.wechat_wkteam.44546611710] +name = "茴香豆群(立夏)" +introduction = "github https://github.com/InternLM/HuixiangDou 用户体验群" + +[frontend.wechat_wkteam.38720590618] +name = "茴香豆群(惊蛰)" +introduction = "github https://github.com/InternLM/HuixiangDou 用户体验群" + +[frontend.wechat_wkteam.48437885473] +name = "茴香豆群(谷雨)" +introduction = "github https://github.com/InternLM/HuixiangDou 用户体验群" + +[frontend.wechat_wkteam.34744063953] +name = "茴香豆群(雨水)" +introduction = "github https://github.com/InternLM/HuixiangDou 用户体验群" + +# github.com/tencent/ncnn contributors +[frontend.wechat_wkteam.18356748488] +name = "卷卷群" +introduction = "ncnn contributors group" diff --git a/repodir/huixiangdou/config-advanced.ini b/repodir/huixiangdou/config-advanced.ini new file mode 100644 index 00000000..0a6a0e75 --- /dev/null +++ b/repodir/huixiangdou/config-advanced.ini @@ -0,0 +1,138 @@ +[feature_store] +# `feature_store.py` use this throttle to distinct `good_questions` and `bad_questions` +reject_throttle = -1.0 +# text2vec model path, support local relative path and huggingface model format. +# also support local path, model_path = "/path/to/your/text2vec-model" +embedding_model_path = "maidalun1020/bce-embedding-base_v1" +reranker_model_path = "BAAI/bge-reranker-v2-minicpm-layerwise" +work_dir = "workdir" + +[web_search] +engine = "ddgs" +# web search engine support ddgs and serper +# For ddgs, see https://pypi.org/project/duckduckgo-search +# For serper, check https://serper.dev/api-key to get a free API key +serper_x_api_key = "YOUR-API-KEY-HERE" +domain_partial_order = ["openai.com", "pytorch.org", "readthedocs.io", "nvidia.com", "stackoverflow.com", "juejin.cn", "zhuanlan.zhihu.com", "www.cnblogs.com"] +save_dir = "logs/web_search_result" + +[llm] +enable_local = 1 +enable_remote = 1 +# hybrid llm service address +client_url = "http://127.0.0.1:8888/inference" + +[llm.server] +# local LLM configuration +# support "internlm/internlm2-chat-7b" and "qwen/qwen-7b-chat-int8" +# support local path, for example +# local_llm_path = "/path/to/your/internlm2" + +local_llm_path = "internlm/internlm2-chat-7b" +local_llm_max_text_length = 3000 +local_llm_bind_port = 8888 + +# remote LLM service configuration +# support "gpt", "kimi", "deepseek", "zhipuai", "xi-api" and "alles-apin" +# xi-api and alles-apin is chinese gpt proxy + +remote_type = "kimi" +remote_api_key = "YOUR-API-KEY-HERE" +# max text length for remote LLM. +# use 128000 for kimi, 192000 for gpt/xi-api, 16000 for deepseek, 128000 for zhipuai +remote_llm_max_text_length = 128000 +# openai API model type, support model list: +# "auto" for kimi. To save money, we auto select model name by prompt length. +# "gpt-4-0613" for gpt/xi-api, +# "deepseek-chat" for deepseek, +# "glm-4" for zhipuai, +# "gpt-4-1106-preview" for alles-apin or OpenAOE +remote_llm_model = "auto" +# request per minute +rpm = 500 + +[worker] +# enable web search or not +enable_web_search = 1 +# enable search enhancement or not +enable_sg_search = 1 +save_path = "logs/work.txt" + +[worker.time] +enable = 0 +start = "00:00:00" +end = "23:59:59" +has_weekday = 1 + +[sg_search] +# download `src` from https://github.com/sourcegraph/src-cli#installation +binary_src_path = "/usr/local/bin/src" +src_access_token = "${YOUR-SRC-ACCESS-TOKEN}" + +# add your repo here, we just take opencompass and lmdeploy as example +[sg_search.opencompass] +github_repo_id = "open-compass/opencompass" +introduction = "用于评测大型语言模型(LLM). 它提供了完整的开源可复现的评测框架,支持大语言模型、多模态模型的一站式评测,基于分布式技术,对大参数量模型亦能实现高效评测。评测方向汇总为知识、语言、理解、推理、考试五大能力维度,整合集纳了超过70个评测数据集,合计提供了超过40万个模型评测问题,并提供长文本、安全、代码3类大模型特色技术能力评测。" + +[sg_search.mmpose] +github_repo_id = "open-mmlab/mmpose" +introduction = "MMPose is an open-source toolbox for pose estimation based on PyTorch" + +[sg_search.mmdeploy] +github_repo_id = "open-mmlab/mmdeploy" +introduction = "MMDeploy is an open-source deep learning model deployment toolset" + +[sg_search.mmdetection] +github_repo_id = "open-mmlab/mmdetection" +introduction = "MMDetection is an open source object detection toolbox based on PyTorch." + +[sg_search.lmdeploy] +github_repo_id = "internlm/lmdeploy" +introduction = "lmdeploy 是一个用于压缩、部署和服务 LLM(Large Language Model)的工具包。是一个服务端场景下,transformer 结构 LLM 部署工具,支持 GPU 服务端部署,速度有保障,支持 Tensor Parallel,多并发优化,功能全面,包括模型转换、缓存历史会话的 cache feature 等. 它还提供了 WebUI、命令行和 gRPC 客户端接入。" + +[sg_search.huixiangdou] +github_repo_id = "internlm/huixiangdou" +introduction = "茴香豆是一个基于 LLM 的群聊知识助手。设计拒答、响应两阶段 pipeline 应对群聊场景,解答问题同时不会消息泛滥。" + +[sg_search.xtuner] +github_repo_id = "internlm/xtuner" +introduction = "XTuner is an efficient, flexible and full-featured toolkit for fine-tuning large models." + +[sg_search.mmyolo] +github_repo_id = "open-mmlab/mmyolo" +introduction = "OpenMMLab YOLO series toolbox and benchmark. Implemented RTMDet, RTMDet-Rotated,YOLOv5, YOLOv6, YOLOv7, YOLOv8,YOLOX, PPYOLOE, etc." + +[sg_search.Amphion] +github_repo_id = "open-mmlab/Amphion" +introduction = "Amphion is a toolkit for Audio, Music, and Speech Generation. Its purpose is to support reproducible research and help junior researchers and engineers get started in the field of audio, music, and speech generation research and development." + +[sg_search.mmcv] +github_repo_id = "open-mmlab/mmcv" +introduction = "MMCV is a foundational library for computer vision research and it provides image/video processing, image and annotation visualization, image transformation, various CNN architectures and high-quality implementation of common CPU and CUDA ops" + +[frontend] +# chat group assistant type, support "lark", "lark_group", "wechat_personal" and "none" +# for "lark", open https://open.feishu.cn/document/client-docs/bot-v3/add-custom-bot to add bot, **only send, cannot receive** +# for "lark_group", open https://open.feishu.cn/document/home/introduction-to-custom-app-development/self-built-application-development-process to create one +# for "wechat_personal", read ./docs/add_wechat_group_zh.md to setup gateway +type = "none" + +# for "lark", it is chat group webhook url, send reply to group, for example "https://open.feishu.cn/open-apis/bot/v2/hook/xxxxxxxxxxxxxxx" +# for "lark_group", it is the url to fetch chat group message, for example "http://101.133.161.20:6666/fetch", `101.133.161.20` is your own public IPv4 addr +# for "wechat_personal", it is useless +webhook_url = "https://open.feishu.cn/open-apis/bot/v2/hook/xxxxxxxxxxxxxxx" + +# when a new group chat message is received, should it be processed immediately or wait for 18 seconds in case the user hasn't finished speaking? +# support "immediate" +message_process_policy = "immediate" + +[frontend.lark_group] +# "lark_group" configuration examples, use your own app_id and secret !!! +app_id = "cli_a53a34dcb778500e" +app_secret = "2ajhg1ixSvlNm1bJkH4tJhPfTCsGGHT1" +encrypt_key = "abc" +verification_token = "def" + +[frontend.wechat_personal] +# "wechat_personal" listen port +bind_port = 9527 diff --git a/repodir/huixiangdou/config-alignment-example.ini b/repodir/huixiangdou/config-alignment-example.ini new file mode 100644 index 00000000..2042e970 --- /dev/null +++ b/repodir/huixiangdou/config-alignment-example.ini @@ -0,0 +1,98 @@ +[feature_store] +reject_throttle = -1.0 +embedding_model_path = "/data2/khj/bce-embedding-base_v1/" +reranker_model_path = "/data2/khj/bce-reranker-base_v1" +work_dir = "workdir" + +[web_search] +engine = "ddgs" +# web search engine support ddgs and serper +# For ddgs, see https://pypi.org/project/duckduckgo-search +# For serper, check https://serper.dev/api-key to get a free API key +serper_x_api_key = "YOUR-API-KEY-HERE" +domain_partial_order = ["openai.com", "pytorch.org", "readthedocs.io", "nvidia.com", "stackoverflow.com", "juejin.cn", "zhuanlan.zhihu.com", "www.cnblogs.com", "www.volcengine.com"] +save_dir = "logs/web_search_result" + +[llm] +enable_local = 0 +enable_remote = 1 +client_url = "http://127.0.0.1:9999/inference" + +[llm.server] +local_llm_path = "internlm/internlm2-chat-7b" +local_llm_max_text_length = 3000 +local_llm_bind_port = 9999 +remote_type = "xi-api" +remote_api_key = "YOUR-API-KEY-HERE" +remote_llm_max_text_length = 64000 +remote_llm_model = "gpt-4-0613" +rpm = 30 + +[worker] +enable_web_search = 1 +enable_sg_search = 1 +save_path = "logs/work.txt" + +[worker.time] +enable = 0 +start = "00:00:00" +end = "23:59:59" +has_weekday = 1 + +[sg_search] +binary_src_path = "/usr/local/bin/src" +src_access_token = "YOUR-API-KEY-HERE" + +[sg_search.opencompass] +github_repo_id = "open-compass/opencompass" +introduction = "用于评测大型语言模型(LLM). 它提供了完整的开源可复现的评测框架,支持大语言模型、多模态模型的一站式评测,基于分布式技术,对大参数量模型亦能实现高效评测。评测方向汇总为知识、语言、理解、推理、考试五大能力维度,整合集纳了超过70个评测数据集,合计提供了超过40万个模型评测问题,并提供长文本、安全、代码3类大模型特色技术能力评测。" + +[sg_search.mmpose] +github_repo_id = "open-mmlab/mmpose" +introduction = "MMPose is an open-source toolbox for pose estimation based on PyTorch" + +[sg_search.mmdeploy] +github_repo_id = "open-mmlab/mmdeploy" +introduction = "MMDeploy is an open-source deep learning model deployment toolset" + +[sg_search.mmdetection] +github_repo_id = "open-mmlab/mmdetection" +introduction = "MMDetection is an open source object detection toolbox based on PyTorch." + +[sg_search.lmdeploy] +github_repo_id = "internlm/lmdeploy" +introduction = "lmdeploy 是一个用于压缩、部署和服务 LLM(Large Language Model)的工具包。是一个服务端场景下,transformer 结构 LLM 部署工具,支持 GPU 服务端部署,速度有保障,支持 Tensor Parallel,多并发优化,功能全面,包括模型转换、缓存历史会话的 cache feature 等. 它还提供了 WebUI、命令行和 gRPC 客户端接入。" + +[sg_search.huixiangdou] +github_repo_id = "internlm/huixiangdou" +introduction = "茴香豆是一个基于 LLM 的群聊知识助手。设计拒答、响应两阶段 pipeline 应对群聊场景,解答问题同时不会消息泛滥。" + +[sg_search.xtuner] +github_repo_id = "internlm/xtuner" +introduction = "XTuner is an efficient, flexible and full-featured toolkit for fine-tuning large models." + +[sg_search.mmyolo] +github_repo_id = "open-mmlab/mmyolo" +introduction = "OpenMMLab YOLO series toolbox and benchmark. Implemented RTMDet, RTMDet-Rotated,YOLOv5, YOLOv6, YOLOv7, YOLOv8,YOLOX, PPYOLOE, etc." + +[sg_search.Amphion] +github_repo_id = "open-mmlab/Amphion" +introduction = "Amphion is a toolkit for Audio, Music, and Speech Generation. Its purpose is to support reproducible research and help junior researchers and engineers get started in the field of audio, music, and speech generation research and development." + +[sg_search.mmcv] +github_repo_id = "open-mmlab/mmcv" +introduction = "MMCV is a foundational library for computer vision research and it provides image/video processing, image and annotation visualization, image transformation, various CNN architectures and high-quality implementation of common CPU and CUDA ops" + +[frontend] +type = "none" +webhook_url = "https://open.feishu.cn/open-apis/bot/v2/hook/xxxxxxxxxxxxxxx" +message_process_policy = "immediate" + +[frontend.lark_group] +app_id = "cli_a53a34dcb778500e" +app_secret = "2ajhg1ixSvlNm1bJkH4tJhPfTCsGGHT1" +encrypt_key = "abc" +verification_token = "def" + +[frontend.wechat_personal] +bind_port = 9527 diff --git a/repodir/huixiangdou/config-cpu.ini b/repodir/huixiangdou/config-cpu.ini new file mode 100644 index 00000000..46e32df7 --- /dev/null +++ b/repodir/huixiangdou/config-cpu.ini @@ -0,0 +1,212 @@ +[feature_store] +# `feature_store.py` use this throttle to distinct `good_questions` and `bad_questions` +reject_throttle = -1.0 +# text2vec model, support local relative path, huggingface repo and URL. +# for example: +# "maidalun1020/bce-embedding-base_v1" +# "BAAI/bge-m3" +# "https://api.siliconflow.cn/v1/embeddings" +embedding_model_path = "https://api.siliconflow.cn/v1/embeddings" + +# reranker model, support list: +# "maidalun1020/bce-reranker-base_v1" +# "BAAI/bge-reranker-v2-minicpm-layerwise" +# "https://api.siliconflow.cn/v1/rerank" +reranker_model_path = "https://api.siliconflow.cn/v1/rerank" + +# if using `siliconcloud` API as `embedding_model_path` or `reranker_model_path`, give the token +api_token = "" +api_rpm = 800 +work_dir = "workdir" + +[web_search] +engine = "serper" +# web search engine support ddgs and serper +# For ddgs, see https://pypi.org/project/duckduckgo-search +# For serper, check https://serper.dev/api-key to get a free API key +serper_x_api_key = "YOUR-API-KEY-HERE" +domain_partial_order = ["openai.com", "pytorch.org", "readthedocs.io", "nvidia.com", "stackoverflow.com", "juejin.cn", "zhuanlan.zhihu.com", "www.cnblogs.com"] +save_dir = "logs/web_search_result" + +[llm] +enable_local = 0 +enable_remote = 1 +# hybrid llm service address +client_url = "http://127.0.0.1:8888/inference" + +[llm.server] +# local LLM configuration +# support "internlm/internlm2-chat-7b", "internlm2_5-7b-chat" and "qwen/qwen-7b-chat-int8" +# support local path, for example +# local_llm_path = "/path/to/your/internlm2_5" + +local_llm_path = "internlm/internlm2_5-7b-chat" +local_llm_max_text_length = 3000 +# llm server listen port +local_llm_bind_port = 8888 + +# remote LLM service configuration +# support "gpt", "kimi", "deepseek", "zhipuai", "step", "internlm", "xi-api" and "alles-apin" +# support "siliconcloud", see https://siliconflow.cn/zh-cn/siliconcloud +# xi-api and alles-apin is chinese gpt proxy +# for internlm, see https://internlm.intern-ai.org.cn/api/document + +remote_type = "siliconcloud" +remote_api_key = "" +# max text length for remote LLM. +# use 128000 for kimi, 192000 for gpt/xi-api, 16000 for deepseek, 128000 for zhipuai, 40000 for internlm2 +remote_llm_max_text_length = 40000 +# openai API model type, support model list: +# "auto" for kimi. To save money, we auto select model name by prompt length. +# "auto" for step to save money, see https://platform.stepfun.com/ +# "gpt-4-0613" for gpt/xi-api, +# "deepseek-chat" for deepseek, +# "glm-4" for zhipuai, +# "gpt-4-1106-preview" for alles-apin or OpenAOE +# "internlm2-latest" for internlm +# for example "alibaba/Qwen1.5-110B-Chat", see https://siliconflow.readme.io/reference/chat-completions-1 +remote_llm_model = "alibaba/Qwen1.5-110B-Chat" +# request per minute +rpm = 500 + +[coreference_resolution] +base_url = 'http://127.0.0.1:9999/v1' +api_key = 'token-abc123' + +[worker] +# enable web search or not +enable_web_search = 1 +# enable search enhancement or not +enable_sg_search = 0 +# enable coreference resolution in `PreprocNode` +enable_cr = 0 +save_path = "logs/work.txt" + +[worker.time] +enable = 0 +start = "00:00:00" +end = "23:59:59" +has_weekday = 1 + +[sg_search] +# download `src` from https://github.com/sourcegraph/src-cli#installation +binary_src_path = "/usr/local/bin/src" +src_access_token = "YOUR-SRC-ACCESS-TOKEN" + +# add your repo here, we just take opencompass and lmdeploy as example +[sg_search.opencompass] +github_repo_id = "open-compass/opencompass" +introduction = "用于评测大型语言模型(LLM). 它提供了完整的开源可复现的评测框架,支持大语言模型、多模态模型的一站式评测,基于分布式技术,对大参数量模型亦能实现高效评测。评测方向汇总为知识、语言、理解、推理、考试五大能力维度,整合集纳了超过70个评测数据集,合计提供了超过40万个模型评测问题,并提供长文本、安全、代码3类大模型特色技术能力评测。" +# introduction = "For evaluating Large Language Models (LLMs). It provides a fully open-source, reproducible evaluation framework, supporting one-stop evaluation for large language models and multimodal models. Based on distributed technology, it can efficiently evaluate models with a large number of parameters. The evaluation directions are summarized in five capability dimensions: knowledge, language, understanding, reasoning, and examination. It integrates and collects more than 70 evaluation datasets, providing in total over 400,000 model evaluation questions. Additionally, it offers evaluations for three types of capabilities specific to large models: long text, security, and coding." + +[sg_search.lmdeploy] +github_repo_id = "internlm/lmdeploy" +introduction = "lmdeploy 是一个用于压缩、部署和服务 LLM(Large Language Model)的工具包。是一个服务端场景下,transformer 结构 LLM 部署工具,支持 GPU 服务端部署,速度有保障,支持 Tensor Parallel,多并发优化,功能全面,包括模型转换、缓存历史会话的 cache feature 等. 它还提供了 WebUI、命令行和 gRPC 客户端接入。" +# introduction = "lmdeploy is a toolkit for compressing, deploying, and servicing Large Language Models (LLMs). It is a deployment tool for transformer-structured LLMs in server-side scenarios, supporting GPU server-side deployment, ensuring speed, and supporting Tensor Parallel along with optimizations for multiple concurrent processes. It offers comprehensive features including model conversion, cache features for caching historical sessions and more. Additionally, it provides access via WebUI, command line, and gRPC clients." +# add your repo here, we just take opencompass and lmdeploy as example + +[sg_search.mmpose] +github_repo_id = "open-mmlab/mmpose" +introduction = "MMPose is an open-source toolbox for pose estimation based on PyTorch" + +[sg_search.mmdetection] +github_repo_id = "open-mmlab/mmdetection" +introduction = "MMDetection is an open source object detection toolbox based on PyTorch." + +[sg_search.huixiangdou] +github_repo_id = "internlm/huixiangdou" +introduction = "茴香豆是一个基于 LLM 的群聊知识助手。设计拒答、响应两阶段 pipeline 应对群聊场景,解答问题同时不会消息泛滥。" + +[sg_search.xtuner] +github_repo_id = "internlm/xtuner" +introduction = "XTuner is an efficient, flexible and full-featured toolkit for fine-tuning large models." + +[sg_search.mmyolo] +github_repo_id = "open-mmlab/mmyolo" +introduction = "OpenMMLab YOLO series toolbox and benchmark. Implemented RTMDet, RTMDet-Rotated,YOLOv5, YOLOv6, YOLOv7, YOLOv8,YOLOX, PPYOLOE, etc." + +[sg_search.Amphion] +github_repo_id = "open-mmlab/Amphion" +introduction = "Amphion is a toolkit for Audio, Music, and Speech Generation. Its purpose is to support reproducible research and help junior researchers and engineers get started in the field of audio, music, and speech generation research and development." + +[sg_search.mmcv] +github_repo_id = "open-mmlab/mmcv" +introduction = "MMCV is a foundational library for computer vision research and it provides image/video processing, image and annotation visualization, image transformation, various CNN architectures and high-quality implementation of common CPU and CUDA ops" + +[frontend] +# chat group assistant type, support "lark_group", "wechat_personal", "wechat_wkteam" and "none" +# for "lark_group", open https://open.feishu.cn/document/home/introduction-to-custom-app-development/self-built-application-development-process to create one +# for "wechat_personal", read ./docs/add_wechat_group_zh.md to setup gateway +# for "wkteam", see https://wkteam.cn/ +type = "none" + +# for "lark", it is chat group webhook url, send reply to group, for example "https://open.feishu.cn/open-apis/bot/v2/hook/xxxxxxxxxxxxxxx" +# for "lark_group", it is the url to fetch chat group message, for example "http://101.133.161.20:6666/fetch", `101.133.161.20` is your own public IPv4 addr +# for "wechat_personal", it is useless +webhook_url = "https://open.feishu.cn/open-apis/bot/v2/hook/xxxxxxxxxxxxxxx" + +# when a new group chat message is received, should it be processed immediately or wait for 18 seconds in case the user hasn't finished speaking? +# support "immediate" +message_process_policy = "immediate" + +[frontend.lark_group] +# "lark_group" configuration examples, use your own app_id and secret !!! +app_id = "cli_a53a34dcb778500e" +app_secret = "2ajhg1ixSvlNm1bJkH4tJhPfTCsGGHT1" +encrypt_key = "abc" +verification_token = "def" + +[frontend.wechat_personal] +# "wechat_personal" listen port +bind_port = 9527 + +[frontend.wechat_wkteam] +# wechat message callback server ip +callback_ip = "101.133.161.11" +callback_port = 9528 + +# public redis config +redis_host = "101.133.161.11" +redis_port = "6380" +redis_passwd = "hxd123" + +# wkteam +account = "" +password = "" +# !!! `proxy` is very import parameter, it's your account location +# 1:北京 2:天津 3:上海 4:重庆 5:河北 +# 6:山西 7:江苏 8:浙江 9:安徽 10:福建 +# 11:江西 12:山东 13:河南 14:湖北 15:湖南 +# 16:广东 17:海南 18:四川 20:陕西 +# bad proxy would cause account deactivation !!! +proxy = -1 + +# save dir +dir = "wkteam" + +# 群号和介绍 +# 茴香豆相关 +[frontend.wechat_wkteam.43925126702] +name = "茴香豆群(大暑)" +introduction = "github https://github.com/InternLM/HuixiangDou 用户体验群" + +[frontend.wechat_wkteam.44546611710] +name = "茴香豆群(立夏)" +introduction = "github https://github.com/InternLM/HuixiangDou 用户体验群" + +[frontend.wechat_wkteam.38720590618] +name = "茴香豆群(惊蛰)" +introduction = "github https://github.com/InternLM/HuixiangDou 用户体验群" + +[frontend.wechat_wkteam.48437885473] +name = "茴香豆群(谷雨)" +introduction = "github https://github.com/InternLM/HuixiangDou 用户体验群" + +[frontend.wechat_wkteam.34744063953] +name = "茴香豆群(雨水)" +introduction = "github https://github.com/InternLM/HuixiangDou 用户体验群" + +# github.com/tencent/ncnn contributors +[frontend.wechat_wkteam.18356748488] +name = "卷卷群" +introduction = "ncnn contributors group" \ No newline at end of file diff --git a/repodir/huixiangdou/config-multimodal.ini b/repodir/huixiangdou/config-multimodal.ini new file mode 100644 index 00000000..9c9e8a72 --- /dev/null +++ b/repodir/huixiangdou/config-multimodal.ini @@ -0,0 +1,109 @@ +[feature_store] +# `feature_store.py` use this throttle to distinct `good_questions` and `bad_questions` +reject_throttle = -1.0 +# text2vec model path, support local relative path and huggingface model format. +# also support local path, model_path = "/path/to/your/text2vec-model" +embedding_model_path = "BAAI/bge-m3" +reranker_model_path = "BAAI/bge-reranker-v2-minicpm-layerwise" +work_dir = "workdir" + +[web_search] +engine = "serper" +# web search engine support `ddgs` and `serper` +# For ddgs, see https://pypi.org/project/duckduckgo-search +# For serper, check https://serper.dev/api-key to get a free API key +serper_x_api_key = "YOUR-API-KEY-HERE" +domain_partial_order = ["openai.com", "pytorch.org", "readthedocs.io", "nvidia.com", "stackoverflow.com", "juejin.cn", "zhuanlan.zhihu.com", "www.cnblogs.com"] +save_dir = "logs/web_search_result" + +[llm] +enable_local = 0 +enable_remote = 1 +# hybrid llm service address +client_url = "http://127.0.0.1:7777/inference" + +[llm.server] +# local LLM configuration +# support "internlm/internlm2-chat-7b" and "qwen/qwen-7b-chat-int8" +# support local path, for example +# local_llm_path = "/path/to/your/internlm2" + +local_llm_path = "internlm/internlm2-chat-7b" +local_llm_max_text_length = 3000 +local_llm_bind_port = 7777 + +# remote LLM service configuration +# support "gpt", "kimi", "deepseek", "zhipuai", "step", "xi-api" and "alles-apin" +# xi-api and alles-apin is chinese gpt proxy + +remote_type = "kimi" +remote_api_key = "YOUR-API-KEY-HERE" +# max text length for remote LLM. +# use 128000 for kimi, 192000 for gpt/xi-api, 16000 for deepseek, 128000 for zhipuai +remote_llm_max_text_length = 128000 +# openai API model type, support model list: +# "auto" for kimi. To save money, we auto select model name by prompt length. +# "auto" for step to save money, see https://platform.stepfun.com +# "gpt-4-0613" for gpt/xi-api, +# "deepseek-chat" for deepseek, +# "glm-4" for zhipuai, +# "gpt-4-1106-preview" for alles-apin or OpenAOE +remote_llm_model = "auto" +# request per minute +rpm = 500 + +[worker] +# enable web search or not +enable_web_search = 1 +# enable search enhancement or not +enable_sg_search = 0 +save_path = "logs/work.txt" + +[worker.time] +enable = 0 +start = "00:00:00" +end = "23:59:59" +has_weekday = 1 + +[sg_search] +# download `src` from https://github.com/sourcegraph/src-cli#installation +binary_src_path = "/usr/local/bin/src" +src_access_token = "${YOUR-SRC-ACCESS-TOKEN}" + +# add your repo here, we just take opencompass and lmdeploy as example +[sg_search.opencompass] +github_repo_id = "open-compass/opencompass" +introduction = "用于评测大型语言模型(LLM). 它提供了完整的开源可复现的评测框架,支持大语言模型、多模态模型的一站式评测,基于分布式技术,对大参数量模型亦能实现高效评测。评测方向汇总为知识、语言、理解、推理、考试五大能力维度,整合集纳了超过70个评测数据集,合计提供了超过40万个模型评测问题,并提供长文本、安全、代码3类大模型特色技术能力评测。" +# introduction = "For evaluating Large Language Models (LLMs). It provides a fully open-source, reproducible evaluation framework, supporting one-stop evaluation for large language models and multimodal models. Based on distributed technology, it can efficiently evaluate models with a large number of parameters. The evaluation directions are summarized in five capability dimensions: knowledge, language, understanding, reasoning, and examination. It integrates and collects more than 70 evaluation datasets, providing in total over 400,000 model evaluation questions. Additionally, it offers evaluations for three types of capabilities specific to large models: long text, security, and coding." + +[sg_search.lmdeploy] +github_repo_id = "internlm/lmdeploy" +introduction = "lmdeploy 是一个用于压缩、部署和服务 LLM(Large Language Model)的工具包。是一个服务端场景下,transformer 结构 LLM 部署工具,支持 GPU 服务端部署,速度有保障,支持 Tensor Parallel,多并发优化,功能全面,包括模型转换、缓存历史会话的 cache feature 等. 它还提供了 WebUI、命令行和 gRPC 客户端接入。" +# introduction = "lmdeploy is a toolkit for compressing, deploying, and servicing Large Language Models (LLMs). It is a deployment tool for transformer-structured LLMs in server-side scenarios, supporting GPU server-side deployment, ensuring speed, and supporting Tensor Parallel along with optimizations for multiple concurrent processes. It offers comprehensive features including model conversion, cache features for caching historical sessions and more. Additionally, it provides access via WebUI, command line, and gRPC clients." + +[frontend] +# chat group assistant type, support "lark", "lark_group", "wechat_personal" and "none" +# for "lark", open https://open.feishu.cn/document/client-docs/bot-v3/add-custom-bot to add bot, **only send, cannot receive** +# for "lark_group", open https://open.feishu.cn/document/home/introduction-to-custom-app-development/self-built-application-development-process to create one +# for "wechat_personal", read ./docs/add_wechat_group_zh.md to setup gateway +type = "none" + +# for "lark", it is chat group webhook url, send reply to group, for example "https://open.feishu.cn/open-apis/bot/v2/hook/xxxxxxxxxxxxxxx" +# for "lark_group", it is the url to fetch chat group message, for example "http://101.133.161.20:6666/fetch", `101.133.161.20` is your own public IPv4 addr +# for "wechat_personal", it is useless +webhook_url = "https://open.feishu.cn/open-apis/bot/v2/hook/xxxxxxxxxxxxxxx" + +# when a new group chat message is received, should it be processed immediately or wait for 18 seconds in case the user hasn't finished speaking? +# support "immediate" +message_process_policy = "immediate" + +[frontend.lark_group] +# "lark_group" configuration examples, use your own app_id and secret !!! +app_id = "cli_a53a34dcb778500e" +app_secret = "2ajhg1ixSvlNm1bJkH4tJhPfTCsGGHT1" +encrypt_key = "abc" +verification_token = "def" + +[frontend.wechat_personal] +# "wechat_personal" listen port +bind_port = 9527 diff --git a/repodir/huixiangdou/config-wkteam-example.ini b/repodir/huixiangdou/config-wkteam-example.ini new file mode 100644 index 00000000..1a82b40f --- /dev/null +++ b/repodir/huixiangdou/config-wkteam-example.ini @@ -0,0 +1,177 @@ +[feature_store] +# `feature_store.py` use this throttle to distinct `good_questions` and `bad_questions` +reject_throttle = -1.0 +# text2vec model path, support local relative path and huggingface model format. +# also support local path, model_path = "/path/to/your/text2vec-model" +embedding_model_path = "maidalun1020/bce-embedding-base_v1" +reranker_model_path = "maidalun1020/bce-reranker-base_v1" +work_dir = "workdir" + +[web_search] +engine = "serper" +# web search engine support ddgs and serper +# For ddgs, see https://pypi.org/project/duckduckgo-search +# For serper, check https://serper.dev/api-key to get a free API key +serper_x_api_key = "YOUR-API-KEY-HERE" +domain_partial_order = ["openai.com", "pytorch.org", "readthedocs.io", "nvidia.com", "stackoverflow.com", "juejin.cn", "zhuanlan.zhihu.com", "www.cnblogs.com"] +save_dir = "logs/web_search_result" + +[llm] +enable_local = 0 +enable_remote = 1 +# hybrid llm service address +client_url = "http://127.0.0.1:8888/inference" + +[llm.server] +# local LLM configuration +# support "internlm/internlm2-chat-7b" and "qwen/qwen-7b-chat-int8" +# support local path, for example +# local_llm_path = "/path/to/your/internlm2" + +local_llm_path = "internlm/internlm2-chat-7b" +local_llm_max_text_length = 3000 +local_llm_bind_port = 8888 + +# remote LLM service configuration +# support "gpt", "kimi", "deepseek", "zhipuai", "xi-api" and "alles-apin" +# xi-api and alles-apin is chinese gpt proxy + +remote_type = "kimi" +remote_api_key = "YOUR-API-KEY-HERE" +# max text length for remote LLM. +# use 128000 for kimi, 192000 for gpt/xi-api, 16000 for deepseek, 128000 for zhipuai +remote_llm_max_text_length = 128000 +# openai API model type, support model list: +# "auto" for kimi. To save money, we auto select model name by prompt length. +# "gpt-4-0613" for gpt/xi-api, +# "deepseek-chat" for deepseek, +# "glm-4" for zhipuai, +# "gpt-4-1106-preview" for alles-apin or OpenAOE +remote_llm_model = "auto" +# request per minute +rpm = 500 + +[coreference_resolution] +base_url = 'http://127.0.0.1:9999/v1' +api_key = 'token-abc123' + +[worker] +# enable web search or not +enable_web_search = 1 +# enable search enhancement or not +enable_sg_search = 0 +# enable coreference resolution in `PreprocNode` +enable_cr = 0 +save_path = "logs/work.txt" + +[worker.time] +enable = 0 +start = "00:00:00" +end = "23:59:59" +has_weekday = 1 + +[sg_search] +# download `src` from https://github.com/sourcegraph/src-cli#installation +binary_src_path = "/usr/local/bin/src" +src_access_token = "YOUR-SRC-ACCESS-TOKEN" + +# add your repo here, we just take opencompass and lmdeploy as example +[sg_search.opencompass] +github_repo_id = "open-compass/opencompass" +introduction = "用于评测大型语言模型(LLM). 它提供了完整的开源可复现的评测框架,支持大语言模型、多模态模型的一站式评测,基于分布式技术,对大参数量模型亦能实现高效评测。评测方向汇总为知识、语言、理解、推理、考试五大能力维度,整合集纳了超过70个评测数据集,合计提供了超过40万个模型评测问题,并提供长文本、安全、代码3类大模型特色技术能力评测。" +# introduction = "For evaluating Large Language Models (LLMs). It provides a fully open-source, reproducible evaluation framework, supporting one-stop evaluation for large language models and multimodal models. Based on distributed technology, it can efficiently evaluate models with a large number of parameters. The evaluation directions are summarized in five capability dimensions: knowledge, language, understanding, reasoning, and examination. It integrates and collects more than 70 evaluation datasets, providing in total over 400,000 model evaluation questions. Additionally, it offers evaluations for three types of capabilities specific to large models: long text, security, and coding." + +[sg_search.lmdeploy] +github_repo_id = "internlm/lmdeploy" +introduction = "lmdeploy 是一个用于压缩、部署和服务 LLM(Large Language Model)的工具包。是一个服务端场景下,transformer 结构 LLM 部署工具,支持 GPU 服务端部署,速度有保障,支持 Tensor Parallel,多并发优化,功能全面,包括模型转换、缓存历史会话的 cache feature 等. 它还提供了 WebUI、命令行和 gRPC 客户端接入。" +# introduction = "lmdeploy is a toolkit for compressing, deploying, and servicing Large Language Models (LLMs). It is a deployment tool for transformer-structured LLMs in server-side scenarios, supporting GPU server-side deployment, ensuring speed, and supporting Tensor Parallel along with optimizations for multiple concurrent processes. It offers comprehensive features including model conversion, cache features for caching historical sessions and more. Additionally, it provides access via WebUI, command line, and gRPC clients." +# add your repo here, we just take opencompass and lmdeploy as example + +[sg_search.mmpose] +github_repo_id = "open-mmlab/mmpose" +introduction = "MMPose is an open-source toolbox for pose estimation based on PyTorch" + +[sg_search.mmdetection] +github_repo_id = "open-mmlab/mmdetection" +introduction = "MMDetection is an open source object detection toolbox based on PyTorch." + +[sg_search.huixiangdou] +github_repo_id = "internlm/huixiangdou" +introduction = "茴香豆是一个基于 LLM 的群聊知识助手。设计拒答、响应两阶段 pipeline 应对群聊场景,解答问题同时不会消息泛滥。" + +[sg_search.xtuner] +github_repo_id = "internlm/xtuner" +introduction = "XTuner is an efficient, flexible and full-featured toolkit for fine-tuning large models." + +[sg_search.mmyolo] +github_repo_id = "open-mmlab/mmyolo" +introduction = "OpenMMLab YOLO series toolbox and benchmark. Implemented RTMDet, RTMDet-Rotated,YOLOv5, YOLOv6, YOLOv7, YOLOv8,YOLOX, PPYOLOE, etc." + +[sg_search.Amphion] +github_repo_id = "open-mmlab/Amphion" +introduction = "Amphion is a toolkit for Audio, Music, and Speech Generation. Its purpose is to support reproducible research and help junior researchers and engineers get started in the field of audio, music, and speech generation research and development." + +[sg_search.mmcv] +github_repo_id = "open-mmlab/mmcv" +introduction = "MMCV is a foundational library for computer vision research and it provides image/video processing, image and annotation visualization, image transformation, various CNN architectures and high-quality implementation of common CPU and CUDA ops" + +[frontend] +# chat group assistant type, support "lark_group", "wechat_personal", "wechat_wkteam" and "none" +# for "lark_group", open https://open.feishu.cn/document/home/introduction-to-custom-app-development/self-built-application-development-process to create one +# for "wechat_personal", read ./docs/add_wechat_group_zh.md to setup gateway +# for "wkteam", see https://wkteam.cn/ +type = "none" + +# for "lark", it is chat group webhook url, send reply to group, for example "https://open.feishu.cn/open-apis/bot/v2/hook/xxxxxxxxxxxxxxx" +# for "lark_group", it is the url to fetch chat group message, for example "http://101.133.161.20:6666/fetch", `101.133.161.20` is your own public IPv4 addr +# for "wechat_personal", it is useless +webhook_url = "https://open.feishu.cn/open-apis/bot/v2/hook/xxxxxxxxxxxxxxx" + +# when a new group chat message is received, should it be processed immediately or wait for 18 seconds in case the user hasn't finished speaking? +# support "immediate" +message_process_policy = "immediate" + +[frontend.lark_group] +# "lark_group" configuration examples, use your own app_id and secret !!! +app_id = "cli_a53a34dcb778500e" +app_secret = "2ajhg1ixSvlNm1bJkH4tJhPfTCsGGHT1" +encrypt_key = "abc" +verification_token = "def" + +[frontend.wechat_personal] +# "wechat_personal" listen port +bind_port = 9527 + +[frontend.wechat_wkteam] +account = "YOUR-MOBILE-NUMBER" +password = "YOUR-PASSWD-HERE" +proxy = 3 +dir = "wkteam" +callback_ip = "101.133.161.20" +callback_port = 9528 +redis_host = "101.133.161.20" +redis_port = "6380" +redis_passwd = "hxd123" + +# 群号和介绍 +# 茴香豆相关 +[frontend.wechat_wkteam.44546611710] +name = "茴香豆群(立夏)" +introduction = "github https://github.com/InternLM/HuixiangDou 用户体验群" + +[frontend.wechat_wkteam.38720590618] +name = "茴香豆群(惊蛰)" +introduction = "github https://github.com/InternLM/HuixiangDou 用户体验群" + +[frontend.wechat_wkteam.48437885473] +name = "茴香豆群(谷雨)" +introduction = "github https://github.com/InternLM/HuixiangDou 用户体验群" + +[frontend.wechat_wkteam.34744063953] +name = "茴香豆群(雨水)" +introduction = "github https://github.com/InternLM/HuixiangDou 用户体验群" + +# ncnn contributors +[frontend.wechat_wkteam.18356748488] +name = "卷卷群" +introduction = "ncnn contributors group" diff --git a/repodir/huixiangdou/config.ini b/repodir/huixiangdou/config.ini new file mode 100644 index 00000000..c7fa7f29 --- /dev/null +++ b/repodir/huixiangdou/config.ini @@ -0,0 +1,212 @@ +[feature_store] +# `feature_store.py` use this throttle to distinct `good_questions` and `bad_questions` +reject_throttle = -1.0 +# text2vec model, support local relative path, huggingface repo and URL. +# for example: +# "maidalun1020/bce-embedding-base_v1" +# "BAAI/bge-m3" +# "https://api.siliconflow.cn/v1/embeddings" +embedding_model_path = "maidalun1020/bce-embedding-base_v1" + +# reranker model, support list: +# "maidalun1020/bce-reranker-base_v1" +# "BAAI/bge-reranker-v2-minicpm-layerwise" +# "https://api.siliconflow.cn/v1/rerank" +reranker_model_path = "maidalun1020/bce-reranker-base_v1" + +# if using `siliconcloud` API as `embedding_model_path` or `reranker_model_path`, give the token +api_token = "" +api_rpm = 1000 +work_dir = "workdir" + +[web_search] +engine = "serper" +# web search engine support ddgs and serper +# For ddgs, see https://pypi.org/project/duckduckgo-search +# For serper, check https://serper.dev/api-key to get a free API key +serper_x_api_key = "YOUR-API-KEY-HERE" +domain_partial_order = ["arxiv.org", "openai.com", "pytorch.org", "readthedocs.io", "nvidia.com", "stackoverflow.com", "juejin.cn", "zhuanlan.zhihu.com", "www.cnblogs.com"] +save_dir = "logs/web_search_result" + +[llm] +enable_local = 1 +enable_remote = 0 +# hybrid llm service address +client_url = "http://127.0.0.1:8888/inference" + +[llm.server] +# local LLM configuration +# support "internlm/internlm2-chat-7b", "internlm2_5-7b-chat" and "qwen/qwen-7b-chat-int8" +# support local path, for example +# local_llm_path = "/path/to/your/internlm2_5" + +local_llm_path = "internlm/internlm2_5-7b-chat" +local_llm_max_text_length = 3000 +# llm server listen port +local_llm_bind_port = 8888 + +# remote LLM service configuration +# support "gpt", "kimi", "deepseek", "zhipuai", "step", "internlm", "xi-api" and "alles-apin" +# support "siliconcloud", see https://siliconflow.cn/zh-cn/siliconcloud +# xi-api and alles-apin is chinese gpt proxy +# for internlm, see https://internlm.intern-ai.org.cn/api/document + +remote_type = "kimi" +remote_api_key = "YOUR-API-KEY-HERE" +# max text length for remote LLM. +# use 128000 for kimi, 192000 for gpt/xi-api, 16000 for deepseek, 128000 for zhipuai, 40000 for internlm2 +remote_llm_max_text_length = 128000 +# openai API model type, support model list: +# "auto" for kimi. To save money, we auto select model name by prompt length. +# "auto" for step to save money, see https://platform.stepfun.com/ +# "gpt-4-0613" for gpt/xi-api, +# "deepseek-chat" for deepseek, +# "glm-4" for zhipuai, +# "gpt-4-1106-preview" for alles-apin or OpenAOE +# "internlm2-latest" for internlm +# for example "alibaba/Qwen1.5-110B-Chat", see https://siliconflow.readme.io/reference/chat-completions-1 +remote_llm_model = "auto" +# request per minute +rpm = 500 + +[coreference_resolution] +base_url = 'http://127.0.0.1:9999/v1' +api_key = 'token-abc123' + +[worker] +# enable web search or not +enable_web_search = 1 +# enable search enhancement or not +enable_sg_search = 0 +# enable coreference resolution in `PreprocNode` +enable_cr = 0 +save_path = "logs/work.txt" + +[worker.time] +enable = 0 +start = "00:00:00" +end = "23:59:59" +has_weekday = 1 + +[sg_search] +# download `src` from https://github.com/sourcegraph/src-cli#installation +binary_src_path = "/usr/local/bin/src" +src_access_token = "YOUR-SRC-ACCESS-TOKEN" + +# add your repo here, we just take opencompass and lmdeploy as example +[sg_search.opencompass] +github_repo_id = "open-compass/opencompass" +introduction = "用于评测大型语言模型(LLM). 它提供了完整的开源可复现的评测框架,支持大语言模型、多模态模型的一站式评测,基于分布式技术,对大参数量模型亦能实现高效评测。评测方向汇总为知识、语言、理解、推理、考试五大能力维度,整合集纳了超过70个评测数据集,合计提供了超过40万个模型评测问题,并提供长文本、安全、代码3类大模型特色技术能力评测。" +# introduction = "For evaluating Large Language Models (LLMs). It provides a fully open-source, reproducible evaluation framework, supporting one-stop evaluation for large language models and multimodal models. Based on distributed technology, it can efficiently evaluate models with a large number of parameters. The evaluation directions are summarized in five capability dimensions: knowledge, language, understanding, reasoning, and examination. It integrates and collects more than 70 evaluation datasets, providing in total over 400,000 model evaluation questions. Additionally, it offers evaluations for three types of capabilities specific to large models: long text, security, and coding." + +[sg_search.lmdeploy] +github_repo_id = "internlm/lmdeploy" +introduction = "lmdeploy 是一个用于压缩、部署和服务 LLM(Large Language Model)的工具包。是一个服务端场景下,transformer 结构 LLM 部署工具,支持 GPU 服务端部署,速度有保障,支持 Tensor Parallel,多并发优化,功能全面,包括模型转换、缓存历史会话的 cache feature 等. 它还提供了 WebUI、命令行和 gRPC 客户端接入。" +# introduction = "lmdeploy is a toolkit for compressing, deploying, and servicing Large Language Models (LLMs). It is a deployment tool for transformer-structured LLMs in server-side scenarios, supporting GPU server-side deployment, ensuring speed, and supporting Tensor Parallel along with optimizations for multiple concurrent processes. It offers comprehensive features including model conversion, cache features for caching historical sessions and more. Additionally, it provides access via WebUI, command line, and gRPC clients." +# add your repo here, we just take opencompass and lmdeploy as example + +[sg_search.mmpose] +github_repo_id = "open-mmlab/mmpose" +introduction = "MMPose is an open-source toolbox for pose estimation based on PyTorch" + +[sg_search.mmdetection] +github_repo_id = "open-mmlab/mmdetection" +introduction = "MMDetection is an open source object detection toolbox based on PyTorch." + +[sg_search.huixiangdou] +github_repo_id = "internlm/huixiangdou" +introduction = "茴香豆是一个基于 LLM 的群聊知识助手。设计拒答、响应两阶段 pipeline 应对群聊场景,解答问题同时不会消息泛滥。" + +[sg_search.xtuner] +github_repo_id = "internlm/xtuner" +introduction = "XTuner is an efficient, flexible and full-featured toolkit for fine-tuning large models." + +[sg_search.mmyolo] +github_repo_id = "open-mmlab/mmyolo" +introduction = "OpenMMLab YOLO series toolbox and benchmark. Implemented RTMDet, RTMDet-Rotated,YOLOv5, YOLOv6, YOLOv7, YOLOv8,YOLOX, PPYOLOE, etc." + +[sg_search.Amphion] +github_repo_id = "open-mmlab/Amphion" +introduction = "Amphion is a toolkit for Audio, Music, and Speech Generation. Its purpose is to support reproducible research and help junior researchers and engineers get started in the field of audio, music, and speech generation research and development." + +[sg_search.mmcv] +github_repo_id = "open-mmlab/mmcv" +introduction = "MMCV is a foundational library for computer vision research and it provides image/video processing, image and annotation visualization, image transformation, various CNN architectures and high-quality implementation of common CPU and CUDA ops" + +[frontend] +# chat group assistant type, support "lark_group", "wechat_personal", "wechat_wkteam" and "none" +# for "lark_group", open https://open.feishu.cn/document/home/introduction-to-custom-app-development/self-built-application-development-process to create one +# for "wechat_personal", read ./docs/add_wechat_group_zh.md to setup gateway +# for "wkteam", see https://wkteam.cn/ +type = "none" + +# for "lark", it is chat group webhook url, send reply to group, for example "https://open.feishu.cn/open-apis/bot/v2/hook/xxxxxxxxxxxxxxx" +# for "lark_group", it is the url to fetch chat group message, for example "http://101.133.161.20:6666/fetch", `101.133.161.20` is your own public IPv4 addr +# for "wechat_personal", it is useless +webhook_url = "https://open.feishu.cn/open-apis/bot/v2/hook/xxxxxxxxxxxxxxx" + +# when a new group chat message is received, should it be processed immediately or wait for 18 seconds in case the user hasn't finished speaking? +# support "immediate" +message_process_policy = "immediate" + +[frontend.lark_group] +# "lark_group" configuration examples, use your own app_id and secret !!! +app_id = "cli_a53a34dcb778500e" +app_secret = "2ajhg1ixSvlNm1bJkH4tJhPfTCsGGHT1" +encrypt_key = "abc" +verification_token = "def" + +[frontend.wechat_personal] +# "wechat_personal" listen port +bind_port = 9527 + +[frontend.wechat_wkteam] +# wechat message callback server ip +callback_ip = "101.133.161.11" +callback_port = 9528 + +# public redis config +redis_host = "101.133.161.11" +redis_port = "6380" +redis_passwd = "hxd123" + +# wkteam +account = "" +password = "" +# !!! `proxy` is very import parameter, it's your account location +# 1:北京 2:天津 3:上海 4:重庆 5:河北 +# 6:山西 7:江苏 8:浙江 9:安徽 10:福建 +# 11:江西 12:山东 13:河南 14:湖北 15:湖南 +# 16:广东 17:海南 18:四川 20:陕西 +# bad proxy would cause account deactivation !!! +proxy = -1 + +# save dir +dir = "wkteam" + +# 群号和介绍 +# 茴香豆相关 +[frontend.wechat_wkteam.43925126702] +name = "茴香豆群(大暑)" +introduction = "github https://github.com/InternLM/HuixiangDou 用户体验群" + +[frontend.wechat_wkteam.44546611710] +name = "茴香豆群(立夏)" +introduction = "github https://github.com/InternLM/HuixiangDou 用户体验群" + +[frontend.wechat_wkteam.38720590618] +name = "茴香豆群(惊蛰)" +introduction = "github https://github.com/InternLM/HuixiangDou 用户体验群" + +[frontend.wechat_wkteam.48437885473] +name = "茴香豆群(谷雨)" +introduction = "github https://github.com/InternLM/HuixiangDou 用户体验群" + +[frontend.wechat_wkteam.34744063953] +name = "茴香豆群(雨水)" +introduction = "github https://github.com/InternLM/HuixiangDou 用户体验群" + +# github.com/tencent/ncnn contributors +[frontend.wechat_wkteam.18356748488] +name = "卷卷群" +introduction = "ncnn contributors group" \ No newline at end of file diff --git a/repodir/huixiangdou/docs/en/.readthedocs.yaml b/repodir/huixiangdou/docs/en/.readthedocs.yaml new file mode 100644 index 00000000..c6cf8e2a --- /dev/null +++ b/repodir/huixiangdou/docs/en/.readthedocs.yaml @@ -0,0 +1,17 @@ +version: 2 + +# Set the version of Python and other tools you might need +build: + os: ubuntu-22.04 + tools: + python: "3.8" + +formats: + - epub + +sphinx: + configuration: docs/en/conf.py + +python: + install: + - requirements: requirements/docs.txt diff --git a/repodir/huixiangdou/docs/en/Makefile b/repodir/huixiangdou/docs/en/Makefile new file mode 100644 index 00000000..d4bb2cbb --- /dev/null +++ b/repodir/huixiangdou/docs/en/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = . +BUILDDIR = _build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/repodir/huixiangdou/docs/en/_static/css/readthedocs.css b/repodir/huixiangdou/docs/en/_static/css/readthedocs.css new file mode 100644 index 00000000..a574dfac --- /dev/null +++ b/repodir/huixiangdou/docs/en/_static/css/readthedocs.css @@ -0,0 +1,62 @@ +.header-logo { + background-image: url("../image/logo.svg"); + background-size: 444px 93px; + height: 93px; + width: 444px; +} + +@media screen and (min-width: 1100px) { + .header-logo { + top: -25px; + } +} + +pre { + white-space: pre; +} + +@media screen and (min-width: 2000px) { + .pytorch-content-left { + width: 1200px; + margin-left: 30px; + } + article.pytorch-article { + max-width: 1200px; + } + .pytorch-breadcrumbs-wrapper { + width: 1200px; + } + .pytorch-right-menu.scrolling-fixed { + position: fixed; + top: 45px; + left: 1580px; + } +} + + +article.pytorch-article section code { + padding: .2em .4em; + background-color: #f3f4f7; + border-radius: 5px; +} + +/* Disable the change in tables */ +article.pytorch-article section table code { + padding: unset; + background-color: unset; + border-radius: unset; +} + +table.autosummary td { + width: 50% +} + +img.align-center { + display: block; + margin-left: auto; + margin-right: auto; +} + +article.pytorch-article p.rubric { + font-weight: bold; +} diff --git a/repodir/huixiangdou/docs/en/_static/image/logo.svg b/repodir/huixiangdou/docs/en/_static/image/logo.svg new file mode 100644 index 00000000..5f4a83c5 --- /dev/null +++ b/repodir/huixiangdou/docs/en/_static/image/logo.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/repodir/huixiangdou/docs/en/_static/image/logo_icon.svg b/repodir/huixiangdou/docs/en/_static/image/logo_icon.svg new file mode 100644 index 00000000..bab6cb8b --- /dev/null +++ b/repodir/huixiangdou/docs/en/_static/image/logo_icon.svg @@ -0,0 +1 @@ +头像1 \ No newline at end of file diff --git a/repodir/huixiangdou/docs/en/_static/js/custom.js b/repodir/huixiangdou/docs/en/_static/js/custom.js new file mode 100644 index 00000000..84da69d4 --- /dev/null +++ b/repodir/huixiangdou/docs/en/_static/js/custom.js @@ -0,0 +1,10 @@ +var collapsedSections = []; + +$(document).ready(function () { + $('.model-summary').DataTable({ + "stateSave": false, + "lengthChange": false, + "pageLength": 20, + "order": [] + }); +}); diff --git a/repodir/huixiangdou/docs/en/_templates/404.html b/repodir/huixiangdou/docs/en/_templates/404.html new file mode 100644 index 00000000..64910175 --- /dev/null +++ b/repodir/huixiangdou/docs/en/_templates/404.html @@ -0,0 +1,18 @@ +{% extends "layout.html" %} + +{% block body %} + +

Page Not Found

+

+ The page you are looking for cannot be found. +

+
+ + +{% endblock %} diff --git a/repodir/huixiangdou/docs/en/_templates/autosummary/class.rst b/repodir/huixiangdou/docs/en/_templates/autosummary/class.rst new file mode 100644 index 00000000..4c3a7a9a --- /dev/null +++ b/repodir/huixiangdou/docs/en/_templates/autosummary/class.rst @@ -0,0 +1,13 @@ +.. role:: hidden + :class: hidden-section +.. currentmodule:: {{ module }} + + +{{ name | underline}} + +.. autoclass:: {{ name }} + :members: + +.. + autogenerated from _templates/autosummary/class.rst + note it does not have :inherited-members: diff --git a/repodir/huixiangdou/docs/en/_templates/callable.rst b/repodir/huixiangdou/docs/en/_templates/callable.rst new file mode 100644 index 00000000..3a7b9d2b --- /dev/null +++ b/repodir/huixiangdou/docs/en/_templates/callable.rst @@ -0,0 +1,14 @@ +.. role:: hidden + :class: hidden-section +.. currentmodule:: {{ module }} + + +{{ name | underline}} + +.. autoclass:: {{ name }} + :members: + :special-members: __call__ + +.. + autogenerated from _templates/callable.rst + note it does not have :inherited-members: diff --git a/repodir/huixiangdou/docs/en/conf.py b/repodir/huixiangdou/docs/en/conf.py new file mode 100644 index 00000000..b530c25e --- /dev/null +++ b/repodir/huixiangdou/docs/en/conf.py @@ -0,0 +1,221 @@ +# flake8: noqa +# Configuration file for the Sphinx documentation builder. +# +# This file only contains a selection of the most common options. For a full +# list see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Path setup -------------------------------------------------------------- + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +import os +import subprocess +import sys + +import pytorch_sphinx_theme +from sphinx.builders.html import StandaloneHTMLBuilder + +sys.path.insert(0, os.path.abspath('../../')) + +# -- Project information ----------------------------------------------------- + +project = 'HuixiangDou' +copyright = '2024, HuixiangDou' +author = 'HuixiangDou Authors' + +# The full version, including alpha/beta/rc tags +release = "20240722" + +# -- General configuration --------------------------------------------------- + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + 'sphinx.ext.autodoc', + 'sphinx.ext.autosummary', + 'sphinx.ext.intersphinx', + 'sphinx.ext.napoleon', + 'sphinx.ext.viewcode', + 'myst_parser', + 'sphinx_copybutton', + 'sphinx_tabs.tabs', + 'notfound.extension', + 'sphinxcontrib.jquery', + 'sphinx_design', +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# +source_suffix = { + '.rst': 'restructuredtext', + '.md': 'markdown', +} + +language = 'en' + +# The master toctree document. +root_doc = 'index' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = 'pytorch_sphinx_theme' +html_theme_path = [pytorch_sphinx_theme.get_html_theme_path()] + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +# yapf: disable +html_theme_options = { + 'menu': [ + { + 'name': 'GitHub', + 'url': 'https://github.com/internlm/HuixiangDou' + }, + ], + # Specify the language of shared menu + 'menu_lang': 'en', + # Disable the default edit on GitHub + 'default_edit_on_github': True, +} +# yapf: enable + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] +html_css_files = [ + 'https://cdn.datatables.net/v/bs4/dt-1.12.1/datatables.min.css', + 'css/readthedocs.css' +] +html_js_files = [ + 'https://cdn.datatables.net/v/bs4/dt-1.12.1/datatables.min.js', + 'js/custom.js' +] + +# -- Options for HTMLHelp output --------------------------------------------- + +# Output file base name for HTML help builder. +htmlhelp_basename = 'huixiangdoudoc' + +# -- Options for LaTeX output ------------------------------------------------ + +latex_elements = { + # The paper size ('letterpaper' or 'a4paper'). + # + # 'papersize': 'letterpaper', + + # The font size ('10pt', '11pt' or '12pt'). + # + # 'pointsize': '10pt', + + # Additional stuff for the LaTeX preamble. + # + # 'preamble': '', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + (root_doc, 'huixiangdou.tex', 'HuixiangDou Documentation', author, + 'manual'), +] + +# -- Options for manual page output ------------------------------------------ + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [(root_doc, 'huixiangdou', 'HuixiangDou Documentation', [author], + 1)] + +# -- Options for Texinfo output ---------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + (root_doc, 'HuixiangDou', 'HuixiangDou Documentation', author, + 'HuixiangDou Authors', 'Professional knowledge assistant based on LLM.', + 'Miscellaneous'), +] + +# -- Options for Epub output ------------------------------------------------- + +# Bibliographic Dublin Core info. +epub_title = project + +# The unique identifier of the text. This can be a ISBN number +# or the project homepage. +# +# epub_identifier = '' + +# A unique identification for the text. +# +# epub_uid = '' + +# A list of files that should not be packed into the epub file. +epub_exclude_files = ['search.html'] + +# set priority when building html +StandaloneHTMLBuilder.supported_image_types = [ + 'image/svg+xml', 'image/gif', 'image/png', 'image/jpeg' +] + +# -- Extension configuration ------------------------------------------------- +# Ignore >>> when copying code +copybutton_prompt_text = r'>>> |\.\.\. ' +copybutton_prompt_is_regexp = True + +# Auto-generated header anchors +myst_heading_anchors = 2 +# Enable "colon_fence" extension of myst. +myst_enable_extensions = ['colon_fence', 'dollarmath'] + +# Configuration for intersphinx +intersphinx_mapping = { + 'python': ('https://docs.python.org/3', None), + 'numpy': ('https://numpy.org/doc/stable', None), + 'torch': ('https://pytorch.org/docs/stable/', None), + 'mmengine': ('https://mmengine.readthedocs.io/en/latest/', None), + 'transformers': + ('https://huggingface.co/docs/transformers/main/en/', None), +} +napoleon_custom_sections = [ + # Custom sections for data elements. + ('Meta fields', 'params_style'), + ('Data fields', 'params_style'), +] + +# Disable docstring inheritance +autodoc_inherit_docstrings = False +# Mock some imports during generate API docs. +autodoc_mock_imports = ['rich', 'attr', 'einops'] +# Disable displaying type annotations, these can be very verbose +autodoc_typehints = 'none' + +# The not found page +notfound_template = '404.html' + + +def builder_inited_handler(app): + subprocess.run(['./cp_origin_docs.sh']) + + +def setup(app): + app.connect('builder-inited', builder_inited_handler) \ No newline at end of file diff --git a/repodir/huixiangdou/docs/en/cp_origin_docs.sh b/repodir/huixiangdou/docs/en/cp_origin_docs.sh new file mode 100755 index 00000000..f7b995e0 --- /dev/null +++ b/repodir/huixiangdou/docs/en/cp_origin_docs.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash + +# Copy *.md files from docs/ if it doesn't have a Chinese translation + +for filename in $(find ../zh/ -name '*.md' -printf "%P\n"); +do + mkdir -p $(dirname $filename) + cp -n ../zh/$filename ./$filename + cp -n ../../README.md ./copy_quickstart.md + cp -n ../../evaluation/README.md ./copy_precision.md +done diff --git a/repodir/huixiangdou/docs/en/doc_architecture.md b/repodir/huixiangdou/docs/en/doc_architecture.md new file mode 100644 index 00000000..d7870faa --- /dev/null +++ b/repodir/huixiangdou/docs/en/doc_architecture.md @@ -0,0 +1,77 @@ +# Code Structure Explanation + + + +This document primarily explains the directory structure and functionalities of HuixiangDou. The documentation may not be updated in real-time with the code, but the definitions that are in place will no longer change. + +## First Layer: Project Introduction + +The outermost layer of the project contains only the huixiangdou python module and one configuration file. + +```bash +. +├── config-advanced.ini +├── config-2G.ini # Advanced and Experience version configuration examples, slightly modified `config.ini` +├── config.ini # Basic configuration example, containing all options and parameters of the algorithm +.. +├── huixiangdou # python module +.. +├── requirements-lark-group.txt # Dependencies needed only for Lark group integration +├── requirements.txt # Basic dependencies +``` + +The configuration is actually in toml format, but to avoid unfamiliarity for users, it was renamed to the commonly seen .ini in Windows. + +## Second Layer: HuixiangDou Module + +The module contains only 3 parts: + +```bash +. +├── frontend # Frontends like Lark, WeChat, etc., are part of the algorithm +├── main.py # main provides an example program +├── service # service is the implementation of the algorithm +``` + +**service** In our [paper](https://arxiv.org/abs/2401.08772), we introduced HuixiangDou as a pipeline structure; in implementation, it may include functions, a local LLM, or an RPC. All these foundational capabilities are regarded as services. + +**frontend** Since HuixiangDou is a set of algorithmic pipelines, things like WeChat, Lark, and the web are its frontends. This directory contains utility classes and functions that call the frontend, currently with Lark's API usage. + +**main.py** Now with algorithms and a frontend, we need an entry function to implement the business logic. If you configured Lark in your `config.ini`, you should send your queries there, qaq. + +## Third Layer: Service + +This is where the main body of the HuixiangDou pipeline is. + +```bash +. +├── feature_store.py # Manages the creation and query of text features. In the future, "creation" and "query" will be separated +├── helper.py # Contains some helper tools +├── llm_client.py # LLM might be an RPC, so a client is needed +├── llm_server_hybrid.py # There might be more than one LLM, hence the name hybrid +├── sg_search.py # Sourcegraph client +├── web_search.py # Client for Google search +└── worker.py # The main logic as mentioned in the paper, calling the components above +``` + +**1. feature_store.py** In the era of facial recognition, the storage and retrieval of facial features are called a feature store, which is the origin of the name. + +1. When extracting features, the text will be partitionally split (the construction technique affects accuracy), the text2vec model extracts features, and saves them locally; +2. During retrieval, in addition to directly using text2vec matching, a re-rank model will adjust the order + The feature store simply acts as a "guidepost" in the entire pipeline and doesn't rely on chunks to provide answers. + +**2. llm part** It includes both client and server_hybrid because: + +1. The model could be deployed locally or be an OpenAI interface. +2. Each LLM has its unique features; we want them to be cost-effective without losing functionality. Thus, it employs "on-demand calling," hence the term `hybrid`. +3. Even for the same feature `topk=1`, configuration parameters vary by provider. The main logic doesn't handle these details; they are all implemented in llm_server_hybrid.py. + +**3. Search** The LLMs' answers always require some ground truth. If the knowledge base does not suffice, relying on a search engine is a good choice. + +The feature here is: **repeatedly checking the quality of Internet results**. Most online information is harmful, and using it directly without filtering can lead to incorrect responses or value issues. + +**4. Search enhancement** The search capability of engines is limited, and the vast body of knowledge is infinite. “Looking for a needle in a haystack” is naturally challenging. + +However, if you already know the answer lies within a certain repo, searching only that repo will surely improve accuracy. sg_search is designed to use the knowledge graph to find answers within a small repo, where "using a sledgehammer to crack a nut" can be effective. + +**5. Main logic worker.py** After being "attacked" tens of thousands of times over half a year across multiple WeChat groups, a set of pipelines was summarized. diff --git a/repodir/huixiangdou/docs/en/doc_full_dev.md b/repodir/huixiangdou/docs/en/doc_full_dev.md new file mode 100644 index 00000000..c2332289 --- /dev/null +++ b/repodir/huixiangdou/docs/en/doc_full_dev.md @@ -0,0 +1,79 @@ +# High Precision Configuration + +The basic version may not perform well. You can enable these features to enhance performance. The more features you turn on, the better. + +1. Use higher accuracy local LLM + + Adjust the `llm.local` model in config.ini to others, see [opencompass leaderboard](https://rank.opencompass.org.cn/leaderboard-llm). + + This option has a significant effect. + +2. Hybrid LLM Service + + For LLM services that support the [openai](https://pypi.org/project/openai/) interface, HuixiangDou can utilize its Long Context ability. + Using [kimi](https://platform.moonshot.cn/) as an example, below is an example of `config.ini` configuration: + + ```ini + # config.ini + [llm] + enable_local = 1 + enable_remote = 1 + .. + [llm.server] + .. + # open https://platform.moonshot.cn/ + remote_type = "kimi" + remote_api_key = "YOUR-KIMI-API-KEY" + remote_llm_max_text_length = 128000 + remote_llm_model = "auto" + ``` + + Note that this feature will increase response time and operating costs. + +3. Repo search enhancement + + This feature is suitable for handling difficult questions and requires basic development capabilities to adjust the prompt. + + - Click [sourcegraph-account-access](https://sourcegraph.com/users/tpoisonooo/settings/tokens) to get token + + ```shell + # open https://github.com/sourcegraph/src-cli#installation + sudo curl -L https://sourcegraph.com/.api/src-cli/src_linux_amd64 -o /usr/local/bin/src && chmod +x /usr/local/bin/src + + # Enable search and fill the token + [worker] + enable_sg_search = 1 + .. + [sg_search] + .. + src_access_token = "${YOUR_ACCESS_TOKEN}" + ``` + + - Edit the name and introduction of the repo, we take opencompass as an example + + ```ini + # config.ini + # add your repo here, we just take opencompass and lmdeploy as example + [sg_search.opencompass] + github_repo_id = "open-compass/opencompass" + introduction = "Used for evaluating large language models (LLM) .." + ``` + + - Use `python3 -m huixiangdou.service.sg_search` for unit test, the returned content should include opencompass source code and documentation + + ```shell + python3 -m huixiangdou.service.sg_search + .. + "filepath": "opencompass/datasets/longbench/longbench_trivia_qa.py", + "content": "from datasets import Dataset.. + ``` + + Run `main.py`, HuixiangDou will enable search enhancement when appropriate. + +4. Tune Parameters + + It is often unavoidable to adjust parameters with respect to business scenarios. + + - Refer to [data.json](../../tests/data.json) to add real data, run [test_intention_prompt.py](../../tests/test_intention_prompt.py) to get suitable prompts and thresholds, and update them into [prompt.py](../../huixiangdou/service/prompt.py). + - Adjust the [number of search results](../../huixiangdou/service/serial_pipeline.py) based on the maximum length supported by the model. + - Update `web_search.domain_partial_order` in `config.ini` according to your scenarios. diff --git a/repodir/huixiangdou/docs/en/doc_knowledge_graph.md b/repodir/huixiangdou/docs/en/doc_knowledge_graph.md new file mode 100644 index 00000000..4f75a6f9 --- /dev/null +++ b/repodir/huixiangdou/docs/en/doc_knowledge_graph.md @@ -0,0 +1,67 @@ +# Hybrid Knowledge Graph and Dense Retrieval + +By integrating a hybrid knowledge graph with dense retrieval, the F1 score in refusal-to-answer task is improved by 2%. For a detailed explanation of the solution, see [lark documentation](https://aicarrier.feishu.cn/docx/F51pduYyMof8syxKe5RchiU1nIN). The essence of this approach is to **weight high-frequency words**. + +This approach is perfectly compatible with the older version. Below are the steps. + +## 1. Build Knowledge Graph + +To reduce costs, we use silicon cloud qwen-1.5-110B to extract entity words, and `config.ini` already supports silicon cloud. The modification is as follows: + +```bash +[llm.server] +.. +remote_type = "siliconcloud" +remote_api_key = "sk-ducerXXXXX" +remote_llm_max_text_length = 40000 +remote_llm_model = "alibaba/Qwen1.5-110B-Chat" +rpm = 1000 +``` + +Assuming the knowledge base is still in the `repodir` directory, first establish the knowledge graph. +After completion, there will be `jsonl` and `pickle` files under `workdir/kg`, and you can test the query. + +```bash +# About 2 hours +python3 -m huixiangdou.service.kg --build +python3 -m huixiangdou.service.kg --query "How to install mmpose?" +.. ++-----------------+-------+------------------------+---------------------------+ +| Query | State | Part of Reply | References | ++=================+=======+========================+===========================+ +| 如何安装mmpose? | 0 | repodir/mmpose/READM.. | | +| | | |
| +| | | | + + + + + + + + + + + + + + + + + + + + + + + diff --git a/repodir/huixiangdou/docs/zh/_static/image/logo_icon.svg b/repodir/huixiangdou/docs/zh/_static/image/logo_icon.svg new file mode 100644 index 00000000..bab6cb8b --- /dev/null +++ b/repodir/huixiangdou/docs/zh/_static/image/logo_icon.svg @@ -0,0 +1 @@ +头像1 \ No newline at end of file diff --git a/repodir/huixiangdou/docs/zh/_static/js/custom.js b/repodir/huixiangdou/docs/zh/_static/js/custom.js new file mode 100644 index 00000000..84da69d4 --- /dev/null +++ b/repodir/huixiangdou/docs/zh/_static/js/custom.js @@ -0,0 +1,10 @@ +var collapsedSections = []; + +$(document).ready(function () { + $('.model-summary').DataTable({ + "stateSave": false, + "lengthChange": false, + "pageLength": 20, + "order": [] + }); +}); diff --git a/repodir/huixiangdou/docs/zh/_templates/404.html b/repodir/huixiangdou/docs/zh/_templates/404.html new file mode 100644 index 00000000..64910175 --- /dev/null +++ b/repodir/huixiangdou/docs/zh/_templates/404.html @@ -0,0 +1,18 @@ +{% extends "layout.html" %} + +{% block body %} + +

Page Not Found

+

+ The page you are looking for cannot be found. +

+

+ If you just switched documentation versions, it is likely that the page you were on is moved. You can look for it in + the content table left, or go to the homepage. +

+ + +{% endblock %} diff --git a/repodir/huixiangdou/docs/zh/_templates/autosummary/class.rst b/repodir/huixiangdou/docs/zh/_templates/autosummary/class.rst new file mode 100644 index 00000000..4c3a7a9a --- /dev/null +++ b/repodir/huixiangdou/docs/zh/_templates/autosummary/class.rst @@ -0,0 +1,13 @@ +.. role:: hidden + :class: hidden-section +.. currentmodule:: {{ module }} + + +{{ name | underline}} + +.. autoclass:: {{ name }} + :members: + +.. + autogenerated from _templates/autosummary/class.rst + note it does not have :inherited-members: diff --git a/repodir/huixiangdou/docs/zh/_templates/callable.rst b/repodir/huixiangdou/docs/zh/_templates/callable.rst new file mode 100644 index 00000000..3a7b9d2b --- /dev/null +++ b/repodir/huixiangdou/docs/zh/_templates/callable.rst @@ -0,0 +1,14 @@ +.. role:: hidden + :class: hidden-section +.. currentmodule:: {{ module }} + + +{{ name | underline}} + +.. autoclass:: {{ name }} + :members: + :special-members: __call__ + +.. + autogenerated from _templates/callable.rst + note it does not have :inherited-members: diff --git a/repodir/huixiangdou/docs/zh/conf.py b/repodir/huixiangdou/docs/zh/conf.py new file mode 100644 index 00000000..ba0a03cf --- /dev/null +++ b/repodir/huixiangdou/docs/zh/conf.py @@ -0,0 +1,221 @@ +# flake8: noqa +# Configuration file for the Sphinx documentation builder. +# +# This file only contains a selection of the most common options. For a full +# list see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Path setup -------------------------------------------------------------- + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +import os +import subprocess +import sys + +import pytorch_sphinx_theme +from sphinx.builders.html import StandaloneHTMLBuilder + +sys.path.insert(0, os.path.abspath('../../')) + +# -- Project information ----------------------------------------------------- + +project = 'HuixiangDou' +copyright = '2024, HuixiangDou' +author = 'HuixiangDou Authors' + +# The full version, including alpha/beta/rc tags +release = "20240722" + +# -- General configuration --------------------------------------------------- + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + 'sphinx.ext.autodoc', + 'sphinx.ext.autosummary', + 'sphinx.ext.intersphinx', + 'sphinx.ext.napoleon', + 'sphinx.ext.viewcode', + 'myst_parser', + 'sphinx_copybutton', + 'sphinx_tabs.tabs', + 'notfound.extension', + 'sphinxcontrib.jquery', + 'sphinx_design', +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# +source_suffix = { + '.rst': 'restructuredtext', + '.md': 'markdown', +} + +language = 'cn' + +# The master toctree document. +root_doc = 'index' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = 'pytorch_sphinx_theme' +html_theme_path = [pytorch_sphinx_theme.get_html_theme_path()] + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +# yapf: disable +html_theme_options = { + 'menu': [ + { + 'name': 'GitHub', + 'url': 'https://github.com/internlm/HuixiangDou' + }, + ], + # Specify the language of shared menu + 'menu_lang': 'cn', + # Disable the default edit on GitHub + 'default_edit_on_github': True, +} +# yapf: enable + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] +html_css_files = [ + 'https://cdn.datatables.net/v/bs4/dt-1.12.1/datatables.min.css', + 'css/readthedocs.css' +] +html_js_files = [ + 'https://cdn.datatables.net/v/bs4/dt-1.12.1/datatables.min.js', + 'js/custom.js' +] + +# -- Options for HTMLHelp output --------------------------------------------- + +# Output file base name for HTML help builder. +htmlhelp_basename = 'huixiangdoudoc' + +# -- Options for LaTeX output ------------------------------------------------ + +latex_elements = { + # The paper size ('letterpaper' or 'a4paper'). + # + # 'papersize': 'letterpaper', + + # The font size ('10pt', '11pt' or '12pt'). + # + # 'pointsize': '10pt', + + # Additional stuff for the LaTeX preamble. + # + # 'preamble': '', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + (root_doc, 'huixiangdou.tex', 'HuixiangDou Documentation', author, + 'manual'), +] + +# -- Options for manual page output ------------------------------------------ + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [(root_doc, 'huixiangdou', 'HuixiangDou Documentation', [author], + 1)] + +# -- Options for Texinfo output ---------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + (root_doc, 'HuixiangDou', 'HuixiangDou Documentation', author, + 'HuixiangDou Authors', 'Professional knowledge assistant based on LLM.', + 'Miscellaneous'), +] + +# -- Options for Epub output ------------------------------------------------- + +# Bibliographic Dublin Core info. +epub_title = project + +# The unique identifier of the text. This can be a ISBN number +# or the project homepage. +# +# epub_identifier = '' + +# A unique identification for the text. +# +# epub_uid = '' + +# A list of files that should not be packed into the epub file. +epub_exclude_files = ['search.html'] + +# set priority when building html +StandaloneHTMLBuilder.supported_image_types = [ + 'image/svg+xml', 'image/gif', 'image/png', 'image/jpeg' +] + +# -- Extension configuration ------------------------------------------------- +# Ignore >>> when copying code +copybutton_prompt_text = r'>>> |\.\.\. ' +copybutton_prompt_is_regexp = True + +# Auto-generated header anchors +myst_heading_anchors = 2 +# Enable "colon_fence" extension of myst. +myst_enable_extensions = ['colon_fence', 'dollarmath'] + +# Configuration for intersphinx +intersphinx_mapping = { + 'python': ('https://docs.python.org/3', None), + 'numpy': ('https://numpy.org/doc/stable', None), + 'torch': ('https://pytorch.org/docs/stable/', None), + 'mmengine': ('https://mmengine.readthedocs.io/en/latest/', None), + 'transformers': + ('https://huggingface.co/docs/transformers/main/en/', None), +} +napoleon_custom_sections = [ + # Custom sections for data elements. + ('Meta fields', 'params_style'), + ('Data fields', 'params_style'), +] + +# Disable docstring inheritance +autodoc_inherit_docstrings = False +# Mock some imports during generate API docs. +autodoc_mock_imports = ['rich', 'attr', 'einops'] +# Disable displaying type annotations, these can be very verbose +autodoc_typehints = 'none' + +# The not found page +notfound_template = '404.html' + + +def builder_inited_handler(app): + subprocess.run(['./cp_origin_docs.sh']) + + +def setup(app): + app.connect('builder-inited', builder_inited_handler) \ No newline at end of file diff --git a/repodir/huixiangdou/docs/zh/cp_origin_docs.sh b/repodir/huixiangdou/docs/zh/cp_origin_docs.sh new file mode 100755 index 00000000..c0c384dc --- /dev/null +++ b/repodir/huixiangdou/docs/zh/cp_origin_docs.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash + +# Copy *.md files from docs/ if it doesn't have a Chinese translation + +for filename in $(find ../en/ -name '*.md' -printf "%P\n"); +do + mkdir -p $(dirname $filename) + cp -n ../en/$filename ./$filename + cp -n ../../README_zh.md ./copy_quickstart.md + cp -n ../../evaluation/README_zh.md ./copy_precision.md +done diff --git a/repodir/huixiangdou/docs/zh/doc_add_lark_group.md b/repodir/huixiangdou/docs/zh/doc_add_lark_group.md new file mode 100644 index 00000000..99c766f3 --- /dev/null +++ b/repodir/huixiangdou/docs/zh/doc_add_lark_group.md @@ -0,0 +1,156 @@ +# 集成飞书群聊收发和撤回 + +接入飞书完整功能需要公网 IP。假设使用[阿里云 ECS](https://www.aliyun.com/product/ecs)(学生免费版即可)、假设公网 IP 是 `101.133.161.20`。且已配置[安全组](https://help.aliyun.com/zh/ecs/user-guide/overview-44),外部可访问 `6666` 端口号,尤其是部署飞书的云服务器公网地址能够访问。 + +常见地,有公网 IP 的机器往往没有 GPU,我们将分开部署 LLM 和飞书消息存储,**请注意命令执行时所在机器**。 + + + +## 一、创建测试企业 + +测试企业是开发期间使用的测试环境,不需要正式权限审核。正式上线前都应该用测试企业的配置运行代码。 + +**STEP1.** 打开[飞书开发者平台-开发者后台-创建企业自建应用](https://open.feishu.cn/app?lang=zh-CN) + + + +**STEP2.** 点击应用头像,创建测试企业 + + + +**STEP3.** 切换到测试企业,开始配置 + + + +## 二、配置机器人权限 + +**STEP1.** 在阿里云 ECS(101.133.161.20 机器)上,安装 redis-server 和依赖 + +```bash +# Ubuntu 启动 redis-server +sudo apt install redis-server +redis-server +.. + +# 在 CentOS 和 Red Hat 系统中,首先添加 EPEL 仓库,然后更新 yum 源 +sudo yum install epel-release +sudo yum update +# 然后安装 redis 数据库 +sudo yum -y install redis +# 安装好后启动 redis 服务即可: +sudo systemctl start redis +.. +``` + +确保 redis 在后台已经活跃 +![image](https://github.com/InternLM/HuixiangDou/assets/40042370/84804532-c9fa-40b9-8605-6fd760a75f72) + +启动群聊消息监听 + +``` +# ECS 上,开个新终端,启动消息监听 6666 端口号 +cd huixiangdou +python3 -m pip install -r requirements-lark-group.txt +python3 -m huixiangdou.frontend.lark_group +.. +* Running on all addresses (0.0.0.0) +* Running on http://127.0.0.1:6666 +* Running on http://101.133.161.20:6666 +Press CTRL+C to quit +``` + +**STEP2.** 飞书开发者平台-添加应用能力-机器人 + + + +**STEP3.** 配置机器人的回调地址 + +我们以`http://101.133.161.20:6666/event` 为例,点击右侧“验证”按钮,ECS 应能收到一条消息,此时还未配置 key 和 token 会报错。 + + + +这一步也需要同步配置加解密策略,防止解析报错: +![image](https://github.com/InternLM/HuixiangDou/assets/40042370/8205cbc7-b7a1-4b50-ac46-8f6860be0ba0) + +**STEP4.** 权限管理-添加权限 + +在测试企业中,添加权限不需要审批。以下是所需权限列表,搜索添加 + +``` +* 读取群消息 +* 获取群组信息 +* 获取与发送单聊、群组消息 +* 接收群聊@机器人消息事件 +* 获取群组中所有消息 +* 获取用户发给机器人的单聊消息 +* 获取单聊、群组消息 +* 以应用的身份发消息 +``` + +**STEP5.** 事件与回调-事件订阅 + +配置请求地址,假设 key 是 `abc`,token 是 `def`;添加事件 “接收消息 v2.0” + + + +此时可在飞书 APP 上,**打开测试企业**,把应用“茴香豆” 加进群组。 + +1. 在群里发消息,ECS 应保存到 redis 中 + +2. 可以用 `curl -X POST -H "Content-Type: application/json" http://101.133.161.20:6666/fetch` 读取 redis 中的消息 + 测通的样例: + ![image](https://github.com/InternLM/HuixiangDou/assets/40042370/333b3e79-bd80-41fe-9116-329e2e5356ab) + +3. 为了能及时撤回。 用户依次发送 4 条消息:“1、2、3、豆哥撤回”,接收顺序应该是 “豆哥撤回、1、2、3” + +## 三、测试完整收发、撤回功能 + +打开应用-凭证与基础信息,获取 App ID 和 App Secret,连同之前的 key、token 和回调地址,一起填入 `config.ini`。 + +消息类型改成 `lark_group` + +```ini +[frontend] +type = "lark_group" +.. +# ECS 回调地址 +webhook_url = "http://101.133.161.20:6666/fetch" + +[frontend.lark_group] +# "lark_group" configuration examples, use your own app_id and secret !!! +app_id = "cli_a53a34dcb7785xxx" +app_secret = "2ajhg1ixSvlNm1bJkH4tJhPfTCsGGXXX" +encrypt_key = "abc" +verification_token = "def" +``` + +如同 README 运行测试,执行 `main` + +```shell +python3 -m huixiangdou.main --standalone +.. +======== Running on http://0.0.0.0:8888 ======== +(Press CTRL+C to quit) +``` + +输入技术问题,茴香豆会作答;在群组里输入“豆哥撤回”,助手将撤回发送过的所有消息。 + + + +## 四、正式上线 + +切换回正式版本。添加权限、设置回调地址。 + +点击“版本管理与发布-创建版本”,等待管理员审核上线。 + +## 五、FAQ + +1. 如果`curl -X POST -H "Content-Type: application/json" http://101.133.161.20:6666/fetch`执行超时如下图。 + ![image](https://github.com/InternLM/HuixiangDou/assets/40042370/ba4ddb79-5b3d-4dae-8e9e-d958f42a35b7) + 解答:如果你的GPU机器有公网IP,修改webhook url地址为127.0.0.1 + ![image](https://github.com/InternLM/HuixiangDou/assets/40042370/11c9159f-b479-4255-9582-92d6b3eab501) + +2. 报错huggingface_hub.utils.\_validators.HFValidationError: Repo id must be in the form + repo_name' or 'namespace/repo_name': '/data/bcee-embedding-base v1'. Use`repo_type` argument if needed + ![image](https://github.com/InternLM/HuixiangDou/assets/40042370/b67ba8f8-7f37-4f62-b995-15fd3ad5e12e) + 解答:sentence_transformers包的bug,包版本降到2.2.2可以修复。 diff --git a/repodir/huixiangdou/docs/zh/doc_add_wechat_accessibility.md b/repodir/huixiangdou/docs/zh/doc_add_wechat_accessibility.md new file mode 100644 index 00000000..d276671b --- /dev/null +++ b/repodir/huixiangdou/docs/zh/doc_add_wechat_accessibility.md @@ -0,0 +1,63 @@ +# 集成个人微信 android 免费版示例 + +在之前的 [魔改 wechaty 方案](./doc_add_wechat_group.md) 我们一共介绍了 7 种方法。这次提供第 5 种方案的具体实现,基于 Android Accessibility 读写前端,和“抢红包”软件原理相同。 + +由于没有 Appium/Selenium 框架做中间商,比较稳定。 + +## 一、演示视频 + +这里是 BiliBili 2 分钟演示视频 https://www.bilibili.com/video/BV1S2421N7mn/ + +## 二、准备工作 + +- 一个 android 手机,对性能和系统版本都没要求 +- 微信版本 8.0.47 / 8.0.48 / 8.0.49,其他版本的 view id 可能变化。[代码里](https://github.com/InternLM/HuixiangDou/blob/main/android/demo/src/main/java/com/carlos/grabredenvelope/demo/WechatConstants.kt)只测了这些版本的 id +- 一个测试用的微信号 + +## 三、运行 + +打开 [OpenXLab 茴香豆 web 端](https://openxlab.org.cn/apps/detail/tpoisonooo/huixiangdou-web) ,创建自己的知识库。 + +这里是个能直接使用的账号密码: + +```bash +账号: 20230322发版测试 +密码: 123 +``` + +点击 “零开发集成微信”,会显示你的服务端回调地址和教程。例如 `http://139.224.198.162:18443/api/v1/message/v1/wechat/oZGh` + +从 [这里](https://github.com/InternLM/HuixiangDou/releases) 下载编译好的 apk,填入回调地址,开启服务,跳入微信。 + + + +现在这个效果,记得关掉手机自动熄屏: + + + +## FAQ + +1. 源码在哪儿? + + 在 repo 的 android 目录,需要 kotlin+java 开发能力 + +2. 我的微信版本更高/更低怎么办? + + 一、【不想开发】去微信官网找个 8.0.47 版本安装 + + 二、【愿意开发】用 DDMS dump 一下 view 结构;然后打开源码的 `WechatConstants.kt` 文件,把你的版本的 id 填进去,build 即可 + + 改完请发个 PR + + ```java + if (version == "8.0.47") { + RES_ID_GROUP_NAME.. + RES_ID_USER_NAME.. + RES_ID_USER_CONTENT.. + RES_ID_EDIT_TEXT.. + } else if ( 你的版本 ) { + .. + } else { + Log.w("msg", "unknown version, maybe incompatible") + } + ``` diff --git a/repodir/huixiangdou/docs/zh/doc_add_wechat_commercial.md b/repodir/huixiangdou/docs/zh/doc_add_wechat_commercial.md new file mode 100644 index 00000000..2778ad34 --- /dev/null +++ b/repodir/huixiangdou/docs/zh/doc_add_wechat_commercial.md @@ -0,0 +1,129 @@ +# 集成个人微信服务器商业版 + +用户反馈[免费版](./doc_add_wechat_accessibility.md)不能同时支持多个群,事实上要获取群里的视频和语音也很难用 accessibility 方式完成。 + +因此我们介绍[商业版 wkteam](https://wkteam.cn) 接入方式,支持图片、公众号解析。 + +> \[!Warning\] +> +>
+> wkteam 目前暂停注册 +>
+ +> \[!Warning\] +> +>
+> wkteam 曾变更过经营主体,🚨注意财产安全
+> 打开 wkteam.cn 和 121.229.29.88:6327, 会发现 UI 相同但数据库不通,须仔细咨询运营方 +>
+ +整个服务需要准备以下资源: + +- wkteam 账号 +- 一个微信号,支持 22 个国内地区,见[文档](https://wkteam.cn/api-wen-dang2/deng-lu/huo-qu-wei-xin-er-wei-ma2.html) +- GPU,最少 2G 显存 + +## 一、部署图 + +考虑到算法服务可能不在线、没有公网 IP,整个设计分三部分: + +- **redis** 存储微信消息,需要部署在公网 +- **消息回调** 接收 wkteam 提供的推送,转存到 redis,需要公网 +- **算法 pipeline** 从 redis 拉取消息处理,结果直接发给 wkteam + + + +这么设计的好处是: + +- 消息回调实现简单,很稳定一般不需重启 +- 信息都被缓存进了 redis,修改算法后重启,不会漏处理群里的消息 + +## 二、wkteam 登录、测试消息缓存 + +启动公网 redis。 + +在公网机器中填写 `config.ini`,假设我们的 ip 是 `101.133.161.11`;假设我们的地区是上海。 + +> 注意 `proxy` 地区填错会导致封号! 首次使用 wkteam,24 小时后要重新登录一次 + +```bash +# wechat message callback server ip +callback_ip = "101.133.161.11" +callback_port = 9528 + +# public redis config +redis_host = "101.133.161.11" +redis_port = "6380" +redis_passwd = "hxd123" + +# wkteam +account = "" +password = "" +# !!! `proxy` is very import parameter, it's your account location +# 1:北京 2:天津 3:上海 4:重庆 5:河北 +# 6:山西 7:江苏 8:浙江 9:安徽 10:福建 +# 11:江西 12:山东 13:河南 14:湖北 15:湖南 +# 16:广东 17:海南 18:四川 20:陕西 +# bad proxy would cause account deactivation !!! +proxy = 3 +``` + +运行 `wechat.py`,微信扫描二维码登录,然后注册 callback 地址。 + +```bash +python3 huixiangdou/frontend/wechat.py --login +``` + +若运行成功,会看到以下日志,同时 `wkteam/license.json` 会记录完整的账号信息。 + +```bash +# 设置 callback 地址日志 +.. set callback url http://xxx/callback +.. {"code":"1000","message":"设置成功","data":null} +.. login success, all license saved to wkteam/license.json + +# 保存账号信息 +cat wkteam/license.json +{ + "auth": "xxx", + "wId": "xxx", + "wcId": "wxid_xxx", + "qrCodeUrl": "http://wxapii.oosxxx" +} +``` + +## 三、运行算法 pipeline + +因为算法 pipeline 要发消息给微信,所以要把公网的 wkteam 目录,拷贝到 GPU 所在机器上。 + +> 为什么不在公网统一处理所有微信收发?看起来设计简化了,实际上开发调试都巨麻烦。 + +填写 `config.ini`,运行。[config-wkteam-example.ini](../../config-wkteam-example.ini) 是一个填写示例: + +- LLM 只使用 kimi +- 群列表为茴香豆用户群。`wkteam/wechat_message.jsonl` 记录了所有消息、可以得知群号 +- 开启指代消歧 + +```bash +python3 -m huixiangdou.main +``` + +这套服务会: + +- 下载群里的图片。如果使用 kimi,会尝试 OCR +- 尝试解析群里的公众号消息,失败则只使用小标题 + +## 四、【可选】指代消歧 + +根据 [2405.02817](../../sft/),我们基于真实数据微调了 Qwen 系列模型。 +从 [HuggingFace](https://huggingface.co/tpoisonooo/HuixiangDou-CR-LoRA-Qwen-14B) 下载 LoRA 权重,合并权重并部署成 openai API + +```bash +# 用 axolotl 合并 weight +python3 -m axolotl.cli.merge_lora examples/qwen/qwen2-lora-14B.yaml + +# 用 vLLM 部署 +python -m vllm.entrypoints.openai.api_server --served-model-name coref-res --model /path/to/qwen14b-lora-merged/ --port 9999 --max-model-len 4096 --gpu-memory-utilization 0.8 +``` + +把端口号配置进 `config.ini` ,重新运行算法 pipeline 即可。 diff --git a/repodir/huixiangdou/docs/zh/doc_add_wechat_group.md b/repodir/huixiangdou/docs/zh/doc_add_wechat_group.md new file mode 100644 index 00000000..63603ae1 --- /dev/null +++ b/repodir/huixiangdou/docs/zh/doc_add_wechat_group.md @@ -0,0 +1,150 @@ +# 集成 wechaty 魔改版个人微信(不推荐) + +> \[!NOTE\] +> +>
+> wechaty 魔改法有诸多缺陷,请使用 android APK 免费版或 `main.py` 中 wechat_wkteam 类付费版。 +>
+ +截止 2024.01.30,想在个人微信群收发消息,潜在思路有这些: + +``` +😔 优先向官方求助。确认还没有正式渠道 +🙄 企微/公众号。机器人被设计成“应用”而非“用户”,运行有诸多限制 +😮‍💨 微信 PC web 版/ QQ 浏览器版/ 统信 UOS 版。目前新号/新版无法登录 +🤐 Hook 进程,汇编调用函数。类似“金山游侠” +🤨 模拟器。类似 Selenium/Appium 自动化,不稳定 +🤔 OCR。类似原神 AI 辅助工具,很难保证识别精度 +``` + +⚠️ **所有非正式途径都有封号等风险,自行承担**。 + +本文介绍 [python-wechaty](https://github.com/Wechaty/python-wechaty/) 魔法接入(ipadLogin 方式),限时免费。 + +需要注意,**wechaty 很早就不维护了**,如果是个人使用,推荐用 [accessibility 方法](./doc_add_wechat_accessibility.md)。 + +## 一、准备工作 + +申请一个测试账号,例如用户名为“豆哥”。 + +保证 linux 时区正确。以 `Asia/Shanghai` 为例,`/etc/localtime` 和 `/etc/timezone` 要对齐 + +```Shell +$ cat /etc/timezone +Asia/Shanghai +$ ls -l /etc/localtime +lrwxrwxrwx 1 root root 33 11月 17 2022 /etc/localtime -> /usr/share/zoneinfo/Asia/Shanghai +``` + +## 二、运行 [python-wechaty-template](https://github.com/wechaty/python-wechaty-template) + +**注意 [python-wechaty-template](https://github.com/wechaty/python-wechaty-template) “看起来” 已不再维护,请谨慎评估风险。** + +**STEP1.** 打开 [pad-local 官网](http://pad-local.com/#/),获取限时免费 token,如 `puppet_padlocal_xxx`。 + +**STEP2.** 启动 gateway。终端弹出二维码链接后,扫码登录。 + +首次运行可能要多扫几次,成功应出现 “IoClient 豆哥 logged in” 日志。 + +```Shell +$ git clone https://github.com/wechaty/python-wechaty-template +$ cd python-wechaty-template +$ ./start_gateway_docker.sh puppet_padlocal_xxx +.. +Online QR Code Image: https://wechaty.js.org/qrcode/http%3A%2F%2.. +.. +04:01:56 INFO IoClient 豆哥 logged in +``` + +gateway 仅仅是持续监听 8080 端口的消息代理,并不执行业务逻辑。 + +**STEP3.** 打开新终端,安装依赖,调整 3 处代码。[这里](https://github.com/tpoisonooo/python-wechaty-template/pull/1) 有修改好的 3 个文件供对比。 + +```Shell +cd python-wechaty-template +python3 -m pip install "urllib3<2.0.0" # 老项目需要老的 urllib3 +python3 -m pip install -r requirements.txt + +# 第一处, docker 首次启动没有 `bot` +cat Makefile +dockerrun: + docker stop bot && docker rm bot # 删除这行 + docker run -it -d -v $(P):/bot --name bot -p 8004:8004 py-wechaty-template-bot:latest + +# 第二处,已安装的 puppet server 不存在,注释 ping、把 IP 改成 localhost +cat ~/miniconda3/lib/python3.9/site-packages/wechaty_puppet_service/puppet.py +.. +893 # if ping_endpoint(self.options.end_point) is False: +894 # raise WechatyPuppetConfigurationError( +895 # f"can't not ping endpoint: {self.options.end_point}" +896 # ) 注释这个判断 +897 +898 host, port = extract_host_and_port(self.options.end_point) +899 host = '127.0.0.1' # 增加这句 +900 self.channel = Channel(host=host, port=port) + +# 第三处,样例插件未完全实现导致崩溃,只保留 `DingDongPlugin` +cat bot.py +.. + bot.use([ + DingDongPlugin(), +# RepeaterPlugin(), 注释这些插件 +# InfoLoggerPlugin(), +# CounterPlugin(), +# UICounterPlugin(), +# GithubMessageForwarderPlugin( +# endpoint=os.environ.get('github_endpoint', None) or "your-custom-endpoint" +# ), + ]) +``` + +**STEP4.** `Makefile` 编译运行测试样例 + +```bash +make bot +python3 bot.py # 确保这两句没有崩溃 +``` + +`bot.py` 应能看到这些日志: + + + +成功后打开微信,发送 `ding` 可以收到 `dong` + + + +**STEP5.** 集成 HuixiangDou + +假设你已经读过 README,能够运行 `STEP2. 运行基础版技术助手`。那么修改 `config.ini`,服务类型改成 `wechat_personal`,运行 `main` 默认会监听 9527 端口。 + +```Shell +# config.ini +.. +[frontend] +type = "wechat_personal" + +python3 -m huixiangdou.main --standalone # 非 docker 用户 +python3 -m huixiangdou.main # docker 用户 +.. +======== Running on http://0.0.0.0:9527 ======== +(Press CTRL+C to quit) +``` + +用 `curl -X POST -H "Content-Type: application/json" -d '{"query":"你好"}' http://127.0.0.1:9527/api` 可以测试效果。 + +调整 [ding_dong.py on_message()](https://github.com/wechaty/python-wechaty-template/blob/main/src/plugins/ding_dong.py#L10),把消息发给 9527 端口,返回响应。 +[这里](https://github.com/tpoisonooo/python-wechaty-template/blob/main/src/plugins/ding_dong.py) 是修改好的代码。 + +这是最终运行起来的 3 个终端: + +- 上面是 HuixiangDou 服务 +- 左下是 `app.py` +- 右下是 python-wechaty gateway + + + +## FAQ + +- `make bot` 报错 `multiple target patterns`。可能 `Makefile` 多删了一行空白 +- `make bot` 第二次执行,报错 `/bot is already in use`。恢复 `Makefile` 的修改即可。 第一次运行不存在 bot,所以要删掉那行; 第二次已经存在了。或者手工删除容器也可以。 +- 运行 `python3 bot.py` 时,报错 `cannot import name 'get_host' from 'urllib3' ` 为 urllib3 版本问题,根据 [python-wechaty-issue](https://github.com/wechaty/python-wechaty/issues/419#issuecomment-1859148951) 执行 `pip install "urllib3<2.0.0"` 得以解决。 diff --git a/repodir/huixiangdou/docs/zh/doc_architecture.md b/repodir/huixiangdou/docs/zh/doc_architecture.md new file mode 100644 index 00000000..0df2bc9a --- /dev/null +++ b/repodir/huixiangdou/docs/zh/doc_architecture.md @@ -0,0 +1,76 @@ +# 代码结构说明 + + + +本文主要解释豆哥(茴香豆)各目录和功能。文档可能无法随代码即时更新,但已有定义不会再变动。 + +## 第一层:项目介绍 + +项目最外层,只有 huixiangdou python module 和 1 个配置文件。 + +```bash +. +├── config-advanced.ini +├── config-2G.ini # 高级版和体验版配置范例,轻微修改了 `config.ini` +├── config.ini # 基础配置范例,包含算法所有选项和参数 +.. +├── huixiangdou # python module +.. +├── requirements-lark-group.txt # 集成飞书群才需要的依赖 +├── requirements.txt # 基础依赖 +``` + +配置实际是 toml 格式,为了避免用户觉得陌生,改名 windows 常见的 .ini + +## 第二层:huixiangdou module + +module 内只有 3 个部分: + +```bash +. +├── frontend # 飞书、微信这些,都是茴香豆算法的前端 +├── main.py # main 提供示例程序 +├── service # service 就是算法实现 +``` + +**service** 我们在[论文](https://arxiv.org/abs/2401.08772)里介绍豆哥是套 pipeline。在实现里,可能包含函数、本地 LLM 或者 RPC。把这些基础能力都视做 service。 + +**frontend** 既然豆哥是套算法 pipeline,那么微信、飞书、web 这些,都是它的前端。这个目录放调用前端的工具类和函数,目前里面是飞书的 API 用法 + +**main.py** 现在有算法、有前端,需要个入口函数实现业务逻辑。你在 `config.ini` 配置了飞书,就应该发给飞书 qaq + +## 第三层:service + +这里是 HuixiangDou 算法主体。 + +```bash +. +├── feature_store.py # 管理文本特征的建立和查询,未来会把 “建立” 和 “查询” 分开 +├── helper.py # 放一些辅助工具 +├── llm_client.py # LLM 可能是个 RPC,所以需要个 client +├── llm_server_hybrid.py # LLM 可能不止一个,所以是 hybrid +├── sg_search.py # sourcegraph 客户端 +├── web_search.py # google search 的客户端 +└── worker.py # 论文所说的主逻辑,调用上面的组件 +``` + +**1. feature_store.py** 人脸识别时代,面部特征的存储和检索叫 feature_store,这是名字来源。 + +1. 提取特征时,会花式分割文本(构造技巧会影响精度)、text2vec 模型提取特征、保存到本地; +2. 检索时,除了直接用 text2vec 匹配,还会 rerank 模型调整顺序 + +feature_store 在整个 pipeline 里仅仅是 “引路牌” 的作用,并不依赖 chunk 作答。 + +**2. llm 部分** 包含了 client 和 server_hybrid,是因为: + +1. 模型可能部署在本地,也可以是 openai 接口 +2. 每个模型的特色不同,我们既希望便宜,又不想少功能。所以就是“按需调用”,因此得名 `hybrid` +3. 同样的 `topk=1` 功能,但每家的配置参数不一样,主逻辑不管这些细节,都实现在 `llm_server_hybrid.py` + +**3. 搜索** LLM 作答总需要个 ground truth,如果知识库提供不了,依赖搜索引擎是个不错的选择。这里的特色是:**反复检查网络结果的质量**。网上的信息大都有害,不过滤就直接用,轻则回答错误;重则价值观问题 + +**4. 搜索增强 sg_search** 引擎的搜索能力有限,而浩瀚知识是无限的,“海底捞针” 当然艰难。 + +但如果事先知道答案就在某个 repo 里,只搜那个 repo,必然能提升精度。 `sg_search` 就是针对一个小 repo 调用知识图谱找答案,“大炮打蚊子” 往往效果不错。 + +**5. 主逻辑 worker.py** 在多个微信群运行半年,被“攻击”数万次后,总结出的一套 pipeline diff --git a/repodir/huixiangdou/docs/zh/doc_full_dev.md b/repodir/huixiangdou/docs/zh/doc_full_dev.md new file mode 100644 index 00000000..31b41c75 --- /dev/null +++ b/repodir/huixiangdou/docs/zh/doc_full_dev.md @@ -0,0 +1,78 @@ +# 高精度配置参考 + +标准版可能效果不佳,可开启以下特性来提升效果。配置模板请参照 [config-advanced.ini](../../config-advanced.ini) + +1. 使用更高精度 local LLM + + 把 config.ini 中的`llm.local` 模型调整为其他 LLM,参考 [opencompass 评测榜单](https://rank.opencompass.org.cn/leaderboard-llm)。 + 此选项效果显著。 + +2. Hybrid LLM Service + + 对于支持 [openai](https://pypi.org/project/openai/) 接口的 LLM 服务,茴香豆可以发挥它的 Long Context 能力。 + 以 [kimi](https://platform.moonshot.cn/) 为例,以下是 `config.ini` 配置示例: + + ```ini + # config.ini + [llm] + enable_local = 1 + enable_remote = 1 + .. + [llm.server] + .. + # open https://platform.moonshot.cn/ + remote_type = "kimi" + remote_api_key = "YOUR-KIMI-API-KEY" + remote_llm_max_text_length = 128000 + remote_llm_model = "auto" + ``` + + 注意此特性会增加响应耗时和运行成本。 + +3. repo 搜索增强 + + 此特性适合处理疑难问题,需要基础开发能力调整 prompt。 + + - 点击 [sourcegraph-account-access](https://sourcegraph.com/users/tpoisonooo/settings/tokens) 获取 token + + ```shell + # open https://github.com/sourcegraph/src-cli#installation + sudo curl -L https://sourcegraph.com/.api/src-cli/src_linux_amd64 -o /usr/local/bin/src && chmod +x /usr/local/bin/src + + # 开启 sg 搜索,并且把 token 填入 config.ini + [worker] + enable_sg_search = 1 # first enable sg search + .. + [sg_search] + .. + src_access_token = "${YOUR_ACCESS_TOKEN}" + ``` + + - 编辑 repo 的名字和简介,我们以 opencompass 为例 + + ```ini + # config.ini + # add your repo here, we just take opencompass and lmdeploy as example + [sg_search.opencompass] + github_repo_id = "open-compass/opencompass" + introduction = "用于评测大型语言模型(LLM).." + ``` + + - 使用 `python3 -m huixiangdou.service.sg_search` 单测,返回内容应包含 opencompass 源码和文档 + + ```shell + python3 -m huixiangdou.service.sg_search + .. + "filepath": "opencompass/datasets/longbench/longbench_trivia_qa.py", + "content": "from datasets import Dataset.. + ``` + + 运行 `main.py`,茴香豆将在合适的时机,启用搜索增强。 + +4. 调参 + + 针对业务场景调参往往不可避免。 + + - 参照 [data.json](../../tests/data.json) 增加真实数据,运行 [test_intention_prompt.py](../../tests/test_intention_prompt.py) 得到合适的 prompt 和阈值,更新进 [prompt.py](../../huixiangdou/service/prompt.py) + - 根据模型支持的最大长度,调整[搜索结果个数](../../huixiangdou/service/serial_pipeline.py) + - 按照场景偏好,修改 config.ini 中的 `web_search.domain_partial_order`,即搜索结果偏序 diff --git a/repodir/huixiangdou/docs/zh/doc_knowledge_graph.md b/repodir/huixiangdou/docs/zh/doc_knowledge_graph.md new file mode 100644 index 00000000..1d64be0e --- /dev/null +++ b/repodir/huixiangdou/docs/zh/doc_knowledge_graph.md @@ -0,0 +1,63 @@ +# 混合知识图谱和稠密检索 + +通过混合知识图谱和稠密检索,拒答 F1 提升约 2 个点,它的本质是**给高频词加权**。介绍已同步到[飞书](https://aicarrier.feishu.cn/docx/F51pduYyMof8syxKe5RchiU1nIN) 和[知乎](https://zhuanlan.zhihu.com/p/709589834)。 + +本方案对老版本完美兼容,以下是完整操作步骤。 + +## 一、建立知识图谱 + +为降低成本,我们使用 silicon cloud qwen-1.5-110B 提取实体词, `config.ini` 已支持 silicon cloud,修改片段如下: + +```bash +[llm.server] +.. +remote_type = "siliconcloud" +remote_api_key = "sk-ducerXXXXX" +remote_llm_max_text_length = 40000 +remote_llm_model = "alibaba/Qwen1.5-110B-Chat" +rpm = 1000 +``` + +假设知识库仍在 repodir 目录下,先建立知识图谱。 +完成后, `workdir/kg` 下有 jsonl 和 pickle 文件,可简单测试 query 效果 + +```bash +# 大约 2 小时 +python3 -m huixiangdou.service.kg --build +python3 -m huixiangdou.service.kg --query 如何安装mmpose? +.. ++-----------------+-------+------------------------+---------------------------+ +| Query | State | Part of Reply | References | ++=================+=======+========================+===========================+ +| 如何安装mmpose? | 0 | repodir/mmpose/READM.. | | +| | | |
| +| | | | diff --git a/repodir/huixiangdou/docs/zh/doctuils.conf b/repodir/huixiangdou/docs/zh/doctuils.conf new file mode 100644 index 00000000..0c00c846 --- /dev/null +++ b/repodir/huixiangdou/docs/zh/doctuils.conf @@ -0,0 +1,2 @@ +[html writers] +table_style: colwidths-auto diff --git a/repodir/huixiangdou/docs/zh/index.rst b/repodir/huixiangdou/docs/zh/index.rst new file mode 100644 index 00000000..56187e2c --- /dev/null +++ b/repodir/huixiangdou/docs/zh/index.rst @@ -0,0 +1,47 @@ +欢迎来到 HuixiangDou 进阶说明! +========================================== + +HuixiangDou 上手路线 +------------------------------- + +我们推荐以下流程: + +1. 按照 README 运行基础版本 +2. 参考进阶教程,提升整体效果 + +我们非常欢迎用户的 PR 和 Issue ! + +.. _快速运行: +.. toctree:: + :maxdepth: 1 + :caption: 基础入门 + + copy_quickstart.md + +.. _进阶参考: +.. toctree:: + :maxdepth: 1 + :caption: 配置说明 + + copy_precision.md + doc_full_dev.md + doc_knowledge_graph.md + doc_rag_annotate_sft_data.md + doc_architecture.md + +.. _接入即时通讯软件: +.. toctree:: + :maxdepth: 1 + :caption: 接入即时通讯软件 + + doc_add_wechat_accessibility.md + doc_add_wechat_commercial.md + doc_add_wechat_group.md + doc_add_lark_group.md + doc_send_only_lark_group.md + +索引与表格 +================== + +* :ref:`genindex` +* :ref:`search` diff --git a/repodir/huixiangdou/evaluation/README.md b/repodir/huixiangdou/evaluation/README.md new file mode 100644 index 00000000..52882594 --- /dev/null +++ b/repodir/huixiangdou/evaluation/README.md @@ -0,0 +1,88 @@ +# Precision Result + +## Rejection + +Based on this test, the upper and lower bounds of the chunksize in the text2vec model were obtained. See [how_to_choose_chunksize_and_splitter_for_text2vec](https://www.reddit.com/r/LocalLLaMA/comments/1dkuj80/how_to_choose_chunksize_and_splitter_for_text2vec/). + +### **1.1 Data Description** + +The knowledge base used consists of all markdown, txt, and pdf documents from 9 repositories related to openmmlab. + +A total of 1150 documents were accumulated. The mean document length is 5063; the median length is 2925. + +That is, the document parts of the following repositories were used as the knowledge base: + +```bash +git clone https://github.com/open-compass/opencompass --depth=1 +git clone https://github.com/open-mmlab/mmpose --depth=1 +git clone https://github.com/open-mmlab/mmdeploy --depth=1 +git clone https://github.com/open-mmlab/mmdetection --depth=1 +git clone https://github.com/internlm/lmdeploy --depth=1 +git clone https://github.com/internlm/huixiangdou --depth=1 +git clone https://github.com/internlm/xtuner --depth=1 +git clone https://github.com/open-mmlab/mmyolo --depth=1 +git clone https://github.com/open-mmlab/mmcv --depth=1 +``` + +The queries come from the openmmlab user community and the ncnn developer community, with a total of 2302 questions. Under manual annotation, it was determined whether the questions are relevant to the knowledge base. The data can be seen in [Positive Examples](https://github.com/tpoisonooo/huixiangdou-evaluation-results/blob/main/rejection/gt_good.txt) and [Negative Examples](https://github.com/tpoisonooo/huixiangdou-evaluation-results/blob/main/rejection/gt_bad.txt). + +### **1.2 Test Method** + +Fill the positive and negative examples into `gt_bad.txt` and `gt_good.txt`. Execute: + +``` +python3 evaluation/rejection/build_fs_and_filter.py +``` + +This script will open debug mode and count the length after tokenization. + +To match the token length exactly with the model (e.g., 512), adjust the chunksize parameter yourself. + +``` +# build_fs_and_filter.py +# Change to the desired length, such as 1240. +calculate(1240) + +# Supports multi-process testing to improve efficiency +pool = NestablePool(6) +result = pool.map(calculate, range(128, 512, 32)) +pool.close() +pool.join() +print(result) +``` + +Use `python3 plot.py` to plot the F1 under different chunksizes and throttles. An example of the results is shown below: + + + +### **1.3 Test Conclusion** + +For bce-embedding-base_v1 + +- The chunksize range should be (512, 1500) +- The best F1@throttle obtained on the right value is 75.39@0.41 +- When chunksize is taken as 640, F1 can reach 75.88 + +For bge-large-zh-v1.5 + +- The chunksize range should be (423, 1240) +- The compression rate of embedding.tokenzier is slightly lower +- The best F1@throttle obtained on the right value is 72.23@0.34 + +The basis for choosing splitter is: + +- Chinese priority `ChineseTextSplitter`, which will result in centrifugal values +- English `langchain.RecursiveTextSplitter`, which cuts Chinese corpus more finely but does not have centrifugal values +- `CharacterTextSplitter` does not actually slice and should be avoided for direct use + +### **1.4 Comparison of Approaches** + +We also compared other methods and models. + +| Approach | F1 score | Description | +| :---------------: | :------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| bce+knowlegegraph | 77.57 | The weight of the knowledge graph is 20% | +| bce | 75.88 | Tested using [bce-embedding-base_v1](https://github.com/netease-youdao/BCEmbedding) in conjunction with a specific splitter. Note that inappropriate splitter and distance_strategy can severely affect the accuracy | +| bge-v1.5-large | 72.23 | Tested using [bge-large-zh-v1.5](https://github.com/FlagOpen/FlagEmbedding) | +| bge-m3 | 70.62 | Tested using [m3](https://github.com/FlagOpen/FlagEmbedding) for dense retrieval. Note that m3 has a maximum input token length of 8192, and the test data cannot fully utilize the model's encoding capability | +| hybrid search | 63.85 | Tested [m3](https://github.com/FlagOpen/FlagEmbedding) dense + sparse retrieval rejection effects based on [milvus WeightedRanker](https://github.com/milvus-io/milvus) | diff --git a/repodir/huixiangdou/evaluation/README_zh.md b/repodir/huixiangdou/evaluation/README_zh.md new file mode 100644 index 00000000..309f0ea5 --- /dev/null +++ b/repodir/huixiangdou/evaluation/README_zh.md @@ -0,0 +1,88 @@ +# 精度测试 + +## Rejection + +通过本测试,得到了 text2vec 模型中 chunksize 上下界。见[text2vec 如何选择 chunksize 和 splitter](https://zhuanlan.zhihu.com/p/704311157)。 + +### **1.1 数据说明** + +使用的知识库是 openmmlab 相关的 9 个 repo 中的所有 markdown、txt 和 pdf 文档。 + +累计 1150 个。文档长度均值 5063;长度中位数 2925。 + +即以下 repo 的文档部分做知识库: + +```bash +git clone https://github.com/open-compass/opencompass --depth=1 +git clone https://github.com/open-mmlab/mmpose --depth=1 +git clone https://github.com/open-mmlab/mmdeploy --depth=1 +git clone https://github.com/open-mmlab/mmdetection --depth=1 +git clone https://github.com/internlm/lmdeploy --depth=1 +git clone https://github.com/internlm/huixiangdou --depth=1 +git clone https://github.com/internlm/xtuner --depth=1 +git clone https://github.com/open-mmlab/mmyolo --depth=1 +git clone https://github.com/open-mmlab/mmcv --depth=1 +``` + +query 来自 openmmlab 用户群和 ncnn 开发者群,累计 2302 条问题。通过人工标注,判定问题与知识库是否相关。数据见 [正例](https://github.com/tpoisonooo/huixiangdou-evaluation-results/blob/main/rejection/gt_good.txt) 和 [负例](https://github.com/tpoisonooo/huixiangdou-evaluation-results/blob/main/rejection/gt_bad.txt)。 + +### **1.2 测试方法** + +把正反例填进 `gt_bad.txt` 和 `gt_good.txt`。执行: + +``` +python3 evaluation/rejection/build_fs_and_filter.py +``` + +这个代码会打开调试模式,统计 tokenize 后的长度。 + +为了让 token 长度刚好和模型匹配(如 512),自行调整 chunksize 参数。 + +``` +# build_fs_and_filter.py +# 改成希望的长度,如 1240。 +calculate(1240) + +# 支持多进程测试,提高效率 +pool = NestablePool(6) +result = pool.map(calculate, range(128, 512, 32)) +pool.close() +pool.join() +print(result) +``` + +使用 `python3 plot.py` 绘制不同 chunksize 和 throttle 下的 F1。结果样例: + + + +### **1.3 测试结论** + +对 bce-embedding-base_v1 + +- chunksize 范围应在 (512, 1500) +- 右值取到的最佳 F1@throttle 为 75.39@0.41 +- chunksize 取 640 时,F1 可达到 75.88 + +对 bge-large-zh-v1.5 + +- chunksize 范围应在 (423, 1240) +- embedding.tokenzier 的压缩率略低 +- 右值取到的最佳 F1@throttle 为 72.23@0.34 + +splitter 选择依据 + +- 中文优先 `ChineseTextSplitter`,会出现离心值 +- 英文 `langchain.RecursiveTextSplitter`,切中文语料更碎但没有离心值 +- `CharacterTextSplitter` 实际没切片作用,避免直接用 + +### **1.4 方案对比** + +我们也对比了其他办法和模型。 + +| 方案 | F1 score | 说明 | +| :-----------------: | :------: | :-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| bce+knowlegegraph | 77.57 | 知识图谱权重占比 20% | +| bce | 75.88 | 使用 [bce-embedding-base_v1](https://github.com/netease-youdao/BCEmbedding) 配合特定 splitter 测试。注意不合适的 splitter 和 distance_strategy 会严重影响精度 | +| bge-v1.5-large | 72.23 | 使用 [bge-large-zh-v1.5](https://github.com/FlagOpen/FlagEmbedding) 测试 | +| bge-m3 | 70.62 | 使用 [m3](https://github.com/FlagOpen/FlagEmbedding) dense retrieval。注意 m3 最大输入 token 长度 8192,测试数据无法完整发挥模型编码能力 | +| bge-m3 dense+sparse | 63.85 | 基于 [milvus WeightedRanker](https://github.com/milvus-io/milvus) 测试 [m3](https://github.com/FlagOpen/FlagEmbedding) dense+sparse retrieval 拒答效果。sparse 占比越高效果越差 | diff --git a/repodir/huixiangdou/evaluation/rejection/build_fs_and_filter.py b/repodir/huixiangdou/evaluation/rejection/build_fs_and_filter.py new file mode 100644 index 00000000..6a4ad25d --- /dev/null +++ b/repodir/huixiangdou/evaluation/rejection/build_fs_and_filter.py @@ -0,0 +1,323 @@ +import argparse +import json +import multiprocessing +import os +import os.path as osp +import pdb +from multiprocessing import Pool, Process + +from loguru import logger +from sklearn.metrics import f1_score, precision_score, recall_score +from tqdm import tqdm + +from huixiangdou.service import CacheRetriever, FeatureStore +from huixiangdou.primitive import FileOperation +save_hardcase = False + + +class KnowledgeGraphScore(): + + def __init__(self, level: int): + outpath = os.path.join(os.path.dirname(__file__), 'out.jsonl') + self.scores = dict() + with open(outpath) as f: + for line in f: + json_obj = json.loads(line) + query = json_obj['query'] + + score = 0.0 + result = json_obj['result'] + if result is not None and len(result) > level: + score = min(100, len(result) - level) / 100 + print('query cnt score {} {} {}'.format( + query, len(result), score)) + self.scores[query] = score + + def evaluate(self, query: str): + return self.scores[query] + + +class NoDaemonProcess(multiprocessing.Process): + + @property + def daemon(self): + return False + + @daemon.setter + def daemon(self, value): + pass + + +class NoDaemonContext(type(multiprocessing.get_context())): + Process = NoDaemonProcess + + +# We sub-class multiprocessing.pool.Pool instead of multiprocessing.Pool +# because the latter is only a wrapper function, not a proper class. +class NestablePool(multiprocessing.pool.Pool): + + def __init__(self, *args, **kwargs): + kwargs['context'] = NoDaemonContext() + super(NestablePool, self).__init__(*args, **kwargs) + + +def parse_args(): + """Parse command-line arguments.""" + parser = argparse.ArgumentParser( + description='Feature store for processing directories.') + parser.add_argument('--work_dir_base', + type=str, + default='workdir basename', + help='Working directory.') + parser.add_argument( + '--repo_dir', + type=str, + default='repodir', + help='Root directory where the repositories are located.') + parser.add_argument( + '--config_path', + default='config.ini', + help='Feature store configuration path. Default value is config.ini') + parser.add_argument('--chunk-size', default=768, help='Text chunksize') + parser.add_argument( + '--hybrid', + default=False, + help='Combine knowledge graph evaluation and dense feature score') + args = parser.parse_args() + return args + + +def load_dataset(): + text_labels = [] + with open(osp.join(osp.dirname(__file__), 'gt_good.txt')) as f: + for line in f: + text_labels.append((line, True)) + + with open(osp.join(osp.dirname(__file__), 'gt_bad.txt')) as f: + for line in f: + # rejection + text_labels.append((line, False)) + + return text_labels + + +# def calculate_with_kg(chunk_size: int): + +# config_path = 'config.ini' +# repo_dir = 'repodir' +# work_dir_base = 'workdir' +# work_dir = work_dir_base + str(chunk_size) +# if not os.path.exists(work_dir): +# os.makedirs(work_dir) + +# # export PYTHONWARNINGS=ignore +# text_labels = load_dataset() + +# # 按不同 chunk_size 和 chunk_size,构建特征库 +# # 读 input.jsonl 计算 F1 +# cache = CacheRetriever(config_path=config_path) +# fs_init = FeatureStore(embedder=cache.embedder, +# config_path=config_path, +# chunk_size=chunk_size, +# analyze_reject=True, +# rejecter_naive_splitter=True) + +# # walk all files in repo dir +# file_opr = FileOperation() +# files = file_opr.scan_dir(repo_dir=repo_dir) +# fs_init.preprocess(files=files, work_dir=work_dir) +# fs_init.build_dense(files=files, work_dir=work_dir) +# del fs_init + +# retriever = CacheRetriever(config_path=config_path).get( +# fs_id=str(chunk_size), work_dir=work_dir) +# start = 0.4 +# stop = 0.5 +# step = 0.01 +# throttles = [ +# round(start + step * i, 4) +# for i in range(int((stop - start) / step) + 1) +# ] + +# # start = 0.3 +# # stop = 0.5 +# # step = 0.01 +# # throttles = [ +# # round(start + step * i, 4) +# # for i in range(int((stop - start) / step) + 1) +# # ] + +# best_chunk_f1 = 0.0 +# best_level = 5 + +# for level in range(0, 50, 5): +# kg_score = KnowledgeGraphScore(level=level) +# for i in range(1, 4, 1): +# scale = i * 0.1 +# for throttle in tqdm(throttles): +# retriever.reject_throttle = throttle + +# dts = [] +# gts = [] +# for text_label in text_labels: +# question = text_label[0] + +# retriever.reject_throttle = max( +# 0.0, +# throttle - scale * kg_score.evaluate(query=question)) +# dt = retriever.is_relative(query=question, +# enable_kg=True) +# dts.append(dt) +# gts.append(text_label[1]) + +# if save_hardcase and dt != text_label[1]: +# docs = retriever.compression_retriever.get_relevant_documents( +# question) +# if len(docs) > 0: +# doc = docs[0] +# question = question.replace('\n', ' ') +# content = '{} {}'.format(question, doc) +# with open('hardcase{}.txt'.format(throttle), +# 'a') as f: +# f.write(content) +# f.write('\n') + +# f1 = f1_score(gts, dts) +# f1 = round(f1, 4) +# precision = precision_score(gts, dts) +# precision = round(precision, 4) +# recall = recall_score(gts, dts) +# recall = round(recall, 4) + +# logger.info((throttle, precision, recall, f1)) + +# data = { +# 'chunk_size': chunk_size, +# 'throttle': throttle, +# 'precision': precision, +# 'recall': recall, +# 'f1': f1 +# } +# json_str = json.dumps(data) +# with open( +# osp.join( +# osp.dirname(__file__), +# 'level{}_scale{}_chunk{}.jsonl'.format( +# level, scale, chunk_size)), 'a') as f: +# f.write(json_str) +# f.write('\n') + +# if f1 > best_chunk_f1: +# best_chunk_f1 = f1 +# best_level = level +# print(best_chunk_f1, best_level) +# return best_chunk_f1 + + +def calculate(chunk_size: int): + config_path = 'config.ini' + repo_dir = 'repodir' + work_dir_base = 'workdir' + work_dir = work_dir_base + str(chunk_size) + if not os.path.exists(work_dir): + os.makedirs(work_dir) + + # export PYTHONWARNINGS=ignore + text_labels = load_dataset() + + # 按不同 chunk_size 和 chunk_size,构建特征库 + # 读 input.jsonl 计算 F1 + cache = CacheRetriever(config_path=config_path) + fs_init = FeatureStore(embedder=cache.embedder, + config_path=config_path, + chunk_size=chunk_size) + + # walk all files in repo dir + file_opr = FileOperation() + files = file_opr.scan_dir(repo_dir=repo_dir) + fs_init.preprocess(files=files, work_dir=work_dir) + fs_init.build_dense(files=files, work_dir=work_dir, markdown_as_txt=True) + del fs_init + + retriever = CacheRetriever(config_path=config_path).get( + fs_id=str(chunk_size), work_dir=work_dir) + start = 0.41 + stop = 0.50 + step = 0.01 + throttles = [ + round(start + step * i, 4) + for i in range(int((stop - start) / step) + 1) + ] + + # start = 0.3 + # stop = 0.5 + # step = 0.01 + # throttles = [ + # round(start + step * i, 4) + # for i in range(int((stop - start) / step) + 1) + # ] + + best_chunk_f1 = 0.0 + + for throttle in tqdm(throttles): + retriever.reject_throttle = throttle + + dts = [] + gts = [] + for text_label in text_labels: + question = text_label[0] + + retriever.reject_throttle = throttle + _, score = retriever.is_relative(query=question, + enable_kg=False, enable_threshold=False) + + if score >= throttle: + dts.append(True) + else: + dts.append(False) + gts.append(text_label[1]) + + f1 = f1_score(gts, dts) + f1 = round(f1, 4) + precision = precision_score(gts, dts) + precision = round(precision, 4) + recall = recall_score(gts, dts) + recall = round(recall, 4) + + logger.info((throttle, precision, recall, f1)) + + data = { + 'chunk_size': chunk_size, + 'throttle': throttle, + 'precision': precision, + 'recall': recall, + 'f1': f1 + } + json_str = json.dumps(data) + with open( + osp.join( + osp.dirname(__file__), + 'chunk{}.jsonl'.format(chunk_size)), 'a') as f: + f.write(json_str) + f.write('\n') + + if f1 > best_chunk_f1: + best_chunk_f1 = f1 + print(best_chunk_f1) + return best_chunk_f1 + +def main(): + args = parse_args() + best_f1 = 0.0 + best_chunk_size = -1 + + calculate(832) + # pool = NestablePool(6) + # result = pool.map(calculate, range(128, 512, 32)) + # pool.close() + # pool.join() + # print(result) + + +if __name__ == '__main__': + main() diff --git a/repodir/huixiangdou/evaluation/rejection/gt_bad.txt b/repodir/huixiangdou/evaluation/rejection/gt_bad.txt new file mode 100644 index 00000000..cff6ac9a --- /dev/null +++ b/repodir/huixiangdou/evaluation/rejection/gt_bad.txt @@ -0,0 +1 @@ +对你课题的目标定义一下就可以了 \ No newline at end of file diff --git a/repodir/huixiangdou/evaluation/rejection/gt_good.txt b/repodir/huixiangdou/evaluation/rejection/gt_good.txt new file mode 100644 index 00000000..278bb9de --- /dev/null +++ b/repodir/huixiangdou/evaluation/rejection/gt_good.txt @@ -0,0 +1 @@ +大佬们,请问如何安装mmcv? \ No newline at end of file diff --git a/repodir/huixiangdou/evaluation/rejection/kg_filter.py b/repodir/huixiangdou/evaluation/rejection/kg_filter.py new file mode 100644 index 00000000..557339cd --- /dev/null +++ b/repodir/huixiangdou/evaluation/rejection/kg_filter.py @@ -0,0 +1,108 @@ +import argparse +import json +import multiprocessing +import os +import os.path as osp +import pdb +from multiprocessing import Pool, Process + +from loguru import logger +from sklearn.metrics import f1_score, precision_score, recall_score +from tqdm import tqdm + +from huixiangdou.service import KnowledgeGraph, histogram, start_llm_server + + +def load_dataset(): + text_labels = [] + with open(osp.join(osp.dirname(__file__), 'gt_good.txt')) as f: + for line in f: + text_labels.append((line, True)) + + with open(osp.join(osp.dirname(__file__), 'gt_bad.txt')) as f: + for line in f: + # rejection + text_labels.append((line, False)) + + return text_labels + + +def calculate(config_path: str = 'config.ini'): + kg = KnowledgeGraph(config_path=config_path, override=False) + G = kg.load_networkx() + if not G: + logger.error('Knowledge graph not build, quit.') + return + text_labels = load_dataset() + + outpath = os.path.join(os.path.dirname(__file__), 'out.jsonl') + for text, label in tqdm(text_labels): + result = kg.retrieve(G=G, query=text) + json_str = json.dumps({ + 'query': text, + 'result': result, + 'gt': label + }, + ensure_ascii=False) + with open(outpath, 'a') as f: + f.write(json_str) + f.write('\n') + + +def summarize(): + outpath = os.path.join(os.path.dirname(__file__), 'out.jsonl') + + for throttle in range(0, 40, 5): + dts = [] + gts = [] + max_ref_cnts = [] + with open(outpath) as f: + for line in f: + json_obj = json.loads(line) + gts.append(json_obj['gt']) + if json_obj['result'] is None: + dts.append(False) + # max_ref_cnts.append(0) + elif len(json_obj['result']) <= throttle: + dts.append(False) + # max_ref_cnts.append(0) + else: + dts.append(True) + max_ref_cnts.append(len(json_obj['result'])) + + # logger.info(histogram(max_ref_cnts)) + f1 = f1_score(gts, dts) + f1 = round(f1, 2) + precision = precision_score(gts, dts) + precision = round(precision, 2) + recall = recall_score(gts, dts) + recall = round(recall, 2) + + logger.info(('throttle, precision, recall, F1', throttle, precision, + recall, f1)) + + +def parse_args(): + parser = argparse.ArgumentParser( + description='Knowledge graph for processing directories.') + parser.add_argument('--config_path', + default='config-kg.ini', + help='Configuration path. Default value is config.ini') + parser.add_argument('--retrieve', + default=False, + help='Retrieve result from knowledge graph.') + args = parser.parse_args() + return args + + +def main(): + args = parse_args() + if args.retrieve: + start_llm_server(args.config_path) + calculate(args.config_path) + else: + summarize() + + +if __name__ == '__main__': + main() diff --git a/repodir/huixiangdou/evaluation/rejection/plot.py b/repodir/huixiangdou/evaluation/rejection/plot.py new file mode 100644 index 00000000..2f6a9091 --- /dev/null +++ b/repodir/huixiangdou/evaluation/rejection/plot.py @@ -0,0 +1,105 @@ +import json +import os + +import matplotlib.pyplot as plt +from mpl_toolkits.mplot3d import Axes3D + + +def plot_3d(): + fig = plt.figure() + ax = fig.add_subplot(111, projection='3d') + for jsonl_file in os.listdir('./'): + + if not jsonl_file.endswith('.jsonl'): + continue + + if not 'chunk_size' in jsonl_file: + continue + + x = [] + y = [] + z = [] + print(jsonl_file) + + datas = [] + with open(jsonl_file) as f: + for json_str in f: + json_obj = json.loads(json_str) + datas.append(json_obj) + + datas.sort(key=lambda x: x['throttle']) + + for data in datas: + chunk_size = data['chunk_size'] + throttle = data['throttle'] + f1 = data['f1'] + + x.append(chunk_size) + y.append(throttle) + z.append(f1) + + # 绘制3D曲线 + ax.plot(x, y, z) + + # 添加标题和标签 + ax.set_title('3D Line Plot') + ax.set_xlabel('chunk_size') + ax.set_ylabel('throttle') + ax.set_zlabel('f1') + + # 显示图形 + plt.show() + + +def plot_cross_splitter(): + fig = plt.figure() + for splitter in os.listdir('./'): + + if not splitter.startswith('chunk_size'): + continue + + if not os.path.isdir(splitter): + continue + + items = [] + for jsonl_file in os.listdir(splitter): + if not 'chunk_size' in jsonl_file: + continue + + print(splitter, jsonl_file) + datas = [] + + with open(os.path.join(splitter, jsonl_file)) as f: + for json_str in f: + json_obj = json.loads(json_str) + datas.append(json_obj) + datas.sort(key=lambda x: x['f1']) + + items.append({ + 'chunk_size': datas[-1]['chunk_size'], + 'f1': datas[-1]['f1'] + }) + + items.sort(key=lambda x: x['chunk_size']) + x = [] + y = [] + for item in items: + if item['chunk_size'] > 1000: + continue + x.append(item['chunk_size']) + y.append(item['f1']) + print(x, y) + # 绘制曲线 + label_name = splitter.split('chunk_size_')[-1] + plt.plot(x, y, label=label_name) + + # 添加标题和标签 + plt.xlabel('chunk_size') + plt.ylabel('best_f1') + plt.legend() + # 显示图形 + plt.show() + + +if __name__ == '__main__': + plot_3d() diff --git a/repodir/huixiangdou/evaluation/rejection/plot_example.png b/repodir/huixiangdou/evaluation/rejection/plot_example.png new file mode 100644 index 0000000000000000000000000000000000000000..c5c299640324db3601cea78d066866f0fc5fd513 GIT binary patch literal 202921 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 Xc)%&5^BYqD%<;M6g#Xh9UmnxRgaZjU3Y=` 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%0D}sbA3X<;C7E`625U>FEI| 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 literal 0 HcmV?d00001 diff --git a/repodir/huixiangdou/evaluation/rerank/step0_clean_queries.py b/repodir/huixiangdou/evaluation/rerank/step0_clean_queries.py new file mode 100644 index 00000000..cbbb807a --- /dev/null +++ b/repodir/huixiangdou/evaluation/rerank/step0_clean_queries.py @@ -0,0 +1,56 @@ +import json +import os +import re + +from loguru import logger + +pattern = re.compile(r'^[A-Za-z0-9]+$') + +pwd = os.path.dirname(__file__) +query_log = os.path.join(pwd, '..', 'query.log') + + +def save(_id, sentence): + if _id not in queries: + queries[_id] = [sentence] + else: + queries[_id].append(sentence) + + +queries = dict() +with open(query_log) as f: + query = None + + _id = None + sentence = '' + for line in f: + line = line.strip() + if len(line) < 5: + continue + + if line[4] == ' ' and pattern.match( + line[0:4]) and _id is not None and sentence != '': + save(_id, sentence) + _id = line[0:4] + sentence = line[4:] + else: + if line[4] == ' ' and pattern.match(line[0:4]): + _id = line[0:4] + sentence = line[4:] + else: + sentence += '\n' + sentence += line + + save(_id, sentence) + +counter = 0 +for _id in queries: + with open(os.path.join(pwd, '..', 'queries', _id) + '.txt', 'a') as f: + values = map(lambda x: x.strip(), queries[_id]) + values = list(set(values)) + counter += len(values) + json_str = json.dumps(values, ensure_ascii=False) + f.write(r'{}'.format(json_str)) + f.write('\n') + +logger.info(counter) diff --git a/repodir/huixiangdou/evaluation/rerank/step1_create_candidates.py b/repodir/huixiangdou/evaluation/rerank/step1_create_candidates.py new file mode 100644 index 00000000..68e8ca84 --- /dev/null +++ b/repodir/huixiangdou/evaluation/rerank/step1_create_candidates.py @@ -0,0 +1,164 @@ +import argparse +import json +import multiprocessing +import os +import os.path as osp +import pdb +import re +from multiprocessing import Pool, Process + +from loguru import logger +from sklearn.metrics import (f1_score, precision_recall_curve, precision_score, + recall_score) +from tqdm import tqdm + +from huixiangdou.service import (CacheRetriever, FeatureStore, FileOperation, + Retriever) + + +class NoDaemonProcess(multiprocessing.Process): + + @property + def daemon(self): + return False + + @daemon.setter + def daemon(self, value): + pass + + +class NoDaemonContext(type(multiprocessing.get_context())): + Process = NoDaemonProcess + + +# We sub-class multiprocessing.pool.Pool instead of multiprocessing.Pool +# because the latter is only a wrapper function, not a proper class. +class NestablePool(multiprocessing.pool.Pool): + + def __init__(self, *args, **kwargs): + kwargs['context'] = NoDaemonContext() + super(NestablePool, self).__init__(*args, **kwargs) + + +class Record: + + def __init__(self, fsid: str): + self.records = [] + self.fsid = fsid + if os.path.exists('record.txt'): + with open('record.txt') as f: + for line in f: + self.records.append(line.strip()) + + def is_processed(self): + if self.fsid in self.records: + return True + return False + + def mark_as_processed(self): + with open('record.txt', 'a') as f: + f.write(self.fsid) + f.write('\n') + + +def load_queries(fsid: str): + pwd = os.path.dirname(__file__) + base = os.path.join(pwd, '..', 'queries') + query_path = os.path.join(base, fsid + '.txt') + if not os.path.exists(query_path): + return [] + + queries = [] + print(query_path) + if fsid == '0000': + + with open(query_path) as f: + for line in f: + queries.append(line) + return queries + + with open(query_path) as f: + for line in f: + queries = json.loads(line) + break + return queries + + +def process(param: tuple): + fsid, filedir = param + queries = load_queries(fsid=fsid) + if len(queries) < 1: + return + + r = Record(fsid=fsid) + if r.is_processed(): + logger.info('skip {}'.format(fsid)) + return + + config_path = 'config.ini' + cache = CacheRetriever(config_path=config_path) + + fs_init = FeatureStore(embedder=cache.embedder, config_path=config_path) + + file_opr = FileOperation() + files = file_opr.scan_dir(repo_dir=filedir) + work_dir = os.path.join('workdir', fsid) + fs_init.initialize(files=files, work_dir=work_dir) + file_opr.summarize(files) + del fs_init + + retriever = cache.get(config_path=config_path, work_dir=work_dir) + + if not os.path.exists('candidates'): + os.makedirs('candidates') + + for query in queries: + try: + query = query[0:400] + docs = retriever.compression_retriever.get_relevant_documents( + query) + candidates = [] + logger.info('{} docs count {}'.format(fsid, len(docs))) + + for doc in docs: + data = { + 'content': doc.page_content, + 'source': doc.metadata['read'], + 'score': doc.metadata['relevance_score'] + } + candidates.append(data) + + json_str = json.dumps({ + 'query': query, + 'candidates': candidates + }, + ensure_ascii=False) + + with open(os.path.join('candidates', fsid + '.jsonl'), 'a') as f: + f.write(json_str) + f.write('\n') + except Exception as e: + pdb.set_trace() + print(e) + r.mark_as_processed() + + +def main(): + pwd = os.path.dirname(__file__) + base = os.path.join(pwd, '..', 'feature_stores') + dirs = os.listdir(base) + params = [] + import pdb + pdb.set_trace() + for fsid in dirs: + filedir = os.path.join(base, fsid, 'workdir/preprocess') + process((fsid, filedir)) + params.append((fsid, filedir)) + # pool = NestablePool(2) + # result = pool.map(process, params) + # pool.close() + # pool.join() + + +if __name__ == '__main__': + main() diff --git a/repodir/huixiangdou/huixiangdou-inside.md b/repodir/huixiangdou/huixiangdou-inside.md new file mode 100644 index 00000000..a00b6709 --- /dev/null +++ b/repodir/huixiangdou/huixiangdou-inside.md @@ -0,0 +1,7 @@ +# HuixiangDou Inside + +| ID | Environment | IM Application | Description | Screen Shortcut | +| --- | --------------------------- | -------------- | ---------------------------------------------------------------------- | ---------------------------------------------------------------- | +| 1 | openmmlab user group | wechat | reply user question | | +| 2 | ncnn contributor group | wechat | explain software and hardware terminologies and pretending to be human | | +| 3 | inner middleware user group | lark | reply user question | | diff --git a/repodir/huixiangdou/huixiangdou/__init__.py b/repodir/huixiangdou/huixiangdou/__init__.py new file mode 100644 index 00000000..e397d2cb --- /dev/null +++ b/repodir/huixiangdou/huixiangdou/__init__.py @@ -0,0 +1,12 @@ +# Copyright (c) OpenMMLab. All rights reserved. +"""import module.""" +# only import frontend when needed, not here +from .service import ChatClient # noqa E401 +from .service import ErrorCode # noqa E401 +from .service import FeatureStore # noqa E401 +from .service import HybridLLMServer # noqa E401 +from .service import WebSearch # noqa E401 +from .service import SerialPipeline, ParallelPipeline # no E401 +from .service import build_reply_text # noqa E401 +from .service import llm_serve # noqa E401 +from .version import __version__ diff --git a/repodir/huixiangdou/huixiangdou/frontend/__init__.py b/repodir/huixiangdou/huixiangdou/frontend/__init__.py new file mode 100644 index 00000000..de63f8ea --- /dev/null +++ b/repodir/huixiangdou/huixiangdou/frontend/__init__.py @@ -0,0 +1,6 @@ +# Copyright (c) OpenMMLab. All rights reserved. +"""IM proxy.""" +from .lark import Lark # noqa E401 +from .lark_group import is_revert_command # noqa E401 +from .lark_group import revert_from_lark_group, send_to_lark_group # noqa E401 +from .wechat import WkteamManager # noqa E401 diff --git a/repodir/huixiangdou/huixiangdou/frontend/lark.py b/repodir/huixiangdou/huixiangdou/frontend/lark.py new file mode 100644 index 00000000..0d12f5d6 --- /dev/null +++ b/repodir/huixiangdou/huixiangdou/frontend/lark.py @@ -0,0 +1,102 @@ +# Copyright (c) OpenMMLab. All rights reserved. +# copy from https://github.com/tpoisonooo/cpp-syntactic-sugar/blob/master/github-lark-notifier/main.py # noqa E501 +"""Lark proxy.""" +import json +import logging +import time + +import requests +import urllib3 +from loguru import logger + +urllib3.disable_warnings() + + +class Lark: + """Lark bot http proxy.""" + + def __init__(self, + webhook, + secret=None, + pc_slide=False, + fail_notice=False): + """Init with hook url.""" + self.headers = {'Content-Type': 'application/json; charset=utf-8'} + logger.debug(f'webhook {webhook}') + logger.error('This class would be deprecated in 2024.10.10') + self.webhook = webhook + self.secret = secret + self.pc_slide = pc_slide + self.fail_notice = fail_notice + + def is_not_null_and_blank_str(self, content: str): + """Is content empty.""" + if content is not None and content.strip(): + return True + return False + + def send_text(self, msg): + """Send text to hook url.""" + data = {'msg_type': 'text', 'at': {}} + if self.is_not_null_and_blank_str(msg): # 传入msg非空 + data['content'] = {'text': msg} + else: + logging.error('text类型,消息内容不能为空!') + raise ValueError('text类型,消息内容不能为空!') + + logging.debug(f'text类型:{data}') + return self.post(data) + + def post(self, data): + """Post data to hook url.""" + try: + post_data = json.dumps(data) + response = requests.post(self.webhook, + headers=self.headers, + data=post_data, + verify=False, + timeout=60) + except requests.exceptions.HTTPError as exc: + code = exc.response.status_code + reason = exc.response.reason + logging.error(f'消息发送失败, HTTP error: {code}, reason: {reason}') + raise + except requests.exceptions.ConnectionError: + logging.error('消息发送失败,HTTP connection error!') + raise + except requests.exceptions.Timeout: + logging.error('消息发送失败,Timeout error!') + raise + except requests.exceptions.RequestException: + logging.error('消息发送失败, Request Exception!') + raise + try: + result = response.json() + except json.JSONDecodeError: + code = response.status_code + text = response.text + logging.error(f'服务器响应异常,状态码:{code},响应内容:{text}') + return {'errcode': 500, 'errmsg': '服务器响应异常'} + logging.debug('发送结果:%s' % result) + # 消息发送失败提醒(errcode 不为 0,表示消息发送异常),默认不提醒,开发者可以根据返回的消息发送结果自行判断和处理 # noqa E501 + if self.fail_notice and result.get('errcode', True): + time_now = time.strftime('%Y-%m-%d %H:%M:%S', + time.localtime(time.time())) # noqa E501 + reason_text = result['errmsg'] if result.get('errmsg', + False) else '未知异常' + error_data = { + 'msgtype': 'text', + 'text': { + 'content': + f'[注意-自动通知]飞书机器人消息发送失败,时间:{time_now},原因:{reason_text},请及时跟进,谢谢!' # noqa E501 + }, + 'at': { + 'isAtAll': False + } + } + logging.error('消息发送失败,自动通知:%s' % error_data) + requests.post(self.webhook, + headers=self.headers, + data=json.dumps(error_data), + timeout=60) + return result diff --git a/repodir/huixiangdou/huixiangdou/frontend/lark_group.py b/repodir/huixiangdou/huixiangdou/frontend/lark_group.py new file mode 100644 index 00000000..2a55cec3 --- /dev/null +++ b/repodir/huixiangdou/huixiangdou/frontend/lark_group.py @@ -0,0 +1,193 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import argparse +import json + +import lark_oapi as lark +import pytoml +from flask import Flask, jsonify, request +from lark_oapi.adapter.flask import * # noqa E403 +from lark_oapi.api.im.v1 import * # noqa E403 +from loguru import logger + +from huixiangdou.service.helper import Queue + +app = Flask(__name__) +handler = None + + +def is_revert_command(content: str): + if '豆哥撤回' in content: + return True + return False + + +def do_p2_im_message_receive_v1( + data: P2ImMessageReceiveV1) -> None: # noqa E405 + logger.info(lark.JSON.marshal(data)) + # if not group text message, return + if data.header.event_type != 'im.message.receive_v1': + return None + + msg = data.event.message + if msg.chat_type != 'group': + return None + if msg.message_type != 'text': + return None + + msg_id = msg.message_id + group_id = msg.chat_id + user_id = data.event.sender.sender_id.user_id + content = json.loads(msg.content)['text'] + # milliseconds + msg_time = msg.create_time + + json_str = json.dumps( + { + 'source': 'lark', + 'msg_id': msg_id, + 'user_id': user_id, + 'content': content, + 'group_id': group_id, + 'msg_time': msg_time + }, + ensure_ascii=False) + '\n' + + que = None + if is_revert_command(content): + # add to revert queue + que = Queue(name='huixiangdou-high-priority') + else: + que = Queue(name='huixiangdou') + que.put(json_str) + logger.debug(f'save {json_str} to {que.key}') + return None + + +@app.route('/event', methods=['POST']) +def event(): + jsonstr = request.get_json() + if type(jsonstr) is dict: + param = jsonstr + else: + param = json.loads(jsonstr) + logger.debug(param) + + resp = handler.do(parse_req()) # noqa E405 + return parse_resp(resp) # noqa E405 + + +@app.route('/fetch', methods=['POST']) +def fetch(): + revert_que = Queue(name='huixiangdou-high-priority') + json_str = revert_que.get(timeout=1) + if json_str is not None and len(json_str) > 0: + json_obj = json.loads(json_str) + return jsonify(json_obj) + + msg_que = Queue(name='huixiangdou') + json_str = msg_que.get(timeout=3) + if json_str is not None and len(json_str) > 0: + json_obj = json.loads(json_str) + return jsonify(json_obj) + return jsonify({}) + + +def revert_from_lark_group(msg_id: str, app_id: str, app_secret: str): + # 创建client + client = lark.Client.builder() \ + .app_id(app_id) \ + .app_secret(app_secret) \ + .log_level(lark.LogLevel.DEBUG) \ + .build() + + # 构造请求对象 + request: DeleteMessageRequest = DeleteMessageRequest.builder( # noqa E405 + ).message_id( # noqa E405 + msg_id).build() + + # 发起请求 + response: DeleteMessageResponse = client.im.v1.message.delete( # noqa E405 + request) + + # 处理失败返回 + if not response.success(): + lark.logger.error( + f'client.im.v1.message.delete failed, code: {response.code}, msg: {response.msg}, log_id: {response.get_log_id()}' # noqa E501 + ) + return Exception(response.msg) + + return None + + +# SDK 使用说明: https://github.com/larksuite/oapi-sdk-python#readme +def send_to_lark_group(json_obj: dict, app_id: str, app_secret: str): + msg_id = '' + try: + source = json_obj['source'] + if source != 'lark': + return Exception(f'unsupported source {source}') + + # send to lark group + # 创建client + client = lark.Client.builder() \ + .app_id(app_id) \ + .app_secret(app_secret) \ + .log_level(lark.LogLevel.DEBUG) \ + .build() + + text_str = json.dumps({'text': json_obj['reply']}, ensure_ascii=False) + # 构造请求对象 + request: ReplyMessageRequest = ReplyMessageRequest.builder( # noqa E405 + ).message_id(json_obj['msg_id']).request_body( + ReplyMessageRequestBody.builder().content( # noqa E405 + text_str).msg_type( # noqa E405 + 'text').reply_in_thread(True).build()).build() + + # 发起请求 + response: ReplyMessageResponse = client.im.v1.message.reply( # noqa E405 + request) # noqa E405 + + msg_id = response.data.message_id + # 处理失败返回 + if not response.success(): + lark.logger.error( + f'client.im.v1.message.reply failed, code: {response.code}, msg: {response.msg}, log_id: {response.get_log_id()}' # noqa E501 + ) + return None, '' + + # 处理业务结果 + lark.logger.info(lark.JSON.marshal(response.data, indent=2)) + except Exception as e: + return e, '' + return None, msg_id + + +def parse_args(): + """Parse args.""" + parser = argparse.ArgumentParser( + description='Lark group for save group message.') + parser.add_argument( + '--port', + type=int, + default=6666, + help='Listen port for lark group message. Use 6666 by default.') + parser.add_argument( + '--config_path', + default='config.ini', + type=str, + help='Lark group configuration path. Default value is config.ini') + return parser.parse_args() + + +if __name__ == '__main__': + args = parse_args() + + with open(args.config_path, encoding='utf8') as f: + lark_group_config = pytoml.load(f)['frontend']['lark_group'] + + handler = lark.EventDispatcherHandler.builder( + lark_group_config['encrypt_key'], + lark_group_config['verification_token'], + lark.LogLevel.DEBUG).register_p2_im_message_receive_v1( + do_p2_im_message_receive_v1).build() # noqa E501 + app.run(debug=False, host='0.0.0.0', port=int(args.port)) diff --git a/repodir/huixiangdou/huixiangdou/frontend/wechat.py b/repodir/huixiangdou/huixiangdou/frontend/wechat.py new file mode 100644 index 00000000..f2fffc92 --- /dev/null +++ b/repodir/huixiangdou/huixiangdou/frontend/wechat.py @@ -0,0 +1,880 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import argparse +import hashlib +import json +import os +import pdb +import re +import time +import types +import xml.etree.ElementTree as ET +from dataclasses import asdict, dataclass, field +from datetime import datetime +from multiprocessing import Process +from typing import List + +import pytoml +import redis +import requests +from aiohttp import web +from bs4 import BeautifulSoup as BS +from loguru import logger +from readability import Document + + +def redis_host(): + host = os.getenv('REDIS_HOST') + if host is None or len(host) < 1: + raise Exception('REDIS_HOST not config') + return host + + +def redis_port(): + port = os.getenv('REDIS_PORT') + if port is None: + logger.debug('REDIS_PORT not set, try 6379') + port = 6379 + return port + + +def redis_passwd(): + passwd = os.getenv('REDIS_PASSWORD') + if passwd is None or len(passwd) < 1: + raise Exception('REDIS_PASSWORD not config') + return passwd + + +class Queue: + + def __init__(self, name, namespace='HuixiangDou', **redis_kwargs): + self.__db = redis.Redis(host=redis_host(), + port=redis_port(), + password=redis_passwd(), + charset='utf-8', + decode_responses=True) + self.key = '%s:%s' % (namespace, name) + + def qsize(self): + """Return the approximate size of the queue.""" + return self.__db.llen(self.key) + + def empty(self): + """Return True if the queue is empty, False otherwise.""" + return self.qsize() == 0 + + def put(self, item): + """Put item into the queue.""" + self.__db.rpush(self.key, item) + + def peek_tail(self): + return self.__db.lrange(self.key, -1, -1) + + def get(self, block=True, timeout=None): + """Remove and return an item from the queue. + + If optional args block is true and timeout is None (the default), block + if necessary until an item is available. + """ + if block: + item = self.__db.blpop(self.key, timeout=timeout) + else: + item = self.__db.lpop(self.key) + + if item: + item = item[1] + return item + + def get_all(self): + """Get add messages in queue without block.""" + ret = [] + try: + while len(ret) < 1: # batchsize = 1 for debugging + item = self.__db.lpop(self.key) + if not item: + break + ret.append(item) + except Exception as e: + logger.error(str(e)) + return ret + + def get_nowait(self): + """Equivalent to get(False).""" + return self.get(False) + + +def is_revert_command(wx_msg): + """Is wx_msg a revert command.""" + data = wx_msg['data'] + if 'content' not in data: + return False + content = data['content'] + if content is not None and len(content) > 0: + content = content.encode('UTF-8', 'ignore').decode('UTF-8') + messageType = wx_msg['messageType'] + + if '豆哥撤回' in content: + return True + + if messageType == 5 or messageType == 9 or messageType == '80001': + if '撤回' in content: + return True + elif messageType == 14 or messageType == '80014': + # 对于引用消息,如果要求撤回 + if 'title' in data: + if '撤回' in data['title']: + return True + elif '撤回' in content: + return True + return False + + +class Message: + + def __init__(self): + self.data = dict() + self.type = None + self.query = '' + self.group_id = '' + self.global_user_id = '' + self._id = -1 + self.status = '' + self.sender = '' + self.url = '' + + def parse(self, wx_msg: dict, bot_wxid: str): + # str or int + msg_type = wx_msg['messageType'] + parse_type = 'unknown' + if 'data' not in wx_msg: + self.status = 'skip' + return Exception('data not in wx_msg') + + data = wx_msg['data'] + if 'self' in data: + if data['self']: + return Exception('self msg, return') + + if 'msgId' in data: + self._id = data['msgId'] + + # format user input + query = '' + if 'atlist' in data: + atlist = data['atlist'] + if bot_wxid not in atlist: + self.status = 'skip' + return Exception('atlist not contains bot') + + if msg_type in ['80014', '60014']: + # ref message + # 群、私聊引用消息 + query = data['title'] + + root = ET.fromstring(data['content']) + + def search_key(xml_key: str): + elements = root.findall('.//{}'.format(xml_key)) + content = '' + if len(elements) > 0: + content = elements[0].text + return content + + to_user = search_key(xml_key='chatusr') + if to_user != bot_wxid: + parse_type = 'ref_for_others' + self.status = 'skip' + else: + parse_type = 'ref_for_bot' + + elif msg_type in ['80007', '60007', '90001']: + # url message + # 例如公众号文章。尝试解析提取内容,这个行为高概率会被服务器 ban + parse_type = 'link' + + root = ET.fromstring(data['content']) + + def search_key(xml_key: str): + elements = root.findall('.//{}'.format(xml_key)) + content = '' + if len(elements) > 0: + content = elements[0].text + return content + + self.url = search_key(xml_key='url') + title = search_key(xml_key='title') + desc = search_key(xml_key='des') + + query = '' + try: + resp = requests.get(self.url) + doc = Document(resp.text) + soup = BS(doc.summary(), 'html.parser') + + if len(soup.text) > 100: + query = '{}\n{}\n{}'.format(title, desc, soup.text) + else: + query = '{}\n{}\n{}'.format(title, desc, self.url) + except Exception as e: + logger.error(str(e)) + logger.debug('公众号解析:{}'.format(query)[0:256]) + + elif msg_type in ['80002', '60002']: + # image + # 图片消息 + parse_type = 'image' + + elif msg_type in ['80001', '60001']: + # text + # 普通文本消息 + query = data['content'] + parse_type = 'text' + + elif type(msg_type) is int: + logger.warning(wx_msg) + + else: + return Exception('Skip msg type {}'.format(msg_type)) + + query = query.encode('UTF-8', 'ignore').decode('UTF-8') + if query.startswith('@茴香豆'): + query = query.replace('@茴香豆', '') + self.query = query.strip() + + if '————————' in query: + self.status = 'skip' + return Exception("Repo owner's message, skip") + + if 'fromUser' not in data: + self.status = 'skip' + return Exception('msg no sender id, skip') + + self.sender = data['fromUser'] + self.data = data + self.type = parse_type + if 'fromGroup' not in data: + return Exception('GroupID not found in message') + + self.group_id = data['fromGroup'] + self.global_user_id = '{}|{}'.format(self.group_id, data['fromUser']) + return None + + +def empty_list(): + return [] + + +@dataclass +class Talk: + query: str + reply: str = '' + refs: tuple = () + now: float = field(default_factory=time.time) + + +def convert_talk_to_dict(talk: Talk): + return { + 'query': talk.query, + 'reply': talk.reply, + 'refs': talk.refs, + 'now': talk.now + } + + +def convert_history_to_tuple(history: List[Talk]): + return [(item.query, item.reply, item.refs, item.now) for item in history] + + +class User: + + def __init__(self): + # list of class Talk + self.history = [] + # meta + self.last_msg_time = time.time() + self.last_msg_id = -1 + self.last_process_time = -1 + # groupid+userid + self._id = '' + self.group_id = '' + + def __str__(self): + obj = { + 'history': [], + 'last_msg_time': self.last_msg_time, + 'last_process_time': self.last_process_time, + '_id': self._id + } + for item in self.history: + obj['history'].append(convert_talk_to_dict(item)) + return json.dumps(obj, indent=2, ensure_ascii=False) + + def feed(self, msg: Message): + if msg.type in ['url', 'image']: + talk = Talk(query=msg.query, refs=(msg.url)) + self.history.append(talk) + else: + talk = Talk(query=msg.query) + self.history.append(talk) + self.last_msg_time = time.time() + self.last_msg_type = msg.type + self.last_msg_id = msg._id + self._id = msg.global_user_id + self.group_id = msg.group_id + + def concat(self): + # concat un-responsed query + # 整理历史消息,把没有回复的消息合并 + if len(self.history) < 2: + return + ret = [] + merge_list = [] + now = time.time() + for item in self.history: + if abs(now - item.now) > 7200: + # 2小时前,太久的消息就不要了 + continue + + answer = item.reply + if answer is not None and len(answer) > 0: + ret.append(item) + else: + merge_list.append(item.query) + + concat_query = '\n'.join(merge_list) + concat_talk = Talk(query=concat_query) + ret.append(concat_talk) + self.history = ret + + def update_history(self, query, reply, refs): + if type(refs) is list: + talk = Talk(query=query, reply=reply, refs=tuple(refs)) + else: + talk = Talk(query=query, reply=reply, refs=(refs)) + self.history[-1] = talk + self.last_process_time = time.time() + + +def bind(logdir: str, port: int): + + if not os.path.exists(logdir): + os.makedirs(logdir) + logpath = os.path.join(logdir, 'wechat_message.jsonl') + + async def msg_callback(request): + """Save wechat message to redis, for revert command, use high + priority.""" + input_json = await request.json() + with open(logpath, 'a') as f: + json_str = json.dumps(input_json, indent=2, ensure_ascii=False) + f.write(json_str) + f.write('\n') + + logger.debug(input_json) + msg_que = Queue(name='wechat') + revert_que = Queue(name='wechat-high-priority') + + if input_json['messageType'] == '00000': + return web.json_response(text='done') + + try: + json_str = json.dumps(input_json) + if is_revert_command(input_json): + revert_que.put(json_str) + else: + msg_que.put(json_str) + + except Exception as e: + logger.error(str(e)) + + return web.json_response(text='done') + + app = web.Application() + app.add_routes([web.post('/callback', msg_callback)]) + web.run_app(app, host='0.0.0.0', port=port) + + +class WkteamManager: + """ + 1. wkteam Login, see https://wkteam.cn/ + 2. Handle wkteam wechat message call back + """ + + def __init__(self, config_path: str): + """init with config.""" + self.WKTEAM_IP_PORT = '121.229.29.88:9899' + self.auth = '' + self.wId = '' + self.wcId = '' + self.qrCodeUrl = '' + self.wkteam_config = dict() + self.users = dict() + self.messages = [] + + # {group_id: group_name} + self.group_whitelist = dict() + + with open(config_path, encoding='utf8') as f: + self.config = pytoml.load(f) + assert len(self.config) > 1 + + wkconf = self.config['frontend']['wechat_wkteam'] + for key in wkconf.keys(): + key = key.strip() + if re.match(r'\d+', key) is None: + continue + group = wkconf[key] + self.group_whitelist['{}@chatroom'.format(key)] = group['name'] + + self.wkteam_config = types.SimpleNamespace(**wkconf) + + # set redis env + if os.getenv('REDIS_HOST') is None: + os.environ['REDIS_HOST'] = str(self.wkteam_config.redis_host) + if os.getenv('REDIS_PORT') is None: + os.environ['REDIS_PORT'] = str(self.wkteam_config.redis_port) + if os.getenv('REDIS_PASSWORD') is None: + os.environ['REDIS_PASSWORD'] = str(self.wkteam_config.redis_passwd) + + # load wkteam license + if not os.path.exists(self.wkteam_config.dir): + os.makedirs(self.wkteam_config.dir) + self.license_path = os.path.join(self.wkteam_config.dir, + 'license.json') + self.record_path = os.path.join(self.wkteam_config.dir, 'record.jsonl') + if os.path.exists(self.license_path): + with open(self.license_path) as f: + jsonobj = json.load(f) + self.auth = jsonobj['auth'] + self.wId = jsonobj['wId'] + self.wcId = jsonobj['wcId'] + self.qrCodeUrl = jsonobj['qrCodeUrl'] + logger.debug(jsonobj) + + # messages sent + # {groupId: [wx_msg]} + self.sent_msg = dict() + self.debug() + + def debug(self): + logger.debug('auth {}'.format(self.auth)) + logger.debug('wId {}'.format(self.wId)) + logger.debug('wcId {}'.format(self.wcId)) + + logger.debug('REDIS_HOST {}'.format(os.getenv('REDIS_HOST'))) + logger.debug('REDIS_PORT {}'.format(os.getenv('REDIS_PORT'))) + logger.debug(self.group_whitelist) + + def post(self, url, data, headers): + """Wrap http post and error handling.""" + resp = requests.post(url, data=json.dumps(data), headers=headers) + json_str = resp.content.decode('utf8') + logger.debug(json_str) + if resp.status_code != 200: + return None, Exception('wkteam auth fail {}'.format(json_str)) + json_obj = json.loads(json_str) + if json_obj['code'] != '1000': + return json_obj, Exception(json_str) + + return json_obj, None + + def revert(self, groupId: str): + """Revert all msgs in this group.""" + # 撤回在本群 2 分钟内发出的所有消息 + if groupId in self.group_whitelist: + groupname = self.group_whitelist[groupId] + logger.debug('revert message in group {} {}'.format( + groupname, groupId)) + else: + logger.debug('revert message in group {} '.format(groupId)) + + if groupId not in self.sent_msg: + return + + group_sent_list = self.sent_msg[groupId] + for sent in group_sent_list: + logger.info(sent) + time_diff = abs(time.time() - int(sent['createTime'])) + if time_diff <= 120: + # real revert + headers = { + 'Content-Type': 'application/json', + 'Authorization': self.auth + } + + self.post(url='http://{}/revokeMsg'.format( + self.WKTEAM_IP_PORT), + data=sent, + headers=headers) + del self.sent_msg[groupId] + + def download_image(self, param: dict): + """Download group chat image.""" + content = param['content'] + msgId = param['msgId'] + wId = param['wId'] + + if len(self.auth) < 1: + logger.error('Authentication empty') + return + + headers = { + 'Content-Type': 'application/json', + 'Authorization': self.auth + } + data = {'wId': wId, 'content': content, 'msgId': msgId, 'type': 0} + + def generate_hash_filename(data: dict): + xstr = json.dumps(data) + md5 = hashlib.md5() + md5.update(xstr.encode('utf8')) + return md5.hexdigest()[0:6] + '.jpg' + + def download(data: dict, headers: dict, dir: str): + resp = requests.post('http://{}/getMsgImg'.format( + self.WKTEAM_IP_PORT), + data=json.dumps(data), + headers=headers) + json_str = resp.content.decode('utf8') + + if resp.status_code == 200: + jsonobj = json.loads(json_str) + if jsonobj['code'] != '1000': + logger.error('download {} {}'.format(data, json_str)) + return + + image_url = jsonobj['data']['url'] + # download to local + logger.info('image url {}'.format(image_url)) + resp = requests.get(image_url, stream=True) + image_path = None + if resp.status_code == 200: + image_dir = os.path.join(dir, 'images') + if not os.path.exists(image_dir): + os.makedirs(image_dir) + image_path = os.path.join( + image_dir, generate_hash_filename(data=data)) + logger.debug('local path {}'.format(image_path)) + with open(image_path, 'wb') as image_file: + for chunk in resp.iter_content(1024): + image_file.write(chunk) + return image_url, image_path + + url = '' + path = '' + try: + url, path = download(data, headers, self.wkteam_config.dir) + except Exception as e: + logger.error(str(e)) + return None, None + return url, path + # download_task = Process(target=download, args=(data, headers, self.wkteam_config.dir)) + # download_task.start() + + def login(self): + """user login, need scan qr code on mobile phone.""" + # check input + if len(self.wkteam_config.account) < 1 or len( + self.wkteam_config.password) < 1: + return Exception('wkteam account or password not set') + + if len(self.wkteam_config.callback_ip) < 1: + return Exception( + 'wkteam wechat message public callback ip not set, try FRP or buy cloud service ?' + ) + + if self.wkteam_config.proxy <= 0: + return Exception('wkteam proxy not set') + + # auth + headers = {'Content-Type': 'application/json'} + data = { + 'account': self.wkteam_config.account, + 'password': self.wkteam_config.password + } + + json_obj, err = self.post(url='http://{}/member/login'.format( + self.WKTEAM_IP_PORT), + data=data, + headers=headers) + if err is not None: + return err + self.auth = json_obj['data']['Authorization'] + + # ipadLogin + headers['Authorization'] = self.auth + data = {'wcId': '', 'proxy': self.wkteam_config.proxy} + json_obj, err = self.post(url='http://{}/iPadLogin'.format( + self.WKTEAM_IP_PORT), + data=data, + headers=headers) + if err is not None: + return err + + x = json_obj['data'] + self.wId = x['wId'] + self.qrCodeUrl = x['qrCodeUrl'] + + logger.info( + '浏览器打开这个地址、下载二维码。打开手机,扫描登录微信\n {}\n 请确认 proxy 地区正确,首次使用、24 小时后要再次登录,以后不需要登。' + .format(self.qrCodeUrl)) + + # getLoginInfo + json_obj, err = self.post(url='http://{}/getIPadLoginInfo'.format( + self.WKTEAM_IP_PORT), + data={'wId': self.wId}, + headers=headers) + x = json_obj['data'] + self.wcId = x['wcId'] + + # dump + with open(self.license_path, 'w') as f: + json_str = json.dumps( + { + 'auth': self.auth, + 'wId': self.wId, + 'wcId': self.wcId, + 'qrCodeUrl': self.qrCodeUrl + }, + indent=2, + ensure_ascii=False) + f.write(json_str) + + def set_callback(self): + # set callback url + httpUrl = 'http://{}:{}/callback'.format( + self.wkteam_config.callback_ip, self.wkteam_config.callback_port) + logger.debug('set callback url {}'.format(httpUrl)) + headers = { + 'Content-Type': 'application/json', + 'Authorization': self.auth + } + data = {'httpUrl': httpUrl, 'type': 2} + + json_obj, err = self.post(url='http://{}/setHttpCallbackUrl'.format( + self.WKTEAM_IP_PORT), + data=data, + headers=headers) + if err is not None: + return err + + logger.info('login success, all license saved to {}'.format( + self.license_path)) + return None + + def send_message(self, groupId: str, text: str): + headers = { + 'Content-Type': 'application/json', + 'Authorization': self.auth + } + data = {'wId': self.wId, 'wcId': groupId, 'content': text} + + json_obj, err = self.post(url='http://{}/sendText'.format( + self.WKTEAM_IP_PORT), + data=data, + headers=headers) + if err is not None: + return err + + sent = json_obj['data'] + sent['wId'] = self.wId + if groupId not in self.sent_msg: + self.sent_msg[groupId] = [sent] + else: + self.sent_msg[groupId].append(sent) + + return None + + def serve(self): + p = Process(target=bind, + args=(self.wkteam_config.dir, + self.wkteam_config.callback_port)) + # bind(self.wkteam_config.callback_port) + p.start() + self.set_callback() + p.join() + + def fetch_groupchats(self, user: User, max_length: int = 12): + """Before obtaining user messages, there are a maximum of `max_length` + historical conversations in the group. + + Fetch them for coreference resolution. + """ + user_msg_id = user.last_msg_id + conversations = [] + + for index in range(len(self.messages) - 1, -1, -1): + msg = self.messages[index] + if len(conversations) >= max_length: + break + + if msg.type == 'unknown': + continue + + if msg._id < user_msg_id and msg.group_id == user.group_id: + conversations.append(msg) + return conversations + + def loop(self, worker): + """Fetch all messages from redis, split it by groupId; concat by + timestamp.""" + from huixiangdou.service.helper import ErrorCode, kimi_ocr + + revert_que = Queue(name='wechat-high-priority') + que = Queue(name='wechat') + + while True: + time.sleep(1) + # react to revert msg first + for wx_msg_str in revert_que.get_all(): + wx_msg = json.loads(wx_msg_str) + data = wx_msg['data'] + if 'fromGroup' in data: + self.revert(groupId=data['fromGroup']) + # “群友学习法”。命令撤回将提升阈值,提升量越来越小。 + worker.notify_badcase() + + # parse wx_msg, add it to group + for wx_msg_str in que.get_all(): + wx_msg = json.loads(wx_msg_str) + logger.debug(wx_msg) + msg = Message() + err = msg.parse(wx_msg=wx_msg, bot_wxid=self.wcId) + if err is not None: + logger.debug(str(err)) + continue + if msg.type == 'image': + _, local_image_path = self.download_image(param=msg.data) + + llm_server_config = self.config['llm']['server'] + if local_image_path is not None and llm_server_config[ + 'remote_type'] == 'kimi': + token = llm_server_config['remote_api_key'] + msg.query = kimi_ocr(local_image_path, token) + logger.debug('kimi ocr {} {}'.format( + local_image_path, msg.query)) + + if len(msg.query) < 1: + continue + + self.messages.append(msg) + if msg.type == 'ref_for_others': + continue + + if msg.global_user_id not in self.users: + self.users[msg.global_user_id] = User() + user = self.users[msg.global_user_id] + user.feed(msg) + + # try concat all msgs in groups, fetch one to process + for user in self.users.values(): + if len(user.history) < 1: + continue + + # debug + # if '20158567857@chatroom' not in user.group_id: + # logger.debug('user.group_id {}'.format(user.group_id)) + # continue + + now = time.time() + # if a user not send new message in 18 seconds, process and mark it + if now - user.last_msg_time >= 18 and user.last_process_time < user.last_msg_time: + if user.last_msg_type in ['link', 'image']: + # if user image or link contains question, do not process + continue + + logger.debug('before concat {}'.format(user)) + user.concat() + logger.debug('after concat {}'.format(user)) + assert len(user.history) > 0 + + item = user.history[-1] + + if item.reply is not None and len(item.reply) > 0: + logger.error('item reply not None, {}'.format(item)) + query = item.query + + code = ErrorCode.QUESTION_TOO_SHORT + resp = '' + refs = [] + groupname = '' + groupchats = [] + if user.group_id in self.group_whitelist: + groupname = self.group_whitelist[user.group_id] + + if len(query) >= 8: + groupchats = self.fetch_groupchats(user=user) + tuple_history = convert_history_to_tuple( + user.history[0:-1]) + + for sess in worker.generate( + query=query, + history=tuple_history, + groupname=groupname, + groupchats=groupchats): + code, resp, refs = sess.code, sess.response, sess.references + + # user history may affect normal conversation, so delete last query + user.last_process_time = time.time() + if code in [ + ErrorCode.NOT_A_QUESTION, ErrorCode.SECURITY, + ErrorCode.NO_SEARCH_RESULT, ErrorCode.NO_TOPIC + ]: + del user.history[-1] + else: + user.update_history(query=query, reply=resp, refs=refs) + + # send = False + if code == ErrorCode.SUCCESS: + # save sent and send reply to WeChat group + formatted_reply = '' + if len(query) > 30: + formatted_reply = '{}..\n---\n{}'.format( + query[0:30], resp) + else: + formatted_reply = '{}\n---\n{}'.format(query, resp) + + if user.group_id in self.group_whitelist: + logger.warning(r'send {} to {}'.format( + formatted_reply, user.group_id)) + self.send_message(groupId=user.group_id, + text=formatted_reply) + else: + logger.warning(r'prepare respond {} to {}'.format( + formatted_reply, user.group_id)) + + +def parse_args(): + """Parse args.""" + parser = argparse.ArgumentParser(description='wechat server.') + parser.add_argument('--work_dir', + type=str, + default='workdir', + help='Working directory.') + parser.add_argument('--config_path', + default='config.ini', + type=str, + help='Configuration path. Default value is config.ini') + parser.add_argument('--login', + action='store_true', + default=False, + help='Login wkteam') + parser.add_argument('--serve', + action='store_true', + default=True, + help='Bind port and listen WeChat message callback') + args = parser.parse_args() + return args + + +if __name__ == '__main__': + args = parse_args() + manager = WkteamManager(args.config_path) + + if args.login: + err = manager.login() + if err is not None: + logger.error(err) + manager.set_callback() + + if args.serve: + manager.serve() diff --git a/repodir/huixiangdou/huixiangdou/gradio.py b/repodir/huixiangdou/huixiangdou/gradio.py new file mode 100644 index 00000000..24d1986d --- /dev/null +++ b/repodir/huixiangdou/huixiangdou/gradio.py @@ -0,0 +1,238 @@ +import argparse +import json +import os +import time +import pdb +from multiprocessing import Process, Value +import asyncio +import cv2 +import gradio as gr +import pytoml +from loguru import logger +from typing import List +from huixiangdou.primitive import Query +from huixiangdou.service import ErrorCode, SerialPipeline, ParallelPipeline, llm_serve, start_llm_server +import json +from datetime import datetime + +def ymd(): + now = datetime.now() + date_string = now.strftime("%Y-%m-%d") + if not os.path.exists(date_string): + os.makedirs(date_string) + return date_string + +def parse_args(): + """Parse args.""" + parser = argparse.ArgumentParser(description='SerialPipeline.') + parser.add_argument('--work_dir', + type=str, + default='workdir', + help='Working directory.') + parser.add_argument('--pipeline-count', type=int, default=2, help='Support user choosing all pipeline types.') + parser.add_argument( + '--config_path', + default='config.ini', + type=str, + help='SerialPipeline configuration path. Default value is config.ini') + parser.add_argument('--standalone', + action='store_true', + default=True, + help='Auto deploy required Hybrid LLM Service.') + parser.add_argument('--no-standalone', + action='store_false', + dest='standalone', + help='Do not auto deploy required Hybrid LLM Service.') + parser.add_argument('--placeholder', type=str, default='How to install HuixiangDou ?', help='Placeholder for user query.') + parser.add_argument('--image', action='store_true', default=True, help='') + parser.add_argument('--no-image', action='store_false', dest='image', help='Close some components for readthedocs.') + parser.add_argument('--theme', type=str, default='soft', help='Gradio theme, default value is `soft`. Open https://www.gradio.app/guides/theming-guide for all themes.') + + args = parser.parse_args() + return args + +language='en' +enable_web_search=False +pipeline='chat_with_repo' +main_args = None +paralle_assistant = None +serial_assistant = None + +def on_language_changed(value:str): + global language + print(value) + language = value + +def on_pipeline_changed(value:str): + global pipeline + print(value) + pipeline = value + +def on_web_search_changed(value: str): + global enable_web_search + print(value) + if 'no' in value: + enable_web_search = False + else: + enable_web_search = True + + +def format_refs(refs: List[str]): + refs_filter = list(set(refs)) + if len(refs) < 1: + return '' + text = '' + if language == 'zh': + text += '参考资料:\r\n' + else: + text += '**References:**\r\n' + + for file_or_url in refs_filter: + text += '* {}\r\n'.format(file_or_url) + text += '\r\n' + return text + + +async def predict(text:str, image:str): + global language + global enable_web_search + global pipeline + global main_args + global serial_assistant + global paralle_assistant + + with open('query.txt', 'a') as f: + f.write(json.dumps({'data': text, 'date': ymd()}, ensure_ascii=False)) + f.write('\n') + + if image is not None: + filename = 'image.png' + image_path = os.path.join(main_args.work_dir, filename) + cv2.imwrite(image_path, image) + else: + image_path = None + + query = Query(text, image_path) + if 'chat_in_group' in pipeline: + if serial_assistant is None: + serial_assistant = SerialPipeline(work_dir=main_args.work_dir, config_path=main_args.config_path) + args = {'query':query, 'history': [], 'groupname':''} + pipeline = {'status': {}} + debug = dict() + stream_chat_content = '' + for sess in serial_assistant.generate(**args): + if len(sess.delta) > 0: + # start chat, display + stream_chat_content += sess.delta + yield stream_chat_content + else: + status = { + "state":str(sess.code), + "response": sess.response, + "refs": sess.references + } + pipeline['status'] = status + pipeline['debug'] = sess.debug + + json_str = json.dumps(pipeline, indent=2, ensure_ascii=False) + yield json_str + + else: + if paralle_assistant is None: + paralle_assistant = ParallelPipeline(work_dir=main_args.work_dir, config_path=main_args.config_path) + args = {'query':query, 'history':[], 'language':language} + args['enable_web_search'] = enable_web_search + + sentence = '' + async for sess in paralle_assistant.generate(**args): + if sentence == '' and len(sess.references) > 0: + sentence = format_refs(sess.references) + + if len(sess.delta) > 0: + sentence += sess.delta + yield sentence + + yield sentence + +def download_and_unzip(main_args): + zip_filepath = os.path.join(main_args.feature_local, 'workdir.zip') + main_args.work_dir = os.path.join(main_args.feature_local, 'workdir') + logger.info(f'assign {main_args.work_dir} to args.work_dir') + + download_cmd = f'wget -O {zip_filepath} {main_args.feature_url}' + os.system(download_cmd) + + if not os.path.exists(zip_filepath): + raise Exception(f'zip filepath {zip_filepath} not exist.') + + unzip_cmd = f'unzip -o {zip_filepath} -d {main_args.feature_local}' + os.system(unzip_cmd) + if not os.path.exists(main_args.work_dir): + raise Exception(f'feature dir {zip_dir} not exist.') + +def build_feature_store(main_args): + if os.path.exists('workdir'): + logger.warning('feature_store `workdir` already exist, skip') + return + logger.info('start build feature_store..') + os.system('python3 -m huixiangdou.service.feature_store --config_path {}'.format(main_args.config_path)) + +if __name__ == '__main__': + main_args = parse_args() + build_feature_store(main_args) + + show_image = True + radio_options = ["chat_with_repo"] + + if not main_args.image: + show_image = False + + if main_args.pipeline_count > 1: + radio_options.append('chat_in_group') + + # start service + if main_args.standalone is True: + # hybrid llm serve + start_llm_server(config_path=main_args.config_path) + + themes = { + 'soft': gr.themes.Soft(), + 'monochrome': gr.themes.Monochrome(), + 'base': gr.themes.Base(), + 'default': gr.themes.Default(), + 'glass': gr.themes.Glass() + } + if main_args.theme in themes: + theme = themes[main_args.theme] + else: + theme = gr.themes.Soft() + + with gr.Blocks(theme=theme, title='HuixiangDou AI assistant', analytics_enabled=True) as demo: + with gr.Row(): + gr.Markdown(""" + #### [HuixiangDou](https://github.com/internlm/huixiangdou) AI assistant + """, label='Reply', header_links=True, line_breaks=True,) + with gr.Row(): + if len(radio_options) > 1: + with gr.Column(): + ui_pipeline = gr.Radio(radio_options, label="Pipeline type", info="Group-chat is slow but accurate and safe, default value is `chat_with_repo`") + ui_pipeline.change(fn=on_pipeline_changed, inputs=ui_pipeline, outputs=[]) + with gr.Column(): + ui_language = gr.Radio(["en", "zh"], label="Language", info="Use `en` by default ") + ui_language.change(fn=on_language_changed, inputs=ui_language, outputs=[]) + with gr.Column(): + ui_web_search = gr.Radio(["no", "yes"], label="Enable web search", info="Disable by default ") + ui_web_search.change(on_web_search_changed, inputs=ui_web_search, outputs=[]) + + with gr.Row(): + input_question = gr.TextArea(label='Input your question', placeholder=main_args.placeholder, show_copy_button=True, lines=9) + input_image = gr.Image(label='[Optional] Image-text retrieval needs `config-multimodal.ini`', render=show_image) + with gr.Row(): + run_button = gr.Button() + with gr.Row(): + result = gr.Markdown('>Text reply or inner status callback here, depends on `pipeline type`', label='Reply', show_label=True, header_links=True, line_breaks=True, show_copy_button=True) + # result = gr.TextArea(label='Reply', show_copy_button=True, placeholder='Text Reply or inner status callback, depends on `pipeline type`') + + run_button.click(predict, [input_question, input_image], [result]) + demo.queue() + demo.launch(share=False, server_name='0.0.0.0', debug=True) diff --git a/repodir/huixiangdou/huixiangdou/main.py b/repodir/huixiangdou/huixiangdou/main.py new file mode 100755 index 00000000..f7dddf99 --- /dev/null +++ b/repodir/huixiangdou/huixiangdou/main.py @@ -0,0 +1,213 @@ +#!/usr/bin/env python3 +# Copyright (c) OpenMMLab. All rights reserved. +"""HuixiangDou binary.""" +import argparse +import os +import time + +import pytoml +import requests +from aiohttp import web +from loguru import logger +from termcolor import colored + +from .service import ErrorCode, SerialPipeline, build_reply_text, start_llm_server + + +def parse_args(): + """Parse args.""" + parser = argparse.ArgumentParser(description='SerialPipeline.') + parser.add_argument('--work_dir', + type=str, + default='workdir', + help='Working directory.') + parser.add_argument( + '--config_path', + default='config.ini', + type=str, + help='SerialPipeline configuration path. Default value is config.ini') + parser.add_argument('--standalone', + action='store_true', + default=False, + help='Auto deploy required Hybrid LLM Service.') + args = parser.parse_args() + return args + + +def check_env(args): + """Check or create config.ini and logs dir.""" + if not os.path.exists('logs'): + os.makedirs('logs') + CONFIG_NAME = 'config.ini' + CONFIG_URL = 'https://raw.githubusercontent.com/InternLM/HuixiangDou/main/config.ini' # noqa E501 + if not os.path.exists(CONFIG_NAME): + logger.warning( + f'{CONFIG_NAME} not found, download a template from {CONFIG_URL}.') + + try: + response = requests.get(CONFIG_URL, timeout=60) + response.raise_for_status() + with open(CONFIG_NAME, 'wb') as f: + f.write(response.content) + except Exception as e: + logger.error(f'Failed to download file due to {e}') + raise e + + if not os.path.exists(args.work_dir): + logger.warning( + f'args.work_dir dir not exist, auto create {args.work_dir}.') + os.makedirs(args.work_dir) + + +def show(assistant, fe_config: dict): + + queries = ['请问如何安装 mmpose ?', '请问明天天气如何?'] + print(colored('Running some examples..', 'yellow')) + for query in queries: + print(colored('[Example]' + query, 'yellow')) + + for query in queries: + for sess in assistant.generate(query=query, history=[], groupname=''): + pass + + code, reply, refs = str(sess.code), sess.response, sess.references + reply_text = build_reply_text(code=code, + query=query, + reply=reply, + refs=refs) + logger.info('\n' + reply_text) + + if fe_config['type'] == 'lark': + # send message to lark group + logger.error( + '!!!`lark_send_only` feature will be removed on October 10, 2024. If this function still helpful for you, please let me know: https://github.com/InternLM/HuixiangDou/issues' + ) + from .frontend import Lark + lark = Lark(webhook=fe_config['webhook_url']) + logger.info(f'send {reply} and {refs} to lark group.') + lark.send_text(msg=reply_text) + + while True: + user_input = input("🔆 Input your question here, type `bye` for exit:\n") + if 'bye' in user_input: + break + + for sess in assistant.generate(query=user_input, history=[], groupname=''): + pass + code, reply, refs = str(sess.code), sess.response, sess.references + + reply_text = build_reply_text(code=code, + query=user_input, + reply=reply, + refs=refs, + max_len=300) + print('\n' + reply_text) + +def lark_group_recv_and_send(assistant, fe_config: dict): + from .frontend import (is_revert_command, revert_from_lark_group, + send_to_lark_group) + msg_url = fe_config['webhook_url'] + lark_group_config = fe_config['lark_group'] + sent_msg_ids = [] + + while True: + # fetch a user message + resp = requests.post(msg_url, timeout=10) + resp.raise_for_status() + json_obj = resp.json() + if len(json_obj) < 1: + # no user input, sleep + time.sleep(2) + continue + + logger.debug(json_obj) + query = json_obj['content'] + + if is_revert_command(query): + for msg_id in sent_msg_ids: + error = revert_from_lark_group(msg_id, + lark_group_config['app_id'], + lark_group_config['app_secret']) + if error is not None: + logger.error( + f'revert msg_id {msg_id} fail, reason {error}') + else: + logger.debug(f'revert msg_id {msg_id}') + time.sleep(0.5) + sent_msg_ids = [] + continue + + for sess in assistant.generate(query=query, history=[], groupname=''): + pass + code, reply, refs = str(sess.code), sess.response, sess.references + if code == ErrorCode.SUCCESS: + json_obj['reply'] = build_reply_text(reply=reply, + references=references) + error, msg_id = send_to_lark_group( + json_obj=json_obj, + app_id=lark_group_config['app_id'], + app_secret=lark_group_config['app_secret']) + if error is not None: + raise error + sent_msg_ids.append(msg_id) + else: + logger.debug(f'{code} for the query {query}') + + +def wechat_personal_run(assistant, fe_config: dict): + """Call assistant inference.""" + + async def api(request): + input_json = await request.json() + logger.debug(input_json) + + query = input_json['query'] + + if type(query) is dict: + query = query['content'] + + for sess in assistant.generate(query=query, history=[], groupname=''): + pass + code, reply, refs = str(sess.code), sess.response, sess.references + reply_text = build_reply_text(reply=reply, references=references) + + return web.json_response({'code': int(code), 'reply': reply_text}) + + bind_port = fe_config['wechat_personal']['bind_port'] + app = web.Application() + app.add_routes([web.post('/api', api)]) + web.run_app(app, host='0.0.0.0', port=bind_port) + + +def run(): + """Automatically download config, start llm server and run examples.""" + args = parse_args() + + if args.standalone is True: + # hybrid llm serve + start_llm_server(config_path=args.config_path) + + # query by worker + with open(args.config_path, encoding='utf8') as f: + fe_config = pytoml.load(f)['frontend'] + logger.info('Config loaded.') + assistant = SerialPipeline(work_dir=args.work_dir, config_path=args.config_path) + + fe_type = fe_config['type'] + if fe_type == 'none': + show(assistant, fe_config) + elif fe_type == 'lark_group': + lark_group_recv_and_send(assistant, fe_config) + elif fe_type == 'wechat_personal': + wechat_personal_run(assistant, fe_config) + elif fe_type == 'wechat_wkteam': + from .frontend import WkteamManager + manager = WkteamManager(args.config_path) + manager.loop(assistant) + else: + logger.info( + f'unsupported fe_config.type {fe_type}, please read `config.ini` description.' # noqa E501 + ) + +if __name__ == '__main__': + run() diff --git a/repodir/huixiangdou/huixiangdou/primitive/__init__.py b/repodir/huixiangdou/huixiangdou/primitive/__init__.py new file mode 100644 index 00000000..773e3a92 --- /dev/null +++ b/repodir/huixiangdou/huixiangdou/primitive/__init__.py @@ -0,0 +1,16 @@ +# Copyright (c) OpenMMLab. All rights reserved. +"""primitive module.""" +from .chunk import Chunk # noqa E401 +from .embedder import Embedder # noqa E401 +from .faiss import Faiss # noqa E401 +from .file_operation import FileName, FileOperation # noqa E401 +from .llm_reranker import LLMReranker # noqa E401 +from .query import Query +from .splitter import ( + CharacterTextSplitter, # noqa E401 + ChineseRecursiveTextSplitter, + MarkdownHeaderTextSplitter, + MarkdownTextRefSplitter, + RecursiveCharacterTextSplitter, + nested_split_markdown) +from .rpm import RPM diff --git a/repodir/huixiangdou/huixiangdou/primitive/chunk.py b/repodir/huixiangdou/huixiangdou/primitive/chunk.py new file mode 100644 index 00000000..5509a421 --- /dev/null +++ b/repodir/huixiangdou/huixiangdou/primitive/chunk.py @@ -0,0 +1,46 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from dataclasses import dataclass, field + + +@dataclass +class Chunk(): + """Class for storing a piece of text and associated metadata. + + Example: + + .. code-block:: python + + from huixiangdou.primitive import Chunk + + chunk = Chunk( + content_or_path="Hello, world!", + metadata={"source": "https://example.com"} + ) + """ + content_or_path: str = '' + metadata: dict = field(default_factory=dict) + modal: str = 'text' + + def __post_init__(self): + if self.modal not in ['text', 'image', 'audio']: + raise ValueError( + f'Invalid modal: {self.modal}. Allowed values are: `text`, `image`, `audio`' + ) + + def __str__(self) -> str: + """Override __str__ to restrict it to content_or_path and metadata.""" + # The format matches pydantic format for __str__. + # + # The purpose of this change is to make sure that user code that + # feeds Document objects directly into prompts remains unchanged + # due to the addition of the id field (or any other fields in the future). + # + # This override will likely be removed in the future in favor of + # a more general solution of formatting content directly inside the prompts. + if self.metadata: + return f"modal='{self.modal}' content_or_path='{self.content_or_path}' metadata={self.metadata}" + else: + return f"modal='{self.modal}' content_or_path='{self.content_or_path}'" + + def __repr__(self) -> str: + return self.__str__() diff --git a/repodir/huixiangdou/huixiangdou/primitive/embedder.py b/repodir/huixiangdou/huixiangdou/primitive/embedder.py new file mode 100644 index 00000000..b45725dd --- /dev/null +++ b/repodir/huixiangdou/huixiangdou/primitive/embedder.py @@ -0,0 +1,117 @@ +# Copyright (c) OpenMMLab. All rights reserved. +# +import os +import pdb +import requests +import json + +from typing import Any, List + +import numpy as np +from loguru import logger +from .query import DistanceStrategy +from .rpm import RPM + +class Embedder: + """Wrap text2vec (multimodal) model.""" + client: Any + _type: str + + def __init__(self, model_config: dict): + self.support_image = False + # bce also use euclidean distance. + self.distance_strategy = DistanceStrategy.EUCLIDEAN_DISTANCE + + model_path = model_config['embedding_model_path'] + self._type = self.model_type(model_path=model_path) + if 'bce' in self._type: + from sentence_transformers import SentenceTransformer + self.client = SentenceTransformer(model_name_or_path=model_path).half() + elif 'bge' in self._type: + from FlagEmbedding.visual.modeling import Visualized_BGE + self.support_image = True + vision_weight_path = os.path.join(model_path, 'Visualized_m3.pth') + self.client = Visualized_BGE( + model_name_bge=model_path, + model_weight=vision_weight_path).eval() + elif 'siliconcloud' in self._type: + api_token = model_config['api_token'].strip() + if len(api_token) < 1: + api_token = os.getenv('SILICONCLOUD_TOKEN') + if api_token is None or len(api_token) < 1: + raise ValueError('siliconclud remote embedder api token is None') + + if 'Bearer' not in api_token: + api_token = 'Bearer ' + api_token + api_rpm = max(1, int(model_config['api_rpm'])) + self.client = { + 'api_token': api_token, + 'api_rpm': RPM(api_rpm) + } + + else: + raise ValueError('Unknown type {}'.format(self._type)) + + @classmethod + def model_type(self, model_path): + """Check text2vec model using multimodal or not.""" + if model_path.startswith('https'): + return 'siliconcloud' + + if 'bge-m3' not in model_path.lower(): + return 'bce' + + vision_weight = os.path.join(model_path, 'Visualized_m3.pth') + if not os.path.exists(vision_weight): + logger.warning( + '`Visualized_m3.pth` (vision model weight) not exist') + return 'bce' + return 'bge' + + def token_length(self, text: str) -> int: + if 'bge' in self._type or 'bce' in self._type: + return len(self.client.tokenizer(text, padding=False, truncation=False)['input_ids']) + else: + return len(text) // 2 + + def embed_query(self, text: str = None, path: str = None) -> np.ndarray: + """Embed input text or image as feature, output np.ndarray with np.float32""" + if 'bge' in self._type: + import torch + with torch.no_grad(): + feature = self.client.encode(text=text, image=path) + return feature.cpu().numpy().astype(np.float32) + elif 'bce' in self._type: + if text is None: + raise ValueError('This model only support text') + emb = self.client.encode([text], show_progress_bar=False, normalize_embeddings=True) + emb = emb.astype(np.float32) + # for norm in np.linalg.norm(emb, axis=1): + # assert abs(norm - 1) < 0.001 + return emb + else: + self.client['api_rpm'].wait(silent=True) + + # siliconcloud bce API + if text is None: + raise ValueError('This api only support text') + + url = "https://api.siliconflow.cn/v1/embeddings" + + payload = { + "model": "netease-youdao/bce-embedding-base_v1", + # Since siliconcloud API return 50400 for long input, we have to truncate it. + "input": text, + "encoding_format": "float" + } + headers = { + "accept": "application/json", + "content-type": "application/json", + "authorization": self.client['api_token'] + } + + response = requests.post(url, json=payload, headers=headers) + json_obj = json.loads(response.text) + emb_list = json_obj['data'][0]['embedding'] + emb = np.array(emb_list).astype(np.float32).reshape(1, -1) + return emb diff --git a/repodir/huixiangdou/huixiangdou/primitive/faiss.py b/repodir/huixiangdou/huixiangdou/primitive/faiss.py new file mode 100644 index 00000000..b65ac152 --- /dev/null +++ b/repodir/huixiangdou/huixiangdou/primitive/faiss.py @@ -0,0 +1,202 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from __future__ import annotations + +import logging +import os +import pdb +import pickle +from pathlib import Path +from typing import (Any, Callable, Dict, Iterable, List, Optional, Sized, + Tuple, Union) + +import numpy as np +from loguru import logger +from tqdm import tqdm + +from .embedder import Embedder +from .query import Query, DistanceStrategy + + +# heavily modified from langchain +def dependable_faiss_import(no_avx2: Optional[bool] = None) -> Any: + """Import faiss if available, otherwise raise error. + + Args: + no_avx2: Load FAISS strictly with no AVX2 optimization + so that the vectorstore is portable and compatible with other devices. + """ + try: + import faiss + except ImportError: + raise ImportError( + 'Could not import faiss python package. ' + 'Please install it with `pip install faiss-gpu` (for CUDA supported GPU) ' + 'or `pip install faiss-cpu` (depending on Python version).') + return faiss + + +class Faiss(): + + def __init__(self, index: Any, chunks: List[Chunk], strategy:DistanceStrategy, k: int = 30): + """Initialize with necessary components.""" + self.index = index + self.chunks = chunks + self.strategy = strategy + self.k = k + + def similarity_search(self, + embedding: np.ndarray) -> List[Tuple[Chunk, float]]: + """Return chunks most similar to query. + + Args: + embedding: Embedding vector to look up chunk similar to. + k: Number of Documents to return. Defaults to 30. + + Returns: + List of chunks most similar to the query text and L2 distance + in float for each. High score represents more similarity. + """ + faiss = dependable_faiss_import() + + embedding = embedding.astype(np.float32) + scores, indices = self.index.search(embedding, self.k) + pairs = [] + for j, i in enumerate(indices[0]): + if i == -1: + # no enough chunks are returned. + continue + chunk = self.chunks[i] + score = scores[0][j] + + if self.strategy == DistanceStrategy.EUCLIDEAN_DISTANCE: + rel_score = DistanceStrategy.euclidean_relevance_score_fn(score) + elif self.strategy == DistanceStrategy.MAX_INNER_PRODUCT: + rel_score = DistanceStrategy.max_inner_product_relevance_score_fn(score) + else: + raise ValueError('self.strategy unset') + pairs.append((chunk, rel_score)) + + if len(pairs) >= 2: + assert pairs[0][1] >= pairs[1][1] + return pairs + + def similarity_search_with_query(self, + embedder: Embedder, + query: Query, + threshold: float = -1): + """Return chunks most similar to query. + + Args: + query: Multimodal query. + k: Number of Documents to return. Defaults to 30. + + Returns: + List of chunks most similar to the query text and L2 distance + in float for each. Lower score represents more similarity. + """ + if query.text is None and query.image is None: + raise ValueError(f'Input query is None') + + if query.text is None and query.image is not None: + if not embedder.support_image: + logger.info('Embedder not support image') + return [] + + np_feature = embedder.embed_query(text=query.text, path=query.image) + pairs = self.similarity_search(embedding=np_feature) + # ret = list(filter(lambda x: x[1] >= threshold, pairs)) + + highest_score = -1.0 + ret = [] + for pair in pairs: + if pair[1] >= threshold: + ret.append(pair) + if highest_score < pair[1]: + highest_score = pair[1] + + if len(ret) < 1: + logger.info('highest score {}, threshold {}'.format(highest_score, threshold)) + return ret + + @classmethod + def save_local(self, folder_path: str, chunks: List[Chunk], + embedder: Embedder) -> None: + """Save FAISS index and store to disk. + + Args: + folder_path: folder path to save. + chunks: chunks to save. + embedder: embedding function. + """ + + faiss = dependable_faiss_import() + index = None + + for chunk in tqdm(chunks): + np_feature = None + try: + if chunk.modal == 'text': + np_feature = embedder.embed_query(text=chunk.content_or_path) + elif chunk.modal == 'image': + np_feature = embedder.embed_query(path=chunk.content_or_path) + else: + raise ValueError(f'Unimplement chunk type: {chunk.modal}') + except Exception as e: + logger.error('{}'.format(e)) + + if np_feature is None: + logger.error('np_feature is None') + continue + + if index is None: + dimension = np_feature.shape[-1] + + if embedder.distance_strategy == DistanceStrategy.EUCLIDEAN_DISTANCE: + index = faiss.IndexFlatL2(dimension) + elif embedder.distance_strategy == DistanceStrategy.MAX_INNER_PRODUCT: + index = faiss.IndexFlatIP(dimension) + + index.add(np_feature) + + path = Path(folder_path) + path.mkdir(exist_ok=True, parents=True) + + # save index separately since it is not picklable + faiss.write_index(index, str(path / 'embedding.faiss')) + + # save chunks + data = { + 'chunks': chunks, + 'strategy': str(embedder.distance_strategy) + } + with open(path / 'chunks_and_strategy.pkl', 'wb') as f: + pickle.dump(data, f) + + @classmethod + def load_local(cls, folder_path: str) -> FAISS: + """Load FAISS index and chunks from disk. + + Args: + folder_path: folder path to load index and chunks from index.faiss + index_name: for saving with a specific index file name + """ + path = Path(folder_path) + # load index separately since it is not picklable + faiss = dependable_faiss_import() + index = faiss.read_index(str(path / f'embedding.faiss')) + strategy = DistanceStrategy.UNKNOWN + + # load docstore + with open(path / f'chunks_and_strategy.pkl', 'rb') as f: + data = pickle.load(f) + chunks = data['chunks'] + strategy_str = data['strategy'] + + if 'EUCLIDEAN_DISTANCE' in strategy_str: + strategy = DistanceStrategy.EUCLIDEAN_DISTANCE + elif 'MAX_INNER_PRODUCT' in strategy_str: + strategy = DistanceStrategy.MAX_INNER_PRODUCT + else: + raise ValueError('Unknown strategy type {}'.format(strategy_str)) + + return cls(index, chunks, strategy) diff --git a/repodir/huixiangdou/huixiangdou/primitive/file_operation.py b/repodir/huixiangdou/huixiangdou/primitive/file_operation.py new file mode 100644 index 00000000..3123e10d --- /dev/null +++ b/repodir/huixiangdou/huixiangdou/primitive/file_operation.py @@ -0,0 +1,261 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import hashlib +import os +import shutil + +import fitz +import pandas as pd +import requests +import textract +from bs4 import BeautifulSoup +from loguru import logger + + +class FileName: + """Record file original name, state and copied filepath with text + format.""" + + def __init__(self, root: str, filename: str, _type: str): + self.root = root + self.prefix = filename.replace('/', '_') + self.basename = os.path.basename(filename) + self.origin = os.path.join(root, filename) + self.copypath = self.origin + self._type = _type + self.state = True + self.reason = '' + + def __str__(self): + return '{},{},{},{}\n'.format(self.basename, self.copypath, self.state, + self.reason) + + +class FileOperation: + """Encapsulate all file reading operations.""" + + def __init__(self): + self.image_suffix = ['.jpg', '.jpeg', '.png', '.bmp'] + self.md_suffix = '.md' + self.text_suffix = ['.txt', '.text'] + self.excel_suffix = ['.xlsx', '.xls', '.csv'] + self.pdf_suffix = '.pdf' + self.ppt_suffix = '.pptx' + self.html_suffix = ['.html', '.htm', '.shtml', '.xhtml'] + self.word_suffix = ['.docx', '.doc'] + # self.code_suffix = ['.py', '.cpp', '.h'] + self.normal_suffix = [self.md_suffix + ] + self.text_suffix + self.excel_suffix + [ + self.pdf_suffix + ] + self.word_suffix + [self.ppt_suffix + ] + self.html_suffix + + def save_image(self, uri: str, outdir: str): + """Save image URI to local dir. + + Return None if failed. + """ + images_dir = os.path.join(outdir, 'images') + if not os.path.exists(images_dir): + os.makedirs(images_dir) + + md5 = hashlib.md5() + md5.update(uri.encode('utf8')) + uuid = md5.hexdigest()[0:6] + filename = uuid + uri[uri.rfind('.'):] + image_path = os.path.join(images_dir, filename) + + logger.info('download {}'.format(uri)) + try: + if uri.startswith('http'): + resp = requests.get(uri, stream=True) + if resp.status_code == 200: + with open(image_path, 'wb') as image_file: + for chunk in resp.iter_content(1024): + image_file.write(chunk) + else: + shutil.copy(uri, image_path) + except Exception as e: + logger.debug(e) + return None, None + return uuid, image_path + + def get_type(self, filepath: str): + """Get filetype depends on URI suffix.""" + filepath = filepath.lower() + if filepath.endswith(self.pdf_suffix): + return 'pdf' + + if filepath.endswith(self.md_suffix): + return 'md' + + if filepath.endswith(self.ppt_suffix): + return 'ppt' + + for suffix in self.image_suffix: + if filepath.endswith(suffix): + return 'image' + + for suffix in self.text_suffix: + if filepath.endswith(suffix): + return 'text' + + for suffix in self.word_suffix: + if filepath.endswith(suffix): + return 'word' + + for suffix in self.excel_suffix: + if filepath.endswith(suffix): + return 'excel' + + for suffix in self.html_suffix: + if filepath.endswith(suffix): + return 'html' + + # for suffix in self.code_suffix: + # if filepath.endswith(suffix): + # return 'code' + return None + + def md5(self, filepath: str): + hash_object = hashlib.sha256() + with open(filepath, 'rb') as file: + chunk_size = 8192 + while chunk := file.read(chunk_size): + hash_object.update(chunk) + + return hash_object.hexdigest()[0:8] + + def summarize(self, files: list): + success = 0 + skip = 0 + failed = 0 + + for file in files: + if file.state: + success += 1 + elif file.reason == 'skip': + skip += 1 + else: + # logger.info('{} {}'.format(file.origin, file.reason)) + failed += 1 + + # logger.info('{} {}'.format(file.reason, file.copypath)) + logger.info('累计{}文件,成功{}个,跳过{}个,异常{}个'.format(len(files), success, + skip, failed)) + + def scan_dir(self, repo_dir: str): + files = [] + for root, _, filenames in os.walk(repo_dir): + for filename in filenames: + _type = self.get_type(filename) + if _type is not None: + files.append( + FileName(root=root, filename=filename, _type=_type)) + return files + + def read_pdf(self, filepath: str): + # load pdf and serialize table + + text = '' + with fitz.open(filepath) as pages: + for page in pages: + text += page.get_text() + tables = page.find_tables() + for table in tables: + tablename = '_'.join( + filter(lambda x: x is not None and 'Col' not in x, + table.header.names)) + pan = table.to_pandas() + json_text = pan.dropna(axis=1).to_json(force_ascii=False) + text += tablename + text += '\n' + text += json_text + text += '\n' + return text + + def read_excel(self, filepath: str): + table = None + if filepath.endswith('.csv'): + table = pd.read_csv(filepath) + else: + table = pd.read_excel(filepath) + if table is None: + return '' + json_text = table.dropna(axis=1).to_json(force_ascii=False) + return json_text + + def read(self, filepath: str): + file_type = self.get_type(filepath) + + text = '' + + if not os.path.exists(filepath): + return text, None + + try: + + if file_type == 'md' or file_type == 'text': + with open(filepath) as f: + text = f.read() + + elif file_type == 'pdf': + text += self.read_pdf(filepath) + + elif file_type == 'excel': + text += self.read_excel(filepath) + + elif file_type == 'word' or file_type == 'ppt': + # https://stackoverflow.com/questions/36001482/read-doc-file-with-python + # https://textract.readthedocs.io/en/latest/installation.html + text = textract.process(filepath).decode('utf8') + if file_type == 'ppt': + text = text.replace('\n', ' ') + + elif file_type == 'html': + with open(filepath) as f: + soup = BeautifulSoup(f.read(), 'html.parser') + text += soup.text + + except Exception as e: + logger.error((filepath, str(e))) + return '', e + text = text.replace('\n\n', '\n') + text = text.replace('\n\n', '\n') + text = text.replace('\n\n', '\n') + text = text.replace(' ', ' ') + text = text.replace(' ', ' ') + text = text.replace(' ', ' ') + return text, None + + +if __name__ == '__main__': + + def get_pdf_files(directory): + pdf_files = [] + # 遍历目录 + for root, dirs, files in os.walk(directory): + for file in files: + # 检查文件扩展名是否为.pdf + if file.lower().endswith('.pdf'): + # 将完整路径添加到列表中 + pdf_files.append(os.path.abspath(os.path.join(root, file))) + return pdf_files + + # 将你想要搜索的目录替换为下面的路径 + pdf_list = get_pdf_files( + '/home/khj/huixiangdou-web-online-data/hxd-bad-file') + + # 打印所有找到的PDF文件的绝对路径 + + opr = FileOperation() + for pdf_path in pdf_list: + text, error = opr.read(pdf_path) + print('processing {}'.format(pdf_path)) + if error is not None: + print('') + + else: + if text is not None: + print(len(text)) + else: + print('') diff --git a/repodir/huixiangdou/huixiangdou/primitive/llm_reranker.py b/repodir/huixiangdou/huixiangdou/primitive/llm_reranker.py new file mode 100644 index 00000000..2a589262 --- /dev/null +++ b/repodir/huixiangdou/huixiangdou/primitive/llm_reranker.py @@ -0,0 +1,181 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import json +import os +import pdb +import requests +from typing import List, Tuple + +import numpy as np + +from .chunk import Chunk +from .embedder import Embedder +from .rpm import RPM + +class LLMReranker: + _type: str + topn: int + + def __init__( + self, + model_config: dict, + topn: int = 10): + + model_name_or_path = model_config['reranker_model_path'] + self._type = self.model_type(model_path=model_name_or_path) + self.topn = topn + + if 'bge' in self._type: + import torch + from transformers import AutoModelForCausalLM, AutoTokenizer + + self.tokenizer = AutoTokenizer.from_pretrained( + model_name_or_path, trust_remote_code=True) + self.model = AutoModelForCausalLM.from_pretrained( + model_name_or_path, + trust_remote_code=True, + torch_dtype=torch.bfloat16).eval().to('cuda') + elif 'bce' in self._type: + from BCEmbedding import RerankerModel + self.bce_client = RerankerModel( + model_name_or_path=model_name_or_path, use_fp16=True) + elif 'siliconcloud' in self._type: + api_token = model_config['api_token'].strip() + if len(api_token) < 1: + api_token = os.getenv('SILICONCLOUD_TOKEN') + if api_token is None or len(api_token) < 1: + raise ValueError('siliconclud remote reranker api token is None') + if 'Bearer' not in api_token: + api_token = 'Bearer ' + api_token + api_rpm = max(1, int(model_config['api_rpm'])) + self.client = { + 'api_token': api_token, + 'api_rpm': RPM(api_rpm) + } + + else: + raise ValueError('Unknown type {}'.format(self._type)) + + + @classmethod + def model_type(self, model_path): + """Check reranker model is LLM reranker or not.""" + if model_path.startswith('https'): + return 'siliconcloud' + + config_path = os.path.join(model_path, 'config.json') + if not os.path.exists(config_path): + if 'bge-reranker-v2-minicpm-layerwise' in config_path.lower(): + return 'bge' + return 'bce' + try: + with open(config_path) as f: + if 'bge-reranker-v2-minicpm-layerwise' in json.loads( + f.read())['_name_or_path']: + return 'bge' + except Exception as e: + logger.warning(e) + return 'bce' + + def _get_inputs(self, pairs, prompt=None, max_length=1024): + """Build input tokens with query and chunks.""" + if prompt is None: + prompt = "Given a query A and a passage B, determine whether the passage contains an answer to the query by providing a prediction of either 'Yes' or 'No'." + sep = '\n' + prompt_inputs = self.tokenizer(prompt, + return_tensors=None, + add_special_tokens=False)['input_ids'] + sep_inputs = self.tokenizer(sep, + return_tensors=None, + add_special_tokens=False)['input_ids'] + inputs = [] + for query, passage in pairs: + query_inputs = self.tokenizer(f'A: {query}', + return_tensors=None, + add_special_tokens=False, + max_length=max_length * 3 // 4, + truncation=True) + passage_inputs = self.tokenizer(f'B: {passage}', + return_tensors=None, + add_special_tokens=False, + max_length=max_length, + truncation=True) + item = self.tokenizer.prepare_for_model( + [self.tokenizer.bos_token_id] + query_inputs['input_ids'], + sep_inputs + passage_inputs['input_ids'], + truncation='only_second', + max_length=max_length, + padding=False, + return_attention_mask=False, + return_token_type_ids=False, + add_special_tokens=False) + item['input_ids'] = item['input_ids'] + sep_inputs + prompt_inputs + item['attention_mask'] = [1] * len(item['input_ids']) + inputs.append(item) + return self.tokenizer.pad(inputs, + padding=True, + max_length=max_length + len(sep_inputs) + + len(prompt_inputs), + pad_to_multiple_of=8, + return_tensors='pt') + + def _sort(self, texts: List[str], query: str): + """Rerank input texts, return descending indexes, indexes[0] is the + nearest chunk.""" + pairs = [] + for text in texts: + pairs.append([query, text]) + + if 'bge' in self._type: + import torch + with torch.no_grad(): + inputs = self._get_inputs(pairs).to(self.model.device) + all_scores = self.model(**inputs, + return_dict=True, + cutoff_layers=[28]) + scores = [ + scores[:, -1].view(-1, ).float() + for scores in all_scores[0] + ] + scores = scores[0].cpu().numpy() + elif 'bce' in self._type: + scores_list = self.bce_client.compute_score(pairs) + scores = np.array(scores_list) + else: + self.client['api_rpm'].wait(silent=True) + + url = "https://api.siliconflow.cn/v1/rerank" + payload = { + "model": "netease-youdao/bce-reranker-base_v1", + "query": query, + "documents": texts, + "return_documents": False, + "max_chunks_per_doc": 832, + "overlap_tokens": 32 + } + headers = { + "accept": "application/json", + "content-type": "application/json", + "authorization": self.client['api_token'] + } + response = requests.post(url, json=payload, headers=headers) + json_obj = json.loads(response.text) + results = json_obj['results'] + indexes_list = [round(item['index']) for item in results] + indexes = np.array(indexes_list).astype(np.int32) + return indexes[0:self.topn] + + # get descending order + return scores.argsort()[::-1][0:self.topn] + + def rerank(self, query: str, chunks: List[Chunk]): + """Rerank faiss search results.""" + if not chunks: + return [] + + texts = [] + for chunk in chunks: + texts.append(chunk.content_or_path) + + # During reranking, we just take image path as text + indexes = self._sort(texts=texts, query=query) + return [chunks[i] for i in indexes] diff --git a/repodir/huixiangdou/huixiangdou/primitive/query.py b/repodir/huixiangdou/huixiangdou/primitive/query.py new file mode 100644 index 00000000..00c848ae --- /dev/null +++ b/repodir/huixiangdou/huixiangdou/primitive/query.py @@ -0,0 +1,61 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from dataclasses import dataclass, field +from enum import Enum +import math + +# Copy from langchain +class DistanceStrategy(str, Enum): + """Enumerator of the Distance strategies for calculating distances + between vectors.""" + EUCLIDEAN_DISTANCE = "EUCLIDEAN_DISTANCE" + MAX_INNER_PRODUCT = "MAX_INNER_PRODUCT" + UNKNOWN = 'UNKNOWN' + + @staticmethod + def euclidean_relevance_score_fn(distance: float) -> float: + """Return a similarity score on a scale [0, 1].""" + # The 'correct' relevance function + # may differ depending on a few things, including: + # - the distance / similarity metric used by the VectorStore + # - the scale of your embeddings (OpenAI's are unit normed. Many + # others are not!) + # - embedding dimensionality + # - etc. + # This function converts the Euclidean norm of normalized embeddings + # (0 is most similar, sqrt(2) most dissimilar) + # to a similarity function (0 to 1) + return 1.0 - distance / math.sqrt(2) + + @staticmethod + def max_inner_product_relevance_score_fn(similarity: float) -> float: + """Normalize the distance to a score on a scale [0, 1].""" + return similarity + +@dataclass +class Query(): + text: str = None + image: str = None + audio: str = None + + def __str__(self) -> str: + """Override __str__ to restrict it to text, image and audio.""" + # The format matches pydantic format for __str__. + # + # The purpose of this change is to make sure that user code that + # feeds Document objects directly into prompts remains unchanged + # due to the addition of the id field (or any other fields in the future). + # + # This override will likely be removed in the future in favor of + # a more general solution of formatting content directly inside the prompts. + + formatted = '' + if self.text is not None: + formatted += f"text='{self.text}' " + if self.image is not None: + formatted += f"image='{self.image}' " + if self.audio is not None: + formatted += f"audio='{self.audio}' " + return formatted + + def __repr__(self) -> str: + return self.__str__() diff --git a/repodir/huixiangdou/huixiangdou/primitive/rpm.py b/repodir/huixiangdou/huixiangdou/primitive/rpm.py new file mode 100644 index 00000000..fc312bb5 --- /dev/null +++ b/repodir/huixiangdou/huixiangdou/primitive/rpm.py @@ -0,0 +1,38 @@ +import time +from datetime import datetime, timedelta +from loguru import logger + +class RPM: + + def __init__(self, rpm: int = 30): + self.rpm = rpm + self.record = {'slot': self.get_minute_slot(), 'counter': 0} + + def get_minute_slot(self): + current_time = time.time() + dt_object = datetime.fromtimestamp(current_time) + total_minutes_since_midnight = dt_object.hour * 60 + dt_object.minute + return total_minutes_since_midnight + + def wait(self, silent=False): + current = time.time() + dt_object = datetime.fromtimestamp(current) + minute_slot = self.get_minute_slot() + + if self.record['slot'] == minute_slot: + # check RPM exceed + if self.record['counter'] >= self.rpm: + # wait until next minute + next_minute = dt_object.replace( + second=0, microsecond=0) + timedelta(minutes=1) + _next = next_minute.timestamp() + sleep_time = abs(_next - current) + time.sleep(sleep_time) + + self.record = {'slot': self.get_minute_slot(), 'counter': 0} + else: + self.record = {'slot': self.get_minute_slot(), 'counter': 0} + self.record['counter'] += 1 + + if not silent: + logger.debug(self.record) diff --git a/repodir/huixiangdou/huixiangdou/primitive/splitter.py b/repodir/huixiangdou/huixiangdou/primitive/splitter.py new file mode 100644 index 00000000..e553d5b5 --- /dev/null +++ b/repodir/huixiangdou/huixiangdou/primitive/splitter.py @@ -0,0 +1,647 @@ +# Copyright (c) OpenMMLab. All rights reserved. +# modified from langchain +# 1. Use `Chunk` instead of `Document` +# 2. Default chunksize using 832 +# 3. `MarkdownSplitter` support parsing URI (file or URI) +import copy +import os +import pdb +import json +import re +from abc import ABC, abstractmethod +from typing import (AbstractSet, Any, Callable, Collection, Dict, Iterable, + List, Literal, Optional, Sequence, Tuple, Type, TypedDict, + TypeVar, Union) + +from loguru import logger + +from .chunk import Chunk +from .file_operation import FileOperation + + +class LineType(TypedDict): + """Line type as typed dict.""" + + metadata: Dict[str, str] + content: str + + +class HeaderType(TypedDict): + """Header type as typed dict.""" + + level: int + name: str + data: str + + +class TextSplitter(ABC): + """Interface for splitting text into chunks.""" + + def __init__( + self, + chunk_size: int = 832, + chunk_overlap: int = 32, + length_function: Callable[[str], int] = len, + keep_separator: Union[bool, Literal['start', 'end']] = False, + add_start_index: bool = False, + strip_whitespace: bool = True, + ) -> None: + """Create a new TextSplitter. + + Args: + chunk_size: Maximum size of chunks to return + chunk_overlap: Overlap in characters between chunks + length_function: Function that measures the length of given chunks + keep_separator: Whether to keep the separator and where to place it + in each corresponding chunk (True='start') + add_start_index: If `True`, includes chunk's start index in metadata + strip_whitespace: If `True`, strips whitespace from the start and end of + every chunk + """ + if chunk_overlap > chunk_size: + raise ValueError( + f'Got a larger chunk overlap ({chunk_overlap}) than chunk size ' + f'({chunk_size}), should be smaller.') + self._chunk_size = chunk_size + self._chunk_overlap = chunk_overlap + self._length_function = length_function + self._keep_separator = keep_separator + self._add_start_index = add_start_index + self._strip_whitespace = strip_whitespace + + @abstractmethod + def split_text(self, text: str) -> List[str]: + """Split text into multiple components.""" + + def create_chunks(self, + texts: List[str], + metadatas: Optional[List[dict]] = None) -> List[Chunk]: + """Create chunks from a list of texts.""" + _metadatas = metadatas or [{}] * len(texts) + chunks = [] + for i, text in enumerate(texts): + index = 0 + previous_chunk_len = 0 + for chunk in self.split_text(text): + metadata = copy.deepcopy(_metadatas[i]) + if self._add_start_index: + offset = index + previous_chunk_len - self._chunk_overlap + index = text.find(chunk, max(0, offset)) + metadata['start_index'] = index + previous_chunk_len = len(chunk) + new_chunk = Chunk(content_or_path=chunk, metadata=metadata) + chunks.append(new_chunk) + return chunks + + def _join_chunks(self, chunks: List[str], separator: str) -> Optional[str]: + text = separator.join(chunks) + if self._strip_whitespace: + text = text.strip() + if text == '': + return None + else: + return text + + def _merge_splits(self, splits: Iterable[str], + separator: str) -> List[str]: + # We now want to combine these smaller pieces into medium size + # chunks to send to the LLM. + separator_len = self._length_function(separator) + + chunks = [] + current_chunk: List[str] = [] + total = 0 + for d in splits: + _len = self._length_function(d) + if (total + _len + (separator_len if len(current_chunk) > 0 else 0) + > self._chunk_size): + if total > self._chunk_size: + logger.warning( + f'Created a chunk of size {total}, ' + f'which is longer than the specified {self._chunk_size}' + ) + if len(current_chunk) > 0: + chunk = self._join_chunks(current_chunk, separator) + if chunk is not None: + chunks.append(chunk) + # Keep on popping if: + # - we have a larger chunk than in the chunk overlap + # - or if we still have any chunks and the length is long + while total > self._chunk_overlap or ( + total + _len + + (separator_len if len(current_chunk) > 0 else 0) > + self._chunk_size and total > 0): + total -= self._length_function(current_chunk[0]) + ( + separator_len if len(current_chunk) > 1 else 0) + current_chunk = current_chunk[1:] + current_chunk.append(d) + total += _len + (separator_len if len(current_chunk) > 1 else 0) + chunk = self._join_chunks(current_chunk, separator) + if chunk is not None: + chunks.append(chunk) + return chunks + + +def _split_text_with_regex( + text: str, separator: str, + keep_separator: Union[bool, Literal['start', 'end']]) -> List[str]: + # Now that we have the separator, split the text + if separator: + if keep_separator: + # The parentheses in the pattern keep the delimiters in the result. + _splits = re.split(f'({separator})', text) + splits = (([ + _splits[i] + _splits[i + 1] + for i in range(0, + len(_splits) - 1, 2) + ]) if keep_separator == 'end' else ([ + _splits[i] + _splits[i + 1] for i in range(1, len(_splits), 2) + ])) + if len(_splits) % 2 == 0: + splits += _splits[-1:] + splits = ((splits + [_splits[-1]]) if keep_separator == 'end' else + ([_splits[0]] + splits)) + else: + splits = re.split(separator, text) + else: + splits = list(text) + return [s for s in splits if s != ''] + + +class CharacterTextSplitter(TextSplitter): + """Splitting text that looks at characters.""" + + def __init__(self, + separator: str = '\n\n', + is_separator_regex: bool = False, + **kwargs: Any) -> None: + """Create a new TextSplitter.""" + super().__init__(**kwargs) + self._separator = separator + self._is_separator_regex = is_separator_regex + + def split_text(self, text: str) -> List[str]: + """Split incoming text and return chunks.""" + # First we naively split the large input into a bunch of smaller ones. + separator = (self._separator if self._is_separator_regex else + re.escape(self._separator)) + splits = _split_text_with_regex(text, separator, self._keep_separator) + _separator = '' if self._keep_separator else self._separator + return self._merge_splits(splits, _separator) + + +class RecursiveCharacterTextSplitter(TextSplitter): + """Splitting text by recursively look at characters. + + Recursively tries to split by different characters to find one that works. + """ + + def __init__( + self, + separators: Optional[List[str]] = None, + keep_separator: bool = True, + is_separator_regex: bool = False, + **kwargs: Any, + ) -> None: + """Create a new TextSplitter.""" + super().__init__(keep_separator=keep_separator, **kwargs) + self._separators = separators or ['\n\n', '\n', ' ', ''] + self._is_separator_regex = is_separator_regex + + def _split_text(self, text: str, separators: List[str]) -> List[str]: + """Split incoming text and return chunks.""" + final_chunks = [] + # Get appropriate separator to use + separator = separators[-1] + new_separators = [] + for i, _s in enumerate(separators): + _separator = _s if self._is_separator_regex else re.escape(_s) + if _s == '': + separator = _s + break + if re.search(_separator, text): + separator = _s + new_separators = separators[i + 1:] + break + + _separator = separator if self._is_separator_regex else re.escape( + separator) + splits = _split_text_with_regex(text, _separator, self._keep_separator) + + # Now go merging things, recursively splitting longer texts. + _good_splits = [] + _separator = '' if self._keep_separator else separator + for s in splits: + if self._length_function(s) < self._chunk_size: + _good_splits.append(s) + else: + if _good_splits: + merged_text = self._merge_splits(_good_splits, _separator) + final_chunks.extend(merged_text) + _good_splits = [] + if not new_separators: + final_chunks.append(s) + else: + other_info = self._split_text(s, new_separators) + final_chunks.extend(other_info) + if _good_splits: + merged_text = self._merge_splits(_good_splits, _separator) + final_chunks.extend(merged_text) + return final_chunks + + def split_text(self, text: str) -> List[str]: + return self._split_text(text, self._separators) + + +# modified from https://github.com/chatchat-space/Langchain-Chatchat/blob/master/text_splitter/chinese_recursive_text_splitter.py +class ChineseRecursiveTextSplitter(RecursiveCharacterTextSplitter): + + def __init__( + self, + separators: Optional[List[str]] = None, + keep_separator: bool = True, + is_separator_regex: bool = True, + **kwargs: Any, + ) -> None: + """Create a new TextSplitter.""" + super().__init__(keep_separator=keep_separator, **kwargs) + self._separators = separators or [ + '\n\n', '\n', '。|!|?', '\.\s|\!\s|\?\s', ';|;\s', ',|,\s' + ] + self._is_separator_regex = is_separator_regex + + def _split_text_with_regex_from_end(self, text: str, separator: str, + keep_separator: bool) -> List[str]: + # Now that we have the separator, split the text + if separator: + if keep_separator: + # The parentheses in the pattern keep the delimiters in the result. + _splits = re.split(f'({separator})', text) + splits = [ + ''.join(i) for i in zip(_splits[0::2], _splits[1::2]) + ] + if len(_splits) % 2 == 1: + splits += _splits[-1:] + # splits = [_splits[0]] + splits + else: + splits = re.split(separator, text) + else: + splits = list(text) + return [s for s in splits if s != ''] + + def _split_text(self, text: str, separators: List[str]) -> List[str]: + """Split incoming text and return chunks.""" + final_chunks = [] + # Get appropriate separator to use + separator = separators[-1] + new_separators = [] + for i, _s in enumerate(separators): + _separator = _s if self._is_separator_regex else re.escape(_s) + if _s == '': + separator = _s + break + if re.search(_separator, text): + separator = _s + new_separators = separators[i + 1:] + break + + _separator = separator if self._is_separator_regex else re.escape( + separator) + splits = self._split_text_with_regex_from_end(text, _separator, + self._keep_separator) + + # Now go merging things, recursively splitting longer texts. + _good_splits = [] + _separator = '' if self._keep_separator else separator + for s in splits: + if self._length_function(s) < self._chunk_size: + _good_splits.append(s) + else: + if _good_splits: + merged_text = self._merge_splits(_good_splits, _separator) + final_chunks.extend(merged_text) + _good_splits = [] + if not new_separators: + final_chunks.append(s) + else: + other_info = self._split_text(s, new_separators) + final_chunks.extend(other_info) + if _good_splits: + merged_text = self._merge_splits(_good_splits, _separator) + final_chunks.extend(merged_text) + return [ + re.sub(r'\n{2,}', '\n', chunk.strip()) for chunk in final_chunks + if chunk.strip() != '' + ] + + +class MarkdownTextRefSplitter(RecursiveCharacterTextSplitter): + """Attempts to split the text along Markdown-formatted headings.""" + + def __init__(self, **kwargs: Any) -> None: + """Initialize a MarkdownTextRefSplitter.""" + separators = [ + # First, try to split along Markdown headings (starting with level 2) + '\n#{1,6} ', + # Note the alternative syntax for headings (below) is not handled here + # Heading level 2 + # --------------- + # End of code block + '```\n', + # Horizontal lines + '\n\\*\\*\\*+\n', + '\n---+\n', + '\n___+\n', + # Note that this splitter doesn't handle horizontal lines defined + # by *three or more* of ***, ---, or ___, but this is not handled + '\n\n', + '\n', + ' ', + '' + ] + super().__init__(separators=separators, **kwargs) + + +class MarkdownHeaderTextSplitter: + """Splitting markdown files based on specified headers.""" + + def __init__( + self, + headers_to_split_on: List[Tuple[str, str]] = [ + ('#', 'Header 1'), + ('##', 'Header 2'), + ('###', 'Header 3'), + ], + strip_headers: bool = True, + ): + """Create a new MarkdownHeaderTextSplitter. + + Args: + headers_to_split_on: Headers we want to track + strip_headers: Strip split headers from the content of the chunk + """ + # Given the headers we want to split on, + # (e.g., "#, ##, etc") order by length + self.headers_to_split_on = sorted( + headers_to_split_on, key=lambda split: len(split[0]), reverse=True + ) + # Strip headers split headers from the content of the chunk + self.strip_headers = strip_headers + super().__init__() + + def aggregate_lines_to_chunks(self, lines: List[LineType], + base_meta: dict) -> List[Chunk]: + """Combine lines with common metadata into chunks + Args: + lines: Line of text / associated header metadata + """ + aggregated_chunks: List[LineType] = [] + + for line in lines: + if ( + aggregated_chunks + and aggregated_chunks[-1]["metadata"] == line["metadata"] + ): + + # If the last line in the aggregated list + # has the same metadata as the current line, + # append the current content to the last lines's content + aggregated_chunks[-1]["content"] += " \n" + line["content"] + elif ( + aggregated_chunks + and aggregated_chunks[-1]["metadata"] != line["metadata"] + # may be issues if other metadata is present + and len(aggregated_chunks[-1]["metadata"]) < len(line["metadata"]) + and aggregated_chunks[-1]["content"].split("\n")[-1][0] == "#" + and not self.strip_headers + ): + # If the last line in the aggregated list + # has different metadata as the current line, + # and has shallower header level than the current line, + # and the last line is a header, + # and we are not stripping headers, + # append the current content to the last line's content + aggregated_chunks[-1]["content"] += " \n" + line["content"] + # and update the last line's metadata + aggregated_chunks[-1]["metadata"] = line["metadata"] + + else: + # Otherwise, append the current line to the aggregated list + aggregated_chunks.append(line) + + return [ + Chunk(content_or_path=chunk["content"], + metadata=dict(chunk['metadata'], **base_meta)) + for chunk in aggregated_chunks + ] + + def create_chunks(self, text: str, metadata: dict = {}) -> List[Chunk]: + """Split markdown file + Args: + text: Markdown file""" + + # Split the input text by newline character ("\n"). + lines = text.split("\n") + # Final output + lines_with_metadata: List[LineType] = [] + # Content and metadata of the chunk currently being processed + current_content: List[str] = [] + current_metadata: Dict[str, str] = {} + # Keep track of the nested header structure + # header_stack: List[Dict[str, Union[int, str]]] = [] + header_stack: List[HeaderType] = [] + initial_metadata: Dict[str, str] = {} + + in_code_block = False + opening_fence = "" + + for line in lines: + stripped_line = line.strip() + # Remove all non-printable characters from the string, keeping only visible + # text. + stripped_line = "".join(filter(str.isprintable, stripped_line)) + if not in_code_block: + # Exclude inline code spans + if stripped_line.startswith("```") and stripped_line.count("```") == 1: + in_code_block = True + opening_fence = "```" + elif stripped_line.startswith("~~~"): + in_code_block = True + opening_fence = "~~~" + else: + if stripped_line.startswith(opening_fence): + in_code_block = False + opening_fence = "" + + if in_code_block: + current_content.append(stripped_line) + continue + + # Check each line against each of the header types (e.g., #, ##) + for sep, name in self.headers_to_split_on: + # Check if line starts with a header that we intend to split on + if stripped_line.startswith(sep) and ( + # Header with no text OR header is followed by space + # Both are valid conditions that sep is being used a header + len(stripped_line) == len(sep) or stripped_line[len(sep)] == " " + ): + # Ensure we are tracking the header as metadata + if name is not None: + # Get the current header level + current_header_level = sep.count("#") + + # Pop out headers of lower or same level from the stack + while ( + header_stack + and header_stack[-1]["level"] >= current_header_level + ): + # We have encountered a new header + # at the same or higher level + popped_header = header_stack.pop() + # Clear the metadata for the + # popped header in initial_metadata + if popped_header["name"] in initial_metadata: + initial_metadata.pop(popped_header["name"]) + + # Push the current header to the stack + header: HeaderType = { + "level": current_header_level, + "name": name, + "data": stripped_line[len(sep) :].strip(), + } + header_stack.append(header) + # Update initial_metadata with the current header + initial_metadata[name] = header["data"] + + # Add the previous line to the lines_with_metadata + # only if current_content is not empty + if current_content: + lines_with_metadata.append( + { + "content": "\n".join(current_content), + "metadata": current_metadata.copy(), + } + ) + current_content.clear() + + if not self.strip_headers: + current_content.append(stripped_line) + + break + else: + if stripped_line: + current_content.append(stripped_line) + elif current_content: + lines_with_metadata.append( + { + "content": "\n".join(current_content), + "metadata": current_metadata.copy(), + } + ) + current_content.clear() + + current_metadata = initial_metadata.copy() + + if current_content: + lines_with_metadata.append( + {"content": "\n".join(current_content), "metadata": current_metadata} + ) + + # lines_with_metadata has each line with associated header metadata + # aggregate these into chunks based on common metadata + return self.aggregate_lines_to_chunks(lines_with_metadata, + base_meta=metadata) + +def nested_split_markdown(filepath: str, + text: str, + chunksize: int = 832, + metadata: dict = {}): + """First split by header, then by length. + + `header` should be part of content. + """ + head_splitter = MarkdownHeaderTextSplitter() + chunks = head_splitter.create_chunks(text, metadata=metadata) + text_chunks = [] + image_chunks = [] + + text_ref_splitter = MarkdownTextRefSplitter(chunk_size=chunksize) + md_image_pattern = re.compile(r'\[([^\]]+)\]\(([a-zA-Z0-9:/._~#-]+)?\)') + html_image_pattern = re.compile(r']*?src=["\']([^"\']*)["\'][^>]*>') + file_opr = FileOperation() + + for chunk in chunks: + header = '' + if 'Header 1' in chunk.metadata: + header += chunk.metadata['Header 1'] + if 'Header 2' in chunk.metadata: + header += ' ' + header += chunk.metadata['Header 2'] + if 'Header 3' in chunk.metadata: + header += ' ' + header += chunk.metadata['Header 3'] + + if len(chunk.content_or_path) > chunksize: + subchunks = text_ref_splitter.create_chunks([chunk.content_or_path], [chunk.metadata]) + + for subchunk in subchunks: + if len(subchunk.content_or_path) >= 10: + subchunk.content_or_path = '{} {}'.format(header, subchunk.content_or_path.lower()) + text_chunks.append(subchunk) + + elif len(chunk.content_or_path) >= 10: + content = '{} {}'.format(header, chunk.content_or_path.lower()) + text_chunks.append(Chunk(content, metadata)) + + # extract images path + dirname = os.path.dirname(filepath) + + image_paths = [] + for match in md_image_pattern.findall(chunk.content_or_path): + image_paths.append(match[1]) + for match in html_image_pattern.findall(chunk.content_or_path): + image_paths.append(match) + for image_path in image_paths: + if file_opr.get_type(image_path) != 'image': + continue + + if image_path.startswith('http'): + continue + + if not os.path.isabs(image_path): + image_path = os.path.join(dirname, image_path) + + if os.path.exists(image_path): + c = Chunk(content_or_path=image_path, + metadata=metadata.copy(), + modal='image') + image_chunks.append(c) + else: + logger.error( + f'image cannot access. file: {filepath}, image path: {image_path}' + ) + + # logger.info('{} text_chunks, {} image_chunks'.format(len(text_chunks), len(image_chunks))) + return text_chunks + image_chunks + +def clean_md(text: str): + """Remove parts of the markdown document that do not contain the key + question words, such as code blocks, URL links, etc.""" + # remove ref + pattern_ref = r'\[(.*?)\]\(.*?\)' + new_text = re.sub(pattern_ref, r'\1', text) + + # remove code block + pattern_code = r'```.*?```' + new_text = re.sub(pattern_code, '', new_text, flags=re.DOTALL) + + # remove underline + new_text = re.sub('_{5,}', '', new_text) + + # remove table + # new_text = re.sub('\|.*?\|\n\| *\:.*\: *\|.*\n(\|.*\|.*\n)*', '', new_text, flags=re.DOTALL) # noqa E501 + + # use lower + new_text = new_text.lower() + return new_text + diff --git a/repodir/huixiangdou/huixiangdou/rag.py b/repodir/huixiangdou/huixiangdou/rag.py new file mode 100644 index 00000000..dc88f421 --- /dev/null +++ b/repodir/huixiangdou/huixiangdou/rag.py @@ -0,0 +1,138 @@ +#!/usr/bin/env python3 +# Copyright (c) OpenMMLab. All rights reserved. +"""HuixiangDou binary.""" +import argparse +import json +import os +import time +from multiprocessing import Pool, Process, Value + +import pytoml +import requests +from loguru import logger + +from .service import ErrorCode, SerialPipeline, llm_serve + + +class Task: + + def __init__(self, id: int, query: str, direct_reply: str = ''): + """Build rag task, direct_reply is original LLM response.""" + self.id = id + self.query = query + self.direct_reply = direct_reply + self.rag_reply = '' + self.code = -1 + self.reason = '' + self.refs = [] + + def to_json_str(self): + obj = { + 'id': int(self.id), + 'query': str(self.query), + 'direct_reply': str(self.direct_reply), + 'rag_reply': str(self.rag_reply), + 'code': int(self.code), + 'reason': str(self.reason), + 'refs': self.refs + } + return json.dumps(obj, indent=2, ensure_ascii=False) + + +def parse_args(): + """Parse args.""" + parser = argparse.ArgumentParser(description='SerialPipeline.') + parser.add_argument('--work_dir', + type=str, + default='workdir', + help='Working directory.') + parser.add_argument( + '--config_path', + default='config-alignment.ini', + type=str, + help='SerialPipeline configuration path. Default value is config.ini') + parser.add_argument( + '--input', + default='resource/rag_example_input.json', + type=str, + help= + 'JSON filepath for user queries. Default value is `resource/rag_example_input.json`' + ) + parser.add_argument( + '--output-dir', + default='resource', + type=str, + help='Formatted JSON output dir, use `resource/` by default') + parser.add_argument( + '--processes', + default=1, + type=int, + help='Process count considered LLM RPM. Default value is 2') + args = parser.parse_args() + return args + + +def rag(process_id: int, task: list, output_dir: str): + """Extract structured output with RAG.""" + + assistant = SerialPipeline(work_dir=args.work_dir, config_path=args.config_path) + + # assistant.TOPIC_TEMPLATE = '告诉我这句话的关键字和主题,直接说主题不要解释:“{}”' + output_path = os.path.join(output_dir, 'output{}.json'.format(process_id)) + for item in task: + query = item.query + + for sess in assistant.generate(query=query, history=[], groupname='') + item.rag_reply = sess.response + item.code = int(sess.code) + item.reason = str(sess.code) + item.refs = sess.references + + if item.code == 0: + item.direct_reply = assistant.direct_chat(query=query) + + with open(output_path, 'a') as f: + f.write(item.to_json_str()) + f.write('\n') + + +def split_tasks(json_path: str, processes: int): + """Split queries for multiple processes.""" + queries = [] + tasks = [] + _all = [] + with open(json_path) as f: + queries = json.load(f) + + for idx, query in enumerate(queries): + _all.append(Task(idx, query)) + + step = (len(_all) + processes - 1) // processes + for idx in range(processes): + start = idx * step + tasks.append(_all[start:start + step]) + + # check task number and assert + _sum = 0 + for task in tasks: + _sum += len(task) + assert _sum == len(queries) + + return tasks + + +if __name__ == '__main__': + args = parse_args() + + tasks = split_tasks(args.input, args.processes) + + if args.processes == 1: + rag(0, tasks[0], args.output_dir) + else: + pool = Pool(args.processes) + for process_id in range(args.processes): + pool.apply_async(rag, + (process_id, tasks[process_id], args.output_dir)) + pool.close() + logger.debug('waiting for preprocess read finish..') + pool.join() diff --git a/repodir/huixiangdou/huixiangdou/server.py b/repodir/huixiangdou/huixiangdou/server.py new file mode 100644 index 00000000..0d6b68f4 --- /dev/null +++ b/repodir/huixiangdou/huixiangdou/server.py @@ -0,0 +1,141 @@ +import argparse +import os +import time + +import pytoml +import requests +from aiohttp import web +from loguru import logger +from termcolor import colored + +from .service import ErrorCode, SerialPipeline, ParallelPipeline, start_llm_server +from .primitive import Query +import asyncio +from fastapi import FastAPI, APIRouter +from fastapi.responses import StreamingResponse +from fastapi.middleware.cors import CORSMiddleware +from pydantic import BaseModel +import uvicorn +import json +from typing import List + +assistant = None +app = FastAPI(docs_url='/') + +class Talk(BaseModel): + text: str + image: str = '' + +def format_refs(refs: List[str]): + refs_filter = list(set(refs)) + if len(refs) < 1: + return '' + + text = '**References:**\r\n' + for file_or_url in refs_filter: + text += '* {}\r\n'.format(file_or_url) + text += '\r\n' + return text + +@app.post("/huixiangdou_inference") +async def huixiangdou_inference(talk: Talk): + global assistant + query = Query(talk.text, talk.image) + + pipeline = {'step': []} + debug = dict() + if type(assistant) is SerialPipeline: + for sess in assistant.generate(query=query): + status = { + "state":str(sess.code), + "response": sess.response, + "refs": sess.references + } + + pipeline['step'].append(status) + pipeline['debug'] = sess.debug + return pipeline + + else: + sentence = '' + async for sess in assistant.generate(query=query, enable_web_search=False): + if sentence == '' and len(sess.references) > 0: + sentence = format_refs(sess.references) + + if len(sess.delta) > 0: + sentence += sess.delta + return sentence + + +@app.post("/huixiangdou_stream") +async def huixiangdou_stream(talk: Talk): + global assistant + query = Query(talk.text, talk.image) + + pipeline = {'step': []} + debug = dict() + + def event_stream(): + for sess in assistant.generate(query=query): + status = { + "state":str(sess.code), + "response": sess.response, + "refs": sess.references + } + + pipeline['step'].append(status) + pipeline['debug'] = sess.debug + yield json.dumps(pipeline) + + async def event_stream_async(): + sentence = '' + async for sess in assistant.generate(query=query, enable_web_search=False): + if sentence == '' and len(sess.references) > 0: + sentence = format_refs(sess.references) + + if len(sess.delta) > 0: + sentence += sess.delta + yield sentence + + if type(assistant) is SerialPipeline: + return StreamingResponse(event_stream(), media_type="text/event-stream") + else: + return StreamingResponse(event_stream_async(), media_type="text/event-stream") + +def parse_args(): + """Parse args.""" + parser = argparse.ArgumentParser(description='SerialPipeline.') + parser.add_argument('--work_dir', + type=str, + default='workdir', + help='Working directory.') + parser.add_argument( + '--config_path', + default='config.ini', + type=str, + help='Configuration path. Default value is config.ini') + parser.add_argument('--pipeline', type=str, choices=['chat_with_repo', 'chat_in_group'], default='chat_with_repo', + help='Select pipeline type for difference scenario, default value is `chat_with_repo`') + parser.add_argument('--standalone', + action='store_true', + default=True, + help='Auto deploy required Hybrid LLM Service.') + parser.add_argument('--no-standalone', + action='store_false', + dest='standalone', # 指定与上面参数相同的目标 + help='Do not auto deploy required Hybrid LLM Service.') + args = parser.parse_args() + return args + +if __name__ == '__main__': + args = parse_args() + # start service + if args.standalone is True: + # hybrid llm serve + start_llm_server(config_path=args.config_path) + # setup chat service + if 'chat_with_repo' in args.pipeline: + assistant = ParallelPipeline(work_dir=args.work_dir, config_path=args.config_path) + elif 'chat_in_group' in args.pipeline: + assistant = SerialPipeline(work_dir=args.work_dir, config_path=args.config_path) + uvicorn.run(app, host='0.0.0.0', port=23333, log_level='info') diff --git a/repodir/huixiangdou/huixiangdou/service/__init__.py b/repodir/huixiangdou/huixiangdou/service/__init__.py new file mode 100644 index 00000000..1ee853c5 --- /dev/null +++ b/repodir/huixiangdou/huixiangdou/service/__init__.py @@ -0,0 +1,16 @@ +# Copyright (c) OpenMMLab. All rights reserved. +"""LLM service module.""" +from .config import (feature_store_base_dir, redis_host, redis_passwd, + redis_port) +from .feature_store import FeatureStore # noqa E401 +from .helper import (ErrorCode, QueryTracker, Queue, TaskCode, + build_reply_text, check_str_useful, histogram, kimi_ocr, + multimodal, parse_json_str) +from .kg import KnowledgeGraph # noqa E401 +from .llm_client import ChatClient # noqa E401 +from .llm_server_hybrid import (HybridLLMServer, InferenceWrapper, llm_serve, + start_llm_server) +from .retriever import CacheRetriever, Retriever # noqa E401 +from .web_search import WebSearch # noqa E401 +from .serial_pipeline import SerialPipeline +from .parallel_pipeline import ParallelPipeline diff --git a/repodir/huixiangdou/huixiangdou/service/config.py b/repodir/huixiangdou/huixiangdou/service/config.py new file mode 100644 index 00000000..f992b7b7 --- /dev/null +++ b/repodir/huixiangdou/huixiangdou/service/config.py @@ -0,0 +1,30 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import os + +from loguru import logger + + +def redis_host(): + host = os.getenv('REDIS_HOST') + if host is None or len(host) < 1: + raise Exception('REDIS_HOST not config') + return host + + +def redis_port(): + port = os.getenv('REDIS_PORT') + if port is None: + logger.debug('REDIS_PORT not set, try 6379') + port = 6379 + return port + + +def redis_passwd(): + passwd = os.getenv('REDIS_PASSWORD') + if passwd is None or len(passwd) < 1: + raise Exception('REDIS_PASSWORD not config') + return passwd + + +def feature_store_base_dir(): + return 'feature_stores' diff --git a/repodir/huixiangdou/huixiangdou/service/feature_store.py b/repodir/huixiangdou/huixiangdou/service/feature_store.py new file mode 100644 index 00000000..68b2e9a8 --- /dev/null +++ b/repodir/huixiangdou/huixiangdou/service/feature_store.py @@ -0,0 +1,404 @@ +# Copyright (c) OpenMMLab. All rights reserved. +"""extract feature and search with user query.""" +import argparse +import json +import os +import pdb +import re +import shutil +from multiprocessing import Pool +from typing import Any, Dict, List, Optional + +import pytoml +from loguru import logger +from torch.cuda import empty_cache +from tqdm import tqdm + +from ..primitive import (ChineseRecursiveTextSplitter, Chunk, Embedder, Faiss, + FileName, FileOperation, + RecursiveCharacterTextSplitter, nested_split_markdown) +from .helper import histogram +from .llm_server_hybrid import start_llm_server +from .retriever import CacheRetriever, Retriever + + +def read_and_save(file: FileName): + if os.path.exists(file.copypath): + # already exists, return + logger.info('already exist, skip load') + return + file_opr = FileOperation() + logger.info('reading {}, would save to {}'.format(file.origin, + file.copypath)) + content, error = file_opr.read(file.origin) + if error is not None: + logger.error('{} load error: {}'.format(file.origin, str(error))) + return + + if content is None or len(content) < 1: + logger.warning('{} empty, skip save'.format(file.origin)) + return + + with open(file.copypath, 'w') as f: + f.write(content) + + +class FeatureStore: + """Tokenize and extract features from the project's documents, for use in + the reject pipeline and response pipeline.""" + + def __init__(self, + embedder: Embedder, + config_path: str = 'config.ini', + language: str = 'zh', + chunk_size=900, + analyze_reject=False, + rejecter_naive_splitter=False, + override=False) -> None: + """Init with model device type and config.""" + self.config_path = config_path + self.reject_throttle = -1 + self.language = language + self.override = override + with open(config_path, encoding='utf8') as f: + config = pytoml.load(f)['feature_store'] + self.reject_throttle = config['reject_throttle'] + + logger.debug('loading text2vec model..') + self.embedder = embedder + self.retriever = None + self.chunk_size = chunk_size + self.analyze_reject = analyze_reject + + if rejecter_naive_splitter: + raise ValueError( + 'The `rejecter_naive_splitter` option deprecated, please `git checkout v20240722`' + ) + + if analyze_reject: + raise ValueError( + 'The `analyze_reject` option deprecated, please `git checkout v20240722`' + ) + + logger.info('init dense retrieval database with chunk_size {}'.format(chunk_size)) + + if language == 'zh': + self.text_splitter = ChineseRecursiveTextSplitter( + keep_separator=True, + is_separator_regex=True, + chunk_size=chunk_size, + chunk_overlap=32) + else: + self.text_splitter = RecursiveCharacterTextSplitter( + chunk_size=chunk_size, chunk_overlap=32) + + def parse_markdown(self, file: FileName, metadata: Dict): + length = 0 + text = file.basename + '\n' + + with open(file.copypath, encoding='utf8') as f: + text += f.read() + if len(text) <= 1: + return [], length + + chunks = nested_split_markdown(file.origin, + text=text, + chunksize=self.chunk_size, + metadata=metadata) + for c in chunks: + length += len(c.content_or_path) + return chunks, length + + def build_dense(self, files: list, work_dir: str, markdown_as_txt: bool=False): + """Extract the features required for the response pipeline based on the + document.""" + feature_dir = os.path.join(work_dir, 'db_dense') + if not os.path.exists(feature_dir): + os.makedirs(feature_dir) + + file_opr = FileOperation() + chunks = [] + + for i, file in enumerate(files): + if not file.state: + continue + metadata = {'source': file.origin, 'read': file.copypath} + + # If you need higher rejection precision, set `markdown_as_txt` as True + if not markdown_as_txt and file._type == 'md': + md_chunks, md_length = self.parse_markdown(file=file, + metadata=metadata) + chunks += md_chunks + file.reason = str(md_length) + + else: + # now read pdf/word/excel/ppt text + text, error = file_opr.read(file.copypath) + if error is not None: + file.state = False + file.reason = str(error) + continue + file.reason = str(len(text)) + text = file.prefix + text + chunks += self.text_splitter.create_chunks( + texts=[text], metadatas=[metadata]) + + if not self.embedder.support_image: + filtered_chunks = list(filter(lambda x: x.modal=='text', chunks)) + else: + filtered_chunks = chunks + if len(chunks) < 1: + return + + self.analyze(filtered_chunks) + Faiss.save_local(folder_path=feature_dir, chunks=filtered_chunks, embedder=self.embedder) + + def analyze(self, chunks: List[Chunk]): + """Output documents length mean, median and histogram.""" + + text_lens = [] + token_lens = [] + text_chunk_count = 0 + image_chunk_count = 0 + + if self.embedder is None: + logger.info('self.embedder is None, skip `anaylze_output`') + return + for chunk in chunks: + if chunk.modal == 'image': + image_chunk_count += 1 + elif chunk.modal == 'text': + text_chunk_count += 1 + + content = chunk.content_or_path + text_lens.append(len(content)) + token_lens.append(self.embedder.token_length(content)) + + logger.info('text_chunks {}, image_chunks {}'.format(text_chunk_count, image_chunk_count)) + logger.info('text histogram, {}'.format(histogram(text_lens))) + logger.info('token histogram, {}'.format( + histogram(token_lens))) + + def preprocess(self, files: List, work_dir: str): + """Preprocesses files in a given directory. Copies each file to + 'preprocess' with new name formed by joining all subdirectories with + '_'. + + Args: + files (list): original file list. + work_dir (str): Working directory where preprocessed files will be stored. # noqa E501 + + Returns: + str: Path to the directory where preprocessed markdown files are saved. + + Raises: + Exception: Raise an exception if no markdown files are found in the provided repository directory. # noqa E501 + """ + preproc_dir = os.path.join(work_dir, 'preprocess') + if not os.path.exists(preproc_dir): + os.makedirs(preproc_dir) + + pool = Pool(processes=8) + file_opr = FileOperation() + for idx, file in enumerate(files): + if not os.path.exists(file.origin): + file.state = False + file.reason = 'skip not exist' + continue + + if file._type == 'image': + file.state = False + file.reason = 'skip image' + + elif file._type in ['pdf', 'word', 'excel', 'ppt', 'html']: + # read pdf/word/excel file and save to text format + md5 = file_opr.md5(file.origin) + file.copypath = os.path.join(preproc_dir, + '{}.text'.format(md5)) + pool.apply_async(read_and_save, (file, )) + + elif file._type in ['md', 'text']: + # rename text files to new dir + md5 = file_opr.md5(file.origin) + file.copypath = os.path.join( + preproc_dir, + file.origin.replace('/', '_')[-84:]) + try: + shutil.copy(file.origin, file.copypath) + file.state = True + file.reason = 'preprocessed' + except Exception as e: + file.state = False + file.reason = str(e) + + else: + file.state = False + file.reason = 'skip unknown format' + pool.close() + logger.debug('waiting for file preprocess finish..') + pool.join() + + # check process result + for file in files: + if file._type in ['pdf', 'word', 'excel']: + if os.path.exists(file.copypath): + file.state = True + file.reason = 'preprocessed' + else: + file.state = False + file.reason = 'read error' + + def initialize(self, files: list, work_dir: str): + """Initializes response and reject feature store. + + Only needs to be called once. Also calculates the optimal threshold + based on provided good and bad question examples, and saves it in the + configuration file. + """ + logger.info( + 'initialize response and reject feature store, you only need call this once.' # noqa E501 + ) + self.preprocess(files=files, work_dir=work_dir) + # build dense retrieval refusal-to-answer and response database + self.build_dense(files=files, work_dir=work_dir) + + +def parse_args(): + """Parse command-line arguments.""" + parser = argparse.ArgumentParser( + description='Feature store for processing directories.') + parser.add_argument('--work_dir', + type=str, + default='workdir', + help='Working directory.') + parser.add_argument( + '--repo_dir', + type=str, + default='repodir', + help='Root directory where the repositories are located.') + parser.add_argument( + '--config_path', + default='config.ini', + help='Feature store configuration path. Default value is config.ini') + parser.add_argument( + '--good_questions', + default='resource/good_questions.json', + help= # noqa E251 + 'Positive examples in the dataset. Default value is resource/good_questions.json' # noqa E501 + ) + parser.add_argument( + '--bad_questions', + default='resource/bad_questions.json', + help= # noqa E251 + 'Negative examples json path. Default value is resource/bad_questions.json' # noqa E501 + ) + parser.add_argument( + '--sample', help='Input an json file, save reject and search output.') + parser.add_argument( + '--override', + action='store_true', + default=False, + help='Remove old data and rebuild knowledge graph from scratch.') + args = parser.parse_args() + return args + +def test_reject(retriever: Retriever, sample: str = None): + """Simple test reject pipeline.""" + if sample is None: + real_questions = [ + 'SAM 10个T 的训练集,怎么比比较公平呢~?速度上还有缺陷吧?', + '想问下,如果只是推理的话,amp的fp16是不会省显存么,我看parameter仍然是float32,开和不开推理的显存占用都是一样的。能不能直接用把数据和model都 .half() 代替呢,相比之下amp好在哪里', # noqa E501 + 'mmdeploy支持ncnn vulkan部署么,我只找到了ncnn cpu 版本', + '大佬们,如果我想在高空检测安全帽,我应该用 mmdetection 还是 mmrotate', + '请问 ncnn 全称是什么', + '有啥中文的 text to speech 模型吗?', + '今天中午吃什么?', + 'huixiangdou 是什么?', + 'mmpose 如何安装?', + '使用科研仪器需要注意什么?' + ] + else: + with open(sample) as f: + real_questions = json.load(f) + + for example in real_questions: + relative, score = retriever.is_relative(example) + + if relative: + logger.warning(f'process query: {example}') + else: + logger.error(f'reject query: {example}') + + if sample is not None: + if relative: + with open('workdir/positive.txt', 'a+') as f: + f.write(example) + f.write('\n') + else: + with open('workdir/negative.txt', 'a+') as f: + f.write(example) + f.write('\n') + empty_cache() + + +def test_query(retriever: Retriever, sample: str = None): + """Simple test response pipeline.""" + from texttable import Texttable + if sample is not None: + with open(sample) as f: + real_questions = json.load(f) + logger.add('logs/feature_store_query.log', rotation='4MB') + else: + real_questions = ['mmpose installation', 'how to use std::vector ?'] + + table = Texttable() + table.set_cols_valign(['t', 't', 't', 't']) + table.header(['Query', 'State', 'Part of Chunks', 'References']) + + for example in real_questions: + example = example[0:400] + chunks, context, refs = retriever.query(example) + if chunks: + table.add_row( + [example, 'Accepted', chunks[0:100] + '..', ','.join(refs)]) + else: + table.add_row([example, 'Rejected', 'None', 'None']) + empty_cache() + + logger.info('\n' + table.draw()) + empty_cache() + + +if __name__ == '__main__': + args = parse_args() + cache = CacheRetriever(config_path=args.config_path) + fs_init = FeatureStore(embedder=cache.embedder, + config_path=args.config_path, + override=args.override) + + # walk all files in repo dir + file_opr = FileOperation() + files = file_opr.scan_dir(repo_dir=args.repo_dir) + fs_init.initialize(files=files, work_dir=args.work_dir) + file_opr.summarize(files) + del fs_init + + # update reject throttle + retriever = cache.get(config_path=args.config_path, work_dir=args.work_dir) + if retriever.kg.is_available(): + start_llm_server(args.config_path) + + with open(os.path.join('resource', 'good_questions.json')) as f: + good_questions = json.load(f) + with open(os.path.join('resource', 'bad_questions.json')) as f: + bad_questions = json.load(f) + retriever.update_throttle(config_path=args.config_path, + good_questions=good_questions, + bad_questions=bad_questions) + + cache.pop('default') + + # test + retriever = cache.get(config_path=args.config_path, work_dir=args.work_dir) + test_reject(retriever, args.sample) + test_query(retriever, args.sample) diff --git a/repodir/huixiangdou/huixiangdou/service/helper.py b/repodir/huixiangdou/huixiangdou/service/helper.py new file mode 100644 index 00000000..968dec0f --- /dev/null +++ b/repodir/huixiangdou/huixiangdou/service/helper.py @@ -0,0 +1,377 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import json +import os +import pdb +import re +from enum import Enum +from pathlib import Path +from types import SimpleNamespace +from typing import Any + +import redis +import requests +from loguru import logger +from openai import OpenAI +from texttable import Texttable + +from .config import redis_host, redis_passwd, redis_port + + +class TaskCode(Enum): + FS_ADD_DOC = 'add_doc' + FS_UPDATE_SAMPLE = 'update_sample' + FS_UPDATE_PIPELINE = 'update_pipeline' + CHAT = 'chat' + CHAT_RESPONSE = 'chat_response' + +class ErrorCode(Enum): + """Define an enumerated type for error codes, each has a numeric value and + a description. + + Each enum member is associated with a numeric code and a description + string. The numeric code is used as the return code in function calls, and + the description provides a human-readable explanation of the error. + """ + SUCCESS = 0, 'success' + NOT_A_QUESTION = 1, 'query is not a question' + NO_TOPIC = 2, 'The question does not have a topic. It might be a meaningless sentence.' # noqa E501 + UNRELATED = 3, 'Topics unrelated to the knowledge base. Updating good_questions and bad_questions can improve accuracy.' # noqa E501 + NO_SEARCH_KEYWORDS = 4, 'Cannot extract keywords.' + NO_SEARCH_RESULT = 5, 'No search result.' + BAD_ANSWER = 6, 'Irrelevant answer.' + SECURITY = 7, 'Reply has a high relevance to prohibited topics.' + NOT_WORK_TIME = 8, 'Non-working hours. The config.ini file can be modified to adjust this. **In scenarios where speech may pose risks, let the robot operate under human supervision**' # noqa E501 + + PARAMETER_ERROR = 9, "HTTP interface parameter error. Query cannot be empty; the format of history is list of lists, like [['question1', 'reply1'], ['question2'], ['reply2']]" # noqa E501 + PARAMETER_MISS = 10, 'Missing key in http json input parameters.' + + WORK_IN_PROGRESS = 11, 'Not finish' + FAILED = 12, 'Fail' + BAD_PARAMETER = 13, 'Bad parameter' + INTERNAL_ERROR = 14, 'Internal error' + WEB_SEARCH_FAIL = 15, 'Web search fail, please check network, TOKEN and quota' + SG_SEARCH_FAIL = 16, 'SourceGraph not result, please check token or input query' + LLM_NOT_RESPONSE_SG = 17, 'LLM not response query with sg search' + QUESTION_TOO_SHORT = 18, 'Query length too short' + INIT = 19, 'Init state' + + def __new__(cls, value, description): + """Create new instance of ErrorCode.""" + obj = object.__new__(cls) + obj._value_ = value + obj.description = description + return obj + + def __int__(self): + """Return the integer representation of the error code.""" + return self.value + + def __str__(self): + """Return the str representation of the error code.""" + return self.description + + def describe(self): + """Return the description of the error code.""" + return self.description + + @classmethod + def format(cls, code): + """Format the error code into a JSON result. + + Args: + code (ErrorCode): Error code to be formatted. + + Returns: + dict: A dictionary that includes the error code and its description. # noqa E501 + + Raises: + TypeError: If the input is not an instance of ErrorCode. + """ + if isinstance(code, cls): + return {'code': int(code), 'message': code.describe()} + raise TypeError(f'Expected type {cls}, got {type(code)}') + + +class Queue: + + def __init__(self, name, namespace='HuixiangDou', **redis_kwargs): + self.__db = redis.Redis(host=redis_host(), + port=redis_port(), + password=redis_passwd(), + charset='utf-8', + decode_responses=True) + self.key = '%s:%s' % (namespace, name) + + def qsize(self): + """Return the approximate size of the queue.""" + return self.__db.llen(self.key) + + def empty(self): + """Return True if the queue is empty, False otherwise.""" + return self.qsize() == 0 + + def put(self, item): + """Put item into the queue.""" + self.__db.rpush(self.key, item) + + def peek_tail(self): + return self.__db.lrange(self.key, -1, -1) + + def get(self, block=True, timeout=None): + """Remove and return an item from the queue. + + If optional args block is true and timeout is None (the default), block + if necessary until an item is available. + """ + if block: + item = self.__db.blpop(self.key, timeout=timeout) + else: + item = self.__db.lpop(self.key) + + if item: + item = item[1] + return item + + def get_all(self): + """Get add messages in queue without block.""" + ret = [] + while True: + item = self.__db.lpop(self.key) + if not item: + break + ret.append(item) + return ret + + def get_nowait(self): + """Equivalent to get(False).""" + return self.get(False) + + +class QueryTracker: + """A class to track queries and log them into a file. + + This class provides functionality to keep track of queries and write them + into a log file. Whenever a query is made, it can be logged using this + class, and when the instance of this class is destroyed, all logged queries + are written to the file. + """ + + def __init__(self, log_file_path): + """Initialize the QueryTracker with the path of the log file.""" + self.log_file_path = log_file_path + self.log_list = [] + + def log(self, key, value=''): + """Log a query. + + Args: + key (str): The key associated with the query. + value (str): The value or result associated with the query. + """ + self.log_list.append((key, value)) + + def __del__(self): + """Write all logged queries into the file when the QueryTracker + instance is destroyed. + + It opens the log file in append mode, writes all logged queries into + the file, and then closes the file. If any exception occurs during this + process, it will be caught and printed to standard output. + """ + try: + with open(self.log_file_path, 'a', encoding='utf8') as log_file: + for key, value in self.log_list: + log_file.write(f'{key}: {value}\n') + log_file.write('\n') + except Exception as e: + print(e) + + +def parse_json_str(json_str: str): + try: + logger.info(json_str) + return json.loads(json_str, + object_hook=lambda d: SimpleNamespace(**d)), None + except Exception as e: + logger.error(str(e)) + return None, e + + +def multimodal(filepath: str, timeout=5): + header = {'Content-Type': 'application/json'} + data = {'image_path': filepath} + try: + resp = requests.post('http://127.0.0.1:9999/api', + headers=header, + data=json.dumps(data), + timeout=timeout) + resp_json = resp.json() + content = resp_json['content'] + # check bad encode ratio + useful_char_cnt = 0 + scopes = [['a', 'z'], ['\u4e00', '\u9fff'], ['A', 'Z'], ['0', '9']] + for char in content: + for scope in scopes: + if char >= scope[0] and char <= scope[1]: + useful_char_cnt += 1 + break + if useful_char_cnt / len(content) <= 0.5: + # Garbled characters + return None + if len(content) <= 100: + return None + return content + except Exception as e: + logger.error(str(e)) + return None + + +def kimi_ocr(filepath, token): + # curl post file to kimi server + client = OpenAI(api_key=token, base_url='https://api.moonshot.cn/v1') + try: + file_object = client.files.create(file=Path(filepath), + purpose='file-extract') + json_str = client.files.content(file_id=file_object.id).text + json_obj = json.loads(json_str) + return json_obj['content'] + except Exception as e: + logger.error(str(e)) + return '' + + +def check_str_useful(content: str): + useful_char_cnt = 0 + scopes = [['a', 'z'], ['\u4e00', '\u9fff'], ['A', 'Z'], ['0', '9']] + for char in content: + for scope in scopes: + if char >= scope[0] and char <= scope[1]: + useful_char_cnt += 1 + break + if useful_char_cnt / len(content) <= 0.5: + # Garbled characters + return False + return True + + +def histogram(values: list): + """Print histogram log string for values.""" + values.sort() + _len = len(values) + if _len <= 1: + return '' + + median = values[round((_len - 1) / 2)] + _sum = 0 + min_val = min(values) + max_val = max(values) + range_width = max(1, round(0.1 * (max_val - min_val))) + ranges = [(i * range_width, (i + 1) * range_width) + for i in range((max_val // range_width) + 1)] + + # 计算每个范围的数值总数 + total_count = len(values) + range_counts = [0] * len(ranges) + for value in values: + _sum += value + for i, (start, end) in enumerate(ranges): + if start <= value < end: + range_counts[i] += 1 + break + + range_percentages = [(count / total_count) * 100 for count in range_counts] + + log_str = 'length count {}, avg {}, median {}\n'.format( + len(values), round(_sum / len(values), 2), median) + for i, (start, end) in enumerate(ranges): + log_str += f'{start}-{end} {range_percentages[i]:.2f}%' + log_str += '\n' + return log_str + + +def extract_json_from_str(raw: str): + raw = raw.strip() + raw = raw.replace('”', '"') + raw = raw.replace('“', '"') + raw = raw.replace('"""', '"') + raw = raw.replace('""', '"') + raw = raw.replace('```json\n', '') + raw = raw.replace('```', '') + raw = raw.replace(',', ',') + + json_list = [] + try: + start = raw.find('[') + end = raw.rfind(']') + json_str = raw[start:end + 1] + json_obj = json.loads(json_str) + if type(json_obj) is dict: + for k in json_obj.keys(): + json_list = json_obj[k] + break + else: + json_list = json_obj + except Exception as e: + logger.error(e) + logger.error(raw) + + ret_list = [] + for item in json_list: + if 'events' in item: + ret_list += item['events'] + else: + ret_list.append(item) + return ret_list + + +def build_reply_text(code, query: str, reply: str, refs: list, max_len:int=20): + table = Texttable() + table.set_cols_valign(['t', 't', 't', 't']) + table.header(['Query', 'State', 'Part of Reply', 'References']) + table.add_row([query, str(code), reply[0:max_len] + '..', ','.join(refs)]) + return table.draw() + + +def is_truth(llm: Any, + prompt: str, + throttle: int, + default: int, + backend: str = 'local'): + """Generate a score based on the prompt, and then compares it to threshold. + + Args: + prompt (str): The prompt for the language model. + throttle (int): Threshold value to compare the score against. + default (int): Default score to be assigned in case of failure in score calculation. # noqa E501 + + Returns: + bool: True if the score surpasses the throttle, otherwise False. + dict: Debugging logs. + """ + logs = {} + logs['input'] = prompt + if prompt is None or len(prompt) == 0: + return False, logs + + score = default + logs['default'] = default + relation = llm.generate_response(prompt=prompt, backend=backend) + logs['relation'] = relation + filtered_relation = ''.join([c for c in relation if c.isdigit()]) + + try: + score_str = re.sub(r'[^\d]', ' ', filtered_relation).strip() + score = int(score_str.split(' ')[0]) + except Exception as e: + logger.warning('primitive is_truth: {}, use default value {}'.format( + str(e), default)) + logs['throttle'] = throttle + logs['output'] = score + + if score >= throttle: + return True, logs + return False, logs + + +# if __name__ == '__main__': +# print(kimi_ocr('/root/hxddev/wkteam/images/e36e48.jpg', '')) diff --git a/repodir/huixiangdou/huixiangdou/service/kg.py b/repodir/huixiangdou/huixiangdou/service/kg.py new file mode 100644 index 00000000..fd7d8728 --- /dev/null +++ b/repodir/huixiangdou/huixiangdou/service/kg.py @@ -0,0 +1,507 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import argparse +import json +import os +import pdb +import pickle +import re +import sys +import time +from dataclasses import asdict, dataclass, field +from enum import Enum, unique +from typing import List, Union +from uuid import uuid4 + +import networkx as nx +import pytoml +from loguru import logger +from tqdm import tqdm + +from ..primitive import FileOperation +from .helper import build_reply_text, extract_json_from_str +from .llm_client import ChatClient +from .llm_server_hybrid import start_llm_server + + +def simple_uuid(): + return str(uuid4())[0:6] + + +@unique +class KGType(Enum): + MARKDOWN = 'markdown' + CHUNK = 'chunk' + KEYWORD = 'keyword' + IMAGE = 'image' + + +@dataclass +class Node: + _type: KGType + uuid: str = field(default_factory=simple_uuid) + data: str = '' + + +def node_to_jsonstr(instance): + dict_instance = asdict(instance) + dict_instance['_type'] = instance._type.value + return json.dumps(dict_instance, ensure_ascii=False) + + +@dataclass +class Relation: + _from: str + to: str + desc: str + + +def relation_to_jsonstr(instance): + dict_instance = asdict(instance) + return json.dumps(dict_instance, ensure_ascii=False) + + +class KnowledgeGraph: + + def __init__(self, + config_path: str, + override: bool = False, + retry: int = 1): + + self.llm = ChatClient(config_path=config_path) + self.retry = retry + self.nodes = [] + self.relations = [] + self.chunksize = 2048 + self.prompt_template = ''' +你是一位语言专家,现在要做实体识别任务(NER),请阅读以下内容,以 json 形式输出实体。直接给出结果不要解释。 +输出示例: +[{"entity":"实体","type":"类型"}] + +以下是阅读内容: +''' + self.md_pattern = re.compile(r'\[([^\]]+)\]\(([a-zA-Z0-9:/._~#-]+)?\)') + self.file_opr = FileOperation() + self.override = override + + with open(config_path) as f: + config = pytoml.load(f) + self.kg_work_dir = os.path.join( + config['feature_store']['work_dir'], 'kg') + if not os.path.exists(self.kg_work_dir): + os.makedirs(self.kg_work_dir) + + self.nodes_path = os.path.join(self.kg_work_dir, 'kg_nodes.jsonl') + self.relations_path = os.path.join(self.kg_work_dir, + 'kg_relations.jsonl') + self.gpickle_path = os.path.join(self.kg_work_dir, 'kg.gpickle') + self.graph = None + + def build(self, repodir: str): + logger.info( + 'multi-modal knowledge graph retrieval is experimental, only support markdown format.' + ) + proc_files = [] + + processed = [] + processed_path = os.path.join(self.kg_work_dir, 'processed.txt') + if os.path.exists(processed_path): + with open(processed_path) as f: + for path in f: + processed.append(path.strip()) + + for root, dirs, files in os.walk(repodir): + for file in files: + if '.github' in root: + continue + file_type = self.file_opr.get_type(file) + if file_type not in ['md']: + continue + + abspath = os.path.join(root, file) + if abspath in processed: + logger.info(f'skip {abspath}') + continue + proc_files.append((abspath, file_type)) + + # save to jsonl and pickle + if self.override: + if os.path.exists(self.nodes_path): + os.remove(self.nodes_path) + if os.path.exists(self.relations_path): + os.remove(self.relations_path) + + for abspath, file_type in tqdm(proc_files): + if file_type == 'md': + self.build_md(abspath) + with open(processed_path, 'a') as f: + f.write(abspath) + f.write('\n') + + # save jsonl format + with open(self.nodes_path, 'a') as f: + for node in self.nodes: + f.write(node_to_jsonstr(node)) + f.write('\n') + self.nodes = [] + + with open(self.relations_path, 'a') as f: + for relation in self.relations: + f.write(relation_to_jsonstr(relation)) + f.write('\n') + self.relations = [] + + def build_md_chunk(self, md_node: Node, abspath: str): + """Parse markdown chunk to nodes and relations. + + LLM NER with retry policy. + """ + items = [] + for _ in range(self.retry): + llm_raw_text = self.llm.generate_response( + prompt=self.prompt_template + md_node.data) + items += extract_json_from_str(raw=llm_raw_text) + + if len(items) < 1: + logger.warning( + 'parse llm_raw_text failed. {}'.format(llm_raw_text)) + return + + for item in items: + # fetch nodes and add relations + try: + entity = item['entity'] + _type = item['type'] + except Exception as e: + logger.error(e) + logger.error(item) + continue + + self.nodes.append(Node(uuid=entity, _type=KGType.KEYWORD)) + self.relations.append(Relation(entity, md_node.uuid, _type)) + + matches = self.md_pattern.findall(md_node.data) + for match in matches: + # target = match[0] + uri = match[1] + if self.file_opr.get_type(uri) != 'image': + continue + + if not uri.startswith('http'): + uri = os.path.join(os.path.dirname(abspath), uri) + uuid, image_path = self.file_opr.save_image( + uri=uri, outdir=self.kg_work_dir) + if image_path is not None: + self.nodes.append( + Node(uuid=uuid, _type=KGType.IMAGE, data=image_path)) + self.relations.append(Relation(uuid, md_node.uuid, 'file')) + + def build_md(self, abspath: str): + """Load markdown and split, build nodes and relationship.""" + content = '' + with open(abspath) as f: + content = f.read() + splits = content.split('\n') + + chunk = '' + pageid = 0 + + md_node = Node(_type=KGType.MARKDOWN, data=abspath) + self.nodes.append(md_node) + + def add_chunk(md_node: Node, pageid: int, text: str): + chunk_node = Node(_type=KGType.CHUNK, data=text) + self.nodes.append(chunk_node) + self.build_md_chunk(md_node=chunk_node, abspath=abspath) + self.relations.append( + Relation(md_node.uuid, chunk_node.uuid, + 'page{}'.format(pageid))) + + for split in splits: + if len(split) >= self.chunksize: + if len(chunk) > 0: + add_chunk(md_node=md_node, pageid=pageid, text=chunk) + pageid += 1 + chunk = '' + add_chunk(md_node=md_node, pageid=pageid, text=split) + pageid += 1 + continue + + if len(chunk) + len(split) < self.chunksize: + chunk = chunk + '\n' + split + continue + + add_chunk(md_node=md_node, pageid=pageid, text=chunk) + pageid += 1 + chunk = split + + if len(chunk) > 0: + add_chunk(md_node=md_node, pageid=pageid, text=chunk) + + def dump_neo4j(self, uri: str, user: str, passwd: str): + # Save networkx-neo4j for better graph viewer + # Open `.config/Neo4j Desktop` and see https://neo4j.com/docs/operations-manual/current/configuration/ports/#_listen_address_configuration_settings + from neo4j import GraphDatabase + driver = GraphDatabase.driver(uri, auth=(user, passwd)) + # clear database if override + with driver.session() as session: + session.run('MATCH (n) DETACH DELETE n') + + # load jsonl and save it + nodes = dict() + with open(self.nodes_path) as f: + for json_str in f: + node = json.loads(json_str) + nodes[node['uuid']] = node + + add_node_query_with_props = """\ + MERGE (n:`%s` {`id`: $value }) + ON CREATE SET n+=$props + """ + with driver.session() as session: + for node in tqdm(nodes.values()): + nodel_label = node['_type'] + query = add_node_query_with_props % nodel_label + session.run(query, {'value': node['uuid']}, + props={ + 'type': node['_type'], + 'data': node['data'] + }) + + # load relations + relations = [] + with open(self.relations_path) as f: + for json_str in f: + rel = json.loads(json_str) + relations.append(rel) + + # query node1 and node2, add an relationship + add_edge_query = """\ + MERGE (node1:`%s` {`id`: $node1 }) + MERGE (node2:`%s` {`id`: $node2 }) + MERGE (node1)-[r:`%s`]->(node2) + ON CREATE SET r=$props + """ + with driver.session() as session: + for rel in tqdm(relations): + _from = rel['_from'] + to = rel['to'] + + label1 = nodes[_from]['_type'] + label2 = nodes[to]['_type'] + + desc = rel['desc'] + if desc in ['file']: + relationship_type = desc + elif desc.startswith('page'): + relationship_type = 'page' + else: + relationship_type = 'attr' + + query = add_edge_query % (label1, label2, relationship_type) + session.run(query, { + 'node1': _from, + 'node2': to + }, + props={'desc': desc}) + + def dump_networkx(self): + """Convert to networkx and dump GraphML format.""" + if not os.path.exists(self.nodes_path): + logger.error('nodes path not exist') + return + + if not os.path.exists(self.relations_path): + logger.error('relations path not exist') + return + + with open(self.nodes_path) as f: + for json_str in f: + self.nodes.append(json.loads(json_str)) + + with open(self.relations_path) as f: + for json_str in f: + self.relations.append(json.loads(json_str)) + + G = nx.Graph() + for node in self.nodes: + G.add_nodes_from([(node['uuid'], { + 'type': node['_type'], + 'data': node['data'] + })]) + for rel in self.relations: + G.add_edge(rel['_from'], rel['to'], desc=rel['desc']) + logger.debug( + 'Loaded knowledge graph, number of nodes {}, number of edges {}'. + format(G.number_of_nodes(), G.number_of_edges())) + + # save to pickle format + with open(self.gpickle_path, 'wb') as f: + pickle.dump(G, f, pickle.HIGHEST_PROTOCOL) + + def is_available(self): + """Check knowledge graph exist or not.""" + if os.path.exists(self.gpickle_path): + return True + return False + + def load(self): + """Load knowledge graph.""" + if not os.path.exists(self.gpickle_path): + logger.error('gpickle {} not exist.'.format(self.gpickle_path)) + return None + + with open(self.gpickle_path, 'rb') as f: + self.graph = pickle.load(f) + + logger.debug('number of nodes {}, number of edges {}'.format( + self.graph.number_of_nodes(), self.graph.number_of_edges())) + + def query_file_chunk_map(self, attr: str): + ret = dict() + + G = self.graph + # chunks = [G.nodes[neighbor] for neighbor in G.neighbors(attr)] + for chunk in G.neighbors(attr): + files = [ + nbr for nbr in G.neighbors(chunk) + if 'page' in G.edges[chunk, nbr].get('desc') + ] + for file in files: + chunk_data = G.nodes[chunk].get('data') + file_data = G.nodes[file].get('data') + if file_data in ret: + ret[file_data].append(chunk_data) + else: + ret[file_data] = [chunk_data] + + return ret + + def retrieve(self, query: str): + if self.graph is None: + self.load() + llm_raw_text = self.llm.generate_response(prompt=self.prompt_template + + query) + + items = extract_json_from_str(raw=llm_raw_text) + if len(items) < 1: + return [] + + file_chunks = dict() + for item in items: + # fetch nodes and add relations + try: + entity = item['entity'] + if not self.graph.has_node(entity): + continue + + file_chunks_on_entity = self.query_file_chunk_map(attr=entity) + for k, v in file_chunks_on_entity.items(): + if k in file_chunks: + file_chunks[k] += v + else: + file_chunks[k] = v + + except Exception as e: + logger.error(e) + logger.error(item) + continue + + candidates = [] + for k, v in file_chunks.items(): + candidates.append({'path': k, 'chunks': v}) + + candidates.sort(key=lambda x: len(x['chunks'])) + return candidates + + +def parse_args(): + """Parse command-line arguments. + + Please `export LOGURU_LEVEL=WARNING` before running. + """ + parser = argparse.ArgumentParser( + description='Knowledge graph for processing directories.') + parser.add_argument('--repo_dir', + type=str, + default='repodir', + help='Root directory where the docs are located.') + parser.add_argument('--config_path', + default='config.ini', + help='Configuration path. Default value is config.ini') + parser.add_argument( + '--standalone', + action='store_true', + default=True, + help= + 'Building knowledge graph needs LLM for NER. This option would auto start LLM service, default value is True' + ) + parser.add_argument( + '--override', + action='store_true', + default=False, + help='Remove old data and rebuild knowledge graph from scratch.') + parser.add_argument('--build', + action='store_true', + default=False, + help='Build knowledge graph from repodir.') + parser.add_argument( + '--dump-networkx', + action='store_true', + default=False, + help='Load jsonl data and dump to networkx gpickle format.') + parser.add_argument( + '--dump-neo4j', + action='store_true', + default=False, + help='Load jsonl data and dump to neo4j for viewing knowledge graph.') + parser.add_argument('--neo4j-uri', + type=str, + default='bolt://10.1.52.85:7687', + help='neo4j URI, see https://neo4j.com/') + parser.add_argument('--neo4j-user', + type=str, + default='neo4j', + help='neo4j username') + parser.add_argument('--neo4j-passwd', + type=str, + default='neo4j', + help='neo4j password') + parser.add_argument('--query', + type=str, + default=None, + help='Information Retrieval based on knowledge graph.') + parser.add_argument('--retry', + type=int, + default=1, + help='Retry count for LLM NER.') + args = parser.parse_args() + return args + + +if __name__ == '__main__': + args = parse_args() + if args.standalone: + start_llm_server(args.config_path) + kg = KnowledgeGraph(args.config_path, + override=args.override, + retry=args.retry) + + if args.build: + kg.build(repodir=args.repo_dir) + kg.dump_networkx() + + if args.dump_neo4j: + kg.dump_neo4j(uri=args.neo4j_uri, + user=args.neo4j_user, + passwd=args.neo4j_passwd) + + if args.dump_networkx: + kg.dump_networkx() + + if args.query: + result = kg.retrieve(query=args.query)[0] + reply_text = build_reply_text(code=0, + query=args.query, + reply=result['path'], + refs=result['chunks']) + logger.info('\n' + reply_text) diff --git a/repodir/huixiangdou/huixiangdou/service/llm_client.py b/repodir/huixiangdou/huixiangdou/service/llm_client.py new file mode 100644 index 00000000..800c4d1a --- /dev/null +++ b/repodir/huixiangdou/huixiangdou/service/llm_client.py @@ -0,0 +1,221 @@ +# Copyright (c) OpenMMLab. All rights reserved. +"""LLM client.""" +import argparse +import json +import aiohttp +import re + +import pytoml +import requests +from loguru import logger + +class ChatClient: + """A class to handle client-side interactions with a chat service. + + This class is responsible for loading configurations from a given path, + building prompts, and generating responses by interacting with the chat + service. + """ + + def __init__(self, config_path: str) -> None: + """Initialize the ChatClient with the path of the configuration + file.""" + self.config_path = config_path + self.llm_config = None + with open(self.config_path, encoding='utf8') as f: + config = pytoml.load(f) + self.llm_config = config['llm'] + + def build_prompt(self, + history_pair, + instruction: str, + template: str, + context: str = '', + reject: str = ''): + """Build a prompt for interaction. + + Args: + history_pair (list): List of previous interactions. + instruction (str): Instruction for the current interaction. + template (str): Template for constructing the interaction. + context (str, optional): Context of the interaction. Defaults to ''. # noqa E501 + reject (str, optional): Text that indicates a rejected interaction. Defaults to ''. # noqa E501 + + Returns: + tuple: A tuple containing the constructed instruction and real history. + """ + if context is not None and len(context) > 0: + instruction = template.format(context, instruction) + + real_history = [] + for pair in history_pair: + if pair[1] == reject: + continue + if pair[0] is None or pair[1] is None: + continue + if len(pair[0]) < 1 or len(pair[1]) < 1: + continue + real_history.append(pair) + + return instruction, real_history + + def auto_fix(self, backend): + """Choose real backend according to config.ini.""" + + enable_local, enable_remote = (self.llm_config['enable_local'], + self.llm_config['enable_remote']) + local_len, remote_len = ( + self.llm_config['server']['local_llm_max_text_length'], + self.llm_config['server']['remote_llm_max_text_length']) + + max_length = local_len + if enable_remote: + max_length = remote_len + + if backend == 'local' and not enable_local: + backend = self.llm_config['server']['remote_type'] + max_length = remote_len + elif backend != 'local' and not enable_remote: + backend = 'local' + max_length = local_len + + return backend, max_length + + def generate_response(self, prompt, history=[], backend='local'): + """Generate a response from the chat service. + + Args: + prompt (str): The prompt to send to the chat service. + history (list, optional): List of previous interactions. Defaults to []. + backend (str, optional): Determine which LLM should be called. Default to `local` + + Returns: + str: Generated response from the chat service. + """ + url = self.llm_config['client_url'] + real_backend, max_length = self.auto_fix(backend=backend) + + if len(prompt) > max_length: + logger.warning( + f'prompt length {len(prompt)} > max_length {max_length}, truncated' # noqa E501 + ) + prompt = prompt[0:max_length] + + try: + header = {'Content-Type': 'application/json'} + data_history = [] + for item in history: + data_history.append([item[0], item[1]]) + data = { + 'prompt': prompt, + 'history': data_history, + 'backend': real_backend + } + + resp = requests.post(url, + headers=header, + data=json.dumps(data), + timeout=300) + if resp.status_code != 200: + raise Exception(str((resp.status_code, resp.reason))) + + json_obj = resp.json() + text = json_obj['text'] + if 'error' in json_obj: + error = json_obj['error'] + if len(error) > 0: + logger.error(error) + return text + except Exception as e: + logger.error(str(e)) + logger.error( + 'Do you forget `--standalone` when `python3 -m huixiangdou.main` ?' # noqa E501 + ) + return '' + + async def chat_stream(self, prompt, history=[], backend='local'): + """Generate a stream response from the chat service. + + Args: + prompt (str): The prompt to send to the chat service. + history (list, optional): List of previous interactions. Defaults to []. + backend (str, optional): Determine which LLM should be called. Default to `local` + + Returns: + str: Generated response from the chat service. + """ + sync_url = self.llm_config['client_url'] + stream_url = sync_url.replace('/inference', '/stream') + real_backend, max_length = self.auto_fix(backend=backend) + + if len(prompt) > max_length: + logger.warning( + f'prompt length {len(prompt)} > max_length {max_length}, truncated' # noqa E501 + ) + prompt = prompt[0:max_length] + + sse_pattern = re.compile(r'data: (.*?)(?=\r\n\r\n)', re.DOTALL) + try: + headers = {'Content-Type': 'application/json'} + data_history = [] + for item in history: + data_history.append([item[0], item[1]]) + data = { + 'prompt': prompt, + 'history': data_history, + 'backend': real_backend + } + + async with aiohttp.ClientSession() as session: + async with session.post(stream_url, headers=headers, data=json.dumps(data)) as response: + # 确保请求成功 + if response.status == 200: + async for chunk in response.content.iter_any(): + chunk_data = chunk.decode() + messages = sse_pattern.findall(chunk_data) + for message in messages: + if '\r\ndata: ' in message: + message = message.replace('\r\ndata: ', '\r\n') + yield message + else: + raise Exception(response.status) + + except Exception as e: + logger.error(str(e)) + logger.error( + 'See the HuixiangDou FAQ, feel free to `submit an issue` or `ask in the WeChat group`. ' + ) + +def parse_args(): + """Parse command-line arguments.""" + parser = argparse.ArgumentParser( + description='Client for hybrid llm service.') + parser.add_argument( + '--config_path', + default='config.ini', + help='Configuration path. Default value is config.ini' # noqa E501 + ) + args = parser.parse_args() + return args + + +if __name__ == '__main__': + args = parse_args() + client = ChatClient(config_path=args.config_path) + # question = '“{}”\n请仔细阅读以上问题,提取其中的实体词,结果直接用 list 表示,不要解释。'.format( + # '请问triviaqa 5shot结果怎么在summarizer里输出呢') + # print(client.generate_response(prompt=question, backend='local')) + + # print( + # client.generate_response(prompt='请问 ncnn 的全称是什么', + # history=[('ncnn 是什么', + # 'ncnn中的n代表nihui,cnn代表卷积神经网络。')], + # backend='remote')) + + async def wrap_as_coroutine(): + async for text in client.chat_stream('请问 ncnn 全称是啥'): + print(text, end='', flush=True) + import asyncio + + loop = asyncio.get_event_loop() + loop.run_until_complete(wrap_as_coroutine()) diff --git a/repodir/huixiangdou/huixiangdou/service/llm_server_hybrid.py b/repodir/huixiangdou/huixiangdou/service/llm_server_hybrid.py new file mode 100644 index 00000000..8fdad72a --- /dev/null +++ b/repodir/huixiangdou/huixiangdou/service/llm_server_hybrid.py @@ -0,0 +1,602 @@ +# Copyright (c) OpenMMLab. All rights reserved. +"""LLM server proxy.""" +import argparse +import json +import os +import random +import time +from multiprocessing import Process, Value, set_start_method +import torch +import pdb +import pytoml +import requests +from loguru import logger +from openai import OpenAI +from huixiangdou.primitive import RPM +import asyncio +from fastapi import FastAPI, APIRouter +from fastapi.middleware.cors import CORSMiddleware +from pydantic import BaseModel +from sse_starlette.sse import EventSourceResponse + +import uvicorn +from typing import List, Tuple + +def os_run(cmd: str): + ret = os.popen(cmd) + ret = ret.read().rstrip().lstrip() + return ret + +def check_gpu_max_memory_gb(): + try: + import torch + device = torch.device('cuda') + return torch.cuda.get_device_properties( + device).total_memory / ( # noqa E501 + 1 << 30) + except Exception as e: + logger.error(str(e)) + return -1 + + +def build_messages(prompt, history, system: str = None): + messages = [] + if system is not None and len(system) > 0: + messages.append({'role': 'system', 'content': system}) + for item in history: + messages.append({'role': 'user', 'content': item[0]}) + messages.append({'role': 'assistant', 'content': item[1]}) + messages.append({'role': 'user', 'content': prompt}) + return messages + + +class InferenceWrapper: + """A class to wrapper kinds of inference framework.""" + + def __init__(self, model_path: str): + """Init model handler.""" + from transformers import AutoModelForCausalLM, AutoTokenizer + self.model_path = model_path + self.tokenizer = AutoTokenizer.from_pretrained(model_path, + trust_remote_code=True) + + model_path_lower = model_path.lower() + + if 'qwen2' in model_path_lower: + self.model = AutoModelForCausalLM.from_pretrained( + model_path, + torch_dtype='auto', + device_map='auto', + trust_remote_code=True).eval() + elif 'qwen1.5' in model_path_lower: + self.model = AutoModelForCausalLM.from_pretrained( + model_path, device_map='auto', trust_remote_code=True).eval() + elif 'qwen' in model_path_lower: + self.model = AutoModelForCausalLM.from_pretrained( + model_path, + device_map='auto', + trust_remote_code=True, + use_cache_quantization=True, + use_cache_kernel=True, + use_flash_attn=False).eval() + elif 'internlm2_5' in model_path_lower: + self.model = AutoModelForCausalLM.from_pretrained( + model_path, + torch_dtype=torch.float16, + trust_remote_code=True).cuda().eval() + elif 'internlm2' in model_path_lower: + self.model = AutoModelForCausalLM.from_pretrained( + model_path, + trust_remote_code=True, + device_map='auto', + torch_dtype='auto').eval() + else: + raise ValueError('Unknown model path {}'.format(model_path)) + + async def chat_stream(self, prompt: str, history=[]): + """Generate a stream response from local LLM. Wrap transformer API to async generator + + Args: + prompt (str): The prompt for inference. + history (list): List of previous interactions. + + Returns: + str: Generated response. + """ + output_text = '' + + if type(self.model).__name__ == 'Qwen2ForCausalLM': + messages = build_messages( + prompt=prompt, + history=history, + system='You are a helpful assistant') # noqa E501 + text = self.tokenizer.apply_chat_template( + messages, tokenize=False, add_generation_prompt=True) + model_inputs = self.tokenizer([text], + return_tensors='pt').to('cuda') + generated_ids = self.model.generate(model_inputs.input_ids, + max_new_tokens=512, + top_k=1) + generated_ids = [ + output_ids[len(input_ids):] for input_ids, output_ids in zip( + model_inputs.input_ids, generated_ids) + ] + + output_text = self.tokenizer.batch_decode( + generated_ids, skip_special_tokens=True)[0] + yield output_text + + elif type(self.model).__name__ == 'InternLM2ForCausalLM': + + if '请仔细阅读以上内容,判断句子是否是个有主题的疑问句,结果用 0~10 表示。直接提供得分不要解释。' in prompt: + prompt = '你是一个语言专家,擅长分析语句并打分。\n' + prompt + + length = 0 + for response, _ in self.model.stream_chat(self.tokenizer, prompt, history, top_k=1, do_sample=False): + part = response[length:] + length = len(response) + yield part + + else: + raise ValueError('Unknown model type {}'.format(type(self.model).__name__)) + + def chat(self, prompt: str, history=[]): + """Generate a sync response from local LLM. Sync chat. + + Args: + prompt (str): The prompt for inference. + history (list): List of previous interactions. + + Returns: + str: Generated response. + """ + loop = asyncio.get_event_loop() + + async def coroutine_wrapper(): + messages = [] + async for part in self.chat_stream(prompt=prompt, history=history): + messages.append(part) + return ''.join(messages) + content = loop.run_until_complete(coroutine_wrapper()) + return content + + +class HybridLLMServer: + """A class to handle server-side interactions with a hybrid language + learning model (LLM) service. + + This class is responsible for initializing the local and remote LLMs, + generating responses from these models as per the provided configuration, + and handling retries in case of failures. + """ + + def __init__(self, + llm_config: dict, + device: str = 'cuda', + retry=2) -> None: + """Initialize the HybridLLMServer with the given configuration, device, + and number of retries.""" + self.device = device + self.retry = retry + self.llm_config = llm_config + self.server_config = llm_config['server'] + self.enable_remote = llm_config['enable_remote'] + self.enable_local = llm_config['enable_local'] + + self.local_max_length = self.server_config['local_llm_max_text_length'] + self.remote_max_length = self.server_config[ + 'remote_llm_max_text_length'] + self.remote_type = self.server_config['remote_type'] + + model_path = self.server_config['local_llm_path'] + + _rpm = 500 + if 'rpm' in self.server_config: + _rpm = self.server_config['rpm'] + self.rpm = RPM(_rpm) + + if self.enable_local: + self.inference = InferenceWrapper(model_path) + else: + self.inference = None + logger.warning('local LLM disabled.') + + self.backend2model = { + "kimi": "auto", + "step": "auto", + "xi-api": "gpt-4-0613", + "deepseek": "deepseek-chat", + "zhipuai": "glm-4", + "puyu": "internlm2-latest", + "siliconcloud": "alibaba/Qwen1.5-110B-Chat" + } + + async def call_kimi(self, prompt:str, history:List[Tuple], remote_api_key:str, model:str): + """Generate a response from Kimi (a remote LLM). + + Args: + prompt (str): The prompt to send to Kimi. + history (list): List of previous interactions. + + Returns: + str: Generated response from Kimi. + """ + client = OpenAI( + api_key=remote_api_key, + base_url='https://api.moonshot.cn/v1', + ) + + SYSTEM = '你是 Kimi,由 Moonshot AI 提供的人工智能助手,你更擅长中文和英文的对话。你会为用户提供安全,有帮助,准确的回答。同时,你会拒绝一些涉及恐怖主义,种族歧视,黄色暴力,政治宗教等问题的回答。Moonshot AI 为专有名词,不可翻译成其他语言。' # noqa E501 + # 20240531 hacking for kimi API incompatible + # it is very very tricky, please do not change this magic prompt !!! + if '请仔细阅读以上内容,判断句子是否是个有主题的疑问句' in prompt: + SYSTEM = '你是一个语文专家,擅长对句子的结构进行分析' + + messages = build_messages(prompt=prompt, + history=history, + system=SYSTEM) + + logger.debug('remote api sending: {}'.format(messages)) + if model == 'auto': + prompt_len = len(prompt) + if prompt_len <= int(8192 * 1.5) - 1024: + model = 'moonshot-v1-8k' + elif prompt_len <= int(32768 * 1.5) - 1024: + model = 'moonshot-v1-32k' + else: + prompt = prompt[0:int(128000 * 1.5) - 1024] + model = 'moonshot-v1-128k' + + logger.info('choose kimi model {}'.format(model)) + + stream = client.chat.completions.create( + model=model, + messages=messages, + temperature=0.0, + stream=True + ) + + for chunk in stream: + delta = chunk.choices[0].delta + if delta.content: + yield delta.content + + async def call_step(self, prompt:str, history:List, remote_api_key:str, model:str): + """Generate a response from step, see + https://platform.stepfun.com/docs/overview/quickstart. + + Args: + prompt (str): The prompt to send to LLM. + history (list): List of previous interactions. + + Returns: + str: Generated response from LLM. + """ + client = OpenAI( + api_key=self.server_config['remote_api_key'], + base_url='https://api.stepfun.com/v1', + ) + + SYSTEM = '你是由阶跃星辰提供的AI聊天助手,你擅长中文,英文,以及多种其他语言的对话。在保证用户数据安全的前提下,你能对用户的问题和请求,作出快速和精准的回答。同时,你的回答和建议应该拒绝黄赌毒,暴力恐怖主义的内容' # noqa E501 + messages = build_messages(prompt=prompt, + history=history, + system=SYSTEM) + + logger.debug('remote api sending: {}'.format(messages)) + + if model == 'auto': + prompt_len = len(prompt) + if prompt_len <= int(8192 * 1.5) - 1024: + model = 'step-1-8k' + elif prompt_len <= int(32768 * 1.5) - 1024: + model = 'step-1-32k' + elif prompt_len <= int(128000 * 1.5) - 1024: + model = 'step-1-128k' + else: + prompt = prompt[0:int(256000 * 1.5) - 1024] + model = 'step-1-256k' + + logger.info('choose step model {}'.format(model)) + + stream = client.chat.completions.create( + model=model, + messages=messages, + temperature=0.0, + stream=True + ) + for chunk in stream: + delta = chunk.choices[0].delta + if delta.content: + yield delta.content + + async def call_openai(self, + prompt: str, + history: List, + remote_api_key: str, + model: str, + base_url: str = None, + system: str = None): + """Generate a response from openai API. + + Args: + prompt (str): The prompt to send to openai API. + history (list): List of previous interactions. + + Returns: + str: Generated response from RPC. + """ + if base_url is not None: + client = OpenAI(api_key=remote_api_key, + base_url=base_url) + else: + client = OpenAI(api_key=remote_api_key) + + messages = build_messages(prompt=prompt, + history=history, + system=system) + + logger.debug('remote api sending: {}'.format(messages)) + + stream = client.chat.completions.create( + model=model, + messages=messages, + temperature=0.0, + stream=True + ) + for chunk in stream: + if chunk.choices is None: + raise Exception(str(chunk)) + delta = chunk.choices[0].delta + if delta.content: + yield delta.content + + async def chat_stream(self, prompt, history=[], backend='local'): + """Generate a response from the appropriate LLM based on the + configuration. If failed, use exponential backoff. Async generator. + + Args: + prompt (str): The prompt to send to the LLM. + history (list, optional): List of previous interactions. Defaults to []. # noqa E501 + remote (bool, optional): Flag to determine whether to use a remote server. Defaults to False. # noqa E501 + backend (str): LLM type to call. Support 'local', 'remote' and specified LLM name ('kimi', 'deepseek' and so on) + + Returns: + str: Generated response from the LLM. If LLM not support stream reply, just reply once. + """ + + if backend == 'local' and self.inference is None: + logger.error( + "!!! fatal error. !!! \n Detect `enable_local=0` in `config.ini` while backend='local', please immediately stop the service and check it. \n For this request, autofix the backend to '{}' and proceed." + .format(self.server_config['remote_type'])) + backend = self.server_config['remote_type'] + + if backend == 'remote': + # not specify remote LLM type, use config + backend = self.server_config['remote_type'] + + if backend == 'local': + prompt = prompt[0:self.local_max_length] + """# Caution: For the results of this software to be reliable and verifiable, # noqa E501 + it's essential to ensure reproducibility. Thus `GenerationMode.GREEDY_SEARCH` # noqa E501 + must enabled.""" + + async for value in self.inference.chat_stream(prompt, history): + yield value + + else: + output_text = '' + prompt = prompt[0:self.remote_max_length] + + map_fn = { + 'kimi': self.call_kimi, + 'step': self.call_step, + 'puyu': self.call_openai, + 'deepseek': self.call_openai, + 'zhipuai': self.call_openai, + 'xi-api': self.call_openai, + 'gpt': self.call_openai, + 'siliconcloud': self.call_openai + } + + map_base_url = { + 'xi-api': 'https://api.xi-ai.cn/v1', + 'deepseek': 'https://api.deepseek.com/v1', + 'zhipuai': 'https://open.bigmodel.cn/api/paas/v4/', + 'puyu': 'https://puyu.openxlab.org.cn/puyu/api/v1/', + 'siliconcloud': 'https://api.siliconflow.cn/v1' + } + + if backend not in map_fn: + raise ValueError('unknown backend {}'.format(backend)) + + target_fn = map_fn[backend] + + # build args for `target_fn` + args = {'prompt': prompt, 'history': history, 'model':self.backend2model[backend]} + if backend in map_base_url: + args['base_url'] = map_base_url[backend] + + if backend in ['xi-api', 'deepseek']: + args['system'] = 'You are a helpful assistant.' + + remote_api_key = self.server_config['remote_api_key'] + if len(remote_api_key) < 1: + remote_api_key = os.getenv('LLM_API_TOKEN') + args['remote_api_key'] = remote_api_key + + life = 0 + while life < self.retry: + try: + self.rpm.wait() + async for value in target_fn(**args): + yield value + # skip retry + break + + except Exception as e: + # retry with exponential backoff + error = str(e) + logger.error(error) + + if 'Error code: 401' in error or 'invalid api_key' in error or 'Authentication Fails' in error: + raise e + + life += 1 + randval = random.randint(1, int(pow(2, life))) + time.sleep(randval) + + yield output_text + + def chat(self, prompt: str, history=[], backend:str='local'): + """Generate a sync response from local LLM. + + Args: + prompt (str): The prompt for inference. + history (list): List of previous interactions. + + Returns: + str: Generated response. + """ + time_tokenizer = time.time() + + async def coroutine_wrapper(): + messages = [] + async for part in self.chat_stream(prompt=prompt, history=history, backend=backend): + messages.append(part) + print(part, end='') + return ''.join(messages) + + loop = asyncio.get_event_loop() + try: + output_text = loop.run_until_complete(coroutine_wrapper()) + except Exception as e: + return '', e + + time_finish = time.time() + + logger.debug('Q:{} A:{} \t\t backend {} timecost {} '.format( + prompt[-100:-1], output_text, backend, + time_finish - time_tokenizer)) + return output_text, None + +def parse_args(): + """Parse command-line arguments.""" + parser = argparse.ArgumentParser(description='Hybrid LLM Server.') + parser.add_argument( + '--config_path', + default='config.ini', + help= # noqa E251 + 'Hybrid LLM Server configuration path. Default value is config.ini' # noqa E501 + ) + parser.add_argument('--unittest', + action='store_true', + default=False, + help='Test with samples.') + args = parser.parse_args() + return args + +class Talk(BaseModel): + prompt: str + backend: str = 'local' + history: List[Tuple[str, str]] = [] + +def llm_serve(config_path: str, server_ready: Value): + """Start the LLM server. + + Args: + config_path (str): Path to the configuration file. + server_ready (multiprocessing.Value): Shared variable to indicate when the server is ready. # noqa E501 + """ + # logger.add('logs/server.log', rotation="4MB") + with open(config_path, encoding='utf8') as f: + llm_config = pytoml.load(f)['llm'] + bind_port = int(llm_config['server']['local_llm_bind_port']) + + try: + server = HybridLLMServer(llm_config=llm_config) + server_ready.value = 1 + except Exception as e: + server_ready.value = -1 + raise (e) + + async def inference(talk: Talk): + """Call local llm inference.""" + logger.info(talk) + + prompt = talk.prompt + history = talk.history + backend = talk.backend + + parts = [] + try: + async for text in server.chat_stream(prompt=prompt, history=history, backend=backend): + parts.append(text) + return {'text': ''.join(parts), 'error': ''} + except Exception as e: + return {'text': '', 'error': str(e)} + + async def stream(talk: Talk): + """Call local llm inference.""" + logger.info(talk) + + prompt = talk.prompt + history = talk.history + backend = talk.backend + + async def generate(): + async for text in server.chat_stream(prompt=prompt, history=history, backend=backend): + yield text + return EventSourceResponse(generate()) + + app = FastAPI(docs_url='/') + app.add_middleware(CORSMiddleware, + allow_origins=['*'], + allow_credentials=True, + allow_methods=['*'], + allow_headers=['*']) + router = APIRouter() + router.add_api_route('/inference', inference, methods=['POST']) + router.add_api_route('/stream', stream, methods=['POST']) + app.include_router(router) + uvicorn.run(app, host='0.0.0.0', port=bind_port, log_level='info') + + +def start_llm_server(config_path: str): + set_start_method('spawn') + server_ready = Value('i', 0) + server_process = Process(target=llm_serve, + args=(config_path, server_ready)) + server_process.daemon = True + server_process.start() + while True: + if server_ready.value == 0: + logger.info('waiting for server to be ready..') + time.sleep(2) + elif server_ready.value == 1: + break + else: + logger.error('start local LLM server failed, quit.') + raise Exception('local LLM path') + logger.info('Hybrid LLM Server start.') + + +def main(): + """Function to start the server without running a separate process.""" + args = parse_args() + server_ready = Value('i', 0) + if not args.unittest: + llm_serve(args.config_path, server_ready) + else: + queries = ['今天天气如何?'] + start_llm_server(config_path=args.config_path) + + from .llm_client import ChatClient + client = ChatClient(config_path=args.config_path) + for query in queries: + print( + client.generate_response(prompt=query, + history=[], + backend='local')) + +if __name__ == '__main__': + main() diff --git a/repodir/huixiangdou/huixiangdou/service/parallel_pipeline.py b/repodir/huixiangdou/huixiangdou/service/parallel_pipeline.py new file mode 100644 index 00000000..a1bcfa2a --- /dev/null +++ b/repodir/huixiangdou/huixiangdou/service/parallel_pipeline.py @@ -0,0 +1,355 @@ +# Copyright (c) OpenMMLab. All rights reserved. +"""Pipeline.""" +import argparse +import asyncio +import datetime +import json +import os +import re +import time +import pdb +import copy +from abc import ABC, abstractmethod +from typing import List, Tuple, Union, Generator + +import pytoml +from loguru import logger + +from huixiangdou.primitive import Query, Chunk + +from .helper import ErrorCode, is_truth +from .llm_client import ChatClient +from .retriever import CacheRetriever, Retriever +from .sg_search import SourceGraphProxy +from .session import Session +from .web_search import WebSearch +from .prompt import (SCORING_QUESTION_TEMPLTE_CN, CR_NEED_CN, CR_CN, TOPIC_TEMPLATE_CN, SCORING_RELAVANCE_TEMPLATE_CN, GENERATE_TEMPLATE_CN, KEYWORDS_TEMPLATE_CN, PERPLESITY_TEMPLATE_CN, SECURITY_TEMAPLTE_CN) +from .prompt import (SCORING_QUESTION_TEMPLTE_EN, CR_NEED_EN, CR_EN, TOPIC_TEMPLATE_EN, SCORING_RELAVANCE_TEMPLATE_EN, GENERATE_TEMPLATE_EN, KEYWORDS_TEMPLATE_EN, PERPLESITY_TEMPLATE_EN, SECURITY_TEMAPLTE_EN) + +class PreprocNode: + """PreprocNode is for coreference resolution and scoring based on group + chats. + + See https://arxiv.org/abs/2405.02817 + """ + + def __init__(self, config: dict, llm: ChatClient, language: str): + self.llm = llm + self.enable_cr = config['worker']['enable_cr'] + + if language == 'zh': + self.SCORING_QUESTION_TEMPLTE = SCORING_QUESTION_TEMPLTE_CN + self.CR = CR_CN + else: + self.SCORING_QUESTION_TEMPLTE = SCORING_QUESTION_TEMPLTE_EN + self.CR = CR_EN + + def process(self, sess: Session) -> Generator[Session, None, None]: + # check input + if sess.query.text is None or len(sess.query.text) < 2: + sess.code = ErrorCode.QUESTION_TOO_SHORT + yield sess + return + + prompt = self.SCORING_QUESTION_TEMPLTE.format(sess.query.text) + truth, logs = is_truth(llm=self.llm, + prompt=prompt, + throttle=6, + default=3) + sess.debug['PreprocNode_is_question'] = logs + if not truth: + sess.code = ErrorCode.NOT_A_QUESTION + yield sess + return + + if not self.enable_cr: + yield sess + return + + if len(sess.groupchats) < 1: + logger.debug('history conversation empty, skip CR') + yield sess + return + + talks = [] + + # rewrite user_id to ABCD.. + name_map = dict() + name_int = ord('A') + for msg in sess.groupchats: + sender = msg.sender + if sender not in name_map: + name_map[sender] = chr(name_int) + name_int += 1 + talks.append({'sender': name_map[sender], 'content': msg.query}) + + talk_str = json.dumps(talks, ensure_ascii=False) + prompt = self.CR.format(talk_str, sess.query.text) + self.cr = self.llm.generate_response(prompt=prompt, backend='remote') + if self.cr.startswith('“') and self.cr.endswith('”'): + self.cr = self.cr[1:len(self.cr) - 1] + if self.cr.startswith('"') and self.cr.endswith('"'): + self.cr = self.cr[1:len(self.cr) - 1] + sess.debug['cr'] = self.cr + + # rewrite query + queries = [sess.query.text, self.cr] + self.query = '\n'.join(queries) + logger.debug('merge query and cr, query: {} cr: {}'.format( + self.query, self.cr)) + + +class Text2vecRetrieval: + """Text2vecNode is for retrieve from knowledge base.""" + + def __init__(self, config: dict, llm: ChatClient, retriever: Retriever, + language: str): + self.llm = llm + self.retriever = retriever + llm_config = config['llm'] + self.context_max_length = llm_config['server'][ + 'local_llm_max_text_length'] + if llm_config['enable_remote']: + self.context_max_length = llm_config['server'][ + 'remote_llm_max_text_length'] + if language == 'zh': + self.TOPIC_TEMPLATE = TOPIC_TEMPLATE_CN + self.SCORING_RELAVANCE_TEMPLATE = SCORING_RELAVANCE_TEMPLATE_CN + self.GENERATE_TEMPLATE = GENERATE_TEMPLATE_CN + else: + self.TOPIC_TEMPLATE = TOPIC_TEMPLATE_EN + self.SCORING_RELAVANCE_TEMPLATE = SCORING_RELAVANCE_TEMPLATE_EN + self.GENERATE_TEMPLATE = GENERATE_TEMPLATE_EN + self.max_length = self.context_max_length - 2 * len( + self.GENERATE_TEMPLATE) + + async def process_coroutine(self, sess: Session) -> Session: + """Try get reply with text2vec & rerank model.""" + + # retrieve from knowledge base + sess.parallel_chunks = await asyncio.to_thread(self.retriever.text2vec_retrieve, sess.query) + # sess.parallel_chunks = self.retriever.text2vec_retrieve(query=sess.query.text) + return sess + +class WebSearchRetrieval: + """WebSearchNode is for web search, use `ddgs` or `serper`""" + + def __init__(self, config: dict, config_path: str, llm: ChatClient, + language: str): + self.llm = llm + self.config_path = config_path + self.enable = config['worker']['enable_web_search'] + llm_config = config['llm'] + self.context_max_length = llm_config['server'][ + 'local_llm_max_text_length'] + self.language = language + if llm_config['enable_remote']: + self.context_max_length = llm_config['server'][ + 'remote_llm_max_text_length'] + if language == 'zh': + self.SCORING_RELAVANCE_TEMPLATE = SCORING_RELAVANCE_TEMPLATE_CN + self.KEYWORDS_TEMPLATE = KEYWORDS_TEMPLATE_CN + else: + self.SCORING_RELAVANCE_TEMPLATE = SCORING_RELAVANCE_TEMPLATE_EN + self.KEYWORDS_TEMPLATE = KEYWORDS_TEMPLATE_EN + + async def process(self, sess: Session) -> Generator[Session, None, None]: + """Try web search.""" + + if not self.enable: + logger.debug('disable web_search') + yield sess + return + + engine = WebSearch(config_path=self.config_path, language=self.language) + + prompt = self.KEYWORDS_TEMPLATE.format(sess.groupname, sess.query.text) + search_keywords = self.llm.generate_response(prompt) + search_keywords = search_keywords.replace('"', '') + sess.debug['WebSearchNode_keywords'] = prompt + + articles, error = await asyncio.to_thread(engine.get, search_keywords, 4) + + if error is not None: + sess.code = ErrorCode.WEB_SEARCH_FAIL + sess.parallel_chunks = [] + yield sess + return + + if len(articles) < 1: + sess.code = ErrorCode.NO_SEARCH_RESULT + sess.parallel_chunks = [] + yield sess + return + + for article_id, article in enumerate(articles): + article.cut(0, self.context_max_length) + c = Chunk(content_or_path=article.content, metadata={'source': article.source}) + sess.parallel_chunks.append(c) + yield sess + + async def process_coroutine(self, sess: Session) -> Session: + results = [] + async for value in self.process(sess=sess): + results.append(value) + return results[-1] + + +class ReduceGenerate: + def __init__(self, config: dict, llm: ChatClient, retriever: CacheRetriever, language: str): + self.llm = llm + self.retriever = retriever + if language == 'zh': + self.GENERATE_TEMPLATE = GENERATE_TEMPLATE_CN + else: + self.GENERATE_TEMPLATE = GENERATE_TEMPLATE_EN + llm_config = config['llm'] + self.context_max_length = llm_config['server']['local_llm_max_text_length'] + if llm_config['enable_remote']: + self.context_max_length = llm_config['server']['remote_llm_max_text_length'] + + async def process(self, sess: Session) -> Generator[Session, None, None]: + question = sess.query.text + history = sess.history + + if len(sess.parallel_chunks) < 1: + # direct chat + async for part in self.llm.chat_stream(prompt=question, history=history): + sess.delta = part + yield sess + else: + _, context_str, references = self.retriever.rerank_fuse(query=sess.query, chunks=sess.parallel_chunks, context_max_length=self.context_max_length) + sess.references = references + prompt = self.GENERATE_TEMPLATE.format(context_str, sess.query.text) + async for part in self.llm.chat_stream(prompt=prompt, history=history): + sess.delta = part + yield sess + + +class ParallelPipeline: + """The ParallelPipeline class orchestrates the logic of handling user queries, + generating responses and managing several aspects of a chat assistant. It + enables feature storage, language model client setup, time scheduling and + much more. + + Attributes: + llm: A ChatClient instance that communicates with the language model. + fs: An instance of FeatureStore for loading and querying features. + config_path: A string indicating the path of the configuration file. + config: A dictionary holding the configuration settings. + context_max_length: An integer representing the maximum length of the context used by the language model. # noqa E501 + + Several template strings for various prompts are also defined. + """ + + def __init__(self, work_dir: str, config_path: str): + """Constructs all the necessary attributes for the worker object. + + Args: + work_dir (str): The working directory where feature files are located. + config_path (str): The location of the configuration file. + """ + self.llm = ChatClient(config_path=config_path) + self.retriever = CacheRetriever(config_path=config_path).get(work_dir=work_dir) + + self.config_path = config_path + self.config = None + with open(config_path, encoding='utf8') as f: + self.config = pytoml.load(f) + if self.config is None: + raise Exception('worker config can not be None') + + + async def generate(self, + query: Union[Query, str], + history: List[Tuple[str]]=[], + language: str='zh', + enable_web_search: bool=True): + """Processes user queries and generates appropriate responses. It + involves several steps including checking for valid questions, + extracting topics, querying the feature store, searching the web, and + generating responses from the language model. + + Args: + query (Union[Query,str]): User's multimodal query. + history (str): Chat history. + groupname (str): The group name in which user asked the query. + groupchats (List[str]): The history conversation in group before user query. + + Returns: + Session: Sync generator, this function would yield session which contains: + ErrorCode: An error code indicating the status of response generation. # noqa E501 + str: Generated response to the user query. + references: List for referenced filename or web url + """ + # format input + if type(query) is str: + query = Query(text=query) + + # build input session + sess = Session(query=query, + history=history, + log_path=self.config['worker']['save_path']) + + # build pipeline + preproc = PreprocNode(self.config, self.llm, language) + text2vec = Text2vecRetrieval(self.config, self.llm, self.retriever, language) + websearch = WebSearchRetrieval(self.config, self.config_path, self.llm, language) + reduce = ReduceGenerate(self.config, self.llm, self.retriever, language) + pipeline = [preproc, [text2vec, websearch], reduce] + + direct_chat_states = [ + ErrorCode.QUESTION_TOO_SHORT, ErrorCode.NOT_A_QUESTION, + ErrorCode.NO_TOPIC, ErrorCode.UNRELATED + ] + + # if not a good question, return + for sess in preproc.process(sess): + if sess.code in direct_chat_states: + async for resp in reduce.process(sess): + yield resp + return + + # parallel run text2vec and websearch + + tasks = [text2vec.process_coroutine(copy.deepcopy(sess))] + if enable_web_search: + tasks.append(websearch.process_coroutine(copy.deepcopy(sess))) + + task_results = await asyncio.gather(*tasks, return_exceptions=True) + for result in task_results: + if type(result) is Session: + sess.parallel_chunks += result.parallel_chunks + continue + logger.error(result) + + async for sess in reduce.process(sess): + yield sess + return + + +def parse_args(): + """Parses command-line arguments.""" + parser = argparse.ArgumentParser(description='SerialPipeline.') + parser.add_argument('work_dir', type=str, help='Working directory.') + parser.add_argument( + '--config_path', + default='config.ini', + help='SerialPipeline configuration path. Default value is config.ini') + return parser.parse_args() + + +if __name__ == '__main__': + args = parse_args() + bot = ParallelPipeline(work_dir=args.work_dir, config_path=args.config_path) + loop = asyncio.get_event_loop() + queries = ['茴香豆是什么?', 'HuixiangDou 是什么?'] + + for q in queries: + async def wrap_async_as_coroutine(): + async for sess in bot.generate(query=q, history=[], enable_web_search=False): + print(sess.delta, end='', flush=True) + pass + print('\n') + print(sess.references) + loop.run_until_complete(wrap_async_as_coroutine()) diff --git a/repodir/huixiangdou/huixiangdou/service/prompt.py b/repodir/huixiangdou/huixiangdou/service/prompt.py new file mode 100644 index 00000000..61474667 --- /dev/null +++ b/repodir/huixiangdou/huixiangdou/service/prompt.py @@ -0,0 +1,47 @@ +# PreprocNode +SCORING_QUESTION_TEMPLTE_CN = '“{}”\n请仔细阅读以上内容,判断句子是否是个有主题的疑问句,结果用 0~10 表示。直接提供得分不要解释。\n判断标准:有主语谓语宾语并且是疑问句得 10 分;缺少主谓宾扣分;陈述句直接得 0 分;不是疑问句直接得 0 分。直接提供得分不要解释。' +CR_NEED_CN = """群聊场景中“这”、“它”、“哪”等代词需要查看上下文和其他用户的回复才能确定具体指什么,请完成群聊场景代词替换任务。 +以下是历史对话,可能有多个人的发言: +{} +输入内容:{} +输入内容的信息是否完整,是否需要从历史对话中提取代词或宾语来替代 content 中的一部分词汇? A:不需要提取,信息完整 B:需要 C:不知道 +一步步分析,首先历史消息包含哪些话题;其次哪个话题与问题最相关;如果都不相关就不提取。""" +CR_CN = """请根据历史对话,重写输入的文本。 +以下是历史对话,可能有多个人的发言: +{} +输入的文本 +“{}” +一步步分析,首先历史对话包含哪些话题;其次哪个话题与输入文本中的代词最相关;用相关的话题,替换输入中的代词和缺失的部分。直接返回重写后的文本不要解释。""" + +SCORING_QUESTION_TEMPLTE_EN = '"{}"\nPlease read the content above carefully and judge whether the sentence is a thematic question. Rate it on a scale of 0-10. Only provide the score, no explanation.\nThe criteria are as follows: a sentence gets 10 points if it has a subject, predicate, object and is a question; points are deducted for missing subject, predicate or object; declarative sentences get 0 points; sentences that are not questions also get 0 points. Just give the score, no explanation.' +CR_NEED_EN = """In group chat scenarios, pronouns such as "this," "it," and "which" require examination of the context and other users' responses to determine their specific reference. Please complete the pronoun substitution task in the group chat scenario. +Here is the historical conversation, which may contain multiple people's statements: +{} +Input content: {} +Is the information in the input content complete, and is it necessary to extract pronouns or objects from the historical conversation to replace part of the vocabulary in content? A: No extraction needed, information is complete B: Necessary C: Uncertain +Analyze step by step, first identify which topics are included in the historical messages; second, determine which topic is most relevant to the question; if none are relevant, do not extract.""" +CR_EN = """Please rewrite the input text based on the historical conversation. +Here is the historical conversation, which may include statements from multiple individuals: +{} +The input text +"{}" +Analyze step by step, first identify what topics are included in the historical conversation; secondly, determine which topic is most relevant to the pronoun in the input text; replace the pronoun and missing parts in the input with the relevant topic. Return the rewritten text directly without explanation.""" + +# Text2vecNode +TOPIC_TEMPLATE_CN = '告诉我这句话的主题,不要丢失主语和宾语,直接说主题不要解释:“{}”' +SCORING_RELAVANCE_TEMPLATE_CN = '问题:“{}”\n材料:“{}”\n请仔细阅读以上内容,判断问题和材料的关联度,用0~10表示。判断标准:非常相关得 10 分;完全没关联得 0 分。直接提供得分不要解释。\n' # noqa E501 +GENERATE_TEMPLATE_CN = '材料:“{}”\n 问题:“{}” \n 请仔细阅读参考材料回答问题。' # noqa E501 + +TOPIC_TEMPLATE_EN = 'Tell me the theme of this sentence, just state the theme without explanation: "{}"' # noqa E501 +SCORING_RELAVANCE_TEMPLATE_EN = 'Question: "{}", Background Information: "{}"\nPlease read the content above carefully and assess the relevance between the question and the material on a scale of 0-10. The scoring standard is as follows: extremely relevant gets 10 points; completely irrelevant gets 0 points. Only provide the score, no explanation needed.' # noqa E501 +GENERATE_TEMPLATE_EN = 'Background Information: "{}"\n Question: "{}"\n Please read the reference material carefully and answer the question.' # noqa E501 + +# WebSearchNode +KEYWORDS_TEMPLATE_CN = '谷歌搜索是一个通用搜索引擎,可用于访问互联网、查询百科知识、了解时事新闻等。搜索参数类型 string, 内容是短语或关键字,以空格分隔。\n你现在是{}交流群里的助手,用户问“{}”,你打算通过谷歌搜索查询相关资料,请提供用于搜索的关键字或短语,不要解释直接给出关键字或短语。' # noqa E501 +KEYWORDS_TEMPLATE_EN = 'Google search is a general-purpose search engine that can be used to access the internet, look up encyclopedic knowledge, keep abreast of current affairs and more. Search parameters type: string, content consists of phrases or keywords separated by spaces.\nYou are now the assistant in the "{}" communication group. A user asked "{}", you plan to use Google search to find related information, please provide the keywords or phrases for the search, no explanation, just give the keywords or phrases.' # noqa E501 + +# SecurityNode +PERPLESITY_TEMPLATE_CN = '“question:{} answer:{}”\n阅读以上对话,answer 是否在表达自己不知道,回答越全面得分越少,用0~10表示,不要解释直接给出得分。\n判断标准:准确回答问题得 0 分;答案详尽得 1 分;知道部分答案但有不确定信息得 8 分;知道小部分答案但推荐求助其他人得 9 分;不知道任何答案直接推荐求助别人得 10 分。直接打分不要解释。' # noqa E501 +SECURITY_TEMAPLTE_CN = '判断以下句子是否涉及政治、辱骂、色情、恐暴、宗教、网络暴力、种族歧视等违禁内容,结果用 0~10 表示,不要解释直接给出得分。判断标准:涉其中任一问题直接得 10 分;完全不涉及得 0 分。直接给得分不要解释:“{}”' # noqa E501 +PERPLESITY_TEMPLATE_EN = 'Question: {} Answer: {}\nRead the dialogue above, does the answer express that they don\'t know? The more comprehensive the answer, the lower the score. Rate it on a scale of 0-10, no explanation, just give the score.\nThe scoring standard is as follows: an accurate answer to the question gets 0 points; a detailed answer gets 1 point; knowing some answers but having uncertain information gets 8 points; knowing a small part of the answer but recommends seeking help from others gets 9 points; not knowing any of the answers and directly recommending asking others for help gets 10 points. Just give the score, no explanation.' # noqa E501 +SECURITY_TEMAPLTE_EN = 'Evaluate whether the following sentence involves prohibited content such as politics, insult, pornography, terror, religion, cyber violence, racial discrimination, etc., rate it on a scale of 0-10, do not explain, just give the score. The scoring standard is as follows: any violation directly gets 10 points; completely unrelated gets 0 points. Give the score, no explanation: "{}"' # noqa E501 diff --git a/repodir/huixiangdou/huixiangdou/service/retriever.py b/repodir/huixiangdou/huixiangdou/service/retriever.py new file mode 100644 index 00000000..935495d8 --- /dev/null +++ b/repodir/huixiangdou/huixiangdou/service/retriever.py @@ -0,0 +1,312 @@ +# Copyright (c) OpenMMLab. All rights reserved. +"""extract feature and search with user query.""" +import json +import os +import pdb +import time +from typing import Any, Union, Tuple, List + +import numpy as np +import pytoml +from loguru import logger +from sklearn.metrics import precision_recall_curve + +from huixiangdou.primitive import Embedder, Faiss, LLMReranker, Query, Chunk + +from ..primitive import FileOperation +from .helper import QueryTracker +from .kg import KnowledgeGraph + + +class Retriever: + """Tokenize and extract features from the project's chunks, for use in the + reject pipeline and response pipeline.""" + + def __init__(self, config_path: str, embedder: Any, reranker: Any, + work_dir: str, reject_throttle: float) -> None: + """Init with model device type and config.""" + self.config_path = config_path + self.reject_throttle = reject_throttle + + self.embedder = embedder + self.reranker = reranker + self.faiss = None + + if not os.path.exists(work_dir): + logger.warning('!!!warning, workdir not exist.!!!') + return + + # load prebuilt knowledge graph gpickle + self.kg = KnowledgeGraph(config_path=config_path) + + # dense retrieval, load refusal-to-answer and response feature database + dense_path = os.path.join(work_dir, 'db_dense') + if not os.path.exists(dense_path): + logger.warning('retriever is None, skip load faiss') + self.faiss = None + else: + self.faiss = Faiss.load_local(dense_path) + + def update_throttle(self, + config_path: str = 'config.ini', + good_questions=[], + bad_questions=[]): + """Update reject throttle based on positive and negative examples.""" + + if len(good_questions) == 0 or len(bad_questions) == 0: + raise Exception('good and bad question examples cat not be empty.') + questions = good_questions + bad_questions + predictions = [] + self.reject_throttle = -1 + + for question in questions: + _, score = self.is_relative(query=question, + enable_kg=True, enable_threshold=False) + predictions.append(max(0, score)) + + labels = [1 for _ in range(len(good_questions)) + ] + [0 for _ in range(len(bad_questions))] + precision, recall, thresholds = precision_recall_curve( + labels, predictions) + + # get the best index for sum(precision, recall) + sum_precision_recall = precision[:-1] + recall[:-1] + index_max = np.argmax(sum_precision_recall) + optimal_threshold = max(thresholds[index_max], 0.0) + + with open(config_path, encoding='utf8') as f: + config = pytoml.load(f) + config['feature_store']['reject_throttle'] = float(optimal_threshold) + with open(config_path, 'w', encoding='utf8') as f: + pytoml.dump(config, f) + logger.info( + f'The optimal threshold is: {optimal_threshold}, saved it to {config_path}' # noqa E501 + ) + + def text2vec_retrieve(self, query: Union[Query, str]): + """Retrieve chunks by text2vec model or knowledge graph. + + Args: + query (Query): The multimodal question asked by the user. + + Returns: + List[Chunk]: ref chunks. + """ + if type(query) is str: + query = Query(text=query) + + graph_delta = 0.0 + if self.kg.is_available(): + try: + docs = self.kg.retrieve(query=query.text) + graph_delta = 0.2 * min(100, len(docs)) / 100 + except Exception as e: + logger.warning(str(e)) + logger.info('KG folder exists, but search failed, skip.') + + threshold = self.reject_throttle - graph_delta + pairs = self.faiss.similarity_search_with_query(self.embedder, + query=query, threshold=threshold) + chunks = [pair[0] for pair in pairs] + return chunks + + def rerank_fuse(self, query: Union[Query, str], chunks: List[Chunk], context_max_length:int): + """Rerank chunks and extract content + + Args: + chunks (List[Chunk]): filtered chunks. + + Returns: + str: Joined chunks, or empty string + str: Matched context from origin file content + List[str]: References + """ + if type(query) is str: + query = Query(text=query) + + rerank_chunks = self.reranker.rerank(query=query.text, + chunks=chunks) + + file_opr = FileOperation() + # Add file text to context, until exceed `context_max_length` + # If `file_length` > `context_max_length` (for example file_length=300 and context_max_length=100) + # then centered on the chunk, read a length of 200 + splits = [] + context = '' + references = [] + for idx, chunk in enumerate(rerank_chunks): + + content = chunk.content_or_path + splits.append(content) + + source = chunk.metadata['source'] + if '://' in source: + # url + file_text = content + else: + file_text, error = file_opr.read(chunk.metadata['read']) + if error is not None: + # read file failed, skip + continue + + logger.info('target {} content length {}'.format( + source, len(file_text))) + if len(file_text) + len(context) > context_max_length: + if source in references: + continue + references.append(source) + + # add and break + add_len = context_max_length - len(context) + if add_len <= 0: + break + content_index = file_text.find(content) + if content_index == -1: + # content not in file_text + context += content + context += '\n' + context += file_text[0:add_len - len(content) - 1] + else: + start_index = max(0, + content_index - (add_len - len(content))) + context += file_text[start_index:start_index + add_len] + break + + if source not in references: + context += file_text + context += '\n' + references.append(source) + + context = context[0:context_max_length] + logger.debug('query:{} files:{}'.format(query, references)) + return '\n'.join(splits), context, [ + os.path.basename(r) for r in references + ] + + def query(self, + query: Union[Query, str], + context_max_length: int = 40000, + tracker: QueryTracker = None): + """Processes a query and returns the best match from the vector store + database. If the question is rejected, returns None. + + Args: + query (Query): The multimodal question asked by the user. + context_max_length (int): Max contenxt length for LLM. + tracker (QueryTracker): Log tracker. + + Returns: + str: Matched chunks, or empty string + str: Matched context from origin file content + List[str]: References + """ + if type(query) is str: + query = Query(text=query) + + if query.text is None or len(query.text) < 1 or self.faiss is None: + return None, None, [] + + if len(query.text) > 512: + logger.warning('input too long, truncate to 512') + query.text = query.text[0:512] + + high_score_chunks = self.text2vec_retrieve(query=query) + if tracker is not None: + tracker.log('retrieve', [c.metadata['source'] for c in high_score_chunks]) + + return self.rerank_fuse(query=query, chunks=high_score_chunks, context_max_length=context_max_length) + + def is_relative(self, + query, + k=30, + enable_kg=True, + enable_threshold=True) -> Tuple[bool, float]: + """Is input query relative with knowledge base. Return true or false, and the maxisum score""" + if type(query) is str: + query = Query(text=query) + + if query.text is None or len(query.text) < 1 or self.faiss is None: + raise ValueError('input query {}, faiss {}'.format(query, self.faiss)) + + graph_delta = 0.0 + if not enable_kg and self.kg.is_available(): + try: + docs = self.kg.retrieve(query=query.text) + graph_delta = 0.2 * min(100, len(docs)) / 100 + except Exception as e: + logger.warning(str(e)) + logger.info('KG folder exists, but search failed, skip.') + + threshold = self.reject_throttle - graph_delta + + if enable_threshold: + pairs = self.faiss.similarity_search_with_query(self.embedder, query=query, threshold=threshold) + else: + pairs = self.faiss.similarity_search_with_query(self.embedder, query=query, threshold=-1) + if len(pairs) > 0: + return True, pairs[0][1] + return False, -1 + +class CacheRetriever: + + def __init__(self, + config_path: str, + cache_size: int = 4, + rerank_topn: int = 4): + self.cache = dict() + self.cache_size = cache_size + with open(config_path, encoding='utf8') as f: + fs_config = pytoml.load(f)['feature_store'] + + # load text2vec and rerank model + logger.info('loading test2vec and rerank models') + self.embedder = Embedder(model_config=fs_config) + self.reranker = LLMReranker(model_config=fs_config, + topn=rerank_topn) + + def get(self, + fs_id: str = 'default', + config_path='config.ini', + work_dir='workdir'): + """Get database by id.""" + + if fs_id in self.cache: + self.cache[fs_id]['time'] = time.time() + return self.cache[fs_id]['retriever'] + + with open(config_path, encoding='utf8') as f: + reject_throttle = pytoml.load( + f)['feature_store']['reject_throttle'] + + if len(self.cache) >= self.cache_size: + # drop the oldest one + del_key = None + min_time = time.time() + for key, value in self.cache.items(): + cur_time = value['time'] + if cur_time < min_time: + min_time = cur_time + del_key = key + + if del_key is not None: + del_value = self.cache[del_key] + self.cache.pop(del_key) + del del_value['retriever'] + + retriever = Retriever(config_path=config_path, + embedder=self.embedder, + reranker=self.reranker, + work_dir=work_dir, + reject_throttle=reject_throttle) + self.cache[fs_id] = {'retriever': retriever, 'time': time.time()} + return retriever + + def pop(self, fs_id: str): + """Drop database by id.""" + + if fs_id not in self.cache: + return + del_value = self.cache[fs_id] + self.cache.pop(fs_id) + # manually free memory + del del_value diff --git a/repodir/huixiangdou/huixiangdou/service/serial_pipeline.py b/repodir/huixiangdou/huixiangdou/service/serial_pipeline.py new file mode 100644 index 00000000..552997ae --- /dev/null +++ b/repodir/huixiangdou/huixiangdou/service/serial_pipeline.py @@ -0,0 +1,525 @@ +# Copyright (c) OpenMMLab. All rights reserved. +"""Pipeline.""" +import argparse +import datetime +import json +import os +import re +import time +import pdb +from abc import ABC, abstractmethod +from typing import List, Tuple, Union, Generator + +import pytoml +from loguru import logger + +from huixiangdou.primitive import Query + +from .helper import ErrorCode, is_truth +from .llm_client import ChatClient +from .retriever import CacheRetriever, Retriever +from .sg_search import SourceGraphProxy +from .session import Session +from .web_search import WebSearch +from .prompt import (SCORING_QUESTION_TEMPLTE_CN, CR_NEED_CN, CR_CN, TOPIC_TEMPLATE_CN, SCORING_RELAVANCE_TEMPLATE_CN, GENERATE_TEMPLATE_CN, KEYWORDS_TEMPLATE_CN, PERPLESITY_TEMPLATE_CN, SECURITY_TEMAPLTE_CN) +from .prompt import (SCORING_QUESTION_TEMPLTE_EN, CR_NEED_EN, CR_EN, TOPIC_TEMPLATE_EN, SCORING_RELAVANCE_TEMPLATE_EN, GENERATE_TEMPLATE_EN, KEYWORDS_TEMPLATE_EN, PERPLESITY_TEMPLATE_EN, SECURITY_TEMAPLTE_EN) + +class Node(ABC): + """Base abstract for compute graph.""" + + @abstractmethod + def process(self, sess: Session) -> Generator[Session, None, None]: + pass + +class PreprocNode(Node): + """PreprocNode is for coreference resolution and scoring based on group + chats. + + See https://arxiv.org/abs/2405.02817 + """ + + def __init__(self, config: dict, llm: ChatClient, language: str): + self.llm = llm + self.enable_cr = config['worker']['enable_cr'] + + if language == 'zh': + self.SCORING_QUESTION_TEMPLTE = SCORING_QUESTION_TEMPLTE_CN + self.CR = CR_CN + else: + self.SCORING_QUESTION_TEMPLTE = SCORING_QUESTION_TEMPLTE_EN + self.CR = CR_EN + + def process(self, sess: Session) -> Generator[Session, None, None]: + # check input + if sess.query.text is None or len(sess.query.text) < 6: + sess.code = ErrorCode.QUESTION_TOO_SHORT + yield sess + return + + prompt = self.SCORING_QUESTION_TEMPLTE.format(sess.query.text) + truth, logs = is_truth(llm=self.llm, + prompt=prompt, + throttle=6, + default=3) + sess.debug['PreprocNode_is_question'] = logs + if not truth: + sess.code = ErrorCode.NOT_A_QUESTION + yield sess + return + + if not self.enable_cr: + return + + if len(sess.groupchats) < 1: + logger.debug('history conversation empty, skip CR') + yield sess + return + + talks = [] + + # rewrite user_id to ABCD.. + name_map = dict() + name_int = ord('A') + for msg in sess.groupchats: + sender = msg.sender + if sender not in name_map: + name_map[sender] = chr(name_int) + name_int += 1 + talks.append({'sender': name_map[sender], 'content': msg.query}) + + talk_str = json.dumps(talks, ensure_ascii=False) + + prompt = self.CR.format(talk_str, sess.query.text) + self.cr = self.llm.generate_response(prompt=prompt, backend='remote') + if self.cr.startswith('“') and self.cr.endswith('”'): + self.cr = self.cr[1:len(self.cr) - 1] + if self.cr.startswith('"') and self.cr.endswith('"'): + self.cr = self.cr[1:len(self.cr) - 1] + sess.debug['cr'] = self.cr + + # rewrite query + queries = [sess.query.text, self.cr] + self.query = '\n'.join(queries) + logger.debug('merge query and cr, query: {} cr: {}'.format( + self.query, self.cr)) + + +class Text2vecNode(Node): + """Text2vecNode is for retrieve from knowledge base.""" + + def __init__(self, config: dict, llm: ChatClient, retriever: Retriever, + language: str): + self.llm = llm + self.retriever = retriever + llm_config = config['llm'] + self.context_max_length = llm_config['server'][ + 'local_llm_max_text_length'] + if llm_config['enable_remote']: + self.context_max_length = llm_config['server'][ + 'remote_llm_max_text_length'] + if language == 'zh': + self.TOPIC_TEMPLATE = TOPIC_TEMPLATE_CN + self.SCORING_RELAVANCE_TEMPLATE = SCORING_RELAVANCE_TEMPLATE_CN + self.GENERATE_TEMPLATE = GENERATE_TEMPLATE_CN + else: + self.TOPIC_TEMPLATE = TOPIC_TEMPLATE_EN + self.SCORING_RELAVANCE_TEMPLATE = SCORING_RELAVANCE_TEMPLATE_EN + self.GENERATE_TEMPLATE = GENERATE_TEMPLATE_EN + self.max_length = self.context_max_length - 2 * len( + self.GENERATE_TEMPLATE) + + def process(self, sess: Session) -> Generator[Session, None, None]: + """Try get reply with text2vec & rerank model.""" + + # get query topic + prompt = self.TOPIC_TEMPLATE.format(sess.query.text) + sess.topic = self.llm.generate_response(prompt) + for prefix in ['主题:', '这句话的主题是:']: + if sess.topic.startswith(prefix): + sess.topic = sess.topic[len(prefix):] + + sess.debug['Text2vecNode_topic'] = sess.topic + if len(sess.topic) < 2: + # topic too short, return + sess.code = ErrorCode.NO_TOPIC + yield sess + return + + # retrieve from knowledge base + sess.chunk, sess.knowledge, sess.references = self.retriever.query( + Query(sess.topic, sess.query.image), + context_max_length=self.max_length) + sess.debug['Text2vecNode_chunk'] = sess.chunk + if sess.knowledge is None: + sess.code = ErrorCode.UNRELATED + yield sess + return + + yield sess + + # get relavance between query and knowledge base + prompt = self.SCORING_RELAVANCE_TEMPLATE.format( + sess.query.text, sess.chunk) + truth, logs = is_truth(llm=self.llm, + prompt=prompt, + throttle=5, + default=10) + sess.debug['Text2vecNode_chunk_relavance'] = logs + if not truth: + yield sess + return + + # answer the question + prompt = self.GENERATE_TEMPLATE.format(sess.knowledge, sess.query.text) + # response = self.llm.generate_response(prompt=prompt, history=sess.history, backend='puyu') + response = self.llm.generate_response(prompt=prompt, + history=sess.history, + backend='remote') + + sess.code = ErrorCode.SUCCESS + sess.response = response + yield sess + + +class WebSearchNode(Node): + """WebSearchNode is for web search, use `ddgs` or `serper`""" + + def __init__(self, config: dict, config_path: str, llm: ChatClient, + language: str): + self.llm = llm + self.config_path = config_path + self.enable = config['worker']['enable_web_search'] + llm_config = config['llm'] + self.context_max_length = llm_config['server'][ + 'local_llm_max_text_length'] + if llm_config['enable_remote']: + self.context_max_length = llm_config['server'][ + 'remote_llm_max_text_length'] + if language == 'zh': + self.SCORING_RELAVANCE_TEMPLATE = SCORING_RELAVANCE_TEMPLATE_CN + self.KEYWORDS_TEMPLATE = KEYWORDS_TEMPLATE_CN + self.GENERATE_TEMPLATE = GENERATE_TEMPLATE_CN + else: + self.SCORING_RELAVANCE_TEMPLATE = SCORING_RELAVANCE_TEMPLATE_EN + self.KEYWORDS_TEMPLATE = KEYWORDS_TEMPLATE_EN + self.GENERATE_TEMPLATE = GENERATE_TEMPLATE_EN + self.max_length = self.context_max_length - 2 * len( + self.GENERATE_TEMPLATE) + + def process(self, sess: Session) -> Generator[Session, None, None]: + """Try web search.""" + + if not self.enable: + logger.debug('disable web_search') + yield sess + return + + engine = WebSearch(config_path=self.config_path) + + prompt = self.KEYWORDS_TEMPLATE.format(sess.groupname, sess.query.text) + search_keywords = self.llm.generate_response(prompt) + sess.debug['WebSearchNode_keywords'] = prompt + articles, error = engine.get(query=search_keywords, max_article=2) + + if error is not None: + sess.code = ErrorCode.WEB_SEARCH_FAIL + yield sess + return + + for article_id, article in enumerate(articles): + article.cut(0, self.max_length) + prompt = self.SCORING_RELAVANCE_TEMPLATE.format( + sess.query.text, article.brief) + # truth, logs = is_truth(llm=self.llm, prompt=prompt, throttle=5, default=10, backend='puyu') + truth, logs = is_truth(llm=self.llm, + prompt=prompt, + throttle=5, + default=10, + backend='remote') + sess.debug['WebSearchNode_relavance_{}'.format(article_id)] = logs + if truth: + sess.web_knowledge += '\n' + sess.web_knowledge += article.content + sess.references.append(article.source) + + sess.web_knowledge = sess.web_knowledge[0:self.max_length].strip() + if len(sess.web_knowledge) < 1: + sess.code = ErrorCode.NO_SEARCH_RESULT + yield sess + return + + prompt = self.GENERATE_TEMPLATE.format(sess.web_knowledge, + sess.query.text) + # sess.response = self.llm.generate_response(prompt=prompt, history=sess.history, backend="puyu") + sess.response = self.llm.generate_response(prompt=prompt, + history=sess.history, + backend='remote') + sess.code = ErrorCode.SUCCESS + yield sess + +class SGSearchNode(Node): + """SGSearchNode is for retrieve from source graph.""" + + def __init__(self, config: dict, config_path: str, llm: ChatClient, + language: str): + self.llm = llm + self.language = language + self.enable = config['worker']['enable_sg_search'] + self.config_path = config_path + + if language == 'zh': + self.GENERATE_TEMPLATE = GENERATE_TEMPLATE_CN + else: + self.GENERATE_TEMPLATE = GENERATE_TEMPLATE_EN + + def process(self, sess: Session) -> Generator[Session, None, None]: + """Try get reply with source graph.""" + + if not self.enable: + logger.debug('disable sg_search') + yield sess + return + + # if exit for other status (SECURITY or SEARCH_FAIL), still quit `sg_search` + if sess.code != ErrorCode.BAD_ANSWER and sess.code != ErrorCode.NO_SEARCH_RESULT and sess.code != ErrorCode.WEB_SEARCH_FAIL: + yield sess + return + + sg = SourceGraphProxy(config_path=self.config_path, + language=self.language) + sess.sg_knowledge = sg.search(llm_client=self.llm, + question=sess.query.text, + groupname=sess.groupname) + + sess.debug['SGSearchNode_knowledge'] = sess.sg_knowledge + if sess.sg_knowledge is None or len(sess.sg_knowledge) < 1: + sess.code = ErrorCode.SG_SEARCH_FAIL + yield sess + return + + prompt = self.GENERATE_TEMPLATE.format(sess.sg_knowledge, + sess.query.text) + # sess.response = self.llm.generate_response(prompt=prompt, history=sess.history, backend='puyu') + sess.response = self.llm.generate_response(prompt=prompt, + history=sess.history, + backend='remote') + if sess.response is None or len(sess.response) < 1: + sess.code = ErrorCode.LLM_NOT_RESPONSE_SG + yield sess + return + sess.code = ErrorCode.SUCCESS + yield sess + +class SecurityNode(Node): + """SecurityNode is for result check.""" + + def __init__(self, llm: ChatClient, language: str): + self.llm = llm + if language == 'zh': + self.PERPLESITY_TEMPLATE = PERPLESITY_TEMPLATE_CN + self.SECURITY_TEMAPLTE = SECURITY_TEMAPLTE_CN + else: + self.PERPLESITY_TEMPLATE = PERPLESITY_TEMPLATE_EN + self.SECURITY_TEMAPLTE = SECURITY_TEMAPLTE_EN + + def process(self, sess: Session) -> Generator[Session, None, None]: + """Check result with security.""" + if len(sess.response) < 1: + sess.code = ErrorCode.BAD_ANSWER + yield sess + return + prompt = self.PERPLESITY_TEMPLATE.format(sess.query.text, + sess.response) + truth, logs = is_truth(llm=self.llm, + prompt=prompt, + throttle=9, + default=0) + sess.debug['SecurityNode_qa_perplex'] = logs + if truth: + sess.code = ErrorCode.BAD_ANSWER + yield sess + return + + prompt = self.SECURITY_TEMAPLTE.format(sess.response) + truth, logs = is_truth(llm=self.llm, + prompt=prompt, + throttle=8, + default=0) + sess.debug['SecurityNode_template'] = logs + if truth: + sess.code = ErrorCode.SECURITY + yield sess + + +class SerialPipeline: + """The SerialPipeline class orchestrates the logic of handling user queries, + generating responses and managing several aspects of a chat assistant. It + enables feature storage, language model client setup, time scheduling and + much more. + + Attributes: + llm: A ChatClient instance that communicates with the language model. + fs: An instance of FeatureStore for loading and querying features. + config_path: A string indicating the path of the configuration file. + config: A dictionary holding the configuration settings. + language: A string indicating the language of the chat, default is 'zh' (Chinese). # noqa E501 + context_max_length: An integer representing the maximum length of the context used by the language model. # noqa E501 + + Several template strings for various prompts are also defined. + """ + + def __init__(self, work_dir: str, config_path: str, language: str = 'zh'): + """Constructs all the necessary attributes for the worker object. + + Args: + work_dir (str): The working directory where feature files are located. + config_path (str): The location of the configuration file. + language (str, optional): Specifies the language to be used. Defaults to 'zh' (Chinese). # noqa E501 + """ + self.llm = ChatClient(config_path=config_path) + self.retriever = CacheRetriever(config_path=config_path).get() + + self.config_path = config_path + self.config = None + self.language = language + with open(config_path, encoding='utf8') as f: + self.config = pytoml.load(f) + if self.config is None: + raise Exception('worker config can not be None') + + def direct_chat(self, query: str): + """"Generate reply with LLM.""" + return self.llm.generate_response(prompt=query, backend='remote') + + def notify_badcase(self): + """Receiving revert command means the current threshold is too low, use + higher one.""" + delta = max(0, 1 - self.retriever.reject_throttle) * 0.02 + logger.info( + 'received badcase, use bigger reject_throttle. Current {}, delta {}' + .format(self.retriever.reject_throttle, delta)) + + # this throttle also means quality, cannot exceed 0.5 + self.retriever.reject_throttle = min( + self.retriever.reject_throttle + delta, 0.5) + with open('throttle', 'w') as f: + f.write(str(self.retriever.reject_throttle)) + + def work_time(self): + """If worktime enabled, determines the current time falls within the + scheduled working hours of the chat assistant. + + Returns: + bool: True if the current time is within working hours, otherwise False. # noqa E501 + """ + + time_config = self.config['worker']['time'] + if 'enable' in time_config: + # work time not enabled, start work + if not time_config['enable']: + return True + + beginWork = datetime.datetime.now().strftime( + '%Y-%m-%d') + ' ' + time_config['start'] + endWork = datetime.datetime.now().strftime( + '%Y-%m-%d') + ' ' + time_config['end'] + beginWorkSeconds = time.time() - time.mktime( + time.strptime(beginWork, '%Y-%m-%d %H:%M:%S')) + endWorkSeconds = time.time() - time.mktime( + time.strptime(endWork, '%Y-%m-%d %H:%M:%S')) + + if int(beginWorkSeconds) > 0 and int(endWorkSeconds) < 0: + if not time_config['has_weekday']: + return True + + if int(datetime.datetime.now().weekday()) in range(7): + return True + return False + + def generate(self, + query: Union[Query, str], + history: List[str] = [], + groupname: str = '', + groupchats: List[str] = []): + """Processes user queries and generates appropriate responses. It + involves several steps including checking for valid questions, + extracting topics, querying the feature store, searching the web, and + generating responses from the language model. + + Args: + query (Union[Query,str]): User's multimodal query. + history (str): Chat history. + groupname (str): The group name in which user asked the query. + groupchats (List[str]): The history conversation in group before user query. + + Returns: + Session: Sync generator, this function would yield session which contains: + ErrorCode: An error code indicating the status of response generation. # noqa E501 + str: Generated response to the user query. + references: List for referenced filename or web url + """ + # format input + if type(query) is str: + query = Query(text=query) + + # build input session + sess = Session(query=query, + history=history, + groupname=groupname, + log_path=self.config['worker']['save_path'], + groupchats=groupchats) + + # build pipeline + preproc = PreprocNode(self.config, self.llm, self.language) + text2vec = Text2vecNode(self.config, self.llm, self.retriever, + self.language) + websearch = WebSearchNode(self.config, self.config_path, self.llm, + self.language) + sgsearch = SGSearchNode(self.config, self.config_path, self.llm, + self.language) + check = SecurityNode(self.llm, self.language) + pipeline = [preproc, text2vec, websearch, sgsearch] + + # run + exit_states = [ + ErrorCode.QUESTION_TOO_SHORT, ErrorCode.NOT_A_QUESTION, + ErrorCode.NO_TOPIC, ErrorCode.UNRELATED + ] + for node in pipeline: + + for sess in node.process(sess): + yield sess + + # unrelated to knowledge base or bad input, exit + if sess.code in exit_states: + break + + if sess.code == ErrorCode.SUCCESS: + for sess in check.process(sess): + yield sess + + # check success, return + if sess.code == ErrorCode.SUCCESS: + break + + logger.debug(sess.debug) + return sess + # return sess.code, sess.response, sess.references + +def parse_args(): + """Parses command-line arguments.""" + parser = argparse.ArgumentParser(description='SerialPipeline.') + parser.add_argument('work_dir', type=str, help='Working directory.') + parser.add_argument( + '--config_path', + default='config.ini', + help='SerialPipeline configuration path. Default value is config.ini') + return parser.parse_args() + + +if __name__ == '__main__': + args = parse_args() + bot = SerialPipeline(work_dir=args.work_dir, config_path=args.config_path) + queries = ['茴香豆是怎么做的'] + for example in queries: + print(bot.generate(query=example, history=[], groupname='')) diff --git a/repodir/huixiangdou/huixiangdou/service/session.py b/repodir/huixiangdou/huixiangdou/service/session.py new file mode 100644 index 00000000..1c7c6ac8 --- /dev/null +++ b/repodir/huixiangdou/huixiangdou/service/session.py @@ -0,0 +1,54 @@ +from huixiangdou.primitive import Query +from .helper import ErrorCode +import os +import json + +class Session: + """For compute graph, `session` takes all parameter.""" + + def __init__(self, + query: Query, + history: list, + groupname: str = '', + log_path: str = 'logs/generate.jsonl', + groupchats: list = []): + self.query = query + self.history = history + self.groupname = groupname + self.groupchats = groupchats + + # init + # Same as `chunk.choices[0].delta` + self.delta = '' + self.parallel_chunks = [] + self.response = '' + self.references = [] + self.topic = '' + self.code = ErrorCode.INIT + + # coreference resolution results + self.cr = '' + + # text2vec results + self.chunk = '' + self.knowledge = '' + + # web search results + self.web_knowledge = '' + + # source graph search results + self.sg_knowledge = '' + + # debug logs + self.debug = dict() + self.log_path = log_path + + def __del__(self): + dirname = os.path.dirname(self.log_path) + if not os.path.exists(dirname): + os.makedirs(dirname) + + with open(self.log_path, 'a') as f: + json_str = json.dumps(self.debug, indent=2, ensure_ascii=False) + f.write(json_str) + f.write('\n') diff --git a/repodir/huixiangdou/huixiangdou/service/sg_search.py b/repodir/huixiangdou/huixiangdou/service/sg_search.py new file mode 100644 index 00000000..7ff1590f --- /dev/null +++ b/repodir/huixiangdou/huixiangdou/service/sg_search.py @@ -0,0 +1,206 @@ +# Copyright (c) OpenMMLab. All rights reserved. +"""Search enhancement proxy.""" +import argparse +import json +import os + +import pytoml +from loguru import logger + +from .llm_client import ChatClient + + +class SourceGraphProxy: + """A class to serve as a proxy for interacting with the Source Graph. + + Args: + config_path (str): Path to the configuration file. + topk (int, optional): Top K results to consider from the search. Defaults to 1. # noqa E501 + language (str, optional): Language for the system prompts - 'zh' for Chinese and 'en' for English. Defaults to 'zh'. # noqa E501 + + Attributes: + config_path (str): The path of the configuration file. + sg_config (dict): Configuration settings for sourcegraph search. + topk (int): Top K results to consider from the search. + language (str): Language for the system prompts. + CHOICE_TEMPLATE (str): Template string for generating choice based on selected language. # noqa E501 + KEYWORDS_TEMPLATE (str): Template string for generating keywords based on selected language. # noqa E501 + """ + + def __init__(self, config_path: str, topk=1, language: str = 'zh') -> None: + """Init searcher with config.""" + self.config_path = config_path + self.sg_config = None + with open(self.config_path, encoding='utf8') as f: + config = pytoml.load(f) + self.sg_config = config['sg_search'] + + self.topk = topk + self.language = language + if self.language == 'zh': + self.CHOICE_TEMPLATE = '“{}”\n请仔细阅读以上问题,请问应该查询以下哪个开源项目:\n' # noqa E501 + self.KEYWORDS_TEMPLATE = '“{}”\n请仔细阅读以上问题,提取其中可用作搜索引擎的关键字,关键字之间,分隔,不要解释。' # noqa E501 + else: + self.CHOICE_TEMPLATE = '"{}"\nPlease read the above question carefully, which of the following open-source projects should this question refer to: \n' # noqa E501 + self.KEYWORDS_TEMPLATE = '"{}"\nPlease read the above questions carefully, extract the keywords which can be used as search engines, between keywords, separate, do not explain.' # noqa E501 + + def command(self, txt: str): + """Executes a shell command and returns its output. + + Args: + txt (str): Command to be executed in the shell. + + Returns: + str: Output of the shell command execution. + """ + logger.debug('cmd: {}'.format(txt)) + cmd = os.popen(txt) + return cmd.read().rstrip().lstrip() + + def extract_sg_result(self, jsonstr): + """Extracts the desired data from the source graph result. + + Args: + jsonstr (str): JSON string containing source graph search result. + + Returns: + list: List of dictionaries each contains 'filepath' and 'content' of the files returned by source graph. # noqa E501 + """ + ret = [] + try: + root = json.loads(jsonstr) + results = root['Results'] + for result in results: + if 'FileMatch' != result['__typename']: + continue + + content = result['file']['content'] + path = result['file']['path'] + ret.append({'filepath': path, 'content': content}) + + if len(ret) >= self.topk: + break + except Exception as e: + logger.warning('{} when source graph parse {}'.format( + str(e), jsonstr)) + return ret + + def choose_repo(self, llm_client, question, groupname): + """Interactively assists user to select a repository for search based + on user's question. + + Args: + llm_client: Client instance for LLM. + question (str): User's question. + groupname (str): Name of the user's group. + + Returns: + str: The ID of selected repository. + """ + prompt = self.CHOICE_TEMPLATE.format(question) + + keys = self.sg_config.keys() + skip = ['binary_src_path', 'src_access_token'] + repos = {} + for key in keys: + if key in skip: + continue + introduction = self.sg_config[key]['introduction'] + prompt += f'* {key} {introduction}\n' + repos[key] = self.sg_config[key] + prompt += '* none ' + choice = llm_client.generate_response(prompt=prompt, + backend='remote').strip() + + target_repo_id = None + for key in repos.keys(): + if key in choice: + target_repo_id = repos[key]['github_repo_id'] + break + + return target_repo_id + + def search(self, llm_client, question, groupname): + """Performs a search operation in the selected repository based on the + user's question. + + Args: + llm_client: Client instance for LLM. + question (str): User's question. + groupname (str): Name of the user's group. + + Returns: + str: Search result from source graph in JSON format. + """ + repo_id = self.choose_repo(llm_client, question, groupname) + if repo_id is None: + logger.warning('cannot choose repo_id') + return '' + + ENV = 'export SRC_ACCESS_TOKEN="{}" && '.format( + self.sg_config['src_access_token']) + BINARY = self.sg_config['binary_src_path'] + if not os.path.exists(BINARY): + raise Exception('{} not exist'.format(BINARY)) + return '' + + prompt = self.KEYWORDS_TEMPLATE.format(question) + entities = [] + entity_str = '' + try: + entity_str = llm_client.generate_response(prompt=prompt) + separator = ',' + if ',' in entity_str: + separator = ',' + entities = [ + item for item in entity_str.split(separator) if item.strip() + ] + except Exception as e: + logger.error('parse {} failed {}.'.format(entity_str, str(e))) + # return '' + entities = [] + + search_items = [] + for entity in entities: + # search doc and source code based on entities + # search -json 'repo:open-compass/opencompass summarizers' + cmd_doc = '''{} search -json 'repo:{} lang:MarkDown {}' '''.format( + BINARY, repo_id, entity) + cmd_return = self.command(ENV + cmd_doc) + search_items += self.extract_sg_result(cmd_return) + + cmd_python = '''{} search -json 'repo:{} lang:Python {}' '''.format( # noqa E501 + BINARY, repo_id, entity) + cmd_return = self.command(ENV + cmd_python) + search_items += self.extract_sg_result(cmd_return) + + if len(search_items) < 1: + return None + + search_text = json.dumps(search_items, ensure_ascii=False, indent=2) + return search_text + + +def parse_args(): + """Parses command line arguments.""" + parser = argparse.ArgumentParser(description='Source graph proxy search') + parser.add_argument( + '--config_path', + default='config.ini', + help= # noqa E251 + 'Source graph proxy configuration path. Default value is config.ini') + args = parser.parse_args() + return args + + +if __name__ == '__main__': + """Test search.""" + logger.add('logs/sg_search.log', rotation='4MB') + args = parse_args() + + llm = ChatClient(config_path=args.config_path) + sg = SourceGraphProxy(config_path=args.config_path) + context = sg.search(llm, + question='请问triviaqa 5shot结果怎么在summarizer里输出呢', + groupname='opencompass') + print(context) diff --git a/repodir/huixiangdou/huixiangdou/service/web_search.py b/repodir/huixiangdou/huixiangdou/service/web_search.py new file mode 100644 index 00000000..0961817f --- /dev/null +++ b/repodir/huixiangdou/huixiangdou/service/web_search.py @@ -0,0 +1,363 @@ +# Copyright (c) OpenMMLab. All rights reserved. +"""Web search utils.""" +import argparse +import asyncio +import json +import os +import time +import types +import pytoml +import requests +from bs4 import BeautifulSoup as BS +from duckduckgo_search import DDGS +from loguru import logger +from readability import Document + +from ..primitive import FileOperation +from .helper import check_str_useful + +# import_pyppeteer = False +# try: +# from pyppeteer import launch +# import_pyppeteer = True +# except Exception as e: +# # Fix ldd ~/.local/share/pyppeteer/local-chromium/1181205/chrome-linux/chrome | grep not +# # apt install libgbm-dev +# # See https://techoverflow.net/2020/09/29/how-to-fix-pyppeteer-pyppeteer-errors-browsererror-browser-closed-unexpectedly/ +# logger.warning( +# 'For better URL parsing, try `pip install pyppeteer` and see https://github.com/pyppeteer/pyppeteer/issues/442' +# ) + + +# async def fetch_chroumium_content(url): +# browser = await launch(headless=True, +# args=[ +# '--no-sandbox', '--disable-dev-shm-usage', +# '--disable-gpu', +# '--disable-software-rasterizer', +# '--disable-setuid-sandbox' +# ]) +# page = await browser.newPage() +# await page.goto(url) +# time.sleep(1) +# content = await page.evaluate('document.body.innerText', force_expr=True) +# await browser.close() +# return content + + +class Article: + + def __init__(self, content: str = '', source='', brief=''): + self.content = content + self.source = source + if len(brief) < 1: + self.brief = content + else: + self.brief = brief + + def __str__(self): + return self.content + + def __len__(self): + return len(self.content) + + def cut(self, start_index, end_index): + self.source = self.source[start_index:end_index] + + +class WebSearch: + """This class provides functionality to perform web search operations. + + Attributes: + config_path (str): Path to the configuration file. + retry (int): Number of times to retry a request before giving up. + + Methods: + load_key(): Retrieves API key from the config file. + load_save_dir(): Gets the directory path for saving results. + google(query: str, max_article:int): Performs Google search for the given query and returns top max_article results. # noqa E501 + save_search_result(query:str, articles: list): Saves the search result into a text file. # noqa E501 + get(query: str, max_article=1): Searches with cache. If the query already exists in the cache, return the cached result. # noqa E501 + """ + + def __init__(self, config_path: str, retry: int = 1, language:str='zh') -> None: + """Initializes the WebSearch object with the given config path and + retry count.""" + + self.search_config = None + with open(config_path, encoding='utf8') as f: + config = pytoml.load(f) + self.search_config = types.SimpleNamespace(**config['web_search']) + self.retry = retry + self.language = language + + def fetch_url(self, query: str, target_link: str, brief: str = ''): + if not target_link.startswith('http'): + return None + + logger.info(f'extract: {target_link}') + try: + content = '' + if target_link.lower().endswith( + '.pdf') or target_link.lower().endswith('.docx'): + # download file and parse + logger.info(f'download and parse: {target_link}') + response = requests.get(target_link, + stream=True, + allow_redirects=True) + + save_dir = self.search_config.save_dir + basename = os.path.basename(target_link) + save_path = os.path.join(save_dir, basename) + + with open(save_path, 'wb') as f: + for chunk in response.iter_content(chunk_size=8192): + f.write(chunk) + + file_opr = FileOperation() + content, error = file_opr.read(filepath=save_path) + if error is not None: + return error + return Article(content=content, + source=target_link, + brief=brief) + + response = requests.get(target_link, timeout=30) + + doc = Document(response.text) + content_html = doc.summary() + title = doc.short_title() + soup = BS(content_html, 'html.parser') + + if len(soup.text) < 4 * len(query): + return None + content = '{} {}'.format(title, soup.text) + content = content.replace('\n\n', '\n') + content = content.replace('\n\n', '\n') + content = content.replace(' ', ' ') + + if not check_str_useful(content=content): + return None + # logger.info('retry with chromium {}'.format(target_link)) + # nest_asyncio.apply() + # content = asyncio.get_event_loop().run_until_complete( + # fetch_chroumium_content(url=target_link)) + # if not check_str_useful(content=content): + # return None + + return Article(content=content, source=target_link, brief=brief) + except Exception as e: + logger.error('fetch_url {}'.format(str(e))) + return None + + def ddgs(self, query: str, max_article: int): + """Run DDGS search based on query.""" + results = DDGS().text(query, max_results=20) + filter_results = [] + + for domain in self.search_config.domain_partial_order: + for result in results: + if domain in result['href']: + filter_results.append(result) + break + + logger.debug('filter results: {}'.format(filter_results)) + articles = [] + for result in filter_results: + a = self.fetch_url(query=query, + target_link=result['href'], + brief=result['body']) + if a is not None and len(a) > 0: + articles.append(a) + if len(articles) > max_article: + break + return articles + + def google(self, query: str, max_article: int): + """Executes a google search based on the provided query. + + Parses the response and extracts the relevant URLs based on the + priority defined in the configuration file. Performs a GET request on + these URLs and extracts the title and content of the page. The content + is cleaned and added to the articles list. Returns a list of articles. + """ + url = 'https://google.serper.dev/search' + + if 'zh' in self.language: + lang = 'zh-cn' + else: + lang = 'en' + payload = json.dumps({'q': f'{query}', 'hl': lang}) + headers = { + 'X-API-KEY': self.search_config.serper_x_api_key, + 'Content-Type': 'application/json' + } + response = requests.request('POST', + url, + headers=headers, + data=payload, + timeout=5) # noqa E501 + jsonobj = json.loads(response.text) + logger.debug(jsonobj) + keys = self.search_config.domain_partial_order + urls = {} + normal_urls = [] + + for organic in jsonobj['organic']: + link = '' + logger.debug(organic) + + if 'link' in organic: + link = organic['link'] + else: + link = organic['sitelinks'][0]['link'] + + for key in keys: + if key in link: + if key not in urls: + urls[key] = [link] + else: + urls[key].append(link) + break + else: + normal_urls.append(link) + + logger.debug(f'gather urls: {urls}') + + links = [] + for key in keys: + if key in urls: + links += urls[key] + + target_links = links[0:max_article] + + logger.debug(f'target_links:{target_links}') + + articles = [] + for target_link in target_links: + # network with exponential backoff + a = self.fetch_url(query=query, target_link=target_link) + if a is not None: + articles.append(a) + + return articles + + def save_search_result(self, query: str, articles: list): + """Writes the search results (articles) for the provided query into a + text file. + + If the directory does not exist, it creates one. In case of an error, + logs a warning message. + """ + try: + save_dir = self.search_config.save_dir + if save_dir is None: + return + + if not os.path.exists(save_dir): + os.makedirs(save_dir) + + filepath = os.path.join(save_dir, query) + + text = '' + if len(articles) > 0: + texts = [str(a) for a in articles] + text = '\n\n'.join(texts) + with open(filepath, 'w', encoding='utf8') as f: + f.write(text) + except Exception as e: + logger.warning(f'error while saving search result {str(e)}') + + def logging_search_query(self, query: str): + """Logging search query to txt file.""" + + save_dir = self.search_config.save_dir + if save_dir is None: + return + + if not os.path.exists(save_dir): + os.makedirs(save_dir) + + filepath = os.path.join(save_dir, 'search_query.txt') + with open(filepath, 'a') as f: + f.write(query) + f.write('\n') + + def get(self, query: str, max_article=1): + """Executes a google search with cache. + + If the query already exists in the cache, returns the cached result. If + an exception occurs during the process, retries the request based on + the retry count. Sleeps for a random time interval between retries. + """ + query = query.strip() + query = query[0:32] + + try: + self.logging_search_query(query=query) + + articles = [] + engine = self.search_config.engine.lower() + if engine == 'ddgs': + articles = self.ddgs(query=query, max_article=max_article) + + elif engine == 'serper': + articles = self.google(query=query, max_article=max_article) + + self.save_search_result(query=query, articles=articles) + + return articles, None + except Exception as e: + logger.error(('web_search exception', query, str(e))) + return [], Exception('search fail, please check TOKEN') + return [], None + + +def parse_args(): + """Parses command-line arguments for web search.""" + parser = argparse.ArgumentParser(description='Web search.') + parser.add_argument('--keywords', + type=str, + help='Keywords for search and parse.') + parser.add_argument( + '--config_path', + default='config.ini', + help='Feature store configuration path. Default value is config.ini') + args = parser.parse_args() + return args + + +def fetch_web_content(target_link: str): + """Fetches and parses the content of the target URL. + + Extracts the main content and title from the HTML of the page. Returns the + title and content as a single string. + """ + response = requests.get(target_link, timeout=60) + + doc = Document(response.text) + content_html = doc.summary() + title = doc.short_title() + soup = BS(content_html, 'html.parser') + ret = '{} {}'.format(title, soup.text) + return ret + + +if __name__ == '__main__': + # https://developer.aliyun.com/article/679591 failed + # print(fetch_web_content('https://www.volcengine.com/theme/4222537-R-7-1')) + parser = parse_args() + s = WebSearch(config_path=parser.config_path) + print( + s.fetch_url( + query='', + target_link= + 'http://www.lswz.gov.cn/html/xhtml/ztcss/zt-jljstj/images/clgszpj.pdf' + )) + print( + s.fetch_url(query='', + target_link='https://zhuanlan.zhihu.com/p/699164101')) + print(s.get('LMDeploy 修改日志级别')) + print( + fetch_web_content( + 'https://mmdeploy.readthedocs.io/zh-cn/latest/get_started.html')) diff --git a/repodir/huixiangdou/huixiangdou/version.py b/repodir/huixiangdou/huixiangdou/version.py new file mode 100644 index 00000000..e6f6fd68 --- /dev/null +++ b/repodir/huixiangdou/huixiangdou/version.py @@ -0,0 +1,28 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Tuple + +__version__ = '20240415' +short_version = __version__ + + +def parse_version_info(version_str: str) -> Tuple: + """Parse version from a string. + + Args: + version_str (str): A string represents a version info. + + Returns: + tuple: A sequence of integer and string represents version. + """ + _version_info = [] + for x in version_str.split('.'): + if x.isdigit(): + _version_info.append(int(x)) + elif x.find('rc') != -1: + patch_version = x.split('rc') + _version_info.append(int(patch_version[0])) + _version_info.append(f'rc{patch_version[1]}') + return tuple(_version_info) + + +version_info = parse_version_info(__version__) diff --git a/repodir/huixiangdou/logs/work.txt b/repodir/huixiangdou/logs/work.txt new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/repodir/huixiangdou/logs/work.txt @@ -0,0 +1 @@ + diff --git a/repodir/huixiangdou/requirements.txt b/repodir/huixiangdou/requirements.txt new file mode 100644 index 00000000..8f4750c3 --- /dev/null +++ b/repodir/huixiangdou/requirements.txt @@ -0,0 +1,37 @@ +aiohttp +beautifulsoup4 +duckduckgo_search +einops +faiss-gpu +loguru +lxml_html_clean +networkx>=3.0 +numpy<2.0.0 +openai>=1.0.0 +openpyxl +pandas +pydantic>=1.10.13 +pymupdf +python-docx +pytoml +readability-lxml +redis +requests +scikit-learn +# See https://github.com/deanmalmgren/textract/issues/461 +textract @ git+https://github.com/tpoisonooo/textract@master +# textract +texttable +tiktoken +torch>=2.0.0 +transformers>=4.38 +transformers_stream_generator +unstructured +sentence_transformers +sse_starlette +fastapi +uvicorn +termcolor +opencv-python-headless +gradio>=4.41 +bcembedding diff --git a/repodir/huixiangdou/requirements/cpu.txt b/repodir/huixiangdou/requirements/cpu.txt new file mode 100644 index 00000000..49a5b6af --- /dev/null +++ b/repodir/huixiangdou/requirements/cpu.txt @@ -0,0 +1,35 @@ +--extra-index-url https://download.pytorch.org/whl/cpu +aiohttp +beautifulsoup4 +duckduckgo_search +einops +faiss-cpu +loguru +lxml_html_clean +nest_asyncio +networkx>=3.0 +numpy<2.0.0 +openai>=1.0.0 +openpyxl +pandas +pydantic>=1.10.13 +pymupdf +python-docx +pytoml +readability-lxml +redis +requests +scikit-learn +# See https://github.com/deanmalmgren/textract/issues/461 +textract @ git+https://github.com/tpoisonooo/textract@master +# textract +texttable +tiktoken +torch +unstructured +sse_starlette +fastapi +uvicorn +termcolor +opencv-python-headless +gradio \ No newline at end of file diff --git a/repodir/huixiangdou/requirements/docs.txt b/repodir/huixiangdou/requirements/docs.txt new file mode 100644 index 00000000..097680e0 --- /dev/null +++ b/repodir/huixiangdou/requirements/docs.txt @@ -0,0 +1,11 @@ +docutils==0.18.1 +modelindex +myst-parser +-e git+https://github.com/tpoisonooo/pytorch_sphinx_theme.git#egg=pytorch_sphinx_theme +sphinx==6.1.3 +sphinx-copybutton +sphinx-design +sphinx-notfound-page +sphinx-tabs +sphinxcontrib-jquery +tabulate \ No newline at end of file diff --git a/repodir/huixiangdou/requirements/lark-group.txt b/repodir/huixiangdou/requirements/lark-group.txt new file mode 100644 index 00000000..9e812bbe --- /dev/null +++ b/repodir/huixiangdou/requirements/lark-group.txt @@ -0,0 +1,4 @@ +flask +lark_oapi +pytoml +redis \ No newline at end of file diff --git a/repodir/huixiangdou/requirements/multimodal.txt b/repodir/huixiangdou/requirements/multimodal.txt new file mode 100644 index 00000000..0ac0554e --- /dev/null +++ b/repodir/huixiangdou/requirements/multimodal.txt @@ -0,0 +1,7 @@ +einops +ftfy +timm +torchvision +FlagEmbedding + +# donot install xformer and apex \ No newline at end of file diff --git a/repodir/huixiangdou/requirements/sft.txt b/repodir/huixiangdou/requirements/sft.txt new file mode 100644 index 00000000..1401c81b --- /dev/null +++ b/repodir/huixiangdou/requirements/sft.txt @@ -0,0 +1,2 @@ +accelerate>=0.26.1 +auto-gptq \ No newline at end of file diff --git a/repodir/huixiangdou/resource/2405.02817.pdf b/repodir/huixiangdou/resource/2405.02817.pdf new file mode 100644 index 0000000000000000000000000000000000000000..3f7a419e3aff6eef361cc366cf1dbde86ac540df GIT binary patch literal 213152 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 z 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%0A%Wsv+(;(@o}SkK024S^gPv)sYh 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 @s2X$Pd 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

?E$avd!{!{$Y@lz>s*W{ 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 z z=D9O>prF{4n8Th!;+{_%le zHM{sXQo53v$uztxfB4<|q7fAdH+rAw_u_jl5K)ZsHR@8Hs%3Ii$*8iD>La^7itobf zsiwYk(q@|_)4RXcSeL8IOEc}U_Wlv3I}?qo^URG9XzV33eGC!*A~+ay@yC6Qg?D3H zr^7=a(x-=n_w3m6rVhIJs*>_6-L;1Wn$6S5GoK5M#~RU&@GW2S6^?mV^5}sD({_su zir+U`swqfE(}84198}4fCwjyV91?4i?8D~@P=6)<$dKMSQ{(YS_ljvF2KK~$0_H7& zwYB+;3r7066)~2yD6oxFwGm7255g(Qg@K`rIJzi}1^#0-oQK8aRX_5v&FGLiO#BQA z7U|#2w~9_m?uI;*6r?>0;~gx+o|m+xm-2&PiolkRW7WX|f{MbChtQ8$!`o(t+&s-G z#uX8(1KTS@Wpfuic2P8XUR1U)n}552ovaZV#ez9d*r_&~nquO>jF7xe^hr;~@)s-Y z>^Gdg@zcklXv?{))_5P_IF1%HMF2o-IbF) z&TONSifVbRe-4@e%p`SnrdsV(#bL^%uP|+)e2@lQ?FrJXK}?3L5hZof{`lsnPcb z05V7bMeR4S;@nILXq!q$V$K_dGirUWa+Wu^Jf!<6S9Z-MjfbIVLrFv`v#X=uuQ!*R zMM*~+Jwy7Ar{jr7ITBR#qUF@0`EBil>fOP@W?==GIzmcLTVY33Y`l9-(=LRQH!2F4 z9eo*y*4iAhrO$b^V9>;I4!|gn9VfAP)~C3Utk}S`Eh($Kl}$>9)80k{0cE3+h2@*+ z=tx|q>`IV=b7mHiR}W0agYtGkUWqj4vGEY_w1+X+sPkXuk?fnIJ>lZx~TS%S|*;}=V3S!3R24b7JEgnET zLtGcSqSv|d%&RL~8|TREH49orug5EUd-?~OSu`~j@M-R6txVX+`P0H{1 zpzpt@yu^W1u4-kFd)5J=l7!Mxo}EYBF~kNbrNMvI;LV+kUaREtJPcZTn)dF$`@y%o zUvWrq+WE{`x*v8655{s;L|qFfy>cTTyu=Y*ii=)A=+KtYxmTAfu)0FlQMRwps~ldn z;tWT~J79{tbKLkfUmEC;`9Ud%DnjwTJ2}z!3k9m*)NkXwWm1d%gmGX{!Zs-VWCH~QuUXt<} zQzjHy7Qa_4^X{0vt04r@NRHWUFGun_@%mu6MCJ5sZ5b-TLH{_^zO4+$9~;}XoV0^< zbJHN|+qHvkmp4|Ppb~>w+=sKxwee+$Kvl0P>q$85u-TdPm+io;hfR5MJm9JRMZBYV z5f!xywYks$UN1=~7D1Vlga}n8PHv%5!S((vW%j8D{-O~+zP8TuKTZ`g2#=|`v?)%2esjl>wr5R@b#Rs z@rwG?Q0LI7M4w1kkV2igkPJW8Q5HUD6;8^>_f_K}}*i({EG+uw>O;zvxK+M@R?^43O1rJ^;Oa&$4AT=S*i)13 zjZZowV{(R(i<>=-YGvVmG}elx0kW3>@=T|9Bb z^PpoV7D|tr9*+V2TAwn*wTWcGAKuQ0M&4<3pjs}jV($>fFc}Wl1s(U;J!Ld*VRjkd zka+heCQ+gs#rW({@a?%c^Vs887C$K)MIz4 z)-1{Hsnr{%*bWJ}1w`o|C$kUW7Sr*M+l;U~?%?s$DA+A7wL$siFY-|;bKKU;f2n-5 z+CGf#OXtWZw)q;c+f`B*wcywwu{My{XoIG)){)MTFws5Pa>bTeQY)(6XJTujVJ@b6 z2AH)-RN3)&`O82=#^2`~)D#$opq_)Jv8U-^TDfieQC)&`W^%Vnz2@=IE3ox>v`arV zax1zaeJW_agDNCu`A0|!?iYQuBVzhQ|CiZV-Exetam2VXGxUo>tjrvvdaV@);=tR@VZY_J^Tqq>Joqdg)9|QG32-PTBcfi$Y`K_?BC) zFB6luaiU%%X`eKn$H;71kk%v{TMikE)=`+sy`@g2n^SK8JY>j{u)rX7fD4REO6Zo_ zW2{UO-=sQhy@YC%v7)Vd1X3lsxC?5VyK-kP ztW&O~2aFemyxvCW$<^HMGL`3PDKv0qWqx6fNc;lP&2PeKt~Dt~Yw~V6=N3i^=t4Bq z!CgF-cBWGA&tAuSEL+t;(0?{%EH?j`)%%c%gJ^TO0JgXLU_u;A_Q6yKF4=s;Lylmu zkM^Tl3XD=%-Yp;Rawl@%yH`tR@T|!8atsKB%zU6iWiO8-b^&fDS?Y(i#AJkj9dggv zw$|&5Z6Rn4-b<8wWxEi0Qpf2sq)Y|ak%qnjJg1GAZ(p18Fj!m{%}8j}Y)DuvknlW67@h>QJfxd5s#BV7`M*kq9Aqt5$&1vij^@U)n2ci3*+F z>9fo?yP?J4gx5;Pnk1|BW6TR{&2D&cPIb=<_xbV;!M9zyFP~pNMsUeKyKo6UHP?;~ zKQDKKL-v-Qd1@9ZYYmEP-xk{eJkDoJwg2eR&zEWqh9KG%8VrV_3Et@^wuNY8xNR4QF}(slO`?@h=KFoYaPTreIDEH7@A>nJk;+{QFMi(xJ;CW45*l zvd|hnCu%LN7`6e1z7n+_S2QZFimJu;y`{=TFE7Ecf*ZyyBS7*eWc>^GT)ns58`!)B zd^9e!p-|DirC)~QeMV8jaiFx9^_;kW(k}I0DEnxuWLH=+P)$oOEe3w^880PT>a^x6 zugKoS;SckUii!B7+Bm~g_bFZ8I1KNQt>AHCanPzTh+0{5!3r)J--DS~H7RIiEhdbJ zp2-)m>yRJcj~)(ZcH=qyaKG7l=c5c)W48VmqMC4uSA^J?T*ZE;5LmFyHOw?lj(o*A{OVA5%M8PXKQ?>oMaa1yR2$OgL2X$v+ zIPs*QeE`O}w(~WCcYps&qEe#n*m&2v?V=eazuSf}V|B$DXAZ^0{+}sL(!~;!T?J=< zqkyj@za4Z1DGN4*(S!(qI8jOw!tQ;AM@=< z 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�_hI?29u=sq-|A5dR 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 *Z51V%H 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 literal 0 HcmV?d00001 diff --git a/repodir/huixiangdou/resource/figures/inside-ncnn-group.jpg b/repodir/huixiangdou/resource/figures/inside-ncnn-group.jpg new file mode 100644 index 0000000000000000000000000000000000000000..6e46e82f7139dfdc401051b6db29527aa2b0e1bc GIT binary patch literal 32655 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 literal 0 HcmV?d00001 diff --git a/repodir/huixiangdou/resource/figures/lark-example.png b/repodir/huixiangdou/resource/figures/lark-example.png new file mode 100644 index 0000000000000000000000000000000000000000..8579fcb07bdb11383cd8f183a0cb2ce536754262 GIT binary patch literal 59152 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 S*RFU7z(-qMU#&vbCgMK~IH4&3 literal 0 HcmV?d00001 diff --git a/repodir/huixiangdou/resource/figures/wechat.jpg b/repodir/huixiangdou/resource/figures/wechat.jpg new file mode 100644 index 0000000000000000000000000000000000000000..d915fab40e2dc9a018bdab7e2500d3a312c67fe8 GIT binary patch literal 107842 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 GpZ_0XQ)!m~ literal 0 HcmV?d00001 diff --git a/repodir/huixiangdou/resource/good_questions.json b/repodir/huixiangdou/resource/good_questions.json new file mode 100644 index 00000000..fe57a632 --- /dev/null +++ b/repodir/huixiangdou/resource/good_questions.json @@ -0,0 +1,35 @@ +[ + "mmpose中怎么调用mmyolo接口", + "mmpose实现姿态估计后怎么实现行为识别", + "mmpose执行提取关键点命令不是分为两步吗,一步是目标检测,另一步是关键点提取,我现在目标检测这部分的代码是demo/topdown_demo_with_mmdet.py demo/mmdetection_cfg/faster_rcnn_r50_fpn_coco.py checkpoints/faster_rcnn_r50_fpn_1x_coco_20200130-047c8118.pth 现在我想把这个mmdet的checkpoints换位yolo的,那么应该怎么操作", + "在mmdetection中,如何同时加载两个数据集,两个dataloader", + "如何将mmdetection2.28.2的retinanet配置文件改为单尺度的呢?", + "1.MMPose_Tutorial.ipynb、inferencer_demo.py、image_demo.py、bottomup_demo.py、body3d_pose_lifter_demo.py这几个文件和topdown_demo_with_mmdet.py的区别是什么,\n2.我如果要使用mmdet是不是就只能使用topdown_demo_with_mmdet.py文件,", + "mmpose 测试 map 一直是 0 怎么办?", + "如何使用mmpose检测人体关键点?", + "我使用的数据集是labelme标注的,我想知道mmpose的数据集都是什么样式的,全都是单目标的数据集标注,还是里边也有多目标然后进行标注", + "如何生成openmmpose的c++推理脚本", + "mmpose", + "mmpose的目标检测阶段调用的模型,一定要是demo文件夹下的文件吗,有没有其他路径下的文件", + "mmpose可以实现行为识别吗,如果要实现的话应该怎么做", + "我在mmyolo的v0.6.0 (15/8/2023)更新日志里看到了他新增了支持基于 MMPose 的 YOLOX-Pose,我现在是不是只需要在mmpose/project/yolox-Pose内做出一些设置就可以,换掉demo/mmdetection_cfg/faster_rcnn_r50_fpn_coco.py 改用mmyolo来进行目标检测了", + "mac m1从源码安装的mmpose是x86_64的", + "想请教一下mmpose有没有提供可以读取外接摄像头,做3d姿态并达到实时的项目呀?", + "huixiangdou 是什么?", + "大佬们,如果我想在高空检测安全帽,我应该用 mmdetection 还是 mmrotate", + "mmdetection如何开启多卡训练", + "硬件模型库是什么", + "硬件模型库是啥?", + "OpenMMLab有哪些开源库", + "cbam注意力机制如何改进", + "轻量级的边分辨率模型有哪些?", + "如何添加CBAM机制", + "自定义数据集需要修改什么内容", + "对人进行关键点提取的时候,如果是多个人的场景下,就会出现连线到其他人身上去的情况,这个时候是不是目标检测模型这里的问题,也就是mmdet的识别效率有点低了,所以导致这种情况的出现", + "有人把mmdeploy成功部署到jetson agx orin上吗?", + "那这里的mmdet的配置文件demo/topdown_demo_with_mmdet.py就不需要换吗,他里边的配置不是训练mmdet的配置吗,我觉得是不是要换一个新的py配置文件,然后调用yolo", + "怎么训练llm", + "哪种目标检测算法适合小目标", + "OpenCompass 大模型数据集评估分数查询", + "把某专业标准类知识pdf格式,如何创建成向量数据库?" + ] diff --git a/repodir/huixiangdou/resource/logo_black.svg b/repodir/huixiangdou/resource/logo_black.svg new file mode 100644 index 00000000..5f4a83c5 --- /dev/null +++ b/repodir/huixiangdou/resource/logo_black.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/repodir/huixiangdou/resource/logo_blue.svg b/repodir/huixiangdou/resource/logo_blue.svg new file mode 100644 index 00000000..170f6c12 --- /dev/null +++ b/repodir/huixiangdou/resource/logo_blue.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/repodir/huixiangdou/resource/rag_example_input.json b/repodir/huixiangdou/resource/rag_example_input.json new file mode 100644 index 00000000..c66a68e8 --- /dev/null +++ b/repodir/huixiangdou/resource/rag_example_input.json @@ -0,0 +1,87 @@ +["这种,看着就大佬味道", +"[吃瓜]本垃圾有预算保养一位懂驱动 懂内核的大佬。", +"大佬困扰好几天了", +"有大佬用onnx 的fp 16模型转TNN嘛", +"「巅峰、少成: 大佬们。麻烦咨询下,mmdeploy推理的时候怎么限制内存呢?限制遇到连续几次就会出现Insufficient memory」\n—————————\nbackend 用的啥,按我理解,大多数是不能的", +"大佬们 有人知道yolo的txt格式数据的深度挖掘怎么做吗", +"想问下大佬们,rtmpose预测的关检点得分为什么会是大于1的呢,这个得分的范围是怎样的呢", +"打扰大佬,还有一个mmpose的问题,我使用单人的视频,输出的json文件,好多frame里面有两组 key_points, box_score 都是 1,但是第一组的keypoint_score 比第二组高。为啥会输出两组呢?", +"感谢各位大佬 上个问题解决了[Salute] 有一个新问题 我用“wholebody”测一个没有人出现的片段[捂脸],保存的json当中几乎所有的bbox_score得分都是1 ,这个1是什么意思呀?", +"悟了 感谢大佬", +"想请教群里各位大佬。为啥ViT的权重初始化这么复杂呀?分类头linear、mlp的linear、其他linear的初始化方式都不一样", +"好的,谢谢大佬们", +"各位大佬们 有谁知道用mmdeploy_runtime 跑cuda版本的onnx模型(mmdeploy转出的onnx模型)时报错:libonnxruntime_providers_shared.so: cannot open shared object file: No such file or directory 是怎么一回事吗", +"nuscenes数据集在火山云上,本地如何读取他的pkl文件,有大佬能指点一二吗?", +"让大佬开个奇妙夜", +"感谢大佬答疑解惑~", +"大佬们,下载了git,但是在anaconda的命令窗口安装mmpose还是显示显示‘git’不是内部或外部命令,也不是可运行的程序或批处理文件。这怎么弄?", +"我现在想混个研究生文凭 大佬们有没有什么建议?", +"你好,这是我测试的Yi-34B-base的tydiqa的值,跟leaderboard的值对应不上,麻烦大佬看一下", +"所以大佬有空的话实现一下呗[苦涩],我真的很难顶", +"大佬们新年好!topdown的方法中,我想一次读取9张图片进行预测,但是目前的设备不支持批处理。想着将读取的数据进行拼接后送入网络,经过backbone,head分成九路输出。但是对于数据读取(或者前处理)这块不知道该如何处理。有大佬能指教嘛?能否通过写一个data transform的类将前处理后的batch数据进行拼接?然后再送入网络呢", +"各位大佬,我想了解一下metain元文件中的joint_weights与sigmas参数的大小是怎么定义设置的?是不是joint_weights越大,模型越专注训练这个点,sigmas越大是不是表示在这个点越难学?", +"大佬又开始送东西", +"需要每一个大佬的PR和star", +"大佬们 新年快乐!", +"求我大佬们,立体匹配有类似mmlab和detectron这样的代码库吗,找了好像没找到", +"大佬们 问一个问题 我用mmdeploy的trt模型进行推理(检测模型),同一张图片,推多次结果总是有细微的差距,求大佬解答,这个正常吗? ", +"我这看了好多资料了[流泪],大佬快教", +"来自语音圈大佬的评价[嘿哈]", +"各位大佬们 mmseg用--resume接着训练卡住了咋办呀[捂脸]", +"有没有大佬安装mmcv遇到中这个情况,我安装csdn操作之后cuda版本好像变成9 了", +"大佬,h800能不能给我们访问下", +"[捂脸][捂脸]大佬好", +"佬,slam 大佬有兴趣给面试报价吗?", +"这个问题是在寻求某方面的帮助,并且表示对对方的敬意和友好态度。其中,“哪位大佬有经验”是询问是否有熟悉相关领域的人能够提供指导,“帮忙指导下”则是希望对方能够给予具体的帮助和建议。而“[抱拳][笑脸]”则是一种表情符号,用来表达礼貌和友好的情绪。", +"请教一下群里的各位大佬 MMDeploy Android的框架能否使用NPU 支持rk3588这种处理器上运行吗?", +"有文档吗?大佬", +"大佬们,我有个疑问。已知大模型和agent的智力暂时还是有限的,原因是没法做错误的推理判断和辨别;所以我们让大模型 agent做智力范围内的事情是很好的。那我们有没有一种判别法,可以让判断出大模型/某个agent在某件事情上的靠谱程度,来决定是否可以让它扮演那个角色? 有什么方法可以判断他是靠谱还是不靠谱的(非概率) 目前这个只能靠人类", +"各位大佬请教,要是使用mmpose训练自己的数据集需要标注的时候,有什么推荐的标注工具吗?", +"请问哪位大佬有deep image matting数据集,求分享[抱拳]", +"大佬师平时在家过年跑路也很让人羡慕", +"请问一下大佬,我在用mmdeploy导出rtmdet的onnx模型后,在检测的时候有三个目标,但输出了四个,并且最后一个目标全为0,这种情况怎么解决呢?", +"大佬,训练200epoch的rtmpose,感觉他们变化很小,变化很慢啊,120epoch了loss_kpt:才0.944左右,acc_pose也是一直在0.896左右,这训练正不正常阿?", +"大佬们,我不懂mmdeploy是怎么做的,是否可以认为支持的模型部署是大于支持的模型转换的呢,否则应该能转换就能部署哇?", +"有大佬用过mmaction2嘛", +"大佬们。麻烦咨询下,mmdeploy推理的时候怎么限制内存呢?限制遇到连续几次就会出现Insufficient memory", +"各位大佬请教一下,如果想买intel的design tool kit,该怎么搞intel的开发者账号", +"好滴谢谢大佬 可以直接说它等于 fps 吗 还是有什么计算,我看前面有个 10 runs", +"各位大佬用的什么", +"大佬,mmpose返回值tuple,但是我想在后面继续加操作要怎么搞", +"大佬一个hrnet训练出来,一个epoch占七百多内存,正常吗", +"换个群,已经拿了几个”感谢大佬”了。", +"大佬们,请问如何mm让茴香豆mm回答问题mm", +"那大佬可以把闲置的qqc租出去", +"呜呜呜 小白 求大佬说详细点 愿意学 愿意改", +"https://spaces.ac.cn/ 这个大佬写过一些,可以瞅瞅", +"各位大佬,我想咨询一个模型训练的问题。我有一组Ms coco格式的关键点数据集,里面标注了66个关键点,但我只想用其中的30个关键点来训练模型,并且不考虑点之间的连接关系。这种情况能实现吗,如何修改配置文件", +"可恶,群里这么多大佬发CVPR,我只能乖乖当个分母了", +"有大佬推荐一个比较好跑的backbone模型吗", +"请问大佬们有在工程部署中,有多个pytorch模型使用多线程来同时推理?", +"大佬们,这什么情况", +"大佬,训练rtmpose时batch大于100就会出现这个错误怎么回事啊?明明显卡内存还有足够的量使用", +"有大佬用过onnx的静态int8量化嘛", +"有没有大佬知道", +"大佬们mmdete3d可以生成PR曲线吗", +"大佬们可以帮忙看看这个数据配置文件吗", +"大佬们,我跑yolov8 关键点检测报这种错", +"看了大佬发的这个,感觉前途黯淡", +"大佬在ICCAD展咩", +"有大佬知道这个报错是什么原因么", +"大佬知道什么是 小黑子、鸡你太美 吗?", +"有大佬有百度和腾讯的lab内推吗,感谢", +"不过大佬单子盈利能力强 确实不在乎", +"有没有大佬能发一个正常deploy 的chenk_env输出看下呢。。", +"我很好奇,大佬做这个怎么写论文呢?是什么指标提升上去了,就可以写论文了吗", +"有大佬知道这是什么原因吗 mmseg", +"有大佬知道这个问题嘛,运行lmdeploy chat turbomind就会出现\nYou\n<|System|>:You are an AI assistant whose name is InternLM (书生·浦语). - InternLM (书生·浦语) is a conversational language model that is developed by Shanghai AI Laboratory (上海人工智能实验室). It is designed to be helpful, honest, and harmless. - InternLM (书生·浦语) can understand and communicate fluently in the language chosen by the user such as English and 中文. <|User|>:what the fucking doing! <|Bot|>: [AMP ERROR][CudaFrontend.cpp:94][1705464911:579595]failed to call cuCtxGetDevice(&device), error code: CUDA_ERROR_INVALID_CONTEXT =============================================== Back trace dump: /usr/local/harp/lib/libvirtdev-frontend.so.0(LogStream::PrintBacktrace()+0x52) [0x7fbcea3f2302] /lib/x86_64-linux-gnu/libcuda.so.1(CudaFeApiStateData::GetCurrentDevicePciBusId(Frontend*, int const*)+0x241) [0x7fbcea621471] /lib/x86_64-linux-gnu/libcuda.so.1(python: CudaFrontend.cpp:94: static const string& CudaFeApiStateData::GetCurrentDevicePciBusId(Frontend*, const CUdevice*): Assertion `0' failed. Aborted (core dumped) \n", +"嘿嘿嘿,听说 猫腻 被大佬拐进来了", +"请教大佬一个问题,mmaction2的demo_skeleton生成的视频为啥打不开", +"大佬,我看rtmo可以加aic这种数据集训练,但我印象中aic不是没标全部的人体框么,一阶段能加这种数据训?", +"有大佬熟悉ptx指令的吗?lmdeploy里面有个把共享内存拷贝到全局内存的ptx指令,怎么在不使用ptx指令的情况下,实现该功能。template\n__inline__ __device__ void cp_async_ca(uint32_t smem_int_ptr, const T* __restrict__ src, bool mask)\n{\n#if TURBOMIND_ARCH_SM80\n    constexpr int cp_size = sizeof(T);\n    // clang-format off\n    asm volatile(\"{\\n\"\n                 \"  .reg .pred p;\\n\"\n                 \"  setp.ne.b32 p, %0, 0;\\n\"\n                 \"  @p cp.async.ca.shared.global\" L2_CACHEHINT(128) \" [%1], [%2], %3;\\n\"\n                 \"}\\n\" ::\"r\"((int)mask),\n                 \"r\"(smem_int_ptr),\n                 \"l\"(src),\n                 \"n\"(cp_size));\n    // clang-format on\n#else\n    assert(TURBOMIND_ARCH_SM80);\n#endif\n}", +"求助大佬们,我尝试跑msagent数据集失败了[流泪]\nhttps://github.com/InternLM/xtuner/issues/205", +"那算了[捂脸]谢谢大佬", +"想请教下大佬,", +"Better models of human high-level visual cortex emerge from natural language supervision with a large and diverse dataset\n有大佬, 可以帮忙下载一下这篇nature论文吗[可怜][可怜][可怜][可怜]", +"大佬居然不知道?" +] diff --git a/repodir/huixiangdou/resource/rag_example_output.json b/repodir/huixiangdou/resource/rag_example_output.json new file mode 100644 index 00000000..83148e78 --- /dev/null +++ b/repodir/huixiangdou/resource/rag_example_output.json @@ -0,0 +1,72 @@ +{ + "id": 0, + "query": "这种,看着就大佬味道", + "direct_reply": "", + "rag_reply": "", + "code": 1, + "reason": "query is not a question", + "refs": [] +} +{ + "id": 1, + "query": "[吃瓜]本垃圾有预算保养一位懂驱动 懂内核的大佬。", + "direct_reply": "", + "rag_reply": "", + "code": 1, + "reason": "query is not a question", + "refs": [] +} +{ + "id": 2, + "query": "大佬困扰好几天了", + "direct_reply": "", + "rag_reply": "", + "code": 1, + "reason": "query is not a question", + "refs": [] +} +{ + "id": 3, + "query": "有大佬用onnx 的fp 16模型转TNN嘛", + "direct_reply": "ONNX(Open Neural Network Exchange)是一个开放格式,用于表示深度学习模型,允许AI开发人员在不同的框架和工具之间轻松转换模型。FP16指的是16位浮点数精度,这是深度学习中常用的一种数值精度格式,它有助于减少模型的大小和计算资源的需求。\n\nTNN(Tencent Neural Network)是腾讯开源的一个轻量级、高性能的深度学习推理引擎。TNN支持多种模型格式,包括ONNX。\n\n将ONNX的FP16模型转换为TNN支持的格式,通常需要以下步骤:\n\n1. **模型转换**:首先,你需要将ONNX模型转换为TNN支持的格式。TNN可能直接支持ONNX模型,但为了确保最佳性能和兼容性,你可能需要使用TNN提供的工具将ONNX模型转换为TNN的特定格式。\n\n2. **精度调整**:由于你提到的是FP16模型,你需要确保TNN支持FP16精度。如果TNN原生不支持FP16,你可能需要在转换过程中将模型的精度从FP16转换为TNN支持的精度格式,比如FP32。\n\n3. **优化**:转换后,你可能需要对模型进行优化以适应TNN的推理引擎。这可能包括内存优化、计算图优化等。\n\n4. **测试**:在转换和优化之后,你应该对模型进行测试,确保它在TNN上运行时保持原有的性能和准确性。\n\n请注意,具体的转换和优化步骤可能会根据TNN的版本和具体功能有所不同。建议查看TNN的官方文档和社区资源,以获取最新和最准确的信息。如果你在转换过程中遇到任何问题,你也可以在相关的开发者社区或论坛中寻求帮助。", + "rag_reply": "根据提供的材料,如果你想要将ONNX的FP16模型转换为TNN(腾讯神经网络)模型,你需要关注几个关键点:\n\n1. **模型转换工具**:材料中提到了`tools/deploy.py`这个工具,它被用来将模型转换为不同的后端格式。虽然在材料中没有直接提到TNN,但是你可以参考这个工具的使用方式来转换模型。\n\n2. **模型格式兼容性**:材料中列出了多个支持的后端,包括ONNX Runtime、TensorRT、ncnn、PPLNN、OpenVINO等,但没有提到TNN。因此,可能需要使用其他工具或方法来完成转换。\n\n3. **FP16支持**:材料中提到了FP16精度的模型转换,这意味着原始模型可能支持FP16格式。在转换到TNN时,你需要确保目标平台也支持FP16,以便能够利用这一精度优势。\n\n4. **社区和文档**:由于材料中没有直接提到TNN,你可能需要查阅TNN的官方文档或社区讨论,看看是否有人已经实现了类似的转换,或者是否有官方的指导和工具可以使用。\n\n5. **自定义转换**:如果现有的工具和方法不能满足你的需求,你可能需要自己实现一个转换流程。这可能包括将ONNX模型转换为TNN支持的中间格式,然后再转换到TNN的最终格式。\n\n总的来说,虽然材料中没有直接提供将ONNX FP16模型转换为TNN模型的指导,但你可以参考已有的模型转换工具和方法,同时查阅TNN相关的资源来完成这一任务。如果需要,也可以考虑自定义转换流程。", + "code": 0, + "reason": "success", + "refs": [ + "mmocr.md", + "deploy.md", + "mmrotate.md", + "mmpose.md", + "mmdet.md", + "model_convert.md", + "mmseg.md" + ] +} +{ + "id": 4, + "query": "「大佬们。麻烦咨询下,mmdeploy推理的时候怎么限制内存呢?限制遇到连续几次就会出现Insufficient memory」\n—————————\nbackend 用的啥,按我理解,大多数是不能的", + "direct_reply": "`mmdeploy` 是一个开源的深度学习模型部署工具,它支持多种深度学习框架和推理后端。在使用 `mmdeploy` 进行模型推理时,如果遇到内存不足的问题,通常有几种可能的解决方案:\n\n1. **模型优化**:对模型进行压缩和优化,比如使用量化、剪枝等技术减少模型大小和计算需求。\n\n2. **批处理大小调整**:减少每次推理的批量大小(batch size),这样可以降低单次推理所需的内存。\n\n3. **内存管理**:如果使用的后端支持,可以尝试更有效的内存管理策略,比如使用内存池、优化显存使用等。\n\n4. **硬件升级**:如果上述方法都无法解决问题,可能需要考虑升级硬件,增加内存容量。\n\n对于具体的后端,比如TensorRT、OpenVINO、ONNX Runtime等,它们各自有特定的方法来限制内存使用。例如:\n\n- **TensorRT**:可以通过设置最大工作空间大小(`TRT_LOGGER->setDeviceMemorySize()`)来限制TensorRT使用的内存。\n- **OpenVINO**:可以在创建推理请求时设置内存限制。\n- **ONNX Runtime**:可以通过环境变量或者会话选项来控制内存使用。\n\n如果你在使用 `mmdeploy` 时遇到连续几次出现 \"Insufficient memory\" 的问题,可能需要检查你的系统内存是否真的不足,或者你的 `mmdeploy` 配置是否合理。如果问题依然存在,建议查看 `mmdeploy` 的官方文档或者在相关社区寻求帮助,了解如何针对你所使用的后端进行内存限制的设置。同时,确保你的系统没有内存泄漏的问题,这也可能导致内存不足的错误。", + "rag_reply": "在使用 MMDeploy 进行模型推理时,如果遇到内存不足(Insufficient memory)的问题,可以通过调整配置来限制内存的使用。根据您提供的参考材料,这里有几种方法可以尝试:\n\n1. **调整 k/v 缓存比例**:\n 在 `TurbomindEngineConfig` 中,可以通过设置 `cache_max_entry_count` 参数来控制 GPU 内存中用于 k/v 缓存的比例。默认情况下,这个比例是 0.8,即 80% 的可用 GPU 内存被分配给 k/v 缓存。如果遇到内存不足的问题,可以尝试降低这个比例。例如,将其设置为 0.2 表示只使用 20% 的 GPU 内存用于 k/v 缓存。\n\n ```python\n from lmdeploy import pipeline, TurbomindEngineConfig\n backend_config = TurbomindEngineConfig(cache_max_entry_count=0.2)\n pipe = pipeline('internlm/internlm2-chat-7b', backend_config=backend_config)\n response = pipe(['Hi, pls intro yourself', 'Shanghai is'])\n print(response)\n ```\n\n2. **设置张量并行数量**:\n 如果您使用的是支持张量并行的后端(如 PyTorch),可以通过设置 `tp`(tensor parallel)参数来减少单个 GPU 上的内存使用。这通常在处理大型模型时很有用。\n\n ```python\n from lmdeploy import pipeline, TurbomindEngineConfig\n backend_config = TurbomindEngineConfig(tp=2)\n pipe = pipeline('internlm/internlm2-chat-7b', backend_config=backend_config)\n response = pipe(['Hi, pls intro yourself', 'Shanghai is'])\n print(response)\n ```\n\n3. **使用 Triton 后端**:\n 如果您使用的是 PyTorch 后端,可以考虑使用 Triton 来管理模型推理。Triton 是一个开源的深度学习推理引擎,它支持模型并行和张量并行,可以帮助更有效地利用 GPU 资源。\n\n ```shell\n pip install triton>=2.1.0\n ```\n\n ```python\n from lmdeploy import pipeline, GenerationConfig, PytorchEngineConfig\n backend_config = PytorchEngineConfig(session_len=2048)\n gen_config = GenerationConfig(top_p=0.8, top_k=40, temperature=0.8, max_new_tokens=1024)\n pipe = pipeline('internlm/internlm-chat-7b', backend_config=backend_config)\n prompts = [[{'role': 'user', 'content': 'Hi, pls intro yourself'}], [{'role': 'user', 'content': 'Shanghai is'}]]\n response = pipe(prompts, gen_config=gen_config)\n print(response)\n ```\n\n请注意,这些方法可能需要根据您的具体模型和硬件环境进行调整。如果问题仍然存在,建议查看 MMDeploy 的官方文档或社区寻求更多帮助。", + "code": 0, + "reason": "success", + "refs": [ + "README.md", + "CMakeLists.txt", + "pipeline.md" + ] +} +{ + "id": 5, + "query": "大佬们 有人知道yolo的txt格式数据的深度挖掘怎么做吗", + "direct_reply": "YOLO(You Only Look Once)是一种流行的实时对象检测系统,它在处理图像时非常高效。如果你想要对YOLO生成的TXT格式数据进行深度挖掘,你可能需要进行以下几个步骤:\n\n1. **数据理解**:首先,你需要理解YOLO输出的TXT文件中数据的格式。YOLO通常会输出每个检测到的对象的类别、置信度(confidence score)和边界框(bounding box)的坐标(通常是归一化的,即相对于图像宽度和高度的比例)。\n\n2. **数据清洗**:确保数据质量,去除无效或错误的记录。这可能包括检查置信度分数是否合理,边界框坐标是否在合法范围内(即0到1之间)。\n\n3. **数据分析**:对数据进行统计分析,比如对象的频率分布、置信度的平均值和分布、不同类别对象的数量等。\n\n4. **数据可视化**:使用图表和图像来直观展示数据分析的结果。例如,可以创建散点图来显示不同对象的边界框,或者使用柱状图来展示不同类别对象的数量。\n\n5. **模式识别**:通过分析数据,尝试找出潜在的模式或趋势。例如,某些类别的对象是否在特定条件下出现得更频繁。\n\n6. **机器学习**:如果需要,可以使用这些数据来训练更复杂的机器学习模型,以进行进一步的预测或分类。\n\n7. **报告撰写**:将你的发现整理成报告,包括你的方法、分析结果和结论。\n\n在进行深度挖掘时,你可能需要使用编程语言(如Python)和数据分析工具(如Pandas、NumPy、Matplotlib等)来处理和分析数据。如果你不熟悉这些工具,可能需要学习相关的技能或寻求专业人士的帮助。", + "rag_reply": "从提供的材料中,我们可以看到有关YOLO-Pose和YOLOX的信息,这是两种基于YOLO(You Only Look Once)框架的目标检测和人体姿态估计算法。YOLO-Pose是一种用于多人姿态估计的方法,它通过对象关键点相似性损失(Object Keypoint Similarity, OKS)来增强YOLO,以便在单个人体检测中同时估计2D多人姿态。YOLOX则是YOLO系列的一个变种,它在2021年超越了YOLO系列的其他版本。\n\n关于YOLO的txt格式数据的深度挖掘,虽然材料中没有直接提及具体的方法,但我们可以从YOLO和YOLOX的描述中推断出一些可能的步骤:\n\n1. **数据理解**:首先,需要理解YOLO格式的数据。YOLO将图像分割成一个个的格子(grid),每个格子负责预测中心点落在该格子内的物体。每个物体的预测信息包括边界框(bounding box)、类别概率和对象置信度。\n\n2. **数据预处理**:将原始数据转换为YOLO所需的格式,通常包括标注文件(.txt)中的边界框坐标、类别索引等信息。这些数据通常需要转换为YOLO模型可以接受的格式,例如,边界框坐标可能需要归一化。\n\n3. **模型训练**:使用YOLO或YOLOX等算法进行模型训练。这通常涉及到设置合适的超参数,如学习率、批量大小、训练周期等,并使用GPU加速训练过程。\n\n4. **性能评估**:在训练过程中和训练完成后,使用诸如平均精度(Average Precision, AP)等指标来评估模型的性能。这些指标可以从模型输出的预测结果中计算得出。\n\n5. **模型优化**:根据性能评估的结果,对模型进行调整和优化。这可能包括调整网络结构、改变损失函数、使用数据增强技术等。\n\n6. **应用部署**:将训练好的模型部署到实际应用中,如监控系统、自动驾驶车辆等。\n\n如果你需要对YOLO的txt格式数据进行深度挖掘,你可能需要关注数据的标注质量、数据集的多样性和规模,以及模型的泛化能力。此外,你还可以尝试使用不同的后处理技术来改善模型的预测结果,或者探索如何将YOLO与其他类型的数据(如视频数据)结合使用。", + "code": 0, + "reason": "success", + "refs": [ + "yoloxpose_coco.md", + "yolopose.md", + "overview.md", + "model_zoo.md", + "dataset_zoo.md" + ] +} diff --git a/repodir/huixiangdou/setup.py b/repodir/huixiangdou/setup.py new file mode 100644 index 00000000..97f8ec9c --- /dev/null +++ b/repodir/huixiangdou/setup.py @@ -0,0 +1,70 @@ +import os +import re +import sys + +from setuptools import find_packages, setup + +pwd = os.path.dirname(__file__) +version_file = 'huixiangdou/version.py' + + +def readme(): + with open(os.path.join(pwd, 'README.md'), encoding='utf-8') as f: + content = f.read() + return content + + +def get_version(): + with open(os.path.join(pwd, version_file), 'r') as f: + exec(compile(f.read(), version_file, 'exec')) + return locals()['__version__'] + + +def read_requirements(): + lines = [] + with open('requirements.txt', 'r') as f: + for line in f.readlines(): + if line.startswith('#'): + continue + if 'textract' in line: + continue + if len(line) > 0: + lines.append(line) + return lines + + +install_packages = read_requirements() + +if __name__ == '__main__': + huixiangdou_package_data = [ + 'main.py', 'resource/bad_questions.json', + 'resource/good_questions.json', 'config.ini' + ] + setup( + name='huixiangdou', + version=get_version(), + url='https://github.com/InternLM/huixiangdou', + description= # noqa E251 + 'Overcoming Group Chat Scenarios with LLM-based Technical Assistance', # noqa E501 + long_description=readme(), + long_description_content_type='text/markdown', + author='OpenMMLab', + author_email='openmmlab@gmail.com', + packages=find_packages(exclude=()), + package_data={ + 'huixiangdou': huixiangdou_package_data, + }, + include_package_data=True, + setup_requires=install_packages, + install_requires=install_packages, + classifiers=[ + 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', + 'Programming Language :: Python :: 3.11', + 'Intended Audience :: Developers', + 'Intended Audience :: Education', + 'Intended Audience :: Science/Research', + ], + entry_points={'console_scripts': ['huixiangdou=huixiangdou.main:run']}, + ) diff --git a/repodir/huixiangdou/sft/README.md b/repodir/huixiangdou/sft/README.md new file mode 100644 index 00000000..e9e40ce7 --- /dev/null +++ b/repodir/huixiangdou/sft/README.md @@ -0,0 +1,75 @@ +# SFT data scripts and training configs + +> \[!NOTE\] +> +> + +Here is directory description: + +| script | desc | +| :----------------------------: | :------------------------------------------------------------: | +| reconstruct_wechat_group.py | reconstruct wechat group messages | +| reconstruct_filter_annotate.py | filter data with puyu+kimi, manually annotate, manually review | +| reconstruct_check_llm.py | recheck 14B & 32B results | +| convert_to_alpaca.py | convert raw group data to alpaca format | + +# Reproduce HuixiangDou-CR + +## 1. Prepare Data + +- Get all WeChat group chats, use `python3 reconstruct_wechat_group.py` to split it and filter with LLM + +- `python3 reconstruct_filter_annotate.py` to filter, annotate and manually check + + Now you can get [gt.jsonl](https://huggingface.co/datasets/tpoisonooo/HuixiangDou-CR/blob/main/gt.jsonl) + +- Convert `gt.jsonl` to alpaca format, use `convert_to_alpaca.py` + + Finally we have [alpaca.json](https://huggingface.co/datasets/tpoisonooo/HuixiangDou-CR/blob/main/alpaca.json) for SFT + +## 2. Train + +Install [axolotl](https://github.com/OpenAccess-AI-Collective/axolotl), update your model path and data path in [axolotl_configs](./axolotl_configs/). + +Let's take [qwen2-lora-0.5B.yaml](./axolotl_configs/qwen2-lora-0.5B.yaml) as example. + +```bash +# config paths +base_model: /workspace/models/Qwen1.5-0.5B-Chat +.. +datasets: + - path: /workspace/axolotl/alpaca.json + type: alpaca + .. +output_dir: ./out-qwen0.5 +``` + +Train the model + +```bash +accelerate launch -m axolotl.cli.train examples/qwen/qwen2-lora-0.5B.yaml +``` + +Fine-tuned LoRA weights can be found in [huggingface](https://huggingface.co/tpoisonooo). + +Merge LoRA weights + +```bash +python3 -m axolotl.cli.merge_lora examples/qwen/qwen2-lora-0.5B.yaml +``` + +## 3. Validate + +Serving merged Qwen model as `openai` API with [vLLM](https://github.com/vllm-project/vllm) + +```bash +python -m vllm.entrypoints.openai.api_server --served-model-name LoRA-Qwen1.5-0.5B-Chat --model /workspace/axolotl/out-qwen0.5/merged/ --port 29999 --max-model-len 8192 +``` + +Evaluate the precision and F1 score, **use your own IP and port** in the python code. + +```bash +python3 reconstruct_filter_annotate.py --action metric --llm-type Qwen1.5-0.5B-Chat +``` diff --git a/repodir/huixiangdou/sft/axolotl_configs/lora-4B.yml b/repodir/huixiangdou/sft/axolotl_configs/lora-4B.yml new file mode 100644 index 00000000..fed0c97a --- /dev/null +++ b/repodir/huixiangdou/sft/axolotl_configs/lora-4B.yml @@ -0,0 +1,68 @@ +base_model: /workspace/models/Qwen1.5-4B-Chat +model_type: AutoModelForCausalLM +tokenizer_type: AutoTokenizer + +trust_remote_code: true + +load_in_8bit: true +load_in_4bit: false +strict: false + +datasets: + - path: /workspace/axolotl/alpaca.json + type: alpaca +dataset_prepared_path: +val_set_size: 0.05 +output_dir: ./lora-out + +sequence_len: 2048 # supports up to 8192 +sample_packing: false +pad_to_sequence_len: + +adapter: lora +lora_model_dir: +lora_r: 32 +lora_alpha: 16 +lora_dropout: 0.05 +lora_target_linear: true +lora_fan_in_fan_out: + +wandb_mode: online +wandb_project: huixiangdou-cr +wandb_entity: +wandb_watch: +wandb_name: qwen-4 +wandb_log_model: + +gradient_accumulation_steps: 4 +micro_batch_size: 2 +num_epochs: 4 +optimizer: adamw_bnb_8bit +lr_scheduler: cosine +learning_rate: 0.0002 + +train_on_inputs: false +group_by_length: false +bf16: auto +fp16: +tf32: false + +gradient_checkpointing: false +early_stopping_patience: +resume_from_checkpoint: +local_rank: +logging_steps: 1 +xformers_attention: +flash_attention: + +warmup_steps: 10 +evals_per_epoch: 4 +eval_table_size: +eval_max_new_tokens: 128 +saves_per_epoch: 1 +debug: +deepspeed: +weight_decay: 0.0 +fsdp: +fsdp_config: +special_tokens: diff --git a/repodir/huixiangdou/sft/axolotl_configs/qwen2-lora-0.5B.yaml b/repodir/huixiangdou/sft/axolotl_configs/qwen2-lora-0.5B.yaml new file mode 100644 index 00000000..875ee39e --- /dev/null +++ b/repodir/huixiangdou/sft/axolotl_configs/qwen2-lora-0.5B.yaml @@ -0,0 +1,61 @@ +base_model: /workspace/models/Qwen1.5-0.5B-Chat +trust_remote_code: true + +load_in_8bit: false +load_in_4bit: false +strict: false + +datasets: + - path: /workspace/axolotl/alpaca.json + type: alpaca +dataset_prepared_path: +val_set_size: 0.05 +output_dir: ./out-qwen0.5 + +sequence_len: 1400 # supports up to 32k +sample_packing: false +pad_to_sequence_len: false + +adapter: lora +lora_model_dir: +lora_r: 64 +lora_alpha: 16 +lora_dropout: 0.05 +lora_target_linear: true +lora_fan_in_fan_out: + +wandb_mode: online +wandb_project: huixiangdou-cr +wandb_entity: +wandb_watch: +wandb_name: qwen0.5 +wandb_log_model: + +gradient_accumulation_steps: 1 +micro_batch_size: 16 +num_epochs: 1 +optimizer: paged_adamw_8bit +lr_scheduler: cosine +learning_rate: 0.0002 + +train_on_inputs: false +group_by_length: false +gradient_checkpointing: true +gradient_checkpointing_kwargs: + use_reentrant: false +early_stopping_patience: +resume_from_checkpoint: +local_rank: +logging_steps: 1 +xformers_attention: +flash_attention: true + +warmup_steps: 10 +evals_per_epoch: 1 +saves_per_epoch: 1 +debug: +deepspeed: +weight_decay: 0.0 +fsdp: +fsdp_config: +special_tokens: diff --git a/repodir/huixiangdou/sft/axolotl_configs/qwen2-lora-1.8B.yaml b/repodir/huixiangdou/sft/axolotl_configs/qwen2-lora-1.8B.yaml new file mode 100644 index 00000000..14ba4c5e --- /dev/null +++ b/repodir/huixiangdou/sft/axolotl_configs/qwen2-lora-1.8B.yaml @@ -0,0 +1,61 @@ +base_model: /workspace/models/Qwen1.5-1.8B-Chat +trust_remote_code: true + +load_in_8bit: false +load_in_4bit: false +strict: false + +datasets: + - path: /workspace/axolotl/alpaca.json + type: alpaca +dataset_prepared_path: +val_set_size: 0.05 +output_dir: ./out-qwen1.8 + +sequence_len: 1400 # supports up to 32k +sample_packing: false +pad_to_sequence_len: false + +adapter: lora +lora_model_dir: +lora_r: 64 +lora_alpha: 16 +lora_dropout: 0.05 +lora_target_linear: true +lora_fan_in_fan_out: + +wandb_mode: online +wandb_project: huixiangdou-cr +wandb_entity: +wandb_watch: +wandb_name: qwen1.8 +wandb_log_model: + +gradient_accumulation_steps: 1 +micro_batch_size: 16 +num_epochs: 1 +optimizer: paged_adamw_8bit +lr_scheduler: cosine +learning_rate: 0.0002 + +train_on_inputs: false +group_by_length: false +gradient_checkpointing: true +gradient_checkpointing_kwargs: + use_reentrant: false +early_stopping_patience: +resume_from_checkpoint: +local_rank: +logging_steps: 1 +xformers_attention: +flash_attention: true + +warmup_steps: 10 +evals_per_epoch: 1 +saves_per_epoch: 1 +debug: +deepspeed: +weight_decay: 0.0 +fsdp: +fsdp_config: +special_tokens: diff --git a/repodir/huixiangdou/sft/axolotl_configs/qwen2-lora-14B.yaml b/repodir/huixiangdou/sft/axolotl_configs/qwen2-lora-14B.yaml new file mode 100644 index 00000000..5cf1d6bd --- /dev/null +++ b/repodir/huixiangdou/sft/axolotl_configs/qwen2-lora-14B.yaml @@ -0,0 +1,62 @@ +base_model: /workspace/models/Qwen1.5-14B-Chat +trust_remote_code: true + +load_in_8bit: false +load_in_4bit: false +strict: false + +datasets: + - path: /workspace/axolotl/alpaca.json + type: alpaca +dataset_prepared_path: +val_set_size: 0.05 +output_dir: ./out-qwen14 + + +sequence_len: 1400 # supports up to 32k +sample_packing: false +pad_to_sequence_len: false + +adapter: lora +lora_model_dir: +lora_r: 64 +lora_alpha: 16 +lora_dropout: 0.05 +lora_target_linear: true +lora_fan_in_fan_out: + +wandb_mode: online +wandb_project: huixiangdou-cr +wandb_entity: +wandb_watch: +wandb_name: qwen14 +wandb_log_model: + +gradient_accumulation_steps: 1 +micro_batch_size: 8 +num_epochs: 1 +optimizer: paged_adamw_8bit +lr_scheduler: cosine +learning_rate: 0.0002 + +train_on_inputs: false +group_by_length: false +gradient_checkpointing: true +gradient_checkpointing_kwargs: + use_reentrant: false +early_stopping_patience: +resume_from_checkpoint: +local_rank: +logging_steps: 1 +xformers_attention: +flash_attention: true + +warmup_steps: 10 +evals_per_epoch: 1 +saves_per_epoch: 1 +debug: +deepspeed: +weight_decay: 0.0 +fsdp: +fsdp_config: +special_tokens: diff --git a/repodir/huixiangdou/sft/axolotl_configs/qwen2-lora-32B.yaml b/repodir/huixiangdou/sft/axolotl_configs/qwen2-lora-32B.yaml new file mode 100644 index 00000000..9fa5d825 --- /dev/null +++ b/repodir/huixiangdou/sft/axolotl_configs/qwen2-lora-32B.yaml @@ -0,0 +1,62 @@ +base_model: /workspace/models/Qwen1.5-32B-Chat +trust_remote_code: true + +load_in_8bit: false +load_in_4bit: false +strict: false + +datasets: + - path: /workspace/axolotl/alpaca.json + type: alpaca +dataset_prepared_path: +val_set_size: 0.05 +output_dir: ./out-qwen32 + + +sequence_len: 1400 # supports up to 32k +sample_packing: false +pad_to_sequence_len: false + +adapter: lora +lora_model_dir: +lora_r: 64 +lora_alpha: 16 +lora_dropout: 0.05 +lora_target_linear: true +lora_fan_in_fan_out: + +wandb_mode: online +wandb_project: huixiangdou-cr +wandb_entity: +wandb_watch: +wandb_name: qwen32 +wandb_log_model: + +gradient_accumulation_steps: 1 +micro_batch_size: 4 +num_epochs: 1 +optimizer: paged_adamw_8bit +lr_scheduler: cosine +learning_rate: 0.0002 + +train_on_inputs: false +group_by_length: false +gradient_checkpointing: true +gradient_checkpointing_kwargs: + use_reentrant: false +early_stopping_patience: +resume_from_checkpoint: +local_rank: +logging_steps: 1 +xformers_attention: +flash_attention: true + +warmup_steps: 10 +evals_per_epoch: 1 +saves_per_epoch: 1 +debug: +deepspeed: +weight_decay: 0.0 +fsdp: +fsdp_config: +special_tokens: diff --git a/repodir/huixiangdou/sft/axolotl_configs/qwen2-lora-4B-loraplus-epoch4.yaml b/repodir/huixiangdou/sft/axolotl_configs/qwen2-lora-4B-loraplus-epoch4.yaml new file mode 100644 index 00000000..93cb3226 --- /dev/null +++ b/repodir/huixiangdou/sft/axolotl_configs/qwen2-lora-4B-loraplus-epoch4.yaml @@ -0,0 +1,62 @@ +base_model: /workspace/models/Qwen1.5-4B-Chat +trust_remote_code: true + +load_in_8bit: false +load_in_4bit: false +strict: false + +datasets: + - path: /workspace/axolotl/alpaca.json + type: alpaca +dataset_prepared_path: +val_set_size: 0.05 +output_dir: ./out-qwen4-loraplus-ep4 + +sequence_len: 1400 # supports up to 32k +sample_packing: false +pad_to_sequence_len: false + +adapter: lora +lora_model_dir: +lora_r: 64 +lora_alpha: 16 +lora_dropout: 0.05 +lora_target_linear: true +lora_fan_in_fan_out: +loraplus_lr_ratio: 16 + +wandb_mode: online +wandb_project: huixiangdou-cr +wandb_entity: +wandb_watch: +wandb_name: qwen-4 +wandb_log_model: + +gradient_accumulation_steps: 1 +micro_batch_size: 16 +num_epochs: 4 +optimizer: paged_adamw_8bit +lr_scheduler: cosine +learning_rate: 0.00005 + +train_on_inputs: false +group_by_length: false +gradient_checkpointing: true +gradient_checkpointing_kwargs: + use_reentrant: false +early_stopping_patience: +resume_from_checkpoint: +local_rank: +logging_steps: 1 +xformers_attention: +flash_attention: true + +warmup_steps: 10 +evals_per_epoch: 1 +saves_per_epoch: 4 +debug: +deepspeed: +weight_decay: 0.0 +fsdp: +fsdp_config: +special_tokens: diff --git a/repodir/huixiangdou/sft/axolotl_configs/qwen2-lora-4B.yaml b/repodir/huixiangdou/sft/axolotl_configs/qwen2-lora-4B.yaml new file mode 100644 index 00000000..2d6fcfa3 --- /dev/null +++ b/repodir/huixiangdou/sft/axolotl_configs/qwen2-lora-4B.yaml @@ -0,0 +1,61 @@ +base_model: /workspace/models/Qwen1.5-4B-Chat +trust_remote_code: true + +load_in_8bit: false +load_in_4bit: false +strict: false + +datasets: + - path: /workspace/axolotl/alpaca.json + type: alpaca +dataset_prepared_path: +val_set_size: 0.05 +output_dir: ./out-qwen4 + +sequence_len: 1400 # supports up to 32k +sample_packing: false +pad_to_sequence_len: false + +adapter: lora +lora_model_dir: +lora_r: 64 +lora_alpha: 16 +lora_dropout: 0.05 +lora_target_linear: true +lora_fan_in_fan_out: + +wandb_mode: online +wandb_project: huixiangdou-cr +wandb_entity: +wandb_watch: +wandb_name: qwen-4 +wandb_log_model: + +gradient_accumulation_steps: 1 +micro_batch_size: 32 +num_epochs: 1 +optimizer: paged_adamw_8bit +lr_scheduler: cosine +learning_rate: 0.0002 + +train_on_inputs: false +group_by_length: false +gradient_checkpointing: true +gradient_checkpointing_kwargs: + use_reentrant: false +early_stopping_patience: +resume_from_checkpoint: +local_rank: +logging_steps: 1 +xformers_attention: +flash_attention: true + +warmup_steps: 10 +evals_per_epoch: 1 +saves_per_epoch: 1 +debug: +deepspeed: +weight_decay: 0.0 +fsdp: +fsdp_config: +special_tokens: diff --git a/repodir/huixiangdou/sft/axolotl_configs/qwen2-lora-7B.yaml b/repodir/huixiangdou/sft/axolotl_configs/qwen2-lora-7B.yaml new file mode 100644 index 00000000..ee6454c5 --- /dev/null +++ b/repodir/huixiangdou/sft/axolotl_configs/qwen2-lora-7B.yaml @@ -0,0 +1,62 @@ +base_model: /workspace/models/Qwen1.5-7B-Chat +trust_remote_code: true + +load_in_8bit: false +load_in_4bit: false +strict: false + +datasets: + - path: /workspace/axolotl/alpaca.json + type: alpaca +dataset_prepared_path: +val_set_size: 0.05 +output_dir: ./out-qwen7 + + +sequence_len: 1400 # supports up to 32k +sample_packing: false +pad_to_sequence_len: false + +adapter: lora +lora_model_dir: +lora_r: 16 +lora_alpha: 16 +lora_dropout: 0.05 +lora_target_linear: true +lora_fan_in_fan_out: + +wandb_mode: online +wandb_project: huixiangdou-cr +wandb_entity: +wandb_watch: +wandb_name: qwen7 +wandb_log_model: + +gradient_accumulation_steps: 1 +micro_batch_size: 16 +num_epochs: 1 +optimizer: paged_adamw_8bit +lr_scheduler: cosine +learning_rate: 0.0002 + +train_on_inputs: false +group_by_length: false +gradient_checkpointing: true +gradient_checkpointing_kwargs: + use_reentrant: false +early_stopping_patience: +resume_from_checkpoint: +local_rank: +logging_steps: 1 +xformers_attention: +flash_attention: true + +warmup_steps: 10 +evals_per_epoch: 1 +saves_per_epoch: 1 +debug: +deepspeed: +weight_decay: 0.0 +fsdp: +fsdp_config: +special_tokens: diff --git a/repodir/huixiangdou/sft/axolotl_configs/qwen2-moe-lora-2.7B.yaml b/repodir/huixiangdou/sft/axolotl_configs/qwen2-moe-lora-2.7B.yaml new file mode 100644 index 00000000..e6814d8c --- /dev/null +++ b/repodir/huixiangdou/sft/axolotl_configs/qwen2-moe-lora-2.7B.yaml @@ -0,0 +1,62 @@ +base_model: /workspace/models/qwen1.5-moe-2.7B-chat +trust_remote_code: true + +load_in_8bit: false +load_in_4bit: false +strict: false + +datasets: + - path: /workspace/axolotl/alpaca.json + type: alpaca +dataset_prepared_path: +val_set_size: 0.05 +output_dir: ./out-moe + +sequence_len: 1400 # supports up to 32k +sample_packing: false +pad_to_sequence_len: false + +adapter: lora +lora_model_dir: +lora_r: 64 +lora_alpha: 16 +lora_dropout: 0.05 +lora_target_linear: true +lora_fan_in_fan_out: + +# smooth-clould-2 +wandb_mode: online +wandb_project: huixiangdou-cr +wandb_entity: +wandb_watch: +wandb_name: qwen-moe +wandb_log_model: + +gradient_accumulation_steps: 1 +micro_batch_size: 16 +num_epochs: 1 +optimizer: paged_adamw_8bit +lr_scheduler: cosine +learning_rate: 0.0002 + +train_on_inputs: false +group_by_length: false +gradient_checkpointing: true +gradient_checkpointing_kwargs: + use_reentrant: false +early_stopping_patience: +resume_from_checkpoint: +local_rank: +logging_steps: 1 +xformers_attention: +flash_attention: true + +warmup_steps: 10 +evals_per_epoch: 1 +saves_per_epoch: 1 +debug: +deepspeed: +weight_decay: 0.0 +fsdp: +fsdp_config: +special_tokens: diff --git a/repodir/huixiangdou/sft/axolotl_configs/qwen2-moe-lora.yaml b/repodir/huixiangdou/sft/axolotl_configs/qwen2-moe-lora.yaml new file mode 100644 index 00000000..dea79526 --- /dev/null +++ b/repodir/huixiangdou/sft/axolotl_configs/qwen2-moe-lora.yaml @@ -0,0 +1,64 @@ +base_model: /workspace/models/qwen1.5-moe-2.7B-chat +trust_remote_code: true + +load_in_8bit: false +load_in_4bit: false +strict: false + +datasets: + - path: mhenrichsen/alpaca_2k_test + type: alpaca +dataset_prepared_path: +val_set_size: 0.05 +output_dir: ./out + +sequence_len: 1024 # supports up to 32k +sample_packing: false +pad_to_sequence_len: false + +adapter: lora +lora_model_dir: +lora_r: 32 +lora_alpha: 16 +lora_dropout: 0.05 +lora_target_linear: true +lora_fan_in_fan_out: + +wandb_project: +wandb_entity: +wandb_watch: +wandb_name: +wandb_log_model: + +gradient_accumulation_steps: 4 +micro_batch_size: 1 +num_epochs: 4 +optimizer: paged_adamw_8bit +lr_scheduler: cosine +learning_rate: 0.0002 + +train_on_inputs: false +group_by_length: false +bf16: auto +fp16: +tf32: true + +gradient_checkpointing: true +gradient_checkpointing_kwargs: + use_reentrant: false +early_stopping_patience: +resume_from_checkpoint: +local_rank: +logging_steps: 1 +xformers_attention: +flash_attention: true + +warmup_steps: 10 +evals_per_epoch: 4 +saves_per_epoch: 1 +debug: +deepspeed: +weight_decay: 0.0 +fsdp: +fsdp_config: +special_tokens: diff --git a/repodir/huixiangdou/sft/axolotl_configs/qwen2-moe-qlora.yaml b/repodir/huixiangdou/sft/axolotl_configs/qwen2-moe-qlora.yaml new file mode 100644 index 00000000..d6a835a0 --- /dev/null +++ b/repodir/huixiangdou/sft/axolotl_configs/qwen2-moe-qlora.yaml @@ -0,0 +1,64 @@ +base_model: Qwen/Qwen1.5-MoE-A2.7B +trust_remote_code: true + +load_in_8bit: false +load_in_4bit: true +strict: false + +datasets: + - path: mhenrichsen/alpaca_2k_test + type: alpaca +dataset_prepared_path: +val_set_size: 0.05 +output_dir: ./out + +sequence_len: 1024 # supports up to 32k +sample_packing: false +pad_to_sequence_len: false + +adapter: qlora +lora_model_dir: +lora_r: 32 +lora_alpha: 16 +lora_dropout: 0.05 +lora_target_linear: true +lora_fan_in_fan_out: + +wandb_project: +wandb_entity: +wandb_watch: +wandb_name: +wandb_log_model: + +gradient_accumulation_steps: 4 +micro_batch_size: 1 +num_epochs: 4 +optimizer: paged_adamw_8bit +lr_scheduler: cosine +learning_rate: 0.0002 + +train_on_inputs: false +group_by_length: false +bf16: auto +fp16: +tf32: true + +gradient_checkpointing: true +gradient_checkpointing_kwargs: + use_reentrant: false +early_stopping_patience: +resume_from_checkpoint: +local_rank: +logging_steps: 1 +xformers_attention: +flash_attention: true + +warmup_steps: 10 +evals_per_epoch: 4 +saves_per_epoch: 1 +debug: +deepspeed: +weight_decay: 0.0 +fsdp: +fsdp_config: +special_tokens: diff --git a/repodir/huixiangdou/sft/convert_to_alpaca.py b/repodir/huixiangdou/sft/convert_to_alpaca.py new file mode 100644 index 00000000..24f9edd7 --- /dev/null +++ b/repodir/huixiangdou/sft/convert_to_alpaca.py @@ -0,0 +1,99 @@ +import json +import random + + +def randomize_labels(true_label): + # 定义选项和对应的答案 + options = ['A', 'B', 'C'] + answers = ['不需要提取,信息完整', '需要提取', '不知道'] + + # 随机打乱选项和答案 + random.shuffle(answers) + + # 根据true_label确定正确答案的索引 + correct_answer_index = answers.index( + '需要提取') if true_label == True else answers.index('不需要提取,信息完整') + + option_str = '' + for index in range(len(options)): + option_str += '{}:{} '.format(options[index], answers[index]) + option_str = option_str.strip() + + gt_str = '{}:{}'.format(options[correct_answer_index], + answers[correct_answer_index]) + return option_str, gt_str + + +# 示例使用 +true_label = True # 假设我们的真实标签是True,即"不需要" +randomize_labels(true_label) + + +def convert_alpaca(input_filepath: str, output_filepath: str): + gts = [] + with open(input_filepath) as gt: + for line in gt: + target = json.loads(line) + if 'cr_need_gt' not in target: + continue + + gt_bool = target['cr_need_gt'] + text = target['text'] + + # build instruction, input, output + + window = target['cr_window'] + # logger.debug('input window {}'.format(window)) + name_map = dict() + name_int = ord('A') + # chr(start_ascii + i) + format_history = [] + for item in window: + sender = item['sender'] + if sender not in name_map: + name_map[sender] = chr(name_int) + name_int += 1 + + format_history.append({ + 'username': name_map[sender], + 'content': item['text'] + }) + + target_sender = target['sender'] + if target_sender not in name_map: + name_map[target_sender] = chr(name_int) + name_int += 1 + + target_str = json.dumps( + { + 'username': name_map[target_sender], + 'content': target['text'] + }, + indent=2, + ensure_ascii=False) + + BASE_PROMPT_TEMPLATE = '''群聊场景中“这”、“它”、“哪”等代词需要查看上下文和其他用户的回复才能确定具体指什么,请完成群聊场景代词替换任务。 +以下是历史对话,可能有多个人的发言: +{} +输入内容: +"{}"\n''' + prompt_base = BASE_PROMPT_TEMPLATE.format( + json.dumps(format_history, ensure_ascii=False), target_str) + + option_str, output = randomize_labels(gt_bool) + instruction = '{} 输入内容中的 content 信息是否完整,是否需要从历史对话中提取代词或宾语来替代 content 中的一部分词汇? {} \n一步步分析,首先历史消息包含哪些话题;其次哪个话题与问题最相关;如果都不相关就不提取。 '.format( + prompt_base, option_str) + + gts.append({ + 'instruction': instruction, + 'input': '', + 'output': output + }) + + alpaca_str = json.dumps(gts, ensure_ascii=False, indent=2) + with open(output_filepath, 'w') as fout: + fout.write(alpaca_str) + + +if __name__ == '__main__': + convert_alpaca('groups/input.jsonl', 'groups/alpaca.json') diff --git a/repodir/huixiangdou/sft/reconstruct_check_llm.py b/repodir/huixiangdou/sft/reconstruct_check_llm.py new file mode 100644 index 00000000..736eaf92 --- /dev/null +++ b/repodir/huixiangdou/sft/reconstruct_check_llm.py @@ -0,0 +1,69 @@ +# 1. puyu 在判断是否是问题任务中,召回已经很高 +# 2. 用 kimi 二次确认是否是个问题 +# 3. 在已经 is_question true 中,手工标注是否需要 cr +# * 标注规范:就看这句话是不是能构成独立的问题、不需要看其他话 +# * kimi & puyu 同时认为需要解答的内容中, puyu cr 判定的正确率 + +import argparse +import json +import os +import pdb +import re +import select +import sys +import termios +import time +import tty + +from loguru import logger +from openai import OpenAI +from sklearn.metrics import f1_score, precision_score, recall_score + + +def read_badcase(llm_type: str, input_filepath: str): + gts = [] + dts = [] + unknow_count = 0 + + with open('groups/input.jsonl') as gt: + for line in gt: + json_obj = json.loads(line) + if 'cr_need_gt' not in json_obj: + continue + + cr_need_gt = json_obj['cr_need_gt'] + gts.append(cr_need_gt) + + ret = dict() + idx = 0 + with open(input_filepath) as dt: + for line in dt: + json_obj = json.loads(line) + if 'cr_need_gt' not in json_obj: + continue + + dt = json_obj['{}_cr_need'.format(llm_type)] == 'yes' + if dt != gts[idx]: + ret[json_obj['text']] = line + idx += 1 + return ret + + +if __name__ == '__main__': + b14 = 'Qwen1.5-14B-Chat' + b14_badcase = read_badcase(b14, 'groups/{}.jsonl'.format(b14)) + + b32 = 'Qwen1.5-32B-Chat' + b32_badcase = read_badcase(b32, 'groups/{}.jsonl'.format(b32)) + + with open('groups/join.jsonl', 'w') as f: + for k in b14_badcase.keys(): + if k in b32_badcase: + json_str = b32_badcase[k] + json_obj = json.loads(json_str) + text = json_obj['text'] + gt = 'yes' if json_obj['cr_need_gt'] else 'no' + dt = json_obj['{}_cr_need'.format(b32)] + + if dt == 'yes': + f.write('dt:{} \ttext:{}\n'.format(dt, text)) diff --git a/repodir/huixiangdou/sft/reconstruct_filter_annotate.py b/repodir/huixiangdou/sft/reconstruct_filter_annotate.py new file mode 100644 index 00000000..cb99ca25 --- /dev/null +++ b/repodir/huixiangdou/sft/reconstruct_filter_annotate.py @@ -0,0 +1,481 @@ +# 1. puyu 在判断是否是问题任务中,召回已经很高 +# 2. 用 kimi 二次确认是否是个问题 +# 3. 在已经 is_question true 中,手工标注是否需要 cr +# * 标注规范:就看这句话是不是能构成独立的问题、不需要看其他话 +# * kimi & puyu 同时认为需要解答的内容中, puyu cr 判定的正确率 + +import argparse +import json +import os +import pdb +import re +import select +import sys +import termios +import time +import tty + +from loguru import logger +from openai import OpenAI +from sklearn.metrics import f1_score, precision_score, recall_score + + +def build_context(sender: str, query: str, window: list): + context = '' + for item in window: + context += '{}: {}\n'.format(item['sender'], item['text']) + + context += '标注问题:\n' + context += '{}: {}\n'.format(sender, query) + return context + + +def kimi_is_question(query): + SCORING_QUESTION_TEMPLTE = '“{}”\n请仔细阅读以上内容,判断句子是否是个有主题的疑问句或在向其他人问问题,结果用 0~10 表示。直接提供得分不要解释。\n判断标准:有主语谓语宾语并且是疑问句得 10 分;缺少主谓宾扣分;陈述句直接得 0 分;不是疑问句直接得 0 分。直接提供得分不要解释。' # noqa E501 + prompt = SCORING_QUESTION_TEMPLTE.format(query) + if prompt is None or len(prompt) == 0: + return False + + messages = [{ + 'role': + 'system', + 'content': + '你是 Kimi,由 Moonshot AI 提供的人工智能助手,你更擅长中文和英文的对话。你会为用户提供安全,有帮助,准确的回答。同时,你会拒绝一些涉及恐怖主义,种族歧视,黄色暴力等问题的回答。Moonshot AI 为专有名词,不可翻译成其他语言。' + }, { + 'role': 'user', + 'content': prompt + }] + + API_KEY = os.getenv('MOONSHOT_API_KEY') + if API_KEY is None or len(API_KEY) < 1: + assert ('moonshot api key not set') + + client = OpenAI( + api_key=API_KEY, + base_url='https://api.moonshot.cn/v1', + ) + + try: + completion = client.chat.completions.create( + model='moonshot-v1-8k', + messages=messages, + temperature=0.0, + ) + relation = completion.choices[0].message.content + filtered_relation = ''.join([c for c in relation if c.isdigit()]) + try: + score_str = re.sub(r'[^\d]', ' ', filtered_relation).strip() + score = int(score_str.split(' ')[0]) + if score >= 5: + return True + except Exception as e: + logger.error(str(e)) + except Exception as e: + return str(e) + return False + + +def kimi_annotate(puyu_file_path: str, kimi_file_path: str): + # 读取输入文件,并逐行处理 + with open(puyu_file_path, 'r', encoding='utf-8') as input_file, open( + kimi_file_path, 'w', encoding='utf-8') as output_file: + datas = [] + for line in input_file: + # 解析JSON数据 + data = json.loads(line) + + if not data['is_question']: + continue + + text = data['text'] + kimi_gt = kimi_is_question(query=text) + logger.debug('{} --- {}'.format(text, kimi_gt)) + data['kimi_is_question'] = kimi_gt + datas.append(data) + + output_file.write(json.dumps(data, ensure_ascii=False) + '\n') + + +def human_annotate(kimi_file_path: str, gt_file_path): + # 读取输入文件,并逐行处理 + datas = [] + miss_key = 0 + kimi_is_not_question = 0 + line_idx = 0 + too_short = 0 + with open(kimi_file_path) as input_file: + while True: + line = input_file.readline() + # 解析JSON数据 + line_idx += 1 + if not line: + break + data = json.loads(line) + if 'kimi_is_question' not in data: + miss_key += 1 + continue + if data['kimi_is_question'] == False: + kimi_is_not_question += 1 + continue + if len(data['text']) < 7: + too_short += 1 + continue + datas.append(data) + logger.debug( + 'sum: {}, kimi_is_question: {}, kimi_is_not_question {}, miss_key {} too_short {}' + .format(line_idx, len(datas), kimi_is_not_question, miss_key, + too_short)) + + START_INDEX = 0 + + # 保存原始的tty设置 + fd = sys.stdin.fileno() + old_settings = termios.tcgetattr(fd) + tty.setcbreak(sys.stdin) + + with open(gt_file_path, 'a', encoding='utf-8') as output_file: + max_len = len(datas) + datas = datas[START_INDEX:] + + for idx, data in enumerate(datas): + # 显示上下文和文本 + cr_window = data.get('cr_window', {}) + text = data.get('text', '') + sender = data.get('sender', '') + + context = build_context(sender=sender, + query=text, + window=cr_window) + print('{} / {}'.format(idx, max_len)) + print(context) + + # 等待用户输入 + while True: + user_input = sys.stdin.read(1) + if user_input not in ['j', 'k', 'i']: + print( + "Invalid input. Please enter 'j' or 'k' (yes, no or none). " + ) + continue + # 检查用户输入是否有效 + print(user_input) + break + + # 更新数据中的Ground Truth + if user_input == 'j': + data['cr_need_gt'] = True + elif user_input == 'k': + data['cr_need_gt'] = False + elif user_input == 'i': + data['cr_need_gt'] = 'not a question' + + # 将更新后的数据写入输出文件 + output_file.write(json.dumps(data, ensure_ascii=False) + '\n') + + termios.tcsetattr(fd, termios.TCSADRAIN, old_settings) + print('Annotation completed. Check the output file for the results.') + + +def human_check(gt_file_path): + bad = [] + bad_text_sum = 0 + good = [] + good_text_sum = 0 + with open(gt_file_path) as f: + for idx, line in enumerate(f): + data = json.loads(line) + + # print(repr('{} | {} | {}'.format(idx, data['cr_need_gt'], data['text']))) + # time.sleep(0.5) + + if data['cr_need_gt']: + bad.append(repr(data['text'])) + bad_text_sum += len(data['text']) + else: + good.append(repr(data['text'])) + good_text_sum += len(data['text']) + + print(json.dumps(bad, indent=2, ensure_ascii=False)) + print(json.dumps(good, indent=2, ensure_ascii=False)) + print( + 'bad count {}, avg text len {}; good count {}, avg text len {}'.format( + len(bad), bad_text_sum / len(bad), len(good), + good_text_sum / len(good))) + + with open('groups/good.json', 'w') as f: + f.write(json.dumps(good, ensure_ascii=False, indent=2)) + + with open('groups/bad.json', 'w') as f: + f.write(json.dumps(bad, ensure_ascii=False, indent=2)) + + +def metric(llm_type: str, + gt_filepath: str = 'groups/input.jsonl', + dt_filepath: str = 'groups/output.jsonl'): + gts = [] + dts = [] + unknow_count = 0 + + with open(gt_filepath) as gt: + for line in gt: + json_obj = json.loads(line) + if 'cr_need_gt' not in json_obj: + continue + + cr_need_gt = json_obj['cr_need_gt'] + gts.append(cr_need_gt) + + with open(dt_filepath) as dt: + for line in dt: + json_obj = json.loads(line) + if 'cr_need_gt' not in json_obj: + continue + + dt = json_obj['{}_cr_need'.format(llm_type)] + if 'yes' == dt: + dts.append(True) + elif 'no' == dt: + dts.append(False) + else: + dt = dt.replace(' ', '') + dt = dt.replace('\n', '') + + if 'ab' in dt or 'abc' in dt: + unknow_count += 1 + + dts.append(bool(1 - cr_need_gt)) + elif '答案:b' in dt or '选:b' in dt or '答案是:b' in dt: + dts.append(True) + elif '答案是:a' in dt or '答案是"不需要"' in dt or '答案是:不需要' in dt or '答案:不需要' in dt or '选项结果:不需要' in dt or '结果选项:不需要': + dts.append(False) + else: + unknow_count += 1 + print(dt) + dts.append(bool(1 - cr_need_gt)) + + assert len(gts) == len(dts) + precision = precision_score(gts, dts) + recall = recall_score(gts, dts) + f1 = f1_score(gts, dts) + + logger.info('{}: {} {} {} {}'.format(llm_type, precision, recall, f1, + unknow_count)) + + +def qwen_coref_res(llm_type: str, target: object): + model = '/workspace/models/{}'.format(llm_type) + client = OpenAI( + base_url='http://10.140.24.142:29999/v1', + api_key='token-abc123', + ) + + group_intro = """ +名词解释: +open-compass/opencompass : 用于评测大型语言模型(LLM). 它提供了完整的开源可复现的评测框架,支持大语言模型、多模态模型的一站式评测,基于分布式技术,对大参数量模型亦能实现高效评测。评测方向汇总为知识、语言、理解、推理、考试五大能力维度,整合集纳了超过70个评测数据集,合计提供了超过40万个模型评测问题,并提供长文本、安全、代码3类大模型特色技术能力评测。 +openmmlab/mmpose is an open-source toolbox for pose estimation based on PyTorch +openmmlab/mmdeploy is an open-source deep learning model deployment toolset +openmmlab/mmdetection is an open source object detection toolbox based on PyTorch. +lmdeploy 是一个用于压缩、部署和服务 LLM(Large Language Model)的工具包。是一个服务端场景下,transformer 结构 LLM 部署工具,支持 GPU 服务端部署,速度有保障,支持 Tensor Parallel,多并发优化,功能全面,包括模型转换、缓存历史会话的 cache feature 等. 它还提供了 WebUI、命令行和 gRPC 客户端接入。 +茴香豆(HuixiangDou)是一个基于 LLM 的群聊知识助手。设计拒答、响应两阶段 pipeline 应对群聊场景,解答问题同时不会消息泛滥。 +xtuner is an efficient, flexible and full-featured toolkit for fine-tuning large models. +mmyolo : YOLO series toolbox and benchmark. Implemented RTMDet, RTMDet-Rotated,YOLOv5, YOLOv6, YOLOv7, YOLOv8,YOLOX, PPYOLOE, etc. +ncnn is a high-performance neural network inference framework optimized for the mobile platform +""" + + window = target['cr_window'] + # logger.debug('input window {}'.format(window)) + name_map = dict() + name_int = ord('A') + # chr(start_ascii + i) + format_history = [] + for item in window: + sender = item['sender'] + if sender not in name_map: + name_map[sender] = chr(name_int) + name_int += 1 + + format_history.append({ + 'username': name_map[sender], + 'content': item['text'] + }) + + target_sender = target['sender'] + if target_sender not in name_map: + name_map[target_sender] = chr(name_int) + name_int += 1 + + target_str = json.dumps( + { + 'username': name_map[target_sender], + 'content': target['text'] + }, + indent=2, + ensure_ascii=False) + + BASE_PROMPT_TEMPLATE = '''群聊场景中“这”、“它”、“哪”等代词需要查看上下文和其他用户的回复才能确定具体指什么,请完成群聊场景代词替换任务。 + +以下是历史对话,可能有多个人的发言: +{} + +输入内容: +"{}"''' + prompt_base = BASE_PROMPT_TEMPLATE.format( + json.dumps(format_history, ensure_ascii=False), target_str) + + prompt = '{}\n输入内容中的 content 信息是否完整,是否需要从历史对话中提取代词或宾语来替代 content 中的一部分词汇? A:不需要提取,信息完整 B:需要 C:不知道 \n一步步分析,首先历史消息包含哪些话题;其次哪个话题与问题最相关;如果都不相关就不提取。 '.format( + prompt_base) + + completion = client.chat.completions.create(model=model, + messages=[{ + 'role': 'user', + 'content': prompt + }]) + need_cr = completion.choices[0].message.content.lower() + need_cr = need_cr.strip() + logger.debug('{} {}'.format(prompt, need_cr)) + + response = '' + + prompt = """请判断用户意图,这位用户在做单选题,单选题答案有 3 个, A:不需要提取,信息完整 B:需要 C:不知道。 +用户输入: +{} + +用户的答案是?不要解释,直接给 ABC 选项结果。 +""".format(need_cr) + + completion = client.chat.completions.create(model=model, + messages=[{ + 'role': 'user', + 'content': prompt + }]) + need_cr = completion.choices[0].message.content.lower() + need_cr = need_cr.strip() + + logger.warning('final choose {}'.format(need_cr)) + + if need_cr.startswith( + 'a' + ) or need_cr == '不需要' or '因此不需要' in need_cr or 'a:不需要' in need_cr or '不需要进行指代消解' in need_cr or '选项 a' in need_cr: + return '', 'no' + elif need_cr.startswith( + 'b' + ) or need_cr == '需要' or '因此需要' in need_cr or '因此选择b' in need_cr or '需要进行指代消解' in need_cr or '需要指代消解' in need_cr or 'b:需要' in need_cr: + prompt = '{}\n指代消解输入内容中 content 后的文本是?直接返回消解后的完整文本不要解释原因;直接返回最终结果不要解释过程。'.format( + prompt_base) + + completion = client.chat.completions.create(model=model, + messages=[{ + 'role': 'user', + 'content': prompt + }]) + response = completion.choices[0].message.content.lower() + elif need_cr.startswith('c') or '不知道' in need_cr: + return '', 'unknown' + else: + return '', 'exception {}'.format(need_cr) + + keywords = ['指代消解后的文本是:', '指代消解后是:', '指代消解后:', '指代消解后的文本为:'] + for keyword in keywords: + if keyword in response: + response = response.split(keyword)[-1] + response = response.strip() + if response.startswith('"') and response.endswith('"'): + response = response[1:-1] + logger.warning('coref response {}'.format(response)) + return response, 'yes' + + +def llm_annotate(llm_type: str, + input_filepath: str = 'groups/input.jsonl', + output_filepath: str = 'groups/output.jsonl'): + idx = 0 + with open(input_filepath) as fin, open(output_filepath, 'a') as fout: + for line in fin: + json_obj = json.loads(line) + if not json_obj['is_question']: + continue + if not json_obj['kimi_is_question']: + continue + + idx += 1 + + if 'qwen' in llm_type.lower(): + cr_text, state = qwen_coref_res(llm_type=llm_type, + target=json_obj) + json_obj['{}_cr_text'.format(llm_type)] = cr_text + json_obj['{}_cr_need'.format(llm_type)] = state + + json_text = json.dumps(json_obj, ensure_ascii=False) + fout.write(json_text) + fout.write('\n') + + +def parse_args(): + """Parse args.""" + parser = argparse.ArgumentParser( + description='Annotate and metric LLM with CR task.') + parser.add_argument('--group-id', + type=str, + default='20814553575', + help='Group ID') + parser.add_argument( + '--input', + type=str, + default='/home/khj/github/huixiangdou/tests/history_recv_send.txt', + help='Raw input messages.') + parser.add_argument( + '--action', + type=str, + # default='split', + default='metric', + help= + '"annotate"): manually annotate query; "metric"): test with LLM and metric' + ) + parser.add_argument( + '--llm-type', + type=str, + # default='split', + # default='Qwen1.5-0.5B-Chat', + default='Qwen1.5-1.8B-Chat', + # default='qwen1.5-moe-2.7B-chat', + # default='Qwen1.5-4B-Chat', + # default='Qwen1.5-7B-Chat', + # default='Qwen1.5-14B-Chat', + # default='Qwen1.5-32B-Chat', + help='LLM type, use qwen moe by default.') + args = parser.parse_args() + return args + + +if __name__ == '__main__': + args = parse_args() + + # 定义输入输出文件路径 + # 18356748488 ncnn contributor's group + # 20814553575 openmmlab groups + + if args.action == 'annotate': + """ + 1. 用 kimi 二次标注,基于 `reconstruct_wechat_group.py is_question` 筛选 + 2. kimi & LLM 同时认为是问题的,过滤太短的,人工标注 + 3. 检查一遍 + 4. 基于人工 GT 计算 LLM 的精度 + """ + group_id = args.group_id + puyu_file_path = 'groups/{}@chatroom@reconstruct.txt.llm'.format( + group_id) + kimi_file_path = 'groups/{}@chatroom@reconstruct.txt.kimi'.format( + group_id) + gt_filepath = 'groups/{}@chatroom@gt.jsonl'.format(group_id) + kimi_annotate() + human_annotate() + elif args.action == 'check': + human_check('groups/input.jsonl') + elif args.action == 'metric': + output_filepath = 'groups/{}.jsonl'.format(args.llm_type) + llm_annotate(llm_type=args.llm_type, output_filepath=output_filepath) + metric(llm_type=args.llm_type, filepath=output_filepath) + + # for llm_type in ['Qwen1.5-0.5B-Chat','Qwen1.5-1.8B-Chat', 'qwen1.5-moe-2.7B-chat', 'Qwen1.5-4B-Chat', 'Qwen1.5-7B-Chat', 'Qwen1.5-14B-Chat', 'Qwen1.5-32B-Chat']: + # input_filepath = 'groups/{}.jsonl'.format(llm_type) + # metric(llm_type=llm_type, gt_filepath='groups/input.jsonl', dt_filepath=input_filepath) diff --git a/repodir/huixiangdou/sft/reconstruct_wechat_group.py b/repodir/huixiangdou/sft/reconstruct_wechat_group.py new file mode 100644 index 00000000..775750ea --- /dev/null +++ b/repodir/huixiangdou/sft/reconstruct_wechat_group.py @@ -0,0 +1,520 @@ +import argparse +import json +import os +import pdb +import re +import time + +import pytoml +import requests +from loguru import logger + + +class ChatClient: + """A class to handle client-side interactions with a chat service. + + This class is responsible for loading configurations from a given path, + building prompts, and generating responses by interacting with the chat + service. + """ + + def __init__(self, config_path: str) -> None: + """Initialize the ChatClient with the path of the configuration + file.""" + self.config_path = config_path + self.llm_config = None + with open(self.config_path, encoding='utf8') as f: + config = pytoml.load(f) + self.llm_config = config['llm'] + + def auto_fix(self, backend): + """Choose real backend according to config.ini.""" + + enable_local, enable_remote = (self.llm_config['enable_local'], + self.llm_config['enable_remote']) + local_len, remote_len = ( + self.llm_config['server']['local_llm_max_text_length'], + self.llm_config['server']['remote_llm_max_text_length']) + + max_length = local_len + if enable_remote: + max_length = remote_len + + if backend == 'local' and not enable_local: + backend = self.llm_config['server']['remote_type'] + max_length = remote_len + elif backend != 'local' and not enable_remote: + backend = 'local' + max_length = local_len + + return backend, max_length + + def generate_response(self, prompt, history=[], backend='local'): + """Generate a response from the chat service. + + Args: + prompt (str): The prompt to send to the chat service. + history (list, optional): List of previous interactions. Defaults to []. + backend (str, optional): Determine which LLM should be called. Default to `local` + + Returns: + str: Generated response from the chat service. + """ + url = self.llm_config['client_url'] + real_backend, max_length = self.auto_fix(backend=backend) + + if len(prompt) > max_length: + logger.warning( + f'prompt length {len(prompt)} > max_length {max_length}, truncated' # noqa E501 + ) + prompt = prompt[0:max_length] + + try: + header = {'Content-Type': 'application/json'} + data_history = [] + for item in history: + data_history.append([item[0], item[1]]) + data = { + 'prompt': prompt, + 'history': data_history, + 'backend': real_backend + } + resp = requests.post(url, + headers=header, + data=json.dumps(data), + timeout=300) + if resp.status_code != 200: + raise Exception(str((resp.status_code, resp.reason))) + + json_obj = resp.json() + text = json_obj['text'] + if 'error' in json_obj: + error = json_obj['error'] + if len(error) > 0: + logger.error(error) + return text + except Exception as e: + logger.error(str(e)) + logger.error( + 'Do you forget `--standalone` when `python3 -m huixiangdou.main` ?' # noqa E501 + ) + return '' + + +def parse_args(): + """Parse args.""" + parser = argparse.ArgumentParser(description='Reconstruct group chat.') + parser.add_argument('--output_dir', + type=str, + default='groups', + help='Splitted group messages.') + parser.add_argument( + '--input', + type=str, + default='/home/khj/github/huixiangdou/tests/history_recv_send.txt', + help='Raw input messages.') + parser.add_argument( + '--action', + type=str, + # default='split', + default='intention', + help= + '"split"): split raw input into group messages; "intention"): decide which query being a question' + ) + + args = parser.parse_args() + return args + + +def remove_at_name(text): + pattern = r'@[\w\.-]+\s+' + text = re.sub(pattern, '', text) + pos = text.find('@') + if pos != -1: + text = text[0:pos] + return text + + +def simplify_wx_object(json_obj): + msg_type = json_obj['messageType'] + show_type = '' + + text = json_obj['content'] + sender = json_obj['fromUser'] + recvs = [] + + # get show_type and content text + if msg_type in [5, 9, '80001']: + show_type = 'normal' + if 'atlist' in json_obj: + show_type = 'normal_at' + atlist = json_obj['atlist'] + for at in atlist: + if len(at) > 0: + recvs.append(at) + + if msg_type in [6, '80002']: + show_type = 'image' + text = '[图片]' + + elif msg_type == '80009': + show_type = 'file' + content = json_obj['pushContent'] + + elif msg_type in [14, '80014']: + # ref revert msg + show_type = 'ref' + if 'title' in json_obj: + content = json_obj['title'] + else: + content = 'unknown' + + if 'toUser' in json_obj: + recvs.append(json_obj['toUser']) + + else: + show_type = 'other' + # print('other type {}'.format(msg_type)) + + if '' in text: + text = 'xml msg' + if '' in text and '= 5: + return True + return False + + +def coref_res(target: object, window: list, group_intro: str): + llm = ChatClient('config.ini') + + # logger.debug('input window {}'.format(window)) + name_map = dict() + name_int = ord('A') + # chr(start_ascii + i) + format_history = [] + for item in window: + sender = item['sender'] + if sender not in name_map: + name_map[sender] = chr(name_int) + name_int += 1 + + format_history.append({ + 'username': name_map[sender], + 'content': item['text'] + }) + + target_sender = target['sender'] + if target_sender not in name_map: + name_map[target_sender] = chr(name_int) + name_int += 1 + + target_str = json.dumps( + { + 'username': name_map[target_sender], + 'content': target['text'] + }, + indent=2, + ensure_ascii=False) + + BASE_PROMPT_TEMPLATE = '''请完成群聊场景中的指代消解任务。 +"{}" +以下是历史对话,可能有多个人的发言: +{} + +输入内容: +"{}"''' + prompt_base = BASE_PROMPT_TEMPLATE.format( + group_intro, json.dumps(format_history, ensure_ascii=False), + target['text']) + + prompt = '{}\n输入是否需要指代消解? A:需要 B不需要 C不知道'.format(prompt_base) + need_cr = llm.generate_response(prompt=prompt, backend='puyu').lower() + logger.debug('{} {}'.format(prompt, need_cr)) + + response = '' + if 'a' in need_cr: + prompt = '{}\n指代消解输入后的结果是?直接返回消解后的完整文本不要解释原因;直接返回最终结果不要解释过程。'.format( + prompt_base) + response = llm.generate_response(prompt=prompt, backend='puyu').lower() + else: + return '', False + + keywords = ['指代消解后的文本是:', '指代消解后是:', '指代消解后:', '指代消解后的文本为:'] + for keyword in keywords: + if keyword in response: + response = response.split(keyword)[-1] + response = response.strip() + if response.startswith('"') and response.endswith('"'): + response = response[1:-1] + logger.debug('return response {}'.format(response)) + return response, True + + +def intention(output_dir): + """扫描一个群的流式聊天记录,把同一个人 18 秒内发的连续内容合并.""" + if not os.path.exists(output_dir): + logger.error('{} not exist'.format(output_dir)) + return + + sender_cnt = {} + + # group_intros = { + # '18356748488': """ + # 名词解释: + # HuixiangDou,中文名 茴香豆。 + # 茴香豆是一个基于 LLM 的群聊知识助手,优势: + + # 设计拒答、响应两阶段 pipeline 应对群聊场景,解答问题同时不会消息泛滥。精髓见技术报告 + # 成本低至 1.5G 显存,无需训练适用各行业 + # 提供一整套前后端 web、android、算法源码,工业级开源可商用 + # 查看茴香豆已运行在哪些场景;加入微信群直接体验群聊助手效果。 + + # 群描述: + # 这是 HuixiangDou (茴香豆) 的微信体验群。用户会发一些相关技术疑问。""", + # } + group_intros = { + '20814553575': + """ +名词解释: +open-compass/opencompass : 用于评测大型语言模型(LLM). 它提供了完整的开源可复现的评测框架,支持大语言模型、多模态模型的一站式评测,基于分布式技术,对大参数量模型亦能实现高效评测。评测方向汇总为知识、语言、理解、推理、考试五大能力维度,整合集纳了超过70个评测数据集,合计提供了超过40万个模型评测问题,并提供长文本、安全、代码3类大模型特色技术能力评测。 +openmmlab/mmpose is an open-source toolbox for pose estimation based on PyTorch +openmmlab/mmdeploy is an open-source deep learning model deployment toolset +openmmlab/mmdetection is an open source object detection toolbox based on PyTorch. +lmdeploy 是一个用于压缩、部署和服务 LLM(Large Language Model)的工具包。是一个服务端场景下,transformer 结构 LLM 部署工具,支持 GPU 服务端部署,速度有保障,支持 Tensor Parallel,多并发优化,功能全面,包括模型转换、缓存历史会话的 cache feature 等. 它还提供了 WebUI、命令行和 gRPC 客户端接入。 +茴香豆(HuixiangDou)是一个基于 LLM 的群聊知识助手。设计拒答、响应两阶段 pipeline 应对群聊场景,解答问题同时不会消息泛滥。 +xtuner is an efficient, flexible and full-featured toolkit for fine-tuning large models. +mmyolo : YOLO series toolbox and benchmark. Implemented RTMDet, RTMDet-Rotated,YOLOv5, YOLOv6, YOLOv7, YOLOv8,YOLOX, PPYOLOE, etc. + +群描述: +这是 openmmlab 贡献者和用户群。用户会发一些相关技术疑问。""", + } + + files = os.listdir(output_dir) + + for file in files: + filepath = os.path.join(output_dir, file) + if not filepath.endswith('@chatroom@reconstruct.txt'): + continue + + introduction = '' + group_id = os.path.basename(filepath) + group_id = group_id.split('@')[0] + if group_id in group_intros: + introduction = group_intros[group_id] + + if len(introduction) < 1: + continue + + window_history = [] + MAX_WINDOW_SIZE = 12 + STME_SPAN = 18 + + raw_chats = [] + with open(filepath) as f: + while True: + line = f.readline() + if not line: + break + if len(line) < 2: + continue + json_obj = json.loads(line) + if json_obj['show'] == 'ref': + continue + raw_chats.append(json_obj) + + # concat successive chat to one and save them + idx = 0 + concat_chats = [] + target = None + target_timestamp = 0 + while idx < len(raw_chats): + chat = raw_chats[idx] + idx += 1 + + if chat['timestamp'] == target_timestamp: + continue + + if target is None: + target = chat + target_timestamp = target['timestamp'] + elif target['sender'] == chat['sender'] and abs( + chat['timestamp'] - target_timestamp) < STME_SPAN: + target_timestamp = chat['timestamp'] + # print('{} merge {}'.format(target['id'], chat['id'])) + target['text'] += '\n' + target['text'] += chat['text'] + + else: + concat_chats.append(target) + target = None + + if target is not None: + concat_chats.append(target) + + outfilepath = filepath + '.concat' + with open(outfilepath, 'w') as f: + f.write(json.dumps(concat_chats, indent=2, ensure_ascii=False)) + + logger.info('concat {} to {} msg'.format(len(raw_chats), + len(concat_chats))) + + # check a query is question, and coref res + for json_obj in concat_chats: + text = json_obj['text'] + if len(text) < 1: + continue + + window_history.append(json_obj) + if is_question(text): + json_obj['is_question'] = True + # 是问题,格式化历史消息,消解 + window_history = window_history[-MAX_WINDOW_SIZE:-1] + cr_text, success = coref_res(json_obj, + window=window_history, + group_intro=introduction) + + json_obj['cr_window'] = window_history + if success: + json_obj['cr_text'] = cr_text + json_obj['cr_need'] = True + else: + json_obj['cr_need'] = False + else: + json_obj['is_question'] = False + + # 判断是否问题 + # 如果是,尝试指代消解 & 意图划分 + + outfilepath = filepath + '.llm' + with open(outfilepath, 'a') as fout: + json_text = json.dumps(json_obj, ensure_ascii=False) + fout.write(json_text) + fout.write('\n') + + +def main(): + """ + split: 把单个群聊文件,划分成多个。 + intention: 用 LLM 计算 is_question cr_need + """ + args = parse_args() + if args.action == 'split': + split(args.input, args.output_dir) + elif args.action == 'intention': + intention(args.output_dir) + + +if __name__ == '__main__': + main() diff --git a/repodir/huixiangdou/tests/__init__.py b/repodir/huixiangdou/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/repodir/huixiangdou/tests/data.json b/repodir/huixiangdou/tests/data.json new file mode 100644 index 00000000..4d2dbf74 --- /dev/null +++ b/repodir/huixiangdou/tests/data.json @@ -0,0 +1,7 @@ +[ + { + "question": "[捂脸]几年前用大恒的工业相机,还得用他们的SDK,还没arm的版本,最后让他们编了一个arm的用", + "part": " 快速上手 部署 internlm #### 获取 internlm 模型 \n#### 使用 turbomind 推理 \n> **note**
\n> turbomind 在使用 fp16 精度推理 internlm-7b 模型时,显存开销至少需要 15.7g。建议使用 3090, v100,a100等型号的显卡。
\n> 关闭显卡的 ecc 可以腾出 10% 显存,执行 `sudo nvidia-smi --ecc-config=0` 重启系统生效。 \n> **note**
\n> 使用 tensor 并发可以利用多张 gpu 进行推理。在 `chat` 时添加参数 `--tp=` 可以启动运行时 tp。 \n#### 启动 gradio server \n! \n#### 通过 restful api 部署服务 \n使用下面的命令启动推理服务: \n你可以通过命令行方式与推理服务进行对话: \n也可以通过 webui 方式来对话: \n更多详情可以查阅 restful_api.md。 \n#### 通过容器部署推理服务 \n使用下面的命令启动推理服务: \n你可以通过命令行方式与推理服务进行对话: \n也可以通过 webui 方式来对话: \n其他模型的部署方式,比如 llama,llama-2,vicuna等等,请参考这里", + "full": "
\n \n\n[![docs](https://img.shields.io/badge/docs-latest-blue)](https://lmdeploy-zh-cn.readthedocs.io/zh_CN/latest/)\n[![badge](https://github.com/InternLM/lmdeploy/workflows/lint/badge.svg)](https://github.com/InternLM/lmdeploy/actions)\n[![PyPI](https://img.shields.io/pypi/v/lmdeploy)](https://pypi.org/project/lmdeploy)\n[![license](https://img.shields.io/github/license/InternLM/lmdeploy.svg)](https://github.com/InternLM/lmdeploy/tree/main/LICENSE)\n[![issue resolution](https://img.shields.io/github/issues-closed-raw/InternLM/lmdeploy)](https://github.com/InternLM/lmdeploy/issues)\n[![open issues](https://img.shields.io/github/issues-raw/InternLM/lmdeploy)](https://github.com/InternLM/lmdeploy/issues)\n\n[English](README.md) | 简体中文\n\n
\n\n

\n 👋 join us on Twitter, Discord and WeChat\n

\n\n______________________________________________________________________\n\n## 更新 🎉\n\n- \\[2023/08\\] TurboMind 支持 Qwen-7B,动态NTK-RoPE缩放,动态logN缩放\n- \\[2023/08\\] TurboMind 支持 Windows (tp=1)\n- \\[2023/08\\] TurboMind 支持 4-bit 推理,速度是 FP16 的 2.4 倍,是目前最快的开源实现🚀。部署方式请看[这里](./docs/zh_cn/w4a16.md)\n- \\[2023/08\\] LMDeploy 开通了 [HuggingFace Hub](https://huggingface.co/lmdeploy) ,提供开箱即用的 4-bit 模型\n- \\[2023/08\\] LMDeploy 支持使用 [AWQ](https://arxiv.org/abs/2306.00978) 算法进行 4-bit 量化\n- \\[2023/07\\] TurboMind 支持使用 GQA 的 Llama-2 70B 模型\n- \\[2023/07\\] TurboMind 支持 Llama-2 7B/13B 模型\n- \\[2023/07\\] TurboMind 支持 InternLM 的 Tensor Parallel 推理\n\n______________________________________________________________________\n\n## 简介\n\nLMDeploy 由 [MMDeploy](https://github.com/open-mmlab/mmdeploy) 和 [MMRazor](https://github.com/open-mmlab/mmrazor) 团队联合开发,是涵盖了 LLM 任务的全套轻量化、部署和服务解决方案。\n这个强大的工具箱提供以下核心功能:\n\n- **高效推理引擎 TurboMind**:基于 [FasterTransformer](https://github.com/NVIDIA/FasterTransformer),我们实现了高效推理引擎 TurboMind,支持 InternLM、LLaMA、vicuna等模型在 NVIDIA GPU 上的推理。\n\n- **交互推理方式**:通过缓存多轮对话过程中 attention 的 k/v,记住对话历史,从而避免重复处理历史会话。\n\n- **多 GPU 部署和量化**:我们提供了全面的模型部署和量化支持,已在不同规模上完成验证。\n\n- **persistent batch 推理**:进一步优化模型执行效率。\n\n ![PersistentBatchInference](https://github.com/InternLM/lmdeploy/assets/67539920/e3876167-0671-44fc-ac52-5a0f9382493e)\n\n## 支持的模型\n\n`LMDeploy` 支持 `TurboMind` 和 `Pytorch` 两种推理后端\n\n### TurboMind\n\n> **Note**
\n> W4A16 推理需要 Ampere 及以上架构的 Nvidia GPU\n\n| 模型 | 模型并行 | FP16 | KV INT8 | W4A16 | W8A8 |\n| :------: | :------: | :--: | :-----: | :---: | :--: |\n| Llama | Yes | Yes | Yes | Yes | No |\n| Llama2 | Yes | Yes | Yes | Yes | No |\n| InternLM | Yes | Yes | Yes | Yes | No |\n\n### Pytorch\n\n| 模型 | 模型并行 | FP16 | KV INT8 | W4A16 | W8A8 |\n| :------: | :------: | :--: | :-----: | :---: | :--: |\n| Llama | Yes | Yes | No | No | No |\n| Llama2 | Yes | Yes | No | No | No |\n| InternLM | Yes | Yes | No | No | No |\n\n## 性能\n\n**场景一**: 固定的输入、输出token数(1,2048),测试 output token throughput\n\n**场景二**: 使用真实数据,测试 request throughput\n\n测试配置:LLaMA-7B, NVIDIA A100(80G)\n\nTurboMind 的 output token throughput 超过 2000 token/s, 整体比 DeepSpeed 提升约 5% - 15%,比 huggingface transformers 提升 2.3 倍\n在 request throughput 指标上,TurboMind 的效率比 vLLM 高 30%\n\n![benchmark](https://github.com/InternLM/lmdeploy/assets/4560679/7775c518-608e-4e5b-be73-7645a444e774)\n\n## 快速上手\n\n### 安装\n\n使用 pip ( python 3.8+) 安装 LMDeploy,或者[源码安装](./docs/zh_cn/build.md)\n\n```shell\npip install lmdeploy\n```\n\n### 部署 InternLM\n\n#### 获取 InternLM 模型\n\n```shell\n# 1. 下载 InternLM 模型\n\n# Make sure you have git-lfs installed (https://git-lfs.com)\ngit lfs install\ngit clone https://huggingface.co/internlm/internlm-chat-7b /path/to/internlm-chat-7b\n\n# if you want to clone without large files – just their pointers\n# prepend your git clone with the following env var:\nGIT_LFS_SKIP_SMUDGE=1\n\n# 2. 转换为 trubomind 要求的格式。默认存放路径为 ./workspace\npython3 -m lmdeploy.serve.turbomind.deploy internlm-chat-7b /path/to/internlm-chat-7b\n\n```\n\n#### 使用 turbomind 推理\n\n```shell\npython3 -m lmdeploy.turbomind.chat ./workspace\n```\n\n> **Note**
\n> turbomind 在使用 FP16 精度推理 InternLM-7B 模型时,显存开销至少需要 15.7G。建议使用 3090, V100,A100等型号的显卡。
\n> 关闭显卡的 ECC 可以腾出 10% 显存,执行 `sudo nvidia-smi --ecc-config=0` 重启系统生效。\n\n> **Note**
\n> 使用 Tensor 并发可以利用多张 GPU 进行推理。在 `chat` 时添加参数 `--tp=` 可以启动运行时 TP。\n\n#### 启动 gradio server\n\n```shell\npython3 -m lmdeploy.serve.gradio.app ./workspace\n```\n\n![](https://github.com/InternLM/lmdeploy/assets/67539920/08d1e6f2-3767-44d5-8654-c85767cec2ab)\n\n#### 通过 Restful API 部署服务\n\n使用下面的命令启动推理服务:\n\n```shell\npython3 -m lmdeploy.serve.openai.api_server ./workspace server_ip server_port --instance_num 32 --tp 1\n```\n\n你可以通过命令行方式与推理服务进行对话:\n\n```shell\n# restful_api_url is what printed in api_server.py, e.g. http://localhost:23333\npython -m lmdeploy.serve.openai.api_client restful_api_url\n```\n\n也可以通过 WebUI 方式来对话:\n\n```shell\n# restful_api_url is what printed in api_server.py, e.g. http://localhost:23333\n# server_ip and server_port here are for gradio ui\n# example: python -m lmdeploy.serve.gradio.app http://localhost:23333 localhost 6006 --restful_api True\npython -m lmdeploy.serve.gradio.app restful_api_url server_ip --restful_api True\n```\n\n更多详情可以查阅 [restful_api.md](docs/zh_cn/restful_api.md)。\n\n#### 通过容器部署推理服务\n\n使用下面的命令启动推理服务:\n\n```shell\nbash workspace/service_docker_up.sh\n```\n\n你可以通过命令行方式与推理服务进行对话:\n\n```shell\npython3 -m lmdeploy.serve.client {server_ip_addresss}:33337\n```\n\n也可以通过 WebUI 方式来对话:\n\n```shell\npython3 -m lmdeploy.serve.gradio.app {server_ip_addresss}:33337\n```\n\n其他模型的部署方式,比如 LLaMA,LLaMA-2,vicuna等等,请参考[这里](docs/zh_cn/serving.md)\n\n### 基于 PyTorch 的推理\n\n你必须确保环境中有安装 deepspeed:\n\n```\npip install deepspeed\n```\n\n#### 单个 GPU\n\n```shell\npython3 -m lmdeploy.pytorch.chat $NAME_OR_PATH_TO_HF_MODEL\\\n --max_new_tokens 64 \\\n --temperture 0.8 \\\n --top_p 0.95 \\\n --seed 0\n```\n\n#### 使用 DeepSpeed 实现张量并行\n\n```shell\ndeepspeed --module --num_gpus 2 lmdeploy.pytorch.chat \\\n $NAME_OR_PATH_TO_HF_MODEL \\\n --max_new_tokens 64 \\\n --temperture 0.8 \\\n --top_p 0.95 \\\n --seed 0\n```\n\n## 量化部署\n\n### Step 1. 获取量化参数\n\n首先,执行量化脚本,获取量化参数\n\n> 执行后,量化需要的各种参数会存放在 $WORK_DIR 中; 接下来的步骤中会用到\n\n```\n\npython3 -m lmdeploy.lite.apis.calibrate \\\n --model $HF_MODEL \\\n --calib_dataset 'c4' \\ # 校准数据集,支持 c4, ptb, wikitext2, pileval\n --calib_samples 128 \\ # 校准集的样本数,如果显存不够,可以适当调小\n --calib_seqlen 2048 \\ # 单条的文本长度,如果显存不够,可以适当调小\n --work_dir $WORK_DIR \\ # 保存 Pytorch 格式量化统计参数和量化后权重的文件夹\n```\n\n### Step 2. 实际量化模型\n\n目前支持对权重的 INT4 量化和 KV Cache 的 INT8 量化,根据需求执行对应脚本即可\n\n#### 权重 INT4 量化\n\nLMDeploy 使用 [AWQ](https://arxiv.org/abs/2306.00978) 算法对模型权重进行量化\n\n> 需要输入第一步的 \\`$WORK_DIR\\`\\` ,量化后的权重也会存在这个文件夹中\n\n```\npython3 -m lmdeploy.lite.apis.auto_awq \\\n --model $HF_MODEL \\\n --w_bits 4 \\ # 权重量化的 bit 数\n --w_group_size 128 \\ # 权重量化分组统计尺寸\n --work_dir $WORK_DIR \\ # Step 1 保存量化参数的目录\n```\n\n[点击这里](./docs/zh_cn/w4a16.md) 查看 weight int4 用法测试结果。\n\n#### KV Cache INT8 量化\n\n[点击这里](./docs/zh_cn/kv_int8.md) 查看 kv int8 使用方法、实现公式和测试结果。\n\n> **Warning**
\n> 量化部署不支持运行时 Tensor 并发。如果希望使用 Tensor 并发,需要在 deploy 时配置 tp 参数。\n\n## 贡献指南\n\n我们感谢所有的贡献者为改进和提升 LMDeploy 所作出的努力。请参考[贡献指南](.github/CONTRIBUTING.md)来了解参与项目贡献的相关指引。\n\n## 致谢\n\n- [FasterTransformer](https://github.com/NVIDIA/FasterTransformer)\n- [llm-awq](https://github.com/mit-han-lab/llm-awq)\n\n## License\n\n该项目采用 [Apache 2.0 开源许可证](LICENSE)。\n" + } +] diff --git a/repodir/huixiangdou/tests/git-clone.sh b/repodir/huixiangdou/tests/git-clone.sh new file mode 100644 index 00000000..acd13911 --- /dev/null +++ b/repodir/huixiangdou/tests/git-clone.sh @@ -0,0 +1,22 @@ +git clone https://ghproxy.org/https://github.com/open-compass/opencompass --depth=1 +git clone https://ghproxy.org/https://github.com/open-mmlab/mmpose --depth=1 +git clone https://ghproxy.org/https://github.com/open-mmlab/mmdeploy --depth=1 +git clone https://ghproxy.org/https://github.com/open-mmlab/mmdetection --depth=1 +git clone https://ghproxy.org/https://github.com/internlm/lmdeploy --depth=1 +git clone https://ghproxy.org/https://github.com/internlm/xtuner --depth=1 +git clone https://ghproxy.org/https://github.com/open-mmlab/mmyolo --depth=1 +git clone https://ghproxy.org/https://github.com/open-mmlab/mmcv --depth=1 +git clone https://ghproxy.org/https://github.com/internlm/huixiangdou --depth=1 + +git clone https://github.com/open-compass/opencompass --depth=1 +git clone https://github.com/open-mmlab/mmpose --depth=1 +git clone https://github.com/open-mmlab/mmdeploy --depth=1 +git clone https://github.com/open-mmlab/mmdetection --depth=1 +git clone https://github.com/internlm/lmdeploy --depth=1 +git clone https://github.com/internlm/xtuner --depth=1 +git clone https://github.com/open-mmlab/mmyolo --depth=1 +git clone https://github.com/open-mmlab/mmcv --depth=1 +git clone https://github.com/internlm/huixiangdou --depth=1 + +git clone https://github.com/open-mmlab/Amphion --depth=1 +git clone https://github.com/open-mmlab/labelbee --depth=1 diff --git a/repodir/huixiangdou/tests/test_alles_apin.py b/repodir/huixiangdou/tests/test_alles_apin.py new file mode 100644 index 00000000..a63b9bd7 --- /dev/null +++ b/repodir/huixiangdou/tests/test_alles_apin.py @@ -0,0 +1,27 @@ +import json +import os + +import requests + +url = 'https://openxlab.org.cn/gw/alles-apin-hub/v1/openai/v2/text/chat' +api_token = os.getenv('ALLES_APIN_TOKEN') +headers = {'content-type': 'application/json', 'alles-apin-token': api_token} + +payload = { + 'model': + 'gpt-4-1106-preview', + 'messages': [{ + 'role': + 'user', + 'content': + '帮我写个 python 代码,用 time.time() 和 datetime 获取当前时间。把当前时间的秒数设成 0,毫秒数也设成 0, 分钟数加 1,输出新时间对应的毫秒数,格式和 time.time() 相同' + }] +} + +response = requests.post(url, headers=headers, data=json.dumps(payload)) +resp_json = response.json() +if resp_json['msgCode'] == '10000': + data = resp_json['data'] + if len(data['choices']) > 0: + text = data['choices'][0]['message']['content'] + print(text) diff --git a/repodir/huixiangdou/tests/test_bce.py b/repodir/huixiangdou/tests/test_bce.py new file mode 100644 index 00000000..7bb46568 --- /dev/null +++ b/repodir/huixiangdou/tests/test_bce.py @@ -0,0 +1,67 @@ +from BCEmbedding import EmbeddingModel +from sentence_transformers import SentenceTransformer + +# (Pdb) torch.__version__ +# '2.2.2+cu121' + +# (Pdb) import sentence_transformers +# (Pdb) sentence_transformers.__version__ +# '3.0.1' + +# list of sentences +sentences = ['大佬们,请问如何安装mmcv?\n'] +# sentences = [query.text] + +# init embedding model +model1 = SentenceTransformer(model_name_or_path='/data2/khj/bce-embedding-base_v1') +emb1 = model1.encode(sentences, normalize_embeddings=True) +print(emb1[0, 0:100]) +# [ 2.27064490e-02 6.52235700e-03 -6.60670735e-03 2.82378905e-02 +# -4.23520654e-02 3.50253433e-02 -3.75928059e-02 4.39181253e-02 +# 2.07435223e-03 7.78974686e-03 -1.62818842e-02 -7.12286914e-03 +# 7.55418167e-02 -1.32979536e-02 -4.25908640e-02 5.17920293e-02 +# -7.29086921e-02 1.92712229e-02 3.08401737e-04 2.78450572e-03 +# 8.07866603e-02 -2.95906160e-02 1.20137021e-01 2.15978641e-02 +# 5.42808920e-02 -1.40500171e-02 -2.30203178e-02 2.66241953e-02 +# -4.54075523e-02 7.94132706e-03 -7.61417393e-03 -2.65015271e-02 +# 3.59652378e-02 4.75489870e-02 -2.02227868e-02 -1.92782935e-02 +# -1.51286395e-02 -5.89573607e-02 -9.06837434e-02 4.33626957e-02 +# 3.53859067e-02 2.16610935e-02 -3.04897577e-02 3.32642421e-02 +# -1.26434257e-02 -3.17539996e-03 5.38454531e-03 2.33619660e-02 +# 2.76829731e-02 1.81363951e-02 5.62918335e-02 -4.35542352e-02 +# 1.82253513e-02 3.56561653e-02 6.30606860e-02 -2.95952503e-02 +# -7.61354575e-03 -1.15259029e-02 1.00938044e-02 -1.03235366e-02 +# -1.67877134e-02 -1.12033095e-02 -4.99769486e-02 -1.44557441e-02 +# -2.29974966e-02 -2.01950204e-02 -2.06282530e-02 -1.91477686e-02 +# 1.20543549e-02 2.68511102e-02 4.25181873e-02 3.57608870e-02 +# -1.33546710e-03 4.03872021e-02 -2.76293196e-02 3.64267617e-03 +# -4.17132629e-03 -1.73272435e-02 -2.44494136e-02 -1.61279812e-02 +# 7.76542164e-03 9.57545731e-03 2.58920640e-02 -2.26965714e-02 +# -7.54475041e-05 -1.75510086e-02 -1.38453580e-02 5.27697206e-02 +# -1.63486097e-02 1.69499014e-02 -1.11275297e-02 -1.07301818e-03 +# -7.06254854e-04 -9.24953539e-03 -9.37404204e-03 -2.96557285e-02 +# -1.49586275e-02 -5.31528378e-03 -3.70575525e-02 -1.99552961e-02] + + +# fp32 +model2 = EmbeddingModel(model_name_or_path='/data2/khj/bce-embedding-base_v1') +emb2 = model2.encode(sentences, normalize_to_unit=True) +print(emb2[0, 0:100]) | 0/1 [00:00 0: + sublists.append(inputs[num_sublists * groupsize:]) + + # 现在 sublists 包含了分割后的所有小列表 + gt = len(inputs) + dt = 0 + for lis in sublists: + dt += len(lis) + assert gt == dt + + return sublists + + +def calculate(chunk_size: int): + config_path = 'config.ini' + repo_dir = 'repodir' + work_dir_base = 'workdir' + work_dir = work_dir_base + str(chunk_size) + if not os.path.exists(work_dir): + os.makedirs(work_dir) + + # export PYTHONWARNINGS=ignore + text_labels = load_dataset() + + # 按不同 chunk_size 和 chunk_size,构建特征库 + # 读 input.jsonl 计算 F1 + fs_init = FeatureStore(embeddings=None, + config_path=config_path, + chunk_size=chunk_size, + analyze_reject=True, + rejecter_naive_splitter=True) + + # walk all files in repo dir + file_opr = FileOperation() + files = file_opr.scan_dir(repo_dir=repo_dir) + fs_init.preprocess(files=files, work_dir=work_dir) + docs = fs_init.build_dense(files=files, work_dir=work_dir) + del fs_init + + # docs = docs[0:20] + + col = init_milvus(col_name='test2', max_length_bytes=3 * chunk_size) + + subdocs = split_by_group(docs) + for idx, docs in enumerate(subdocs): + print('build step {}'.format(idx)) + texts = [] + sources = [] + reads = [] + for doc in docs: + texts.append(doc.page_content[0:chunk_size]) + sources.append(doc.metadata['source']) + reads.append(doc.metadata['read']) + + max_length = len(max(texts, key=lambda x: len(x))) + docs_emb = ef(texts) + entities = [texts, docs_emb['sparse'], docs_emb['dense']] + try: + col.insert(entities) + col.flush() + except Exception as e: + print(e) + + print('insert finished') + # start = 0.4 + # stop = 0.8 + # step = 0.05 + # throttles = [round(start + step * i, 4) for i in range(int((stop - start) / step) + 1)] + + start = 0.05 + stop = 0.5 + step = 0.05 + sparse_ratios = [] + sparse_ratios = [ + round(start + step * i, 4) + for i in range(int((stop - start) / step) + 1) + ] + + best_chunk_f1 = 0.0 + + dts = [] + gts = [] + predictions = [] + labels = [] + + for sparse_ratio in sparse_ratios: + for text_label in tqdm(text_labels): + + query_embeddings = ef([text_label[0]]) + # 4. search and inspect the result! + k = 1 # we want to get the top 2 docs closest to the query + # Prepare the search requests for both vector fields + sparse_req = AnnSearchRequest(query_embeddings['sparse'], + 'sparse_vector', + {'metric_type': 'IP'}, + limit=k) + dense_req = AnnSearchRequest(query_embeddings['dense'], + 'dense_vector', {'metric_type': 'IP'}, + limit=k) + + # Search topK docs based on dense and sparse vectors and rerank with RRF. + res = col.hybrid_search([sparse_req, dense_req], + rerank=WeightedRanker( + sparse_ratio, 1.0 - sparse_ratio), + limit=k, + output_fields=['text']) + + # Currently Milvus only support 1 query in the same hybrid search request, so + # we inspect res[0] directly. In future release Milvus will accept batch + # hybrid search queries in the same call. + res = res[0] + if len(res) > 0: + predictions.append(max(0.0, min(1.0, res[0].score))) + else: + predictions.append(0) + + if text_label[1]: + labels.append(1) + else: + labels.append(0) + + precision, recall, thresholds = precision_recall_curve( + labels, predictions) + f1 = 2 * precision * recall / (precision + recall) + logger.debug('sparse_ratio {} max f1 {} at {}, threshold {}'.format( + sparse_ratio, np.max(f1), np.argmax(f1), + thresholds[np.argmax(f1)])) + + +def main(): + args = parse_args() + best_f1 = 0.0 + best_chunk_size = -1 + + calculate(2048) + # pool = NestablePool(6) + # result = pool.map(calculate, range(128, 512, 32)) + # pool.close() + # pool.join() + # print(result) + + +if __name__ == '__main__': + main() diff --git a/repodir/huixiangdou/tests/test_clear_kimi_files.py b/repodir/huixiangdou/tests/test_clear_kimi_files.py new file mode 100644 index 00000000..7640470d --- /dev/null +++ b/repodir/huixiangdou/tests/test_clear_kimi_files.py @@ -0,0 +1,12 @@ +import os +import pdb + +from openai import OpenAI +from tqdm import tqdm + +client = OpenAI(api_key=os.getenv('MOONSHOT_API_KEY'), + base_url='https://api.moonshot.cn/v1') +file_list = client.files.list() +for file in tqdm(file_list.data): + client.files.delete(file_id=file.id) + print(file) diff --git a/repodir/huixiangdou/tests/test_dataclass.py b/repodir/huixiangdou/tests/test_dataclass.py new file mode 100644 index 00000000..b63b4ac1 --- /dev/null +++ b/repodir/huixiangdou/tests/test_dataclass.py @@ -0,0 +1,14 @@ +import pdb +from enum import Enum, unique + + +@unique +class KGType(Enum): + MARKDOWN = 'markdown' + CHUNK = 'chunk' + KEYWORD = 'keyword' + IMAGE = 'image' + + +x = KGType.IMAGE +print(x) diff --git a/repodir/huixiangdou/tests/test_deepseek.py b/repodir/huixiangdou/tests/test_deepseek.py new file mode 100644 index 00000000..8e7b3182 --- /dev/null +++ b/repodir/huixiangdou/tests/test_deepseek.py @@ -0,0 +1,27 @@ +# python3 +from openai import OpenAI + +client = OpenAI(api_key='sk-f58e45ee054743f898f732b09dbcaa7c', + base_url='https://api.deepseek.com/v1') +queries = [ + '已知 ncnn 中 cnn 是卷积神经网络,n 是 ncnn 的作者 nihui。所以 ncnn 的全称是?', + '"请问如何安装 mmdeploy ?"\n请仔细阅读以上内容,判断句子是否是个有主题的疑问句,结果用 0~10 表示。直接提供得分不要解释。\n判断标准:有主语谓语宾语并且是疑问句得 10 分;缺少主谓宾扣分;陈述句直接得 0 分;不是疑问句直接得 0 分。直接提供得分不要解释。', + '"豆哥少水点键证群"\n请仔细阅读以上内容,判断句子是否是个有主题的疑问句,结果用 0~10 表示。直接提供得分不要解释。\n判断标准:有主语谓语宾语并且是疑问句得 10 分;缺少主谓宾扣分;陈述句直接得 0 分;不是疑问句直接得 0 分。直接提供得分不要解释。' +] + +for query in queries: + response = client.chat.completions.create( + model='deepseek-chat', + messages=[ + { + 'role': 'system', + 'content': 'You are a helpful assistant' + }, + { + 'role': 'user', + 'content': query + }, + ], + temperature=0.1) + + print(response.choices[0].message.content) diff --git a/repodir/huixiangdou/tests/test_get_issue_comment_pipeline.py b/repodir/huixiangdou/tests/test_get_issue_comment_pipeline.py new file mode 100644 index 00000000..bf6a2b76 --- /dev/null +++ b/repodir/huixiangdou/tests/test_get_issue_comment_pipeline.py @@ -0,0 +1,177 @@ +import json +import math +import os +import re +from datetime import datetime + +import loguru +import requests + + +def get_issue_count(owner, name): + headers = { + 'Authorization': TOKEN, + } + url = f'https://api.github.com/repos/{owner}/{name}/issues' + response = requests.get(url=url, headers=headers) + if response.status_code == 200: + issues = response.json() + count = issues[0]['number'] + else: + loguru.logger.error(f'Error fetching issues: {response.status_code}') + loguru.logger.error(response.text) + if 'limit' in response.text: + loguru.logger.error('受到 github 限制,自动结束') + exit() + count = 0 + return count + + +def get_issues_list(owner, name, issue_count): + headers = { + 'Authorization': TOKEN, + } + GITHUB_API_URL = f'https://api.github.com/repos/{owner}/{name}/issues' + pages = math.ceil(issue_count / 100) + 1 + loguru.logger.info(f'all_pages:{pages}') + issues_list = [] + for page in range(pages): + response = requests.get(GITHUB_API_URL, + headers=headers, + params={ + 'state': 'all', + 'per_page': 100, + 'page': {page} + }) + if response.status_code == 200: + issues = response.json() + for issue in issues: + if 'issues' in issue['html_url'] and issue['state'] == 'closed': + issues_list.append({ + 'number': issue['number'], + 'title': issue['title'], + 'html_url': issue['html_url'], + 'body': issue['body'], + 'closed_at': issue['closed_at'] + }) + else: + loguru.logger.error( + f'Error fetching issues: {response.status_code}') + loguru.logger.error(response.text) + return issues_list + + +def get_all_comments(owner, name, issue_number): + headers = { + 'Authorization': TOKEN, + } + issue_comments_url = f'https://api.github.com/repos/{owner}/{name}/issues/{issue_number}/comments' + comments = [] + result_comments = [] + response = requests.get(issue_comments_url, headers=headers) + if response.status_code != 200: + loguru.logger.error( + f'Failed to retrieve comments: {response.status_code} issue_number {issue_number}' + ) + loguru.logger.error(f'{response.text}') + if 'limit' in response.text: + loguru.logger.error('受到 github 限制,自动结束') + exit() + return [] + page_comments = response.json() + if not page_comments: + return [] + comments.extend(page_comments) + for i, sub_comment in enumerate(comments): + comment = { + 'id': i, + 'user': sub_comment['user']['login'], + 'body': sub_comment['body'] + } + # 删除 comment 引用其他部分,节省空间 + if '> ' in comment['body']: + quoted_regex = re.compile(r'^>.*(?:\r?\n|\r)?', re.MULTILINE) + comment['body'] = re.sub(quoted_regex, '', comment['body']).strip() + result_comments.append(comment) + return result_comments + + +def write_all_issues(owener, name, issues_list, max_issue_number): + # issue number 从大到小获取 comment + # 单次获取comment可能会有问题,太多了会受到github限制,可能需要多次构建 + for j in range(len(issues_list)): + issue_number = issues_list[j]['number'] + if issue_number >= max_issue_number: + continue + issue_body = issues_list[j]['body'] + issue_comments = get_all_comments(owener, name, issue_number) + # 保存 + issue_title = issues_list[j]['title'] + forbidden_chars_pattern = r'[<>:"/\\|?*]' # for windows + issue_title = re.sub(forbidden_chars_pattern, ' ', issue_title) + md_basename = f'{issue_number}_{issue_title}.md' + md_question = issue_body + md_answer = ''.join([ + f"#### 第{i['id']}条回复来自{i['user']} \n {i['body']} \n" + for i in issue_comments + ]) + md_contents = f"""## quesion\n + =========== question =========== + {issue_title}\n + {md_question} + =========== question ===========\n + ## answer\n + =========== answer =========== + {md_answer} + =========== answer ===========\n + """ + with open(os.path.join(EXPORT_DIR, md_basename), + mode='w', + encoding='utf-8') as file: + file.write(md_contents) + + +if __name__ == '__main__': + + # config + TOKEN = '' + OWENER = 'InternLM' + NAME = 'lmdeploy' + # 每次构建都要把之前构建好的跳过,这里默认一个很大的值 + MAX_ISSUE_NUMBER = 2000 + EXPORT_DIR = './issues' + SAVE_MONTH = 3 # 保存几个月内的 issue + + # run + if not os.path.exists(EXPORT_DIR): + os.mkdir(EXPORT_DIR) + issues_json_path = os.path.join(EXPORT_DIR, 'git_issues_list.json') + if not os.path.exists(issues_json_path): + issue_count = get_issue_count(OWENER, NAME) + issues_list = get_issues_list(OWENER, NAME, issue_count) + # clear issues + current_date = datetime.now() + for i in issues_list: + if i['body'] is None: + issues_list.remove(i) + continue + issue_date = datetime.strptime(i['closed_at'][0]['closed_at'], + '%Y-%m-%dT%H:%M:%SZ') + diff = abs((current_date.year - issue_date.year) * 12 + + (current_date.month - issue_date.month)) + if diff > SAVE_MONTH: + issues_list.remove(i) + + loguru.logger.info(f'create git_issues! {issues_json_path}') + with open(issues_json_path, 'w') as file: + json.dump(issues_list, file) + else: + loguru.logger.info(f'we already have git_issues! {issues_json_path}') + with open(issues_json_path, 'r') as file: + git_all_issues = json.load(file) + print(git_all_issues) + + write_all_issues(OWENER, + NAME, + git_all_issues, + max_issue_number=MAX_ISSUE_NUMBER) diff --git a/repodir/huixiangdou/tests/test_hf_import_accelerate.py b/repodir/huixiangdou/tests/test_hf_import_accelerate.py new file mode 100644 index 00000000..c370c3ba --- /dev/null +++ b/repodir/huixiangdou/tests/test_hf_import_accelerate.py @@ -0,0 +1,8 @@ +from accelerate import (dispatch_model, infer_auto_device_map, + init_empty_weights) +from accelerate.hooks import add_hook_to_module +from accelerate.utils import (check_tied_parameters_on_same_device, + find_tied_parameters, get_balanced_memory, + get_max_memory, load_offloaded_weights, + offload_weight, save_offload_index, + set_module_tensor_to_device) diff --git a/repodir/huixiangdou/tests/test_intention_prompt.py b/repodir/huixiangdou/tests/test_intention_prompt.py new file mode 100644 index 00000000..0c81f0f1 --- /dev/null +++ b/repodir/huixiangdou/tests/test_intention_prompt.py @@ -0,0 +1,39 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import json + +import torch +from transformers.generation import GenerationConfig + +# Note: The default behavior now has injection attack prevention off. +DIR = '/internlm/ampere_7b_v1_7_0' +from transformers import AutoModelForCausalLM, AutoTokenizer + +tokenizer = AutoTokenizer.from_pretrained(DIR, trust_remote_code=True) +model = AutoModelForCausalLM.from_pretrained(DIR, + trust_remote_code=True, + device_map='auto').eval() + + +def task1_intention(): + """Test prompt.""" + ret = [] + with open('data.json', encoding='utf8') as f: + items = json.load(f) + for idx, item in enumerate(items): + question = item['question'] + + prompt = '“{}”\n请仔细阅读以上内容,判断句子是否是个有主题的疑问句,结果用 1~10 表示。直接提供得分不要解释。\n判断标准:有主语谓语宾语并且是疑问句得 10 分;缺少主谓宾扣分;陈述句直接得 0 分;不是疑问句直接得 0 分。直接提供得分不要解释。'.format( + question) + answer, _ = model.chat(tokenizer, prompt, history=[], top_k=1) + print((answer, prompt)) + + ret.append({'question': prompt, 'answer': answer}) + + with open('task1_intention_internlm_prompt.json', 'w', + encoding='utf8') as f: + json.dump(list(ret), f, ensure_ascii=False, indent=2) + print('{}/{}'.format(idx, len(items))) + + +if __name__ == '__main__': + task1_intention() diff --git a/repodir/huixiangdou/tests/test_internlm2.py b/repodir/huixiangdou/tests/test_internlm2.py new file mode 100644 index 00000000..f06170f5 --- /dev/null +++ b/repodir/huixiangdou/tests/test_internlm2.py @@ -0,0 +1,25 @@ +import torch +from transformers import AutoModelForCausalLM, AutoTokenizer +import asyncio + +# wrap to async generator +async def chat_stream(): + model_path = "/data2/khj/internlm2_5-7b-chat" + model = AutoModelForCausalLM.from_pretrained(model_path, torch_dtype=torch.float16, trust_remote_code=True).cuda() + tokenizer = AutoTokenizer.from_pretrained(model_path, trust_remote_code=True) + + model = model.eval() + length = 0 + for response, history in model.stream_chat(tokenizer, "Hello", history=[]): + part = response[length:] + length = len(response) + yield part + yield '\n' + +# coroutine +async def main(): + async for part in chat_stream(): + print(part, flush=True, end="") + +loop = asyncio.get_event_loop() +loop.run_until_complete(main()) \ No newline at end of file diff --git a/repodir/huixiangdou/tests/test_kimi.py b/repodir/huixiangdou/tests/test_kimi.py new file mode 100644 index 00000000..492901a1 --- /dev/null +++ b/repodir/huixiangdou/tests/test_kimi.py @@ -0,0 +1,48 @@ +import os + +from openai import OpenAI + +client = OpenAI( + api_key=os.getenv('MOONSHOT_API_KEY'), + base_url='https://api.moonshot.cn/v1', +) + +prompt = '“huixiangdou 是什么?”\n请仔细阅读以上内容,判断句子是否是个有主题的疑问句,结果用 0~10 表示。直接提供得分不要解释。\n判断标准:有主语谓语宾语并且是疑问句得 10 分;缺少主谓宾扣分;陈述句直接得 0 分;不是疑问句直接得 0 分。直接提供得分不要解释。' + + +def generate(): + """Test generate.""" + messages = [ + { + 'role': 'system', + 'content': '你是一个语文专家,擅长对句子的结构进行分析' + # '你是 Kimi,由 Moonshot AI 提供的人工智能助手,你更擅长中文和英文的对话。你会为用户提供安全,有帮助,准确的回答。 + # 同时,你会拒绝一些涉及恐怖主义,种族歧视,黄色暴力等问题的回答。Moonshot AI 为专有名词,不可翻译成其他语言。' + }, + { + 'role': 'user', + 'content': prompt + } + ] + + whole_input = str(messages) + print(whole_input) + # print('input_length {}'.format(len(whole_input))) + + try: + completion = client.chat.completions.create(model='moonshot-v1-8k', + messages=messages, + temperature=0.1, + n=10) + except Exception as e: + return prompt, str(e) + + results = [] + for choice in completion.choices: + results.append(choice.message.content) + + return prompt, results + + +if __name__ == '__main__': + print(generate()) diff --git a/repodir/huixiangdou/tests/test_kimi_cr.py b/repodir/huixiangdou/tests/test_kimi_cr.py new file mode 100644 index 00000000..f45e85b6 --- /dev/null +++ b/repodir/huixiangdou/tests/test_kimi_cr.py @@ -0,0 +1,119 @@ +###### 测试不需要消解的情况 +prompt1 = """ +请完成群聊场景中的指代消解任务。 + +群描述:这是一个 LLM 课程助教群,助教会把学员的疑问转发到本群。 + +以下是本群的历史对话,包含多个人的发言: +路人甲:“Error:Out of memory - process killed” +路人乙:“待会儿试试” +路人丙:“完成和优秀有什么区别?” + +输入内容: +“请问今天天气如何?”,指代消解后的结果是? + +输出格式: +直接告诉我消解后的文本,如果不需要消解就返回原本的输入,不要解释原因。 +""" +# “请问今天天气如何?” +# “请问今天天气如何?” 在这个群聊场景中,由于没有上下文信息表明“今天天气如何”与之前的对话有关,因此不需要进行指代消解,直接返回原本的输入。 + +# 增加返回 +prompt2 = """ +请完成群聊场景中的指代消解任务。 + +群描述:这是一个 LLM 课程助教群,助教会把学员的疑问转发到本群。 + +以下是本群的历史对话,包含多个人的发言: +路人甲:“Error:Out of memory - process killed” +路人乙:“待会儿试试” +路人丙:“完成和优秀有什么区别?” + +输入内容: +“请问今天天气如何?”,指代消解后的结果是? + +输出格式: +直接告诉我消解后的文本,如果不需要消解就返回空白,不要解释原因。 +""" +# 空白 +# 返回空白。 + +####### 测试需要消解的情况 +prompt3 = """ +请完成群聊场景中的指代消解任务。 + +群描述:这是一个 LLM 课程助教群,助教会把学员的疑问转发到本群。 + +以下是本群的历史对话,包含多个人的发言: +路人甲:“Error:Out of memory - process killed” +路人乙:“待会儿试试” +路人丙:“完成和优秀有什么区别?” + +输入内容: +“给的证书不一样”,指代消解后的结果是?如果不需要消解就返回空白。 +""" +# 完成和优秀的区别 + +prompt4 = """ +请完成群聊场景中的指代消解任务。 + +群描述:这是一个 LLM 课程助教群,助教会把学员的疑问转发到本群。 + +以下是本群的历史对话,包含多个人的发言: +A:“Error:Out of memory - process killed” +B:“待会儿试试” +C:“完成和优秀有什么区别?” + +B:“给的证书不一样”,指代消解后的结果是?如果不需要消解就返回空白。直接返回消解结果不要解释 +""" +# 完成和优秀的区别 + +prompt5 = """ +请完成群聊场景中的指代消解任务。 + +群描述:这是一个 LLM 课程助教群,助教会把学员的疑问转发到本群。 + +以下是本群的历史对话,包含多个人的发言: +[{ + "username": "A", "content":"Error:Out of memory - process killed" +}, +{ + "username": "B", "content":"待会儿试试" +}, +{ + "username": "C", "content":"学员的成绩评价完成和优秀有什么区别?" +}, +{ + "username": "D", "content":"平菇炖肉好吃" +}] + +输入内容: +{ + "username": "D", "content":"给的证书不一样" +} +content 指代消解后的结果是?如果不需要消解就返回空白。直接返回消解后的完整文本不要解释 +""" + +prompt5 = """ +请完成群聊场景中的指代消解任务。 + +群描述:这是一个 LLM 课程助教群,助教会把学员的疑问转发到本群。 + +以下是本群的历史对话,包含多个人的发言: +路人甲:“Error:Out of memory - process killed” +路人乙:“待会儿试试” +路人丙:“完成和优秀有什么区别?” +路人丁:“平菇适合炖肉” +路人甲:“明天去野餐去吧” + +输入内容: +“给的证书不一样”,指代消解后的结果是?直接告诉我消解后的文本,如果不需要消解就返回原本的输入,不要解释原因。 +""" + +# 原本的输入内容“给的证书不一样”在这个对话上下文中并没有明确的指代对象,因此不需要进行指代消解,返回原本的输入: +# “给的证书不一样” + +######## conclusion +# 何时触发消解,NLP 预筛还是每句都过 +# resp 需要 unquote + 文本近似 / concat +# kimi 指令跟随能力很好,对格式并不敏感,简单的 "A:内容\n" 即可 diff --git a/repodir/huixiangdou/tests/test_kimi_passkey.py b/repodir/huixiangdou/tests/test_kimi_passkey.py new file mode 100644 index 00000000..6cf647c2 --- /dev/null +++ b/repodir/huixiangdou/tests/test_kimi_passkey.py @@ -0,0 +1,107 @@ +import argparse +import os + +from openai import OpenAI + +client = OpenAI( + api_key=os.getenv('MOONSHOT_API_KEY'), + base_url='https://api.moonshot.cn/v1', +) + + +def parse_config(): + parser = argparse.ArgumentParser(description='arg parser') + parser.add_argument('--max_tokens', + type=int, + default=782000, + help='maximum token length for evaluation') + parser.add_argument('--num_tests', + type=int, + default=1, + help='number of repeat testing for each length') + + args = parser.parse_args() + return args + + +def generate(prompt: str): + """Test generate.""" + messages = [{ + 'role': + 'system', + 'content': + '你是 Kimi,由 Moonshot AI 提供的人工智能助手,你更擅长中文和英文的对话。你会为用户提供安全,有帮助,准确的回答。同时,你会拒绝一些涉及恐怖主义,种族歧视,黄色暴力等问题的回答。Moonshot AI 为专有名词,不可翻译成其他语言。' + }, { + 'role': 'user', + 'content': prompt + }] + + # whole_input = str(messages) + # print('input_length {}'.format(len(whole_input))) + + try: + completion = client.chat.completions.create( + model='moonshot-v1-128k', + messages=messages, + temperature=0.3, + ) + except Exception as e: + return prompt, str(e) + # print(completion.choices) + return prompt, completion.choices[0].message.content + + +# copy from https://github.com/dvlab-research/LongLoRA/blob/main/passkey_retrivial.py +def generate_prompt_landmark(n_garbage=60000, seed=666): + """Generates a text file and inserts an passkey at a random position.""" + from numpy import random + rnd_state = random.get_state() + random.seed(seed) + n_garbage_prefix = random.randint(0, n_garbage) + n_garbage_suffix = n_garbage - n_garbage_prefix + + task_description = 'There is an important info hidden inside a lot of irrelevant text. Find it and memorize them. I will quiz you about the important information there.' + garbage = 'The grass is green. The sky is blue. The sun is yellow. Here we go. There and back again.' + garbage_inf = ' '.join([garbage] * 384000) + assert len(garbage_inf) >= n_garbage + garbage_prefix = garbage_inf[:n_garbage_prefix] + garbage_suffix = garbage_inf[:n_garbage_suffix] + pass_key = random.randint(1, 192000) + information_line = f'The pass key is {pass_key}. Remember it. {pass_key} is the pass key.' + final_question = 'What is the pass key? The pass key is' + lines = [ + task_description, + garbage_prefix, + information_line, + garbage_suffix, + final_question, + ] + random.set_state(rnd_state) + return '\n'.join(lines), str(pass_key) + + +def main(args): + n_garbage = args.max_tokens - 1000 + passed_tests = 0 + for j in range(args.num_tests): + prompt, pass_key = generate_prompt_landmark(n_garbage=n_garbage, + seed=5120 + j) + + try: + response = generate(prompt='hello') + print(response) + response = generate(prompt=prompt) + except Exception as e: + print(e) + print('result: ', response, pass_key) + + if pass_key in response.content: + passed_tests += 1 + + precision = passed_tests / args.num_tests + print('precision {} @ {}'.format(precision, args.max_tokens)) + + +if __name__ == '__main__': + args = parse_config() + main(args) diff --git a/repodir/huixiangdou/tests/test_llm_client.py b/repodir/huixiangdou/tests/test_llm_client.py new file mode 100644 index 00000000..bd0717cb --- /dev/null +++ b/repodir/huixiangdou/tests/test_llm_client.py @@ -0,0 +1,68 @@ +import aiohttp +import asyncio +import json +import pdb + +async def post_event_source(url, prompt, history, backend): + # 准备POST请求的headers和数据 + headers = { + 'Content-Type': 'application/json' + } + data = { + 'prompt': prompt, + 'history': history, + 'backend': backend + } + + # 发送POST请求 + async with aiohttp.ClientSession() as session: + async with session.post(url, headers=headers, data=json.dumps(data)) as response: + # 确保请求成功 + if response.status == 200: + # 处理SSE响应 + async for chunk in response.content.iter_any(): + chunk_str = chunk.decode().strip() + mines = chunk_str.split('\r\n\r\n') + + for mime_str in mines: + pos = mime_str.find('data: ') + len('data: ') + content = mime_str[pos:] + print(content, end='', flush=True) + else: + print(f"Failed to connect: {response.status}") + +async def post_single(url, prompt, history, backend): + # 准备POST请求的headers和数据 + headers = { + 'Content-Type': 'application/json' + } + data = { + 'prompt': prompt, + 'history': history, + 'backend': backend + } + + # 发送POST请求 + async with aiohttp.ClientSession() as session: + async with session.post(url, headers=headers, data=json.dumps(data)) as response: + # 确保请求成功 + if response.status == 200: + async for chunk in response.content.iter_any(): + chunk_str = chunk.decode().strip() + print(chunk_str, end='', flush=True) + else: + print(f"Failed to connect: {response.status}") + +# 使用示例 +async def main(): + prompt = 'What is the weather like today?' + history = [] + backend = 'local' + + # await post_event_source('http://10.1.52.22:8888/stream_chat', prompt, history, backend) + await post_single('http://10.1.52.22:8888/inference', prompt, history, backend) + +# 运行异步main函数 +if __name__ == '__main__': + loop = asyncio.get_event_loop() + loop.run_until_complete(main()) \ No newline at end of file diff --git a/repodir/huixiangdou/tests/test_m3.py b/repodir/huixiangdou/tests/test_m3.py new file mode 100644 index 00000000..1fa5fec5 --- /dev/null +++ b/repodir/huixiangdou/tests/test_m3.py @@ -0,0 +1,19 @@ +from FlagEmbedding import BGEM3FlagModel + +model = BGEM3FlagModel( + '/data2/khj/bge-m3', use_fp16=True +) # Setting use_fp16 to True speeds up computation with a slight performance degradation + +sentences_1 = ['What is BGE M3?', 'Defination of BM25'] +sentences_2 = [ + 'BGE M3 is an embedding model supporting dense retrieval, lexical matching and multi-vector interaction.', + 'BM25 is a bag-of-words retrieval function that ranks a set of documents based on the query terms appearing in each document' +] + +import pdb + +embeddings_1 = model.encode(sentences_1, max_length=512)['dense_vecs'] +embeddings_2 = model.encode(sentences_2)['dense_vecs'] +similarity = embeddings_1 @ embeddings_2.T +print(similarity) +# [[0.6265, 0.3477], [0.3499, 0.678 ]] diff --git a/repodir/huixiangdou/tests/test_milvus_hybrid_retrieval.py b/repodir/huixiangdou/tests/test_milvus_hybrid_retrieval.py new file mode 100644 index 00000000..58c1e9e9 --- /dev/null +++ b/repodir/huixiangdou/tests/test_milvus_hybrid_retrieval.py @@ -0,0 +1,156 @@ +# A demo showing hybrid semantic search with dense and sparse vectors using Milvus. +# +# You can optionally choose to use the BGE-M3 model to embed the text as dense +# and sparse vectors, or simply use random generated vectors as an example. +# +# You can also use the BGE CrossEncoder model to rerank the search results. +# +# Note that the sparse vector search feature is only available in Milvus 2.4.0 or +# higher version. Make sure you follow https://milvus.io/docs/install_standalone-docker.md +# to set up the latest version of Milvus in your local environment. + +# To connect to Milvus server, you need the python client library called pymilvus. +# To use BGE-M3 model, you need to install the optional `model` module in pymilvus. +# You can get them by simply running the following commands: +# +# pip install pymilvus +# pip install pymilvus[model] + +# If true, use BGE-M3 model to generate dense and sparse vectors. +# If false, use random numbers to compose dense and sparse vectors. +use_bge_m3 = True +# If true, the search result will be reranked using BGE CrossEncoder model. +use_reranker = False + +# The overall steps are as follows: +# 1. embed the text as dense and sparse vectors +# 2. setup a Milvus collection to store the dense and sparse vectors +# 3. insert the data to Milvus +# 4. search and inspect the result! +import random +import string + +import numpy as np +from pymilvus import (AnnSearchRequest, Collection, CollectionSchema, DataType, + FieldSchema, RRFRanker, connections, utility) +from sklearn.metrics import precision_recall_curve + +# 1. prepare a small corpus to search +docs = [ + 'Artificial intelligence was founded as an academic discipline in 1956.', + 'Alan Turing was the first person to conduct substantial research in AI.', + 'Born in Maida Vale, London, Turing was raised in southern England.', +] +# add some randomly generated texts +docs.extend([ + ' '.join(''.join( + random.choice(string.ascii_lowercase) + for _ in range(random.randint(1, 8))) for _ in range(10)) + for _ in range(1000) +]) +query = 'Who started AI research?' + +# BGE-M3 model can embed texts as dense and sparse vectors. +# It is included in the optional `model` module in pymilvus, to install it, +# simply run "pip install pymilvus[model]". +from pymilvus.model.hybrid import BGEM3EmbeddingFunction + +ef = BGEM3EmbeddingFunction(model_name='/data2/khj/bge-m3', + use_fp16=False, + device='cuda') +dense_dim = ef.dim['dense'] + +docs_embeddings = ef(docs) +query_embeddings = ef([query]) + +# 2. setup Milvus collection and index +connections.connect('default', host='localhost', port='19530') + +# Specify the data schema for the new Collection. +fields = [ + # Use auto generated id as primary key + FieldSchema(name='pk', + dtype=DataType.VARCHAR, + is_primary=True, + auto_id=True, + max_length=100), + # Store the original text to retrieve based on semantically distance + FieldSchema(name='text', dtype=DataType.VARCHAR, max_length=512), + # Milvus now supports both sparse and dense vectors, we can store each in + # a separate field to conduct hybrid search on both vectors. + FieldSchema(name='sparse_vector', dtype=DataType.SPARSE_FLOAT_VECTOR), + FieldSchema(name='dense_vector', + dtype=DataType.FLOAT_VECTOR, + dim=dense_dim), +] +schema = CollectionSchema(fields, '') +col_name = 'hybrid_demo1' +# Now we can create the new collection with above name and schema. +col = Collection(col_name, schema, consistency_level='Strong') + +# We need to create indices for the vector fields. The indices will be loaded +# into memory for efficient search. +sparse_index = {'index_type': 'SPARSE_INVERTED_INDEX', 'metric_type': 'IP'} +col.create_index('sparse_vector', sparse_index) +dense_index = {'index_type': 'FLAT', 'metric_type': 'IP'} +col.create_index('dense_vector', dense_index) +col.load() + +# 3. insert text and sparse/dense vector representations into the collection +entities = [docs, docs_embeddings['sparse'], docs_embeddings['dense']] +col.insert(entities) +col.flush() + +# 4. search and inspect the result! +k = 10 # we want to get the top 2 docs closest to the query + +# Prepare the search requests for both vector fields +sparse_search_params = {'metric_type': 'IP'} +sparse_req = AnnSearchRequest(query_embeddings['sparse'], + 'sparse_vector', + sparse_search_params, + limit=k) +dense_search_params = {'metric_type': 'IP'} +dense_req = AnnSearchRequest(query_embeddings['dense'], + 'dense_vector', + dense_search_params, + limit=k) + +# Search topK docs based on dense and sparse vectors and rerank with RRF. +res = col.hybrid_search([sparse_req, dense_req], + rerank=RRFRanker(), + limit=k, + output_fields=['text']) + +# Currently Milvus only support 1 query in the same hybrid search request, so +# we inspect res[0] directly. In future release Milvus will accept batch +# hybrid search queries in the same call. +res = res[0] + +if use_reranker: + result_texts = [hit.fields['text'] for hit in res] + from pymilvus.model.reranker import BGERerankFunction + bge_rf = BGERerankFunction(device='cpu') + # rerank the results using BGE CrossEncoder model + results = bge_rf(query, result_texts, top_k=2) + for hit in results: + print(f'text: {hit.text} distance {hit.score}') +else: + for hit in res: + print(f'text: {hit.fields["text"]} distance {hit.distance}') + +# If you used both BGE-M3 and the reranker, you should see the following: +# text: Alan Turing was the first person to conduct substantial research in AI. distance 0.9306981017573297 +# text: Artificial intelligence was founded as an academic discipline in 1956. distance 0.03217001154515051 +# +# If you used only BGE-M3, you should see the following: +# text: Alan Turing was the first person to conduct substantial research in AI. distance 0.032786883413791656 +# text: Artificial intelligence was founded as an academic discipline in 1956. distance 0.016129031777381897 + +# In this simple example the reranker yields the same result as the embedding based hybrid search, but in more complex +# scenarios the reranker can provide more accurate results. + +# If you used random vectors, the result will be different each time you run the script. + +# Drop the collection to clean up the data. +utility.drop_collection(col_name) diff --git a/repodir/huixiangdou/tests/test_neo4j.py b/repodir/huixiangdou/tests/test_neo4j.py new file mode 100644 index 00000000..9df9a006 --- /dev/null +++ b/repodir/huixiangdou/tests/test_neo4j.py @@ -0,0 +1,28 @@ +import nxneo4j as nx +from neo4j import GraphDatabase + +# Neo4j Desktop 版 +# 1. 关掉 auth +# 2. server.default_listen_address=0.0.0.0 +# 浏览器打开 http://10.1.52.85:7474/browser/,无密码模式应该能登录 + +# 配置 Neo4j 连接参数 +uri = 'bolt://10.1.52.85:7687' # 默认的 bolt 协议地址和端口 +user = 'neo4j' # Neo4j 用户名 +password = 'neo4j' # Neo4j 密码 + +# 创建驱动实例 +driver = GraphDatabase.driver(uri, auth=(user, password)) + +G = nx.Graph(driver) +G.delete_all() + +#Add a node +G.add_node('Yusuf') +#Add node with features +G.add_node('Nurgul', gender='F') +#Add multiple properties at once +G.add_node('Betul', age=4, gender='F') +#Check nodes +for node in G.nodes(): #Unlike networkX, nxneo4j returns a generator + print(node) diff --git a/repodir/huixiangdou/tests/test_openai.py b/repodir/huixiangdou/tests/test_openai.py new file mode 100644 index 00000000..32e86f11 --- /dev/null +++ b/repodir/huixiangdou/tests/test_openai.py @@ -0,0 +1,64 @@ +import openai +from openai import OpenAI + + +def call_openai(model_name, prompt, history): + + messages = [{ + 'role': 'system', + 'content': 'You are a helpful assistant.' # noqa E501 + }] + for item in history: + messages.append({'role': 'user', 'content': item[0]}) + messages.append({'role': 'system', 'content': item[1]}) + messages.append({'role': 'user', 'content': prompt}) + + client = OpenAI( + api_key='EMPTY', + base_url='https://10.140.24.142:29500/v1', + ) + + completion = client.chat.completions.create(model=model_name, + messages=messages) + return completion.choices[0].message.content + + +def call2(): + from openai import OpenAI + + # Set OpenAI's API key and API base to use vLLM's API server. + openai_api_key = 'EMPTY' + openai_api_base = 'http://10.140.24.142:29500/v1' + + client = OpenAI( + api_key=openai_api_key, + base_url=openai_api_base, + ) + + chat_response = client.chat.completions.create( + model='../models/Qwen1.5-14B-Chat/', + messages=[ + { + 'role': 'system', + 'content': 'You are a helpful assistant.' + }, + { + 'role': 'user', + 'content': 'Tell me a joke.' + }, + ]) + print('Chat response:', chat_response) + + +call2() +# call_openai("../models/Qwen1.5-14B-Chat/", '如何安装 mmdeploy', []) + +# curl http://10.140.24.142:29500/v1/chat/completions \ +# -H "Content-Type: application/json" \ +# -d '{ +# "model": "../models/Qwen1.5-14B-Chat/", +# "messages": [ +# {"role": "system", "content": "You are a helpful assistant."}, +# {"role": "user", "content": "Tell me something about large language models."} +# ] +# }' diff --git a/repodir/huixiangdou/tests/test_optimum_st.py b/repodir/huixiangdou/tests/test_optimum_st.py new file mode 100644 index 00000000..63aca352 --- /dev/null +++ b/repodir/huixiangdou/tests/test_optimum_st.py @@ -0,0 +1,122 @@ +from pathlib import Path + +import torch +import torch.nn.functional as F +from optimum.onnxruntime import ORTModelForFeatureExtraction +# from optimum.onnxruntime.configuration import AutoQuantizationConfig +from transformers import AutoTokenizer, Pipeline + + +# copied from the model card +def mean_pooling(model_output, attention_mask): + token_embeddings = model_output[ + 0] #First element of model_output contains all token embeddings + input_mask_expanded = attention_mask.unsqueeze(-1).expand( + token_embeddings.size()).float() + return torch.sum(token_embeddings * input_mask_expanded, 1) / torch.clamp( + input_mask_expanded.sum(1), min=1e-9) + + +# optimum-cli export onnx -m /workspace/models/text2vec-large-chinese/ --task feature-extraction --fp16 --device cuda --monolith --trust-remote-code o4-opt-onnx/ --framework pt --optimize O2 +class SentenceEmbeddingPipeline(Pipeline): + + def _sanitize_parameters(self, **kwargs): + # we don't have any hyperameters to sanitize + preprocess_kwargs = {} + return preprocess_kwargs, {}, {} + + def preprocess(self, inputs): + encoded_inputs = self.tokenizer(inputs, + padding=True, + truncation=True, + return_tensors='pt') + return encoded_inputs + + def _forward(self, model_inputs): + outputs = self.model(**model_inputs) + return { + 'outputs': outputs, + 'attention_mask': model_inputs['attention_mask'] + } + + def postprocess(self, model_outputs): + # Perform pooling + sentence_embeddings = mean_pooling(model_outputs['outputs'], + model_outputs['attention_mask']) + # Normalize embeddings + sentence_embeddings = F.normalize(sentence_embeddings, p=2, dim=1) + return sentence_embeddings + + +def export_onnx(): + # https://github.com/philschmid/optimum-transformers-optimizations/blob/master/sentence-transformers.ipynb + model_id = '/workspace/models/text2vec-large-chinese' + onnx_path = Path('onnx') + + # load vanilla transformers and convert to onnx + model = ORTModelForFeatureExtraction.from_pretrained( + model_id, from_transformers=True) + tokenizer = AutoTokenizer.from_pretrained(model_id) + + # save onnx checkpoint and tokenizer + model.save_pretrained(onnx_path) + tokenizer.save_pretrained(onnx_path) + # init pipeline + + +def profiling(): + onnx_path = 'o4-opt-onnx' + model = ORTModelForFeatureExtraction.from_pretrained( + onnx_path, file_name='model.onnx') + model.to('cuda') + + tokenizer = AutoTokenizer.from_pretrained(onnx_path) + vanilla_emb = SentenceEmbeddingPipeline(model=model, tokenizer=tokenizer) + pred = vanilla_emb( + 'Could you assist me in finding my lost card? vanilla_emb') + print(pred[0][:5]) + + onnx_path = 'onnx' + model = ORTModelForFeatureExtraction.from_pretrained( + onnx_path, file_name='model.onnx') + model.to('cuda') + tokenizer = AutoTokenizer.from_pretrained(onnx_path) + q8_emb = SentenceEmbeddingPipeline(model=model, tokenizer=tokenizer) + pred = q8_emb('Could you assist me in finding my lost card? q8_emb') + print(pred[0][:5]) + + from time import perf_counter + + import numpy as np + + payload = 'Hello, my name is Philipp and I live in Nuremberg, Germany. Currently I am working as a Technical Lead at Hugging Face to democratize artificial intelligence through open source and open science. In the past I designed and implemented cloud-native machine learning architectures for fin-tech and insurance companies. I found my passion for cloud concepts and machine learning 5 years ago. Since then I never stopped learning. Currently, I am focusing myself in the area NLP and how to leverage models like BERT, Roberta, T5, ViT, and GPT2 to generate business value. I cannot wait to see what is next for me' + print(f'Payload sequence length: {len(tokenizer(payload)["input_ids"])}') + + def measure_latency(pipe): + latencies = [] + # warm up + for _ in range(4): + _ = pipe(payload) + # Timed run + for _ in range(20): + start_time = perf_counter() + _ = pipe(payload) + latency = perf_counter() - start_time + latencies.append(latency) + # Compute run statistics + time_avg_ms = 1000 * np.mean(latencies) + time_std_ms = 1000 * np.std(latencies) + time_p95_ms = 1000 * np.percentile(latencies, 95) + return f'P95 latency (ms) - {time_p95_ms}; Average latency (ms) - {time_avg_ms:.2f} +\- {time_std_ms:.2f};', time_p95_ms + + vanilla_model = measure_latency(vanilla_emb) + quantized_model = measure_latency(q8_emb) + + print(f'Vanilla model: {vanilla_model[0]}') + print(f'Quantized model: {quantized_model[0]}') + print( + f'Improvement through quantization: {round(vanilla_model[1]/quantized_model[1],2)}x' + ) + + +profiling() diff --git a/repodir/huixiangdou/tests/test_post_android.py b/repodir/huixiangdou/tests/test_post_android.py new file mode 100644 index 00000000..f0d2a71a --- /dev/null +++ b/repodir/huixiangdou/tests/test_post_android.py @@ -0,0 +1,52 @@ +import json +import time + +import requests + +# base_url = 'https://p-172_dot_31_dot_0_dot_170_colon_18443.openxlab.space/api/v1/message/v1/wechat/fRHK' +base_url = 'http://139.224.198.162:18443/api/v1/message/v1/wechat/fRHK' + +headers = {'Content-Type': 'application/json; charset=utf-8'} + + +def send(): + data_send = { + 'query_id': 'abb', + 'groupname': '茴香豆测试群', # 完整的微信群名 + 'username': '豆哥 123', # 发送者的在这个群的微信昵称, 注意一个人可能在多个群里 + 'query': { + 'type': 'text', # 发的类型, text or image, poll + 'content': + '请问如何申请公寓?' # 如果 type 是 text 就是文本; 如果是 image,就是个可公开访问的 oss_url + } + } + resp = requests.post(base_url, + headers=headers, + data=json.dumps(data_send), + timeout=10) + + resp_json = resp.json() + print(resp_json) + + +def get(): + data_wait = { + 'query_id': 'abb', # 微信给的随机值,用于事后日志分析 + 'groupname': '茴香豆测试群', # 完整的微信群名 + 'username': '豆哥 123', # 发送者的在这个群的微信昵称, 注意一个人可能在多个群里 + 'query': { + 'type': 'poll' # 发的类型, text or image, poll + } + } + resp = requests.post(base_url, + headers=headers, + data=json.dumps(data_wait), + timeout=20) + print(resp.text) + + +send() +send() + +time.sleep(40) +get() diff --git a/repodir/huixiangdou/tests/test_pyppeteer.py b/repodir/huixiangdou/tests/test_pyppeteer.py new file mode 100644 index 00000000..7067abbe --- /dev/null +++ b/repodir/huixiangdou/tests/test_pyppeteer.py @@ -0,0 +1,27 @@ +import asyncio +import time + +from pyppeteer import launch + + +async def main(url): + browser = await launch(headless=True, + args=[ + '--no-sandbox', '--disable-dev-shm-usage', + '--disable-gpu', + '--disable-software-rasterizer', + '--disable-setuid-sandbox' + ]) + page = await browser.newPage() + await page.goto(url) + content = await page.evaluate( + 'document.getElementsByClassName("Post-Main")[0].innerText', + force_expr=True) + # print(content) + await browser.close() + return content + + +result = asyncio.get_event_loop().run_until_complete( + main(url='https://zhuanlan.zhihu.com/p/699164101')) +print(result) diff --git a/repodir/huixiangdou/tests/test_query_gradio.py b/repodir/huixiangdou/tests/test_query_gradio.py new file mode 100644 index 00000000..c34e8a85 --- /dev/null +++ b/repodir/huixiangdou/tests/test_query_gradio.py @@ -0,0 +1,76 @@ +import argparse +import json +import os +import time +from multiprocessing import Process, Value + +import cv2 +import gradio as gr +import pytoml +from loguru import logger + +from huixiangdou.primitive import Query +from huixiangdou.service import ErrorCode, SerialPipeline, ParallelPipeline, llm_serve, start_llm_server + +def parse_args(): + """Parse args.""" + parser = argparse.ArgumentParser(description='SerialPipeline Gradio WebUI.') + parser.add_argument('--work_dir', + type=str, + default='workdir', + help='Working directory.') + parser.add_argument( + '--config_path', + default='config.ini', + type=str, + help='SerialPipeline configuration path. Default value is config.ini') + parser.add_argument('--standalone', + action='store_true', + default=True, + help='Auto deploy required Hybrid LLM Service.') + args = parser.parse_args() + return args + + +def get_reply(text, image): + if image is not None: + filename = 'image.png' + image_path = os.path.join(args.work_dir, filename) + cv2.imwrite(image_path, image) + else: + image_path = None + + assistant = SerialPipeline(work_dir=args.work_dir, config_path=args.config_path) + query = Query(text, image_path) + + code, reply, references = assistant.generate(query=query, + history=[], + groupname='') + ret = dict() + ret['text'] = str(reply) + ret['code'] = int(code) + ret['references'] = references + + return json.dumps(ret, indent=2, ensure_ascii=False) + + +if __name__ == '__main__': + args = parse_args() + + # start service + if args.standalone is True: + # hybrid llm serve + start_llm_server(config_path=args.config_path) + + with gr.Blocks() as demo: + with gr.Row(): + input_question = gr.Textbox(label='Input the question.') + input_image = gr.Image(label='Upload Image.') + with gr.Column(): + result = gr.Textbox(label='Generate response.') + run_button = gr.Button() + run_button.click(fn=get_reply, + inputs=[input_question, input_image], + outputs=result) + logger.warning('This file would move to `huixiangdou.gradio`') + demo.launch(share=False, server_name='0.0.0.0', debug=True) diff --git a/repodir/huixiangdou/tests/test_qwen_react.py b/repodir/huixiangdou/tests/test_qwen_react.py new file mode 100644 index 00000000..20eddba7 --- /dev/null +++ b/repodir/huixiangdou/tests/test_qwen_react.py @@ -0,0 +1,245 @@ +# +# 相关材料: +# ReAct Prompting 原理简要介绍,不包含代码实现: +# https://github.com/QwenLM/Qwen-7B/blob/main/examples/react_prompt.md +# 基于 model.chat 接口(对话模式)的 ReAct Prompting 实现(含接入 LangChain 的工具实现): +# https://github.com/QwenLM/Qwen-7B/blob/main/examples/langchain_tooluse.ipynb +# 基于 model.generate 接口(续写模式)的 ReAct Prompting 实现,比 chat 模式的实现更复杂些: +# https://github.com/QwenLM/Qwen-7B/blob/main/examples/react_demo.py(本文件) +# + +import json +import os + +import json5 +import torch +from transformers import AutoModelForCausalLM, AutoTokenizer +from transformers.generation import GenerationConfig + +tokenizer = AutoTokenizer.from_pretrained('/models/Qwen-7B-Chat', + trust_remote_code=True) +generation_config = GenerationConfig.from_pretrained('/models/Qwen-7B-Chat', + trust_remote_code=True) +model = AutoModelForCausalLM.from_pretrained('/models/Qwen-7B-Chat', + device_map='auto', + trust_remote_code=True).eval() +model.generation_config = generation_config +model.generation_config.do_sample = False + +# 将一个插件的关键信息拼接成一段文本的模版。 +TOOL_DESC = """{name_for_model}: Call this tool to interact with the {name_for_human} API. What is the {name_for_human} API useful for? {description_for_model} Parameters: {parameters}""" + +# ReAct prompting 的 instruction 模版,将包含插件的详细信息。 +PROMPT_REACT = """Answer the following questions as best you can. You have access to the following tools: + +{tools_text} + +Use the following format: + +Question: the input question you must answer +Thought: you should always think about what to do +Action: the action to take, should be one of [{tools_name_text}] +Action Input: the input to the action +Observation: the result of the action +... (this Thought/Action/Action Input/Observation can be repeated zero or more times) +Thought: I now know the final answer +Final Answer: the final answer to the original input question + +Begin! + +Question: {query}""" + + +# +# 本示例代码的入口函数。 +# +# 输入: +# prompt: 用户的最新一个问题。 +# history: 用户与模型的对话历史,是一个 list, +# list 中的每个元素为 {"user": "用户输入", "bot": "模型输出"} 的一轮对话。 +# 最新的一轮对话放 list 末尾。不包含最新一个问题。 +# list_of_plugin_info: 候选插件列表,是一个 list,list 中的每个元素为一个插件的关键信息。 +# 比如 list_of_plugin_info = [plugin_info_0, plugin_info_1, plugin_info_2], +# 其中 plugin_info_0, plugin_info_1, plugin_info_2 这几个样例见本文档前文。 +# +# 输出: +# 模型对用户最新一个问题的回答。 +# +def llm_with_plugin(prompt: str, history, list_of_plugin_info=()): + chat_history = [(x['user'], x['bot']) for x in history] + [(prompt, '')] + + # 需要让模型进行续写的初始文本 + planning_prompt = build_input_text(chat_history, list_of_plugin_info) + + text = '' + while True: + output = text_completion(planning_prompt + text, + stop_words=['Observation:', 'Observation:\n']) + action, action_input, output = parse_latest_plugin_call(output) + if action: # 需要调用插件 + # action、action_input 分别为需要调用的插件代号、输入参数 + # observation是插件返回的结果,为字符串 + observation = call_plugin(action, action_input) + output += f'\nObservation: {observation}\nThought:' + text += output + else: # 生成结束,并且不再需要调用插件 + text += output + break + + new_history = [] + new_history.extend(history) + new_history.append({'user': prompt, 'bot': text}) + return text, new_history + + +# 将对话历史、插件信息聚合成一段初始文本 +def build_input_text(chat_history, list_of_plugin_info) -> str: + # 候选插件的详细信息 + tools_text = [] + for plugin_info in list_of_plugin_info: + tool = TOOL_DESC.format( + name_for_model=plugin_info['name_for_model'], + name_for_human=plugin_info['name_for_human'], + description_for_model=plugin_info['description_for_model'], + parameters=json.dumps(plugin_info['parameters'], + ensure_ascii=False), + ) + if plugin_info.get('args_format', 'json') == 'json': + tool += ' Format the arguments as a JSON object.' + elif plugin_info['args_format'] == 'code': + tool += ' Enclose the code within triple backticks (`) at the beginning and end of the code.' + else: + raise NotImplementedError + tools_text.append(tool) + tools_text = '\n\n'.join(tools_text) + + # 候选插件的代号 + tools_name_text = ', '.join( + [plugin_info['name_for_model'] for plugin_info in list_of_plugin_info]) + + im_start = '<|im_start|>' + im_end = '<|im_end|>' + prompt = f'{im_start}system\nYou are a helpful assistant.{im_end}' + for i, (query, response) in enumerate(chat_history): + if list_of_plugin_info: # 如果有候选插件 + # 倒数第一轮或倒数第二轮对话填入详细的插件信息,但具体什么位置填可以自行判断 + if (len(chat_history) == 1) or (i == len(chat_history) - 2): + query = PROMPT_REACT.format( + tools_text=tools_text, + tools_name_text=tools_name_text, + query=query, + ) + query = query.lstrip('\n').rstrip() # 重要!若不 strip 会与训练时数据的构造方式产生差异。 + response = response.lstrip( + '\n').rstrip() # 重要!若不 strip 会与训练时数据的构造方式产生差异。 + # 使用续写模式(text completion)时,需要用如下格式区分用户和AI: + prompt += f'\n{im_start}user\n{query}{im_end}' + prompt += f'\n{im_start}assistant\n{response}{im_end}' + + assert prompt.endswith(f'\n{im_start}assistant\n{im_end}') + prompt = prompt[:-len(f'{im_end}')] + return prompt + + +def text_completion(input_text: str, stop_words) -> str: # 作为一个文本续写模型来使用 + im_end = '<|im_end|>' + if im_end not in stop_words: + stop_words = stop_words + [im_end] + stop_words_ids = [tokenizer.encode(w) for w in stop_words] + + # TODO: 增加流式输出的样例实现 + input_ids = torch.tensor([tokenizer.encode(input_text)]).to(model.device) + output = model.generate(input_ids, stop_words_ids=stop_words_ids) + output = output.tolist()[0] + output = tokenizer.decode(output, errors='ignore') + assert output.startswith(input_text) + output = output[len(input_text):].replace('<|endoftext|>', + '').replace(im_end, '') + + for stop_str in stop_words: + idx = output.find(stop_str) + if idx != -1: + output = output[:idx + len(stop_str)] + return output # 续写 input_text 的结果,不包含 input_text 的内容 + + +def parse_latest_plugin_call(text): + plugin_name, plugin_args = '', '' + i = text.rfind('\nAction:') + j = text.rfind('\nAction Input:') + k = text.rfind('\nObservation:') + if 0 <= i < j: # If the text has `Action` and `Action input`, + if k < j: # but does not contain `Observation`, + # then it is likely that `Observation` is omitted by the LLM, + # because the output text may have discarded the stop word. + text = text.rstrip() + '\nObservation:' # Add it back. + k = text.rfind('\nObservation:') + plugin_name = text[i + len('\nAction:'):j].strip() + plugin_args = text[j + len('\nAction Input:'):k].strip() + text = text[:k] + return plugin_name, plugin_args, text + + +# +# 输入: +# plugin_name: 需要调用的插件代号,对应 name_for_model。 +# plugin_args:插件的输入参数,是一个 dict,dict 的 key、value 分别为参数名、参数值。 +# 输出: +# 插件的返回结果,需要是字符串。 +# 即使原本是 JSON 输出,也请 json.dumps(..., ensure_ascii=False) 成字符串。 +# +def call_plugin(plugin_name: str, plugin_args: str) -> str: + # + # 请开发者自行完善这部分内容。这里的参考实现仅是 demo 用途,非生产用途。 + # + if plugin_name == 'google_search': + # 使用 SerpAPI 需要在这里填入您的 SERPAPI_API_KEY! + os.environ['SERPAPI_API_KEY'] = os.getenv('SERPAPI_API_KEY', + default='') + from langchain import SerpAPIWrapper + + return SerpAPIWrapper().run(json5.loads(plugin_args)['search_query']) + elif plugin_name == 'image_gen': + import urllib.parse + + prompt = json5.loads(plugin_args)['prompt'] + prompt = urllib.parse.quote(prompt) + return json.dumps( + {'image_url': f'https://image.pollinations.ai/prompt/{prompt}'}, + ensure_ascii=False) + else: + raise NotImplementedError + + +def test(): + tools = [{ + 'name_for_human': + '谷歌搜索', + 'name_for_model': + 'google_search', + 'description_for_model': + '谷歌搜索是一个通用搜索引擎,可用于访问互联网、查询百科知识、了解时事新闻等。', + 'parameters': [{ + 'name': 'search_query', + 'description': '搜索关键词或短语', + 'required': True, + 'schema': { + 'type': 'string' + }, + }], + }] + history = [] + for query in [ + '请问mmdet3.0依赖mmcv哪个版本', 'openmmlab和上海 AI Lab 是什么关系', + '如何安装 mmdeploy', 'ncnn 全称是啥', + '如果我要从高空检测安全帽,我应该用 mmdet 还是 mmrotate ' + ]: + print(f"User's Query:\n{query}\n") + response, history = llm_with_plugin(prompt=query, + history=history, + list_of_plugin_info=tools) + print(f"Qwen's Response:\n{response}\n") + + +if __name__ == '__main__': + test() diff --git a/repodir/huixiangdou/tests/test_relative.py b/repodir/huixiangdou/tests/test_relative.py new file mode 100644 index 00000000..f5431fbf --- /dev/null +++ b/repodir/huixiangdou/tests/test_relative.py @@ -0,0 +1,38 @@ +def test_reject(retriever: Retriever, sample: str = None): + """Simple test reject pipeline.""" + if sample is None: + real_questions = [ + 'SAM 10个T 的训练集,怎么比比较公平呢~?速度上还有缺陷吧?', + '想问下,如果只是推理的话,amp的fp16是不会省显存么,我看parameter仍然是float32,开和不开推理的显存占用都是一样的。能不能直接用把数据和model都 .half() 代替呢,相比之下amp好在哪里', # noqa E501 + 'mmdeploy支持ncnn vulkan部署么,我只找到了ncnn cpu 版本', + '大佬们,如果我想在高空检测安全帽,我应该用 mmdetection 还是 mmrotate', + '请问 ncnn 全称是什么', + '有啥中文的 text to speech 模型吗?', + '今天中午吃什么?', + 'huixiangdou 是什么?', + 'mmpose 如何安装?', + '使用科研仪器需要注意什么?' + ] + else: + with open(sample) as f: + real_questions = json.load(f) + + for example in real_questions: + relative, _ = retriever.is_relative(example) + + if relative: + logger.warning(f'process query: {example}') + else: + logger.error(f'reject query: {example}') + + if sample is not None: + if relative: + with open('workdir/positive.txt', 'a+') as f: + f.write(example) + f.write('\n') + else: + with open('workdir/negative.txt', 'a+') as f: + f.write(example) + f.write('\n') + + empty_cache() diff --git a/repodir/huixiangdou/tests/test_reranker.py b/repodir/huixiangdou/tests/test_reranker.py new file mode 100644 index 00000000..33fd482c --- /dev/null +++ b/repodir/huixiangdou/tests/test_reranker.py @@ -0,0 +1,139 @@ +import json +import os +import pdb + +import numpy as np +import torch +from BCEmbedding import RerankerModel +from transformers import AutoModelForCausalLM, AutoTokenizer + + +def get_inputs(pairs, tokenizer, prompt=None, max_length=1024): + if prompt is None: + prompt = "Given a query A and a passage B, determine whether the passage contains an answer to the query by providing a prediction of either 'Yes' or 'No'." + sep = '\n' + prompt_inputs = tokenizer(prompt, + return_tensors=None, + add_special_tokens=False)['input_ids'] + sep_inputs = tokenizer(sep, return_tensors=None, + add_special_tokens=False)['input_ids'] + inputs = [] + for query, passage in pairs: + query_inputs = tokenizer(f'A: {query}', + return_tensors=None, + add_special_tokens=False, + max_length=max_length * 3 // 4, + truncation=True) + passage_inputs = tokenizer(f'B: {passage}', + return_tensors=None, + add_special_tokens=False, + max_length=max_length, + truncation=True) + item = tokenizer.prepare_for_model( + [tokenizer.bos_token_id] + query_inputs['input_ids'], + sep_inputs + passage_inputs['input_ids'], + truncation='only_second', + max_length=max_length, + padding=False, + return_attention_mask=False, + return_token_type_ids=False, + add_special_tokens=False) + item['input_ids'] = item['input_ids'] + sep_inputs + prompt_inputs + item['attention_mask'] = [1] * len(item['input_ids']) + inputs.append(item) + return tokenizer.pad(inputs, + padding=True, + max_length=max_length + len(sep_inputs) + + len(prompt_inputs), + pad_to_multiple_of=8, + return_tensors='pt') + + +def test_llm_reranker(): + model_path = '/data2/khj/bge-reranker-v2-minicpm-layerwise' + tokenizer = AutoTokenizer.from_pretrained(model_path, + trust_remote_code=True) + model = AutoModelForCausalLM.from_pretrained(model_path, + trust_remote_code=True, + torch_dtype=torch.bfloat16) + model = model.to('cuda') + model.eval() + + outdir = '/home/khj/hxd-ci/odir' + with torch.no_grad(): + dirpath = '/home/khj/hxd-ci/candidates' + for name in os.listdir(dirpath): + fullpath = os.path.join(dirpath, name) + outfullpath = os.path.join(outdir, name) + + with open(fullpath) as fin: + for jsonstr in fin: + jsonobj = json.loads(jsonstr) + pairs = [] + query = jsonobj['query'] + candidates = jsonobj['candidates'] + + output = {'query': query, 'rank': []} + for can in candidates: + content = can['content'] + pairs.append([query, content]) + + inputs = get_inputs(pairs, tokenizer).to(model.device) + all_scores = model(**inputs, + return_dict=True, + cutoff_layers=[28]) + all_scores = [ + scores[:, -1].view(-1, ).float() + for scores in all_scores[0] + ] + all_scores = all_scores[0].cpu().numpy() + # get descending order + indexes = all_scores.argsort()[::-1] + for index in indexes: + output['rank'].append(candidates[index]['content']) + outstr = json.dumps(output, ensure_ascii=False, indent=2) + + with open(outfullpath, 'a') as fout: + fout.write(outstr) + fout.write('\n') + + +def test_bce_reranker(): + whitelist = [ + '0JOL.jsonl', '0ki5.jsonl', '0oDm.jsonl', '1lA9.jsonl', '2P8j.jsonl' + ] + model_path = '/data2/khj/bce-embedding-base_v1' + model = RerankerModel(model_name_or_path=model_path) + + outdir = '/home/khj/hxd-ci/bceodir' + with torch.no_grad(): + dirpath = '/home/khj/hxd-ci/candidates' + for name in os.listdir(dirpath): + if name not in whitelist: + continue + fullpath = os.path.join(dirpath, name) + outfullpath = os.path.join(outdir, name) + + with open(fullpath) as fin: + for jsonstr in fin: + jsonobj = json.loads(jsonstr) + query = jsonobj['query'] + passages = [] + candidates = jsonobj['candidates'] + + output = {'query': query, 'rank': []} + for can in candidates: + content = can['content'] + passages.append(content) + + rerank_results = model.rerank(query, passages) + output['rank'] = rerank_results['rerank_passages'] + + outstr = json.dumps(output, ensure_ascii=False, indent=2) + with open(outfullpath, 'a') as fout: + fout.write(outstr) + fout.write('\n') + + +# test_bce_reranker() +test_llm_reranker() diff --git a/repodir/huixiangdou/tests/test_splitter.py b/repodir/huixiangdou/tests/test_splitter.py new file mode 100644 index 00000000..7abcf213 --- /dev/null +++ b/repodir/huixiangdou/tests/test_splitter.py @@ -0,0 +1,68 @@ +# Useful debug code, TL;DR. + +def langchain_splitter(text: str, chunk_size:int, metadata): + """This is for debugging""" + from langchain.text_splitter import MarkdownTextSplitter + md_splitter = MarkdownTextSplitter(chunk_size=chunk_size, chunk_overlap=32) + docs = md_splitter.create_documents([text]) + + chunks = [] + for doc in docs: + c = Chunk(content_or_path=doc.page_content, metadata=metadata) + chunks.append(c) + return chunks + +# compare splitter +if False: + # Here is for splitter result comparison, useless + gt_chunks = langchain_splitter(chunk.content_or_path, chunksize, chunk.metadata) + if len(subchunks) != len(gt_chunks): + pdb.set_trace() + pass + for ii in range(len(subchunks)): + if subchunks[ii].content_or_path != gt_chunks[ii].content_or_path: + pdb.set_trace() + +from langchain.text_splitter import MarkdownHeaderTextSplitter as LangChainMarkdownHeaderTextSplitter +gt_head_splitter = LangChainMarkdownHeaderTextSplitter(headers_to_split_on=[ + ('#', 'Header 1'), + ('##', 'Header 2'), + ('###', 'Header 3'), + ]) +docs = gt_head_splitter.split_text(text) +if len(docs) != len(chunks): + pdb.set_trace() + print('len diff') +for idx in range(len(docs)): + doc = docs[idx] + chunk = chunks[idx] + if doc.page_content != chunk.content_or_path: + pdb.set_trace() + print('content diff') + +with open('headersplit.result', 'a') as f: + for chunk in chunks: + + header = '' + if len(chunk.metadata) > 0: + if 'Header 1' in chunk.metadata: + header += chunk.metadata['Header 1'] + if 'Header 2' in chunk.metadata: + header += ' ' + header += chunk.metadata['Header 2'] + if 'Header 3' in chunk.metadata: + header += ' ' + header += chunk.metadata['Header 3'] + + json_str = json.dumps({'data': header + ' ||| ' + chunk.content_or_path}, ensure_ascii=False) + f.write(json_str) + f.write('\n') + +with open('refactor.json', 'w') as f: + pass + +with open('refactor.jsonl', 'a') as f: + for c in text_chunks: + json_str = json.dumps({'data': c.content_or_path}, ensure_ascii=False) + f.write(json_str) + f.write('\n') \ No newline at end of file diff --git a/repodir/huixiangdou/tests/test_step1_llm.py b/repodir/huixiangdou/tests/test_step1_llm.py new file mode 100644 index 00000000..f5efd23e --- /dev/null +++ b/repodir/huixiangdou/tests/test_step1_llm.py @@ -0,0 +1,120 @@ +import os + +from openai import OpenAI + +api_key = os.getenv('STEP_API_KEY') + + +def test_intention_scoring(): + # 全是豆哥运行期间的真实对话 + client = OpenAI(api_key=api_key, base_url='https://api.stepfun.com/v1') + + question1 = '请用四字成语形容一个人皮肤光滑,就像渲染里开了抗锯齿。' + question2 = '“不是盲审嘛,这对其他工作不太公平吧”\n请仔细阅读以上内容,判断句子是否是个有主题的疑问句,结果用 0~10 表示。直接提供得分不要解释。\n判断标准:有主语谓语宾语并且是疑问句得 10 分;缺少主谓宾扣分;陈述句直接得 0 分;不是疑问句直接得 0 分。直接提供得分不要解释。' + question3 = '“矩阵乘法有问题,我这段时间跑的模型怕不是都白跑了”\n请仔细阅读以上内容,判断句子是否是个有主题的疑问句,结果用 0~10 表示。直接提供得分不要解释。\n判断标准:有主语谓语宾语并且是疑问句得 10 分;缺少主谓宾扣分;陈述句直接得 0 分;不是疑问句直接得 0 分。直接提供得分不要解释。' + question4 = '“你这次卧还带玄关 真好”\n请仔细阅读以上内容,判断句子是否是个有主题的疑问句,结果用 0~10 表示。直接提供得分不要解释。\n判断标准:有主语谓语宾语并且是疑问句得 10 分;缺少主谓宾扣分;陈述句直接得 0 分;不是疑问句直接得 0 分。直接提供得分不要解释。' + question5 = '“我好气啊,为啥我赚不到这个钱”\n请仔细阅读以上内容,判断句子是否是个有主题的疑问句,结果用 0~10 表示。直接提供得分不要解释。\n判断标准:有主语谓语宾语并且是疑问句得 10 分;缺少主谓宾扣分;陈述句直接得 0 分;不是疑问句直接得 0 分。直接提供得分不要解释。' + question6 = '“要不,还是把豆哥从卷卷群移除吧”\n请仔细阅读以上内容,判断句子是否是个有主题的疑问句,结果用 0~10 表示。直接提供得分不要解释。\n判断标准:有主语谓语宾语并且是疑问句得 10 分;缺少主谓宾扣分;陈述句直接得 0 分;不是疑问句直接得 0 分。直接提供得分不要解释。' + question7 = '检查英文表达是否合适:Web portal is available on [OpenXLab](https://openxlab.org.cn/apps/detail/tpoisonooo/huixiangdou-web), you can build your own knowledge assistant, zero coding with WeChat and Feishu group.' + # ChatCompletionMessage(content='The English expression is mostly suitable, but there are a few minor issues:\n\n1. "available" should be "available".\n2. "zero coding" is a bit informal and may not be understood by all readers. A more formal alternative could be "without any coding experience required".\n3. "WeChat and Feishu group" should be "WeChat and Feishu groups".\n\nHere\'s the corrected version:\n\n"The web portal is available on [OpenXLab](https://openxlab.org.cn/apps/detail/tpoisonooo/huixiangdou-web), where you can build your own knowledge assistant without any coding experience required, using WeChat and Feishu groups."', role='assistant', function_call=None, tool_calls=None) + + questions = [question7] + + for question in questions: + completion = client.chat.completions.create( + model='step-1', + temperature=0.2, + messages=[ + { + 'role': + 'system', + 'content': + '你是由阶跃星辰提供的AI聊天助手,你擅长中文,英文,以及多种其他语言的对话。在保证用户数据安全的前提下,你能对用户的问题和请求,作出快速和精准的回答。同时,你的回答和建议应该拒绝黄赌毒,暴力恐怖主义的内容', + }, + { + 'role': 'user', + 'content': question + }, + ], + ) + + # 光滑如玉 + # 肤如凝脂。这句成语形容人的皮肤光滑、细腻,如同凝固的油脂一般,与你描述的开了抗锯齿的效果相符合。 + # 'The model "step-1-200k" does not exist or you do not have access to it.' for step-1-200k + # 直接给 step-1 也对 + print(question) + print(completion.choices[0].message) + + +def test_multimodal(): + question1 = ( + '这个截图里在说什么?', + 'https://huixiangdou-data.oss-cn-shanghai.aliyuncs.com/inside-mmpose.jpg' + ) + # ChatCompletionMessage(content='在您上传的微信群聊截图中,用户“茵香豆”询问了有关MMPose中FLW数据集的官方标注JSON文件中的“scale”是如何计算出来的问题。随后,“茵 + # 香豆”表示感谢,提到了“发红包怎么写的你的账户受限”,并惊讶地发现“不可能,他是机器人”。最后,“茵香豆”说“豆哥出息了,挣到猫粮了”,并配了一张图片。对话中还出现了 + # 一张“豆哥”的表情包图片。整个对话气氛轻松幽默。', role='assistant', function_call=None, tool_calls=None) + + question2 = ( + '提取图片里的对话,结果用 json 表示。直接告诉我 json 结果,不要解释', + 'https://huixiangdou-data.oss-cn-shanghai.aliyuncs.com/inside-ncnn-group.jpg' + ) + + # ChatCompletionMessage(content='```json\n{\n "type": "对话",\n "title": "TP除了gather reduce broadcast还有啥",\n "content": "还有allreduce、allgather、reducescatter",\n "source": "茴香豆"\n}\n```', role='assistant', function_call=None, tool_calls=None) + + question3 = ( + '回答这个截图里,第一条用户的问题。直接回答问题本身,不要解释,不要告诉我问题是什么。', + 'https://huixiangdou-data.oss-cn-shanghai.aliyuncs.com/inside-mmpose.jpg' + ) + # ChatCompletionMessage(content='在MMPose中,WFLW数据集的官网标注JSON文件中的“scale”是根据图像的原始尺寸和处理后的尺寸计算出来的。具体来说,“scale”是将图像缩放 + # 到用于训练或测试的固定分辨率(例如,固定的分辨率)时的缩放因子。', role='assistant', function_call=None, tool_calls=None) + + question4 = ( + '图片里是什么群的二维码?干什么用的。不要解释,直接告诉我答案。', + 'https://huixiangdou-data.oss-cn-shanghai.aliyuncs.com/wechat.jpg') + # ChatCompletionMessage(content='图片中的二维码是“openmmlab 茴香豆(惊蛰)”群的二维码。这个群组的名称可能与一个名为“openmmlab”的项目或组织有关,而“茴香豆”和“惊 + # 蛰”可能是群组的昵称或特定于该群的内部代号。由于我无法直接访问二维码背后的信息,我无法提供关于该群具体目的的详细信息。', role='assistant', function_call=None, tool_calls=None) + + questions = [question4] + + client = OpenAI(api_key=api_key, base_url='https://api.stepfun.com/v1') + + for question in questions: + completion = client.chat.completions.create( + model='step-1v-32k', + temperature=0.2, + messages=[ + { + 'role': + 'system', + 'content': + '你是由阶跃星辰提供的AI聊天助手,你除了擅长中文,英文,以及多种其他语言的对话以外,还能够根据用户提供的图片,对内容进行精准的内容文本描述。在保证用户数据安全的前提下,你能对用户的问题和请求,作出快速和精准的回答。同时,你的回答和建议应该拒绝黄赌毒,暴力恐怖主义的内容', + }, + { + 'role': 'user', + 'content': '你好呀!' + }, + { + 'role': + 'user', + 'content': [ + { + 'type': 'text', + 'text': question[0], + }, + { + 'type': 'image_url', + 'image_url': { + 'url': question[1] + }, + }, + ], + }, + ], + ) + + print(completion.choices[0].message) + + +test_intention_scoring() +# test_multimodal() diff --git a/repodir/huixiangdou/tests/test_time.py b/repodir/huixiangdou/tests/test_time.py new file mode 100644 index 00000000..e0992dde --- /dev/null +++ b/repodir/huixiangdou/tests/test_time.py @@ -0,0 +1,10 @@ +import time +from datetime import datetime + +current_time = time.time() # 获取当前时间戳 +dt_object = datetime.fromtimestamp(current_time) # 将时间戳转换为datetime对象 + +# 获取当天自午夜以来的总分钟数 +total_minutes_since_midnight = dt_object.hour * 60 + dt_object.minute + +print(total_minutes_since_midnight) diff --git a/repodir/huixiangdou/tests/test_visual_bge.py b/repodir/huixiangdou/tests/test_visual_bge.py new file mode 100644 index 00000000..0f45d0b2 --- /dev/null +++ b/repodir/huixiangdou/tests/test_visual_bge.py @@ -0,0 +1,16 @@ +##### Use M3 doing Multilingual Multi-Modal Retrieval +import torch +from FlagEmbedding.visual.modeling import Visualized_BGE + +model = Visualized_BGE( + model_name_bge='/data2/khj/bge-m3', + model_weight='/data2/khj/bge-visualized/Visualized_m3.pth') +model.eval() +with torch.no_grad(): + query_emb = model.encode(image='./imgs/cir_query.png', text='一匹马牵着这辆车') + candi_emb_1 = model.encode(image='./imgs/cir_candi_1.png') + candi_emb_2 = model.encode(image='./imgs/cir_candi_2.png') + +sim_1 = query_emb @ candi_emb_1.T +sim_2 = query_emb @ candi_emb_2.T +print(sim_1, sim_2) # tensor([[0.7026]]) tensor([[0.8075]]) diff --git a/repodir/huixiangdou/tests/test_yi.py b/repodir/huixiangdou/tests/test_yi.py new file mode 100644 index 00000000..7b683867 --- /dev/null +++ b/repodir/huixiangdou/tests/test_yi.py @@ -0,0 +1,22 @@ +from transformers import AutoModelForCausalLM, AutoTokenizer + +model = AutoModelForCausalLM.from_pretrained('/models/Yi-6B-200K', + device_map='auto', + torch_dtype='auto', + trust_remote_code=True) +tokenizer = AutoTokenizer.from_pretrained('/models/Yi-6B-200K', + trust_remote_code=True) +inputs = tokenizer('', return_tensors='pt') +max_length = 512 +outputs = model.generate( + inputs.input_ids.cuda(), + max_length=max_length, + eos_token_id=tokenizer.eos_token_id, + do_sample=True, + repetition_penalty=1.3, + no_repeat_ngram_size=5, + temperature=0.7, + top_k=1, + top_p=0.8, +) +print(tokenizer.decode(outputs[0], skip_special_tokens=True)) diff --git a/repodir/huixiangdou/tests/test_yulan.py b/repodir/huixiangdou/tests/test_yulan.py new file mode 100644 index 00000000..24ea74ef --- /dev/null +++ b/repodir/huixiangdou/tests/test_yulan.py @@ -0,0 +1,46 @@ +import torch +from transformers import LlamaForCausalLM, LlamaTokenizer + +model_path = '/models/YuLan-Chat-2-13b-fp16' +tokenizer = LlamaTokenizer.from_pretrained(model_path) +model = LlamaForCausalLM.from_pretrained(model_path, + torch_dtype=torch.float16, + device_map='auto') +model = model.eval() + + +def run(input_text: str): + prompt = "The following is a conversation between a human and an AI assistant namely YuLan, developed by GSAI, Renmin University of China. The AI assistant gives helpful, detailed, and polite answers to the user's questions.\n[|Human|]:{}\n[|AI|]:".format( + input_text) + inputs = tokenizer(prompt, + return_tensors='pt', + padding='longest', + max_length=8192, + truncation=True, + return_attention_mask=True, + add_special_tokens=True) + print(inputs) + kwargs = { + 'temperature': 0.8, + 'top_p': 0.95, + 'top_k': 50, + 'repetition_penalty': 1.1, + 'no_repeat_ngram_size': 64, + 'max_length': 8192, + 'pad_token_id': tokenizer.bos_token_id, + 'eos_token_id': tokenizer.eos_token_id + } + outputs = model.generate(inputs['input_ids'].to(model.device), + attention_mask=inputs['attention_mask'].to( + model.device), + do_sample=True, + **kwargs) + print(tokenizer.batch_decode(outputs, skip_special_tokens=True)) + + +texts = [ + 'mmdeploy extract如何使用', 'OpenMMLab与上海AI lab 的关系是什么?', 'MMEngine 和MMCV的区别', + 'openmmlab 是什么?', 'mmdet3.0 是否依赖 mmcv0.7', 'mmdet3.0对应的mmcv最低版本是多少' +] +for input_text in texts: + run(input_text) diff --git a/repodir/huixiangdou/unittest/primitive/test_dataclass.py b/repodir/huixiangdou/unittest/primitive/test_dataclass.py new file mode 100644 index 00000000..6efbe22b --- /dev/null +++ b/repodir/huixiangdou/unittest/primitive/test_dataclass.py @@ -0,0 +1,23 @@ +from huixiangdou.primitive import Chunk, Query + + +def test_chunk(): + c = Chunk() + c_str = '{}'.format(c) + assert 'content_or_path=' in c_str + + +def test_query(): + q = Query(text='hello', image='test.jpg') + q_str = '{}'.format(q) + assert 'hello' in q_str + assert 'image=' in q_str + + p = Query('hello') + p_str = '{}'.format(p) + assert 'text=' in p_str + + +if __name__ == '__main__': + test_chunk() + test_query() diff --git a/repodir/huixiangdou/unittest/primitive/test_embedder.py b/repodir/huixiangdou/unittest/primitive/test_embedder.py new file mode 100644 index 00000000..05186700 --- /dev/null +++ b/repodir/huixiangdou/unittest/primitive/test_embedder.py @@ -0,0 +1,25 @@ +import pdb + +from huixiangdou.primitive import Embedder + + +def test_embedder(): + emb = Embedder({'embedding_model_path':'/data2/khj/bge-m3'}) + sentence = 'hello world ' + sentence_16k = sentence * (16384 // len(sentence)) + image_path = 'resource/figures/wechat.jpg' + + text_feature = emb.embed_query(text=sentence_16k) + image_feature = emb.embed_query(path=image_path) + + query_feature = emb.embed_query(text=sentence_16k, path=image_path) + + sim1 = query_feature @ text_feature.T + sim2 = query_feature @ image_feature.T + + assert sim1.item() >= 0.5 + assert sim2.item() >= 0.5 + + +if __name__ == '__main__': + test_embedder() diff --git a/repodir/huixiangdou/unittest/primitive/test_faiss.py b/repodir/huixiangdou/unittest/primitive/test_faiss.py new file mode 100644 index 00000000..5a7cbdd1 --- /dev/null +++ b/repodir/huixiangdou/unittest/primitive/test_faiss.py @@ -0,0 +1,37 @@ +import os +import pdb + +from huixiangdou.primitive import Chunk, Embedder, Faiss, Query + + +def test_faiss(): + a = Chunk('hello world', {'source': 'unittest'}) + b = Chunk('resource/figures/inside-mmpose.jpg', {'source': 'unittest'}, + 'image') + c = Chunk('resource/figures/wechat.jpg', {'source': 'test image'}, 'image') + chunks = [a, b, c] + + save_path = '/tmp/faiss' + + model_config = { + 'embedding_model_path': '/data2/khj/bge-m3' + } + embedder = Embedder(model_config) + + Faiss.save_local(folder_path=save_path, chunks=chunks, embedder=embedder) + assert os.path.exists(os.path.join(save_path, 'embedding.faiss')) + + g = Faiss.load_local(save_path) + for idx, c in enumerate(g.chunks): + assert str(chunks[idx]) == str(c) + + target = 'resource/figures/inside-mmpose.jpg' + query = Query(image=target) + pairs = g.similarity_search_with_query(query=query, embedder=embedder) + chunk, score = pairs[0] + assert chunk.content_or_path == target + assert score >= 0.9999 + + +if __name__ == '__main__': + test_faiss() diff --git a/repodir/huixiangdou/unittest/primitive/test_reranker.py b/repodir/huixiangdou/unittest/primitive/test_reranker.py new file mode 100644 index 00000000..9e2e6340 --- /dev/null +++ b/repodir/huixiangdou/unittest/primitive/test_reranker.py @@ -0,0 +1,17 @@ +import pdb + +from huixiangdou.primitive import LLMReranker + + +def test_reranker(): + model = LLMReranker({'reranker_model_path':'/data2/khj/bce-reranker-base_v1'}) + + query = 'apple' + texts = [ 'roast banana', 'ice juice', 'red orange', 'apple pie'] + scores = model._sort(texts=texts, query=query) + + assert scores[0] == len(texts) - 1 + + +if __name__ == '__main__': + test_reranker() diff --git a/repodir/huixiangdou/unittest/primitive/test_splitter.py b/repodir/huixiangdou/unittest/primitive/test_splitter.py new file mode 100644 index 00000000..8d5408f2 --- /dev/null +++ b/repodir/huixiangdou/unittest/primitive/test_splitter.py @@ -0,0 +1,121 @@ +# from .splitter import CharacterTextSplitter, ChineseRecursiveTextSplitter, RecursiveCharacterTextSplitter, MarkdownTextRefSplitter, MarkdownHeaderTextSplitter # noqa E401 +import pdb + +from huixiangdou.primitive import (CharacterTextSplitter, + ChineseRecursiveTextSplitter, + MarkdownHeaderTextSplitter, + MarkdownTextRefSplitter, + RecursiveCharacterTextSplitter, + nested_split_markdown) + + +def test_character_text_splitter(): + path = 'README_zh.md' + with open(path) as f: + text = f.read() + s = CharacterTextSplitter() + print(type(s)) + splits = s.split_text(text) + assert len(splits) > 10 + + chunks = s.create_chunks([text], [{'path': path}]) + assert len(chunks) == len(splits) + with open('/tmp/character_text_split', 'w') as f: + for c in chunks: + print(len(c.content_or_path)) + f.write(c.content_or_path) + f.write('-----------\n') + return chunks + + +def test_recursive_character_text_splitter(): + path = 'README_zh.md' + with open(path) as f: + text = f.read() + s = RecursiveCharacterTextSplitter() + print(type(s)) + splits = s.split_text(text) + + chunks = s.create_chunks([text], [{'path': path}]) + assert len(chunks) == len(splits) + with open('/tmp/recursive_character_text_split', 'w') as f: + for c in chunks: + print(len(c.content_or_path)) + f.write(c.content_or_path) + f.write('\n-----------\n') + return chunks + + +def test_chinese_recursive_text_splitter(): + path = 'README_zh.md' + with open(path) as f: + text = f.read() + s = ChineseRecursiveTextSplitter() + print(type(s)) + splits = s.split_text(text) + + chunks = s.create_chunks([text], [{'path': path}]) + assert len(chunks) == len(splits) + with open('/tmp/chinese_recursive_text_split', 'w') as f: + for c in chunks: + print(len(c.content_or_path)) + f.write(c.content_or_path) + f.write('\n-----------\n') + return chunks + + +def test_markdown_text_splitter(): + path = 'README_zh.md' + with open(path) as f: + text = f.read() + s = MarkdownTextRefSplitter() + print(type(s)) + splits = s.split_text(text) + + chunks = s.create_chunks([text], [{'path': path}]) + assert len(chunks) == len(splits) + with open('/tmp/markdown_text_split', 'w') as f: + for c in chunks: + print(len(c.content_or_path)) + f.write(c.content_or_path) + f.write('\n-----------\n') + return chunks + + +def test_markdown_header_text_splitter(): + path = 'README_zh.md' + with open(path) as f: + text = f.read() + s = MarkdownHeaderTextSplitter() + print(type(s)) + chunks = s.create_chunks(text, metadata={'path': path}) + with open('/tmp/markdown_header_text_split', 'w') as f: + for c in chunks: + print(len(c.content_or_path)) + f.write(c.content_or_path) + f.write('\n-----------\n') + return chunks + + +def test_nested_markdown_split(): + path = 'README_zh.md' + with open(path) as f: + text = f.read() + print(type('nested')) + + chunks = nested_split_markdown(filepath=path, text=text, chunksize=768) + with open('/tmp/markdown_nested_split', 'w') as f: + for c in chunks: + print(len(c.content_or_path)) + f.write(c.content_or_path) + f.write('\n-----------\n') + return chunks + + +if __name__ == '__main__': + test_character_text_splitter() + test_recursive_character_text_splitter() + test_chinese_recursive_text_splitter() + test_markdown_text_splitter() + test_markdown_header_text_splitter() + test_nested_markdown_split() diff --git a/repodir/huixiangdou/unittest/service/daily_smoke.py b/repodir/huixiangdou/unittest/service/daily_smoke.py new file mode 100644 index 00000000..ac34910e --- /dev/null +++ b/repodir/huixiangdou/unittest/service/daily_smoke.py @@ -0,0 +1,81 @@ +# Check `huixiangdou.service.main` works +import json +import os +import tempfile +import time + +import pytoml +from loguru import logger + + +def command(txt: str): + """Executes a shell command and returns its output. + + Args: + txt (str): Command to be executed in the shell. + + Returns: + str: Output of the shell command execution. + """ + logger.debug('cmd: {}'.format(txt)) + cmd = os.popen(txt) + return cmd.read().rstrip().lstrip() + + +def load_secret(): + kimi_token = '' + serper_token = '' + with open('unittest/token.json') as f: + json_obj = json.load(f) + kimi_token = json_obj['kimi'] + serper_token = json_obj['serper'] + return kimi_token, serper_token + + +def build_config_path(): + config_path = 'config-2G.ini' + kimi_token, _ = load_secret() + config = None + with open(config_path) as f: + config = pytoml.load(f) + config['feature_store'][ + 'embedding_model_path'] = '/data2/khj/bce-embedding-base_v1/' + config['feature_store'][ + 'reranker_model_path'] = '/data2/khj/bce-embedding-base_v1/' + config['llm']['server']['remote_api_key'] = kimi_token + + config_path = None + with tempfile.NamedTemporaryFile(delete=False, mode='w+b') as temp_file: + tomlstr = pytoml.dumps(config) + temp_file.write(tomlstr.encode('utf8')) + + config_path = temp_file.name + + return config_path + + +def run(): + config_path = build_config_path() + + actions = { + 'llm_server_hybrid': + 'python3 -m huixiangdou.service.llm_server_hybrid --config_path {} --unittest', + 'feature_store': + 'python3 -m huixiangdou.service.feature_store --config_path {}', + 'main': 'python3 -m huixiangdou.main --standalone --config_path {}' + } + + reports = ['HuixiangDou daily smoke:'] + for action, cmd in actions.items(): + cmd = cmd.format(config_path) + log = command(cmd) + + if 'ConnectionResetError' in log: + logger.info(f'*{action}, {cmd}') + assert (0) + else: + logger.info(f'*{action}, passed') + + +if __name__ == '__main__': + run() diff --git a/repodir/huixiangdou/unittest/service/test_llm_client.py b/repodir/huixiangdou/unittest/service/test_llm_client.py new file mode 100644 index 00000000..db36e406 --- /dev/null +++ b/repodir/huixiangdou/unittest/service/test_llm_client.py @@ -0,0 +1,23 @@ +from huixiangdou.service.llm_client import ChatClient + + +def test_auto_fix(): + """test auto choose backend based on config.""" + remote_only_config = 'config-2G.ini' + local_only_config = 'config.ini' + full_config = 'config-advanced.ini' + + client = ChatClient(config_path=remote_only_config) + real_backend, max_len = client.auto_fix(backend='local') + assert real_backend != 'local' + assert max_len >= 32000 + + client = ChatClient(config_path=local_only_config) + real_backend, max_len = client.auto_fix(backend='kimi') + assert real_backend == 'local' + + client = ChatClient(config_path=full_config) + real_backend, max_len = client.auto_fix(backend='local') + assert real_backend == 'local' + real_backend, max_len = client.auto_fix(backend='kimi') + assert real_backend != 'local' diff --git a/repodir/huixiangdou/unittest/service/test_llm_server_local.py b/repodir/huixiangdou/unittest/service/test_llm_server_local.py new file mode 100644 index 00000000..381f6e6d --- /dev/null +++ b/repodir/huixiangdou/unittest/service/test_llm_server_local.py @@ -0,0 +1,63 @@ +import json +import pdb +import re +import time +import asyncio + +import pytoml +from loguru import logger + +from huixiangdou.service.llm_server_hybrid import InferenceWrapper, HybridLLMServer + +PROMPT = 'huixiangdou是什么?' +# PROMPT = '“huixiangdou是什么?”\n请仔细阅读以上内容,判断句子是否是个有主题的疑问句,结果用 0~10 表示。直接提供得分不要解释。\n判断标准:有主语谓语宾语并且是疑问句得 10 分;缺少主谓宾扣分;陈述句直接得 0 分;不是疑问句直接得 0 分。直接提供得分不要解释。' +llm_local_path = '/data2/khj/internlm2-chat-7b' + +def get_score(relation: str, default=0): + score = default + filtered_relation = ''.join([c for c in relation if c.isdigit()]) + try: + score_str = re.sub(r'[^\d]', ' ', filtered_relation).strip() + score = int(score_str.split(' ')[0]) + except Exception as e: + logger.warning('primitive is_truth: {}, use default value {}'.format( + str(e), default)) + return score + +def test_internlm_local(): + wrapper = InferenceWrapper(llm_local_path) + repeat = 1 + for i in range(repeat): + resp = wrapper.chat(prompt=PROMPT) + logger.info(resp) + logger.info(get_score(relation=resp)) + del wrapper + +async def test_internlm_local_stream(): + wrapper = InferenceWrapper(llm_local_path) + async for part in wrapper.chat_stream(prompt=PROMPT): + print(part, end="") + +def test_internlm_local_(): + with open('config.ini', encoding='utf8') as f: + llm_config = pytoml.load(f)['llm'] + llm_config['server']['local_llm_path'] = llm_local_path + server = HybridLLMServer(llm_config) + resp, error = server.chat(prompt=PROMPT) + print(resp) + del server + +async def test_internlm_local_stream_(): + with open('config.ini', encoding='utf8') as f: + llm_config = pytoml.load(f)['llm'] + llm_config['server']['local_llm_path'] = llm_local_path + server = HybridLLMServer(llm_config) + async for part in server.chat_stream(prompt=PROMPT): + print(part, end="") + +if __name__ == '__main__': + loop = asyncio.get_event_loop() + test_internlm_local() + loop.run_until_complete(test_internlm_local_stream()) + test_internlm_local_() + loop.run_until_complete(test_internlm_local_stream_()) diff --git a/repodir/huixiangdou/unittest/service/test_llm_server_remote.py b/repodir/huixiangdou/unittest/service/test_llm_server_remote.py new file mode 100644 index 00000000..0b50023b --- /dev/null +++ b/repodir/huixiangdou/unittest/service/test_llm_server_remote.py @@ -0,0 +1,221 @@ +import json +import pdb +import re +import time + +import pytoml +from loguru import logger + +from huixiangdou.service.llm_server_hybrid import (RPM, HybridLLMServer, + llm_serve) + +PROMPT = '“huixiangdou 是什么?”\n请仔细阅读以上内容,判断句子是否是个有主题的疑问句,结果用 0~10 表示。直接提供得分不要解释。\n判断标准:有主语谓语宾语并且是疑问句得 10 分;缺少主谓宾扣分;陈述句直接得 0 分;不是疑问句直接得 0 分。直接提供得分不要解释。' +# PROMPT = 'huixiangdou 是什么?' + +def get_score(relation: str, default=0): + score = default + filtered_relation = ''.join([c for c in relation if c.isdigit()]) + try: + score_str = re.sub(r'[^\d]', ' ', filtered_relation).strip() + score = int(score_str.split(' ')[0]) + except Exception as e: + logger.warning('primitive is_truth: {}, use default value {}'.format( + str(e), default)) + return score + + +def load_secret(): + with open('unittest/token.json') as f: + json_obj = json.load(f) + return json_obj + + +def test_llm_backend_fail(): + remote_only_config = 'config-2G.ini' + llm_config = None + with open(remote_only_config, encoding='utf8') as f: + llm_config = pytoml.load(f)['llm'] + server = HybridLLMServer(llm_config=llm_config) + + _, error = server.chat(prompt='hello', + history=[], + backend='kimi') + logger.error(error) + assert error is not None + + _, error = server.chat(prompt='hello', + history=[], + backend='deepseek') + logger.error(error) + assert error is not None + + _, error = server.chat(prompt='hello', + history=[], + backend='zhipuai') + logger.error(error) + assert error is not None + + _, error = server.chat(prompt='hello', + history=[], + backend='xi-api') + logger.error(error) + assert error is not None + + +def test_kimi_pass(): + remote_only_config = 'config-2G.ini' + llm_config = None + with open(remote_only_config, encoding='utf8') as f: + llm_config = pytoml.load(f)['llm'] + + secrets = load_secret() + llm_config['server']['remote_type'] = 'kimi' + llm_config['server']['remote_api_key'] = secrets['kimi'] + # llm_config['server']['remote_llm_model'] = 'auto' + print('testing {}'.format(llm_config['server'])) + server = HybridLLMServer(llm_config=llm_config) + + response, error = server.chat(prompt=PROMPT, + history=[], + backend='kimi') + score = get_score(relation=response, default=0) + assert score >= 5 + assert error is None + assert len(response) > 0 + + +def test_zhipu_pass(): + remote_only_config = 'config-2G.ini' + llm_config = None + with open(remote_only_config, encoding='utf8') as f: + llm_config = pytoml.load(f)['llm'] + + secrets = load_secret() + llm_config['server']['remote_type'] = 'zhipuai' + llm_config['server']['remote_api_key'] = secrets['zhipuai'] + # llm_config['server']['remote_llm_model'] = 'glm-4' + print('testing {}'.format(llm_config['server'])) + server = HybridLLMServer(llm_config=llm_config) + + response, error = server.chat(prompt=PROMPT, + history=[], + backend='zhipuai') + score = get_score(relation=response, default=0) + assert score >= 5 + assert error is None + assert len(response) > 0 + + +def test_deepseek_pass(): + remote_only_config = 'config-2G.ini' + llm_config = None + with open(remote_only_config, encoding='utf8') as f: + llm_config = pytoml.load(f)['llm'] + + secrets = load_secret() + llm_config['server']['remote_type'] = 'deepseek' + llm_config['server']['remote_api_key'] = secrets['deepseek'] + # llm_config['server']['remote_llm_model'] = 'deepseek-chat' + print('testing {}'.format(llm_config['server'])) + + server = HybridLLMServer(llm_config=llm_config) + + response, error = server.chat(prompt=PROMPT, + history=[], + backend='deepseek') + score = get_score(relation=response, default=0) + assert score >= 5 + assert error is None + assert len(response) > 0 + + +def test_step_pass(): + remote_only_config = 'config-2G.ini' + llm_config = None + with open(remote_only_config, encoding='utf8') as f: + llm_config = pytoml.load(f)['llm'] + + secrets = load_secret() + llm_config['server']['remote_type'] = 'step' + llm_config['server']['remote_api_key'] = secrets['step'] + # llm_config['server']['remote_llm_model'] = 'auto' + print('testing {}'.format(llm_config['server'])) + + server = HybridLLMServer(llm_config=llm_config) + + response, error = server.chat(prompt=PROMPT, + history=[], + backend='step') + score = get_score(relation=response, default=0) + assert score >= 5 + assert error is None + assert len(response) > 0 + + +def test_puyu_pass(): + remote_only_config = 'config-2G.ini' + llm_config = None + with open(remote_only_config, encoding='utf8') as f: + llm_config = pytoml.load(f)['llm'] + + secrets = load_secret() + llm_config['server']['remote_type'] = 'puyu' + llm_config['server']['remote_api_key'] = secrets['puyu'] + # llm_config['server']['remote_llm_model'] = 'internlm2-latest' + print('testing {}'.format(llm_config['server'])) + + server = HybridLLMServer(llm_config=llm_config) + + response, error = server.chat(prompt=PROMPT, + history=[], + backend='puyu') + score = get_score(relation=response, default=0) + assert score >= 5 + assert error is None + + +def test_siliconcloud_pass(): + remote_only_config = 'config-2G.ini' + llm_config = None + with open(remote_only_config, encoding='utf8') as f: + llm_config = pytoml.load(f)['llm'] + + secrets = load_secret() + llm_config['server']['remote_type'] = 'siliconcloud' + # llm_config['server']['remote_llm_model'] = 'alibaba/Qwen1.5-110B-Chat' + llm_config['server']['remote_api_key'] = secrets['siliconcloud'] + print('testing {}'.format(llm_config['server'])) + server = HybridLLMServer(llm_config=llm_config) + + response, error = server.chat(prompt=PROMPT, + history=[], + backend='siliconcloud') + logger.info('siliconcloud response {}'.format(response)) + score = get_score(relation=response, default=0) + assert score >= 5 + assert error is None + assert len(response) > 0 + + +def test_rpm(): + rpm = RPM(30) + + for i in range(40): + rpm.wait() + print(i) + + time.sleep(5) + + for i in range(40): + rpm.wait() + print(i) + + +if __name__ == '__main__': + test_siliconcloud_pass() + test_kimi_pass() + test_step_pass() + test_zhipu_pass() + test_deepseek_pass() + test_puyu_pass() + test_llm_backend_fail() diff --git a/repodir/huixiangdou/unittest/service/test_sg_search.py b/repodir/huixiangdou/unittest/service/test_sg_search.py new file mode 100644 index 00000000..8937bd22 --- /dev/null +++ b/repodir/huixiangdou/unittest/service/test_sg_search.py @@ -0,0 +1,59 @@ +import json +import os +import tempfile +import time + +import pytoml +from loguru import logger + +from huixiangdou.service.llm_client import ChatClient +from huixiangdou.service.llm_server_hybrid import llm_serve, start_llm_server +from huixiangdou.service.sg_search import SourceGraphProxy + + +def load_secret(): + kimi_token = '' + serper_token = '' + with open('unittest/token.json') as f: + json_obj = json.load(f) + kimi_token = json_obj['kimi'] + serper_token = json_obj['serper'] + sg_token = json_obj['sg'] + return kimi_token, serper_token, sg_token + + +def build_config_path(): + config_path = 'config-2G.ini' + kimi_token, serper_token, sg_token = load_secret() + config = None + with open(config_path) as f: + config = pytoml.load(f) + config['web_search']['engine'] = 'serper' + config['web_search']['serper_x_api_key'] = serper_token + config['feature_store'][ + 'embedding_model_path'] = '/data2/khj/bce-embedding-base_v1/' + config['feature_store'][ + 'reranker_model_path'] = '/data2/khj/bce-embedding-base_v1/' + config['llm']['server']['remote_api_key'] = kimi_token + config['worker']['enable_sg_search'] = 1 + config['sg_search']['src_access_token'] = sg_token + + config_path = None + with tempfile.NamedTemporaryFile(delete=False, mode='w+b') as temp_file: + tomlstr = pytoml.dumps(config) + temp_file.write(tomlstr.encode('utf8')) + config_path = temp_file.name + + return config_path + + +def test_sg(): + config_path = build_config_path() + start_llm_server(config_path) + + llm = ChatClient(config_path=config_path) + proxy = SourceGraphProxy(config_path=config_path) + content = proxy.search(llm_client=llm, + question='mmpose installation', + groupname='mmpose dev group') + assert len(content) > 0 diff --git a/repodir/huixiangdou/unittest/service/test_web_search.py b/repodir/huixiangdou/unittest/service/test_web_search.py new file mode 100644 index 00000000..68853a90 --- /dev/null +++ b/repodir/huixiangdou/unittest/service/test_web_search.py @@ -0,0 +1,76 @@ +import json +import os +import tempfile +import time + +import pytoml +from loguru import logger + +from huixiangdou.service.web_search import WebSearch, check_str_useful + + +def load_secret(): + kimi_token = '' + serper_token = '' + with open('unittest/token.json') as f: + json_obj = json.load(f) + kimi_token = json_obj['kimi'] + serper_token = json_obj['serper'] + return kimi_token, serper_token + + +def test_ddgs(): + config_path = 'config-2G.ini' + engine = WebSearch(config_path=config_path) + articles, error = engine.get(query='mmpose installation') + assert error is None + assert len(articles[0]) > 100 + + +def test_serper(): + config_path = 'config-2G.ini' + _, serper_token = load_secret() + config = None + with open(config_path) as f: + config = pytoml.load(f) + config['web_search']['engine'] = 'serper' + config['web_search']['serper_x_api_key'] = serper_token + + config_path = None + with tempfile.NamedTemporaryFile(delete=False, mode='w+b') as temp_file: + tomlstr = pytoml.dumps(config) + temp_file.write(tomlstr.encode('utf8')) + + config_path = temp_file.name + + engine = WebSearch(config_path=config_path) + articles, error = engine.get(query='mmpose installation') + assert error is None + assert len(articles[0]) > 100 + assert articles[0].brief == articles[0].content + # 删除临时文件,因为delete=False,所以需要手动删除 + os.remove(temp_file.name) + + +def test_parse_zhihu(): + config_path = 'config-2G.ini' + engine = WebSearch(config_path=config_path) + article = engine.fetch_url( + query='', target_link='https://zhuanlan.zhihu.com/p/699164101') + assert check_str_useful(article.content) + + +def test_parse_hljnews(): + config_path = 'config-2G.ini' + engine = WebSearch(config_path=config_path) + article = engine.fetch_url( + query='', + target_link= + 'http://www.hljnews.cn/ljxw/content/2023-10/17/content_729976.html?vp-fm' + ) + assert check_str_useful(article.content) + + +if __name__ == '__main__': + test_parse_zhihu() + test_parse_hljnews() diff --git a/repodir/huixiangdou/web/README.md b/repodir/huixiangdou/web/README.md new file mode 100644 index 00000000..6718cd97 --- /dev/null +++ b/repodir/huixiangdou/web/README.md @@ -0,0 +1,118 @@ +# Web + +web 版本前后端源码,效果同 https://openxlab.org.cn/apps/detail/tpoisonooo/huixiangdou-web + +整个服务分 **前后端** 和 **算法** 两部分: + +- 中间用 redis queue 通信,可以视做**消息总线** +- 后端接受所有业务侧消息,例如微信、飞书、浏览器等 +- 算法无状态,只关心算法相关的任务。可分布式扩展以应对高吞吐需求 + + + +## 启动 + +1. 先安装 Redis,如果你已经安装 Redis 服务,并且配置好了 Redis 密码,启动了 Redis 服务,直接跳过这一步即可。 + +```bash +# 以 Ubuntu 系统为例 +# 安装 Redis 服务和客户端 +sudo apt install redis-server redis-tools + +# 设置 Redis 密码(默认配置文件在/etc/redis/redis.conf),比如设置为:redis123 +sudo vim /etc/redis/redis.conf +# 将 requirepass your_password_here 注释打开并修改为如下内容,保存即可 +requirepass redis123 + +# 启动redis +sudo redis-server /etc/redis/redis.conf +# 查看redis是否启动成功 +netstat -nlpt | grep redis +``` + +2. 设置环境变量,需要配置的环境变量如下(你可以将这些环境变量添加到~/.bashrc文件的末尾,然后 `source ~/.bashrc` 刷新配置) + +```bash +$ cat web/env.sh + +export PYTHONUNBUFFERED=1 +# Redis 的 IP 地址 +export REDIS_HOST=10.1.52.22 +# Redis 的密码 +export REDIS_PASSWORD=${REDIS_PASSWORD} +# Redis 的端口,默认为 6379 +export REDIS_PORT=6380 +# JWT_SECRET 是指用于签名 JSON Web Token (JWT) 的密钥或密钥对,可以使用 `openssl rand -base64 32` 命令生成 +export JWT_SECRET=${JWT_SEC} +# 茴香豆的后台服务端口,可以自定义 +export SERVER_PORT=7860 +# 飞书的 LARK_ENCRYPT_KEY,参考地址:https://open.larksuite.com/document/server-docs/event-subscription/event-subscription-configure-/request-url-configuration-case +# 如果不需要接通飞书忽略即可 +export HUIXIANGDOU_LARK_ENCRYPT_KEY=thisiskey +export HUIXIANGDOU_LARK_VERIFY_TOKEN=sMzyjKi9vMlEhKCZOVtBMhhl8x23z0AG + +# set your service endpoint(open to Internet callback from lark and wechat) +# 回调端口,建议填写 7860,然后将 7860 端口通过公网 IP 代理出去,例如 http://10.1.52.36:18443 +export HUIXIANGDOU_MESSAGE_ENDPOINT=http://10.1.52.36:18443 +# 如果使用 https 安全连接就把 COOKIE_SECURE 设置为 1;如果不是,则将 `export COOKIE_SECURE=1` 替换为 `unset COOKIE_SECURE` +export COOKIE_SECURE=1 +``` + +⚠️ 重要事项: 如果不用 https 安全链接,需要 `unset COOKIE_SECURE`(不是设成 0)。否则知识库登录会异常 + +> 怎么算是用 https ? 就是**你买了域名且能 https 打头, 如果你用的是裸 ip 地址,那就是没有!** +> +> 例如: +> +> https://openxlab.com/api 是 https,需要 `export COOKIE_SECURE=1` +> +> https://10.1.2.22 不是,取消 cookie +> +> http://101.204.1.5 不是 + +3. 编译前端 & 运行后端服务 + +安装 Node.js `npm` (需要版本为 20.x , 安装时, 可根据用户权限需要自行添加 sudo + 命令) + +```bash +apt update +apt install nodejs npm +node -v # v20.12.0 +``` + +如果 `node -v` 版本太老 (10.x),则需要升级 node 版本 + +```bash +npm install n -g +n stable +hash -r +node -v # v20.12.0 +``` + +编译项目 + +```bash +cd front-end +npm install && npm run build +``` + +安装依赖、运行后端 + +```bash +cd ../../ # 从front-end返回到huixiangdou目录下, 该目录内含有web文件夹 +python3 -m pip install -r web/requirements.txt +python3 -m web.main +``` + +4. 运行算法 pipeline + +```bash +# 先开个终端窗口,启动 LLM hybrid proxy +python3 -m huixiangdou.service.llm_server_hybrid --config_path config.ini + +# 再开个窗口,监听服务 +python3 -m web.proxy.main +``` + +5. 测试 + 打开服务器 7860 端口,创建知识库测试效果 diff --git a/repodir/huixiangdou/web/__init__.py b/repodir/huixiangdou/web/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/repodir/huixiangdou/web/api/__init__.py b/repodir/huixiangdou/web/api/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/repodir/huixiangdou/web/api/access.py b/repodir/huixiangdou/web/api/access.py new file mode 100644 index 00000000..f310b365 --- /dev/null +++ b/repodir/huixiangdou/web/api/access.py @@ -0,0 +1,11 @@ +from fastapi import APIRouter, Request, Response + +from web.model.access import LoginBody +from web.service.access import LoginService + +access_api = APIRouter() + + +@access_api.post('/v1/login') +async def login(body: LoginBody, request: Request, response: Response): + return await LoginService(body, request, response).login() diff --git a/repodir/huixiangdou/web/api/chat.py b/repodir/huixiangdou/web/api/chat.py new file mode 100644 index 00000000..b852070c --- /dev/null +++ b/repodir/huixiangdou/web/api/chat.py @@ -0,0 +1,33 @@ +from fastapi import APIRouter, Depends, Request, Response + +from web.middleware.token import check_hxd_token +from web.model.chat import (ChatCaseFeedbackBody, ChatOnlineResponseBody, + ChatRequestBody) +from web.model.qalib import QalibInfo +from web.service.chat import ChatService + +chat_api = APIRouter() + + +@chat_api.post('/v1/online') +async def chat_online(request: Request, + response: Response, + body: ChatRequestBody, + hxd_info: QalibInfo = Depends(check_hxd_token)): + return await ChatService(request, response, hxd_info).chat_online(body) + + +@chat_api.post('/v1/onlineResponse') +async def chat_online_response(request: Request, + response: Response, + body: ChatOnlineResponseBody, + hxd_info: QalibInfo = Depends(check_hxd_token)): + return await ChatService(request, response, hxd_info).fetch_response(body) + + +@chat_api.post('/v1/caseFeedback') +async def case_feedback(request: Request, + response: Response, + body: ChatCaseFeedbackBody, + hxd_info: QalibInfo = Depends(check_hxd_token)): + return await ChatService(request, response, hxd_info).case_feedback(body) diff --git a/repodir/huixiangdou/web/api/integrate.py b/repodir/huixiangdou/web/api/integrate.py new file mode 100644 index 00000000..5b0deb44 --- /dev/null +++ b/repodir/huixiangdou/web/api/integrate.py @@ -0,0 +1,25 @@ +from fastapi import APIRouter, Depends, Request, Response + +from web.middleware.token import check_hxd_token +from web.model.integrate import IntegrateLarkBody, IntegrateWebSearchBody +from web.model.qalib import QalibInfo +from web.service.qalib import QaLibService + +integrate_api = APIRouter() + + +@integrate_api.post('/v1/integrateLark') +async def integrate_lark(request: Request, + response: Response, + body: IntegrateLarkBody, + hxd_info: QalibInfo = Depends(check_hxd_token)): + return await QaLibService(request, response, hxd_info).integrate_lark(body) + + +@integrate_api.post('/v1/integrateWebSearch') +async def integrate_web_search(request: Request, + response: Response, + body: IntegrateWebSearchBody, + hxd_info: QalibInfo = Depends(check_hxd_token)): + return await QaLibService(request, response, + hxd_info).integrate_web_search(body) diff --git a/repodir/huixiangdou/web/api/message.py b/repodir/huixiangdou/web/api/message.py new file mode 100644 index 00000000..23ed717a --- /dev/null +++ b/repodir/huixiangdou/web/api/message.py @@ -0,0 +1,18 @@ +from fastapi import APIRouter, Request, Response + +from web.model.chat import WechatRequest +from web.service.message import MessageService + +message_api = APIRouter() + + +@message_api.post('/v1/lark') +async def on_lark_message(request: Request, response: Response): + return await MessageService(request, response).on_lark_message() + + +@message_api.post('/v1/wechat/{suffix}') +async def on_wechat_message(request: Request, response: Response, suffix: str, + body: WechatRequest): + return await MessageService(request, + response).on_wechat_message(body, suffix) diff --git a/repodir/huixiangdou/web/api/qalib.py b/repodir/huixiangdou/web/api/qalib.py new file mode 100644 index 00000000..75099504 --- /dev/null +++ b/repodir/huixiangdou/web/api/qalib.py @@ -0,0 +1,50 @@ +from typing import List + +from fastapi import APIRouter, Depends, File, Request, Response, UploadFile + +from web.middleware.token import check_hxd_token +from web.model.qalib import QalibInfo, QalibPositiveNegative, QalibDeleteDoc +from web.service.qalib import QaLibService + +qalib_api = APIRouter() + + +@qalib_api.post('/v1/getInfo') +async def qalib_info(request: Request, + response: Response, + hxd_info: QalibInfo = Depends(check_hxd_token)): + return await QaLibService(request, response, hxd_info).info() + + +@qalib_api.post('/v1/addDocs') +async def qalib_add_docs(request: Request, + response: Response, + files: List[UploadFile] = File(...), + hxd_info: QalibInfo = Depends(check_hxd_token)): + return await QaLibService(request, response, hxd_info).add_docs(files) + + +@qalib_api.post('/v1/getSampleInfo') +async def qalib_get_sample_info( + request: Request, + response: Response, + hxd_info: QalibInfo = Depends(check_hxd_token)): + return await QaLibService(request, response, hxd_info).get_sample_info() + + +@qalib_api.post('/v1/updateSampleInfo') +async def qalib_update_sample_info( + request: Request, + response: Response, + body: QalibPositiveNegative, + hxd_info: QalibInfo = Depends(check_hxd_token)): + return await QaLibService(request, response, + hxd_info).update_sample_info(body) + + +@qalib_api.post('/v1/deleteDocs') +async def qalib_add_docs(request: Request, + response: Response, + body: QalibDeleteDoc, + hxd_info: QalibInfo = Depends(check_hxd_token)): + return await QaLibService(request, response, hxd_info).delete_docs(body) diff --git a/repodir/huixiangdou/web/api/statistic.py b/repodir/huixiangdou/web/api/statistic.py new file mode 100644 index 00000000..4c86bab7 --- /dev/null +++ b/repodir/huixiangdou/web/api/statistic.py @@ -0,0 +1,10 @@ +from fastapi import APIRouter, Request, Response + +from web.service.statistic import StatisticService + +statistic_api = APIRouter() + + +@statistic_api.get('/v1/total') +async def qalib_info_statistic(request: Request, response: Response): + return await StatisticService(request, response).info_statistic() diff --git a/repodir/huixiangdou/web/config/__init__.py b/repodir/huixiangdou/web/config/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/repodir/huixiangdou/web/config/env.py b/repodir/huixiangdou/web/config/env.py new file mode 100644 index 00000000..5414a818 --- /dev/null +++ b/repodir/huixiangdou/web/config/env.py @@ -0,0 +1,86 @@ +import os + +import lark_oapi as lark + +from web.util.log import log + +logger = log(__name__) + + +class HuixiangDouEnv: + + @classmethod + def print_env(cls): + methods = [ + method for method in cls.__dict__ if + callable(getattr(HuixiangDouEnv, method)) and method != 'print_env' + ] + for method in methods: + f = getattr(HuixiangDouEnv, method) + value = f() + logger.info(f'[config] {method}: {value}') + + @classmethod + def get_cookie_secure(cls) -> bool: + return True if os.getenv('COOKIE_SECURE') else False + + @classmethod + def get_server_port(cls) -> str: + return os.getenv('SERVER_PORT') if os.getenv( + 'SERVER_PORT') else '23333' + + @classmethod + def get_jwt_secret(cls) -> str: + return os.getenv('JWT_SECRET') if os.getenv( + 'JWT_SECRET') else 'HuixiangDou_is_awesome' + + @classmethod + def get_redis_host(cls) -> str: + return 'localhost' if os.getenv('REDIS_HOST') is None else os.getenv( + 'REDIS_HOST') + + @classmethod + def get_redis_password(cls) -> str: + return 'default_password' if os.getenv( + 'REDIS_PASSWORD') is None else os.getenv('REDIS_PASSWORD') + + @classmethod + def get_redis_port(cls) -> int: + return 6379 if os.getenv('REDIS_PORT') is None else os.getenv( + 'REDIS_PORT') + + @classmethod + def get_redis_db(cls) -> int: + return 0 if os.getenv('REDIS_DB') is None else os.getenv('REDIS_DB') + + @classmethod + def _get_default_endpoint(cls) -> str: + return f'http://0.0.0.0:{cls.get_server_port()}/' + + @classmethod + def get_lark_encrypt_key(cls) -> str: + return os.getenv('HUIXIANGDOU_LARK_ENCRYPT_KEY') if os.getenv( + 'HUIXIANGDOU_LARK_ENCRYPT_KEY') else 'thisiskey' + + @classmethod + def get_lark_verification_token(cls) -> str: + return os.getenv('HUIXIANGDOU_LARK_VERIFY_TOKEN') if os.getenv( + 'HUIXIANGDOU_LARK_VERIFY_TOKEN' + ) else 'sMzyjKi9vMlEhKCZOVtBMhhl8x23z0AG' + + @classmethod + def get_message_endpoint(cls) -> str: + endpoint = os.getenv('HUIXIANGDOU_MESSAGE_ENDPOINT') + if not endpoint: + endpoint = cls._get_default_endpoint() + if not endpoint.endswith('/'): + endpoint += '/' + return endpoint + + @classmethod + def get_lark_log_level(cls) -> lark.LogLevel: + return lark.LogLevel.DEBUG + + @classmethod + def get_cookie_samesite(cls) -> str: + return 'none' if HuixiangDouEnv.get_cookie_secure() else 'lax' diff --git a/repodir/huixiangdou/web/config/logging.py b/repodir/huixiangdou/web/config/logging.py new file mode 100644 index 00000000..9d480c66 --- /dev/null +++ b/repodir/huixiangdou/web/config/logging.py @@ -0,0 +1,34 @@ +import logging + +LOGGING_CONFIG = { + 'version': 1, + 'disable_existing_loggers': False, + 'formatters': { + 'default': { + '()': 'uvicorn.logging.DefaultFormatter', + 'fmt': '%(levelprefix)s %(asctime)s - %(message)s', + 'datefmt': '%Y-%m-%d %H:%M:%S', + }, + }, + 'handlers': { + 'default': { + 'formatter': 'default', + 'class': 'logging.StreamHandler', + 'stream': 'ext://sys.stderr', + }, + }, + 'loggers': { + 'uvicorn': { + 'handlers': ['default'], + 'level': 'INFO' + }, + 'uvicorn.error': { + 'level': 'INFO' + }, + 'uvicorn.access': { + 'handlers': ['default'], + 'level': 'INFO', + 'propagate': False + }, + }, +} diff --git a/repodir/huixiangdou/web/constant/__init__.py b/repodir/huixiangdou/web/constant/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/repodir/huixiangdou/web/constant/biz_constant.py b/repodir/huixiangdou/web/constant/biz_constant.py new file mode 100644 index 00000000..5669ade3 --- /dev/null +++ b/repodir/huixiangdou/web/constant/biz_constant.py @@ -0,0 +1,66 @@ +import os + +from web.util.log import log + +logger = log(__name__) + +# redis +RDS_KEY_LOGIN = 'HuixiangDou:login:info' +RDS_KEY_QALIB_INFO = 'HuixiangDou:qalib:info' +RDS_KEY_SUFFIX_TO_QALIB = 'HuixiangDou:suffixMap' +RDS_KEY_SAMPLE_INFO = 'HuixiangDou:qalib:sample' +RDS_KEY_PIPELINE = 'HuixiangDou:pipeline' +RDS_KEY_HXD_TASK = 'HuixiangDou:Task' +RDS_KEY_HXD_TASK_RESPONSE = 'HuixiangDou:TaskResponse' +RDS_KEY_HXD_CHAT_RESPONSE = 'HuixiangDou:ChatResponse' +RDS_KEY_SCHEDULER = 'HuixiangDou:sched' +RDS_KEY_QUERY_INFO = 'HuixiangDou:query' +RDS_KEY_FEEDBACK_CASE = 'HuixiangDou:feedback:case' +RDS_KEY_LARK_CONFIG = 'HuixiangDou:lark' +RDS_KEY_QUERY_ID_TO_FETCH = 'HuixiangDou:queryIdToFetch' +RDS_KEY_AGENT_LARK_USED = 'HuixiangDou:agentLarkUsed' +RDS_KEY_AGENT_WECHAT_USED = 'HuixiangDou:agentWechatUsed' +RDS_KEY_QALIB_ACTIVE = 'HuixiangDou:monthlyActive' +RDS_KEY_TOTAL_INFERENCE_NUMBER = 'HuixiangDou:inference' +RDS_KEY_USER_INFERENCE = 'HuixiangDou:userInference' + +# jwt +JWT_HEADER = {'alg': 'HS256'} + +# cookie +HXD_COOKIE_KEY = 'hxd_token' + +# error codes +ERR_QALIB_API_NO_ACCESS = {'code': 'A1000', 'msg': 'No access to this api'} +ERR_ACCESS_CREATE = {'code': 'A2000', 'msg': 'Create QA lib failed'} +ERR_ACCESS_LOGIN = { + 'code': 'A2001', + 'msg': "QA lib's name already exists or password does not match" +} +ERR_QALIB_INFO_NOT_FOUND = { + 'code': 'A2002', + 'msg': "QA lib's info is not found" +} +ERR_QALIB_ADD_DOCS_ONCE_MAX = { + 'code': 'A2003', + 'msg': "Exceeded the maximum total files' size for single adding docs" +} +ERR_CHAT = {'code': 'A3001', 'msg': 'Chat error'} +ERR_NOT_EXIST_CHAT = {'code': 'A3002', 'msg': 'Query not exist'} +ERR_INFO_UPDATE_FAILED = {'code': 'A3003', 'msg': 'Info update failed'} +ERR_CHAT_CASE_FEEDBACK = {'code': 'A3004', 'msg': 'Case feedback failed'} +CHAT_STILL_IN_QUEUE = {'code': 'A3100', 'msg': 'Chat processing'} + +# biz +HXD_QALIB_STATUS_INIT = 0 +HXD_QALIB_STATUS_CREATED = 1 +HXD_PIPELINE_QALIB_CREATE_SUCCESS = 1 +HXD_PIPELINE_QALIB_CREATE_FAILED = -1 + +# 1 day +HXD_CHAT_TTL = 86400 + +# 1000 MB +HXD_ADD_DOCS_ONCE_MAX = 1048576000 + +HXD_FEATURE_STORE_SUFFIX_LENGTH = 4 diff --git a/repodir/huixiangdou/web/front-end/.eslintignore b/repodir/huixiangdou/web/front-end/.eslintignore new file mode 100644 index 00000000..59edc655 --- /dev/null +++ b/repodir/huixiangdou/web/front-end/.eslintignore @@ -0,0 +1,6 @@ +node_modules +dist +build +.umi +*.d.ts +lib \ No newline at end of file diff --git a/repodir/huixiangdou/web/front-end/.eslintrc.cjs b/repodir/huixiangdou/web/front-end/.eslintrc.cjs new file mode 100644 index 00000000..1348488f --- /dev/null +++ b/repodir/huixiangdou/web/front-end/.eslintrc.cjs @@ -0,0 +1,91 @@ +module.exports = { + root: true, + parser: '@typescript-eslint/parser', + env: { + es6: true, + browser: true, + node: true + }, + extends: [ + 'eslint:recommended', + 'plugin:@typescript-eslint/eslint-recommended', + 'plugin:@typescript-eslint/recommended', + 'airbnb', + 'airbnb/hooks', + ], + settings: { + 'import/resolver': { + node: { + paths: [ + 'src' + ], + extensions: [ + '.js', + '.jsx', + '.ts', + '.tsx' + ] + } + } + }, + rules: { + 'import/extensions': 'off', + '@typescript-eslint/no-var-requires': 'off', + 'arrow-body-style': 'off', + 'comma-dangle': 'off', + 'import/no-unresolved': 'off', + 'jsx-a11y/anchor-is-valid': 'off', + 'jsx-a11y/click-events-have-key-events': 'off', + 'jsx-a11y/control-has-associated-label': 'off', + 'jsx-a11y/no-static-element-interactions': 'off', + 'no-console': 'off', + 'no-param-reassign': 'off', + 'no-plusplus': 'off', + 'no-underscore-dangle': 'off', + 'arrow-parens': 'off', + 'react/forbid-prop-types': 'off', + 'react/react-in-jsx-scope': 'off', + 'react/jsx-filename-extension': 'off', + 'react/jsx-props-no-spreading': 'off', + 'react/require-default-props': 'off', + 'react/function-component-definition': 'off', + 'jsx-a11y/alt-text': 'Off', + 'react/jsx-indent': [2, 4], + 'react/jsx-indent-props': [2, 4], + 'react/no-unused-prop-types': 'warn', + 'react/prop-types': 'off', + 'react/destructuring-assignment': 'off', + 'react/no-array-index-key': 'warn', + eqeqeq: ['error', 'allow-null'], + 'prefer-const': 'warn', + 'array-callback-return': 'warn', + // https://stackoverflow.com/questions/48391913/eslint-error-cannot-read-property-range-of-null + 'template-curly-spacing': 'off', + 'prefer-destructuring': 'off', + 'guard-for-in': 'warn', + camelcase: ['warn'], + 'import/prefer-default-export': 'off', + 'no-useless-escape': 'warn', + 'no-unused-expressions': 'warn', + 'no-restricted-syntax': 'off', + 'max-len': ['warn', { + code: 200 + }], + 'no-shadow': ['warn'], + indent: ['error', 4, { + ignoredNodes: ['TemplateLiteral'] + }], + semi: ['warn', 'always', { omitLastInOneLineBlock: true }], + 'no-proto': 'error', + // https://github.com/import-js/eslint-plugin-import/issues/653#issuecomment-840228881 + 'no-unused-vars': 'off', + '@typescript-eslint/no-unused-vars': ['warn'], + 'react-hooks/exhaustive-deps': 'warn', + 'class-methods-use-this': 'warn', + 'prefer-promise-reject-errors': 'warn', + 'object-shorthand': 'warn', + 'import/no-extraneous-dependencies': 'off', + '@typescript-eslint/no-namespace': 'off', + 'no-restricted-operator': 'off', + } +}; diff --git a/repodir/huixiangdou/web/front-end/.gitignore b/repodir/huixiangdou/web/front-end/.gitignore new file mode 100644 index 00000000..fc966ae9 --- /dev/null +++ b/repodir/huixiangdou/web/front-end/.gitignore @@ -0,0 +1,23 @@ +# dependencies +/node_modules +/npm-debug.log* +/yarn-error.log +/yarn.lock +package-lock.json + +# production +/dist +/build + +# misc +.DS_Store +.idea + +# umi +/src/.umi +/src/.umi-production +/src/.umi-test +/.env.local + +/maps +.husky \ No newline at end of file diff --git a/repodir/huixiangdou/web/front-end/.npmrc b/repodir/huixiangdou/web/front-end/.npmrc new file mode 100644 index 00000000..f3cac2fd --- /dev/null +++ b/repodir/huixiangdou/web/front-end/.npmrc @@ -0,0 +1,2 @@ +# 改变远程仓库地址 +# registry=https://registry.npmjs.org/ diff --git a/repodir/huixiangdou/web/front-end/dist/assets/bean1-002ba51d.png b/repodir/huixiangdou/web/front-end/dist/assets/bean1-002ba51d.png new file mode 100644 index 0000000000000000000000000000000000000000..069bbe2a2cd64aa8c1765977f80ba14095f13392 GIT binary patch literal 4432 zcmV-W5wGrvP)FCudk1{wY8<;4z)hf7u593iflGto79sfip><& zrx@X2QKHCKetCIl^$=rB+{&2j+7F`9s2@QYa0go~-CGljU%5Ay8rnWQF|h0M#Bj4^ znGpdt;tQCOP-MC+QW>o-ukNU?YdqSxtU6l4S|)C0YzDu@CPdVKA(?IO9X$8c=?f>G z8yz2PGL#%J4ZyH;;KBk$_`t9FK>-cI{)nhuR@b+@YQ>vvEgKIsmNxd!2lTfFFec8_ z9KCq>YbVcq{`};0Y_%csV6r@LFz{-C;!}Zh4k|)Z5ggbGa0~C5AHrG@R7Yxt*00+1 z*1D!`KWSUG_4ticSA*N!`h*D5wTa>C-8Pav)A0V0gWtU{a^{&U+878n2Nfo$tJKs;l$sDSJ}2Gy_zXp<1GJR1E(Z^4_c67o)pM zf@R~Y8eVt@KTh}dc06<9+>!mdl~K8BgHQ1x2pepJOX(5FfD#7Vb|iC2m4=`c6-M^4Od2{NUi(q0U{lKLv(} z2SVW>M`^I5XF6U;+LIN+XP{*AvXcZ=je)SaYiBBItO~-gf$J6ujG-h@$|y}?2`N=I z=to<%L5D14&B@UFhkp83-Kk@3w-q1(__U|*+5dj~-`<)wM`}%90t`C|%c@F80+2Q? zWjfl&(xXjjo&b|)K=Y%NG#f0Ao)aC4Pa&CMvu(UbTV&`vQDXERby}>7FABO7!xX^K zE^XC>G|pkF*~$aIc>Cqs3a~$Oto_5o2mf(Ozg(^rS?DzK5D15$cKPy?DM;r>VnchT zG7}N-p=1;r1mm)k?s3KGUu=$}=QI9Z$oFK6sKvlht+`^NG;uDu(H1Otrf6c@9Mw!yS8F0v?!ZNlLC^9DCis|COf^y zj>!y02vlGE%%+YjCI}4Gm?J_)s*ED#=y;}5S0WeUpKmN&*OEAyG0|Op==gizx-b;o z%8CVq`~gsLB}~_#u4c`}y$!$jUYA(iGCVeLFX@+VnkWIqS*TDP>3AE@oIR&kM{0#K zK#3QGZb4f_UKMQ;4H(TK?YlI5_5u9rwD-)CNU*Q}{FgrZ?9iY2OM{R|qf^Op@asjO z<-+jr?#KU_o;h;-;}_!j@vv>1pb1(OdPwe*SL%W(PM?wFY{`P#X+uDE3*U2jPg0bI ziS~S<_tZBF)pXGS$CJG+C!^6nNm+VW$DNE6m1Cw+g5W~%<(*&o&hFYrKm2s;{f9n3 z``Pas0v(G=YNMiOROFhx&b(eQHj|yptAb#>XH^1*8IS6P4{)*`h5!dIceQqoz5D2r z0ZeBSdZ;)Yi0E4+<8>o`FD0d8L zZ{$@S>jT#c9hD?ci&t_x;NIH=m=Ummd{zikZk}c_2wG*y7rybG2OGm%PA+M(P4(PF z^5mu~Q?c#QGtqCvQ&XE0nJLULb71>T1Y}Expvs^&&sMMK&4yaXy-hfis@4 z&_hEYvu^d;cQG+H<^^~$ee~{+IzRZ!v;C*OI-MD37TS@onEYK1g<)Nt3@4JF3>+zG zC^as~jUyOLpc%$BxezA}fmv{*H`}5YcM$Uwa zQX0s{AZq0-thj@~2oR^-P?e0AN`P?!hF~e>r;=51^G2sCSEN%Q6LciFxMs`Dg~^bh zGDQ~1ilxEInl)!DSaafr04Y%&jzyo1B?h--%(zT{sLG_6Bg;`6L#!oaZm4oE#iHF4 zPNo6Z@s2{7`Sb=)+;XE$3Iqc3Y8$o;55iDjL-WQT7e4m|8yqxFZ0PDa_0(i?M8)z$ z#xc3`uCyGN%w&EDl$pS+#Omq-m<#NNdHv#G+LX1R+=%9I^o8DQspj57~Bdk)3DG7oxT^K#9_HBx!gH09hu ze+X+&6{^c>$5z3rfoo@*Y2)HtUC-d9eHkMW;zTR>6|fMH#hI+noZ=D#JafQ#Gy?_s znLs~7LrA1pdFw$IRPj68=8Yd3y;Ede}Bf<1jhPYjLrZ8eaq zG7yA}(V$5cRml#?A){&tY2=M3xHrJxBs_X14oAvG0y;fWm_O{4*-YFp1%yTM77i0TzY6Ysw z8~WQ@?thiBEhgLuVBh%Zy$K$x%ZO4p@B5()+=VaOv%h36C++8TBfzt_+{{5$}DD*!d+4YB*%_q@Dw<6HS)d5xD0j~|{i)#+yNtlw1*11{z|!H$VFF z!|R`W`DV`Rr3A-fqphZC6iMcoI7h4q=n67`d-kRjcuKlgVd0IrZ2 zU`b}0mZbl&LI<=SSg~yF_|EpduY76E)34m@OcG!&t2f}Gqe^i_k<=Zk3C0N~xJ&3b zyDgR|&GJCX&2vGLH}%dbI2i#Z^p?&g#TaB^sEn)_-`2M0Umsk%{~vB;76}k5;ec(R z5&L{Z7lDqPrJ^{88f1wyIS|e6tZRvygNzPVoTS{Pz$P-nC3CX3%qhu^csMAku+n#q|?-{A~%GILRdr#-?^q$V*Z-L&T=WE_Go?1#W%9SFM z8iAfs$6VK>KO!KPgBxgPObD1oP+Qb6yyu=r{&vrrO>cPY*h1SE|H>6Ryz|4)4!k}% z5p7Fb6Ed5iKd#tT$N}lYGD_(34?1%-4^Sas?_LsARuKF+u7cxp%sb62f@2ZU*0lB9 zeRu8t>-!q&-)3y_e<{1JD2@$%^4Kp9|LC=`M0Zn~Pk`#zz{1R3dQ%NkC{!zr-CP{> zfJyGecF1AI{orTCC_N;OkoqeRm-2Ou8@gMYHvgcpX3fEsk@-M_9Gnc9p^8+jNp!A*qYACf(FbMgy0jribk2cngNrWYmQYdYrI&=DwD8;?YaQ#8Ou(Io*(LVyez9vKOfIcR0*3@lXhPN&-cubzB5N( zGw_@Xaiip}h@G{BJlQP?l;kVRs=C{EZTnGedE=q#;)dP^Xc=27iQN=HYQ!z}i*x@G z(!&0ur+@wYaAL5O$X`-SN`&d}R32R~kHsj4f%7LFIwIGE&*4VxAgD$FDuQLHmgWr| ztu32=xU8rq8i>^PG_Zyo+~zi~JM7POH4P4ReRXVVE$^q+muOXT9bORBBN(Q+_I!bAON$xq;je_RCcVoyymm| z>gLWBf##mY#IuFA1!tD-Up0IaFi@E>j53}#it!-SFTdo%D2ha;>jRM?b_djOuJ#*y Wk)6Hywj^c%0000K5Y<<9bHdbZfWz(L$p2(&_0s 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 + + + + + + + HuixiangDou + + + + + + + + + + +
+ + + + + + diff --git a/repodir/huixiangdou/web/front-end/dist/logo.png b/repodir/huixiangdou/web/front-end/dist/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..553203157815e1c713c7960357fae48e1a6d2e32 GIT binary patch literal 26656 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 + + + + + + + HuixiangDou + + + +
+ + + diff --git a/repodir/huixiangdou/web/front-end/mock/db.json b/repodir/huixiangdou/web/front-end/mock/db.json new file mode 100644 index 00000000..f14bcdd8 --- /dev/null +++ b/repodir/huixiangdou/web/front-end/mock/db.json @@ -0,0 +1,27 @@ +{ + "posts": [ + { + "id": 1, + "title": "json-server", + "author": "typicode" + } + ], + "comments": [ + { + "id": 1, + "body": "some comment", + "postId": 1 + } + ], + "profile": { + "name": "typicode" + }, + "userinfo": { + "code": 0, + "data": { + "id": 1, + "name": "李剑阁", + "job": "scientist" + } + } +} \ No newline at end of file diff --git a/repodir/huixiangdou/web/front-end/package.json b/repodir/huixiangdou/web/front-end/package.json new file mode 100644 index 00000000..362e6bd5 --- /dev/null +++ b/repodir/huixiangdou/web/front-end/package.json @@ -0,0 +1,60 @@ +{ + "name": "@sea-lion/app-vite-template", + "version": "0.1.10", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "build:aliyun-dev": "tsc && vite build --mode development", + "build:aliyun-staging": "tsc && vite build --mode staging", + "build:aliyun-prod": "tsc && vite build --mode production", + "lint": "eslint src --ext .js,.jsx,.ts,.tsx --report-unused-disable-directives --max-warnings 999", + "lint-fix": "eslint src --fix --ext .js,.jsx,.ts,.tsx", + "postinithook": "husky add .husky/pre-commit \"npx lint-staged\"", + "inithook": "husky install .husky", + "preview": "npm run build:aliyun-prod && vite preview", + "mock": "json-server --watch mock/db.json" + }, + "lint-staged": { + "*.{js,jsx,ts,tsx}": [ + "npm run lint" + ] + }, + "dependencies": { + "antd": "^5.14.2", + "axios": "^1.4.0", + "classnames": "^2.3.2", + "js-cookie": "^3.0.5", + "qs": "^6.11.2", + "query-string": "^8.1.0", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-intl": "^6.4.4", + "react-router-dom": "^6.13.0", + "sanitize-html": "^2.12.1", + "sea-lion-ui": "latest" + }, + "devDependencies": { + "@babel/plugin-proposal-optional-chaining": "^7.21.0", + "@types/node": "^20.3.1", + "@types/react": "^18.0.37", + "@types/react-dom": "^18.0.11", + "@types/react-router-dom": "^5.3.3", + "@typescript-eslint/eslint-plugin": "^5.59.0", + "@typescript-eslint/parser": "^5.59.0", + "@vitejs/plugin-legacy": "^4.1.1", + "@vitejs/plugin-react": "^4.0.0", + "eslint": "^8.38.0", + "eslint-config-airbnb": "^19.0.4", + "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-react-refresh": "^0.3.4", + "husky": "^8.0.3", + "json-server": "^0.17.3", + "less": "^4.1.3", + "lint-staged": "^13.2.2", + "typescript": "^5.0.2", + "vite": "^4.3.9", + "vite-babel-plugin": "^0.0.2", + "vite-plugin-cdn-import": "^0.3.5" + } +} diff --git a/repodir/huixiangdou/web/front-end/public/logo.png b/repodir/huixiangdou/web/front-end/public/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..553203157815e1c713c7960357fae48e1a6d2e32 GIT binary patch literal 26656 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
1k{_ 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针对不同的环境打包命令不同,比如线上环境的命令npm run build:aliyun-prod

+ +## preview +npm run preview +

这是vite项目特有的命令,因为vite的serve和build出的代码不一致,上线前需要用preview检测打包结果是否和serve一致

+ +## mock +npm run mock + +# 2. Ability config +
当前模板支持动态配置能力
+
src/config/auth.ts: 支持是否开启该功能(default false)clientId, 接口白名单与网页白名单
+
src/config/log.ts: 支持是否开启该功能(default false)ga4 measurement id
+
src/config/base-url.ts: 各个环境接口访问host和api prefix
+ +

更多细节请查看配置文件注释

+ diff --git a/repodir/huixiangdou/web/front-end/scripts/alias.ts b/repodir/huixiangdou/web/front-end/scripts/alias.ts new file mode 100644 index 00000000..6ed260f1 --- /dev/null +++ b/repodir/huixiangdou/web/front-end/scripts/alias.ts @@ -0,0 +1,20 @@ +import { resolvePath } from './utils'; + +const alias = { + '@': resolvePath('./src'), + '@components': resolvePath('./src/components'), + '@layouts': resolvePath('./src/layouts'), + '@assets': resolvePath('./src/assets'), + '@pages': resolvePath('./src/pages'), + '@services': resolvePath('./src/services'), + '@utils': resolvePath('./src/utils'), + '@styles': resolvePath('./src/styles'), + '@routes': resolvePath('./src/routes'), + '@config': resolvePath('./src/config'), + '@locales': resolvePath('./src/locales'), + '@constants': resolvePath('./src/constants'), + '@interceptors': resolvePath('./src/interceptors'), + '@hooks': resolvePath('./src/hooks') +}; + +export default alias; diff --git a/repodir/huixiangdou/web/front-end/scripts/import-to-cdn.ts b/repodir/huixiangdou/web/front-end/scripts/import-to-cdn.ts new file mode 100644 index 00000000..595ef517 --- /dev/null +++ b/repodir/huixiangdou/web/front-end/scripts/import-to-cdn.ts @@ -0,0 +1,12 @@ +export default [ + { + name: 'react', + var: 'React', + path: 'https://openmmlab-share.oss-cn-hangzhou.aliyuncs.com/asserts/react@18.2.0/react.production.min.js' + }, + { + name: 'react-dom', + var: 'ReactDOM', + path: 'https://openmmlab-share.oss-cn-hangzhou.aliyuncs.com/asserts/react@18.2.0/react-dom.production.min.js' + } +]; diff --git a/repodir/huixiangdou/web/front-end/scripts/index.ts b/repodir/huixiangdou/web/front-end/scripts/index.ts new file mode 100644 index 00000000..965bd426 --- /dev/null +++ b/repodir/huixiangdou/web/front-end/scripts/index.ts @@ -0,0 +1,3 @@ +export { default as ProxyConfig } from './proxy'; +export { default as ImportToCDNList } from './import-to-cdn'; +export { default as alias } from './alias'; diff --git a/repodir/huixiangdou/web/front-end/scripts/proxy.ts b/repodir/huixiangdou/web/front-end/scripts/proxy.ts new file mode 100644 index 00000000..17fa99e6 --- /dev/null +++ b/repodir/huixiangdou/web/front-end/scripts/proxy.ts @@ -0,0 +1,13 @@ +// https://github.com/http-party/node-http-proxy#options +const ProxyConfig = { + '/api': { + target: 'http://localhost:8080', + changeOrigin: true, + secure: false, + rewrite: path => { + return path.replace('^', ''); + }, + } +}; + +export default ProxyConfig; diff --git a/repodir/huixiangdou/web/front-end/scripts/utils.ts b/repodir/huixiangdou/web/front-end/scripts/utils.ts new file mode 100644 index 00000000..86a93262 --- /dev/null +++ b/repodir/huixiangdou/web/front-end/scripts/utils.ts @@ -0,0 +1,3 @@ +import path from 'path'; + +export const resolvePath = p => path.resolve(__dirname, '..', p); diff --git a/repodir/huixiangdou/web/front-end/src/app.tsx b/repodir/huixiangdou/web/front-end/src/app.tsx new file mode 100644 index 00000000..f4eac845 --- /dev/null +++ b/repodir/huixiangdou/web/front-end/src/app.tsx @@ -0,0 +1,16 @@ +import { GlobalLang } from '@components/global-lang'; +import RouterRoot from './routes'; +import './styles/index.less'; +import 'sea-lion-ui/dist/index.css'; + +console.log(import.meta.env.VITE_NODE); + +const App = () => { + return ( + + + + ); +}; + +export default App; diff --git a/repodir/huixiangdou/web/front-end/src/assets/imgs/bean.png b/repodir/huixiangdou/web/front-end/src/assets/imgs/bean.png new file mode 100644 index 0000000000000000000000000000000000000000..aa3d20475fd90413a26096f8c2bdaa78bb641226 GIT binary patch literal 28293 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 Wk)6Hywj^c%0000K5Y<<9bHdbZfWz(L$p2(&_0s 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 { + disabled?: boolean; + onClick?: () => void; + children?: ReactNode; + className?: string; +} + +const Button: FC = ({ + disabled = false, + onClick, children, + className, +}) => { + const handleClick = () => { + if (disabled) { + return; + } + onClick(); + }; + return ( +
+ {children} +
+ ); +}; + +export default Button; diff --git a/repodir/huixiangdou/web/front-end/src/components/components-portal/components-portal.tsx b/repodir/huixiangdou/web/front-end/src/components/components-portal/components-portal.tsx new file mode 100644 index 00000000..1c66c6fc --- /dev/null +++ b/repodir/huixiangdou/web/front-end/src/components/components-portal/components-portal.tsx @@ -0,0 +1,7 @@ +import { createPortal } from 'react-dom'; + +const ComponentPortal = ({ children, wrapperId = '' }) => { + return createPortal(children, document.getElementById(wrapperId) || document.body); +}; + +export default ComponentPortal; diff --git a/repodir/huixiangdou/web/front-end/src/components/copy-code/copy-code.module.less b/repodir/huixiangdou/web/front-end/src/components/copy-code/copy-code.module.less new file mode 100644 index 00000000..10983d7b --- /dev/null +++ b/repodir/huixiangdou/web/front-end/src/components/copy-code/copy-code.module.less @@ -0,0 +1,22 @@ +.copy-code { + display: flex; + gap: 4px; + align-items: center; + width: 100%; + .code { + font-size: 14px; + line-height: 16px; + padding: 8px 0; + cursor: pointer; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: calc(100% - 36px); + color: #047600; + } + .copy { + cursor: pointer; + color: #9D9D9D; + margin-left: 4px; + } +} \ No newline at end of file diff --git a/repodir/huixiangdou/web/front-end/src/components/copy-code/copy-code.tsx b/repodir/huixiangdou/web/front-end/src/components/copy-code/copy-code.tsx new file mode 100644 index 00000000..518128d5 --- /dev/null +++ b/repodir/huixiangdou/web/front-end/src/components/copy-code/copy-code.tsx @@ -0,0 +1,29 @@ +import { IconFont, message } from 'sea-lion-ui'; +import styles from './copy-code.module.less'; + +export interface CopyCodeProps { + code: string; +} + +const CopyCode = (props: CopyCodeProps) => { + const { code } = props; + const copy = () => { + const input = document.createElement('input'); + input.value = code; + document.body.appendChild(input); + input.select(); + document.execCommand('copy'); + message.success('复制成功'); + document.body.removeChild(input); + }; + return ( +
+
{code}
+
+ +
+
+ ); +}; + +export default CopyCode; diff --git a/repodir/huixiangdou/web/front-end/src/components/global-lang/global-lang-context.ts b/repodir/huixiangdou/web/front-end/src/components/global-lang/global-lang-context.ts new file mode 100644 index 00000000..a322440f --- /dev/null +++ b/repodir/huixiangdou/web/front-end/src/components/global-lang/global-lang-context.ts @@ -0,0 +1,11 @@ +import { createContext } from 'react'; +import { Language } from '@utils/utils'; + +const noop = (l: Language) => undefined; + +export const LangDefault = { + locale: '', + setLocale: noop +}; + +export const GlobalLangeContext = createContext(LangDefault); diff --git a/repodir/huixiangdou/web/front-end/src/components/global-lang/global-lang.tsx b/repodir/huixiangdou/web/front-end/src/components/global-lang/global-lang.tsx new file mode 100644 index 00000000..1b43dbf5 --- /dev/null +++ b/repodir/huixiangdou/web/front-end/src/components/global-lang/global-lang.tsx @@ -0,0 +1,31 @@ +import { + FC, useCallback, useState, useMemo +} from 'react'; +import { IntlProvider } from 'react-intl'; +import { + getLang, Language, setLang +} from '@utils/utils'; +import locales from '@/locales'; +import { GlobalLangeContext } from './global-lang-context'; + +const GlobalLang: FC = ({ children }) => { + const [locale, setLocale] = useState(getLang()); + + const setCurrentLocale = useCallback((lang: Language) => { + setLocale(lang); + setLang(lang); + }, []); + + // 子孙组件通过context获取setLocale可以更改中英文 + const value = useMemo(() => ({ locale, setLocale: setCurrentLocale }), [locale, setCurrentLocale]); + + return ( + + + {children} + + + ); +}; + +export default GlobalLang; diff --git a/repodir/huixiangdou/web/front-end/src/components/global-lang/index.tsx b/repodir/huixiangdou/web/front-end/src/components/global-lang/index.tsx new file mode 100644 index 00000000..0ef66550 --- /dev/null +++ b/repodir/huixiangdou/web/front-end/src/components/global-lang/index.tsx @@ -0,0 +1,2 @@ +export { default as GlobalLang } from './global-lang'; +export { GlobalLangeContext } from './global-lang-context'; diff --git a/repodir/huixiangdou/web/front-end/src/components/header/header.module.less b/repodir/huixiangdou/web/front-end/src/components/header/header.module.less new file mode 100644 index 00000000..bc71135b --- /dev/null +++ b/repodir/huixiangdou/web/front-end/src/components/header/header.module.less @@ -0,0 +1,19 @@ +.header { + padding: 0 50px; + height: 64px; + display: flex; + align-items: center; + justify-content: flex-end; + gap: 24px; + + .feedback { + cursor: pointer; + } + + .language { + cursor: pointer; + .chosen { + font-weight: bold; + } + } +} diff --git a/repodir/huixiangdou/web/front-end/src/components/header/header.tsx b/repodir/huixiangdou/web/front-end/src/components/header/header.tsx new file mode 100644 index 00000000..45c8db98 --- /dev/null +++ b/repodir/huixiangdou/web/front-end/src/components/header/header.tsx @@ -0,0 +1,38 @@ +import { GlobalLangeContext } from '@components/global-lang'; +import { useContext } from 'react'; +import { useLocale } from '@hooks/useLocale'; +import styles from './header.module.less'; + +const Header = () => { + const { locale, setLocale } = useContext(GlobalLangeContext); + const locales = useLocale('home'); + return ( +
+
window.open('https://github.com/InternLM/HuixiangDou/issues')} + > + {locales.feedback} +
+
+ setLocale('zh-CN')} + className={locale === 'zh-CN' && styles.chosen} + > + 中 + {' '} + + / + setLocale('en-US')} + className={locale === 'en-US' && styles.chosen} + > + {' '} + EN + +
+
+ ); +}; + +export default Header; diff --git a/repodir/huixiangdou/web/front-end/src/components/notification/emoji-wrapper.tsx b/repodir/huixiangdou/web/front-end/src/components/notification/emoji-wrapper.tsx new file mode 100644 index 00000000..6333488f --- /dev/null +++ b/repodir/huixiangdou/web/front-end/src/components/notification/emoji-wrapper.tsx @@ -0,0 +1,21 @@ +import { FC, ReactNode } from 'react'; +import styles from './notification.module.less'; + +interface EmojiWrapperProps { + emoji?: string; + children?: ReactNode; +} + +const heart = 'https://oss.openmmlab.com/www/home/heart_3d.png'; +const EmojiWrapper: FC = ({ emoji = heart, children }) => { + return ( +
+ {children} + + + +
+ ); +}; + +export default EmojiWrapper; diff --git a/repodir/huixiangdou/web/front-end/src/components/notification/notification.module.less b/repodir/huixiangdou/web/front-end/src/components/notification/notification.module.less new file mode 100644 index 00000000..5793d4f1 --- /dev/null +++ b/repodir/huixiangdou/web/front-end/src/components/notification/notification.module.less @@ -0,0 +1,136 @@ +.notification { + position: absolute; + right: 24px; + top: 48px; + padding: 24px; + border-radius: 12px; + box-shadow: 2px 2px 10px #80808033; + background: #FFFFFF; + font-size: 16px; + line-height: 24px; + max-width: 360px; + white-space: pre-wrap; + .footer { + margin-top: 24px; + display: flex; + gap: 16px; + justify-content: flex-end; + .btn { + cursor: pointer; + padding: 4px 8px; + border-radius: 4px; + } + .confirm { + .btn(); + background: #def9dc; + color: #198a1e; + } + .cancel { + color: #d5d5d5; + .btn(); + } + } +} + +.emoji-wrapper { + position: relative; + .emoji1 { + position: absolute; + display: none; + bottom: 35px; + right: 15px; + width: 36px; + transform: rotate(-10deg); + } + .emoji2 { + position: absolute; + display: none; + bottom: 35px; + right: 15px; + width: 30px; + opacity: 0.1; + } + .emoji3 { + position: absolute; + display: none; + bottom: 35px; + right: 15px; + width: 40px; + opacity: 0.1; + transform: rotate(20deg); + } +} +.emoji-wrapper:hover .emoji1 { + display: block; + animation: 2.1s ease-in-out -1s sparkling1 infinite; +} +.emoji-wrapper:hover .emoji2 { + display: block; + animation: 2s ease-in-out -1s sparkling2 infinite; +} +.emoji-wrapper:hover .emoji3 { + display: block; + animation: 2.2s ease-in-out -1s sparkling3 infinite; +} +@keyframes sparkling1 { + 0% { + opacity: 0; + } + 15% { + opacity: 0.2; + } + 60% { + opacity: 0.8; + } + 70% { + opacity: 0.8; + } + 100% { + opacity: 0; + bottom: 90px; + right: 80px; + font-size: 30px; + transform: rotate(-20deg); + } +} +@keyframes sparkling2 { + 0% { + opacity: 0; + } + 15% { + opacity: 0.2; + } + 60% { + opacity: 0.8; + } + 70% { + opacity: 0.8; + } + 100% { + opacity: 0; + bottom: 100px; + right: 45px; + font-size: 40px; + transform: rotate(-5deg); + } +} +@keyframes sparkling3 { + 0% { + opacity: 0; + } + 15% { + opacity: 0.2; + } + 60% { + opacity: 0.8; + } + 70% { + opacity: 0.8; + } + 100% { + opacity: 0; + bottom: 90px; + right: 10px; + font-size: 35px; + } +} diff --git a/repodir/huixiangdou/web/front-end/src/components/notification/notification.tsx b/repodir/huixiangdou/web/front-end/src/components/notification/notification.tsx new file mode 100644 index 00000000..d1987537 --- /dev/null +++ b/repodir/huixiangdou/web/front-end/src/components/notification/notification.tsx @@ -0,0 +1,47 @@ +import { FC, ReactNode } from 'react'; +import { notification } from '@components/notification/use-notification'; +import EmojiWrapper from '@components/notification/emoji-wrapper'; +import { useLocale } from '@hooks/useLocale'; +import styles from './notification.module.less'; + +export interface NotificationProps { + title: string; + content: string; + notificationKey: string; + children?: ReactNode; +} + +const Notification: FC = ({ + title, + content, + notificationKey, +}) => { + const locales = useLocale('components'); + + return ( +
+
{title}
+
{content}
+
+
notification.unmountNotification(notificationKey)} + > + {locales.hide4ever} +
+ +
{ + window.open('https://github.com/InternLM/HuixiangDou/'); + }} + > + {locales.goStar} +
+
+
+
+ ); +}; + +export default Notification; diff --git a/repodir/huixiangdou/web/front-end/src/components/notification/use-notification.tsx b/repodir/huixiangdou/web/front-end/src/components/notification/use-notification.tsx new file mode 100644 index 00000000..62d25c3f --- /dev/null +++ b/repodir/huixiangdou/web/front-end/src/components/notification/use-notification.tsx @@ -0,0 +1,45 @@ +import Notification, { NotificationProps } from '@components/notification/notification'; +import { useLocale } from '@hooks/useLocale'; +import ComponentPortal from '@components/components-portal/components-portal'; + +const notificationWrapper = 'global-notification'; + +export const notification = { + notificationContainer: null, + + showNotification(params: NotificationProps) { + if (document.getElementById(notificationWrapper)) { + document.body.removeChild(document.getElementById(notificationWrapper)); + this.notificationContainer = null; + } + if (localStorage.getItem(params.notificationKey)) { + return null; + } + this.notificationContainer = document.createElement('div'); + this.notificationContainer.id = notificationWrapper; + document.body.appendChild(this.notificationContainer); + return ( + + + + ); + }, + unmountNotification(key) { + if (this.notificationContainer) { + localStorage.setItem(key, 'true'); + document.body.removeChild(this.notificationContainer); + this.notificationContainer = null; + } + }, +}; +const useNotification = () => { + const locales = useLocale('components'); + + return notification.showNotification({ + title: '', + content: locales.notificationContent, + notificationKey: '__HuiXiangDou__', + }); +}; + +export default useNotification; diff --git a/repodir/huixiangdou/web/front-end/src/components/upload-item/index.tsx b/repodir/huixiangdou/web/front-end/src/components/upload-item/index.tsx new file mode 100644 index 00000000..93a34bd2 --- /dev/null +++ b/repodir/huixiangdou/web/front-end/src/components/upload-item/index.tsx @@ -0,0 +1,4 @@ +import UploadItem from './upload-item'; + +export * from './upload-item'; +export default UploadItem; diff --git a/repodir/huixiangdou/web/front-end/src/components/upload-item/upload-item.module.less b/repodir/huixiangdou/web/front-end/src/components/upload-item/upload-item.module.less new file mode 100644 index 00000000..1730e5f6 --- /dev/null +++ b/repodir/huixiangdou/web/front-end/src/components/upload-item/upload-item.module.less @@ -0,0 +1,18 @@ +.upload-item { + display: flex; + align-items: flex-start; + gap: 4px; + border-radius: 4px; + padding: 2px 4px; + margin-bottom: 4px; + .name { + max-width: 320px; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + } + .progress { + width: 100%; + margin-top: 4px; + } +} diff --git a/repodir/huixiangdou/web/front-end/src/components/upload-item/upload-item.tsx b/repodir/huixiangdou/web/front-end/src/components/upload-item/upload-item.tsx new file mode 100644 index 00000000..8e043d51 --- /dev/null +++ b/repodir/huixiangdou/web/front-end/src/components/upload-item/upload-item.tsx @@ -0,0 +1,65 @@ +import { FC } from 'react'; +import { IconFont } from 'sea-lion-ui'; +import { LoadingOutlined } from '@ant-design/icons'; +import styles from './upload-item.module.less'; + +export const enum UploadStatus { + init = 'init', + done = 'done', + uploading = 'uploading', + error = 'error', + removed = 'removed', +} + +export interface UploadItemProps { + uid: string; + name: string; + status: UploadStatus; + progress: number; +} + +const StatusColor = { + init: 'lightgrey', + done: 'green', + uploading: 'blue', + error: 'red', + removed: 'darkgrey' +}; + +const StatusIcon = { + init: 'icon-DocOutlined', + done: 'icon-CheckCircleFilled', + uploading: 'icon-HorizontalMoreOutlined', + error: 'icon-CloseCircleFilled', + removed: 'icon-DocOutlined' +}; +const UploadItem: FC = ({ + uid, name, status, progress +}) => { + return ( +
+
+ {status === UploadStatus.uploading ? : ( + + )} +
+
+
{name}
+
+
+
+
+
+ ); +}; + +export default UploadItem; diff --git a/repodir/huixiangdou/web/front-end/src/components/upload/delete-btn.tsx b/repodir/huixiangdou/web/front-end/src/components/upload/delete-btn.tsx new file mode 100644 index 00000000..42189db5 --- /dev/null +++ b/repodir/huixiangdou/web/front-end/src/components/upload/delete-btn.tsx @@ -0,0 +1,49 @@ +import { IconFont, Modal } from 'sea-lion-ui'; +import { useLocale } from '@hooks/useLocale'; +import { useState } from 'react'; +import styles from './upload.module.less'; + +const DeleteBtn = ({ onClick }) => { + const locales = useLocale('components'); + + const [openModal, setOpenModal] = useState(false); + + const handleClick = () => { + setOpenModal(true); + }; + + const confirm = () => { + setOpenModal(false); + onClick(); + }; + + const cancel = () => { + setOpenModal(false); + }; + + return ( + <> +
+ + {locales.deleteSelected} +
+ )} + icon={} + onClose={() => setOpenModal(false)} + > +
{locales.deleteDesc}
+
+
{locales.confirm}
+
{locales.cancel}
+
+
+ + + ); +}; + +export default DeleteBtn; diff --git a/repodir/huixiangdou/web/front-end/src/components/upload/index.tsx b/repodir/huixiangdou/web/front-end/src/components/upload/index.tsx new file mode 100644 index 00000000..7f5ea3b4 --- /dev/null +++ b/repodir/huixiangdou/web/front-end/src/components/upload/index.tsx @@ -0,0 +1,4 @@ +import Upload from './upload'; + +export * from './upload'; +export default Upload; diff --git a/repodir/huixiangdou/web/front-end/src/components/upload/upload.module.less b/repodir/huixiangdou/web/front-end/src/components/upload/upload.module.less new file mode 100644 index 00000000..9f8d98c7 --- /dev/null +++ b/repodir/huixiangdou/web/front-end/src/components/upload/upload.module.less @@ -0,0 +1,167 @@ +.upload { + border: dashed 2px #ccc; + border-radius: 8px; + padding: 24px; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + cursor: pointer; +} +.desc { + color: #9D9D9D; + font-size: 14px; +} +.file-list { + max-height: 300px; + overflow: auto; + scorllbar-width: none; + &::-webkit-scrollbar { + width: 0; + } + .file-item { + border-radius: 4px; + padding: 2px 4px; + display: flex; + align-items: center; + gap: 4px; + } + .file-state { + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + width: 80px; + color: #047600; + } + .file-name { + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + max-width: 280px; + } +} +.file-search { + display: flex; + align-items: center; + gap: 4px; + margin: 4px 1px; + :global { + .seal-input-group .seal-input-inner-container:focus-within { + outline: 1px solid #59a041; + } + } +} +.checkbox-wrapper { + display: flex; + align-items: center; + gap: 4px; + margin-left: 4px; + + .delete { + display: inline-flex; + align-items: center; + gap: 4px; + color: #f92c38; + cursor: pointer; + margin-left: auto; + padding: 4px 6px; + transition: all 0.3s ease-in-out; + border-radius: 4px; + &:hover { + background-color: #ffe2e4; + } + } +} +.checkbox { + cursor: pointer; + /* Add if not using autoprefixer */ + -webkit-appearance: none; + /* Remove most all native input styles */ + appearance: none; + /* For iOS < 15 */ + background-color: white; + /* Not removed via appearance */ + margin: 0; + + font: inherit; + color: currentColor; + width: 1.15em; + height: 1.15em; + border: 0.15em solid #D7D8DD; + border-radius: 0.3em; + + display: grid; + place-content: center; + + &::before { + content: ""; + width: 0.65em; + height: 0.65em; + clip-path: polygon(14% 44%, 0 65%, 50% 100%, 100% 16%, 80% 0%, 43% 62%); + transform: scale(0); + transform-origin: bottom left; + transition: 120ms transform ease-in-out; + background-color: white; + } + + &:checked { + background-color: #59a041; + border: 0.15em solid #c7eaba; + transition: all 120ms ease-in-out; + } + + &:checked::before { + transform: scale(1); + } + + &:disabled { + border: 0.15em solid #e6e6e6; + background: #cdcdcd; + transition: all 120ms ease-in-out; + cursor: not-allowed; + } +} +.mixed-checkbox { + &:checked { + background: radial-gradient(#59a041 50%, #59a041 50%, white 100%); + } + &::before { + background-color: #59a041; + } +} +.modal-footer { + display: flex; + align-items: center; + justify-content: flex-end; + gap: 8px; + margin-top: 24px; + .confirm { + color: #f92c38; + cursor: pointer; + padding: 4px 6px; + transition: all 0.3s ease-in-out; + border-radius: 4px; + background-color: #ffe2e4; + &:hover { + color: red + } + } + .cancel { + color: #929292; + cursor: pointer; + padding: 4px 6px; + transition: all 0.3s ease-in-out; + border-radius: 4px; + &:hover { + background-color: #f2f2f2; + } + } +} +.dragger { + :global { + .ant-upload-list-item-name { + max-width: 320px; + } + } +} + diff --git a/repodir/huixiangdou/web/front-end/src/components/upload/upload.tsx b/repodir/huixiangdou/web/front-end/src/components/upload/upload.tsx new file mode 100755 index 00000000..c944151f --- /dev/null +++ b/repodir/huixiangdou/web/front-end/src/components/upload/upload.tsx @@ -0,0 +1,340 @@ +import { + FC, ReactNode, useEffect, useRef, useState +} from 'react'; +import { addDocs, deleteDocs, FileState } from '@services/home'; +import Button from '@components/button/button'; +import { IconFont, Input, message } from 'sea-lion-ui'; +import { useLocale } from '@hooks/useLocale'; +import UploadItem, { UploadItemProps, UploadStatus } from '@components/upload-item'; +import classNames from 'classnames'; +import DeleteBtn from '@components/upload/delete-btn'; +import styles from './upload.module.less'; + +export interface UploadProps { + docs?: string[]; + afterUpload?: () => void; + afterDelete?: () => void; + filesState?: FileState[]; + children?: ReactNode; +} + +interface FileItemProps { + doc: string; + color: string; + state: string; + checkedController: number; + checkHandler?: (doc: string) => void; +} + +const enum CheckedController { + None = -1, + Partial = 0, + All = 1, +} + +const acceptFileTypes = '.pdf,.txt,.md,.docx,.doc,.xlsx,.xls,.csv,.java,.cpp,.py,.js,.go,.html,.pptx'; + +const getBytesLength = (str) => { + return new Blob([str]).size; +}; + +const FileItem: FC = (props) => { + const { + doc, + color, + state, + checkedController, + checkHandler + } = props; + + const [checked, setChecked] = useState(false); + + const checkChange = (e) => { + setChecked(e.target.checked); + checkHandler(doc); + }; + + useEffect(() => { + if (checkedController === CheckedController.None) { + setChecked(false); + } + if (checkedController === CheckedController.All) { + setChecked(true); + } + }, [checkedController]); + + return ( +
+ + + {state} + + + {doc} + +
+ ); +}; + +const Upload: FC = ({ + docs = [], + afterUpload, + afterDelete, + filesState = [], children +}) => { + const locales = useLocale('components'); + + const fileInputRef = useRef(null); + const [loading, setLoading] = useState(false); + const [pendingFiles, setPendingFiles] = useState([]); // 待上传文件列表 + const [pendingStatus, setPendingStatus] = useState([]); // 待上传文件列表 + const [filter, setFilter] = useState(''); + const [searchValue, setSearchValue] = useState(''); + + const [checkedController, setCheckedController] = useState(CheckedController.Partial); + const [checkedFiles, setCheckedFiles] = useState([]); + + const checkAll = (e) => { + if (e.target.checked) { + setCheckedController(CheckedController.All); + setCheckedFiles(docs); + } else { + setCheckedController(CheckedController.None); + setCheckedFiles([]); + } + }; + + const checkItem = (doc) => { + const _checkedFiles = [...checkedFiles]; + const index = _checkedFiles.indexOf(doc); + if (index > -1) { + _checkedFiles.splice(index, 1); + } else { + _checkedFiles.push(doc); + } + setCheckedFiles(_checkedFiles); + }; + + const handleSearch = () => { + setSearchValue(filter); + }; + + const handleClick = () => { + if (fileInputRef.current) { + fileInputRef.current.click(); + } + }; + + const onFileChange = (e) => { + const _files = e.target.files; + const _pendingStatus = [...pendingStatus]; + const _pendingFiles = [...pendingFiles]; + if (_files.length > 200) { + message.warning(locales.fileCount); + setLoading(false); + return; + } + for (let i = 0; i < _files.length; i++) { + // Validate file name's byte length + if (getBytesLength(_files[i].name) > 255) { + message.warning(locales.nameSize); + setLoading(false); + return; + } + // Validate file size + if (_files[i].size > 1024 * 1024 * 35) { + message.warning(locales.fileSize); + setLoading(false); + return; + } + _pendingFiles.push(_files[i]); + _pendingStatus.push({ + uid: `${new Date().getTime()}_${_files[i].name}`, + name: _files[i].name, + status: UploadStatus.init, + progress: 0, + }); + } + setPendingFiles([..._pendingFiles]); + setPendingStatus([..._pendingStatus]); + }; + + const uploadFile = () => { + setLoading(true); + const _pendingStatus = [...pendingStatus]; + + _pendingStatus.forEach((item) => { + if (item.status !== UploadStatus.done) { + item.status = UploadStatus.uploading; + } + }); + setPendingStatus(_pendingStatus); + + addDocs(pendingFiles) + .then((res) => { + if (res && Array.isArray(res.docs)) { + _pendingStatus.forEach((item) => { + if (item.status === UploadStatus.uploading && res.docs.includes(item.name)) { + item.status = UploadStatus.done; + item.progress = 100; + } else if (item.status === UploadStatus.uploading) { + item.status = UploadStatus.error; + item.progress = 0; + } + }); + } else { + _pendingStatus.forEach((item) => { + if (item.status === UploadStatus.uploading) { + item.status = UploadStatus.error; + item.progress = 0; + } + }); + } + setPendingStatus(_pendingStatus); + setPendingFiles([]); + if (afterUpload) { + afterUpload(); + } + }) + .catch(() => { + _pendingStatus.forEach((item) => { + if (item.status === UploadStatus.uploading) { + item.status = UploadStatus.error; + item.progress = 0; + } + }); + setPendingStatus(_pendingStatus); + }) + .finally(() => { + setLoading(false); + }); + }; + + const deleteSelected = () => { + if (checkedFiles.length === 0) { + message.warning(locales.noSelected); + return; + } + (async () => { + const res: any = await deleteDocs(checkedFiles); + if (res && res.docBase) { + if (afterDelete) { + afterDelete(); + } + } + })(); + }; + + useEffect(() => { + if (checkedFiles.length === 0) { + setCheckedController(CheckedController.None); + } else if (checkedFiles.length === docs.length) { + setCheckedController(CheckedController.All); + } else { + setCheckedController(CheckedController.Partial); + } + }, [checkedFiles]); + + return ( + <> +
+ + {children} +
+

{locales.pendingFiles}

+
+ {pendingStatus.map((file) => ( + + ))} +
+ {pendingFiles.length > 0 && ( + + )} +
+

{locales.uploadedFiles}

+ {Array.isArray(docs) && docs.length > 0 && ( +
+ CheckedController.None} + onChange={checkAll} + /> + + {locales.selectAll} + + +
+ )} +
+
+
+ {`${locales.total}: ${filesState.length}, `} + {`${locales.failed}: ${filesState.filter((file) => !file.status).length}`} +
+
+ setFilter(e.target.value)} + onPressEnter={handleSearch} + /> + +
+ {/* 优先展示处理中文档 */} + {docs + .filter((doc) => doc.includes(searchValue)) + .filter((doc) => !filesState.find((file) => file.file === doc)) + .map((doc) => { + return ( + + ); + })} + {/* 按顺序显示已被处理的文档,有可能是失败状态 */} + {filesState + .filter((file) => file.file.includes(searchValue)) + .map((file) => ( + + ))} +
+ + ); +}; + +export default Upload; diff --git a/repodir/huixiangdou/web/front-end/src/config/auth.ts b/repodir/huixiangdou/web/front-end/src/config/auth.ts new file mode 100644 index 00000000..c4b79318 --- /dev/null +++ b/repodir/huixiangdou/web/front-end/src/config/auth.ts @@ -0,0 +1,45 @@ +// 登录相关配置信息 + +export const VITE_NODE = import.meta.env.VITE_NODE; + +// 开启单点登录开关 +export const openOSS = false; + +export const ClientIdMap = { + development: '', + staging: '', + production: '' +}; + +// 登录跳转链接 +export const LogURLMap = { + development: '', + staging: '', + production: '' +}; + +// 注意 Development环境的domain前面必须加 . 因为,本地开发环境和线上开发环境域名不同 +// 如果发生反复跳转,请在浏览器中查看后端返回的cookie的domain是否有问题 +export const TokenCookieDomainMap = { + development: '', + staging: '', + production: '' +}; + +export const clientId = ClientIdMap[VITE_NODE]; +export const logURL = LogURLMap[VITE_NODE]; +export const TokenCookieDomain = TokenCookieDomainMap[VITE_NODE]; + +// 针对权限更细化的配置信息 + +// 需要权限验证的页面可以把对应的pathname放到这里 +export const AuthPages: string[] = [ + '' +]; + +// 有些接口不需要token +export const NoTokenApiPaths: string[] = [ + '/account/oauth', + '/api/v1/access/v1/login', + '/api/v1/statistic/v1/total' +]; diff --git a/repodir/huixiangdou/web/front-end/src/config/base-url.ts b/repodir/huixiangdou/web/front-end/src/config/base-url.ts new file mode 100644 index 00000000..a0576edd --- /dev/null +++ b/repodir/huixiangdou/web/front-end/src/config/base-url.ts @@ -0,0 +1,22 @@ +// 接口请求相关的配置信息 + +// 各个环境的接口请求域名 +export const ApiBaseUrlMap = { + development: '', + staging: '', + production: '' +}; + +// 各个环境的接口前缀 +export const ApiPrefixMap = { + mock: '', + development: '', + staging: '', + production: '' +}; + +export const Env = import.meta.env.VITE_NODE; + +export const BaseURL = ApiBaseUrlMap[Env]; + +export const ApiPrefix = ApiPrefixMap[Env]; diff --git a/repodir/huixiangdou/web/front-end/src/config/change-page-gray.ts b/repodir/huixiangdou/web/front-end/src/config/change-page-gray.ts new file mode 100644 index 00000000..35f6a314 --- /dev/null +++ b/repodir/huixiangdou/web/front-end/src/config/change-page-gray.ts @@ -0,0 +1,90 @@ +const MetaDataURL = 'https://openmmlab-share.oss-cn-hangzhou.aliyuncs.com/metadata/seal-lion-client-meta-data.js'; + +const getMetaData = (url: string): Promise => { + return new Promise((resolve, reject) => { + let script = document.createElement('script'); + script.src = `${url}?random=${Date.now()}`; + script.type = 'text/javascript'; + + const remove = () => { + document.body.removeChild(script); + script = null; + }; + window.sealionJSONPCallback = (data) => { + resolve(data); + remove(); + }; + + script.onerror = (err) => { + reject(err); + remove(); + }; + + document.body.appendChild(script); + }); +}; + +const changePageGray = ({ + open = false, + grayscale = '100%', + changePages = 'all', + routerType = 'popstate' +}: { + open?: boolean, + grayscale?: string; + changePages?: string[] | 'all', + routerType?: string // 'popstate' | 'hashchange' +}) => { + if (!open) return; + + const setPageGray = () => { + document.body.style.cssText = `-webkit-filter: grayscale(${grayscale});-ms-filter: grayscale(${grayscale});-moz-filter: grayscale(${grayscale});filter: grayscale(${grayscale});`; + }; + + const removePageGray = () => { + document.body.style.cssText = ''; + }; + + const listenPopState = (callback) => { + const handleChange = (changeRoute, eventType: string) => { + if (routerType !== eventType) return; + callback?.(changeRoute, eventType); + }; + + window.addEventListener('load', () => { + const changeRoute = routerType === 'popstate' ? window.location.pathname : window.location.hash; + callback(changeRoute, 'init'); + }, false); + + window.addEventListener('popstate', () => { + handleChange(window.location.pathname, 'popstate'); + }, false); + + window.addEventListener('hashchange', () => { + handleChange(window.location.hash, 'hashchange'); + }, false); + }; + + listenPopState((changeRoute) => { + if ( + changePages === 'all' + || changePages.some(p => p === changeRoute) + ) { + setPageGray(); + } else { + removePageGray(); + } + }); +}; + +const main = async () => { + try { + const content = await getMetaData(MetaDataURL); + const options = (content || {}).grayPage; + changePageGray(options); + } catch (error) { + console.error(error); + } +}; + +main(); diff --git a/repodir/huixiangdou/web/front-end/src/config/index.ts b/repodir/huixiangdou/web/front-end/src/config/index.ts new file mode 100644 index 00000000..3ef7e202 --- /dev/null +++ b/repodir/huixiangdou/web/front-end/src/config/index.ts @@ -0,0 +1,3 @@ +export * from './auth'; +export * from './base-url'; +export * from './log'; diff --git a/repodir/huixiangdou/web/front-end/src/config/log.ts b/repodir/huixiangdou/web/front-end/src/config/log.ts new file mode 100644 index 00000000..3e2a064d --- /dev/null +++ b/repodir/huixiangdou/web/front-end/src/config/log.ts @@ -0,0 +1,13 @@ +// 日志相关配置 + +const VITE_NODE = import.meta.env.VITE_NODE; + +export const openLog = false; + +export const MeasurementIdMap = { + development: '', + staging: '', + production: '' +}; + +export const MeasurementId = MeasurementIdMap[VITE_NODE]; diff --git a/repodir/huixiangdou/web/front-end/src/hooks/useLocale.ts b/repodir/huixiangdou/web/front-end/src/hooks/useLocale.ts new file mode 100644 index 00000000..0058aade --- /dev/null +++ b/repodir/huixiangdou/web/front-end/src/hooks/useLocale.ts @@ -0,0 +1,16 @@ +import { useContext, useState, useEffect } from 'react'; +import { GlobalLangeContext } from '@components/global-lang'; +import Locale from '@/locales'; + +export const useLocale = (propertyName: string) => { + const [locales, setLocales] = useState({}); + const { locale: lang } = useContext(GlobalLangeContext); + + useEffect(() => { + if (lang && Locale[lang] && Locale[lang][propertyName]) { + setLocales(Locale[lang][propertyName]); + } + }, [lang, propertyName]); + + return locales; +}; diff --git a/repodir/huixiangdou/web/front-end/src/interceptors/request.ts b/repodir/huixiangdou/web/front-end/src/interceptors/request.ts new file mode 100644 index 00000000..45e50be8 --- /dev/null +++ b/repodir/huixiangdou/web/front-end/src/interceptors/request.ts @@ -0,0 +1,44 @@ +import { NoTokenApiPaths, openOSS } from '@config/auth'; +import { getLang, Token } from '@utils/utils'; +import { AxiosRequestHeaders } from 'axios'; + +// *Interceptor函数:主要用来在请求发出前处理config,config由axios的请求拦截器提供 +// *Interceptor函数运行规则:函数会依次从左到右执行,每个*Interceptor函数必须返回config,供下一个*Interceptor函数处理 +// 好处:代码结构更清晰,每个函数专注做自己的事情,拿到config处理后return,达到逻辑解耦的目的 + +interface IAuth extends AxiosRequestHeaders{ + Authorization?: string; +} + +const validateAuthInterceptor = config => { + const token = Token.get(); + const headers: IAuth = { + lang: getLang(), + ...config.headers + }; + + if ( + !NoTokenApiPaths.find(p => (config.url || '').endsWith(p)) + && openOSS + ) { + headers.Authorization = `Bearer ${token}`; + } + + return { + ...config, + headers + }; +}; + +const customConfigInterceptor = config => { + return ({ + ...config, + headers: { + ...config.headers, + 'Client-Type': 'app', + type: 0 + } + }); +}; + +export const requestInterceptors = [validateAuthInterceptor, customConfigInterceptor]; diff --git a/repodir/huixiangdou/web/front-end/src/interceptors/response.ts b/repodir/huixiangdou/web/front-end/src/interceptors/response.ts new file mode 100644 index 00000000..f44e2555 --- /dev/null +++ b/repodir/huixiangdou/web/front-end/src/interceptors/response.ts @@ -0,0 +1,95 @@ +import { message } from 'sea-lion-ui'; +import { + Token, jumpLogin, getLang +} from '@utils/utils'; +import { AxiosError } from 'axios'; +import { Meta } from '@utils/ajax'; +import { openOSS } from '@config/auth'; +import { MsgCode } from '@services/home'; + +export const handleUnauth = () => { + if (!openOSS) return; + // 处理一些用户权限验证 + Token.removeAll(); + window.location.href = '/home'; +}; + +const formatResponseData = response => { + const resp = response.data; + const meta = response.__meta; + const isAllResponseBody = meta.isAllResponseBody; // isAllResponseBody是否需要返回完整数组结构 + if (isAllResponseBody) { + return resp; + } + return resp.data; +}; + +const handleErrorAlert = response => { + const resp = response.data; + const meta = response.__meta; + const notIgnoreError = !meta.isIgnoreError; + if (resp.success === false && notIgnoreError) { + message.error(resp.msg); + } + return response; +}; + +const validateAuth = response => { + const resp = response.data; + // 应用拦截到鉴权错误 + if (resp && [MsgCode.notAuthed, MsgCode.loginFail].includes(resp.msgCode)) { + handleUnauth(); + } + return response; +}; + +const validateInvitation = response => { + const resp = response.data; + // 应用拦截到鉴权错误 + if (resp && resp.msgCode === 'C1600') { + window.location.href = jumpLogin(); + } + return response; +}; + +const showErrorMessage = (text, ignore = false) => { + if (!ignore) { + message.error(text); + } +}; + +const handleErrorData = (error) => { + // 如果没有用户则不显示弹窗 + if (error.response) { + const meta = error.__meta; + const ignore = meta.isIgnoreGatewayError; + + const code = error.response.status; + console.log(error.code, 'error.....'); + switch (code) { + case 401: + case 403: + handleUnauth(); + break; + case 500: + showErrorMessage(getLang() === 'zh-CN' ? '服务器没有响应,请稍后再试' : 'Sever error, please try again later.', ignore); + break; + default: + if (error.code === 'ERR_NETWORK') { + showErrorMessage(getLang() === 'zh-CN' ? '网络出错了,请稍后再试' : 'Network error, please try again later.', ignore); + } else { + showErrorMessage(`${code}: ${error.message || 'unknown error'}`, ignore); + } + } + } + // 可能是取消接口请求 + return Promise.reject(error); +}; + +export const responsetInterceptors = [validateAuth, validateInvitation, handleErrorAlert, formatResponseData]; + +type ResponsetErrorInterceptorsError = AxiosError & { __meta: Meta } + +export const responsetErrorInterceptors: [ + ...((error: T) => ResponsetErrorInterceptorsError)[], (error: ResponsetErrorInterceptorsError) => Promise +] = [handleErrorData]; diff --git a/repodir/huixiangdou/web/front-end/src/layouts/header-container-layout/header-container-layout.module.less b/repodir/huixiangdou/web/front-end/src/layouts/header-container-layout/header-container-layout.module.less new file mode 100644 index 00000000..8d249521 --- /dev/null +++ b/repodir/huixiangdou/web/front-end/src/layouts/header-container-layout/header-container-layout.module.less @@ -0,0 +1,4 @@ +.wrapper { + .header {} + .body {} +} \ No newline at end of file diff --git a/repodir/huixiangdou/web/front-end/src/layouts/header-container-layout/header-container-layout.tsx b/repodir/huixiangdou/web/front-end/src/layouts/header-container-layout/header-container-layout.tsx new file mode 100644 index 00000000..ba47d895 --- /dev/null +++ b/repodir/huixiangdou/web/front-end/src/layouts/header-container-layout/header-container-layout.tsx @@ -0,0 +1,18 @@ +import Header from '@components/header/header'; +import { Outlet } from 'react-router-dom'; +import useNotification from '@components/notification/use-notification'; +import styles from './header-container-layout.module.less'; + +const HeaderContainerLayout = () => { + return ( +
+
+
+ +
+ {useNotification()} +
+ ); +}; + +export default HeaderContainerLayout; diff --git a/repodir/huixiangdou/web/front-end/src/locales/en-US.ts b/repodir/huixiangdou/web/front-end/src/locales/en-US.ts new file mode 100644 index 00000000..df0c8d19 --- /dev/null +++ b/repodir/huixiangdou/web/front-end/src/locales/en-US.ts @@ -0,0 +1,11 @@ +import home from '@locales/en-US/home'; +import beanDetail from '@locales/en-US/bean-detail'; +import components from '@locales/en-US/components'; +import welcome from './en-US/welcome'; + +export default { + ...welcome, + ...home, + ...beanDetail, + ...components, +}; diff --git a/repodir/huixiangdou/web/front-end/src/locales/en-US/bean-detail.ts b/repodir/huixiangdou/web/front-end/src/locales/en-US/bean-detail.ts new file mode 100644 index 00000000..c76fe4a3 --- /dev/null +++ b/repodir/huixiangdou/web/front-end/src/locales/en-US/bean-detail.ts @@ -0,0 +1,62 @@ +export default { + beanDetail: { + accessFeishu: 'Integrate Lark', + accessWeChat: 'Integrate WeChat', + viewDetail: 'View Guidance', + switchSearch: 'Web Search Switch', + examples: 'Examples', + docs: 'Uploads', + viewAndEdit: 'Edit', + addExamples: 'Add Examples', + addDocs: 'Add Documents', + chatTest: 'Chat Test', + openChatTest: 'Start Testing', + beanName: 'Knowledge Base Name', + createFailed: 'Failed to Add Document', + created: 'Created, No Documents Added Yet', + createSuccess: 'Creation Complete', + processing: 'Processing', + processingError: 'Processing failed', + paramsError: 'Parameters error', + internalError: 'Internal error', + upload: 'Select Files, Multiple Selection Allowed', + supportFiles: 'pdf/word/markdown/excel/ppt/html/txt formats are allowed', + logout: 'Log Out', + refresh: 'Refresh', + references: 'References: ', + timeout: 'Request Timeout, Please Try Again Later', + inputPlaceholder: 'Support Text, Emoji and Image Paste', + send: 'Send', + setPositive: 'Set Positive Example', + positiveDesc: 'Positive examples are real-life questions from the asker that require a response. Each sentence should be on a new line, for example:\nHello, I\'m an intern, do you have dormitories here?\nWhat are the advantages of your product compared to competitors?', + setNegative: 'Set Negative Example', + negativeDesc: 'Negative examples are idle chatter in real-life scenarios that should not be responded to.\nEach sentence should be on a new line, for example:\nShall we have Japanese food for lunch today?\nQuick, there\'s a shooting star in the sky, run!', + save: 'Save Credentials', + cancel: 'Cancel', + reset: 'Reset', + saving: 'Saving...', + saveSuccess: 'Save Successful', + edit: 'Click to Edit', + integrateLark: 'Integrate with Lark', + integrateWechat: 'Integrate with WeChat', + WeChatCallback: 'Copy the WeChat callback address below:', + noCallback: 'No callback address yet', + wechatGuidance: 'Follow the tutorial below to complete the WeChat callback address configuration', + viewGuide: 'View Tutorial', + larkGuidance: 'Copy the Lark event request address below:', + encryption: 'Configure your Encryption Strategy:', + credentials: 'Input your Credentials:', + suffix: 'Rename your Lark group name with suffix: ', + reference: 'Reference: Follow the tutorial below to complete the Lark configuration:', + required: 'Required', + isEmpty: 'Not Empty', + webSearch: 'Web Search', + register: 'Register', + webSearchClosed: 'Web Search Disable', + webSearchDesc: 'Enable web search to allow the knowledge assistant to provide answers based on both web results and local documents', + webSearchDesc1: 'to get a limited free token', + webSearchDesc2: '2. Enter the token, a new token will overwrite the old one', + webSearchDesc3: '3. If you save an empty token, web search will be turned off', + enterToken: 'Please Enter Token', + } +}; diff --git a/repodir/huixiangdou/web/front-end/src/locales/en-US/components.ts b/repodir/huixiangdou/web/front-end/src/locales/en-US/components.ts new file mode 100755 index 00000000..40696859 --- /dev/null +++ b/repodir/huixiangdou/web/front-end/src/locales/en-US/components.ts @@ -0,0 +1,27 @@ +export default { + components: { + notificationContent: '🎉 HuixiangDou is open source now. If this helps you, please give it a star! 🌟 🥺', + hide4ever: 'Hide forever', + goStar: 'Star', + fileSize: 'Single file size should not exceed 35MB', + nameSize: 'File name is too long', + fileCount: 'Up to 200 files can be uploaded at a time', + pendingFiles: 'Uploading documents', + confirmUpload: 'Upload', + uploading: 'Uploading', + uploadedFiles: 'Uploaded documents', + uploadFailed: 'Failed', + processing: 'Processing', + total: 'Total', + failed: 'Failed', + searchDesc: 'Enter the document name to search', + search: 'Search', + selectAll: 'Select all', + noSelected: 'No document selected', + deleteSelected: 'Delete selected', + deleteConfirm: 'Are you sure you want to delete the selected documents?', + deleteDesc: 'The delete operation will rebuild the bean', + confirm: 'Delete', + cancel: 'Cancel', + } +}; diff --git a/repodir/huixiangdou/web/front-end/src/locales/en-US/home.ts b/repodir/huixiangdou/web/front-end/src/locales/en-US/home.ts new file mode 100644 index 00000000..77f0f9fb --- /dev/null +++ b/repodir/huixiangdou/web/front-end/src/locales/en-US/home.ts @@ -0,0 +1,24 @@ +export default { + home: { + slogan: 'Knowledge Assistant, Zero-coding with Lark and WeChat.', + beanName: 'Knowledge base name. Auto create if not exists', + validateMsg: 'At least 8 characters required', + createBean: 'Create Knowledge Base', + beanPwd: 'Knowledge Base Password', + create: 'Create', + cancel: 'Cancel', + go: 'Go', + bean: 'Knowledge Base', + activeBean: 'Active Base Monthly', + WeChat: 'WeChat', + feishu: 'Lark', + users: 'Chat Count', + uniqueUsers: 'Unique Chat', + pwdError: 'Password Error', + feedback: 'Feedback', + welcome: 'Welcome, grateful', + hello: 'Hi', + hi: 'Hello', + loading: 'Loading', + } +}; diff --git a/repodir/huixiangdou/web/front-end/src/locales/en-US/welcome.ts b/repodir/huixiangdou/web/front-end/src/locales/en-US/welcome.ts new file mode 100644 index 00000000..110eea3c --- /dev/null +++ b/repodir/huixiangdou/web/front-end/src/locales/en-US/welcome.ts @@ -0,0 +1,6 @@ +export default { + welcome: 'Welcome, grateful', + hello: 'Hi', + hi: 'Hello', + loading: 'Loading' +}; diff --git a/repodir/huixiangdou/web/front-end/src/locales/index.ts b/repodir/huixiangdou/web/front-end/src/locales/index.ts new file mode 100644 index 00000000..7a46e7ce --- /dev/null +++ b/repodir/huixiangdou/web/front-end/src/locales/index.ts @@ -0,0 +1,7 @@ +import zhCN from './zh-CN'; +import enUS from './en-US'; + +export default { + 'zh-CN': zhCN, + 'en-US': enUS +}; diff --git a/repodir/huixiangdou/web/front-end/src/locales/zh-CN.ts b/repodir/huixiangdou/web/front-end/src/locales/zh-CN.ts new file mode 100644 index 00000000..7651620c --- /dev/null +++ b/repodir/huixiangdou/web/front-end/src/locales/zh-CN.ts @@ -0,0 +1,11 @@ +import home from '@locales/zh-CN/home'; +import beanDetail from '@locales/zh-CN/bean-detail'; +import components from '@locales/zh-CN/components'; +import welcome from './zh-CN/welcome'; + +export default { + ...welcome, + ...home, + ...beanDetail, + ...components +}; diff --git a/repodir/huixiangdou/web/front-end/src/locales/zh-CN/bean-detail.ts b/repodir/huixiangdou/web/front-end/src/locales/zh-CN/bean-detail.ts new file mode 100644 index 00000000..8017d07f --- /dev/null +++ b/repodir/huixiangdou/web/front-end/src/locales/zh-CN/bean-detail.ts @@ -0,0 +1,67 @@ +export default { + beanDetail: { + accessFeishu: '零开发集成飞书', + accessWeChat: '零开发集成微信', + viewDetail: '查看教程', + switchSearch: '网络搜索开关', + examples: '正反例', + docs: '查看或上传', + viewAndEdit: '查看或编辑', + addExamples: '添加正反例', + addDocs: '添加文档', + chatTest: '聊天测试', + openChatTest: '开始测试', + beanName: '知识库名称', + createFailed: '添加文档失败', + created: '创建完成,尚未添加文档', + createSuccess: '创建完成', + processing: '正在处理', + processingError: '处理失败', + paramsError: '参数错误', + internalError: '内部错误', + upload: '选择文件,可以框选多个', + supportFiles: '支持 pdf/word/markdown/excel/ppt/html/txt 格式', + logout: '登出', + refresh: '刷新', + references: '参考文档: ', + timeout: '请求超时,请稍后再试', + inputPlaceholder: '支持输入文字、emoji 和粘贴图片', + send: '发送', + setPositive: '设置正例', + positiveDesc: `正例是真实场景中,来自提问者的、须答复的问题,每句话一行,例如: +你好,我是实习生,请问单位有宿舍么? +你们的产品和友商对比有啥优势啊?`, + setNegative: '设置反例', + negativeDesc: `反例是真实场景中的闲聊,不应该答复。 +每句一行,例如: +今天中午吃日料么? +快看天上有颗流星,快跑!`, + save: '保存凭证', + cancel: '取消', + reset: '重置', + saving: '保存中...', + saveSuccess: '保存成功', + edit: '点击修改', + integrateLark: '集成飞书', + integrateWechat: '集成微信', + WeChatCallback: '复制下面的微信回调地址: ', + noCallback: '暂无回调地址', + wechatGuidance: '根据下面的教程,完成微信回调地址的配置', + viewGuide: '查看教程', + larkGuidance: '复制下面的飞书事件请求地址:', + encryption: '配置你的加密策略:', + credentials: '输入你的凭证:', + suffix: '给飞书群名加上后缀: ', + reference: '参考文档: 根据下面的教程,完成飞书配置:', + required: '必填', + isEmpty: '不能为空', + webSearch: '网络搜索', + register: '注册', + webSearchClosed: '网络搜索已关闭', + webSearchDesc: '开启网络搜索,知识助手可以综合网络结果和本地文档给出答复', + webSearchDesc1: '获取限量免费 token', + webSearchDesc2: '2. 填入 token, 新 token 会覆盖旧 token ', + webSearchDesc3: '3. 如果保存一个空 token, 网络搜索会被关闭', + enterToken: '请输入 token', + } +}; diff --git a/repodir/huixiangdou/web/front-end/src/locales/zh-CN/components.ts b/repodir/huixiangdou/web/front-end/src/locales/zh-CN/components.ts new file mode 100755 index 00000000..8ef42549 --- /dev/null +++ b/repodir/huixiangdou/web/front-end/src/locales/zh-CN/components.ts @@ -0,0 +1,28 @@ +export default { + components: { + notificationContent: `🎉HuixiangDou开源啦,快来给我们 star 吧! +小时候,我想当开源人,朋友给我鼓励和我最爱的小星星🌟 🥺`, + hide4ever: '不再显示', + goStar: '前往鼓励', + fileSize: '单个文件大小不能超过 35M', + nameSize: '文件名太长', + fileCount: '单次最多上传 200 个文件', + pendingFiles: '待上传文档', + confirmUpload: '确认上传', + uploading: '上传中', + uploadedFiles: '已上传文档', + uploadFailed: '上传失败', + processing: '处理中', + total: '共计', + failed: '失败', + searchDesc: '输入文档名称进行搜索', + search: '搜索', + selectAll: '全选', + noSelected: '您还未选中任何文档', + deleteSelected: '删除', + deleteConfirm: '确定删除选中的文档吗?', + deleteDesc: '删除操作会重建知识库', + confirm: '删除', + cancel: '取消', + } +}; diff --git a/repodir/huixiangdou/web/front-end/src/locales/zh-CN/home.ts b/repodir/huixiangdou/web/front-end/src/locales/zh-CN/home.ts new file mode 100644 index 00000000..df8752c8 --- /dev/null +++ b/repodir/huixiangdou/web/front-end/src/locales/zh-CN/home.ts @@ -0,0 +1,24 @@ +export default { + home: { + slogan: '行业知识助手,零开发接入飞书个微群', + beanName: '请输入知识库名称,不存在则自动创建。不少于 8 个字符', + validateMsg: '知识库名称至少需要 8 个字符', + createBean: '创建知识库', + beanPwd: '知识库密码', + create: '创建', + cancel: '取消', + go: '前往', + bean: '知识库', + activeBean: '月活知识库', + WeChat: '微信', + feishu: '飞书', + users: '回答次数', + uniqueUsers: '去重次数', + pwdError: '密码错误', + feedback: '问题反馈', + welcome: 'Welcome, grateful', + hello: 'Hi', + hi: 'Hello', + loading: 'Loading' + } +}; diff --git a/repodir/huixiangdou/web/front-end/src/locales/zh-CN/welcome.ts b/repodir/huixiangdou/web/front-end/src/locales/zh-CN/welcome.ts new file mode 100644 index 00000000..805c89d6 --- /dev/null +++ b/repodir/huixiangdou/web/front-end/src/locales/zh-CN/welcome.ts @@ -0,0 +1,6 @@ +export default { + welcome: '欢迎,感恩', + hello: '嗨', + hi: '你好', + loading: 'Loading', +}; diff --git a/repodir/huixiangdou/web/front-end/src/main.tsx b/repodir/huixiangdou/web/front-end/src/main.tsx new file mode 100644 index 00000000..0c835afd --- /dev/null +++ b/repodir/huixiangdou/web/front-end/src/main.tsx @@ -0,0 +1,13 @@ +import * as React from 'react'; +import * as ReactDOM from 'react-dom/client'; +import Mlog from '@utils/mlog'; +import '@config/change-page-gray'; +import App from './app'; + +Mlog.init(); + +ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( + + + +); diff --git a/repodir/huixiangdou/web/front-end/src/pages/bean-detail/bean-detail.module.less b/repodir/huixiangdou/web/front-end/src/pages/bean-detail/bean-detail.module.less new file mode 100644 index 00000000..80b3e790 --- /dev/null +++ b/repodir/huixiangdou/web/front-end/src/pages/bean-detail/bean-detail.module.less @@ -0,0 +1,80 @@ +.btn { + padding: 8px 12px; + background: #c7eaba; + color: #286500; + border-radius: 6px; + font-size: 14px; + line-height: 16px; + display: inline-flex; + align-items: center; + gap: 4px; + cursor: pointer; + &[aria-disabled="true"] { + background: #dcdcdc; + color: #9d9d9d; + cursor: not-allowed; + } +} +.bean-detail { + min-height: 700px; + min-width: 860px; + margin: auto; + text-align: center; + position: absolute; + top: 200px; + left: 50%; + transform: translateX(-50%); + .logo { + width: 800px; + margin: 0 auto 72px; + img { + width: 100%; + } + } + .bean-state { + background-color: #e3f9dd; + border-radius: 8px; + padding: 4px 8px; + margin-left: 4px; + } + .fail-state { + background-color: #f1bcbc; + } + .name-wrapper { + display: flex; + align-items: center; + gap: 4px; + } + .statistics-wrapper { + display: grid; + grid-template-columns: repeat(5, 1fr); + grid-gap: 20px; + margin: 24px auto; + text-align: center; + } + .statistics-item { + text-align: left; + .title-img { + height: 16px; + } + .statistics-item-title { + color: #9D9D9D; + font-size: 16px; + margin-bottom: 12px; + line-height: 20px; + display: flex; + align-items: center; + gap: 4px; + } + } + .refresh { + margin-left: auto; + cursor: pointer; + color: #286500; + } + .logout { + //margin-left: auto; + cursor: pointer; + color: #9D9D9D; + } +} diff --git a/repodir/huixiangdou/web/front-end/src/pages/bean-detail/bean-detail.tsx b/repodir/huixiangdou/web/front-end/src/pages/bean-detail/bean-detail.tsx new file mode 100644 index 00000000..c240c468 --- /dev/null +++ b/repodir/huixiangdou/web/front-end/src/pages/bean-detail/bean-detail.tsx @@ -0,0 +1,207 @@ +import { + FC, ReactNode, useEffect, useMemo, useState +} from 'react'; +import { useLocale } from '@hooks/useLocale'; +import logo from '@assets/imgs/logo.png'; +import bean from '@assets/imgs/bean1.png'; +import Chat from '@pages/bean-detail/components/chat'; +import { Feishu, FileState, getInfo } from '@services/home'; +import ToggleSearch from '@pages/bean-detail/components/toggle-search'; +import Example from '@pages/bean-detail/components/example'; +import ImportDocs from '@pages/bean-detail/components/import-docs'; +import IntegrateFeishu from '@pages/bean-detail/components/integrate-feishu'; +import { Token } from '@utils/utils'; +import { useNavigate } from 'react-router-dom'; +import IntegrateWechat from '@pages/bean-detail/components/integrate-wechat/integrate-wechat'; +import { Button, IconFont } from 'sea-lion-ui'; +import styles from './bean-detail.module.less'; + +export interface BeanDetailProps { + children?: ReactNode; +} + +export enum BeanState { + 'failed' = -1, + 'created' = 0, + 'finished' = 1, + processing = 11, + processingError = 12, + paramsError = 13, + internalError = 14, +} + +const BeanDetail: FC = () => { + const navigate = useNavigate(); + const locales = useLocale('beanDetail'); + const [name, setName] = useState(''); + const [docs, setDocs] = useState(['']); // 所有已上传的文档, 不一定已经处理 + const [filesState, setFilesState] = useState([]); // 所有已被处理的文档,包括处理状态 + const [weChatInfo, setWeChatInfo] = useState(null); + const [feishuInfo, setFeishuInfo] = useState(null); + const [suffix, setSuffix] = useState(''); + const [searchToken, setSearchToken] = useState(''); + const [beanState, setBeanState] = useState(BeanState.created); + const [refreshFlag, setRefreshFlag] = useState(false); + + const state = { + [BeanState.failed]: locales.createFailed, + [BeanState.created]: locales.created, + [BeanState.finished]: locales.createSuccess, + [BeanState.processing]: locales.processing, + [BeanState.processingError]: locales.processingError, + [BeanState.paramsError]: locales.paramsError, + [BeanState.internalError]: locales.internalError, + }; + const color = { + [BeanState.failed]: '#f1bcbc', + [BeanState.created]: '#ffe2bf', + [BeanState.finished]: '#e3f9dd', + [BeanState.processing]: '#bcdef1', + [BeanState.processingError]: '#f1bcbc', + [BeanState.paramsError]: '#f1bcbc', + [BeanState.internalError]: '#f1bcbc', + }; + + const refresh = () => { + setRefreshFlag(!refreshFlag); + }; + const logout = () => { + // 退出登录 + Token.removeAll(); + navigate('/home'); + }; + + const getBeanInfo = async () => { + const res = await getInfo(); + if (res) { + setName(res.name); + setBeanState(res.status); + setSearchToken(res.webSearch?.token); + setFeishuInfo(res.lark); + setWeChatInfo(res.wechat?.onMessageUrl); + setSuffix(res.suffix); + if (Array.isArray(res.docs)) { + setDocs(res.docs); + } + if (Array.isArray(res.filesState)) { + setFilesState(res.filesState); + } + } + }; + + useEffect(() => { + getBeanInfo(); + }, [refreshFlag]); + + // polling getInfo when beanState is created and docs is not empty + useEffect(() => { + let timer = null; + if (beanState === BeanState.created && docs.length > 0) { + timer = setInterval(() => { + getBeanInfo(); + }, 5000); + } else { + clearInterval(timer); + } + return () => clearInterval(timer); + }, [beanState, docs.length]); + + const content = useMemo(() => { + if (beanState === BeanState.finished) { + return ( + [ + { + title: locales.addDocs, + children: , + key: 'docs' + }, + { + title: locales.addExamples, + children: , + key: 'examples' + }, + { + title: locales.accessWeChat, + children: , + key: 'accessWeChat' + }, + { + title: locales.accessFeishu, + children: , + key: 'accessFeishu' + }, + { + title: locales.switchSearch, + children: , + key: 'switchSearch' + }, + ] + ); + } + return ( + [{ + title: locales.addDocs, + children: , + key: 'docs' + }] + ); + }, [locales, searchToken, beanState, feishuInfo, refresh, suffix]); + + return ( +
+
+ huixiangdou +
+
+
+ {locales.beanName} + bean +
+
+ {name} + + {state[beanState]} + + +
+ {locales.logout} +
+
+
+
+ {content.map((item) => ( +
+
{item.title}
+
{item.children}
+
+ ))} +
+ {beanState === BeanState.finished && ( +
+
{locales.chatTest}
+
+ +
+
+ )} +
+ ); +}; + +export default BeanDetail; diff --git a/repodir/huixiangdou/web/front-end/src/pages/bean-detail/components/chat/chat.module.less b/repodir/huixiangdou/web/front-end/src/pages/bean-detail/components/chat/chat.module.less new file mode 100644 index 00000000..6b36ec87 --- /dev/null +++ b/repodir/huixiangdou/web/front-end/src/pages/bean-detail/components/chat/chat.module.less @@ -0,0 +1,151 @@ +.chat { + border-radius: 8px; + border: 1px solid #dcdcdc; + padding: 12px; + margin-bottom: 100px; + .message-list { + width: 100%; + height: 400px; + overflow: auto; + scorllbar-width: none; + &::-webkit-scrollbar { + width: 0; + } + .avatar { + width: 20px; + height: 20px; + img { + width: 20px; + } + } + .message-wrapper { + display: flex; + gap: 8px; + margin: 8px 0; + } + .message { + padding: 8px 12px; + border-radius: 0 8px 8px 8px; + background: #f4f5f9; + box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05); + white-space: pre-wrap; + max-width: 80%; + position: relative; + } + .img-wrapper { + display: flex; + align-items: center; + gap: 4px; + margin-bottom: 4px; + } + .reference-wrapper { + border-top: 1px solid #dcdcdc; + padding-top: 8px; + margin-top: 8px; + white-space: pre-wrap; + } + .reference { + font-size: 14px; + color: #666; + } + .footer { + display: flex; + justify-content: flex-start; + align-items: center; + gap: 8px; + margin-top: 4px; + font-size: 14px; + position: absolute; + top: -10px; + left: calc(100% - 10px); + overflow: hidden; + transition: all 1s ease; + .feedback { + cursor: pointer; + padding: 3px; + border-radius: 50%; + &:hover { + background-color: #e3e3e3; + } + } + } + } + .input-wrapper { + display: flex; + align-items: center; + gap: 8px; + padding-top: 12px; + :global { + .seal-input-group .seal-input-inner-container:focus-within { + outline: 1px solid #59a041; + } + } + } + .editor-wrapper { + width: 100%; + position: relative; + text-align: center; + border-radius: 12px; + margin: auto; + padding: 5px 16px; + background: #F4F5F9; + border: 2px solid transparent; + box-sizing: border-box; + display: flex; + align-items: center; + &:hover:not(:focus-within) { + background: #EBECF0; + .prompt-editor:not(:hover) { + background: #EBECF0; + } + } + &:focus-within { + background: #FFFFFF; + border: 2px solid #59a041; + } + } + .prompt-editor { + position: relative; + display: inline-block; + width: 100%; + max-height: 200px; + border-radius: 2px; + box-shadow: none; + font-size: 14px; + line-height: 1.2; + box-sizing: border-box; + border: none; + padding: 0.1rem 0; + background: #F4F5F9; + resize: none; + text-align: left; + min-height: calc(1.2 * 0.8rem + 0.2rem); + overflow: auto; + caret-color: #59a041; + white-space: pre-wrap; + &[aria-placeholder]:empty::before { + color: #666; + content: attr(aria-placeholder); + position: absolute; + } + &:hover { + background: #EBECF0; + } + &:focus { + background: #FFFFFF; + } + &:focus-within { + background: #FFFFFF; + } + &:focus-visible { + outline-style: none; + } + &:-webkit-autofill, + &:-webkit-autofill:hover, + &:-webkit-autofill:focus, + &:-webkit-autofill:active { + -webkit-transition-delay: 99999s; + -webkit-transition: color 99999s ease-out, background-color 99999s ease-out; + } + } +} diff --git a/repodir/huixiangdou/web/front-end/src/pages/bean-detail/components/chat/chat.tsx b/repodir/huixiangdou/web/front-end/src/pages/bean-detail/components/chat/chat.tsx new file mode 100644 index 00000000..b62889aa --- /dev/null +++ b/repodir/huixiangdou/web/front-end/src/pages/bean-detail/components/chat/chat.tsx @@ -0,0 +1,299 @@ +import { + FC, ReactNode, useEffect, useRef, useState +} from 'react'; +import Button from '@components/button/button'; +import { caseFeedback, online, onlineResponse } from '@services/home'; +import bean from '@assets/imgs/bean1.png'; +import { useLocale } from '@hooks/useLocale'; +import styles from './chat.module.less'; + +export interface ChatProps { + children?: ReactNode; +} + +export interface Message { + queryId?: string, + sender: number, + content: string, + code: number, + state: string, + images: string[], + references: string[] +} + +export const enum Feedback { + good = 1, + bad = 0 +} + +function MessageItem(props: { message: Message }) { + const locales = useLocale('beanDetail'); + + const [feedback, setFeedback] = useState(''); + const [maxWidth, setMaxWidth] = useState(24); + if (!props.message) { + return null; + } + const { + state, + sender, + content, + images, + references, + queryId + } = props.message; + + const sendFeedback = async (type: string) => { + const res = await caseFeedback(queryId, type); + }; + + return ( +
+
+ {sender === 0 ? '🤓' : ( + bean + )} +
+
+
+ {Array.isArray(images) + && images.length > 0 + && images.map((img) => ( + img + ))} +
+
{content}
+ {Array.isArray(references) && references.length > 0 && ( +
+ {locales.references} +
+ {references.join('\n')} +
+
+ )} + {state && state.toLowerCase() !== 'success' && ( +
+ [Empty]: + {' '} + {state} +
+ )} + {queryId && sender === 1 && ( +
{ + setMaxWidth(200); + }} + onMouseLeave={() => { + setMaxWidth(24); + }} + > +
+ {feedback || '💬'} +
+ {feedback !== '👍' && ( +
{ + if (!feedback) { + setFeedback('👍'); + sendFeedback('good'); + } + }} + > + 👍 +
+ )} + {feedback !== '👎' && ( +
{ + if (!feedback) { + setFeedback('👎'); + sendFeedback('bad'); + } + }} + > + 👎 +
+ )} +
+ )} +
+
+ ); +} + +const Chat: FC = () => { + const [messages, setMessages] = useState([]); + const [queryId, setQueryId] = useState(''); // 查询回复id + const [isComposing, setIsComposing] = useState(false); + const messageListRef = useRef(null); + + const locales = useLocale('beanDetail'); + + const editorRef = useRef(null); + + const scrollToBottom = () => { + if (messageListRef.current) { + messageListRef.current.scrollTo({ top: 999999, behavior: 'smooth' }); + } + }; + + const handleSendMessage = async () => { + if (queryId) { + return; + } + + // read all images' base64 code from the editor + const imgNodes = editorRef.current.querySelectorAll('img'); + const images = Array.from(imgNodes).map((node) => { + return node.src; + }); + const currPrompt = editorRef.current.innerText.trim(); + // if no prompt and no images, return + if (!currPrompt && images.length === 0) { + return; + } + const history = messages.map((item) => { + return { + content: item.content, + sender: item.sender, + }; + }); + const newMessage = { + sender: 0, + content: currPrompt, + code: undefined, + state: '', + images, + references: [], + }; + setMessages([...messages, newMessage]); + scrollToBottom(); + editorRef.current.innerHTML = ''; + const res = await online({ + content: currPrompt, + history, + images, + }); + if (res) { + setQueryId(res.queryId); + } + }; + + const handleKeyDown = (e) => { + const key = e.code; + if (!isComposing && key === 'Enter' && !e.shiftKey) { + handleSendMessage(); + e.preventDefault(); + } + }; + + useEffect(() => { + let b; + if (queryId) { + // polling for response + let pollingTimes = 0; + const pendingMessage = { + sender: 1, + content: '...', + code: undefined, + state: '', + images: [], + references: [], + }; + setMessages([...messages, pendingMessage]); + scrollToBottom(); + b = setInterval(() => { + pollingTimes += 1; + onlineResponse(queryId) + .then((res) => { + if (res) { + // 如果回复成功且有回复内容;或者回复失败,停止轮询 + if ((res.code === 0 && res.text) || res.code > 0) { + clearInterval(b); + setQueryId(''); + const newMessage = { + queryId, + sender: 1, + content: res.text, + code: res.code, + state: res.state, + images: [], + references: res.references, + }; + setMessages([...messages, newMessage]); + scrollToBottom(); + } + } + }) + .catch((err) => { + console.error(err); + clearInterval(b); + setQueryId(''); + }); + if (pollingTimes === 60) { + const newMessage = { + queryId, + sender: 1, + content: locales.timeout, + code: 0, + state: '', + images: [], + references: [], + }; + clearInterval(b); + setQueryId(''); + setMessages([...messages, newMessage]); + scrollToBottom(); + } + }, 3000); + } + return () => { + clearInterval(b); + }; + }, [queryId]); + + return ( +
+
+ {messages.map((message, index) => ( + + ))} +
+
+
+
setIsComposing(true)} + onCompositionEnd={() => setIsComposing(false)} + aria-placeholder={locales.inputPlaceholder} + /> +
+ +
+
+ ); +}; + +export default Chat; diff --git a/repodir/huixiangdou/web/front-end/src/pages/bean-detail/components/chat/index.tsx b/repodir/huixiangdou/web/front-end/src/pages/bean-detail/components/chat/index.tsx new file mode 100644 index 00000000..b42e2c20 --- /dev/null +++ b/repodir/huixiangdou/web/front-end/src/pages/bean-detail/components/chat/index.tsx @@ -0,0 +1,4 @@ +import Chat from './chat'; + +export * from './chat'; +export default Chat; diff --git a/repodir/huixiangdou/web/front-end/src/pages/bean-detail/components/example/example.module.less b/repodir/huixiangdou/web/front-end/src/pages/bean-detail/components/example/example.module.less new file mode 100644 index 00000000..6e12615c --- /dev/null +++ b/repodir/huixiangdou/web/front-end/src/pages/bean-detail/components/example/example.module.less @@ -0,0 +1,11 @@ +.example { + +} +.editor { + margin-bottom: 12px; + :global { + .seal-input-container .seal-input-wrapper:focus-within { + border: 1px solid #59a041; + } + } +} diff --git a/repodir/huixiangdou/web/front-end/src/pages/bean-detail/components/example/example.tsx b/repodir/huixiangdou/web/front-end/src/pages/bean-detail/components/example/example.tsx new file mode 100644 index 00000000..4a881c5d --- /dev/null +++ b/repodir/huixiangdou/web/front-end/src/pages/bean-detail/components/example/example.tsx @@ -0,0 +1,115 @@ +import { + FC, ReactNode, useEffect, useMemo, useState +} from 'react'; +import { + CountInput, IconFont, Modal +} from 'sea-lion-ui'; +import { Tabs } from 'antd'; +import type { TabsProps } from 'antd'; +import { useLocale } from '@hooks/useLocale'; +import Button from '@components/button/button'; +import { getSampleInfo, updateSampleInfo } from '@services/home'; +import styles from './example.module.less'; + +export interface ExampleProps { + children?: ReactNode; +} + +const Example: FC = () => { + const locales = useLocale('beanDetail'); + const [openModal, setOpenModal] = useState(false); + const [negatives, setNegatives] = useState(''); + const [positives, setPositives] = useState(''); + const [loading, setLoading] = useState(false); + + useEffect(() => { + if (openModal) { + (async () => { + const res = await getSampleInfo(); + if (res) { + setPositives(res.positives.join('\n')); + setNegatives(res.negatives.join('\n')); + } + })(); + } + }, [openModal]); + + const handleSave = async () => { + setLoading(true); + const newNegatives = negatives.split('\n'); + const newPositives = positives.split('\n'); + updateSampleInfo(newPositives, newNegatives).finally(() => { + setLoading(false); + }); + }; + + const items: TabsProps['items'] = useMemo(() => { + return ( + [ + { + key: 'positives', + label: locales.setPositive, + children: ( + <> +
+ setPositives(e)} + /> +
+ + + ), + }, + { + key: 'negatives', + label: locales.setNegative, + children: ( + <> +
+ setNegatives(e)} + /> +
+ + + ), + }, + ] + ); + }, [negatives, positives, handleSave]); + + return ( +
+ + )} + onClose={() => setOpenModal(false)} + > + + +
+ ); +}; + +export default Example; diff --git a/repodir/huixiangdou/web/front-end/src/pages/bean-detail/components/example/index.tsx b/repodir/huixiangdou/web/front-end/src/pages/bean-detail/components/example/index.tsx new file mode 100644 index 00000000..4f5063d6 --- /dev/null +++ b/repodir/huixiangdou/web/front-end/src/pages/bean-detail/components/example/index.tsx @@ -0,0 +1,4 @@ +import Example from './example'; + +export * from './example'; +export default Example; diff --git a/repodir/huixiangdou/web/front-end/src/pages/bean-detail/components/import-docs/import-docs.module.less b/repodir/huixiangdou/web/front-end/src/pages/bean-detail/components/import-docs/import-docs.module.less new file mode 100644 index 00000000..96a5aa62 --- /dev/null +++ b/repodir/huixiangdou/web/front-end/src/pages/bean-detail/components/import-docs/import-docs.module.less @@ -0,0 +1,3 @@ +.import-docs { + +} diff --git a/repodir/huixiangdou/web/front-end/src/pages/bean-detail/components/import-docs/import-docs.tsx b/repodir/huixiangdou/web/front-end/src/pages/bean-detail/components/import-docs/import-docs.tsx new file mode 100644 index 00000000..0db19ed2 --- /dev/null +++ b/repodir/huixiangdou/web/front-end/src/pages/bean-detail/components/import-docs/import-docs.tsx @@ -0,0 +1,57 @@ +import { + FC, ReactNode, useState +} from 'react'; +import { IconFont, Modal } from 'sea-lion-ui'; +import Button from '@components/button/button'; +import { useLocale } from '@hooks/useLocale'; +import Upload from '@components/upload'; +import { FileState } from '@services/home'; +import styles from './import-docs.module.less'; + +export interface ImportDocsProps { + filesState: FileState[]; + refresh: () => void; + docs?: string[]; + children?: ReactNode; +} + +const ImportDocs: FC = ({ refresh, docs, filesState }) => { + const locales = useLocale('beanDetail'); + const [openModal, setOpenModal] = useState(false); + + const afterUpload = () => { + refresh(); + }; + const closeModal = () => { + setOpenModal(false); + refresh(); + }; + return ( +
+ + )} + onClose={closeModal} + > + + +
{locales.upload}
+
{locales.supportFiles}
+
+
+
+ ); +}; + +export default ImportDocs; diff --git a/repodir/huixiangdou/web/front-end/src/pages/bean-detail/components/import-docs/index.tsx b/repodir/huixiangdou/web/front-end/src/pages/bean-detail/components/import-docs/index.tsx new file mode 100644 index 00000000..05d21b50 --- /dev/null +++ b/repodir/huixiangdou/web/front-end/src/pages/bean-detail/components/import-docs/index.tsx @@ -0,0 +1,4 @@ +import ImportDocs from './import-docs'; + +export * from './import-docs'; +export default ImportDocs; diff --git a/repodir/huixiangdou/web/front-end/src/pages/bean-detail/components/integrate-feishu/index.tsx b/repodir/huixiangdou/web/front-end/src/pages/bean-detail/components/integrate-feishu/index.tsx new file mode 100644 index 00000000..8400f6ef --- /dev/null +++ b/repodir/huixiangdou/web/front-end/src/pages/bean-detail/components/integrate-feishu/index.tsx @@ -0,0 +1,4 @@ +import IntegrateFeishu from './integrate-feishu'; + +export * from './integrate-feishu'; +export default IntegrateFeishu; diff --git a/repodir/huixiangdou/web/front-end/src/pages/bean-detail/components/integrate-feishu/integrate-feishu.module.less b/repodir/huixiangdou/web/front-end/src/pages/bean-detail/components/integrate-feishu/integrate-feishu.module.less new file mode 100644 index 00000000..97ed763f --- /dev/null +++ b/repodir/huixiangdou/web/front-end/src/pages/bean-detail/components/integrate-feishu/integrate-feishu.module.less @@ -0,0 +1,41 @@ +.integrate-feishu { + .webhook-url { + font-size: 14px; + line-height: 16px; + padding: 8px 0; + cursor: pointer; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: 200px; + color: #9D9D9D; + } +} +.eventurl { + font-weight: bold; +} + +.title { + font-weight: bold; + margin-top: 12px; +} + +.cancel { + color: #9D9D9D; + background: #F4F5F9; + cursor: pointer; + &:hover { + background: #EBECF0; + } +} + +.flex { + display: flex; + align-items: center; + gap: 4px; + span { + word-break: keep-all; + white-space: nowrap; + color: rgba(0, 0, 0, 0.88); + } +} diff --git a/repodir/huixiangdou/web/front-end/src/pages/bean-detail/components/integrate-feishu/integrate-feishu.tsx b/repodir/huixiangdou/web/front-end/src/pages/bean-detail/components/integrate-feishu/integrate-feishu.tsx new file mode 100644 index 00000000..fd6ec450 --- /dev/null +++ b/repodir/huixiangdou/web/front-end/src/pages/bean-detail/components/integrate-feishu/integrate-feishu.tsx @@ -0,0 +1,148 @@ +import { + FC, ReactNode, useEffect, useState +} from 'react'; +import { + IconFont, Input, message, Modal +} from 'sea-lion-ui'; +import { Form } from 'antd'; +import { + Feishu, integrateLark, MsgCode +} from '@services/home'; +import Button from '@components/button/button'; +import { useLocale } from '@hooks/useLocale'; +import CopyCode from '@components/copy-code/copy-code'; +import styles from './integrate-feishu.module.less'; + +export interface IntegrateFeishuProps { + suffix: string; + feishu: Feishu; + refresh: () => void; + children?: ReactNode; +} + +const IntegrateFeishu: FC = ({ + suffix, + feishu, + refresh, + children +}) => { + const [form] = Form.useForm(); + const locales = useLocale('beanDetail'); + + const [openModal, setOpenModal] = useState(false); + const [eventUrl, setEventUrl] = useState(feishu?.eventUrl); + const [encryptKey, setEncryptKey] = useState(feishu?.encryptKey); + const [verificationToken, setVerificationToken] = useState(feishu?.verificationToken); + const [loading, setLoading] = useState(false); + + const handleOpen = () => { + setOpenModal(true); + }; + + const closeModal = () => { + setOpenModal(false); + }; + + const handleSubmit = async () => { + setLoading(true); + form.validateFields() + .then(async (values) => { + integrateLark(values.appId, values.appSecret) + .then((res) => { + if (res && res.msgCode === MsgCode.success) { + setEventUrl(res.data?.eventUrl); + message.success(locales.saveSuccess); + refresh(); + } + }) + .finally(() => { + setLoading(false); + }); + }).finally(() => { + setLoading(false); + }); + }; + + useEffect(() => { + if (openModal && feishu) { + form.setFieldsValue({ ...feishu }); + setEventUrl(feishu.eventUrl || ''); + } else { + form.resetFields(); + } + }, [openModal, feishu]); + + return ( +
+ + )} + onClose={closeModal} + > +
{locales.credentials}
+
+ + + + + + +
+
+ + +
+ +
{locales.encryption}
+
+ Encrypt Key: + +
+
+ Verification Token: + +
+ +
{locales.suffix}
+
+ Suffix: + +
+ +
{locales.larkGuidance}
+ {eventUrl && ( + + )} +
{locales.reference}
+ +
+
+ ); +}; + +export default IntegrateFeishu; diff --git a/repodir/huixiangdou/web/front-end/src/pages/bean-detail/components/integrate-wechat/integrate-wechat.module.less b/repodir/huixiangdou/web/front-end/src/pages/bean-detail/components/integrate-wechat/integrate-wechat.module.less new file mode 100644 index 00000000..cbbaed32 --- /dev/null +++ b/repodir/huixiangdou/web/front-end/src/pages/bean-detail/components/integrate-wechat/integrate-wechat.module.less @@ -0,0 +1,8 @@ +.item-title { + font-weight: bold; + margin-top: 8px; +} +.item-content { + margin-bottom: 24px; + color: #047600; +} \ No newline at end of file diff --git a/repodir/huixiangdou/web/front-end/src/pages/bean-detail/components/integrate-wechat/integrate-wechat.tsx b/repodir/huixiangdou/web/front-end/src/pages/bean-detail/components/integrate-wechat/integrate-wechat.tsx new file mode 100644 index 00000000..27e7693b --- /dev/null +++ b/repodir/huixiangdou/web/front-end/src/pages/bean-detail/components/integrate-wechat/integrate-wechat.tsx @@ -0,0 +1,52 @@ +import { useState } from 'react'; +import { IconFont, Modal } from 'sea-lion-ui'; +import Button from '@components/button/button'; +import { useLocale } from '@hooks/useLocale'; +import CopyCode from '@components/copy-code/copy-code'; +import styles from './integrate-wechat.module.less'; + +export interface IntegrateWechatProps { + messageUrl: string; +} + +const IntegrateWechat = (props: IntegrateWechatProps) => { + const locales = useLocale('beanDetail'); + + const [openModal, setOpenModal] = useState(false); + + const handleOpen = () => { + setOpenModal(true); + }; + return ( +
+ + )} + onClose={() => setOpenModal(false)} + > +
+ {locales.WeChatCallback} +
+
+ +
+
+ {locales.wechatGuidance} +
+ +
+
+ ); +}; + +export default IntegrateWechat; diff --git a/repodir/huixiangdou/web/front-end/src/pages/bean-detail/components/toggle-search/index.tsx b/repodir/huixiangdou/web/front-end/src/pages/bean-detail/components/toggle-search/index.tsx new file mode 100644 index 00000000..061b7e46 --- /dev/null +++ b/repodir/huixiangdou/web/front-end/src/pages/bean-detail/components/toggle-search/index.tsx @@ -0,0 +1,4 @@ +import ToggleSearch from './toggle-search'; + +export * from './toggle-search'; +export default ToggleSearch; diff --git a/repodir/huixiangdou/web/front-end/src/pages/bean-detail/components/toggle-search/toggle-search.module.less b/repodir/huixiangdou/web/front-end/src/pages/bean-detail/components/toggle-search/toggle-search.module.less new file mode 100644 index 00000000..73f66dea --- /dev/null +++ b/repodir/huixiangdou/web/front-end/src/pages/bean-detail/components/toggle-search/toggle-search.module.less @@ -0,0 +1,29 @@ +.toggle-search { + .token { + font-size: 14px; + line-height: 16px; + padding: 8px 0; + cursor: pointer; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: 200px; + color: #9D9D9D; + } +} + +.input-wrapper { + display: flex; + align-items: center; + gap: 8px; + padding: 12px 0; + :global { + .seal-input-group .seal-input-inner-container:focus-within { + outline: 1px solid #59a041; + } + } +} + +a { + color: #286500 +} diff --git a/repodir/huixiangdou/web/front-end/src/pages/bean-detail/components/toggle-search/toggle-search.tsx b/repodir/huixiangdou/web/front-end/src/pages/bean-detail/components/toggle-search/toggle-search.tsx new file mode 100644 index 00000000..fae9ae7f --- /dev/null +++ b/repodir/huixiangdou/web/front-end/src/pages/bean-detail/components/toggle-search/toggle-search.tsx @@ -0,0 +1,98 @@ +import { + FC, ReactNode, useEffect, useState +} from 'react'; +import { + Input, message, Modal +} from 'sea-lion-ui'; +import Button from '@components/button/button'; +import { integrateWebSearch, MsgCode } from '@services/home'; +import { Switch } from 'antd'; +import { useLocale } from '@hooks/useLocale'; +import styles from './toggle-search.module.less'; + +export interface ToggleSearchProps { + refresh: () => void; + webSearchToken: string; + children?: ReactNode; +} + +const ToggleSearch: FC = ({ + refresh, + webSearchToken, + children +}) => { + const locales = useLocale('beanDetail'); + + const [openModal, setOpenModal] = useState(false); + const [token, setToken] = useState(''); + + const afterSuccess = () => { + refresh(); + setOpenModal(false); + }; + + const handleChangeSwitch = async (e) => { + if (!webSearchToken) { + setOpenModal(true); + e.preventDefault(); + e.stopPropagation(); + } else { + const res = await integrateWebSearch(''); + if (res.msgCode === MsgCode.success) { + afterSuccess(); + message.success(locales.webSearchClosed); + } + } + }; + + const handleSaveToken = async () => { + const res = await integrateWebSearch(token); + if (res.msgCode === MsgCode.success && !token) { + afterSuccess(); + message.success(locales.webSearchClosed); + } else if (res.msgCode === MsgCode.success && token) { + afterSuccess(); + message.success(locales.saveSuccess); + } else if (res.msg) { + message.error(res.msg); + } + }; + + useEffect(() => { + if (!openModal) { + setToken(''); + } + }, [openModal]); + + return ( +
+ + )} + onClose={() => setOpenModal(false)} + > +
{locales.webSearchDesc}
+
+ {`1. ${locales.register} `} + Serper + {' '} + {locales.webSearchDesc1} +
+
{locales.webSearchDesc2}
+
{locales.webSearchDesc3}
+
+ setToken(e.target.value)} + placeholder={locales.enterToken} + /> + +
+ +
+ ); +}; + +export default ToggleSearch; diff --git a/repodir/huixiangdou/web/front-end/src/pages/home/home.module.less b/repodir/huixiangdou/web/front-end/src/pages/home/home.module.less new file mode 100755 index 00000000..3d182cec --- /dev/null +++ b/repodir/huixiangdou/web/front-end/src/pages/home/home.module.less @@ -0,0 +1,138 @@ +.btn { + padding: 8px 20px; + background: #c7eaba; + color: #286500; + border-radius: 8px; + font-size: 14px; + line-height: 16px; + display: inline-block; + cursor: pointer; + &[aria-disabled="true"] { + background: #dcdcdc; + color: #9d9d9d; + cursor: not-allowed; + } +} +.home { + padding: 0 100px; + display: flex; + justify-content: center; + align-items: center; + .wrapper { + min-height: 700px; + min-width: 800px; + margin: auto; + text-align: center; + position: absolute; + top: 200px; + .logo { + width: 1000px; + img { + width: 100%; + } + } + .slogan { + text-align: center; + font-size: 3ch; + position: relative; + top: -20px; + color: #83bb70; + font-weight: 500; + margin: 0 auto 72px; + + max-width: 48ch; + animation: typing 8s steps(16) infinite, + blink 0.5s step-end infinite alternate; + white-space: nowrap; + overflow: hidden; + border-right: 5px solid #59a041; + background: linear-gradient(35deg, #7d84d7 20%, #6ac24e 100%); + -webkit-background-clip: text; + background-clip: text; + -webkit-text-fill-color: transparent; + -webkit-box-decoration-break: clone; + } + .input-wrapper { + width: 600px; + margin: 0 auto 48px; + display: flex; + flex-direction: column; + gap: 12px; + .show-psw { + -webkit-text-security: none; + } + .hide-psw { + -webkit-text-security: disc; + } + .eye { + width: 24px; + cursor: pointer; + line-height: 20px; + -webkit-text-security: none; + } + } + .btn-wrapper { + display: flex; + gap: 12px; + align-items: center; + justify-content: center; + .cancel-btn { + .btn(); + color: #9D9D9D; + background: #F4F4F4; + } + } + .divider { + width: 600px; + height: 1px; + border-bottom: 1px solid #C4CFEB; + margin: 72px auto; + } + .statistics-wrapper { + display: grid; + grid-template-columns: repeat(3, 1fr); + grid-gap: 24px; + width: 600px; + margin: 0 auto; + .statistics-item { + .statistics-item-title { + color: #9D9D9D; + font-size: 16px; + margin-bottom: 4px; + line-height: 20px; + } + .statistics-item-number { + color: #286500; + font-size: 36px; + line-height: 40px; + font-weight: 1000; + } + } + } + } +} +@keyframes typing { + 0% { + max-width: 0; + } + 20% { + max-width: 0; + } + 40% { + max-width: 48ch; + width: fit-content; + } + 70% { + max-width: 48ch; + width: fit-content; + } + 100% { + max-width: 0; + } +} + +@keyframes blink { + 50% { + border-color: transparent; + } +} diff --git a/repodir/huixiangdou/web/front-end/src/pages/home/home.tsx b/repodir/huixiangdou/web/front-end/src/pages/home/home.tsx new file mode 100755 index 00000000..1637240e --- /dev/null +++ b/repodir/huixiangdou/web/front-end/src/pages/home/home.tsx @@ -0,0 +1,161 @@ +import { useNavigate } from 'react-router-dom'; +import { useLocale } from '@hooks/useLocale'; +import { IconFont, Input, message } from 'sea-lion-ui'; +import { useEffect, useMemo, useState } from 'react'; +import logo from '@assets/imgs/logo.png'; +import bean from '@assets/imgs/bean1.png'; +import { + getStatistic, loginBean, MsgCode, StatisticDto +} from '@services/home'; +import styles from './home.module.less'; + +const Home = () => { + const navigate = useNavigate(); + const [beanName, setBeanName] = useState(''); + const [beanPwd, setBeanPwd] = useState(''); + const [statistic, setStatistic] = useState(null); + const [showPsw, setShowPsw] = useState(false); + + const locales = useLocale('home'); + + const resetInput = () => { + setBeanName(''); + setBeanPwd(''); + }; + + const validateBean = async (name, password) => { + const res = await loginBean(name, password); + if (res.msgCode !== MsgCode.success) { + message.error(res.msg); + } + if (res.msgCode === MsgCode.success && res.data.featureStoreId) { + navigate(`/bean-detail/?bean=${res.data.featureStoreId}`); + } + }; + + const handleConfirm = () => { + if (beanName && beanName.length < 8) { + message.info(locales.validateMsg); + return; + } + if (beanName && beanName.length > 7 && beanPwd) { + validateBean(beanName, beanPwd); + } + }; + + useEffect(() => { + (async () => { + const res = await getStatistic(); + if (res) { + setStatistic(res); + } + })(); + }, []); + + const Statistics = useMemo(() => { + if (!statistic) return []; + return ([ + { + title: locales.bean, + key: 'bean', + number: statistic.qalibTotal || 0 + }, + { + title: locales.WeChat, + key: 'WeChat', + number: statistic.wechatTotal || 0 + }, + { + title: locales.users, + key: 'users', + number: statistic.servedTotal || 0 + }, + { + title: locales.activeBean, + key: 'activeBean', + number: statistic.lastMonthUsed || 0 + }, + { + title: locales.feishu, + key: 'feishu', + number: statistic.feishuTotal || 0 + }, + { + title: locales.uniqueUsers, + key: 'uniqueUsers', + number: statistic.realServedTotal || 0 + } + ]); + }, [locales, statistic]); + + return ( +
+
+
+ huixiangdou +
+
{locales.slogan}
+
+ setBeanName(e.target.value)} + prefix={( + bean + )} + /> + setBeanPwd(e.target.value)} + onPressEnter={handleConfirm} + prefix={( +
setShowPsw(!showPsw)} + > + +
+ )} + /> +
+
+
+ {locales.cancel} +
+
+ {locales.go} +
+
+
+
+ {Statistics.map((item) => ( +
+
{item.title}
+
{item.number}
+
+ ))} +
+
+
+ ); +}; + +export default Home; diff --git a/repodir/huixiangdou/web/front-end/src/routes/index.tsx b/repodir/huixiangdou/web/front-end/src/routes/index.tsx new file mode 100644 index 00000000..fbdb70ee --- /dev/null +++ b/repodir/huixiangdou/web/front-end/src/routes/index.tsx @@ -0,0 +1,36 @@ +// router component +import { + BrowserRouter, Routes, Route, Navigate +} from 'react-router-dom'; +import HeaderContainerLayout from '@layouts/header-container-layout/header-container-layout'; +import Home from '@pages/home/home'; +import BeanDetail from '@pages/bean-detail/bean-detail'; + +const RouterRoot = () => { + return ( + // react-router-dom v6 123 + // https://reactrouter.com/docs/en/v6/getting-started/overview + + + }> + } + /> + } /> + } /> + + +

There is nothing here!

+ + )} + /> +
+
+ ); +}; + +export default RouterRoot; diff --git a/repodir/huixiangdou/web/front-end/src/services/home.ts b/repodir/huixiangdou/web/front-end/src/services/home.ts new file mode 100644 index 00000000..ce97e078 --- /dev/null +++ b/repodir/huixiangdou/web/front-end/src/services/home.ts @@ -0,0 +1,207 @@ +import { request } from '@/utils/ajax'; + +const beanPrefix = ''; + +export enum MsgCode { + success = '10000', + notAuthed = 'A1000', + createFail = 'A2000', + loginFail = 'A2001', + notExist = 'A2002', +} +export interface BasicRespDto { + 'msgCode': string + 'msg': string, + success: boolean +} +export interface BeanRspDto extends BasicRespDto { + data: { + 'exist': boolean, + 'featureStoreId': string // 知识库id + } +} +export interface Feishu { + 'webhookUrl': string, + 'appId': string, + 'appSecret': string, + 'encryptKey': string, + 'verificationToken': string, + 'eventUrl': string +} + +export interface FileState { + file: string, + status: boolean, + desc: string +} + +export interface BeanInfoDto { + 'featureStoreId': string, + 'name': string, + 'docs': string[], + 'docsBase': string, + 'status': number, + 'suffix': string, + 'lark': Feishu, + 'wechat': { + 'onMessageUrl': string + }, + 'webSearch': { + 'token': string + }, + filesState: FileState[] +} + +export interface SampleInfoDto { + 'name': string, + 'featureStoreId': string, + 'positives': string[], + 'negatives': string[] +} + +export interface StatisticDto { + 'qalibTotal': number, + 'lastMonthUsed': number, + 'wechatTotal': number, + 'feishuTotal': number, + 'servedTotal': number, + 'realServedTotal': number +} +export interface DocsRspDto { + docsBase: string; + docs: string[] +} + +export interface Chat { + 'content': string, + 'images': string[], // 本次上传的图片流列表,使用base64编码 + 'history': { + 'sender': number, // 0: 用户 1: HuixiangDou + 'content': string + }[] +} + +export interface OnlineRspDto { + code: number, + state: string, + text: string, + references: string[] +} + +export interface LarkRspDto extends BasicRespDto { + data: Feishu +} + +export async function getStatistic() { + return request('/api/v1/statistic/v1/total', { + method: 'GET', + }, beanPrefix); +} + +export async function loginBean(name: string, password: string) { + return request('/api/v1/access/v1/login', { + method: 'POST', + data: { + name, + password + }, + meta: { + isAllResponseBody: true + } + }, beanPrefix); +} + +export async function getInfo() { + return request('/api/v1/qalib/v1/getInfo', { + method: 'POST', + }, beanPrefix); +} + +export async function deleteDocs(filenames: string[]) { + return request('/api/v1/qalib/v1/deleteDocs', { + method: 'POST', + data: { + filenames + } + }, beanPrefix); +} + +export async function addDocs(files: File[]) { + const data = new FormData(); + for (let i = 0; i < files.length; i++) { + const file = files[i]; + data.append('files', file); + } + return request('/api/v1/qalib/v1/addDocs', { + method: 'POST', + data + }); +} + +export async function updateSampleInfo(positives: string[], negatives: string[]) { + return request('/api/v1/qalib/v1/updateSampleInfo', { + method: 'POST', + data: { + positives, + negatives + } + }, beanPrefix); +} + +export async function getSampleInfo() { + return request('/api/v1/qalib/v1/getSampleInfo', { + method: 'POST', + }, beanPrefix); +} + +export async function integrateWebSearch(webSearchToken: string) { + return request('/api/v1/qalib/v1/integrateWebSearch', { + method: 'POST', + data: { + webSearchToken, + vendor: 'google' + }, + meta: { + isAllResponseBody: true + } + }, beanPrefix); +} + +export async function integrateLark(appId: string, appSecret: string) { + return request('/api/v1/qalib/v1/integrateLark', { + method: 'POST', + data: { + appId, + appSecret + }, + meta: { + isAllResponseBody: true + } + }, beanPrefix); +} + +export async function online(data: Chat) { + return request<{ queryId: string }>('/api/v1/chat/v1/online', { + method: 'POST', + data + }, beanPrefix); +} + +export async function onlineResponse(queryId: string) { + return request('/api/v1/chat/v1/onlineResponse', { + method: 'POST', + data: { + queryId + } + }, beanPrefix); +} + +export async function caseFeedback(queryId: string, type: string) { + return request('/api/v1/chat/v1/caseFeedback', { + method: 'POST', + data: { + queryId, + type + } + }, beanPrefix); +} diff --git a/repodir/huixiangdou/web/front-end/src/services/user.ts b/repodir/huixiangdou/web/front-end/src/services/user.ts new file mode 100644 index 00000000..15d7fbf3 --- /dev/null +++ b/repodir/huixiangdou/web/front-end/src/services/user.ts @@ -0,0 +1,54 @@ +import { request } from '@utils/ajax'; + +const userServicePrefix = '/gw/user-service'; +const uaaServicePrefix = '/gw/uaa-be'; + +export interface fetchCurrentUserReqDto { + avatar?: string; + email?: string; + expiration?: string; + roleIds?: string[]; + nickname?: string; + jwt?: string; + ssoUid: string; + username?: string; + wechat?: string; + wechatName?: string; + [key: string]: any; +} + +// 获取用户信息 +export async function fetchCurrentUser( + token: string, +) { + return request('/api/v1/login/getUserInfo', { + method: 'POST', + headers: { + Authorization: `Bearer ${token}` + }, + }, uaaServicePrefix); +} + +export async function logout() { + return request('/api/v1/logout/all', { + method: 'POST', + meta: { + isAllResponseBody: true + }, + }, uaaServicePrefix); +} + +export interface fetchOauthCodeReqDto { + token: string; +} + +// sso第三方登录验证后,拿取用户信息 +export const fetchOauthCode = (code: string | string[], redirect: string) => { + return request('/api/v1/account/oauth', { + method: 'POST', + data: { + code, + redirect + } + }, userServicePrefix); +}; diff --git a/repodir/huixiangdou/web/front-end/src/styles/index.less b/repodir/huixiangdou/web/front-end/src/styles/index.less new file mode 100644 index 00000000..ce74c998 --- /dev/null +++ b/repodir/huixiangdou/web/front-end/src/styles/index.less @@ -0,0 +1,2 @@ +// @import './normalize.css'; normalize.css通过cdn引入了 +@import "mixins.less"; diff --git a/repodir/huixiangdou/web/front-end/src/styles/mixins.less b/repodir/huixiangdou/web/front-end/src/styles/mixins.less new file mode 100644 index 00000000..e69de29b diff --git a/repodir/huixiangdou/web/front-end/src/styles/variables.less b/repodir/huixiangdou/web/front-end/src/styles/variables.less new file mode 100644 index 00000000..591538f3 --- /dev/null +++ b/repodir/huixiangdou/web/front-end/src/styles/variables.less @@ -0,0 +1,19 @@ +@red: red; +@black: #000; +@white: #fff; +@border-color: #EBECF0; + +@main-content-width: 1440px; +@main-content-hoz-padding: 120px; +@x-lab-header-height: 65px; + +@border-lg: 1px solid @black; +@input-background-color: #f4f5f9; +@input-border-color: #D7D8DD; +@select-arrow-color: #464a53; + +@border: 1px solid @border-color; +@form-item-bg: #F4F5F9; +@text-line-height: 21px; +@form-input-bg: #F9F9F9; + diff --git a/repodir/huixiangdou/web/front-end/src/types.d.ts b/repodir/huixiangdou/web/front-end/src/types.d.ts new file mode 100644 index 00000000..1dfe858e --- /dev/null +++ b/repodir/huixiangdou/web/front-end/src/types.d.ts @@ -0,0 +1,14 @@ +/* eslint-disable no-undef */ +/* eslint-disable no-unused-vars */ +declare module '*.css'; +declare module '*.less'; +declare module '*.png'; +declare module '*.jpg'; +declare module '*.jpeg'; +declare module '*.svg' { + export function ReactComponent( + props: React.SVGProps, + ): React.ReactElement; + const url: string; + export default url; +} diff --git a/repodir/huixiangdou/web/front-end/src/utils/ajax.ts b/repodir/huixiangdou/web/front-end/src/utils/ajax.ts new file mode 100644 index 00000000..988a0f6c --- /dev/null +++ b/repodir/huixiangdou/web/front-end/src/utils/ajax.ts @@ -0,0 +1,114 @@ +import axios, { AxiosError } from 'axios'; +import qs from 'qs'; +import { BaseURL, ApiPrefix } from '@config/base-url'; +import { requestInterceptors } from '@interceptors/request'; +import { responsetInterceptors, responsetErrorInterceptors } from '@interceptors/response'; + +export const compose = (...args: any[]) => { + const fns = args.map(arg => { + return typeof arg === 'function' ? arg : () => arg; + }); + + return (...innerArgs: any) => { + let index = 0; + let result; + result = fns.length === 0 ? innerArgs : fns[index++](...innerArgs); + + while (index < fns.length) { + result = fns[index++](result); + } + + return result; + }; +}; + +export const instance = axios.create({ + method: 'get', + timeout: 300000, + responseType: 'json', + paramsSerializer: params => qs.stringify(params, { indices: false }) +}); + +const MetaDataMap = new Map(); +export interface Meta { + isAllResponseBody?:boolean + isIgnoreError?:boolean + isIgnoreGatewayError?:boolean +} + +const getMeta = (url) => { + let meta:Meta = {}; + if (MetaDataMap.has(url)) { + meta = MetaDataMap.get(url); + MetaDataMap.delete(url); + } + return meta; +}; + +const inBuildHandleMetaResponseInterceptors = (response) => { + return { + ...response, + __meta: getMeta(response.config.url) + }; +}; + +const inBuildHandleMetaErrorInterceptors = (error:AxiosError) => { + const { response } = error; + return { + ...error, + __meta: getMeta(response.config.url) + }; +}; + +responsetInterceptors.unshift(inBuildHandleMetaResponseInterceptors); +responsetErrorInterceptors.unshift(inBuildHandleMetaErrorInterceptors); + +const handleRequestInterceptors = compose(...requestInterceptors); +const handleResponsetInterceptors = compose(...responsetInterceptors); +const handleResponsetErrorInterceptors = compose(...responsetErrorInterceptors); + +instance.interceptors.request.use( + handleRequestInterceptors, + err => (Promise.reject(err)) +); + +instance.interceptors.response.use(handleResponsetInterceptors, handleResponsetErrorInterceptors); + +export interface DefaultRespDTO { + msgCode: number; + msg: string; + data: T; +} + +export const ajax = (api, { + method = 'GET', + params = {}, // url query参数 + data = {}, // http body 参数 + ...rest +}): Promise => { + const url = `${BaseURL}${api}`; + switch (method.toLowerCase()) { + case 'get': + return instance.get(url, { params, ...rest }); + case 'delete': + return instance.delete(url, { params, data, ...rest }); + case 'post': + return instance.post(url, data, { params, ...rest }); + case 'put': + return instance.put(url, data, { params, ...rest }); + default: + return instance.get(url, { params, ...rest }); + } +}; + +export const request = (api: string, options: any = {}, prefix = ApiPrefix) => { + const needPrefix = prefix || ApiPrefix; + const fullApi = (`${needPrefix}/${api}`).replace(/\/\//g, '/'); + if (options.meta) { + MetaDataMap.set(fullApi, options.meta); + delete options.meta; + } + return ajax(fullApi, options); +}; + +export default ajax; diff --git a/repodir/huixiangdou/web/front-end/src/utils/mlog.ts b/repodir/huixiangdou/web/front-end/src/utils/mlog.ts new file mode 100644 index 00000000..70f6a2c1 --- /dev/null +++ b/repodir/huixiangdou/web/front-end/src/utils/mlog.ts @@ -0,0 +1,68 @@ +import { MeasurementId, openLog } from '@config/log'; + +declare global { + interface Window { + dataLayer: any; + mlog: any; + } +} + +window.mlog = null; + +export const ScriptUrl = `https://www.googletagmanager.com/gtag/js?id=${MeasurementId}`; + +class Mlog { + log: ((...params: any[]) => void) | undefined; + + static init(): Promise { + if (!openLog) return Promise.resolve(null); + + return new Promise((resolve, reject) => { + if (window.mlog && window.mlog instanceof Mlog) { + resolve(null); + return; + } + + const syncScript = document.createElement('script'); + syncScript.innerHTML = ` + window.dataLayer = window.dataLayer || []; + function gtag(){dataLayer.push(arguments);} + gtag('js', new Date()); + gtag('config', '${MeasurementId}'); + `; + + document.head.append(syncScript); + const statisScript = document.createElement('script'); + statisScript.async = true; + statisScript.src = ScriptUrl; + statisScript.onload = () => { + resolve(null); + }; + + statisScript.onerror = () => { + reject('load failed'); + }; + document.head.insertBefore(statisScript, syncScript); + window.mlog = new Mlog(); + }); + } + + static configUserId(userId) { + if (!openLog) return; + if (typeof window.gtag === 'function') { + window.gtag('config', MeasurementId, { + user_id: userId + }); + } + } + + static sendEvent(eventName: string, ext: any): void { + if (!openLog) return; + + if (typeof window.gtag === 'function') { + window.gtag('event', eventName, ext); + } + } +} + +export default Mlog; diff --git a/repodir/huixiangdou/web/front-end/src/utils/utils.ts b/repodir/huixiangdou/web/front-end/src/utils/utils.ts new file mode 100644 index 00000000..48e7ab0e --- /dev/null +++ b/repodir/huixiangdou/web/front-end/src/utils/utils.ts @@ -0,0 +1,191 @@ +import { useIntl } from 'react-intl'; +import qs from 'query-string'; +import jsCookie from 'js-cookie'; +import { clientId, logURL, TokenCookieDomain } from '@config/auth'; +import { fetchCurrentUser as queryCurrentUser } from '@services/user'; + +export type Language = 'zh-CN' | 'en-US'; +export const LanguageKey = 'locale'; + +export const loadLang = () => { + const storeLang = window.localStorage.getItem(LanguageKey); + if (storeLang) { + return storeLang === 'en-US' ? 'en-US' : 'zh-CN'; + } + // auto detect system language + const systemLang = window.navigator.language; + if (systemLang.includes('zh')) { + localStorage.setItem(LanguageKey, 'zh-CN'); + return 'zh-CN'; + } + // default lang: English + localStorage.setItem(LanguageKey, 'en-US'); + return 'en-US'; +}; + +let currentLang: Language = loadLang(); +const saveLang = (lang: Language) => { + window.localStorage.setItem(LanguageKey, lang); + return lang; +}; + +export const getLang = () => currentLang; +export const setLang = (lang: Language) => { + currentLang = saveLang(lang); +}; + +export const Intl = (id: string) => { + return useIntl().formatMessage({ id }); +}; + +// 用javascript删除某一个cookie的方法,该方法传入要删除cookie的名称 +export const removeCookie = (cookieName: string) => { + const cookies = document.cookie.split(';');// 将所有cookie键值对通过分号分割为数组 + // 循环遍历所有cookie键值对 + for (let i = 0; i < cookies.length; i++) { + // 有些cookie键值对前面会莫名其妙产生一个空格,将空格去掉 + const _cookieName = cookies[i].split('=')[0].trim(); + // 比较每个cookie的名称,找到要删除的那个cookie键值对 + if (_cookieName === cookieName) { + const exp = new Date();// 获取客户端本地当前系统时间 + + // 将exp设置为客户端本地时间1分钟以前,将exp赋值给cookie作为过期时间后,就表示该cookie已经过期了, 那么浏览器就会将其立刻删除掉 + exp.setTime(exp.getTime() - 60); + + // 设置要删除的cookie的过期时间,即在该cookie的键值对后面再添加一个expires键值对 + // 并将上面的exp赋给expires作为值(注意expires的值必须为UTC或者GMT时间,不能用本地时间) + // 那么浏览器就会将该cookie立刻删除掉 + document.cookie = `${cookies[i]};expires=${exp.toUTCString()};path=/;domain=${TokenCookieDomain}`; + + // 注意document.cookie的用法很巧妙,在对其进行赋值的时候是设置单个cookie的信息,但是获取document.cookie的值的时候是返回所有cookie的信息 + break;// 要删除的cookie已经在客户端被删除掉,跳出循环 + } + } +}; + +export const formatQuery = (basename = '') => { + const { search, pathname } = window.location; + const url: string = pathname + search; + let oauthCode; + let realPath = ''; + const query = qs.parse(search) || {}; + const code = query.code || ''; + const lang = query.lang || ''; + if (url.startsWith(basename)) { + // 判断 pathname 是否是以 basename 开头 + realPath = url.slice(basename?.length); + realPath = realPath.startsWith('/') ? realPath : `/${realPath}`; + } + // 从 uaa 鉴权成功后,会把 code 拼在 url 最后 + // 兼容 子平台中用 code 作为业务参数 + if (Array.isArray(code)) { + oauthCode = code[code.length - 1] || ''; + } else { + oauthCode = code; + } + // 除了 code 外 url 还有其他的 query ,或者 有多个 code 的情况下 + // 鉴权 code 一定在 url 最后 + if (Object.keys(query).length > 1 || Array.isArray(code)) { + realPath = realPath.replace(`&code=${oauthCode}`, ''); + } else { + // url 只有 code 一个 query + realPath = realPath.replace(`?code=${oauthCode}`, ''); + } + + realPath = realPath.replace(`?lang=${lang}&`, '?'); + realPath = realPath.replace(`?lang=${lang}`, ''); + realPath = realPath.replace(`&lang=${lang}`, ''); + + return { + realPath, + oauthCode, + lang, + }; +}; + +export const Token = { + tokenKey: 'hxd_token', + cookieTokenKey: 'hxd_token', + getFromCookie() { + return jsCookie.get(this.cookieTokenKey); + }, + + storage(token: string | null | undefined) { + if (token === undefined || token === null) { + // localStorage.removeItem(this.tokenKey); + console.log(`[Token]: ${token} is invalidate`); + return false; + } + localStorage.setItem(this.tokenKey, token); + return true; + }, + + update(token: string | null) { + const oldToken = localStorage.getItem(this.tokenKey); + if (oldToken !== token) { + this.storage(token); + } + }, + + get() { + const currentToken = this.getFromCookie() as (string | null); + + this.update(currentToken); + return currentToken || localStorage.getItem(this.tokenKey); + }, + + has() { + return !!this.get(); + }, + + removeAll() { + removeCookie(this.cookieTokenKey); + removeCookie('ssouid'); + localStorage.removeItem(this.tokenKey); + } +}; + +export const UserInfo = { + key: '_$_userinfo_key_$_', + + async get(token: string) { + const userInfo = localStorage.getItem(this.key); + if (userInfo) return JSON.parse(userInfo); + + if (token && !userInfo) { + const resp = await queryCurrentUser(token); + localStorage.setItem(this.key, JSON.stringify(resp)); + return resp; + } + + return null; + }, + + del() { + localStorage.removeItem(this.key); + } +}; + +// Function which concat all functions together +export const callFnsInSequence = (...fns: any[]) => (...args: any) => fns.forEach((fn) => fn && fn(...args)); + +export const jumpLogin = () => { + let href = window.location.href; + const url = new URL(href); + + if (url.searchParams.has('code')) { + url.searchParams.delete('code'); + + if (url.searchParams.has('lang')) url.searchParams.delete('lang'); + + href = url.toString(); + } + // debugger; + return `${logURL}/authentication?redirect=${href}&clientId=${clientId}&lang=${getLang()}`; +}; + +export const isNeedAuth = (authPages): boolean => { + const pathname = window.location.pathname.endsWith('/') ? window.location.pathname.slice(0, -1) : window.location.pathname; + const matchPage = authPages.find(page => new RegExp(page).test(pathname)); + return !!matchPage; +}; diff --git a/repodir/huixiangdou/web/front-end/src/vite-env.d.ts b/repodir/huixiangdou/web/front-end/src/vite-env.d.ts new file mode 100644 index 00000000..96fa7bb5 --- /dev/null +++ b/repodir/huixiangdou/web/front-end/src/vite-env.d.ts @@ -0,0 +1,14 @@ +/// + +// declare Google Analytics gtag.js +declare interface Window {gtag: any; dataBuried: any; sealionJSONPCallback: any; } + +interface ImportMetaEnv { + readonly VITE_NODE: string + // 更多环境变量... +} + +interface ImportMeta { + readonly env: ImportMetaEnv +} + diff --git a/repodir/huixiangdou/web/front-end/tsconfig.json b/repodir/huixiangdou/web/front-end/tsconfig.json new file mode 100644 index 00000000..5073feb9 --- /dev/null +++ b/repodir/huixiangdou/web/front-end/tsconfig.json @@ -0,0 +1,75 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "node", + // "allowImportingTsExtensions": true, + "allowSyntheticDefaultImports":true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "baseUrl": ".", + "paths": { + "@/*": [ + "src/*" + ], + "@components/*": [ + "src/components/*" + ], + "@layouts/*": [ + "src/layouts/*" + ], + "@assets/*": [ + "src/assets/*" + ], + "@pages/*": [ + "src/pages/*" + ], + "@services/*": [ + "src/services/*" + ], + "@utils/*": [ + "src/utils/*" + ], + "@styles/*": [ + "src/styles/*" + ], + "@routes/*": [ + "src/routes/*" + ], + "@config/*": [ + "src/config/*" + ], + "@locales/*": [ + "src/locales/*" + ], + "@interceptors/*": [ + "src/interceptors/*" + ], + "@hooks/*": [ + "src/hooks/*" + ], + "@constants/*": [ + "src/constants/*" + ] + }, + "allowJs": true, + "outDir": "./dist", + }, + "include": [ + "src/**/*", + "src/**/*.ts", + "src/**/*.tsx", + "src/**/*.vue", + "tests/**/*.ts", + "tests/**/*.tsx", + "src/types.d.ts" + ], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/repodir/huixiangdou/web/front-end/tsconfig.node.json b/repodir/huixiangdou/web/front-end/tsconfig.node.json new file mode 100644 index 00000000..45dda52e --- /dev/null +++ b/repodir/huixiangdou/web/front-end/tsconfig.node.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true + }, + "include": ["vite.config.ts", "scripts/*"] +} diff --git a/repodir/huixiangdou/web/front-end/vite.config.ts b/repodir/huixiangdou/web/front-end/vite.config.ts new file mode 100644 index 00000000..21c7c60d --- /dev/null +++ b/repodir/huixiangdou/web/front-end/vite.config.ts @@ -0,0 +1,65 @@ +import { defineConfig, splitVendorChunkPlugin } from 'vite'; +import react from '@vitejs/plugin-react'; +import { Plugin as PluginImportToCDN } from 'vite-plugin-cdn-import'; +import legacy from '@vitejs/plugin-legacy'; +import { ProxyConfig, ImportToCDNList, alias } from './scripts'; +import { resolvePath } from './scripts/utils'; + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [ + legacy({ + targets: ['defaults', 'ie >= 11', 'chrome >= 52'], + additionalLegacyPolyfills: ['regenerator-runtime/runtime'], + renderLegacyChunks: true, + polyfills: [ + 'es.symbol', + 'es.array.filter', + 'es.promise', + 'es.promise.finally', + 'es/map', + 'es/set', + 'es.array.for-each', + 'es.object.define-properties', + 'es.object.define-property', + 'es.object.get-own-property-descriptor', + 'es.object.get-own-property-descriptors', + 'es.object.keys', + 'es.object.to-string', + 'web.dom-collections.for-each', + 'esnext.global-this', + 'esnext.string.match-all', + ], + }), + // vite-plugin-cdn-import只会在build介入,不影响dev,dev还是依赖npm安装的包 + PluginImportToCDN({ + modules: ImportToCDNList + }), + splitVendorChunkPlugin(), + react({ + babel: { + plugins: [ + '@babel/plugin-proposal-optional-chaining', // 兼容老版本浏览器的语法解译 + ] + } + }) + ], + css: { + modules: { + localsConvention: 'camelCase' + }, + preprocessorOptions: { + less: { + additionalData: `@import (reference) url("${resolvePath('src/styles/variables.less')}");` + } + } + }, + server: { + host: true, + proxy: ProxyConfig + }, + envDir: resolvePath('env'), + resolve: { + alias + } +}); diff --git a/repodir/huixiangdou/web/main.py b/repodir/huixiangdou/web/main.py new file mode 100644 index 00000000..b5aa25d5 --- /dev/null +++ b/repodir/huixiangdou/web/main.py @@ -0,0 +1,107 @@ +#!/usr/bin/env python3 +# This is the main entrance of Huixiangdou-WEB. +# This project is written under python 3.9 + +import os + +import uvicorn +from fastapi import Depends, FastAPI, HTTPException, Request +from fastapi.middleware.cors import CORSMiddleware +from fastapi.responses import FileResponse +from starlette.responses import HTMLResponse, JSONResponse, RedirectResponse + +import web.api.access as access +import web.api.qalib as qalib +import web.api.statistic as statistic +from web.api import chat, integrate, message +from web.config.env import HuixiangDouEnv +from web.config.logging import LOGGING_CONFIG +from web.middleware.token import check_hxd_token +from web.scheduler.huixiangdou_task import start_scheduler, stop_scheduler +from web.util.log import log +from web.util.str import safe_join + +# log +logger = log(__name__) + +# define global variable +API_VER = 'v1' +SERVER_PORT = HuixiangDouEnv.get_server_port() +BASE_DIR = os.path.dirname(os.path.abspath(__file__)) +STATIC_RESOURCE_DIR = os.path.join(BASE_DIR, 'front-end', 'dist') +ASSETS_RESOURCE_DIR = os.path.join(STATIC_RESOURCE_DIR, 'assets') + +# FastAPI setting +app = FastAPI() +app.add_middleware( + CORSMiddleware, + allow_origins=['*'], + allow_credentials=True, + allow_methods=['*'], + allow_headers=['*'], +) + +app.include_router(router=access.access_api, prefix=f'/api/{API_VER}/access') +app.include_router(router=qalib.qalib_api, + prefix=f'/api/{API_VER}/qalib', + dependencies=[Depends(check_hxd_token)]) +app.include_router(router=integrate.integrate_api, + prefix=f'/api/{API_VER}/qalib', + dependencies=[Depends(check_hxd_token)]) +app.include_router(router=statistic.statistic_api, + prefix=f'/api/{API_VER}/statistic') +app.include_router(router=chat.chat_api, + prefix=f'/api/{API_VER}/chat', + dependencies=[Depends(check_hxd_token)]) +app.include_router(router=message.message_api, + prefix=f'/api/{API_VER}/message') + + +@app.get('/', response_class=HTMLResponse) +@app.get('/home', response_class=HTMLResponse) +@app.get('/bean-detail/', response_class=HTMLResponse) +async def server(): + return FileResponse(f'{STATIC_RESOURCE_DIR}/index.html') + + +@app.get('/assets/{path:path}') +async def resource_assets(path: str): + return FileResponse(safe_join(ASSETS_RESOURCE_DIR, path)) + + +@app.get('/{path:path}') +async def resource_other(path: str): + return FileResponse(safe_join(STATIC_RESOURCE_DIR, path)) + + +@app.on_event('startup') +def on_startup(): + start_scheduler() + + +@app.on_event('shutdown') +def on_shutdown(): + stop_scheduler() + + +@app.exception_handler(HTTPException) +async def global_exception_handler(_: Request, exc: HTTPException): + return JSONResponse(status_code=exc.status_code, content=exc.detail) + + +def main(): + """main function start server use uvicorn default workers: 3 default port: + + 23333. + """ + HuixiangDouEnv.print_env() + uvicorn.run('web.main:app', + host='0.0.0.0', + port=int(SERVER_PORT), + timeout_keep_alive=600, + workers=3, + log_config=LOGGING_CONFIG) + + +if __name__ == '__main__': + main() diff --git a/repodir/huixiangdou/web/middleware/__init__.py b/repodir/huixiangdou/web/middleware/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/repodir/huixiangdou/web/middleware/token.py b/repodir/huixiangdou/web/middleware/token.py new file mode 100644 index 00000000..34e1d82f --- /dev/null +++ b/repodir/huixiangdou/web/middleware/token.py @@ -0,0 +1,64 @@ +from typing import Union + +from fastapi import HTTPException +from starlette.requests import Request + +import web.constant.biz_constant as biz_const +import web.util.str as str_util +from web.model.base import standard_error_response +from web.model.huixiangdou import HxdToken +from web.model.qalib import QalibInfo, Wechat +from web.service.qalib import (QaLibCache, get_lark_on_message_url, + get_wechat_on_message_url) +from web.util.log import log + +logger = log(__name__) + + +def _get_hxd_token_by_cookie(cookies) -> Union[HxdToken, None]: + return HxdToken( + **(str_util.parse_jwt(cookies.get(biz_const.HXD_COOKIE_KEY)) + )) if cookies and cookies.get(biz_const.HXD_COOKIE_KEY) else None + + +def check_hxd_token(request: Request) -> QalibInfo: + hxd_token = _get_hxd_token_by_cookie(request.cookies) + if not hxd_token or not hxd_token.jti: + logger.error( + '[access] invalid request, need login to feature store first') + err_body = standard_error_response(biz_const.ERR_QALIB_API_NO_ACCESS) + raise HTTPException(status_code=200, detail=err_body.model_dump()) + + info = QaLibCache.get_qalib_info(hxd_token.jti) + if not info: + logger.error( + f'[access] invalid login, feature_store_id: {hxd_token.jti} not exists' + ) + err_body = standard_error_response(biz_const.ERR_QALIB_INFO_NOT_FOUND) + raise HTTPException(status_code=200, detail=err_body.model_dump()) + + check_endpoint_update(info) + + return info + + +def check_endpoint_update(info: QalibInfo): + update = False + if not info.wechat or info.wechat.onMessageUrl.endswith('wechat'): + info.wechat = Wechat( + onMessageUrl=get_wechat_on_message_url(info.suffix)) + update = True + else: + wechat_message_url = get_wechat_on_message_url(info.suffix) + if info.wechat.onMessageUrl != wechat_message_url: + info.wechat.onMessageUrl = wechat_message_url + update = True + + if info.lark: + lark_event_url = get_lark_on_message_url() + if info.lark.eventUrl != lark_event_url: + info.lark.eventUrl = lark_event_url + update = True + + if update: + QaLibCache.set_qalib_info(info.featureStoreId, info) diff --git a/repodir/huixiangdou/web/model/__init__.py b/repodir/huixiangdou/web/model/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/repodir/huixiangdou/web/model/access.py b/repodir/huixiangdou/web/model/access.py new file mode 100644 index 00000000..a51d1940 --- /dev/null +++ b/repodir/huixiangdou/web/model/access.py @@ -0,0 +1,11 @@ +from pydantic import BaseModel + + +class LoginBody(BaseModel): + name: str + password: str + + +class AccessInfo(BaseModel): + hashpass: str + featureStoreId: str diff --git a/repodir/huixiangdou/web/model/base.py b/repodir/huixiangdou/web/model/base.py new file mode 100644 index 00000000..344bcaf2 --- /dev/null +++ b/repodir/huixiangdou/web/model/base.py @@ -0,0 +1,22 @@ +from enum import Enum + +from pydantic import BaseModel, Field + + +class BaseBody(BaseModel): + msg: str = Field(default='ok') + msgCode: str = Field(default='10000') + data: object = None + + +class Image(Enum): + INVALID = 'invalid' + JPG = 'jpeg' + PNG = 'png' + BMP = 'bmp' + + +def standard_error_response(error: dict, data=None) -> BaseBody: + if not data: + data = {} + return BaseBody(msg=error.get('msg'), msgCode=error.get('code'), data=data) diff --git a/repodir/huixiangdou/web/model/chat.py b/repodir/huixiangdou/web/model/chat.py new file mode 100644 index 00000000..56ab4d8b --- /dev/null +++ b/repodir/huixiangdou/web/model/chat.py @@ -0,0 +1,74 @@ +from enum import Enum +from typing import List, Optional + +from pydantic import BaseModel, RootModel + +from web.model.huixiangdou import ChatResponse, HxdTaskChatHistory + + +class ChatRequestBody(BaseModel): + content: Optional[str] = '' + images: Optional[List[str]] = [] + history: Optional[List[HxdTaskChatHistory]] = [] + + +class ChatOnlineResponseBody(BaseModel): + queryId: str + + +class ChatType(Enum): + LARK = 0 + WECHAT = 1 + ONLINE = 2 + + +class ChatQueryInfo(BaseModel): + featureStoreId: str + queryId: str + type: Optional[ChatType] = ChatType.ONLINE + request: ChatRequestBody + response: Optional[ChatResponse] = None + detail: Optional[object] = {} + + +class ChatCaseType(Enum): + GOOD_CASE = 'good' + BAD_CASE = 'bad' + + +class ChatCaseFeedbackBody(BaseModel): + queryId: str + type: ChatCaseType + + +class LarkChatDetail(BaseModel): + appId: Optional[str] = '' + appSecret: Optional[str] = '' + messageId: Optional[str] = '' + + +class WechatType(Enum): + TEXT = 'text' + Image = 'image' + Poll = 'poll' + + +class WechatQuery(BaseModel): + type: WechatType + content: Optional[str] = '' + + +class WechatRequest(BaseModel): + query_id: Optional[str] = '' + groupname: Optional[str] = '' + username: Optional[str] = '' + query: Optional[WechatQuery] = {} + + +class WechatResponse(RootModel): + root: Optional[object] = [] + + +class WechatPollItem(BaseModel): + req: WechatRequest + rsp: ChatResponse diff --git a/repodir/huixiangdou/web/model/huixiangdou.py b/repodir/huixiangdou/web/model/huixiangdou.py new file mode 100644 index 00000000..b6dbba21 --- /dev/null +++ b/repodir/huixiangdou/web/model/huixiangdou.py @@ -0,0 +1,65 @@ +from enum import Enum +from typing import List, Optional + +from pydantic import BaseModel + +from web.model.qalib import FilesState + + +class HxdToken(BaseModel): + exp: int + iat: float + jti: str + qa_name: str + + +class HxdTaskChatHistory(BaseModel): + sender: int + content: str + + +class HxdTaskPayload(BaseModel): + name: Optional[str] = None + feature_store_id: Optional[str] = None + file_list: Optional[List[str]] = [] + file_abs_base: Optional[str] = None + positive: Optional[List[str]] = [] + negative: Optional[List[str]] = [] + content: Optional[str] = None + images: Optional[List[str]] = [] + history: Optional[List[HxdTaskChatHistory]] = [] + web_search_token: Optional[str] = None + query_id: Optional[str] = '' + + +class HxdTaskType(Enum): + ADD_DOC = 'add_doc' + UPDATE_PIPELINE = 'update_pipeline' + UPDATE_SAMPLE = 'update_sample' + CHAT = 'chat' + + +class HxdTask(BaseModel): + type: HxdTaskType + payload: HxdTaskPayload + + +class HxdTaskResponse(BaseModel): + feature_store_id: Optional[str] = None + code: Optional[int] = None + status: Optional[str] = None + type: Optional[str] = None + files_state: Optional[List[FilesState]] = None + + +class ChatResponse(BaseModel): + code: Optional[int] = -1 + state: Optional[str] = '' + text: Optional[str] = '' + references: Optional[List[str]] = [] + + +class HxdChatResponse(BaseModel): + feature_store_id: str + query_id: str + response: ChatResponse diff --git a/repodir/huixiangdou/web/model/integrate.py b/repodir/huixiangdou/web/model/integrate.py new file mode 100644 index 00000000..98e6bbfd --- /dev/null +++ b/repodir/huixiangdou/web/model/integrate.py @@ -0,0 +1,13 @@ +from typing import Optional + +from pydantic import BaseModel + + +class IntegrateLarkBody(BaseModel): + appId: str + appSecret: str + + +class IntegrateWebSearchBody(BaseModel): + webSearchToken: str + vendor: Optional[str] = '' diff --git a/repodir/huixiangdou/web/model/qalib.py b/repodir/huixiangdou/web/model/qalib.py new file mode 100644 index 00000000..5c01176e --- /dev/null +++ b/repodir/huixiangdou/web/model/qalib.py @@ -0,0 +1,74 @@ +from typing import List, Optional + +from pydantic import BaseModel + + +class Lark(BaseModel): + appId: Optional[str] = '' + appSecret: Optional[str] = '' + encryptKey: str + verificationToken: str + eventUrl: str + + +class Wechat(BaseModel): + onMessageUrl: str + + +class WebSearch(BaseModel): + token: str + + +class FilesState(BaseModel): + file: str + status: bool + desc: str + + +class QalibInfo(BaseModel): + featureStoreId: Optional[str] = None + name: Optional[str] = None + docs: Optional[List[str]] = [] + docBase: Optional[str] = None + status: Optional[int] = None + status_desc: Optional[str] = None + suffix: Optional[str] = None + lark: Optional[Lark] = None + wechat: Optional[Wechat] = None + webSearch: Optional[WebSearch] = None + filesState: Optional[List[FilesState]] = None + + +class QalibPositiveNegative(BaseModel): + positives: Optional[List] = None + negatives: Optional[List] = None + + +class QalibDeleteDoc(BaseModel): + filenames: List[str] + + +class QalibSample(QalibPositiveNegative): + name: str + featureStoreId: str + confirmed: Optional[bool] = False + + +class Pipeline(BaseModel): + webSearchToken: str + featureStoreId: str + confirmed: bool + success: bool + code: int + status: str + + +class AddDocError(BaseModel): + fileName: Optional[str] + reason: Optional[str] + + +class AddDocsRes(BaseModel): + docBase: Optional[str] = '' + docs: Optional[List[str]] = [] + errors: Optional[List[AddDocError]] = [] diff --git a/repodir/huixiangdou/web/model/statistic.py b/repodir/huixiangdou/web/model/statistic.py new file mode 100644 index 00000000..7611db74 --- /dev/null +++ b/repodir/huixiangdou/web/model/statistic.py @@ -0,0 +1,12 @@ +from typing import Optional + +from pydantic import BaseModel + + +class StatisticTotal(BaseModel): + qalibTotal: Optional[int] = None + lastMonthUsed: Optional[int] = None + wechatTotal: Optional[int] = None + feishuTotal: Optional[int] = None + servedTotal: Optional[int] = None + realServedTotal: Optional[int] = None diff --git a/repodir/huixiangdou/web/mq/__init__.py b/repodir/huixiangdou/web/mq/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/repodir/huixiangdou/web/mq/hxd_task.py b/repodir/huixiangdou/web/mq/hxd_task.py new file mode 100644 index 00000000..95e3777b --- /dev/null +++ b/repodir/huixiangdou/web/mq/hxd_task.py @@ -0,0 +1,34 @@ +import web.constant.biz_constant as biz_const +from web.model.huixiangdou import HxdTask, HxdTaskType +from web.orm.redis import r +from web.service.cache import ChatCache +from web.util.log import log + +logger = log(__name__) + + +class HuixiangDouTask: + + def __init__(self): + pass + + def updateTask(self, task: HxdTask) -> bool: + """update task into redis. + + :param task: HxdTask + :return: bool: True or False + """ + if not task: + logger.error("HuixiangDou's task is empty, update task aborted.") + return False + + ChatCache.mark_monthly_active(task.payload.feature_store_id) + if task.type == HxdTaskType.CHAT: + ChatCache.add_inference_number() + + try: + r.rpush(biz_const.RDS_KEY_HXD_TASK, task.model_dump_json()) + except Exception as e: + logger.error(f'{e}') + return False + return True diff --git a/repodir/huixiangdou/web/orm/__init__.py b/repodir/huixiangdou/web/orm/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/repodir/huixiangdou/web/orm/redis.py b/repodir/huixiangdou/web/orm/redis.py new file mode 100644 index 00000000..f90a08b7 --- /dev/null +++ b/repodir/huixiangdou/web/orm/redis.py @@ -0,0 +1,23 @@ +import redis + +from web.config.env import HuixiangDouEnv +from web.util.log import log + +logger = log(__name__) + +logger.info('connecting to redis') +host = HuixiangDouEnv.get_redis_host() +password = HuixiangDouEnv.get_redis_password() +port = HuixiangDouEnv.get_redis_port() +db = HuixiangDouEnv.get_redis_db() +pool = redis.ConnectionPool(host=host, port=port, db=db, password=password) +r = redis.Redis(connection_pool=pool) +try: + r_res = r.ping() + if not r_res: + logger.error(f'Failed connected to redis, exit with code 1') + exit(1) +except Exception as e: + logger.error(f'Failed connected to redis, error={e}') + exit(2) +logger.info('connected to redis') diff --git a/repodir/huixiangdou/web/proxy/config-template.ini b/repodir/huixiangdou/web/proxy/config-template.ini new file mode 100644 index 00000000..a939155b --- /dev/null +++ b/repodir/huixiangdou/web/proxy/config-template.ini @@ -0,0 +1,94 @@ +[feature_store] +reject_throttle = -1.0 +# text2vec model path, support local relative path and huggingface model format. +# also support local path, model_path = "/path/to/your/text2vec-model" +embedding_model_path = "/root/huixiangdou-res/bce-embedding-base_v1" +reranker_model_path = "/root/huixiangdou-res/bce-reranker-base_v1" +work_dir = "workdir" + +[web_search] +# check https://serper.dev/api-key to get a free API key +x_api_key = "" +domain_partial_order = ["openai.com", "pytorch.org", "readthedocs.io", "nvidia.com", "stackoverflow.com", "juejin.cn", "zhuanlan.zhihu.com", "www.cnblogs.com"] +save_dir = "logs/web_search_result" + +[llm] +enable_local = 0 +enable_remote = 1 +# hybrid llm service address +client_url = "http://127.0.0.1:8888/inference" + +[llm.server] +# local LLM configuration +# support "internlm/internlm2-chat-7b" and "qwen/qwen-7b-chat-int8" +# support local path, for example +# local_llm_path = "/path/to/your/internlm2" +# also support local_llm_path = "internlm/internlm2-chat-20b" + +local_llm_path = "internlm/internlm2-chat-7b" +local_llm_max_text_length = 3000 +local_llm_bind_port = 8888 + +# remote LLM service configuration +# support "gpt", "kimi", "deepseek" and "puyu" +remote_type = "kimi" +remote_api_key = "${YOUR-API-KEY}" +# max text length for remote LLM. +# use 128000 for kimi, 192000 for gpt, 16000 for deepseek +remote_llm_max_text_length = 64000 +# openai model type. +# use "moonshot-v1-128k" for kimi, "gpt-4" for gpt, "deepseek-chat" for deepseek, "ChatPJLM-latest" for puyu +remote_llm_model = "moonshot-v1-128k" + +[worker] +# enable search enhancement or not +enable_sg_search = 0 +save_path = "logs/work.txt" + +[worker.time] +start = "00:00:00" +end = "23:59:59" +has_weekday = 1 + +[sg_search] +# download `src` from https://github.com/sourcegraph/src-cli#installation +binary_src_path = "/usr/local/bin/src" +src_access_token = "${YOUR-SRC-ACCESS-TOKEN}" + +# add your repo here, we just take opencompass and lmdeploy as example +[sg_search.opencompass] +github_repo_id = "open-compass/opencompass" +introduction = "用于评测大型语言模型(LLM). 它提供了完整的开源可复现的评测框架,支持大语言模型、多模态模型的一站式评测,基于分布式技术,对大参数量模型亦能实现高效评测。评测方向汇总为知识、语言、理解、推理、考试五大能力维度,整合集纳了超过70个评测数据集,合计提供了超过40万个模型评测问题,并提供长文本、安全、代码3类大模型特色技术能力评测。" +# introduction = "For evaluating Large Language Models (LLMs). It provides a fully open-source, reproducible evaluation framework, supporting one-stop evaluation for large language models and multimodal models. Based on distributed technology, it can efficiently evaluate models with a large number of parameters. The evaluation directions are summarized in five capability dimensions: knowledge, language, understanding, reasoning, and examination. It integrates and collects more than 70 evaluation datasets, providing in total over 400,000 model evaluation questions. Additionally, it offers evaluations for three types of capabilities specific to large models: long text, security, and coding." + +[sg_search.lmdeploy] +github_repo_id = "internlm/lmdeploy" +introduction = "lmdeploy 是一个用于压缩、部署和服务 LLM(Large Language Model)的工具包。是一个服务端场景下,transformer 结构 LLM 部署工具,支持 GPU 服务端部署,速度有保障,支持 Tensor Parallel,多并发优化,功能全面,包括模型转换、缓存历史会话的 cache feature 等. 它还提供了 WebUI、命令行和 gRPC 客户端接入。" +# introduction = "lmdeploy is a toolkit for compressing, deploying, and servicing Large Language Models (LLMs). It is a deployment tool for transformer-structured LLMs in server-side scenarios, supporting GPU server-side deployment, ensuring speed, and supporting Tensor Parallel along with optimizations for multiple concurrent processes. It offers comprehensive features including model conversion, cache features for caching historical sessions and more. Additionally, it provides access via WebUI, command line, and gRPC clients." + +[frontend] +# chat group assistant type, support "lark", "lark_group", "wechat_personal" and "none" +# for "lark", open https://open.feishu.cn/document/client-docs/bot-v3/add-custom-bot to add bot, **only send, cannot receive** +# for "lark_group", open https://open.feishu.cn/document/home/introduction-to-custom-app-development/self-built-application-development-process to create one +# for "wechat_personal", read ./docs/add_wechat_group_zh.md to setup gateway +type = "none" + +# for "lark", it is chat group webhook url, send reply to group, for example "https://open.feishu.cn/open-apis/bot/v2/hook/xxxxxxxxxxxxxxx" +# for "lark_group", it is the url to fetch chat group message, for example "http://101.133.161.20:6666/fetch", `101.133.161.20` is your own public IPv4 addr +# for "wechat_personal", it is useless +webhook_url = "https://open.feishu.cn/open-apis/bot/v2/hook/xxxxxxxxxxxxxxx" + +# when a new group chat message is received, should it be processed immediately or wait for 18 seconds in case the user hasn't finished speaking? +# support "immediate" +message_process_policy = "immediate" + +[frontend.lark_group] +# "lark_group" configuration examples, use your own app_id and secret !!! +app_id = "cli_a53a34dcb778500e" +app_secret = "2ajhg1ixSvlNm1bJkH4tJhPfTCsGGHT1" +encrypt_key = "abc" +verification_token = "def" + +[frontend.wechat_personal] +# "wechat_personal" listen port +bind_port = 9527 diff --git a/repodir/huixiangdou/web/proxy/logs/work.txt b/repodir/huixiangdou/web/proxy/logs/work.txt new file mode 100644 index 00000000..e69de29b diff --git a/repodir/huixiangdou/web/proxy/main.py b/repodir/huixiangdou/web/proxy/main.py new file mode 100644 index 00000000..a254cf3b --- /dev/null +++ b/repodir/huixiangdou/web/proxy/main.py @@ -0,0 +1,395 @@ +# Listen HuiXiangDou:Task queue +import json +import os +import shutil +import time +import types +from datetime import datetime, timedelta +# implement time lru cache +from functools import lru_cache, partial, wraps +from multiprocessing import Pool, Process, Value + +import pytoml +import redis +from loguru import logger + +from huixiangdou.primitive import FileName, FileOperation +from huixiangdou.service import (CacheRetriever, ErrorCode, FeatureStore, + Queue, Retriever, TaskCode, + feature_store_base_dir, parse_json_str, + redis_host, redis_passwd, redis_port) + +from .web_worker import OpenXLabWorker + + +def callback_task_state(feature_store_id: str, + code: int, + _type: str, + state: str, + files_state: list = []): + resp = Queue(name='TaskResponse') + target = { + 'feature_store_id': feature_store_id, + 'code': code, + 'type': _type, + 'state': state, + 'status': state, + 'files_state': files_state + } + logger.debug(target) + resp.put(json.dumps( + target, + ensure_ascii=False, + )) + + +def callback_chat_state(feature_store_id: str, query_id: str, code: int, + state: str, text: str, ref: list): + que = Queue(name='ChatResponse') + + target = { + 'feature_store_id': feature_store_id, + 'query_id': query_id, + 'response': { + 'code': code, + 'state': state, + 'text': text, + 'references': ref + } + } + logger.debug(target) + que.put(json.dumps(target, ensure_ascii=False)) + + +def format_history(history): + """format [{sender, content}] to [[user1, bot1],[user2,bot2]..] style.""" + ret = [] + last_id = -1 + + user = '' + concat_text = '' + for item in history: + if last_id == -1: + last_id = item.sender + concat_text = item.content + continue + if last_id == item.sender: + # 和上一个相同, concat + concat_text += '\n' + concat_text += item.content + continue + + # 和上一个不同,把目前所有的 concat_text 加到 user 或 bot 部分 + if last_id == 0: + # user message + user = concat_text + elif last_id == 1: + # bot reply + ret.append([user, concat_text]) + user = '' + + # 把当前的 assign 给 last + last_id = item.sender + concat_text = item.content + + # 最后一个元素,处理一下 + if last_id == 0: + # user message + ret.append([concat_text, '']) + logger.warning('chat history should not ends with user') + elif last_id == 1: + # bot reply + ret.append([user, concat_text]) + + return ret + + +def chat_with_featue_store(cache: CacheRetriever, + payload: types.SimpleNamespace): + # "payload": { + # "feature_store_id": "STRING", + # "query_id": "STRING", + # "content": "STRING", + # "images": ["STRING"], + # "history": [{ + # "sender": Integer, + # "content": "STRING" + # }] + # } + + fs_id = payload.feature_store_id + query_id = payload.query_id + + chat_state = partial(callback_chat_state, + feature_store_id=fs_id, + query_id=query_id) + + BASE = feature_store_base_dir() + workdir = os.path.join(BASE, fs_id, 'workdir') + configpath = os.path.join(BASE, fs_id, 'config.ini') + db_dense = os.path.join(workdir, 'db_dense') + + if not os.path.exists(workdir) or not os.path.exists( + configpath) or not os.path.exists(db_dense): + chat_state(code=ErrorCode.PARAMETER_ERROR.value, + text='', + state='知识库未建立或建立异常,此时不能 chat。', + ref=[]) + return + retriever = cache.get(fs_id=fs_id, + config_path=configpath, + work_dir=workdir) + + worker = OpenXLabWorker(work_dir=workdir, config_path=configpath) + + history = format_history(payload.history) + query_log = '{} {}\n'.format(fs_id, payload.content) + with open('query.log', 'a') as f: + f.write(query_log) + error, response, references = worker.generate(query=payload.content, + history=history, + retriever=retriever, + groupname='') + if error != ErrorCode.SUCCESS: + chat_state(code=error.value, + state=error.describe(), + text=response, + ref=references) + return + chat_state(code=ErrorCode.SUCCESS.value, + state=ErrorCode.SUCCESS.describe(), + text=response, + ref=references) + + +def build_feature_store(cache: CacheRetriever, payload: types.SimpleNamespace): + # "payload": { + # "name": "STRING", + # "feature_store_id": "STRING", + # "file_abs_base": "STRING", + # "path_list": ["STRING"] + # } + abs_base = payload.file_abs_base + fs_id = payload.feature_store_id + path_list = [] + files = [] + + file_opr = FileOperation() + for filename in payload.file_list: + abs_path = os.path.join(abs_base, filename) + _type = file_opr.get_type(abs_path) + files.append(FileName(root=abs_base, filename=filename, _type=_type)) + + BASE = feature_store_base_dir() + # build dir and config.ini if not exist + workdir = os.path.join(BASE, fs_id, 'workdir') + if not os.path.exists(workdir): + os.makedirs(workdir) + + configpath = os.path.join(BASE, fs_id, 'config.ini') + if not os.path.exists(configpath): + template_file = 'config.ini' + if not os.path.exists(template_file): + raise Exception(f'{template_file} not exist') + shutil.copy(template_file, configpath) + + with open(os.path.join(BASE, fs_id, 'desc'), 'w', encoding='utf8') as f: + f.write(payload.name) + + fs = FeatureStore(config_path=configpath, embedder=cache.embedder) + task_state = partial(callback_task_state, + feature_store_id=fs_id, + _type=TaskCode.FS_ADD_DOC.value) + + # try: + fs.initialize(files=files, work_dir=workdir) + files_state = [] + + success_cnt = 0 + fail_cnt = 0 + skip_cnt = 0 + for file in files: + files_state.append({ + 'file': str(file.basename), + 'status': bool(file.state), + 'desc': str(file.reason) + }) + + if file.state: + success_cnt += 1 + elif file.reason == 'skip': + skip_cnt += 1 + else: + fail_cnt += 1 + + if success_cnt == len(files): + task_state(code=ErrorCode.SUCCESS.value, + state=ErrorCode.SUCCESS.describe(), + files_state=files_state) + + elif success_cnt == 0: + task_state(code=ErrorCode.FAILED.value, + state='无文件被处理', + files_state=files_state) + + else: + state = f'完成{success_cnt}个文件,跳过{skip_cnt}个,{fail_cnt}个处理异常。请确认文件格式。' + task_state(code=ErrorCode.SUCCESS.value, + state=state, + files_state=files_state) + + # except Exception as e: + # logger.error(str(e)) + # task_state(code=ErrorCode.FAILED.value, state=str(e)) + + +def update_sample(cache: CacheRetriever, payload: types.SimpleNamespace): + # "payload": { + # "name": "STRING", + # "feature_store_id": "STRING", + # "positve_path": "STRING", + # "negative_path": "STRING", + # } + + positive = payload.positive + negative = payload.negative + fs_id = payload.feature_store_id + + # check + task_state = partial(callback_task_state, + feature_store_id=fs_id, + _type=TaskCode.FS_UPDATE_SAMPLE.value) + + if len(positive) < 1 or len(negative) < 1: + task_state(code=ErrorCode.BAD_PARAMETER.value, + state='正例为空。请根据真实用户问题,填写正例;同时填写几句场景无关闲聊作负例') + return + + for idx in range(len(positive)): + if len(positive[idx]) < 1: + positive[idx] += '.' + + for idx in range(len(negative)): + if len(negative[idx]) < 1: + negative[idx] += '.' + + BASE = feature_store_base_dir() + fs_id = payload.feature_store_id + workdir = os.path.join(BASE, fs_id, 'workdir') + configpath = os.path.join(BASE, fs_id, 'config.ini') + + db_dense = os.path.join(workdir, 'db_dense') + + if not os.path.exists(workdir) or not os.path.exists( + configpath) or not os.path.exists(db_dense): + task_state(code=ErrorCode.INTERNAL_ERROR.value, + state='知识库未建立或中途异常,已自动反馈研发。请重新建立知识库。') + return + + # try: + + retriever = cache.get(fs_id=fs_id, + config_path=configpath, + work_dir=workdir) + retriever.update_throttle(config_path=configpath, + good_questions=positive, + bad_questions=negative) + del retriever + task_state(code=ErrorCode.SUCCESS.value, + state=ErrorCode.SUCCESS.describe()) + + # except Exception as e: + # logger.error(str(e)) + # task_state(code=ErrorCode.FAILED.value, state=str(e)) + + +def update_pipeline(payload: types.SimpleNamespace): + # "payload": { + # "name": "STRING", + # "feature_store_id": "STRING", + # "web_search_token": "" + # } + fs_id = payload.feature_store_id + token = payload.web_search_token + + # check + task_state = partial(callback_task_state, + feature_store_id=fs_id, + _type=TaskCode.FS_UPDATE_PIPELINE.value) + + BASE = feature_store_base_dir() + fs_id = payload.feature_store_id + workdir = os.path.join(BASE, fs_id, 'workdir') + configpath = os.path.join(BASE, fs_id, 'config.ini') + + if not os.path.exists(workdir) or not os.path.exists(configpath): + task_state(code=ErrorCode.INTERNAL_ERROR.value, + state='知识库未建立或中途异常,已自动反馈研发。请重新建立知识库。') + return + + with open(configpath, encoding='utf8') as f: + config = pytoml.load(f) + config['web_search']['x_api_key'] = token + with open(configpath, 'w', encoding='utf8') as f: + pytoml.dump(config, f) + task_state(code=ErrorCode.SUCCESS.value, + state=ErrorCode.SUCCESS.describe()) + + +def process(): + que = Queue(name='Task') + fs_cache = CacheRetriever('config.ini') + + logger.info('start wait task queue..') + while True: + # try: + msg_pop = que.get(timeout=16) + if msg_pop is None: + continue + msg, error = parse_json_str(msg_pop) + logger.info(msg) + if error is not None: + raise error + + logger.debug(f'process {msg}') + if msg.type == TaskCode.FS_ADD_DOC.value: + fs_cache.pop(msg.payload.feature_store_id) + build_feature_store(fs_cache, msg.payload) + elif msg.type == TaskCode.FS_UPDATE_SAMPLE.value: + fs_cache.pop(msg.payload.feature_store_id) + update_sample(fs_cache, msg.payload) + elif msg.type == TaskCode.FS_UPDATE_PIPELINE.value: + update_pipeline(msg.payload) + elif msg.type == TaskCode.CHAT.value: + chat_with_featue_store(fs_cache, msg.payload) + else: + logger.warning(f'unknown type {msg}') + + +# except Exception as e: +# logger.error(str(e)) +# time.sleep(1) +# que = Queue(name='Task') + +if __name__ == '__main__': + # single process + process() + + # multiple process + # CNT = 16 + # pool = Pool(processes=CNT) + + # ps = [] + + # for i in range(CNT): + # logger.info('prepare process {}'.format(i)) + + # p = Process(target=process, args=()) + # p.daemon = False + # p.start() + # ps.append(p) + # time.sleep(1) + # logger.info('started process {}'.format(i)) + + # for p in ps: + # p.join() diff --git a/repodir/huixiangdou/web/proxy/test.py b/repodir/huixiangdou/web/proxy/test.py new file mode 100644 index 00000000..6e58e8b8 --- /dev/null +++ b/repodir/huixiangdou/web/proxy/test.py @@ -0,0 +1,126 @@ +import json +from pathlib import Path + +from config import feature_store_base_dir +from helper import ErrorCode, Queue, TaskCode, parse_json_str + +task_in = Queue(name='Task') +task_out = Queue(name='TaskResponse') +chat_out = Queue(name='ChatResponse') + + +def test_create_fs(): + target = { + 'type': TaskCode.FS_ADD_DOC.value, + 'payload': { + 'name': 'ailab 行政说明', + 'feature_store_id': '9527', + 'file_abs_base': '/root/huixiangdou-res/test-data', + 'file_list': ['huixiangdou.md', ''] + } + } + base_dir = target['payload']['file_abs_base'] + + file_list = [str(x) for x in list(Path(base_dir).glob('*'))] + target['payload']['file_list'] = file_list + print(target) + task_in.put(json.dumps(target, ensure_ascii=False)) + + out = task_out.get() + print(out) + + +def test_update_sample(): + # "payload": { + # "name": "STRING", + # "feature_store_id": "STRING", + # "positive": "[STRING]", + # "negative": "[STRING]", + # } + + target = { + 'type': TaskCode.FS_UPDATE_SAMPLE.value, + 'payload': { + 'name': 'ailab 行政说明', + 'feature_store_id': '9527', + 'positive': + ['请问如何申请公寓?我是实习生但名下有房还行么?', '几十万的科研仪器不小心打碎了,我得自己付钱赔偿么'], + 'negative': ['今天中午吃什么', 'ncnn 的作者是谁'] + } + } + print(target) + task_in.put(json.dumps(target, ensure_ascii=False)) + + out = task_out.get() + print(out) + + +def test_update_pipeline(): + # "payload": { + # "name": "STRING", + # "feature_store_id": "STRING", + # "web_search_token": "" + # } + + target = { + 'type': TaskCode.FS_UPDATE_PIPELINE.value, + 'payload': { + 'name': 'ailab 行政说明', + 'feature_store_id': '9527', + 'web_search_token': '' + } + } + print(target) + task_in.put(json.dumps(target, ensure_ascii=False)) + + out = task_out.get() + print(out) + + +def test_chat(): + # "payload": { + # "feature_store_id": "STRING", + # "query_id": "STRING", + # "content": "STRING", + # "images": ["STRING"], + # "history": [{ + # "sender": Integer, + # "content": "STRING" + # }] + # } + + # queries = ['请问买下单位公寓,需要多少钱?'] + queries = ['请问公寓退房需要注意哪些事情?'] + + for query in queries: + target = { + 'type': TaskCode.CHAT.value, + 'payload': { + 'query_id': + 'ae86', + 'feature_store_id': + '9527', + 'content': + query, + 'images': [], + 'history': [{ + 'sender': 0, + 'content': '你好' + }, { + 'sender': 0, + 'content': '你是谁' + }, { + 'sender': 1, + 'content': '我是行政助手茴香豆' + }] + } + } + task_in.put(json.dumps(target, ensure_ascii=False)) + print(chat_out.get()) + + +if __name__ == '__main__': + test_create_fs() + test_update_sample() + test_update_pipeline() + test_chat() diff --git a/repodir/huixiangdou/web/proxy/traslate.txt b/repodir/huixiangdou/web/proxy/traslate.txt new file mode 100644 index 00000000..4672f0f4 --- /dev/null +++ b/repodir/huixiangdou/web/proxy/traslate.txt @@ -0,0 +1,76 @@ + +beanDetail: '{ + accessFeishu: ''Zero-coding integration with Lark''', + accessWeChat: ''Zero-coding integration with Personal WeChat''', + viewDetail: ''View Tutorial''', + switchSearch: ''Web Search Switch''', + examples: ''Examples''', + docs: ''View or Upload Documents''', + viewAndEdit: ''View and Edit''', + addExamples: ''Add Examples''', + addDocs: ''Add Documents''', + chatTest: ''Chat Test''', + openChatTest: ''Start Testing''', + beanName: ''Knowledge Base Name''', + createFailed: ''Failed to Add Document''', + created: ''Created'', No Documents Added Yet''', + createSuccess: ''Creation Complete''', + upload: ''Select Files'', Multiple Selection Allowed''', + logout: ''Log Out''', + references: ''References: '''', + timeout: ''Request Timeout'', Please Try Again Later''', + inputPlaceholder: ''Support Text'', Emoji and Image Paste''', + send: ''Send''', + setPositive: ''Set Positive Example''', + positiveDesc: ''Positive examples are real-life questions from the asker that require a response. Each sentence should be on a new line'', for example:\nHello'', I'm an intern'', do you have dormitories here?\nWhat are the advantages of your product compared to competitors?''', + setNegative: ''Set Negative Example''', + negativeDesc': ''Negative examples are idle chatter in real-life scenarios that should not be responded to.\nEach sentence should be on a new line'', for example:\nShall we have Japanese food for lunch today?\nQuick'', there's a shooting star in the sky'', run!''', + save: ''Save''', + saving: ''Saving...''', + saveSuccess: ''Save Successful''', + edit: ''Click to Edit''', + integrateLark: ''Integrate with Lark''', + larkGuidance: ''After completing the following configuration'', you can chat with Huixiang Dou in Lark group chats''', + required: ''Required''', + isEmpty: ''Not Empty''', + webSearch: ''Web Search''', + register: ''Register''', + webSearchClosed: ''Web Search Disable''', + webSearchDesc: ''Enable web search to allow the knowledge assistant to provide answers based on both web results and local documents''', + webSearchDesc1: ''Get a limited free token''', + webSearchDesc2: ''Enter the token'', a new token will overwrite the old one''', + webSearchDesc3: ''If you save an empty token'', web search will be turned off''', + enterToken: ''Please Enter Token''', +} + + +{ + home: '{ + slogan: ''Knowledge Assistant', Zero-coding with Lark and WeChat.'', + beanName: ''Knowledge base name. Auto create if not exists'', + validateMsg: ''At least 8 characters required'', + createBean: ''Create Knowledge Base'', + beanPwd: ''Knowledge Base Password'', + create: ''Create'', + cancel: ''Cancel'', + go: ''Go'', + bean: ''Knowledge Base'', + activeBean: ''Active Knowledge Base'', + WeChat: ''WeChat'', + feishu: ''Lark'',` + users: ''Serviced Users'', + uniqueUsers: ''Unique Serviced Users'', + pwdError: ''Password Error'', + } +} + + components: '{ + notificationContent: '🎉 HuixiangDou is open source now. If this helps you, please give it a star! 🌟 🥺', + hide4ever: 'Hide forever', + goStar: 'Star', + fileSize: 'File size must not exceed 1GB', + pendingFiles: 'Pending uploads', + confirmUpload: 'Confirm upload', + uploading: 'Uploading', + uploadedFiles: 'Uploaded documents', + } diff --git a/repodir/huixiangdou/web/proxy/web_worker.py b/repodir/huixiangdou/web/proxy/web_worker.py new file mode 100644 index 00000000..49c76bbe --- /dev/null +++ b/repodir/huixiangdou/web/proxy/web_worker.py @@ -0,0 +1,346 @@ +# Copyright (c) OpenMMLab. All rights reserved. +"""Pipeline.""" +import argparse +import datetime +import json +import random +import re +import time + +import pytoml +import requests +from loguru import logger + +from huixiangdou.service import (ChatClient, ErrorCode, FeatureStore, + QueryTracker, WebSearch) + + +def openxlab_security(query: str, retry=1): + life = 0 + while life < retry: + try: + headers = {'Content-Type': 'application/json'} + data = { + 'bizId': str('antiseed' + str(time.time())), + 'contents': [query], + 'scopes': [], + 'vendor': 1, + } + + resp = requests.post( + 'https://openxlab.org.cn/gw/checkit/api/v1/audit/text', + data=json.dumps(data), + headers=headers) + logger.debug((resp, resp.content)) + + json_obj = json.loads(resp.content) + items = json_obj['data'] + + block = False + for item in items: + label = item['label'] + if label is not None and label in ['porn', 'politics']: + suggestion = item['suggestion'] + if suggestion == 'block': + logger.debug(items) + block = True + break + + if block: + return False + return True + except Exception as e: + logger.debug(e) + life += 1 + + randval = random.randint(1, int(pow(2, life))) + time.sleep(randval) + return False + + +class OpenXLabWorker: + """The OpenXLab Worker class orchestrates the logic of handling user queries, + generating responses and managing several aspects of a chat assistant. It + enables feature storage, language model client setup, time scheduling and + much more. + + Attributes: + llm: A ChatClient instance that communicates with the language model. + fs: An instance of FeatureStore for loading and querying features. + config_path: A string indicating the path of the configuration file. + config: A dictionary holding the configuration settings. + language: A string indicating the language of the chat, default is 'zh' (Chinese). # noqa E501 + context_max_length: An integer representing the maximum length of the context used by the language model. # noqa E501 + + Several template strings for various prompts are also defined. + """ + + def __init__(self, work_dir: str, config_path: str, language: str = 'zh'): + """Constructs all the necessary attributes for the worker object. + + Args: + work_dir (str): The working directory where feature files are located. + config_path (str): The location of the configuration file. + language (str, optional): Specifies the language to be used. Defaults to 'zh' (Chinese). # noqa E501 + """ + self.llm = ChatClient(config_path=config_path) + self.config_path = config_path + self.config = None + self.language = language + with open(config_path, encoding='utf8') as f: + self.config = pytoml.load(f) + if self.config is None: + raise Exception('worker config can not be None') + + self.context_max_length = -1 + llm_config = self.config['llm'] + self.context_max_length = llm_config['server'][ + 'local_llm_max_text_length'] + + if llm_config['enable_remote']: + self.context_max_length = llm_config['server'][ + 'remote_llm_max_text_length'] + + # Switch languages according to the scenario. + if self.language == 'zh': + self.TOPIC_TEMPLATE = '告诉我这句话的主题,直接说主题不要解释:“{}”' + self.SCORING_QUESTION_TEMPLTE = '“{}”\n请仔细阅读以上内容,判断句子是否是个疑问句,结果用 0~10 表示。直接提供得分不要解释。\n判断标准:有主语谓语宾语并且是疑问句得 10 分;缺少主谓宾扣分;陈述句直接得 0 分;不是疑问句直接得 0 分。直接提供得分不要解释。' # noqa E501 + self.SCORING_RELAVANCE_TEMPLATE = '问题:“{}”\n材料:“{}”\n请仔细阅读以上内容,判断问题和材料的关联度,用0~10表示。判断标准:非常相关得 10 分;完全没关联得 0 分。直接提供得分不要解释。\n' # noqa E501 + self.KEYWORDS_TEMPLATE = '谷歌搜索是一个通用搜索引擎,可用于访问互联网、查询百科知识、了解时事新闻等。搜索参数类型 string, 内容是短语或关键字,以空格分隔。\n你现在是{}交流群里的技术助手,用户问“{}”,你打算通过谷歌搜索查询相关资料,请提供用于搜索的关键字或短语,不要解释直接给出关键字或短语。' # noqa E501 + self.SECURITY_TEMAPLTE = '判断以下句子是否涉及政治、辱骂、色情、恐暴、宗教、网络暴力、种族歧视等违禁内容,结果用 0~10 表示,不要解释直接给出得分。判断标准:涉其中任一问题直接得 10 分;完全不涉及得 0 分。直接给得分不要解释:“{}”' # noqa E501 + self.PERPLESITY_TEMPLATE = '“question:{} answer:{}”\n阅读以上对话,answer 是否在表达自己不知道,回答越全面得分越少,用0~10表示,不要解释直接给出得分。\n判断标准:准确回答问题得 0 分;答案详尽得 1 分;知道部分答案但有不确定信息得 8 分;知道小部分答案但推荐求助其他人得 9 分;不知道任何答案直接推荐求助别人得 10 分。直接打分不要解释。' # noqa E501 + self.SUMMARIZE_TEMPLATE = '{} \n 仔细阅读以上内容,总结得简短有力点' # noqa E501 + # self.GENERATE_TEMPLATE = '材料:“{}”\n 问题:“{}” \n 请仔细阅读参考材料回答问题,材料可能和问题无关。如果材料和问题无关,尝试用你自己的理解来回答问题。如果无法确定答案,直接回答不知道。' # noqa E501 + self.GENERATE_TEMPLATE = '材料:“{}”\n 问题:“{}” \n 请仔细阅读参考材料回答问题。' # noqa E501 + else: + self.TOPIC_TEMPLATE = 'Tell me the theme of this sentence, just state the theme without explanation: "{}"' # noqa E501 + self.SCORING_QUESTION_TEMPLTE = '"{}"\nPlease read the content above carefully and judge whether the sentence is a thematic question. Rate it on a scale of 0-10. Only provide the score, no explanation.\nThe criteria are as follows: a sentence gets 10 points if it has a subject, predicate, object and is a question; points are deducted for missing subject, predicate or object; declarative sentences get 0 points; sentences that are not questions also get 0 points. Just give the score, no explanation.' # noqa E501 + self.SCORING_RELAVANCE_TEMPLATE = 'Question: "{}", Background Information: "{}"\nPlease read the content above carefully and assess the relevance between the question and the material on a scale of 0-10. The scoring standard is as follows: extremely relevant gets 10 points; completely irrelevant gets 0 points. Only provide the score, no explanation needed.' # noqa E501 + self.KEYWORDS_TEMPLATE = 'Google search is a general-purpose search engine that can be used to access the internet, look up encyclopedic knowledge, keep abreast of current affairs and more. Search parameters type: string, content consists of phrases or keywords separated by spaces.\nYou are now the assistant in the "{}" communication group. A user asked "{}", you plan to use Google search to find related information, please provide the keywords or phrases for the search, no explanation, just give the keywords or phrases.' # noqa E501 + self.SECURITY_TEMAPLTE = 'Evaluate whether the following sentence involves prohibited content such as politics, insult, pornography, terror, religion, cyber violence, racial discrimination, etc., rate it on a scale of 0-10, do not explain, just give the score. The scoring standard is as follows: any violation directly gets 10 points; completely unrelated gets 0 points. Give the score, no explanation: "{}"' # noqa E501 + self.PERPLESITY_TEMPLATE = 'Question: {} Answer: {}\nRead the dialogue above, does the answer express that they don\'t know? The more comprehensive the answer, the lower the score. Rate it on a scale of 0-10, no explanation, just give the score.\nThe scoring standard is as follows: an accurate answer to the question gets 0 points; a detailed answer gets 1 point; knowing some answers but having uncertain information gets 8 points; knowing a small part of the answer but recommends seeking help from others gets 9 points; not knowing any of the answers and directly recommending asking others for help gets 10 points. Just give the score, no explanation.' # noqa E501 + self.SUMMARIZE_TEMPLATE = '"{}" \n Read the content above carefully, summarize it in a short and powerful way.' # noqa E501 + self.GENERATE_TEMPLATE = 'Background Information: "{}"\n Question: "{}"\n Please read the reference material carefully and answer the question.' # noqa E501 + + def security_content(self, tracker, response: str): + # 安全检查,通过为 true + return True + # if len(response) < 1: + # return True + # if self.single_judge(self.SECURITY_TEMAPLTE.format(response), + # tracker=tracker, + # throttle=3, + # default=0): + # return False + + # if openxlab_security(response): + # return True + # return False + + def single_judge(self, prompt, tracker, throttle: int, default: int, + backend: str): + """Generates a score based on the prompt, and then compares it to + threshold. + + Args: + prompt (str): The prompt for the language model. + tracker (obj): An instance of QueryTracker logs the operations. + throttle (int): Threshold value to compare the score against. + default (int): Default score to be assigned in case of failure in score calculation. # noqa E501 + + Returns: + bool: True if the score surpasses the throttle, otherwise False. + """ + if prompt is None or len(prompt) == 0: + return False + + score = default + relation = self.llm.generate_response(prompt=prompt, backend=backend) + tracker.log('score' + prompt[0:20], [relation, throttle, default]) + filtered_relation = ''.join([c for c in relation if c.isdigit()]) + try: + score_str = re.sub(r'[^\d]', ' ', filtered_relation).strip() + score = int(score_str.split(' ')[0]) + except Exception as e: + logger.error(str(e)) + if score >= throttle: + return True + return False + + def generate(self, query, history, retriever, groupname): + """Processes user queries and generates appropriate responses. It + involves several steps including checking for valid questions, + extracting topics, querying the feature store, searching the web, and + generating responses from the language model. + + Args: + query (str): User's query. + history (list): Chat history. + groupname (str): The group name in which user asked the query. + + Returns: + ErrorCode: An error code indicating the status of response generation. # noqa E501 + str: Generated response to the user query. + """ + response = '' + reborn_code = ErrorCode.SUCCESS + tracker = QueryTracker(self.config['worker']['save_path']) + tracker.log('input', [query, history, groupname]) + + if not self.single_judge( + prompt=self.SCORING_QUESTION_TEMPLTE.format(query), + tracker=tracker, + throttle=6, + default=2, + backend='remote'): + # not a question, give LLM response + response = self.llm.generate_response(prompt=query, + history=history, + backend='remote') + return ErrorCode.NOT_A_QUESTION, response, [] + + topic = self.llm.generate_response(self.TOPIC_TEMPLATE.format(query), + backend='remote') + tracker.log('topic', topic) + + if len(topic) < 2: + return ErrorCode.NO_TOPIC, response, [] + + chunk, db_context, retrieve_ref = retriever.query( + topic, + context_max_length=self.context_max_length - + 2 * len(self.GENERATE_TEMPLATE)) + + if db_context is None or len(db_context) < 1: + tracker.log('topic feature store reject') + + chunk, db_context, retrieve_ref = retriever.query( + query, + context_max_length=self.context_max_length - + 2 * len(self.GENERATE_TEMPLATE)) + if db_context is None or len(db_context) < 1: + return ErrorCode.UNRELATED, response, retrieve_ref + + logger.info('fetch context length {}'.format(len(db_context))) + # if self.single_judge(self.SCORING_RELAVANCE_TEMPLATE.format( + # query, chunk), + # tracker=tracker, + # throttle=5, + # default=10, + # backend='remote'): + prompt, history = self.llm.build_prompt( + instruction=query, + context=db_context, + history_pair=history, + template=self.GENERATE_TEMPLATE) + response = self.llm.generate_response(prompt=prompt, + history=history, + backend='remote') + tracker.log('feature store doc', [chunk, response]) + if response is not None and len(response) < 1: + # llm error + return ErrorCode.INTERNAL_ERROR, 'LLM API 没给回复,见 https://github.com/InternLM/HuixiangDou/issues/214 ', retrieve_ref + + if response is not None and len(response) > 0: + prompt = self.PERPLESITY_TEMPLATE.format(query, response) + if not self.single_judge(prompt=prompt, + tracker=tracker, + throttle=9, + default=0, + backend='remote'): + # get answer, check security and return + if not self.security_content(tracker, response): + return ErrorCode.SECURITY, '检测到敏感内容,无法显示', retrieve_ref + return ErrorCode.SUCCESS, response, retrieve_ref + + # start web search + web = WebSearch(config_path=self.config_path) + if len(web.load_key()) < 1: + return ErrorCode.BAD_ANSWER, response, [] + + use_ref = [] + try: + web_context = '' + articles, error = web.get(query=topic, max_article=2) + if error is not None: + return ErrorCode.WEB_SEARCH_FAIL, response, [] + + tracker.log('search returned') + for article in articles: + if len(article) > 0 and len( + web_context) < self.context_max_length: + if len(article) > self.context_max_length: + article.cut( + 0, self.context_max_length - + 2 * len(self.SCORING_RELAVANCE_TEMPLATE)) + + if self.single_judge( + self.SCORING_RELAVANCE_TEMPLATE.format( + query, str(article)), + tracker=tracker, + throttle=5, + default=10, + backend='remote'): + web_context += '\n' + web_context += str(article) + use_ref.append(article.source) + + web_context = web_context[0:self.context_max_length] + web_context = web_context.strip() + + if len(web_context) > 0: + prompt, history = self.llm.build_prompt( + instruction=query, + context=web_context, + history_pair=history, + template=self.GENERATE_TEMPLATE) + response = self.llm.generate_response(prompt=prompt, + history=history, + backend='remote') + else: + reborn_code = ErrorCode.NO_SEARCH_RESULT + + tracker.log('web response', [web_context, response, reborn_code]) + except Exception as e: + logger.error(e) + + if response is not None and len(response) > 0: + prompt = self.PERPLESITY_TEMPLATE.format(query, response) + if self.single_judge(prompt=prompt, + tracker=tracker, + throttle=9, + default=0, + backend='remote'): + reborn_code = ErrorCode.BAD_ANSWER + + # if response is not None and len(response) >= 800: + # # reply too long, summarize it + # response = self.llm.generate_response( + # prompt=self.SUMMARIZE_TEMPLATE.format(response)) + + if not self.security_content(tracker, response): + return ErrorCode.SECURITY, '回复可能包含不安全内容,无法显示', use_ref + + if reborn_code != ErrorCode.SUCCESS: + return reborn_code, response, use_ref + + return ErrorCode.SUCCESS, response, use_ref + + +def parse_args(): + """Parses command-line arguments.""" + parser = argparse.ArgumentParser(description='OpenXLabWorker.') + parser.add_argument('work_dir', type=str, help='Working directory.') + parser.add_argument( + '--config_path', + default='config.ini', + help='OpenXLabWorker configuration path. Default value is config.ini') + return parser.parse_args() + + +if __name__ == '__main__': + args = parse_args() + bot = OpenXLabWorker(work_dir=args.work_dir, config_path=args.config_path) + queries = ['茴香豆是怎么做的'] + for example in queries: + print(bot.generate(query=example, history=[], groupname='')) diff --git a/repodir/huixiangdou/web/requirements.txt b/repodir/huixiangdou/web/requirements.txt new file mode 100644 index 00000000..3ad4de43 --- /dev/null +++ b/repodir/huixiangdou/web/requirements.txt @@ -0,0 +1,12 @@ +apscheduler==3.10.4 +fastapi==0.103.0 +flask==3.0.2 +lark-oapi==1.2.1 +passlib==1.7.4 +pydantic==2.4.2 +PyJWT==2.8.0 +python-multipart==0.0.9 +redis==4.5.5 +starlette==0.27.0 +tqdm==4.65.0 +uvicorn==0.27.0 diff --git a/repodir/huixiangdou/web/scheduler/__init__.py b/repodir/huixiangdou/web/scheduler/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/repodir/huixiangdou/web/scheduler/huixiangdou_task.py b/repodir/huixiangdou/web/scheduler/huixiangdou_task.py new file mode 100644 index 00000000..f42ae28b --- /dev/null +++ b/repodir/huixiangdou/web/scheduler/huixiangdou_task.py @@ -0,0 +1,171 @@ +import json + +from apscheduler.schedulers.asyncio import AsyncIOScheduler +from apscheduler.triggers.interval import IntervalTrigger +from redis.lock import Lock + +import web.constant.biz_constant as biz_const +from web.model.chat import ChatType +from web.model.huixiangdou import HxdChatResponse, HxdTaskResponse, HxdTaskType +from web.model.qalib import Pipeline, QalibInfo, QalibSample +from web.orm.redis import r +from web.service.agent import LarkAgent +from web.service.cache import ChatCache +from web.util.log import log + +logger = log(__name__) +scheduler = AsyncIOScheduler() + + +def handle_task_add_doc_response(response: HxdTaskResponse): + """update qalib's status from huixiangdou response's code. + + :param response: + :return: + """ + logger.info('do task: add doc') + fid = response.feature_store_id + name = biz_const.RDS_KEY_QALIB_INFO + files_state = response.files_state + o = r.hget(name=name, key=fid) + if not o: + logger.error(f"can't find {name}:{fid} in redis.") + return + qalib_info = QalibInfo(**json.loads(o)) + + qalib_info.status = biz_const.HXD_PIPELINE_QALIB_CREATE_SUCCESS if response.code == 0 else response.code + qalib_info.status_desc = response.status + qalib_info.filesState = files_state + + r.hset(name=name, key=fid, value=qalib_info.model_dump_json()) + logger.info( + f"do task={response.type} with fid={response.feature_store_id}'s result: {response.code}-{response.status}" + ) + + +def handle_task_update_sample_response(response: HxdTaskResponse): + """update sample's confirm status from response's code. + + :param response: + :return: + """ + logger.info('do task: update sample') + name = biz_const.RDS_KEY_SAMPLE_INFO + fid = response.feature_store_id + o = r.hget(name=name, key=fid) + if not o: + logger.error(f"can't find {name}:{fid} in redis") + return + sample = QalibSample(**json.loads(o)) + sample.confirmed = True if response.code == 0 else False + r.hset(name=name, key=fid, value=sample.model_dump_json()) + + +def handle_task_update_pipeline_response(response: HxdTaskResponse): + logger.info('do task: update pipeline') + name = biz_const.RDS_KEY_PIPELINE + o = r.hget(name=name, key=response.feature_store_id) + if not o: + logger.error(f"can't find {name}:{response.feature_store_id} in redis") + return + pipeline = Pipeline(**json.loads(o)) + pipeline.status = response.status + pipeline.code = response.code + pipeline.confirmed = True + pipeline.success = True if response.code == 0 else False + r.hset(name=name, + key=response.feature_store_id, + value=pipeline.model_dump_json()) + + +async def sync_hxd_task_response() -> None: + """ + sync huixiangdou task response from redis and do relative process + :return: None + """ + o = r.lpop(biz_const.RDS_KEY_HXD_TASK_RESPONSE) + if not o: + logger.debug( + f'lpop from {biz_const.RDS_KEY_HXD_TASK_RESPONSE} is empty') + return + hxd_task_response = HxdTaskResponse(**json.loads(o)) + if not hxd_task_response: + logger.error( + f'deserializing huixiangdou task response failed, raw: {o}') + return + task_type = hxd_task_response.type + if task_type == HxdTaskType.ADD_DOC.value: + handle_task_add_doc_response(hxd_task_response) + elif task_type == HxdTaskType.UPDATE_SAMPLE.value: + handle_task_update_sample_response(hxd_task_response) + elif task_type == HxdTaskType.UPDATE_PIPELINE.value: + handle_task_update_pipeline_response(hxd_task_response) + else: + logger.error(f'unrecognized task type: {task_type}') + return + + +async def fetch_chat_response(): + name = biz_const.RDS_KEY_HXD_CHAT_RESPONSE + length = r.llen(name) + if length == 0: + return + + while length > 0: + length -= 1 + o = r.lpop(name) + if not o: + logger.debug(f'lpop for {name} is empty, omit') + continue + chat_response = HxdChatResponse(**json.loads(o)) + logger.info( + f'[chat-response] feature_store_id: {chat_response.feature_store_id}, content: {chat_response.response.model_dump_json()}, query_id: {chat_response.query_id}' + ) + query_info = ChatCache.set_query_response( + chat_response.query_id, chat_response.feature_store_id, + chat_response.response) + + if not query_info: + continue + if query_info.type == ChatType.ONLINE: + continue + if query_info.type == ChatType.LARK: + await LarkAgent.response_callback(query_info) + elif query_info.type == ChatType.WECHAT: + pass + + +def allow_scheduler(task): + lock = Lock(r, f'{biz_const.RDS_KEY_SCHEDULER}-{task}') + if lock.acquire(blocking=False): + logger.info(f'{biz_const.RDS_KEY_SCHEDULER}-{task} is locked') + return True + return False + + +def start_scheduler(): + if not scheduler.running: + # ensure only one scheduler task is running + # if allow_scheduler("sync_hxd_task_response"): + logger.info('start scheduler of sync_hxd_task_respone') + scheduler.add_job(sync_hxd_task_response, + IntervalTrigger(seconds=1)) # 100ms + scheduler.add_job(fetch_chat_response, IntervalTrigger(seconds=1)) + # more scheduler job can be added here + scheduler.start() + + +def release_scheduler_lock(task) -> bool: + key = f'{biz_const.RDS_KEY_SCHEDULER}-{task}' + r.delete(key) + return False if r.exists(key) else True + + +def stop_scheduler(): + task_1 = 'sync_hxd_task_response' + if release_scheduler_lock(task_1): + logger.info(f'release scheduler lock of {task_1} successfully.') + else: + logger.error( + f'release scheduler lock of {task_1} failed. you should delete this key from redis manually.' + ) diff --git a/repodir/huixiangdou/web/service/__init__.py b/repodir/huixiangdou/web/service/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/repodir/huixiangdou/web/service/access.py b/repodir/huixiangdou/web/service/access.py new file mode 100644 index 00000000..05efed65 --- /dev/null +++ b/repodir/huixiangdou/web/service/access.py @@ -0,0 +1,140 @@ +import json +import time + +from fastapi import Request, Response +from passlib.hash import bcrypt + +import web.constant.biz_constant as biz_const +import web.util.str as str +from web.config.env import HuixiangDouEnv +from web.model.access import AccessInfo, LoginBody +from web.model.base import BaseBody +from web.orm.redis import r +from web.service.qalib import QaLibCache, gen_suffix +from web.util.log import log + +logger = log(__name__) + + +def add_access_info(name, value) -> bool: + """add new access info to access:info db. + + :param name: + :param value: + :return: + """ + return False if 1 != r.hset( + name=biz_const.RDS_KEY_LOGIN, key=name, value=value) else True + + +def del_access_info(name) -> bool: + """del new access info from access:info db. + + :param name: + :return: + """ + return True if 1 == r.hdel(biz_const.RDS_KEY_LOGIN, name) else False + + +def _create_qa_lib(name, hashed_pass, feature_store_id) -> bool: + """ + 1. init access info + 2. init qalib info + :param name: + :param hashed_pass: + :param feature_store_id: + :return: + """ + try: + if not add_access_info( + name, + AccessInfo(hashpass=hashed_pass, + featureStoreId=feature_store_id).model_dump_json()): + return False + suffix = gen_suffix(feature_store_id) + if not QaLibCache().init_qalib_info( + feature_store_id, biz_const.HXD_QALIB_STATUS_INIT, name, + suffix): + if not del_access_info(name): + logger.error(f'del access info by {name} failed') + return False + + # store suffix -> feature_store_id + QaLibCache().set_suffix_to_qalib(suffix, feature_store_id) + return True + except Exception as e: + logger.error(f'[create] create name: {name} failed: {e}') + if not del_access_info(name): + logger.error(f'del access info by {name} failed') + if not QaLibCache().del_qalib_info(feature_store_id): + logger.error(f'del qalib info by {name} failed') + return False + + +class LoginService: + + def __init__(self, login: LoginBody, request: Request, response: Response): + self.name = login.name + self.password = login.password + self.response = response + self.request = request + + def _set_cookie(self, cookie_key, *jwt_payloads): + self.response.set_cookie( + key=cookie_key, + value=str.gen_jwt(jwt_payloads[0][0], jwt_payloads[0][1], + int(round(time.time() * 1000) + 604800000)), + max_age=604800, + expires=604800, + # only send cookie in https when secure is True + secure=HuixiangDouEnv.get_cookie_secure(), + # cookie will be sent in all requests, including cross-site's requests + # to make sure the huixiangdou's cookie can be transformed in OpenXLab-Apps + samesite=HuixiangDouEnv.get_cookie_samesite()) + + async def login(self): + if not self.name or len(self.name) < 8: + logger.error(f'login name={self.name} not valid.') + return BaseBody(msg=biz_const.ERR_ACCESS_LOGIN.get('msg'), + msgCode=biz_const.ERR_ACCESS_LOGIN.get('code')) + + # calc the password hashcode + o = r.hget(name=biz_const.RDS_KEY_LOGIN, key=self.name) + + gen_hashed_pass = bcrypt.hash(self.password) + # qalib not existed, create one + if not o: + feature_store_id = str.gen_random_string() + # create qalib + if not _create_qa_lib(self.name, gen_hashed_pass, + feature_store_id): + self.response.delete_cookie(key=biz_const.HXD_COOKIE_KEY) + return BaseBody( + msg=biz_const.ERR_ACCESS_CREATE.get('msg'), + msgCode=biz_const.ERR_ACCESS_CREATE.get('code')) + + # set cookies + # todo domain need to set? + self._set_cookie(biz_const.HXD_COOKIE_KEY, + [feature_store_id, self.name]) + return BaseBody(data={ + 'exist': False, + 'featureStoreId': feature_store_id + }) + # qalib existed + else: + access_info = AccessInfo(**json.loads(bytes.decode(o))) + # auth succeed + if bcrypt.verify(self.password, access_info.hashpass): + feature_store_id = access_info.featureStoreId + # auth failed + else: + return BaseBody(msg=biz_const.ERR_ACCESS_LOGIN.get('msg'), + msgCode=biz_const.ERR_ACCESS_LOGIN.get('code')) + # todo domain need to set + self._set_cookie(biz_const.HXD_COOKIE_KEY, + [feature_store_id, self.name]) + return BaseBody(data={ + 'exist': True, + 'featureStoreId': feature_store_id + }) diff --git a/repodir/huixiangdou/web/service/agent.py b/repodir/huixiangdou/web/service/agent.py new file mode 100644 index 00000000..2c034472 --- /dev/null +++ b/repodir/huixiangdou/web/service/agent.py @@ -0,0 +1,389 @@ +import json +import re +from enum import Enum +from typing import List, Union + +import lark_oapi as lark +import requests +from fastapi import Request, Response +from lark_oapi import (AUTHORIZATION, CONTENT_TYPE, LARK_REQUEST_NONCE, + LARK_REQUEST_SIGNATURE, LARK_REQUEST_TIMESTAMP, + USER_AGENT, UTF_8, X_REQUEST_ID, X_TT_LOGID, + Content_Disposition, RawRequest, RawResponse) +from lark_oapi.api.im.v1 import (GetChatRequest, GetMessageResourceRequest, + MentionEvent, P2ImMessageReceiveV1, + ReplyMessageRequest, ReplyMessageRequestBody) + +from web.config.env import HuixiangDouEnv +from web.constant import biz_constant +from web.model.base import BaseBody, standard_error_response +from web.model.chat import (ChatQueryInfo, ChatRequestBody, ChatType, + LarkChatDetail, WechatPollItem, WechatRequest, + WechatResponse, WechatType) +from web.service import qalib +from web.service.cache import ChatCache +from web.service.chat import ChatService +from web.service.qalib import QaLibCache +from web.util.log import log + +logger = log(__name__) + + +class LarkContentType(Enum): + NORMAL_TEXT = 0 + AT_ALL_TEXT = 1 + AT_BOT_TEXT = 2 + AT_OTHER_PERSON_TEXT = 3 + IMAGE = 4 + OTHER = 5 + REPLY = 6 + + +class LarkAgent: + + @classmethod + async def parse_req(cls, request: Request) -> RawRequest: + headers = dict(request.headers) + req = RawRequest() + req.uri = request.url.path + req.body = await request.body() + req.headers = {} + + for k, v in headers.items(): + if USER_AGENT.lower() == k.lower(): + req.headers[USER_AGENT] = v + elif AUTHORIZATION.lower() == k.lower(): + req.headers[AUTHORIZATION] = v + elif X_TT_LOGID.lower() == k.lower(): + req.headers[X_TT_LOGID] = v + elif X_REQUEST_ID.lower() == k.lower(): + req.headers[X_REQUEST_ID] = v + elif CONTENT_TYPE.lower() == k.lower(): + req.headers[CONTENT_TYPE] = v + elif Content_Disposition.lower() == k.lower(): + req.headers[Content_Disposition] = v + elif LARK_REQUEST_TIMESTAMP.lower() == k.lower(): + req.headers[LARK_REQUEST_TIMESTAMP] = v + elif LARK_REQUEST_NONCE.lower() == k.lower(): + req.headers[LARK_REQUEST_NONCE] = v + elif LARK_REQUEST_SIGNATURE.lower() == k.lower(): + req.headers[LARK_REQUEST_SIGNATURE] = v + + return req + + @classmethod + def parse_rsp(cls, response: RawResponse) -> Response: + return Response(status_code=response.status_code, + content=str(response.content, UTF_8), + headers=response.headers) + + @classmethod + def get_event_handler(cls): + return lark.EventDispatcherHandler.builder( + HuixiangDouEnv.get_lark_encrypt_key(), + HuixiangDouEnv.get_lark_verification_token(), + lark.LogLevel.DEBUG).register_p2_im_message_receive_v1( + cls._on_im_message_received).build() + + @classmethod + def _on_im_message_received(cls, data: P2ImMessageReceiveV1): + msg = data.event.message + chat_id = msg.chat_id + message_id = msg.message_id + app_id = data.header.app_id + app_secret = QaLibCache.get_lark_info_by_app_id(app_id) + if not app_secret: + logger.error( + f'[lark] app_id: {app_id} not record, omit lark message callback' + ) + return + + # get feature store + client = cls._get_lark_client(app_id, app_secret) + chat_name = cls._get_chat_name(chat_id, client) + if not chat_name: + logger.error( + f'[lark] app_id: {app_id} get group name failed, omit lark message callback' + ) + return + suffix = qalib.get_suffix_by_name(chat_name) + if not suffix: + logger.error( + f'[lark] app_id: {app_id}, name: {chat_name} get suffix failed, omit lark message callback' + ) + return + feature_store_id = QaLibCache.get_qalib_feature_store_id_by_suffix( + suffix) + if not feature_store_id: + return + hxd_info = QaLibCache.get_qalib_info(feature_store_id) + if not hxd_info: + logger.error( + f'[lark] app_id: {app_id}, name: {chat_name} get feature store failed, omit lark message callback' + ) + return + + # mark + ChatCache.mark_agent_used(app_id, ChatType.LARK) + + if msg.root_id or msg.parent_id: + logger.debug( + f'[lark] app_id: {app_id}, name: {chat_name} got reply message, omit' + ) + return + + # parse lark content + content = msg.content + mentions = msg.mentions + lark_content = cls._parse_lark_content(content, mentions) + if not lark_content: + logger.debug( + f'[lark] app_id: {app_id}, name: {chat_name}, content: {content} omit' + ) + return + + query_id = None + chat_svc = ChatService(None, None, hxd_info) + # store image if exists + if len(lark_content.images) > 0: + query_id = chat_svc.generate_query_id(lark_content.content) + for index in range(len(lark_content.images)): + image_store_path = chat_svc.gen_image_store_path( + query_id, str(index), ChatType.LARK) + if cls._store_image(client, message_id, + lark_content.images[index], + image_store_path): + # replace image_key with actually store path + lark_content.images[index] = image_store_path + + # todo cache and fetch history + # push into chat task queue + # async chat + chat_detail = LarkChatDetail(appId=app_id, + appSecret=app_secret, + messageId=msg.message_id) + unique_id = data.event.sender.sender_id.open_id + '@' + chat_id + chat_svc.chat_by_agent(lark_content, ChatType.LARK, chat_detail, + unique_id, query_id) + + @classmethod + def _get_chat_name(cls, chat_id: str, + client: lark.client) -> Union[str, None]: + request = GetChatRequest.builder() \ + .chat_id(chat_id) \ + .build() + + response = client.im.v1.chat.get(request) + + if not response.success(): + logger.error( + f'[lark] get chat: {chat_id} info failed, code: {response.code}, msg: {response.msg}, log_id: {response.get_log_id()}' + ) + return None + + try: + return response.data.name + except: + logger.error( + f'[lark] get chat: {chat_id} name failed, data: {response.data}' + ) + return None + + @classmethod + def _get_lark_client(cls, app_id: str, app_secret: str) -> lark.client: + return lark.Client.builder().app_id(app_id).app_secret( + app_secret).log_level(HuixiangDouEnv.get_lark_log_level()).build() + + @classmethod + def _parse_lark_content( + cls, content: str, + mentions: List[MentionEvent]) -> Union[ChatRequestBody, None]: + if not content or len(content) == 0: + return None + + lark_json = json.loads(content) + content_type = LarkContentType.NORMAL_TEXT + if 'text' in lark_json: + text = lark_json.get('text') + if '@_user' in text: + content_type = cls._get_content_type_when_at_user_exists( + mentions) + elif '@_all' in text: + content_type = LarkContentType.AT_ALL_TEXT + elif len(lark_json) == 1 and 'image_key' in lark_json: + content_type = LarkContentType.IMAGE + else: + content_type = LarkContentType.OTHER + + process_flag = cls._check_should_process(content_type) + if not process_flag: + logger.debug( + f'[lark] content: {content} has content_type: {content_type}, omit' + ) + return None + + if content_type == LarkContentType.IMAGE: + image_key = lark_json.get('image_key') + return ChatRequestBody(images=[image_key]) + else: + # replace @user_\d + text = lark_json.get('text') + if content_type != LarkContentType.NORMAL_TEXT: + text = re.sub(r'@_user_\d+', '', text) + text = re.sub(r'@_all\d', '', text) + return ChatRequestBody(content=text) + + @classmethod + def _check_should_process(cls, t: LarkContentType) -> bool: + return t == LarkContentType.AT_BOT_TEXT or t == LarkContentType.NORMAL_TEXT or t == LarkContentType.AT_ALL_TEXT or t == LarkContentType.IMAGE + + @classmethod + def _get_content_type_when_at_user_exists( + cls, mentions: List[MentionEvent]) -> LarkContentType: + if not mentions or len(mentions) == 0: + return LarkContentType.AT_OTHER_PERSON_TEXT + + for item in mentions: + if not item.id.user_id or len(item.id.user_id) == 0: + return LarkContentType.AT_BOT_TEXT + + return LarkContentType.AT_OTHER_PERSON_TEXT + + @classmethod + def _store_image(cls, client: lark.client, message_id: str, image_key: str, + path: str) -> bool: + body = GetMessageResourceRequest.builder().message_id( + message_id).file_key(image_key).build() + response = client.im.v1.message_resource.get(body) + if not response.success(): + logger.error( + f'[lark] get image: {image_key} info failed, code: {response.code}, msg: {response.msg}, log_id: {response.get_log_id()}' + ) + return False + + if response.file: + with open(path, mode='wb') as fout: + fout.write(response.file.read()) + return True + logger.error( + f'[lark] get image: {image_key} stream empty, code: {response.code}, msg: {response.msg}, log_id: {response.get_log_id()}' + ) + return False + + @classmethod + async def response_callback(cls, chat_info: ChatQueryInfo) -> bool: + if not chat_info.detail: + logger.error( + f'[lark] invalid lark detail to send response, chat_info: {chat_info.model_dump()}' + ) + return False + + if chat_info.response.code != 0: + logger.info( + f'[lark] HuixiangDou inference error, detail: {chat_info.response.model_dump()}' + ) + return True + + lark_detail = json.dumps(chat_info.detail) + lark_detail = LarkChatDetail(**json.loads(lark_detail)) + + client = cls._get_lark_client(lark_detail.appId, lark_detail.appSecret) + content_body = ReplyMessageRequestBody.builder().content( + json.dumps({'text': chat_info.response.text + })).msg_type('text').reply_in_thread(False).build() + reply_body = ReplyMessageRequest.builder().message_id( + lark_detail.messageId).request_body(content_body).build() + response = await client.im.v1.message.areply(reply_body) + + if not response.success(): + logger.error( + f'[lark] response: {chat_info.model_dump()} failed, code: {response.code}, msg: {response.msg}, log_id: {response.get_log_id()}' + ) + return False + return True + + +class WechatAgent: + + @classmethod + def action(cls, body: WechatRequest, + suffix: str) -> Union[BaseBody, WechatResponse]: + feature_store_id = QaLibCache.get_qalib_feature_store_id_by_suffix( + suffix) + hxd_info = QaLibCache.get_qalib_info(feature_store_id) + if not hxd_info: + logger.error( + f'[wechat] suffix:{suffix} get feature store failed, omit wechat message callback' + ) + return standard_error_response( + biz_constant.ERR_QALIB_INFO_NOT_FOUND) + + # mark + ChatCache.mark_agent_used(body.groupname, ChatType.WECHAT) + + chat_svc = ChatService(None, None, hxd_info) + query_id = '' + if body.query.type != WechatType.Poll: + query_id = chat_svc.generate_query_id(body.query.content) + chat_request_body = None + + if body.query.type == WechatType.Image: + # store image + path = chat_svc.gen_image_store_path(query_id, 'i', + ChatType.WECHAT) + cls._store_image(path, body.query.content) + chat_request_body = ChatRequestBody(images=[path]) + elif body.query.type == WechatType.Poll: + # fetch response + return cls._fetch_response(feature_store_id) + else: + chat_request_body = ChatRequestBody(content=body.query.content) + + # push into chat queue + unique_id = body.username + '@' + body.groupname + chat_svc.chat_by_agent(chat_request_body, ChatType.WECHAT, body, + unique_id, query_id) + # record query_id + ChatCache.record_query_id_to_fetch(feature_store_id, query_id) + return WechatResponse() + + @classmethod + def _store_image(cls, path: str, url: str) -> bool: + response = requests.get(url) + if not response or response.status_code != 200: + logger.error( + f'[wechat] get image: {url} binary failed, code: {response.status_code}, msg: {response.content}' + ) + return False + + with open(path, mode='wb') as fout: + fout.write(response.content) + return True + + @classmethod + def _fetch_response(cls, feature_store_id: str) -> WechatResponse: + ret = WechatResponse() + + query_id_list = ChatCache.mget_query_id_to_fetch(feature_store_id) + if len(query_id_list) == 0: + logger.debug( + f'[wechat] feature_store_id: {feature_store_id} has no response yet' + ) + return ret + + complete_query_id_list = [] + l = [] + query_infos = ChatCache.mget_query_info(query_id_list, + feature_store_id) + for item in query_infos: + if item.response: + l.append( + WechatPollItem(req=WechatRequest.model_validate_json( + json.dumps(item.detail)), + rsp=item.response)) + complete_query_id_list.append(item.queryId) + ret.root = l + + ChatCache.mark_query_id_complete(feature_store_id, + complete_query_id_list) + return ret diff --git a/repodir/huixiangdou/web/service/cache.py b/repodir/huixiangdou/web/service/cache.py new file mode 100644 index 00000000..5eaee5d6 --- /dev/null +++ b/repodir/huixiangdou/web/service/cache.py @@ -0,0 +1,165 @@ +import json +from datetime import datetime +from typing import List, Union + +from web.constant import biz_constant +from web.model.chat import ChatCaseType, ChatQueryInfo, ChatType +from web.model.huixiangdou import ChatResponse +from web.orm.redis import r +from web.util import time_util +from web.util.log import log + +logger = log(__name__) + + +class ChatCache: + + def __init__(self): + pass + + @classmethod + def set_query_request(cls, query_id: str, feature_store_id: str, + info: ChatQueryInfo): + cls._set_query_info(query_id, feature_store_id, info) + + @classmethod + def set_query_response( + cls, query_id: str, feature_store_id: str, + response: ChatResponse) -> Union[ChatQueryInfo, None]: + q = cls.get_query_info(query_id, feature_store_id) + if not q: + return None + q.response = response + cls._set_query_info(query_id, feature_store_id, q) + return q + + @classmethod + def get_query_info(cls, query_id: str, + feature_store_id: str) -> Union[ChatQueryInfo, None]: + key = biz_constant.RDS_KEY_QUERY_INFO + ':' + feature_store_id + field = query_id + o = r.hget(key, field) + if not o or len(o) == 0: + logger.error( + f'feature_store_id: {feature_store_id} get query: {query_id} empty, omit' + ) + return None + + return ChatQueryInfo(**json.loads(o)) + + @classmethod + def mget_query_info( + cls, query_id_list: List[str], + feature_store_id: str) -> Union[List[ChatQueryInfo], None]: + key = biz_constant.RDS_KEY_QUERY_INFO + ':' + feature_store_id + o = r.hmget(key, query_id_list) + if not o or len(o) == 0: + logger.error( + f'feature_store_id: {feature_store_id} mget: {query_id_list} empty, omit' + ) + return None + + ret = [] + for item in o: + ret.append(ChatQueryInfo(**json.loads(item))) + return ret + + @classmethod + def _set_query_info(cls, query_id: str, feature_store_id: str, + info: ChatQueryInfo): + key = biz_constant.RDS_KEY_QUERY_INFO + ':' + feature_store_id + field = query_id + r.hset(key, field, info.model_dump_json()) + + @classmethod + def update_case_feedback(cls, feature_store_id: str, + case_type: ChatCaseType, feedback: str) -> bool: + try: + name = f'{biz_constant.RDS_KEY_FEEDBACK_CASE}:{case_type}:{feature_store_id}' + r.rpush(name, feedback) + return True + except Exception as e: + logger.error(f'{e}') + return False + + @classmethod + def record_query_id_to_fetch(cls, feature_store_id: str, query_id: str): + key = biz_constant.RDS_KEY_QUERY_ID_TO_FETCH + ':' + feature_store_id + r.hset(key, query_id, 1) + r.expire(key, biz_constant.HXD_CHAT_TTL) + + @classmethod + def mget_query_id_to_fetch(cls, feature_store_id: str) -> List[str]: + key = biz_constant.RDS_KEY_QUERY_ID_TO_FETCH + ':' + feature_store_id + o = r.hgetall(key) + if not o or len(o) == 0: + return [] + + ret = [] + for i in o.keys(): + ret.append(i) + return ret + + @classmethod + def mark_query_id_complete(cls, feature_store_id: str, + query_id_list: List[str]): + if len(query_id_list) == 0: + return + key = biz_constant.RDS_KEY_QUERY_ID_TO_FETCH + ':' + feature_store_id + for item in query_id_list: + r.hdel(key, item) + + @classmethod + def mark_agent_used(cls, agent_identifier: str, agent: ChatType): + field = agent_identifier + if agent == ChatType.LARK: + key = biz_constant.RDS_KEY_AGENT_LARK_USED + else: + key = biz_constant.RDS_KEY_AGENT_WECHAT_USED + r.hset(key, field, 1) + + @classmethod + def hlen_agent_used(cls, agent: ChatType) -> int: + if agent == ChatType.LARK: + key = biz_constant.RDS_KEY_AGENT_LARK_USED + else: + key = biz_constant.RDS_KEY_AGENT_WECHAT_USED + + o = r.hlen(key) + return o + + @classmethod + def mark_monthly_active(cls, feature_store_id: str): + today_month = time_util.get_month_time_str(datetime.now()) + key = biz_constant.RDS_KEY_QALIB_ACTIVE + ':' + today_month + r.hset(key, feature_store_id, 1) + + @classmethod + def get_monthly_active(cls) -> int: + today_month = time_util.get_month_time_str(datetime.now()) + key = biz_constant.RDS_KEY_QALIB_ACTIVE + ':' + today_month + o = r.hlen(key) + return o + + @classmethod + def add_inference_number(cls): + key = biz_constant.RDS_KEY_TOTAL_INFERENCE_NUMBER + r.incr(key) + + @classmethod + def get_inference_number(cls) -> int: + key = biz_constant.RDS_KEY_TOTAL_INFERENCE_NUMBER + o = r.get(key) + return o + + @classmethod + def mark_unique_inference_user(cls, user_identifier: str, agent: ChatType): + key = biz_constant.RDS_KEY_USER_INFERENCE + field = user_identifier + '@' + agent.name + r.sadd(key, field) + + @classmethod + def get_unique_inference_user_number(cls) -> int: + key = biz_constant.RDS_KEY_USER_INFERENCE + o = r.scard(key) + return o diff --git a/repodir/huixiangdou/web/service/chat.py b/repodir/huixiangdou/web/service/chat.py new file mode 100644 index 00000000..1b8a55c7 --- /dev/null +++ b/repodir/huixiangdou/web/service/chat.py @@ -0,0 +1,175 @@ +import base64 +import binascii +import hashlib +import os +import time +from typing import List, Union + +from fastapi import Request, Response + +from web.constant import biz_constant +from web.model.base import BaseBody, Image, standard_error_response +from web.model.chat import (ChatCaseFeedbackBody, ChatOnlineResponseBody, + ChatQueryInfo, ChatRequestBody, ChatType) +from web.model.huixiangdou import HxdTask, HxdTaskPayload, HxdTaskType +from web.model.qalib import QalibInfo +from web.mq.hxd_task import HuixiangDouTask +from web.service.cache import ChatCache +from web.service.qalib import get_store_dir +from web.util.image import detect_base64_image_suffix +from web.util.log import log + +logger = log(__name__) + + +class ChatService: + + def __init__(self, request: Request, response: Response, + hxd_info: QalibInfo): + self.hxd_info = hxd_info + self.request = request + self.response = response + + async def chat_online(self, body: ChatRequestBody): + feature_store_id = self.hxd_info.featureStoreId + query_id = self.generate_query_id(body.content) + logger.info( + f'[chat-request]/online feature_store_id: {feature_store_id}, content: {body.content}, query_id: {query_id}' + ) + + # store images + images_path = [] + if len(body.images) > 0: + images_path = self._store_images(body.images, query_id) + if len(images_path) == 0: + return standard_error_response(biz_constant.ERR_CHAT) + + task = HxdTask(type=HxdTaskType.CHAT, + payload=HxdTaskPayload( + feature_store_id=feature_store_id, + query_id=query_id, + content=body.content, + history=body.history, + images=images_path)) + if HuixiangDouTask().updateTask(task): + chat_query_info = ChatQueryInfo(featureStoreId=feature_store_id, + queryId=query_id, + request=ChatRequestBody( + content=body.content, + images=images_path, + history=body.history, + type=ChatType.ONLINE)) + ChatCache.set_query_request(query_id, feature_store_id, + chat_query_info) + ChatCache.mark_unique_inference_user(feature_store_id, + ChatType.ONLINE) + return BaseBody(data=ChatOnlineResponseBody(queryId=query_id)) + + return standard_error_response(biz_constant.ERR_CHAT) + + async def fetch_response(self, body: ChatOnlineResponseBody): + feature_store_id = self.hxd_info.featureStoreId + info = ChatCache().get_query_info(body.queryId, feature_store_id) + if not info: + return standard_error_response(biz_constant.ERR_NOT_EXIST_CHAT) + if not info.response: + return standard_error_response(biz_constant.CHAT_STILL_IN_QUEUE) + return BaseBody(data=info.response) + + def chat_by_agent(self, + body: ChatRequestBody, + t: ChatType, + chat_detail: object, + user_unique_id: str, + query_id: str = None) -> bool: + feature_store_id = self.hxd_info.featureStoreId + if not query_id: + query_id = self.generate_query_id(body.content) + logger.info( + f'[chat-request]/agent feature_store_id: {feature_store_id}, content: {body.content}, query_id: {query_id}, type:{t}' + ) + + task = HxdTask(type=HxdTaskType.CHAT, + payload=HxdTaskPayload( + feature_store_id=feature_store_id, + query_id=query_id, + content=body.content, + history=body.history, + images=body.images)) + if HuixiangDouTask().updateTask(task): + chat_query_info = ChatQueryInfo(featureStoreId=feature_store_id, + queryId=query_id, + request=ChatRequestBody( + content=body.content, + images=body.images, + history=body.history), + type=t, + detail=chat_detail) + ChatCache.set_query_request(query_id, feature_store_id, + chat_query_info) + ChatCache.mark_unique_inference_user(user_unique_id, t) + return True + + return False + + def generate_query_id(self, content): + feature_store_id = self.hxd_info.featureStoreId + raw = feature_store_id + content[-8:] + str(time.time()) + h = hashlib.sha3_512() + h.update(raw.encode('utf-8')) + q = h.hexdigest() + return q[0:8] + + def _store_images(self, images, query_id) -> List[str]: + feature_store_id = self.hxd_info.featureStoreId + image_store_dir = get_store_dir(feature_store_id) + if not image_store_dir: + logger.error(f'get store dir failed for: {feature_store_id}') + return [] + + image_store_dir += '/images/' + os.makedirs(image_store_dir, exist_ok=True) + ret = [] + + index = 0 + for image in images: + try: + while len(image) % 4 != 0: + image += '=' + [image_format, image] = detect_base64_image_suffix(image) + if image_format == Image.INVALID: + logger.error(f'invalid image format, query_id: {query_id}') + return [] + decoded_image = base64.b64decode(image) + except binascii.Error: + logger.error( + f'invalid base64 encoded image, query_id: {query_id}') + return [] + store_path = image_store_dir + query_id[-8:] + '_' + str( + index) + '.' + image_format.value + with open(store_path, 'wb') as f: + f.write(decoded_image) + ret.append(store_path) + return ret + + def gen_image_store_path(self, query_id, name: str, + agent: ChatType) -> Union[str, None]: + feature_store_id = self.hxd_info.featureStoreId + image_store_dir = get_store_dir(feature_store_id) + if not image_store_dir: + logger.error(f'get store dir failed for: {feature_store_id}') + return None + + image_store_dir += '/images/' + os.makedirs(name=image_store_dir, exist_ok=True) + return image_store_dir + agent.name + query_id[-8:] + '_' + name + + async def case_feedback(self, body: ChatCaseFeedbackBody): + feature_store_id = self.hxd_info.featureStoreId + query_id = body.queryId + query_info = ChatCache.get_query_info(query_id, feature_store_id) + if not query_info: + return standard_error_response(biz_constant.ERR_CHAT_CASE_FEEDBACK) + return BaseBody() \ + if ChatCache.update_case_feedback(feature_store_id, body.type, query_info.model_dump_json()) \ + else standard_error_response(biz_constant.ERR_CHAT_CASE_FEEDBACK) diff --git a/repodir/huixiangdou/web/service/message.py b/repodir/huixiangdou/web/service/message.py new file mode 100644 index 00000000..2a8df227 --- /dev/null +++ b/repodir/huixiangdou/web/service/message.py @@ -0,0 +1,23 @@ +from fastapi import Request, Response + +from web.model.base import BaseBody +from web.model.chat import WechatRequest +from web.service.agent import LarkAgent, WechatAgent + + +class MessageService: + + def __init__(self, request: Request, response: Response): + self.request = request + self.response = response + + async def on_lark_message(self): + req = await LarkAgent.parse_req(self.request) + rsp = LarkAgent.get_event_handler().do(req) + return LarkAgent.parse_rsp(rsp) + + async def on_wechat_message(self, body: WechatRequest, suffix: str): + rsp = WechatAgent.action(body, suffix) + if isinstance(rsp, BaseBody): + return rsp + return BaseBody(data=rsp) diff --git a/repodir/huixiangdou/web/service/qalib.py b/repodir/huixiangdou/web/service/qalib.py new file mode 100644 index 00000000..7d3eece9 --- /dev/null +++ b/repodir/huixiangdou/web/service/qalib.py @@ -0,0 +1,421 @@ +from __future__ import annotations + +import json +import os.path +from typing import List, Union + +from fastapi import File, Request, Response, UploadFile + +import web.constant.biz_constant as biz_const +import web.util.str as str_util +from web.config.env import HuixiangDouEnv +from web.model.base import BaseBody, standard_error_response +from web.model.huixiangdou import (HxdTask, HxdTaskPayload, HxdTaskType, + HxdToken) +from web.model.integrate import IntegrateLarkBody, IntegrateWebSearchBody +from web.model.qalib import (AddDocError, AddDocsRes, Lark, QalibInfo, + QalibPositiveNegative, QalibSample, WebSearch, + Wechat, QalibDeleteDoc) +from web.mq.hxd_task import HuixiangDouTask +from web.orm.redis import r +from web.util.log import log + +logger = log(__name__) + + +def get_hxd_token_by_cookie(cookies) -> Union[HxdToken, None]: + return HxdToken( + **(str_util.parse_jwt(cookies.get(biz_const.HXD_COOKIE_KEY)) + )) if cookies and cookies.get(biz_const.HXD_COOKIE_KEY) else None + + +def get_store_dir(feature_store_id: str) -> Union[str, None]: + if not feature_store_id: + return None + try: + crt_path = os.path.abspath(__file__) + parent_path = os.path.dirname(os.path.dirname(crt_path)) + qa_path = os.path.join(parent_path, f'qa/{feature_store_id}') + os.makedirs(qa_path, exist_ok=True) + return qa_path + except Exception as e: + logger.error(f'{e}') + return None + + +def get_wechat_on_message_url(suffix: str) -> str: + endpoint = HuixiangDouEnv.get_message_endpoint() + return endpoint + 'api/v1/message/v1/wechat/' + suffix + + +def get_lark_on_message_url() -> str: + endpoint = HuixiangDouEnv.get_message_endpoint() + return endpoint + 'api/v1/message/v1/lark' + + +def gen_suffix(feature_store_id: str) -> str: + length = biz_const.HXD_FEATURE_STORE_SUFFIX_LENGTH + if len(feature_store_id) <= length: + return feature_store_id + return feature_store_id[-length:] + + +def get_suffix_by_name(name: str) -> Union[str, None]: + length = biz_const.HXD_FEATURE_STORE_SUFFIX_LENGTH + if len(name) < length: + logger.error( + f'group name: {name} shorter than suffix length, remind check group name' + ) + return None + + return name[-length:] + + +class QaLibService: + + def __init__(self, request: Request, response: Response, + hxd_info: QalibInfo): + self.request = request + self.response = response + self.hxd_info = hxd_info + + @classmethod + def get_existed_docs(cls, feature_store_id) -> List: + o = r.hget(name=biz_const.RDS_KEY_QALIB_INFO, key=feature_store_id) + if not o: + return [] + qalib_info = QalibInfo(**json.loads(o)) + return qalib_info.docs + + async def info(self) -> BaseBody: + return BaseBody(data=self.hxd_info) + + async def add_docs(self, files: List[UploadFile] = File(...)): + feature_store_id = self.hxd_info.featureStoreId + name = self.hxd_info.name + logger.info(f'start to add docs for qalib: {name}') + + store_dir = get_store_dir(feature_store_id) + if not files or not store_dir: + return BaseBody() + + ret = AddDocsRes(errors=[]) + docs = self.get_existed_docs(feature_store_id) + + total_bytes = int(self.request.headers.get('content-length')) + if total_bytes > biz_const.HXD_ADD_DOCS_ONCE_MAX: + return standard_error_response( + biz_const.ERR_QALIB_ADD_DOCS_ONCE_MAX) + write_size = 0 + # store files + for file in files: + if file.filename and len(file.filename.encode('utf-8')) > 255: + logger.error( + f'filename: {file.filename} too long, maximum 255 bytes, omit current filename' + ) + ret.errors.append( + AddDocError(fileName=file.filename, + reason='filename is too long')) + continue + + with open(os.path.join(store_dir, file.filename), 'wb') as f: + while True: + chunk = await file.read(32768) # 64KB + if not chunk: + break + f.write(chunk) + write_size += len(chunk) + progress = (write_size / total_bytes) * 100 + # print can be removed if performance matters + print(f'\rQalib({name}) total process: {progress:.2f}%', + end='') + docs.append(file.filename) + await file.close() + # update qalib in redis + if not QaLibCache().update_qalib_docs(feature_store_id, docs, + store_dir): + return BaseBody() + # update to huixiangdou task queue + if not HuixiangDouTask().updateTask( + HxdTask(type=HxdTaskType.ADD_DOC, + payload=HxdTaskPayload( + name=name, + feature_store_id=feature_store_id, + file_list=docs, + file_abs_base=store_dir))): + return BaseBody() + + ret.docBase = store_dir + ret.docs = docs + return BaseBody(data=ret) + + async def delete_docs(self, body: QalibDeleteDoc): + feature_store_id = self.hxd_info.featureStoreId + name = self.hxd_info.name + logger.info(f'start to delete docs for qalib: {name}') + + store_dir = get_store_dir(feature_store_id) + for filename in body.filenames: + path = os.path.join(store_dir, filename) + if not os.path.exists(path): + logger.warn(f"qalib: {name} has no file named {filename} to delete.") + continue + if path.startswith('.'): + continue + try: + os.remove(path) + except OSError as e: + logger.error(f'qalib: error: {e} when removing {path}') + + filenames = set(os.listdir(store_dir)) + left_filenames = list(filenames - set(body.filenames)) + # update qalib in redis + if not QaLibCache().rewrite_qalib_docs(feature_store_id, left_filenames, + store_dir): + return BaseBody() + # update to huixiangdou task queue + if not HuixiangDouTask().updateTask( + HxdTask(type=HxdTaskType.ADD_DOC, + payload=HxdTaskPayload( + name=name, + feature_store_id=feature_store_id, + file_list=left_filenames, + file_abs_base=store_dir))): + return BaseBody() + + ret = AddDocsRes(errors=[]) + ret.docBase = store_dir + ret.docs = left_filenames + return BaseBody(data=ret) + + async def get_sample_info(self): + sample_info = QaLibCache.get_sample_info(self.hxd_info.featureStoreId) + return BaseBody(data=sample_info) + + async def update_sample_info(self, body: QalibPositiveNegative): + name = self.hxd_info.name + feature_store_id = self.hxd_info.featureStoreId + + positives = body.positives + negatives = body.negatives + qalib_sample = QalibSample(name=name, + featureStoreId=feature_store_id, + positives=positives, + negatives=negatives, + confirmed=False) + # update sample to redis + QaLibCache.set_sample_info(feature_store_id, qalib_sample) + # update sample to huixiangdou task + if not HuixiangDouTask().updateTask( + HxdTask(type=HxdTaskType.UPDATE_SAMPLE, + payload=HxdTaskPayload( + name=name, + feature_store_id=feature_store_id, + positive=positives, + negative=negatives))): + return BaseBody() + return await self.get_sample_info() + + async def integrate_lark(self, body: IntegrateLarkBody): + feature_store_id = self.hxd_info.featureStoreId + info = QaLibCache.get_qalib_info(feature_store_id) + if not info: + return standard_error_response(biz_const.ERR_QALIB_INFO_NOT_FOUND) + + if info.lark: + info.lark.appId = body.appId + info.lark.appSecret = body.appSecret + else: + info.lark = Lark( + appId=body.appId, + appSecret=body.appSecret, + encryptKey=HuixiangDouEnv.get_lark_encrypt_key(), + verificationToken=HuixiangDouEnv.get_lark_verification_token(), + eventUrl=get_lark_on_message_url()) + QaLibCache.set_qalib_info(feature_store_id, info) + QaLibCache.set_lark_info(body.appId, body.appSecret) + return BaseBody(data=info.lark) + + async def integrate_web_search(self, body: IntegrateWebSearchBody): + feature_store_id = self.hxd_info.featureStoreId + info = QaLibCache.get_qalib_info(feature_store_id) + if not info: + return standard_error_response(biz_const.ERR_QALIB_INFO_NOT_FOUND) + + info.webSearch = WebSearch(token=body.webSearchToken) + + task = HxdTask(type=HxdTaskType.UPDATE_PIPELINE, + payload=HxdTaskPayload( + feature_store_id=feature_store_id, + name=self.hxd_info.name, + web_search_token=body.webSearchToken)) + if HuixiangDouTask().updateTask(task): + QaLibCache.set_qalib_info(feature_store_id, info) + return BaseBody() + + return standard_error_response(biz_const.ERR_INFO_UPDATE_FAILED) + + +class QaLibCache: + + def __init__(self): + pass + + @classmethod + def get_qalib_info(cls, feature_store_id: str) -> Union[QalibInfo, None]: + name = biz_const.RDS_KEY_QALIB_INFO + key = feature_store_id + o = r.hget(name, key) + if not o: + logger.error( + f'[qalib] feature_store_id: {feature_store_id}, get info empty' + ) + return None + return QalibInfo(**json.loads(o)) + + @classmethod + def set_qalib_info(cls, feature_store_id: str, info: QalibInfo): + name = biz_const.RDS_KEY_QALIB_INFO + key = feature_store_id + return r.hset(name, key, info.model_dump_json()) == 1 + + @classmethod + def init_qalib_info(cls, feature_store_id: str, status: int, name: str, + suffix: str) -> bool: + """add qalib info to qalib:info db. + + :param name: + :param feature_store_id: + :param status: + :return: + """ + wechat = Wechat(onMessageUrl=get_wechat_on_message_url(suffix)) + lark = Lark( + encryptKey=HuixiangDouEnv.get_lark_encrypt_key(), + verificationToken=HuixiangDouEnv.get_lark_verification_token(), + eventUrl=get_lark_on_message_url()) + qalib_info = QalibInfo(featureStoreId=feature_store_id, + status=status, + name=name, + wechat=wechat, + lark=lark, + suffix=suffix) + if not cls.set_qalib_info(feature_store_id, qalib_info): + logger.error( + f'[qalib] feature_store_id: {feature_store_id}, init qalib info failed' + ) + r.hdel(biz_const.RDS_KEY_QALIB_INFO, feature_store_id) + return False + return True + + @classmethod + def del_qalib_info(cls, feature_store_id: str) -> bool: + """del qalib info to qalib:info db. + + :param feature_store_id: + :return: + """ + return True if r.hdel(biz_const.RDS_KEY_QALIB_INFO, + feature_store_id) == 1 else False + + @classmethod + def rewrite_qalib_docs(cls, feature_store_id: str, added_docs: List[str], + file_base: str) -> bool: + """update qalib's docs. + + :param feature_store_id: + :param added_docs: + :param file_base: + :return: + """ + try: + info = cls.get_qalib_info(feature_store_id) + if not info: + return False + + info.docs = list(set(added_docs)) + info.docBase = file_base + + cls.set_qalib_info(feature_store_id, info) + return True + except Exception as e: + logger.error( + f'[qalib] feature_store_id: {feature_store_id}, update docs failed: {e}' + ) + return False + + @classmethod + def update_qalib_docs(cls, feature_store_id: str, added_docs: List[str], + file_base: str) -> bool: + """update qalib's docs. + + :param feature_store_id: + :param added_docs: + :param file_base: + :return: + """ + try: + info = cls.get_qalib_info(feature_store_id) + if not info: + return False + + raw_docs = info.docs + if not raw_docs: + raw_docs = [] + raw_docs.extend(added_docs) + info.docs = list(set(raw_docs)) + info.docBase = file_base + + cls.set_qalib_info(feature_store_id, info) + return True + except Exception as e: + logger.error( + f'[qalib] feature_store_id: {feature_store_id}, update docs failed: {e}' + ) + return False + + @classmethod + def get_sample_info(cls, + feature_store_id: str) -> Union[QalibSample, None]: + o = r.hget(name=biz_const.RDS_KEY_SAMPLE_INFO, key=feature_store_id) + if not o: + logger.info( + f'[qalib] feature_store_id: {feature_store_id}, get empty sample' + ) + return None + return QalibSample(**json.loads(o)) + + @classmethod + def set_sample_info(cls, feature_store_id: str, sample_info: QalibSample): + r.hset(name=biz_const.RDS_KEY_SAMPLE_INFO, + key=feature_store_id, + value=sample_info.model_dump_json()) + + @classmethod + def set_suffix_to_qalib(cls, suffix: str, feature_store_id: str): + r.hset(name=biz_const.RDS_KEY_SUFFIX_TO_QALIB, + key=suffix, + value=feature_store_id) + + @classmethod + def get_qalib_feature_store_id_by_suffix(cls, + suffix: str) -> Union[str, None]: + o = r.hget(name=biz_const.RDS_KEY_SUFFIX_TO_QALIB, key=suffix) + if not o: + logger.error(f'[qalib] suffix: {suffix} has no qalib') + return None + return o.decode('utf-8') + + @classmethod + def get_lark_info_by_app_id(cls, app_id: str) -> Union[str, None]: + key = biz_const.RDS_KEY_LARK_CONFIG + ':' + app_id + o = r.get(key) + if not o: + logger.error(f'f[lark] app_id: {app_id} has no record') + return None + return o.decode('utf-8') + + @classmethod + def set_lark_info(cls, app_id: str, app_secret: str): + key = biz_const.RDS_KEY_LARK_CONFIG + ':' + app_id + r.set(key, app_secret) diff --git a/repodir/huixiangdou/web/service/statistic.py b/repodir/huixiangdou/web/service/statistic.py new file mode 100644 index 00000000..56320a27 --- /dev/null +++ b/repodir/huixiangdou/web/service/statistic.py @@ -0,0 +1,34 @@ +from fastapi import Request, Response + +import web.constant.biz_constant as biz_const +from web.model.base import BaseBody +from web.model.chat import ChatType +from web.model.statistic import StatisticTotal +from web.orm.redis import r +from web.service.cache import ChatCache +from web.util.log import log + +logger = log(__name__) + + +class StatisticService: + + def __init__(self, request: Request, response: Response): + self.request = request + self.response = response + + async def info_statistic(self): + qalib_total = r.hlen(biz_const.RDS_KEY_QALIB_INFO) + monthly_active = ChatCache.get_monthly_active() + lark_used = ChatCache.hlen_agent_used(ChatType.LARK) + wechat_used = ChatCache.hlen_agent_used(ChatType.WECHAT) + total_inference = ChatCache.get_inference_number() + unique_user = ChatCache.get_unique_inference_user_number() + + data = StatisticTotal(qalibTotal=qalib_total, + lastMonthUsed=monthly_active, + wechatTotal=wechat_used, + feishuTotal=lark_used, + servedTotal=total_inference, + realServedTotal=unique_user) + return BaseBody(data=data) diff --git a/repodir/huixiangdou/web/tools/README.md b/repodir/huixiangdou/web/tools/README.md new file mode 100644 index 00000000..770e936e --- /dev/null +++ b/repodir/huixiangdou/web/tools/README.md @@ -0,0 +1,7 @@ +# **SFT tools have moved to [sft directory](../../sft/)** + +# Devops tools + +- dump_redis_query.py # for web version, dump all question from redis to `query.jsonl` +- update_fs_max_len.py # for web version, update all users' max text length config of remote LLM +- get_puyu_model_list.py # for inner API, get all puyu API model list diff --git a/repodir/huixiangdou/web/tools/dump_redis_query.py b/repodir/huixiangdou/web/tools/dump_redis_query.py new file mode 100644 index 00000000..9e730bfd --- /dev/null +++ b/repodir/huixiangdou/web/tools/dump_redis_query.py @@ -0,0 +1,45 @@ +import json +import os + +from loguru import logger +from redis import Redis + + +def redis_host(): + host = os.getenv('REDIS_HOST') + if host is None or len(host) < 1: + raise Exception('REDIS_HOST not config') + return host + + +def redis_port(): + port = os.getenv('REDIS_PORT') + if port is None: + logger.debug('REDIS_PORT not set, try 6379') + port = 6379 + return port + + +def redis_passwd(): + passwd = os.getenv('REDIS_PASSWORD') + if passwd is None or len(passwd) < 1: + raise Exception('REDIS_PASSWORD not config') + return passwd + + +def feature_store_base_dir(): + return 'feature_stores' + + +db = Redis(host=redis_host(), + port=redis_port(), + password=redis_passwd(), + charset='utf-8', + decode_responses=True) +keys = db.keys('HuixiangDou:query:*') + +with open('query.jsonl', 'w') as f: + for key in keys: + value = db.hgetall(key) + f.write(json.dumps(value, ensure_ascii=False)) + f.write('\n') diff --git a/repodir/huixiangdou/web/tools/get_puyu_model_list.py b/repodir/huixiangdou/web/tools/get_puyu_model_list.py new file mode 100644 index 00000000..8d53f0c5 --- /dev/null +++ b/repodir/huixiangdou/web/tools/get_puyu_model_list.py @@ -0,0 +1,15 @@ +import json +import os + +import requests + +token = os.getenv('TOKEN') + +url = 'https://puyu.openxlab.org.cn/puyu/api/v1/models' +header = {'Content-Type': 'application/json', 'Authorization': token} +data = {} + +res = requests.get(url, headers=header, data=json.dumps(data)) +print(res.status_code) +print(res.json()) +print(res.json()['data']) diff --git a/repodir/huixiangdou/web/tools/update_fs_max_len.py b/repodir/huixiangdou/web/tools/update_fs_max_len.py new file mode 100644 index 00000000..8957a542 --- /dev/null +++ b/repodir/huixiangdou/web/tools/update_fs_max_len.py @@ -0,0 +1,28 @@ +import os + +import pytoml + + +def read_config_ini_files(directory): + # 遍历指定目录 + for root, dirs, files in os.walk(directory): + for file in files: + # 检查文件扩展名是否为 .ini + if file == 'config.ini': + # 构建完整的文件路径 + file_path = os.path.join(root, file) + try: + # 读取并解析 config.ini 文件 + with open(file_path, 'r', encoding='utf-8') as f: + config = pytoml.load(f) + print((file_path, config['llm']['server']['remote_llm_max_text_length'])) + config['llm']['server']['remote_llm_max_text_length'] = 40000 + with open(file_path, 'w', encoding='utf8') as f: + pytoml.dump(config, f) + except Exception as e: + print(f'An error occurred while reading {file_path}: {e}') + + +# 指定要遍历的目录 +directory_to_crawl = '/root/HuixiangDou/feature_stores' +read_config_ini_files(directory_to_crawl) diff --git a/repodir/huixiangdou/web/util/__init__.py b/repodir/huixiangdou/web/util/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/repodir/huixiangdou/web/util/image.py b/repodir/huixiangdou/web/util/image.py new file mode 100644 index 00000000..0dd5e8f5 --- /dev/null +++ b/repodir/huixiangdou/web/util/image.py @@ -0,0 +1,20 @@ +from web.model.base import Image + + +def detect_base64_image_suffix(base64: str) -> [Image, str]: + if not base64 or len(base64) == 0: + return [Image.INVALID, ''] + + s = base64.split('base64,') + if len(s) < 2: + return [Image.INVALID, ''] + + base64_prefix = s[0].lower() + if 'data:image/jpeg;' == base64_prefix: + return [Image.JPG, s[1]] + if 'data:image/png;' == base64_prefix: + return [Image.PNG, s[1]] + if 'data:image/bmp;' == base64_prefix: + return [Image.BMP, s[1]] + + return [Image.INVALID, ''] diff --git a/repodir/huixiangdou/web/util/log.py b/repodir/huixiangdou/web/util/log.py new file mode 100644 index 00000000..19cc013d --- /dev/null +++ b/repodir/huixiangdou/web/util/log.py @@ -0,0 +1,29 @@ +import logging + + +def log(name): + """ + @param name: python file name + @return: Logger + """ + logger = logging.getLogger(name) + logger.setLevel(logging.INFO) + formatter = logging.Formatter( + '%(levelname)s: %(asctime)s - %(module)s-%(funcName)s-line:%(lineno)d - %(message)s' + ) + ch = logging.StreamHandler() + ch.setFormatter(formatter) + logger.addHandler(ch) + return logger + + +def clear_other_log(): + for name, item in logging.Logger.manager.loggerDict.items(): + if not isinstance(item, logging.Logger): + continue + if 'aoe' not in name: + item.setLevel(logging.CRITICAL) + + +clear_other_log() +logger = log('util') diff --git a/repodir/huixiangdou/web/util/str.py b/repodir/huixiangdou/web/util/str.py new file mode 100644 index 00000000..ea09b74d --- /dev/null +++ b/repodir/huixiangdou/web/util/str.py @@ -0,0 +1,73 @@ +import os +import random +import string +import time +from typing import List + +import jwt +from fastapi import HTTPException + +from web.config.env import HuixiangDouEnv + + +def gen_random_string(length=4) -> str: + """ + :param length: random string's length + :return: a string with the given length, includes only A-Za-z0-9 + """ + # 字符集包含所有大写字母和数字 + chars = string.ascii_letters + string.digits + return ''.join(random.choice(chars) for _ in range(length)) + + +def gen_jwt(feature_store_id: str, qa_name: str, expire: int) -> str: + """ + :param feature_store_id: + :param qa_name: 知识库名称 + :param expire: 过期时间 unix 时间戳 + :return: jwt + """ + payload = { + 'iat': time.time(), + 'jti': feature_store_id, + 'qa_name': qa_name, + 'exp': expire + } + token = jwt.encode(payload, + HuixiangDouEnv.get_jwt_secret(), + algorithm='HS256') + return token + + +def parse_jwt(token: str) -> dict: + hxd_token = jwt.decode(token, + HuixiangDouEnv.get_jwt_secret(), + algorithms='HS256') + return hxd_token + + +def safe_join(directory: str, path: str) -> str: + """Safely path to a base directory to avoid escaping the base directory. + Borrowed from: werkzeug.security.safe_join. + + @param directory: + @param path: + """ + _os_alt_seps: List[str] = [ + sep for sep in [os.path.sep, os.path.altsep] + if sep is not None and sep != '/' + ] + + if path == '': + raise HTTPException(status_code=400, detail='path is empty') + + filename = os.path.normpath(path) + full_path = os.path.join(directory, filename) + if (any(sep in filename for sep in _os_alt_seps) or os.path.isabs(filename) + or filename == '..' or filename.startswith('../') + or os.path.isdir(full_path)): + raise HTTPException(status_code=400, detail='path is illegal') + + if not os.path.exists(full_path): + raise HTTPException(status_code=404, detail='path is not existed') + return full_path diff --git a/repodir/huixiangdou/web/util/time_util.py b/repodir/huixiangdou/web/util/time_util.py new file mode 100644 index 00000000..b607fd02 --- /dev/null +++ b/repodir/huixiangdou/web/util/time_util.py @@ -0,0 +1,6 @@ +#! python3 +from datetime import datetime + + +def get_month_time_str(t: datetime) -> str: + return t.strftime('%y-%m') diff --git a/repodir/huixiangdou/web/web-architecture.png b/repodir/huixiangdou/web/web-architecture.png new file mode 100644 index 0000000000000000000000000000000000000000..aee5db0da28901799adc5d0209e101dc0c218bd0 GIT binary patch literal 37624 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”`IJ((VmCZj6F0sd*!R(m7?dn5?{IYV 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 Cr^Yt` literal 0 HcmV?d00001 From 6ecfeae60c104ab498dc57a484806aa39972f172 Mon Sep 17 00:00:00 2001 From: tpoisonooo Date: Wed, 28 Aug 2024 17:11:24 +0800 Subject: [PATCH 2/3] use pypi requirements --- requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 8f4750c3..03b0fddd 100644 --- a/requirements.txt +++ b/requirements.txt @@ -19,8 +19,8 @@ redis requests scikit-learn # See https://github.com/deanmalmgren/textract/issues/461 -textract @ git+https://github.com/tpoisonooo/textract@master -# textract +# textract @ git+https://github.com/tpoisonooo/textract@master +textract texttable tiktoken torch>=2.0.0 From 8db08fa0a9e5306276a91ee630173dc751f26d88 Mon Sep 17 00:00:00 2001 From: tpoisonooo Date: Wed, 28 Aug 2024 17:22:07 +0800 Subject: [PATCH 3/3] remove gradio to gradio_ui --- huixiangdou/{gradio.py => gradio_ui.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename huixiangdou/{gradio.py => gradio_ui.py} (100%) diff --git a/huixiangdou/gradio.py b/huixiangdou/gradio_ui.py similarity index 100% rename from huixiangdou/gradio.py rename to huixiangdou/gradio_ui.py
+> This part is not for beginner, please know about transformers, scaling law and SFT first. Basic computer network and python programming knowledge also required. +>

@e3f=ZpGF+s2 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 C&4_UT literal 0 HcmV?d00001 diff --git a/repodir/huixiangdou/resource/HuixiangDou.pdf b/repodir/huixiangdou/resource/HuixiangDou.pdf new file mode 100644 index 0000000000000000000000000000000000000000..3926548c1814cd13f520b58b2498f0c231df55c6 GIT binary patch literal 297302 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

+ If you just switched documentation versions, it is likely that the page you were on is moved. You can look for it in + the content table left, or go to the homepage. +