diff --git a/.env b/.env index 8d7ea05..e413eef 100644 --- a/.env +++ b/.env @@ -1,5 +1,5 @@ OPENAI_API_KEY=sk-xxaiabmfhzwwpijuledllkmkzhzwsqeicjxmjwnvriqpwmpk OPENAI_API_BASE=https://api.siliconflow.cn/v1/ LLM_MODEL_NAME=deepseek-ai/DeepSeek-V3 - +# LLM_MODEL_NAME=deepseek-ai/DeepSeek-R1 RERANKER_MODEL_NAME=bge-reranker-v2-m3 \ No newline at end of file diff --git a/poetry.lock b/poetry.lock index fd71099..34f62ee 100644 --- a/poetry.lock +++ b/poetry.lock @@ -753,6 +753,72 @@ type = "legacy" url = "http://mirrors.aliyun.com/pypi/simple" reference = "ali-mirrors" +[[package]] +name = "gevent" +version = "25.5.1" +description = "Coroutine-based network library" +optional = false +python-versions = ">=3.9" +files = [ + {file = "gevent-25.5.1-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:8e5a0fab5e245b15ec1005b3666b0a2e867c26f411c8fe66ae1afe07174a30e9"}, + {file = "gevent-25.5.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7b80a37f2fb45ee4a8f7e64b77dd8a842d364384046e394227b974a4e9c9a52"}, + {file = "gevent-25.5.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:29ab729d50ae85077a68e0385f129f5b01052d01a0ae6d7fdc1824f5337905e4"}, + {file = "gevent-25.5.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80d20592aeabcc4e294fd441fd43d45cb537437fd642c374ea9d964622fad229"}, + {file = "gevent-25.5.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8ba0257542ccbb72a8229dc34d00844ccdfba110417e4b7b34599548d0e20e9"}, + {file = "gevent-25.5.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cad0821dff998c7c60dd238f92cd61380342c47fb9e92e1a8705d9b5ac7c16e8"}, + {file = "gevent-25.5.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:017a7384c0cd1a5907751c991535a0699596e89725468a7fc39228312e10efa1"}, + {file = "gevent-25.5.1-cp310-cp310-win_amd64.whl", hash = "sha256:469c86d02fccad7e2a3d82fe22237e47ecb376fbf4710bc18747b49c50716817"}, + {file = "gevent-25.5.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:12380aba5c316e9ff53cc21d8ab80f4a91c0df3ada58f65d4f5eb2cf693db00e"}, + {file = "gevent-25.5.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f0694daab1a041b69a53f53c2141c12994892b2503870515cabe6a5dbd2a928"}, + {file = "gevent-25.5.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2797885e9aeffdc98e1846723e5aa212e7ce53007dbef40d6fd2add264235c41"}, + {file = "gevent-25.5.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cde6aaac36b54332e10ea2a5bc0de6a8aba6c205c92603fe4396e3777c88e05d"}, + {file = "gevent-25.5.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24484f80f14befb8822bf29554cfb3a26a26cb69cd1e5a8be9e23b4bd7a96e25"}, + {file = "gevent-25.5.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8fdc7446895fa184890d8ca5ea61e502691114f9db55c9b76adc33f3086c4368"}, + {file = "gevent-25.5.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5b6106e2414b1797133786258fa1962a5e836480e4d5e861577f9fc63b673a5a"}, + {file = "gevent-25.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:bc899212d90f311784c58938a9c09c59802fb6dc287a35fabdc36d180f57f575"}, + {file = "gevent-25.5.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:d87c0a1bd809d8f70f96b9b229779ec6647339830b8888a192beed33ac8d129f"}, + {file = "gevent-25.5.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b87a4b66edb3808d4d07bbdb0deed5a710cf3d3c531e082759afd283758bb649"}, + {file = "gevent-25.5.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f076779050029a82feb0cb1462021d3404d22f80fa76a181b1a7889cd4d6b519"}, + {file = "gevent-25.5.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bb673eb291c19370f69295f7a881a536451408481e2e3deec3f41dedb7c281ec"}, + {file = "gevent-25.5.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c1325ed44225c8309c0dd188bdbbbee79e1df8c11ceccac226b861c7d52e4837"}, + {file = "gevent-25.5.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:fcd5bcad3102bde686d0adcc341fade6245186050ce14386d547ccab4bd54310"}, + {file = "gevent-25.5.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1a93062609e8fa67ec97cd5fb9206886774b2a09b24887f40148c9c37e6fb71c"}, + {file = "gevent-25.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:2534c23dc32bed62b659ed4fd9e198906179e68b26c9276a897e04163bdde806"}, + {file = "gevent-25.5.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:a022a9de9275ce0b390b7315595454258c525dc8287a03f1a6cacc5878ab7cbc"}, + {file = "gevent-25.5.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3fae8533f9d0ef3348a1f503edcfb531ef7a0236b57da1e24339aceb0ce52922"}, + {file = "gevent-25.5.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c7b32d9c3b5294b39ea9060e20c582e49e1ec81edbfeae6cf05f8ad0829cb13d"}, + {file = "gevent-25.5.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7b95815fe44f318ebbfd733b6428b4cb18cc5e68f1c40e8501dd69cc1f42a83d"}, + {file = "gevent-25.5.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d316529b70d325b183b2f3f5cde958911ff7be12eb2b532b5c301f915dbbf1e"}, + {file = "gevent-25.5.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f6ba33c13db91ffdbb489a4f3d177a261ea1843923e1d68a5636c53fe98fa5ce"}, + {file = "gevent-25.5.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:37ee34b77c7553777c0b8379915f75934c3f9c8cd32f7cd098ea43c9323c2276"}, + {file = "gevent-25.5.1-cp313-cp313-win_amd64.whl", hash = "sha256:9fa6aa0da224ed807d3b76cdb4ee8b54d4d4d5e018aed2478098e685baae7896"}, + {file = "gevent-25.5.1-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:0bacf89a65489d26c7087669af89938d5bfd9f7afb12a07b57855b9fad6ccbd0"}, + {file = "gevent-25.5.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e30169ef9cc0a57930bfd8fe14d86bc9d39fb96d278e3891e85cbe7b46058a97"}, + {file = "gevent-25.5.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:e72ad5f8d9c92df017fb91a1f6a438cfb63b0eff4b40904ff81b40cb8150078c"}, + {file = "gevent-25.5.1-cp39-cp39-win32.whl", hash = "sha256:e5f358e81e27b1a7f2fb2f5219794e13ab5f59ce05571aa3877cfac63adb97db"}, + {file = "gevent-25.5.1-cp39-cp39-win_amd64.whl", hash = "sha256:b83aff2441c7d4ee93e519989713b7c2607d4510abe990cd1d04f641bc6c03af"}, + {file = "gevent-25.5.1-pp310-pypy310_pp73-macosx_11_0_universal2.whl", hash = "sha256:60ad4ca9ca2c4cc8201b607c229cd17af749831e371d006d8a91303bb5568eb1"}, + {file = "gevent-25.5.1.tar.gz", hash = "sha256:582c948fa9a23188b890d0bc130734a506d039a2e5ad87dae276a456cc683e61"}, +] + +[package.dependencies] +cffi = {version = ">=1.17.1", markers = "platform_python_implementation == \"CPython\" and sys_platform == \"win32\""} +greenlet = {version = ">=3.2.2", markers = "platform_python_implementation == \"CPython\""} +"zope.event" = "*" +"zope.interface" = "*" + +[package.extras] +dnspython = ["dnspython (>=1.16.0,<2.0)", "idna"] +docs = ["furo", "repoze.sphinx.autointerface", "sphinx", "sphinxcontrib-programoutput", "zope.schema"] +monitor = ["psutil (>=5.7.0)"] +recommended = ["cffi (>=1.17.1)", "dnspython (>=1.16.0,<2.0)", "idna", "psutil (>=5.7.0)"] +test = ["cffi (>=1.17.1)", "coverage (>=5.0)", "dnspython (>=1.16.0,<2.0)", "idna", "objgraph", "psutil (>=5.7.0)", "requests"] + +[package.source] +type = "legacy" +url = "http://mirrors.aliyun.com/pypi/simple" +reference = "ali-mirrors" + [[package]] name = "greenlet" version = "3.2.2" @@ -826,6 +892,32 @@ type = "legacy" url = "http://mirrors.aliyun.com/pypi/simple" reference = "ali-mirrors" +[[package]] +name = "gunicorn" +version = "23.0.0" +description = "WSGI HTTP Server for UNIX" +optional = false +python-versions = ">=3.7" +files = [ + {file = "gunicorn-23.0.0-py3-none-any.whl", hash = "sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d"}, + {file = "gunicorn-23.0.0.tar.gz", hash = "sha256:f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec"}, +] + +[package.dependencies] +packaging = "*" + +[package.extras] +eventlet = ["eventlet (>=0.24.1,!=0.36.0)"] +gevent = ["gevent (>=1.4.0)"] +setproctitle = ["setproctitle"] +testing = ["coverage", "eventlet", "gevent", "pytest", "pytest-cov"] +tornado = ["tornado (>=0.2)"] + +[package.source] +type = "legacy" +url = "http://mirrors.aliyun.com/pypi/simple" +reference = "ali-mirrors" + [[package]] name = "h11" version = "0.16.0" @@ -2412,6 +2504,31 @@ type = "legacy" url = "http://mirrors.aliyun.com/pypi/simple" reference = "ali-mirrors" +[[package]] +name = "setuptools" +version = "80.9.0" +description = "Easily download, build, install, upgrade, and uninstall Python packages" +optional = false +python-versions = ">=3.9" +files = [ + {file = "setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922"}, + {file = "setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c"}, +] + +[package.extras] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)", "ruff (>=0.8.0)"] +core = ["importlib_metadata (>=6)", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"] +cover = ["pytest-cov"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"] +enabler = ["pytest-enabler (>=2.2)"] +test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.7.2)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] +type = ["importlib_metadata (>=7.0.2)", "jaraco.develop (>=7.21)", "mypy (==1.14.*)", "pytest-mypy"] + +[package.source] +type = "legacy" +url = "http://mirrors.aliyun.com/pypi/simple" +reference = "ali-mirrors" + [[package]] name = "six" version = "1.17.0" @@ -2912,6 +3029,88 @@ type = "legacy" url = "http://mirrors.aliyun.com/pypi/simple" reference = "ali-mirrors" +[[package]] +name = "zope-event" +version = "5.0" +description = "Very basic event publishing system" +optional = false +python-versions = ">=3.7" +files = [ + {file = "zope.event-5.0-py3-none-any.whl", hash = "sha256:2832e95014f4db26c47a13fdaef84cef2f4df37e66b59d8f1f4a8f319a632c26"}, + {file = "zope.event-5.0.tar.gz", hash = "sha256:bac440d8d9891b4068e2b5a2c5e2c9765a9df762944bda6955f96bb9b91e67cd"}, +] + +[package.dependencies] +setuptools = "*" + +[package.extras] +docs = ["Sphinx"] +test = ["zope.testrunner"] + +[package.source] +type = "legacy" +url = "http://mirrors.aliyun.com/pypi/simple" +reference = "ali-mirrors" + +[[package]] +name = "zope-interface" +version = "7.2" +description = "Interfaces for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "zope.interface-7.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ce290e62229964715f1011c3dbeab7a4a1e4971fd6f31324c4519464473ef9f2"}, + {file = "zope.interface-7.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:05b910a5afe03256b58ab2ba6288960a2892dfeef01336dc4be6f1b9ed02ab0a"}, + {file = "zope.interface-7.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:550f1c6588ecc368c9ce13c44a49b8d6b6f3ca7588873c679bd8fd88a1b557b6"}, + {file = "zope.interface-7.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0ef9e2f865721553c6f22a9ff97da0f0216c074bd02b25cf0d3af60ea4d6931d"}, + {file = "zope.interface-7.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:27f926f0dcb058211a3bb3e0e501c69759613b17a553788b2caeb991bed3b61d"}, + {file = "zope.interface-7.2-cp310-cp310-win_amd64.whl", hash = "sha256:144964649eba4c5e4410bb0ee290d338e78f179cdbfd15813de1a664e7649b3b"}, + {file = "zope.interface-7.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1909f52a00c8c3dcab6c4fad5d13de2285a4b3c7be063b239b8dc15ddfb73bd2"}, + {file = "zope.interface-7.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:80ecf2451596f19fd607bb09953f426588fc1e79e93f5968ecf3367550396b22"}, + {file = "zope.interface-7.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:033b3923b63474800b04cba480b70f6e6243a62208071fc148354f3f89cc01b7"}, + {file = "zope.interface-7.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a102424e28c6b47c67923a1f337ede4a4c2bba3965b01cf707978a801fc7442c"}, + {file = "zope.interface-7.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:25e6a61dcb184453bb00eafa733169ab6d903e46f5c2ace4ad275386f9ab327a"}, + {file = "zope.interface-7.2-cp311-cp311-win_amd64.whl", hash = "sha256:3f6771d1647b1fc543d37640b45c06b34832a943c80d1db214a37c31161a93f1"}, + {file = "zope.interface-7.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:086ee2f51eaef1e4a52bd7d3111a0404081dadae87f84c0ad4ce2649d4f708b7"}, + {file = "zope.interface-7.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:21328fcc9d5b80768bf051faa35ab98fb979080c18e6f84ab3f27ce703bce465"}, + {file = "zope.interface-7.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f6dd02ec01f4468da0f234da9d9c8545c5412fef80bc590cc51d8dd084138a89"}, + {file = "zope.interface-7.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8e7da17f53e25d1a3bde5da4601e026adc9e8071f9f6f936d0fe3fe84ace6d54"}, + {file = "zope.interface-7.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cab15ff4832580aa440dc9790b8a6128abd0b88b7ee4dd56abacbc52f212209d"}, + {file = "zope.interface-7.2-cp312-cp312-win_amd64.whl", hash = "sha256:29caad142a2355ce7cfea48725aa8bcf0067e2b5cc63fcf5cd9f97ad12d6afb5"}, + {file = "zope.interface-7.2-cp313-cp313-macosx_10_9_x86_64.whl", hash = "sha256:3e0350b51e88658d5ad126c6a57502b19d5f559f6cb0a628e3dc90442b53dd98"}, + {file = "zope.interface-7.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:15398c000c094b8855d7d74f4fdc9e73aa02d4d0d5c775acdef98cdb1119768d"}, + {file = "zope.interface-7.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:802176a9f99bd8cc276dcd3b8512808716492f6f557c11196d42e26c01a69a4c"}, + {file = "zope.interface-7.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb23f58a446a7f09db85eda09521a498e109f137b85fb278edb2e34841055398"}, + {file = "zope.interface-7.2-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a71a5b541078d0ebe373a81a3b7e71432c61d12e660f1d67896ca62d9628045b"}, + {file = "zope.interface-7.2-cp313-cp313-win_amd64.whl", hash = "sha256:4893395d5dd2ba655c38ceb13014fd65667740f09fa5bb01caa1e6284e48c0cd"}, + {file = "zope.interface-7.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d3a8ffec2a50d8ec470143ea3d15c0c52d73df882eef92de7537e8ce13475e8a"}, + {file = "zope.interface-7.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:31d06db13a30303c08d61d5fb32154be51dfcbdb8438d2374ae27b4e069aac40"}, + {file = "zope.interface-7.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e204937f67b28d2dca73ca936d3039a144a081fc47a07598d44854ea2a106239"}, + {file = "zope.interface-7.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:224b7b0314f919e751f2bca17d15aad00ddbb1eadf1cb0190fa8175edb7ede62"}, + {file = "zope.interface-7.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baf95683cde5bc7d0e12d8e7588a3eb754d7c4fa714548adcd96bdf90169f021"}, + {file = "zope.interface-7.2-cp38-cp38-win_amd64.whl", hash = "sha256:7dc5016e0133c1a1ec212fc87a4f7e7e562054549a99c73c8896fa3a9e80cbc7"}, + {file = "zope.interface-7.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7bd449c306ba006c65799ea7912adbbfed071089461a19091a228998b82b1fdb"}, + {file = "zope.interface-7.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a19a6cc9c6ce4b1e7e3d319a473cf0ee989cbbe2b39201d7c19e214d2dfb80c7"}, + {file = "zope.interface-7.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:72cd1790b48c16db85d51fbbd12d20949d7339ad84fd971427cf00d990c1f137"}, + {file = "zope.interface-7.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:52e446f9955195440e787596dccd1411f543743c359eeb26e9b2c02b077b0519"}, + {file = "zope.interface-7.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ad9913fd858274db8dd867012ebe544ef18d218f6f7d1e3c3e6d98000f14b75"}, + {file = "zope.interface-7.2-cp39-cp39-win_amd64.whl", hash = "sha256:1090c60116b3da3bfdd0c03406e2f14a1ff53e5771aebe33fec1edc0a350175d"}, + {file = "zope.interface-7.2.tar.gz", hash = "sha256:8b49f1a3d1ee4cdaf5b32d2e738362c7f5e40ac8b46dd7d1a65e82a4872728fe"}, +] + +[package.dependencies] +setuptools = "*" + +[package.extras] +docs = ["Sphinx", "furo", "repoze.sphinx.autointerface"] +test = ["coverage[toml]", "zope.event", "zope.testing"] +testing = ["coverage[toml]", "zope.event", "zope.testing"] + +[package.source] +type = "legacy" +url = "http://mirrors.aliyun.com/pypi/simple" +reference = "ali-mirrors" + [[package]] name = "zstandard" version = "0.23.0" @@ -3032,4 +3231,4 @@ reference = "ali-mirrors" [metadata] lock-version = "2.0" python-versions = ">=3.11,<3.13" -content-hash = "9c8317a7a8b141b864d0bbac4c482445339c928cb9cc55bc7a5a86f63b7e39fd" +content-hash = "2b34e52cba181483fd7ab37d7d9ed92b4703aecc604befbd150d831e84de535d" diff --git a/pyproject.toml b/pyproject.toml index 745659e..b4c0db9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,6 +22,8 @@ tqdm = "^4.67.1" xlsxwriter = "^3.2.3" flask = "^3.1.1" psycopg2 = "^2.9.10" +gunicorn = "^23.0.0" +gevent = "^25.5.1" [build-system] requires = ["poetry-core"] build-backend = "poetry.core.masonry.api" diff --git a/rag2_0/dify/chat_dify_by_workorder.py b/rag2_0/dify/chat_dify_by_workorder.py new file mode 100644 index 0000000..8a6d445 --- /dev/null +++ b/rag2_0/dify/chat_dify_by_workorder.py @@ -0,0 +1,151 @@ +from rag2_0.dify.workflow_chat import NewWorkflowChat +import pandas as pd +from concurrent.futures import ThreadPoolExecutor +from tqdm import tqdm +import concurrent.futures + + +class ChatDifyByWorkorder: + + def __init__(self, api_key=None, base_url="https://api.dify.ai/v1") -> None: + """ + 初始化ChatDifyByWorkorder类 + + Args: + api_key: Dify API密钥,默认为None + base_url: Dify API的基础URL,默认为"https://api.dify.ai/v1" + """ + baseurl = "http://172.20.0.145/v1" + new_workflow_api_key = "app-qxsSybCs7ABiKlC1JabTYVn6" + self.new_chat = NewWorkflowChat(api_key=new_workflow_api_key, base_url=baseurl) + self.new_chat_answer = NewWorkflowChat(api_key=new_workflow_api_key, base_url=baseurl) + + + def get_soft_name(self, row) -> str: + if "博微配网计价通D3" in row["产品线"]: + return "博微配网计价通D3" + elif "博微电力建设计价通软件" in row["产品线"]: + return "电力建设计价通软件" + elif "新能源系列" in row["产品线"] and "博微新型储能电站建设计价通C1软件" in row["产品名称"]: + return "储能C1软件" + elif "博微西藏计价通Z1" in row["产品线"]: + return "西藏计价通Z1" + elif "博微技改检修计价通T1软件" in row["产品线"] and "技改检修计价通T1软件-概预算" in row["产品名称"]: + return "技改检修工程计价通T1" + elif "博微技改检修计价通T1软件" in row["产品线"] and "技改检修计价通T1软件-清单" in row["产品名称"]: + return "检修清单计价通T1" + return "" + + def process_query(self, q:str) -> dict: + """ + 发送问题并获取回答及相关工作流信息 + + Args: + q: 用户问题 + + Returns: + dict: 包含问题、回答和工作流信息的字典 + """ + retry_count = 0 + max_retries = 2 + + while retry_count <= max_retries: + try: + # 发送问题获取回答和消息ID + result = self.new_chat.process_question(q) + return result + except Exception as e: + retry_count += 1 + if retry_count <= max_retries: + continue + else: + raise e + + def process_answer(self, q:str) -> dict: + """ + 发送问题并获取回答及相关工作流信息 + + Args: + q: 用户问题 + + Returns: + dict: 包含问题、回答和工作流信息的字典 + """ + retry_count = 0 + max_retries = 2 + + while retry_count <= max_retries: + try: + # 发送问题获取回答和消息ID + result = self.new_chat_answer.process_question(q) + return result + except Exception as e: + retry_count += 1 + if retry_count <= max_retries: + continue + else: + raise + + def process_row(self, row): + """处理单行数据""" + soft_name = self.get_soft_name(row=row) + if soft_name == "": + return None + + # 使用线程池并发执行查询 + with ThreadPoolExecutor() as executor: + try: + # 提交两个任务并获取Future对象 + query_future = executor.submit(self.process_query, q=f"{soft_name},{row['客户问题']}") + answer_future = executor.submit(self.process_answer, q=f"{soft_name},{row['解决方案']}") + + # 获取结果 + query_result = query_future.result() + answer_result = answer_future.result() + except Exception as e: + print(f"处理工单 {row.get('工单编号', '未知')} 时发生错误: {str(e)}") + return None + + worker_id = str(row["工单编号"]) + if query_result is None or answer_result is None: + print("处理对话出现错误") + return None + + worker_order_info = { + "工单编号": worker_id, + "用户问题": row['客户问题'], + "解决方案": row['解决方案'], + "AI回答": query_result["新流程答案"], + "用户问题检索到的词条": query_result["新检索词条"], + "解决方案检索到的词条": answer_result["新检索词条"], + } + return worker_order_info + + def run(self, excel_path:str): + df_data = pd.read_excel(excel_path) + list_worker_order_info = [] + + # 创建进度条 + with tqdm(total=len(df_data), desc="处理工单") as pbar: + # 创建线程池,最大并发数可以根据需要调整 + with ThreadPoolExecutor(max_workers=5) as executor: + # 提交所有任务 + future_to_row = {executor.submit(self.process_row, row): idx for idx, row in df_data.iterrows()} + + # 处理完成的任务 + for future in concurrent.futures.as_completed(future_to_row): + result = future.result() + if result is not None: + list_worker_order_info.append(result) + pbar.update(1) + + return list_worker_order_info + + + +if __name__=="__main__": + worker_chat = ChatDifyByWorkorder() + result = worker_chat.run(excel_path="data/excel/工单记录_均衡提取2000条.xlsx") + # 可以选择保存结果到Excel + if result: + pd.DataFrame(result).to_excel("data/excel/工单处理结果.xlsx", index=False) \ No newline at end of file diff --git a/rag2_0/dify/dify_tool.py b/rag2_0/dify/dify_tool.py index 1210be7..2dd2130 100644 --- a/rag2_0/dify/dify_tool.py +++ b/rag2_0/dify/dify_tool.py @@ -23,7 +23,7 @@ class PgSql: 连接到 PostgreSQL 数据库。 使用预定义的凭据连接到 'dify' 数据库。 - 如果连接失败,会打印错误信息。 + 如果连接失败,会抛出异常。 """ try: # 连接数据库 @@ -36,17 +36,16 @@ class PgSql: ) except (Exception, psycopg2.Error) as error: - print("Error while connecting to PostgreSQL", error) + raise Exception(f"Error while connecting to PostgreSQL: {error}") def close_connection(self): """ 关闭当前的 PostgreSQL 数据库连接。 - 如果存在活动的连接,则关闭它并打印确认信息。 + 如果存在活动的连接,则关闭它。 """ if self.connection: self.connection.close() - print("PostgreSQL connection is closed") def get_appinfo(self, appid:str)->dict | None: @@ -74,7 +73,7 @@ class PgSql: return dict(zip(colnames, result)) return None except (Exception, psycopg2.Error) as error: - print("Error while getting tenant_id by appid", error) + raise Exception(f"Error while getting tenant_id by appid: {error}") def get_messages_info(self, appid:str, query:str)->dict | None: @@ -103,7 +102,7 @@ class PgSql: return dict(zip(colnames, result)) return None except (Exception, psycopg2.Error) as error: - print("Error while getting messages_info", error) + raise Exception(f"Error while getting messages_info: {error}") def get_messages_info_by_id(self, message_id:str)->dict | None: """ @@ -123,7 +122,7 @@ class PgSql: return dict(zip(colnames, result)) return None except (Exception, psycopg2.Error) as error: - print("Error while getting messages_info", error) + raise Exception(f"Error while getting messages_info by id: {error}") def get_workflow_node_executions_info(self, workflow_run_id:str)->list[dict] | None: """ @@ -150,7 +149,7 @@ class PgSql: return [dict(zip(colnames, row)) for row in result] return None except (Exception, psycopg2.Error) as error: - print("Error while getting workflow_node_executions_info", error) + raise Exception(f"Error while getting workflow_node_executions_info: {error}") class DifyTool: """ @@ -165,16 +164,21 @@ class DifyTool: 根据消息 ID 从 'messages' 表中获取消息信息。 """ dify_pgsql = PgSql() - messages_info = dify_pgsql.get_messages_info_by_id(message_id) - if not messages_info: - return None - workflow_node_executions_info = dify_pgsql.get_workflow_node_executions_info(messages_info['workflow_run_id']) - if not workflow_node_executions_info: - return None - return { - "messages_info": messages_info, - "workflow_node_executions_info": workflow_node_executions_info - } + try: + messages_info = dify_pgsql.get_messages_info_by_id(message_id) + if not messages_info: + return None + workflow_node_executions_info = dify_pgsql.get_workflow_node_executions_info(messages_info['workflow_run_id']) + if not workflow_node_executions_info: + return None + return { + "messages_info": messages_info, + "workflow_node_executions_info": workflow_node_executions_info + } + except Exception as e: + raise Exception(f"Error in get_message_debug_info_by_id: {e}") + finally: + dify_pgsql.close_connection() @staticmethod @@ -195,21 +199,30 @@ class DifyTool: 查询到的应用数据、消息数据和节点执行数据。 """ dify_pgsql = PgSql() - appinfo = dify_pgsql.get_appinfo(appid) - if not appinfo: - return None - messages_info = dify_pgsql.get_messages_info(appid, query) - if not messages_info: - return None - workflow_node_executions_info = dify_pgsql.get_workflow_node_executions_info(messages_info['workflow_run_id']) - if not workflow_node_executions_info: - return None - return { - "appinfo": appinfo, - "messages_info": messages_info, - "workflow_node_executions_info": workflow_node_executions_info - } + try: + appinfo = dify_pgsql.get_appinfo(appid) + if not appinfo: + return None + messages_info = dify_pgsql.get_messages_info(appid, query) + if not messages_info: + return None + workflow_node_executions_info = dify_pgsql.get_workflow_node_executions_info(messages_info['workflow_run_id']) + if not workflow_node_executions_info: + return None + return { + "appinfo": appinfo, + "messages_info": messages_info, + "workflow_node_executions_info": workflow_node_executions_info + } + except Exception as e: + raise Exception(f"Error in get_message_debug_info_by_query: {e}") + finally: + dify_pgsql.close_connection() if __name__ == "__main__": - print(DifyTool.get_message_debug_info_by_query("ccf92b97-2789-4a3f-90e0-135a869a37c5", "电力建设计价通软件,导入结算后没有暂列金怎么办?要手动添加么?")) + try: + result = DifyTool.get_message_debug_info_by_query("ccf92b97-2789-4a3f-90e0-135a869a37c5", "电力建设计价通软件,导入结算后没有暂列金怎么办?要手动添加么?") + print(result) + except Exception as e: + print(f"执行出错: {e}") diff --git a/rag2_0/dify/intent_recognition_api.py b/rag2_0/dify/intent_recognition_api.py index 02e1278..daef05b 100644 --- a/rag2_0/dify/intent_recognition_api.py +++ b/rag2_0/dify/intent_recognition_api.py @@ -4,16 +4,32 @@ from dotenv import load_dotenv from rag2_0.intent_recognition import IntentRecognizer import json import time +import threading +import datetime + # 加载环境变量 load_dotenv() app = Flask(__name__) -# 初始化意图识别器 -api_key = os.getenv("OPENAI_API_KEY") -base_url = os.getenv("OPENAI_API_BASE") -model_name = os.getenv("LLM_MODEL_NAME", "gpt-3.5-turbo") -recognizer = IntentRecognizer(api_key=api_key, base_url=base_url, model_name=model_name) +# 创建线程锁,用于保护共享资源 +recognizer_lock = threading.Lock() + +# 使用单例模式创建意图识别器 +class RecognizerSingleton: + _instance = None + _lock = threading.Lock() + + @classmethod + def get_instance(cls): + if cls._instance is None: + with cls._lock: + if cls._instance is None: + api_key = os.getenv("OPENAI_API_KEY") + base_url = os.getenv("OPENAI_API_BASE") + model_name = os.getenv("LLM_MODEL_NAME", "gpt-3.5-turbo") + cls._instance = IntentRecognizer(api_key=api_key, base_url=base_url, model_name=model_name) + return cls._instance @app.route('/intent_recognize', methods=['POST']) def intent_recognize(): @@ -22,10 +38,16 @@ def intent_recognize(): query = data.get('query') if not query: return Response(json.dumps({"error": "缺少query参数"}, ensure_ascii=False), content_type='application/json; charset=utf-8', status=400) + start_time = time.time() + + # 获取单例实例并使用线程锁保护关键操作 + recognizer = RecognizerSingleton.get_instance() result = recognizer.process_query_with_slots(query) + end_time = time.time() - print(f"意图识别耗时: {end_time - start_time:.2f}秒") + current_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S %z") + print(f"[{current_time}] [{os.getpid()}] [INFO] 意图识别耗时: {end_time - start_time:.2f}秒") # 提取分类信息 classification = result["classification"] @@ -63,7 +85,10 @@ def intent_recognize(): } return Response(json.dumps(response_result, ensure_ascii=False), content_type='application/json; charset=utf-8') except Exception as e: + print(f"意图识别出错: {str(e)}") return Response(json.dumps({"error": str(e)}, ensure_ascii=False), content_type='application/json; charset=utf-8', status=500) if __name__ == "__main__": - app.run(host="0.0.0.0", port=8001) \ No newline at end of file + # 开发环境使用Flask内置服务器 + # 生产环境使用gunicorn支持高并发 poetry run gunicorn -w 10 -k gevent -b 0.0.0.0:8001 rag2_0.dify.intent_recognition_api:app + app.run(host="0.0.0.0", port=8001, threaded=True) \ No newline at end of file diff --git a/rag2_0/dify/workflow_chat.py b/rag2_0/dify/workflow_chat.py new file mode 100644 index 0000000..fce903a --- /dev/null +++ b/rag2_0/dify/workflow_chat.py @@ -0,0 +1,310 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import os +import json +from concurrent.futures import ThreadPoolExecutor, as_completed +from rag2_0.dify.dify_client import ChatClient, DifyClient +from rag2_0.dify.dify_tool import DifyTool +from pydantic import BaseModel, Field +from langchain.output_parsers import PydanticOutputParser +from threading import Lock + +class ContentSource(BaseModel): + score: int = Field(description="相关性分数") + reason: str = Field(description="评分理由") + +class BaseWorkflowChat: + """ + 工作流对话基类,封装了与Dify API交互的基本功能 + """ + def __init__(self, api_key: str, base_url: str): + """ + 初始化工作流对话基类 + + Args: + api_key: Dify API的密钥 + base_url: Dify API的基础URL + """ + self.chat_client = ChatClient(api_key=api_key, base_url=base_url) + self.content_source_parser = PydanticOutputParser(pydantic_object=ContentSource) + + def create_chat_message(self, query: str): + """ + 创建聊天消息 + + Args: + query: 问题内容 + + Returns: + tuple: (聊天响应, 消息ID) + """ + try: + response = self.chat_client.create_chat_message(inputs={}, query=query, user="AutoTestDifyChat").json() + return response, response["message_id"] + except Exception as e: + raise e + + def calculate_score(self, query: str, content: str) -> int: + """ + 使用LLM判断query与content之间的相关性分数 + + Args: + query (str): 用户问题 + content (str): 检索内容 + + Returns: + int: 相关性分数,1-10分,10代表完全相关,1代表完全不相关;-1表示评分失败 + """ + from rag2_0.tool.ModelTool import OpenAiLLM + + try: + prompt = f"""你是一个专业的信息相关性评估助手。请根据以下标准对用户query和检索内容的相关性进行1-10评分(10=完全相关,1=完全不相关),并按指定格式输出JSON结果。 + +【评分标准】 +10分:完全契合,主题/意图完全一致且涵盖所有关键信息 +8-9分:高度相关,核心要素匹配但存在少量信息缺失 +6-7分:部分相关,涉及相同主题但存在重要信息缺失 +4-5分:弱相关,仅次要信息点匹配 +1-3分:完全不相关或信息冲突 + +【评估维度】 +1. 主题一致性:核心主题/意图的匹配程度 +2. 内容覆盖度:是否涵盖query的关键要素 +3. 信息准确性:是否存在矛盾/错误信息 +4. 细节丰富度:是否提供query要求的详细信息 + +【输出格式】 +{{ + "score": 评分, + "reason": "简明扼要的评分理由(中文)" +}} + +【示例】 +query: "新冠疫苗的常见副作用" +内容: "辉瑞疫苗常见反应包括注射部位疼痛(84.1%)、疲劳(62.9%)" +输出: {{"score":8,"reason":"主题完全匹配,涵盖主要副作用但未提及发热等常见反应"}} + +现在评估: +query: "{query}" +content: "{content}" +""" + api_key = os.getenv("OPENAI_API_KEY") + base_url = os.getenv("OPENAI_API_BASE") + model = os.getenv("LLM_MODEL_NAME") + llm = OpenAiLLM(api_key=api_key, base_url=base_url, model=model) + response = llm.invoke(user_prompt=prompt, need_retry=True) + + # 解析JSON响应 + try: + parsed_output = self.content_source_parser.parse(response.content) + return parsed_output.score + except Exception as e: + return -1 + except Exception as e: + return -1 + + def get_retrieve_info(self, query: str, outputs: dict) -> tuple: + """ + 获取检索信息并计算分数 + + Args: + query (str): 用户问题 + outputs (dict): 检索输出结果 + + Returns: + tuple: (检索内容列表, 最高分, 最低分, 平均分) + """ + max_score = 0 + min_score = 10 + total_score = 0 + valid_scores = 0 + retrieve_content = [] + + # 使用线程池并发计算分数 + with ThreadPoolExecutor() as executor: + # 创建任务列表 + future_to_content = {} + for result in outputs["result"]: + content = result["content"].strip() + future = executor.submit(self.calculate_score, query=query, content=content) + future_to_content[future] = content + + # 收集结果 + for future in as_completed(future_to_content): + content = future_to_content[future] + score = future.result() + content_title = content.split("\n")[0] + + if score != -1: + max_score = max(max_score, score) + min_score = min(min_score, score) + total_score += score + valid_scores += 1 + + if content_title: + retrieve_content.append(content_title + f"--相关性得分({score}分)") + + avg_score = total_score / valid_scores if valid_scores > 0 else 0 + return retrieve_content, max_score, min_score, avg_score + + +class NewWorkflowChat(BaseWorkflowChat): + """ + 新工作流对话类,用于调用新工作流发送对话并解析获取相关数据 + """ + def process_question(self, query: str) -> dict: + """ + 处理问题,获取新工作流的回答和相关信息 + + Args: + query: 问题内容 + + Returns: + dict: 包含问题、回答和相关信息的字典 + """ + response, message_id = self.create_chat_message(query) + + if isinstance(response, str) and response.startswith("error:"): + raise RuntimeError(f"create_chat_message 出错:{response}") + + answer = response["answer"] + workflow_info = self.get_workflow_info(query, message_id) + + if workflow_info is None: + return None + + result = { + "问题": query, + "新流程答案": answer, + "新问题改写": workflow_info["问题改写"], + "新问题分类": workflow_info["问题分类"], + "槽点信息": workflow_info["槽点信息"], + "新检索词条": workflow_info["检索词条"], + "检索内容": workflow_info["检索内容"], + "message_id":message_id + } + + return result + + def get_workflow_info(self, query: str, message_id: str) -> dict: + """ + 获取新工作流的问题分类和检索信息 + + Args: + query (str): 用户问题 + message_id (str): 新工作流的消息ID + + Returns: + dict: 包含问题分类结果的字典 + """ + retrieve_title = [] + retrieve_content = [] + max_score = 0 + min_score = 0 + avg_score = 0 + rewrite_query = "" + vertical_classification = "" + sub_classification = "" + slot_info = "" + + try: + message_info = DifyTool.get_message_debug_info_by_id(message_id=message_id) + for workflow_node in message_info["workflow_node_executions_info"]: + if workflow_node["title"] == "知识检索结果后处理": + outputs = json.loads(workflow_node["outputs"]) + retrieve_title, max_score, min_score, avg_score = self.get_retrieve_info(query=query, outputs=outputs) + retrieve_content = outputs["result"] + elif workflow_node["title"] == "问题优化结果解析": + outputs = json.loads(workflow_node["outputs"]) + rewrite_query = outputs["optimize_query"] + llm_result_json = json.loads(workflow_node['inputs'])["llm_result"] + json_result = json.loads(llm_result_json) + vertical_classification = json_result['vertical_classification'] + sub_classification = json_result['sub_classification'] + slot_info = json.dumps(json_result["slot_filling"], ensure_ascii=False, indent=2) + except Exception as e: + raise e + + return { + "问题改写": rewrite_query, + "检索词条": "\n".join(retrieve_title) if retrieve_title else "未检索知识库", + "检索内容": retrieve_content, + "问题分类": f"{vertical_classification} - {sub_classification}", + "槽点信息": slot_info, + + } + + +class OldWorkFlowChat(BaseWorkflowChat): + """ + 旧工作流对话类,用于调用旧工作流发送对话并解析获取相关数据 + """ + def process_question(self, query: str) -> dict: + """ + 处理问题,获取旧工作流的回答和相关信息 + + Args: + query: 问题内容 + + Returns: + dict: 包含问题、回答和相关信息的字典 + """ + response, message_id = self.create_chat_message(query) + + if isinstance(response, str) and response.startswith("error:"): + return None + + answer = response["answer"] + workflow_info = self.get_workflow_info(query, message_id) + + if workflow_info is None: + return None + + result = { + "问题": query, + "旧流程答案": answer, + "旧问题改写": workflow_info["问题改写"], + "旧检索词条": workflow_info["检索词条"], + "检索内容": workflow_info["检索内容"], + "message_id":message_id + } + + return result + + def get_workflow_info(self, query: str, message_id: str) -> dict: + """ + 获取旧工作流的问题改写和检索信息 + + Args: + query (str): 用户问题 + message_id (str): 旧工作流的消息ID + + Returns: + dict: 包含问题改写和检索信息的字典 + """ + retrieve_title = [] + retrieve_content = [] + max_score = 0 + min_score = 0 + avg_score = 0 + rewrite_query = "" + + try: + message_info = DifyTool.get_message_debug_info_by_id(message_id=message_id) + for workflow_node in message_info["workflow_node_executions_info"]: + if workflow_node["title"] == "知识检索结果后处理": + outputs = json.loads(workflow_node["outputs"]) + retrieve_title, max_score, min_score, avg_score = self.get_retrieve_info(query=query, outputs=outputs) + retrieve_content = outputs["result"] + elif workflow_node["title"] == "问题优化结果解析": + outputs = json.loads(workflow_node["outputs"]) + rewrite_query = outputs["optimize_query"] + except Exception as e: + return None + + return { + "问题改写": rewrite_query, + "检索词条": "\n".join(retrieve_title) if retrieve_title else "未检索知识库", + "检索内容": retrieve_content, + } \ No newline at end of file diff --git a/rag2_0/tool/APIKeyManager.py b/rag2_0/tool/APIKeyManager.py index 696cbd4..3489e33 100644 --- a/rag2_0/tool/APIKeyManager.py +++ b/rag2_0/tool/APIKeyManager.py @@ -5,6 +5,18 @@ from typing import List, Optional, Dict from threading import Lock import requests +# 4090dify 中用的硅基流动 apikey +# sk-iuyfpcewztavgnivrllwnegffvrrsyeiuwvjrabngtejsqwy +# sk-skynrwfqvipknbcvjsxkhjaqlivmocpkdppkocjndbyulado +# sk-gjeanmnxtfcqezqagixyexarlxeztkazbrsciqescrfxrgpw +# sk-uapywdmjaylwwyufaivraqwbpqtxjpbsbkltlrmwqftvfech +# sk-dwmxnhaeephbxgsfncbonyajubuhuyhsfqwfsxahlepkiwas +# sk-lnxedlpzufrurrmvylugpccnppwyqdccgeiicoijrqnslcgm +# sk-duccaryfxcrpvwrbwvjbuwjwazyqleyebumhvrutksuqbxug +# sk-njcrhxpvevtxkzbmhkxshxcpwpnjzmccjgfdykdncaxjicez +# sk-bdagppigfxexcofiossccywvcqggbpywjapkdbtqycbgvqpz +# sk-dvbaktabkdwdpjgxyoozlwnejosjyhdgqwllfeborqahndxs + API_KEY_LIST=[ "sk-hrojkkkrrkmsajtnizokbcgexsfggdiqavbtvbayuwqbnmom", "sk-kkdklmnyompoiotzkfqahpayzlkgogfudjkyaebehtsowvid", @@ -56,6 +68,36 @@ API_KEY_LIST=[ "sk-zjwbwyocnuqxfshlpgfzdwlgjjrpewzgvoqwzyhufisidnos", "sk-kjxpzjbteiurpzhwjbbjqpjjfoewsahpjtmyqwectdubxhgf", "sk-sqdcnhapyzudneipdsuqlfawusrndxqkuwoaoumtonwdnppo", +"sk-yvyvoiegjrdlgihnxlaaznzdhnvpmfowtwmofomcodaoeaqs", +"sk-vccwuaomxhjcszjhheipoqqmsnuetasiveombkyrptstesbi", +"sk-mxbapcczwjsyrictwgigxgcvdgptyfrlynrewqioegqwrggv", +"sk-dujjzxrknevesbagqgqmuffxsosjoueviubnmodoormlmlzt", +"sk-rpptsvdeifcnyfkkrwnphgkrlchrqrbkglxrmztdzvfutdor", +"sk-lsukfggzghmdhtfhqcbmlfqabbtapwpuxnvtwshqqqlaesie", +"sk-aulumxzhvaladchcwgmsxidtdsvzytbpvzqgfuvcxlwbwcgl", +"sk-tzdqzroakecvseclcmrbhdnepveatybhhzxfpzxzgirpqcdy", +"sk-otxxemniwhxkdvroszmmkitswwuykosnqoldrkzdoflqpgvw", +"sk-zlruqobfdbjebyyvkmehakpcvfgnlfbdlbfrepusazzckbnv", +"sk-zryimztrlkgvcaiolarhvbcewmhwruhqfcndbylonzlqvdox", +"sk-rczjqufgdisqplkrmvhaxmdgcboluvxympvzljlreuqeeviq", +"sk-xfnvcksdgwufsktvmhpqrwpgovsxxtaeehtxnaqjtxmubqzl", +"sk-gcostftlutooxzsnqefcgyfxqytidvfjpxhbuxppgatwczoq", +"sk-wwonvjnowbcxmoyoluynnkjwerghspzdulyidskunkordaft", +"sk-rbuykocecbdoqteveeggwzvrhbvisgaerffexjsnyvjefhdk", +"sk-qmrkfvvbbfssuoreyvwqawoveyowuvxviqzqknotyweqmuog", +"sk-nprpuknjmikvoaxnwgyshwwwtnifvixpuqtzkzmcacdnvoib", +"sk-xanwnicepdxfqrfejzuxjcrhdsglfypkoxlcmmtamrtjkork", +"sk-lvtdgodiaurqyiwdxtdrgxifguychhccqlqkhqctscvqbfgi", +"sk-aedlbtlmqcttxwnvlfmxzaysamocamqxjceoyqjfgpcowybw", +"sk-fahdvndjblyjlizamvwcrxnilsgmbgbvwssxgquhkezgpqne", +"sk-tzludgttzxvpvwayazdbppbauvathdtccafjrhojpemucgyi", +"sk-hrbroidbfusidwnsmxenuzljxgdzzxiimlezygxplavnxjik", +"sk-ylgoiqxmtxeojdnonthxtweungyzldaqarvjxlqyztlvyrff", +"sk-asuqbqwdhjcqnvtjlwufyrkrwkobnrbmukzarvcctsgjipdp", +"sk-dpgpymiydutoexgvkajwgahagnfmcqzafwulccudnzvleifz", +"sk-nbksjgcngsayoumnsdbkcpnqivnvxjenwpzuazzrkhnsgeoo", +"sk-iaafvpjyqiocgzchbdldbkgcffqniahkcbgoviuevuogulcm", +"sk-muvjguqeshyimzowqnqgxwpsgujlpkqgrisxsimthtyrpypx", ] class APIKeyManager: