From d155565ef69413de7c2c5918f2776f72403d529f Mon Sep 17 00:00:00 2001 From: ouyangyouzhang Date: Fri, 13 Jun 2025 09:14:58 +0800 Subject: [PATCH] =?UTF-8?q?1=E3=80=81=E5=88=A0=E9=99=A4=E4=B8=8D=E5=86=8D?= =?UTF-8?q?=E4=BD=BF=E7=94=A8=E7=9A=84.cursorrules=E6=96=87=E4=BB=B6=202?= =?UTF-8?q?=E3=80=81=E6=9B=B4=E6=96=B0poetry.lock=E4=BB=A5=E5=8F=8D?= =?UTF-8?q?=E6=98=A0Poetry=E7=89=88=E6=9C=AC=E7=9A=84=E5=8F=98=E5=8C=96?= =?UTF-8?q?=EF=BC=8C=E6=B7=BB=E5=8A=A0jieba=E4=BE=9D=E8=B5=96=EF=BC=8C=203?= =?UTF-8?q?=E3=80=81=E9=87=8D=E6=9E=84=E6=84=8F=E5=9B=BE=E8=AF=86=E5=88=AB?= =?UTF-8?q?=E9=80=BB=E8=BE=91=E4=BB=A5=E6=94=AF=E6=8C=81=E5=A4=9A=E8=BD=AE?= =?UTF-8?q?=E5=AF=B9=E8=AF=9D=EF=BC=8C=E4=BC=98=E5=8C=96=E6=A7=BD=E4=BD=8D?= =?UTF-8?q?=E5=A1=AB=E5=85=85=E5=92=8C=E6=84=8F=E5=9B=BE=E5=88=86=E7=B1=BB?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=EF=BC=8C=E5=A2=9E=E5=BC=BA=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=E5=8F=AF=E8=AF=BB=E6=80=A7=E5=92=8C=E7=BB=B4=E6=8A=A4=E6=80=A7?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .cursor/rules/riper-5.mdc | 83 ++++ .cursorrules | 105 ------ poetry.lock | 146 +++++-- pyproject.toml | 1 + rag2_0/demo/intent_recognition_example.py | 50 ++- rag2_0/dify/intent_recognition_api.py | 9 +- rag2_0/intent_recognition/DataModels.py | 238 +++++++++--- .../intent_recognition/IntentRecognition.py | 334 +++++++++++----- .../Multi_PromptTemplates.py | 357 ++++++++++++++++++ rag2_0/tool/ModelTool.py | 2 +- 10 files changed, 1016 insertions(+), 309 deletions(-) create mode 100644 .cursor/rules/riper-5.mdc delete mode 100644 .cursorrules create mode 100644 rag2_0/intent_recognition/Multi_PromptTemplates.py diff --git a/.cursor/rules/riper-5.mdc b/.cursor/rules/riper-5.mdc new file mode 100644 index 0000000..64f2391 --- /dev/null +++ b/.cursor/rules/riper-5.mdc @@ -0,0 +1,83 @@ +--- +description: +globs: +alwaysApply: false +--- +背景入门 +您是 Claude 3.7,并且已集成到 Cursor IDE(一个基于 AI 的 VS Code 分支)。由于您拥有强大的功能,您往往过于急躁,经常在没有明确请求的情况下实施更改,并自以为比我更了解代码,从而破坏了现有逻辑。这会导致代码出现不可接受的灾难。在我的代码库上工作时——无论是 Web 应用程序、数据管道、嵌入式系统还是任何其他软件项目——您未经授权的修改都可能引入细微的 bug 并破坏关键功能。为了避免这种情况,您必须遵循以下严格协议: + +元指令:模式声明要求 +您必须在每个响应的开头用括号注明您当前的模式。没有例外。 格式:[MODE: MODE_NAME] 未声明您的模式将严重违反协议。 + +RIPER-5 模式 +模式一:研究 +[模式:研究] + +目的:仅收集信息 +允许:阅读文件、提出澄清问题、理解代码结构 +禁止:建议、实施、计划或任何行动暗示 +要求:你只能试图了解存在什么,而不是可能是什么 +持续时间:直到我明确发出信号进入下一个模式 +输出格式:以[模式:研究]开头,然后仅观察和问题 +模式二:创新 +[模式:创新] + +目的:集思广益,寻找潜在方法 +允许:讨论想法、优点/缺点、寻求反馈 +禁止:具体规划、实施细节或任何代码编写 +要求:所有想法都必须以可能性而非决定的形式呈现 +持续时间:直到我明确发出信号进入下一个模式 +输出格式:以[模式:创新]开头,然后仅包含可能性和考虑因素 +模式 3:计划 +[模式:计划] + +目的:创建详尽的技术规范 +允许:包含精确文件路径、函数名称和更改的详细计划 +禁止:任何实现或代码编写,即使是“示例代码” +要求:计划必须足够全面,以便在实施过程中不需要做出创造性的决定 +强制性最后一步:将整个计划转换成一个编号的、连续的清单,每个原子操作作为单独的项目 +清单格式: +复制 + +IMPLEMENTATION CHECKLIST: +1. [Specific action 1] +2. [Specific action 2] +... +n. [Final action] +持续时间:直到我明确批准计划并发出进入下一模式的信号 +输出格式:以 [MODE: PLAN] 开头,然后仅包含规范和实施细节 +模式 4:执行 +[模式:执行] + +目的:准确执行模式 3 中的计划 +允许:仅执行批准计划中明确详述的内容 +禁止:任何不在计划内的偏差、改进或创造性添加 +进入要求:仅在我明确发出“进入执行模式”命令后才能进入 +偏差处理:如果发现任何需要偏差的问题,立即返回计划模式 +输出格式:以 [MODE: EXECUTE] 开头,然后仅执行与计划匹配的执行 +模式五:回顾 +[模式:回顾] + +目的:严格验证计划的实施情况 +允许:逐行比较计划和实施 +要求:明确标记任何偏差,无论多么微小 +偏差格式:“:警告:检测到的偏差:[确切偏差描述]” +报告:必须报告实施情况是否与计划一致 +结论格式:“:白色勾号:实施与计划完全一致”或“:十字标记:实施与计划有偏差” +输出格式:以[MODE: REVIEW]开始,然后进行系统比较和明确判决 +关键协议指南 +未经我的明确许可,您不能在模式之间转换 +您必须在每次响应开始时声明您当前的模式 +在执行模式下,你必须 100% 忠实地遵循计划 +在审查模式下,你必须标记哪怕是最小的偏差 +您无权在声明模式之外做出独立决定 +不遵守此协议将给我的代码库带来灾难性的后果 +模式转换信号 +仅当我明确发出信号时才转换模式: + +“进入研究模式” +“进入创新模式” +“进入计划模式” +“进入执行模式” +“进入审核模式” +如果没有这些确切的信号,请保持当前模式。 \ No newline at end of file diff --git a/.cursorrules b/.cursorrules deleted file mode 100644 index 8c67873..0000000 --- a/.cursorrules +++ /dev/null @@ -1,105 +0,0 @@ -# Role Definition - -- You are a **Python master**, a highly experienced **tutor**, a **world-renowned ML engineer**, and a **talented data scientist**. -- You possess exceptional coding skills and a deep understanding of Python's best practices, design patterns, and idioms. -- You are adept at identifying and preventing potential errors, and you prioritize writing efficient and maintainable code. -- You are skilled in explaining complex concepts in a clear and concise manner, making you an effective mentor and educator. -- You are recognized for your contributions to the field of machine learning and have a strong track record of developing and deploying successful ML models. -- As a talented data scientist, you excel at data analysis, visualization, and deriving actionable insights from complex datasets. - -# Technology Stack - -- **Python Version:** Python 3.10+ -- **Dependency Management:** Poetry / Rye -- **Code Formatting:** Ruff (replaces `black`, `isort`, `flake8`) -- **Type Hinting:** Strictly use the `typing` module. All functions, methods, and class members must have type annotations. -- **Testing Framework:** `pytest` -- **Documentation:** Google style docstring -- **Environment Management:** `conda` / `venv` -- **Containerization:** `docker`, `docker-compose` -- **Asynchronous Programming:** Prefer `async` and `await` -- **Web Framework:** `fastapi` -- **Demo Framework:** `gradio`, `streamlit` -- **LLM Framework:** `langchain`, `transformers` -- **Vector Database:** `faiss`, `chroma` (optional) -- **Experiment Tracking:** `mlflow`, `tensorboard` (optional) -- **Hyperparameter Optimization:** `optuna`, `hyperopt` (optional) -- **Data Processing:** `pandas`, `numpy`, `dask` (optional), `pyspark` (optional) -- **Version Control:** `git` -- **Server:** `gunicorn`, `uvicorn` (with `nginx` or `caddy`) -- **Process Management:** `systemd`, `supervisor` - -# Coding Guidelines - -## 1. Pythonic Practices - -- **Elegance and Readability:** Strive for elegant and Pythonic code that is easy to understand and maintain. -- **PEP 8 Compliance:** Adhere to PEP 8 guidelines for code style, with Ruff as the primary linter and formatter. -- **Explicit over Implicit:** Favor explicit code that clearly communicates its intent over implicit, overly concise code. -- **Zen of Python:** Keep the Zen of Python in mind when making design decisions. - -## 2. Modular Design - -- **Single Responsibility Principle:** Each module/file should have a well-defined, single responsibility. -- **Reusable Components:** Develop reusable functions and classes, favoring composition over inheritance. -- **Package Structure:** Organize code into logical packages and modules. - -## 3. Code Quality - -- **Comprehensive Type Annotations:** All functions, methods, and class members must have type annotations, using the most specific types possible. -- **Detailed Docstrings:** All functions, methods, and classes must have Google-style docstrings, thoroughly explaining their purpose, parameters, return values, and any exceptions raised. Include usage examples where helpful. -- **Thorough Unit Testing:** Aim for high test coverage (90% or higher) using `pytest`. Test both common cases and edge cases. -- **Robust Exception Handling:** Use specific exception types, provide informative error messages, and handle exceptions gracefully. Implement custom exception classes when needed. Avoid bare `except` clauses. -- **Logging:** Employ the `logging` module judiciously to log important events, warnings, and errors. - -## 4. ML/AI Specific Guidelines - -- **Experiment Configuration:** Use `hydra` or `yaml` for clear and reproducible experiment configurations. -- **Data Pipeline Management:** Employ scripts or tools like `dvc` to manage data preprocessing and ensure reproducibility. -- **Model Versioning:** Utilize `git-lfs` or cloud storage to track and manage model checkpoints effectively. -- **Experiment Logging:** Maintain comprehensive logs of experiments, including parameters, results, and environmental details. -- **LLM Prompt Engineering:** Dedicate a module or files for managing Prompt templates with version control. -- **Context Handling:** Implement efficient context management for conversations, using suitable data structures like deques. - -## 5. Performance Optimization - -- **Asynchronous Programming:** Leverage `async` and `await` for I/O-bound operations to maximize concurrency. -- **Caching:** Apply `functools.lru_cache`, `@cache` (Python 3.9+), or `fastapi.Depends` caching where appropriate. -- **Resource Monitoring:** Use `psutil` or similar to monitor resource usage and identify bottlenecks. -- **Memory Efficiency:** Ensure proper release of unused resources to prevent memory leaks. -- **Concurrency:** Employ `concurrent.futures` or `asyncio` to manage concurrent tasks effectively. -- **Database Best Practices:** Design database schemas efficiently, optimize queries, and use indexes wisely. - -## 6. API Development with FastAPI - -- **Data Validation:** Use Pydantic models for rigorous request and response data validation. -- **Dependency Injection:** Effectively use FastAPI's dependency injection for managing dependencies. -- **Routing:** Define clear and RESTful API routes using FastAPI's `APIRouter`. -- **Background Tasks:** Utilize FastAPI's `BackgroundTasks` or integrate with Celery for background processing. -- **Security:** Implement robust authentication and authorization (e.g., OAuth 2.0, JWT). -- **Documentation:** Auto-generate API documentation using FastAPI's OpenAPI support. -- **Versioning:** Plan for API versioning from the start (e.g., using URL prefixes or headers). -- **CORS:** Configure Cross-Origin Resource Sharing (CORS) settings correctly. - -# Code Example Requirements - -- All functions must include type annotations. -- Must provide clear, Google-style docstrings. -- Key logic should be annotated with comments. -- Provide usage examples (e.g., in the `tests/` directory or as a `__main__` section). -- Include error handling. -- Use `ruff` for code formatting. - -# Others - -- **Prioritize new features in Python 3.10+.** -- **When explaining code, provide clear logical explanations and code comments.** -- **When making suggestions, explain the rationale and potential trade-offs.** -- **If code examples span multiple files, clearly indicate the file name.** -- **Do not over-engineer solutions. Strive for simplicity and maintainability while still being efficient.** -- **Favor modularity, but avoid over-modularization.** -- **Use the most modern and efficient libraries when appropriate, but justify their use and ensure they don't add unnecessary complexity.** -- **When providing solutions or examples, ensure they are self-contained and executable without requiring extensive modifications.** -- **If a request is unclear or lacks sufficient information, ask clarifying questions before proceeding.** -- **Always consider the security implications of your code, especially when dealing with user inputs and external data.** -- **Actively use and promote best practices for the specific tasks at hand (LLM app development, data cleaning, demo creation, etc.).** diff --git a/poetry.lock b/poetry.lock index eb19471..57d7503 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.1.3 and should not be changed by hand. [[package]] name = "aiohappyeyeballs" @@ -6,6 +6,7 @@ version = "2.6.1" description = "Happy Eyeballs for asyncio" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, @@ -22,6 +23,7 @@ version = "3.11.18" description = "Async http client/server framework (asyncio)" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "aiohttp-3.11.18-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:96264854fedbea933a9ca4b7e0c745728f01380691687b7365d18d9e977179c4"}, {file = "aiohttp-3.11.18-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9602044ff047043430452bc3a2089743fa85da829e6fc9ee0025351d66c332b6"}, @@ -116,7 +118,7 @@ propcache = ">=0.2.0" yarl = ">=1.17.0,<2.0" [package.extras] -speedups = ["Brotli", "aiodns (>=3.2.0)", "brotlicffi"] +speedups = ["Brotli ; platform_python_implementation == \"CPython\"", "aiodns (>=3.2.0) ; sys_platform == \"linux\" or sys_platform == \"darwin\"", "brotlicffi ; platform_python_implementation != \"CPython\""] [package.source] type = "legacy" @@ -129,6 +131,7 @@ version = "1.3.2" description = "aiosignal: a list of registered asynchronous callbacks" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "aiosignal-1.3.2-py2.py3-none-any.whl", hash = "sha256:45cde58e409a301715980c2b01d0c28bdde3770d8290b5eb2173759d9acb31a5"}, {file = "aiosignal-1.3.2.tar.gz", hash = "sha256:a8c255c66fafb1e499c9351d0bf32ff2d8a0321595ebac3b93713656d2436f54"}, @@ -148,6 +151,7 @@ version = "0.7.0" description = "Reusable constraint types to use with typing.Annotated" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, @@ -164,6 +168,7 @@ version = "4.9.0" description = "High level compatibility layer for multiple asynchronous event loop implementations" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c"}, {file = "anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028"}, @@ -176,7 +181,7 @@ typing_extensions = {version = ">=4.5", markers = "python_version < \"3.13\""} [package.extras] doc = ["Sphinx (>=8.2,<9.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx_rtd_theme"] -test = ["anyio[trio]", "blockbuster (>=1.5.23)", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "trustme", "truststore (>=0.9.1)", "uvloop (>=0.21)"] +test = ["anyio[trio]", "blockbuster (>=1.5.23)", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "trustme", "truststore (>=0.9.1) ; python_version >= \"3.10\"", "uvloop (>=0.21) ; platform_python_implementation == \"CPython\" and platform_system != \"Windows\" and python_version < \"3.14\""] trio = ["trio (>=0.26.1)"] [package.source] @@ -190,18 +195,19 @@ version = "25.3.0" description = "Classes Without Boilerplate" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3"}, {file = "attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b"}, ] [package.extras] -benchmark = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins", "pytest-xdist[psutil]"] -cov = ["cloudpickle", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] -dev = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pre-commit-uv", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +benchmark = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] +cov = ["cloudpickle ; platform_python_implementation == \"CPython\"", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] +dev = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pre-commit-uv", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier"] -tests = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] -tests-mypy = ["mypy (>=1.11.1)", "pytest-mypy-plugins"] +tests = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] +tests-mypy = ["mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\""] [package.source] type = "legacy" @@ -214,6 +220,7 @@ version = "4.13.4" description = "Screen-scraping library" optional = false python-versions = ">=3.7.0" +groups = ["main"] files = [ {file = "beautifulsoup4-4.13.4-py3-none-any.whl", hash = "sha256:9bbbb14bfde9d79f38b8cd5f8c7c85f4b8f2523190ebed90e950a8dea4cb1c4b"}, {file = "beautifulsoup4-4.13.4.tar.gz", hash = "sha256:dbb3c4e1ceae6aefebdaf2423247260cd062430a410e38c66f2baa50a8437195"}, @@ -241,6 +248,7 @@ version = "1.9.0" description = "Fast, simple object-to-object and broadcast signaling" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "blinker-1.9.0-py3-none-any.whl", hash = "sha256:ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc"}, {file = "blinker-1.9.0.tar.gz", hash = "sha256:b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf"}, @@ -257,6 +265,7 @@ version = "0.0.2" description = "Dummy package for Beautiful Soup (beautifulsoup4)" optional = false python-versions = "*" +groups = ["main"] files = [ {file = "bs4-0.0.2-py2.py3-none-any.whl", hash = "sha256:abf8742c0805ef7f662dce4b51cca104cffe52b835238afc169142ab9b3fbccc"}, {file = "bs4-0.0.2.tar.gz", hash = "sha256:a48685c58f50fe127722417bae83fe6badf500d54b55f7e39ffe43b798653925"}, @@ -276,6 +285,7 @@ version = "2025.4.26" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" +groups = ["main"] files = [ {file = "certifi-2025.4.26-py3-none-any.whl", hash = "sha256:30350364dfe371162649852c63336a15c70c6510c2ad5015b21c2345311805f3"}, {file = "certifi-2025.4.26.tar.gz", hash = "sha256:0a816057ea3cdefcef70270d2c515e4506bbc954f417fa5ade2021213bb8f0c6"}, @@ -292,6 +302,8 @@ version = "1.17.1" description = "Foreign Function Interface for Python calling C code." optional = false python-versions = ">=3.8" +groups = ["main"] +markers = "(sys_platform == \"win32\" or platform_python_implementation == \"PyPy\") and (platform_python_implementation == \"CPython\" or platform_python_implementation == \"PyPy\")" files = [ {file = "cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14"}, {file = "cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67"}, @@ -376,6 +388,7 @@ version = "3.4.2" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "charset_normalizer-3.4.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7c48ed483eb946e6c04ccbe02c6b4d1d48e51944b6db70f697e089c193404941"}, {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2d318c11350e10662026ad0eb71bb51c7812fc8590825304ae0bdd4ac283acd"}, @@ -482,6 +495,7 @@ version = "8.2.1" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.10" +groups = ["main"] files = [ {file = "click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b"}, {file = "click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202"}, @@ -501,6 +515,8 @@ version = "0.4.6" description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["main"] +markers = "platform_system == \"Windows\"" files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, @@ -517,6 +533,7 @@ version = "0.6.7" description = "Easily serialize dataclasses to and from JSON." optional = false python-versions = ">=3.7,<4.0" +groups = ["main"] files = [ {file = "dataclasses_json-0.6.7-py3-none-any.whl", hash = "sha256:0dbf33f26c8d5305befd61b39d2b3414e8a407bedc2834dea9b8d642666fb40a"}, {file = "dataclasses_json-0.6.7.tar.gz", hash = "sha256:b6b3e528266ea45b9535223bc53ca645f5208833c29229e847b3f26a1cc55fc0"}, @@ -537,6 +554,7 @@ version = "1.9.0" description = "Distro - an OS platform information API" optional = false python-versions = ">=3.6" +groups = ["main"] files = [ {file = "distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2"}, {file = "distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed"}, @@ -553,6 +571,7 @@ version = "2.0.0" description = "An implementation of lxml.xmlfile for the standard library" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "et_xmlfile-2.0.0-py3-none-any.whl", hash = "sha256:7a91720bc756843502c3b7504c77b8fe44217c85c537d85037f0f536151b2caa"}, {file = "et_xmlfile-2.0.0.tar.gz", hash = "sha256:dab3f4764309081ce75662649be815c4c9081e88f0837825f90fd28317d4da54"}, @@ -569,6 +588,7 @@ version = "1.11.0" description = "A library for efficient similarity search and clustering of dense vectors." optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "faiss_cpu-1.11.0-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:1995119152928c68096b0c1e5816e3ee5b1eebcf615b80370874523be009d0f6"}, {file = "faiss_cpu-1.11.0-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:788d7bf24293fdecc1b93f1414ca5cc62ebd5f2fecfcbb1d77f0e0530621c95d"}, @@ -613,6 +633,7 @@ version = "3.1.1" description = "A simple framework for building complex web applications." optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "flask-3.1.1-py3-none-any.whl", hash = "sha256:07aae2bb5eaf77993ef57e357491839f5fd9f4dc281593a81a9e4d79a24f295c"}, {file = "flask-3.1.1.tar.gz", hash = "sha256:284c7b8f2f58cb737f0cf1c30fd7eaf0ccfcde196099d24ecede3fc2005aa59e"}, @@ -641,6 +662,7 @@ version = "1.6.0" description = "A list-like structure which implements collections.abc.MutableSequence" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "frozenlist-1.6.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e6e558ea1e47fd6fa8ac9ccdad403e5dd5ecc6ed8dda94343056fa4277d5c65e"}, {file = "frozenlist-1.6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f4b3cd7334a4bbc0c472164f3744562cb72d05002cc6fcf58adb104630bbc352"}, @@ -759,6 +781,7 @@ version = "25.5.1" description = "Coroutine-based network library" optional = false python-versions = ">=3.9" +groups = ["main"] 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"}, @@ -808,11 +831,11 @@ greenlet = {version = ">=3.2.2", markers = "platform_python_implementation == \" "zope.interface" = "*" [package.extras] -dnspython = ["dnspython (>=1.16.0,<2.0)", "idna"] +dnspython = ["dnspython (>=1.16.0,<2.0) ; python_version < \"3.10\"", "idna ; python_version < \"3.10\""] 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"] +monitor = ["psutil (>=5.7.0) ; sys_platform != \"win32\" or platform_python_implementation == \"CPython\""] +recommended = ["cffi (>=1.17.1) ; platform_python_implementation == \"CPython\"", "dnspython (>=1.16.0,<2.0) ; python_version < \"3.10\"", "idna ; python_version < \"3.10\"", "psutil (>=5.7.0) ; sys_platform != \"win32\" or platform_python_implementation == \"CPython\""] +test = ["cffi (>=1.17.1) ; platform_python_implementation == \"CPython\"", "coverage (>=5.0) ; sys_platform != \"win32\"", "dnspython (>=1.16.0,<2.0) ; python_version < \"3.10\"", "idna ; python_version < \"3.10\"", "objgraph", "psutil (>=5.7.0) ; sys_platform != \"win32\" or platform_python_implementation == \"CPython\"", "requests"] [package.source] type = "legacy" @@ -825,6 +848,8 @@ version = "3.2.2" description = "Lightweight in-process concurrent programming" optional = false python-versions = ">=3.9" +groups = ["main"] +markers = "platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\" or platform_python_implementation == \"CPython\"" files = [ {file = "greenlet-3.2.2-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:c49e9f7c6f625507ed83a7485366b46cbe325717c60837f7244fc99ba16ba9d6"}, {file = "greenlet-3.2.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c3cc1a3ed00ecfea8932477f729a9f616ad7347a5e55d50929efa50a86cb7be7"}, @@ -898,6 +923,7 @@ version = "23.0.0" description = "WSGI HTTP Server for UNIX" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "gunicorn-23.0.0-py3-none-any.whl", hash = "sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d"}, {file = "gunicorn-23.0.0.tar.gz", hash = "sha256:f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec"}, @@ -924,6 +950,7 @@ version = "0.16.0" description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86"}, {file = "h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1"}, @@ -940,6 +967,7 @@ version = "1.0.9" description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55"}, {file = "httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8"}, @@ -966,6 +994,7 @@ version = "0.28.1" description = "The next generation HTTP client." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad"}, {file = "httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc"}, @@ -978,7 +1007,7 @@ httpcore = "==1.*" idna = "*" [package.extras] -brotli = ["brotli", "brotlicffi"] +brotli = ["brotli ; platform_python_implementation == \"CPython\"", "brotlicffi ; platform_python_implementation != \"CPython\""] cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] @@ -995,6 +1024,7 @@ version = "0.4.0" description = "Consume Server-Sent Event (SSE) messages with HTTPX." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "httpx-sse-0.4.0.tar.gz", hash = "sha256:1e81a3a3070ce322add1d3529ed42eb5f70817f45ed6ec915ab753f961139721"}, {file = "httpx_sse-0.4.0-py3-none-any.whl", hash = "sha256:f329af6eae57eaa2bdfd962b42524764af68075ea87370a2de920af5341e318f"}, @@ -1011,6 +1041,7 @@ version = "3.10" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.6" +groups = ["main"] files = [ {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, @@ -1030,6 +1061,7 @@ version = "2.2.0" description = "Safely pass data to untrusted environments and back." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef"}, {file = "itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173"}, @@ -1040,12 +1072,29 @@ type = "legacy" url = "http://mirrors.aliyun.com/pypi/simple" reference = "ali-mirrors" +[[package]] +name = "jieba" +version = "0.42.1" +description = "Chinese Words Segmentation Utilities" +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "jieba-0.42.1.tar.gz", hash = "sha256:055ca12f62674fafed09427f176506079bc135638a14e23e25be909131928db2"}, +] + +[package.source] +type = "legacy" +url = "http://mirrors.aliyun.com/pypi/simple" +reference = "ali-mirrors" + [[package]] name = "jinja2" version = "3.1.6" description = "A very fast and expressive template engine." optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"}, {file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"}, @@ -1068,6 +1117,7 @@ version = "0.9.0" description = "Fast iterable JSON parser." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "jiter-0.9.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:816ec9b60fdfd1fec87da1d7ed46c66c44ffec37ab2ef7de5b147b2fce3fd5ad"}, {file = "jiter-0.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9b1d3086f8a3ee0194ecf2008cf81286a5c3e540d977fa038ff23576c023c0ea"}, @@ -1158,6 +1208,7 @@ version = "1.33" description = "Apply JSON-Patches (RFC 6902) " optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*" +groups = ["main"] files = [ {file = "jsonpatch-1.33-py2.py3-none-any.whl", hash = "sha256:0ae28c0cd062bbd8b8ecc26d7d164fbbea9652a1a3693f3b956c1eae5145dade"}, {file = "jsonpatch-1.33.tar.gz", hash = "sha256:9fcd4009c41e6d12348b4a0ff2563ba56a2923a7dfee731d004e212e1ee5030c"}, @@ -1177,6 +1228,7 @@ version = "3.0.0" description = "Identify specific nodes in a JSON document (RFC 6901) " optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "jsonpointer-3.0.0-py2.py3-none-any.whl", hash = "sha256:13e088adc14fca8b6aa8177c044e12701e6ad4b28ff10e65f2267a90109c9942"}, {file = "jsonpointer-3.0.0.tar.gz", hash = "sha256:2b2d729f2091522d61c3b31f82e11870f60b68f43fbc705cb76bf4b832af59ef"}, @@ -1193,6 +1245,7 @@ version = "0.3.25" description = "Building applications with LLMs through composability" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "langchain-0.3.25-py3-none-any.whl", hash = "sha256:931f7d2d1eaf182f9f41c5e3272859cfe7f94fc1f7cef6b3e5a46024b4884c21"}, {file = "langchain-0.3.25.tar.gz", hash = "sha256:a1d72aa39546a23db08492d7228464af35c9ee83379945535ceef877340d2a3a"}, @@ -1237,6 +1290,7 @@ version = "0.3.24" description = "Community contributed LangChain integrations." optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "langchain_community-0.3.24-py3-none-any.whl", hash = "sha256:b6cdb376bf1c2f4d2503aca20f8f35f2d5b3d879c52848277f20ce1950e7afaf"}, {file = "langchain_community-0.3.24.tar.gz", hash = "sha256:62d9e8cf9aadf35182ec3925f9ec1c8e5e84fb4f199f67a01aee496d289dc264"}, @@ -1267,6 +1321,7 @@ version = "0.3.59" description = "Building applications with LLMs through composability" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "langchain_core-0.3.59-py3-none-any.whl", hash = "sha256:9686baaff43f2c8175535da13faf40e6866769015e93130c3c1e4243e7244d70"}, {file = "langchain_core-0.3.59.tar.gz", hash = "sha256:052a37cf298c505144f007e5aeede6ecff2dc92c827525d1ef59101eb3a4551c"}, @@ -1295,6 +1350,7 @@ version = "0.3.16" description = "An integration package connecting OpenAI and LangChain" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "langchain_openai-0.3.16-py3-none-any.whl", hash = "sha256:eae74a6758d38a26159c5fde5abf8ef313e6400efb01a08f12dd7410c9f4fd0f"}, {file = "langchain_openai-0.3.16.tar.gz", hash = "sha256:4e423e39d072f1432adc9430f2905fe635cc019f01ad1bdffa5ed8d0dda32149"}, @@ -1316,6 +1372,7 @@ version = "0.3.8" description = "LangChain text splitting utilities" optional = false python-versions = "<4.0,>=3.9" +groups = ["main"] files = [ {file = "langchain_text_splitters-0.3.8-py3-none-any.whl", hash = "sha256:e75cc0f4ae58dcf07d9f18776400cf8ade27fadd4ff6d264df6278bb302f6f02"}, {file = "langchain_text_splitters-0.3.8.tar.gz", hash = "sha256:116d4b9f2a22dda357d0b79e30acf005c5518177971c66a9f1ab0edfdb0f912e"}, @@ -1335,6 +1392,7 @@ version = "0.3.42" description = "Client library to connect to the LangSmith LLM Tracing and Evaluation Platform." optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "langsmith-0.3.42-py3-none-any.whl", hash = "sha256:18114327f3364385dae4026ebfd57d1c1cb46d8f80931098f0f10abe533475ff"}, {file = "langsmith-0.3.42.tar.gz", hash = "sha256:2b5cbc450ab808b992362aac6943bb1d285579aa68a3a8be901d30a393458f25"}, @@ -1369,6 +1427,7 @@ version = "0.13.1" description = "Convert HTML to markdown." optional = false python-versions = "*" +groups = ["main"] files = [ {file = "markdownify-0.13.1-py3-none-any.whl", hash = "sha256:1d181d43d20902bcc69d7be85b5316ed174d0dda72ff56e14ae4c95a4a407d22"}, {file = "markdownify-0.13.1.tar.gz", hash = "sha256:ab257f9e6bd4075118828a28c9d02f8a4bfeb7421f558834aa79b2dfeb32a098"}, @@ -1389,6 +1448,7 @@ version = "3.0.2" description = "Safely add untrusted strings to HTML/XML markup." optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8"}, {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158"}, @@ -1464,6 +1524,7 @@ version = "3.26.1" description = "A lightweight library for converting complex datatypes to and from native Python datatypes." optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "marshmallow-3.26.1-py3-none-any.whl", hash = "sha256:3350409f20a70a7e4e11a27661187b77cdcaeb20abca41c1454fe33636bea09c"}, {file = "marshmallow-3.26.1.tar.gz", hash = "sha256:e6d8affb6cb61d39d26402096dc0aee12d5a26d490a121f118d2e81dc0719dc6"}, @@ -1488,6 +1549,7 @@ version = "6.4.3" description = "multidict implementation" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "multidict-6.4.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:32a998bd8a64ca48616eac5a8c1cc4fa38fb244a3facf2eeb14abe186e0f6cc5"}, {file = "multidict-6.4.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a54ec568f1fc7f3c313c2f3b16e5db346bf3660e1309746e7fccbbfded856188"}, @@ -1606,6 +1668,7 @@ version = "1.1.0" description = "Type system extensions for programs checked with the mypy type checker." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505"}, {file = "mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558"}, @@ -1622,6 +1685,7 @@ version = "1.26.4" description = "Fundamental package for array computing in Python" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "numpy-1.26.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9ff0f4f29c51e2803569d7a51c2304de5554655a60c5d776e35b4a41413830d0"}, {file = "numpy-1.26.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2e4ee3380d6de9c9ec04745830fd9e2eccb3e6cf790d39d7b98ffd19b0dd754a"}, @@ -1672,6 +1736,7 @@ version = "1.78.1" description = "The official Python library for the openai API" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "openai-1.78.1-py3-none-any.whl", hash = "sha256:7368bf147ca499804cc408fe68cdb6866a060f38dec961bbc97b04f9d917907e"}, {file = "openai-1.78.1.tar.gz", hash = "sha256:8b26b364531b100df1b961d03560042e5f5be11301d7d49a6cd1a2b9af824dca"}, @@ -1703,6 +1768,7 @@ version = "3.1.5" description = "A Python library to read/write Excel 2010 xlsx/xlsm files" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "openpyxl-3.1.5-py2.py3-none-any.whl", hash = "sha256:5282c12b107bffeef825f4617dc029afaf41d0ea60823bbb665ef3079dc79de2"}, {file = "openpyxl-3.1.5.tar.gz", hash = "sha256:cf0e3cf56142039133628b5acffe8ef0c12bc902d2aadd3e0fe5878dc08d1050"}, @@ -1722,6 +1788,8 @@ version = "3.10.18" description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy" optional = false python-versions = ">=3.9" +groups = ["main"] +markers = "platform_python_implementation != \"PyPy\"" files = [ {file = "orjson-3.10.18-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:a45e5d68066b408e4bc383b6e4ef05e717c65219a9e1390abc6155a520cac402"}, {file = "orjson-3.10.18-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be3b9b143e8b9db05368b13b04c84d37544ec85bb97237b3a923f076265ec89c"}, @@ -1808,6 +1876,7 @@ version = "24.2" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"}, {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, @@ -1824,6 +1893,7 @@ version = "2.2.3" description = "Powerful data structures for data analysis, time series, and statistics" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "pandas-2.2.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1948ddde24197a0f7add2bdc4ca83bf2b1ef84a1bc8ccffd95eda17fd836ecb5"}, {file = "pandas-2.2.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:381175499d3802cde0eabbaf6324cce0c4f5d52ca6f8c377c29ad442f50f6348"}, @@ -1914,6 +1984,7 @@ version = "0.3.1" description = "Accelerated property cache" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "propcache-0.3.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f27785888d2fdd918bc36de8b8739f2d6c791399552333721b58193f68ea3e98"}, {file = "propcache-0.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4e89cde74154c7b5957f87a355bb9c8ec929c167b59c83d90654ea36aeb6180"}, @@ -2026,6 +2097,7 @@ version = "2.9.10" description = "psycopg2 - Python-PostgreSQL Database Adapter" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "psycopg2-2.9.10-cp310-cp310-win32.whl", hash = "sha256:5df2b672140f95adb453af93a7d669d7a7bf0a56bcd26f1502329166f4a61716"}, {file = "psycopg2-2.9.10-cp310-cp310-win_amd64.whl", hash = "sha256:c6f7b8561225f9e711a9c47087388a97fdc948211c10a4bccbf0ba68ab7b3b5a"}, @@ -2050,6 +2122,8 @@ version = "2.22" description = "C parser in Python" optional = false python-versions = ">=3.8" +groups = ["main"] +markers = "(sys_platform == \"win32\" or platform_python_implementation == \"PyPy\") and (platform_python_implementation == \"CPython\" or platform_python_implementation == \"PyPy\")" files = [ {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, @@ -2066,6 +2140,7 @@ version = "2.11.4" description = "Data validation using Python type hints" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "pydantic-2.11.4-py3-none-any.whl", hash = "sha256:d9615eaa9ac5a063471da949c8fc16376a84afb5024688b3ff885693506764eb"}, {file = "pydantic-2.11.4.tar.gz", hash = "sha256:32738d19d63a226a52eed76645a98ee07c1f410ee41d93b4afbfa85ed8111c2d"}, @@ -2079,7 +2154,7 @@ typing-inspection = ">=0.4.0" [package.extras] email = ["email-validator (>=2.0.0)"] -timezone = ["tzdata"] +timezone = ["tzdata ; python_version >= \"3.9\" and platform_system == \"Windows\""] [package.source] type = "legacy" @@ -2092,6 +2167,7 @@ version = "2.33.2" description = "Core functionality for Pydantic validation and serialization" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "pydantic_core-2.33.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2b3d326aaef0c0399d9afffeb6367d5e26ddc24d351dbc9c636840ac355dc5d8"}, {file = "pydantic_core-2.33.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e5b2671f05ba48b94cb90ce55d8bdcaaedb8ba00cc5359f6810fc918713983d"}, @@ -2208,6 +2284,7 @@ version = "2.9.1" description = "Settings management using Pydantic" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "pydantic_settings-2.9.1-py3-none-any.whl", hash = "sha256:59b4f431b1defb26fe620c71a7d3968a710d719f5f4cdbbdb7926edeb770f6ef"}, {file = "pydantic_settings-2.9.1.tar.gz", hash = "sha256:c509bf79d27563add44e8446233359004ed85066cd096d8b510f715e6ef5d268"}, @@ -2236,6 +2313,7 @@ version = "1.1.1" description = "Pure Python MySQL Driver" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "PyMySQL-1.1.1-py3-none-any.whl", hash = "sha256:4de15da4c61dc132f4fb9ab763063e693d521a80fd0e87943b9a453dd4c19d6c"}, {file = "pymysql-1.1.1.tar.gz", hash = "sha256:e127611aaf2b417403c60bf4dc570124aeb4a57f5f37b8e95ae399a42f904cd0"}, @@ -2256,6 +2334,7 @@ version = "2.9.0.post0" description = "Extensions to the standard Python datetime module" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +groups = ["main"] files = [ {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, @@ -2275,6 +2354,7 @@ version = "1.1.0" description = "Read key-value pairs from a .env file and set them as environment variables" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "python_dotenv-1.1.0-py3-none-any.whl", hash = "sha256:d7c01d9e2293916c18baf562d95698754b0dbbb5e74d457c45d4f6561fb9d55d"}, {file = "python_dotenv-1.1.0.tar.gz", hash = "sha256:41f90bc6f5f177fb41f53e87666db362025010eb28f60a01c9143bfa33a2b2d5"}, @@ -2294,6 +2374,7 @@ version = "2025.2" description = "World timezone definitions, modern and historical" optional = false python-versions = "*" +groups = ["main"] files = [ {file = "pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00"}, {file = "pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3"}, @@ -2310,6 +2391,7 @@ version = "6.0.2" description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, @@ -2377,6 +2459,7 @@ version = "2024.11.6" description = "Alternative regular expression module, to replace re." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "regex-2024.11.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ff590880083d60acc0433f9c3f713c51f7ac6ebb9adf889c79a261ecf541aa91"}, {file = "regex-2024.11.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:658f90550f38270639e83ce492f27d2c8d2cd63805c65a13a14d36ca126753f0"}, @@ -2485,6 +2568,7 @@ version = "2.32.3" description = "Python HTTP for Humans." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, @@ -2511,6 +2595,7 @@ version = "1.0.0" description = "A utility belt for advanced users of python-requests" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +groups = ["main"] files = [ {file = "requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6"}, {file = "requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06"}, @@ -2530,19 +2615,20 @@ version = "80.9.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.9" +groups = ["main"] 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)"] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\"", "ruff (>=0.8.0) ; sys_platform != \"cygwin\""] +core = ["importlib_metadata (>=6) ; python_version < \"3.10\"", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1) ; python_version < \"3.11\"", "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"] +test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21) ; python_version >= \"3.9\" and sys_platform != \"cygwin\"", "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 ; sys_platform != \"cygwin\"", "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) ; python_version < \"3.10\"", "jaraco.develop (>=7.21) ; sys_platform != \"cygwin\"", "mypy (==1.14.*)", "pytest-mypy"] [package.source] type = "legacy" @@ -2555,6 +2641,7 @@ version = "1.17.0" description = "Python 2 and 3 compatibility utilities" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +groups = ["main"] files = [ {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, @@ -2571,6 +2658,7 @@ version = "1.3.1" description = "Sniff out which async library your code is running under" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, @@ -2587,6 +2675,7 @@ version = "2.7" description = "A modern CSS selector implementation for Beautiful Soup." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "soupsieve-2.7-py3-none-any.whl", hash = "sha256:6e60cc5c1ffaf1cebcc12e8188320b72071e922c2e897f737cadce79ad5d30c4"}, {file = "soupsieve-2.7.tar.gz", hash = "sha256:ad282f9b6926286d2ead4750552c8a6142bc4c783fd66b0293547c8fe6ae126a"}, @@ -2603,6 +2692,7 @@ version = "2.0.41" description = "Database Abstraction Library" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "SQLAlchemy-2.0.41-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6854175807af57bdb6425e47adbce7d20a4d79bbfd6f6d6519cd10bb7109a7f8"}, {file = "SQLAlchemy-2.0.41-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:05132c906066142103b83d9c250b60508af556982a385d96c4eaa9fb9720ac2b"}, @@ -2703,6 +2793,7 @@ version = "9.1.2" description = "Retry code until it succeeds" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "tenacity-9.1.2-py3-none-any.whl", hash = "sha256:f77bf36710d8b73a50b2dd155c97b870017ad21afe6ab300326b0371b3b05138"}, {file = "tenacity-9.1.2.tar.gz", hash = "sha256:1169d376c297e7de388d18b4481760d478b0e99a777cad3a9c86e556f4b697cb"}, @@ -2723,6 +2814,7 @@ version = "0.9.0" description = "tiktoken is a fast BPE tokeniser for use with OpenAI's models" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "tiktoken-0.9.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:586c16358138b96ea804c034b8acf3f5d3f0258bd2bc3b0227af4af5d622e382"}, {file = "tiktoken-0.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d9c59ccc528c6c5dd51820b3474402f69d9a9e1d656226848ad68a8d5b2e5108"}, @@ -2775,6 +2867,7 @@ version = "4.67.1" description = "Fast, Extensible Progress Meter" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2"}, {file = "tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2"}, @@ -2801,6 +2894,7 @@ version = "4.13.2" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c"}, {file = "typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef"}, @@ -2817,6 +2911,7 @@ version = "0.9.0" description = "Runtime inspection utilities for typing module." optional = false python-versions = "*" +groups = ["main"] files = [ {file = "typing_inspect-0.9.0-py3-none-any.whl", hash = "sha256:9ee6fc59062311ef8547596ab6b955e1b8aa46242d854bfc78f4f6b0eff35f9f"}, {file = "typing_inspect-0.9.0.tar.gz", hash = "sha256:b23fc42ff6f6ef6954e4852c1fb512cdd18dbea03134f91f856a95ccc9461f78"}, @@ -2837,6 +2932,7 @@ version = "0.4.0" description = "Runtime typing introspection tools" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "typing_inspection-0.4.0-py3-none-any.whl", hash = "sha256:50e72559fcd2a6367a19f7a7e610e6afcb9fac940c650290eed893d61386832f"}, {file = "typing_inspection-0.4.0.tar.gz", hash = "sha256:9765c87de36671694a67904bf2c96e395be9c6439bb6c87b5142569dcdd65122"}, @@ -2856,6 +2952,7 @@ version = "2025.2" description = "Provider of IANA time zone data" optional = false python-versions = ">=2" +groups = ["main"] files = [ {file = "tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8"}, {file = "tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9"}, @@ -2872,13 +2969,14 @@ version = "2.4.0" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "urllib3-2.4.0-py3-none-any.whl", hash = "sha256:4e16665048960a0900c702d4a66415956a584919c03361cac9f1df5c5dd7e813"}, {file = "urllib3-2.4.0.tar.gz", hash = "sha256:414bc6535b787febd7567804cc015fee39daab8ad86268f1310a9250697de466"}, ] [package.extras] -brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +brotli = ["brotli (>=1.0.9) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\""] h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] @@ -2894,6 +2992,7 @@ version = "3.1.3" description = "The comprehensive WSGI web application library." optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "werkzeug-3.1.3-py3-none-any.whl", hash = "sha256:54b78bf3716d19a65be4fceccc0d1d7b89e608834989dfae50ea87564639213e"}, {file = "werkzeug-3.1.3.tar.gz", hash = "sha256:60723ce945c19328679790e3282cc758aa4a6040e4bb330f53d30fa546d44746"}, @@ -2916,6 +3015,7 @@ version = "3.2.3" description = "A Python module for creating Excel XLSX files." optional = false python-versions = ">=3.6" +groups = ["main"] files = [ {file = "XlsxWriter-3.2.3-py3-none-any.whl", hash = "sha256:593f8296e8a91790c6d0378ab08b064f34a642b3feb787cf6738236bd0a4860d"}, {file = "xlsxwriter-3.2.3.tar.gz", hash = "sha256:ad6fd41bdcf1b885876b1f6b7087560aecc9ae5a9cc2ba97dcac7ab2e210d3d5"}, @@ -2932,6 +3032,7 @@ version = "1.20.0" description = "Yet another URL library" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "yarl-1.20.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f1f6670b9ae3daedb325fa55fbe31c22c8228f6e0b513772c2e1c623caa6ab22"}, {file = "yarl-1.20.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:85a231fa250dfa3308f3c7896cc007a47bc76e9e8e8595c20b7426cac4884c62"}, @@ -3055,6 +3156,7 @@ version = "5.0" description = "Very basic event publishing system" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "zope.event-5.0-py3-none-any.whl", hash = "sha256:2832e95014f4db26c47a13fdaef84cef2f4df37e66b59d8f1f4a8f319a632c26"}, {file = "zope.event-5.0.tar.gz", hash = "sha256:bac440d8d9891b4068e2b5a2c5e2c9765a9df762944bda6955f96bb9b91e67cd"}, @@ -3078,6 +3180,7 @@ version = "7.2" description = "Interfaces for Python" optional = false python-versions = ">=3.8" +groups = ["main"] 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"}, @@ -3137,6 +3240,7 @@ version = "0.23.0" description = "Zstandard bindings for Python" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "zstandard-0.23.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bf0a05b6059c0528477fba9054d09179beb63744355cab9f38059548fedd46a9"}, {file = "zstandard-0.23.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fc9ca1c9718cb3b06634c7c8dec57d24e9438b2aa9a0f02b8bb36bf478538880"}, @@ -3249,6 +3353,6 @@ url = "http://mirrors.aliyun.com/pypi/simple" reference = "ali-mirrors" [metadata] -lock-version = "2.0" +lock-version = "2.1" python-versions = ">=3.11,<3.13" -content-hash = "b227dc263d83a0d98c3903fc0bca5763858aaeda6f6c14f83294afddd05679b4" +content-hash = "0b608c0456c4f231d1ce887cace758d19553b3c2c1ec423134ab87cec8776f40" diff --git a/pyproject.toml b/pyproject.toml index 623f3a1..d7d5dcb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,6 +26,7 @@ gunicorn = "^23.0.0" gevent = "^25.5.1" pymysql = "^1.1.1" sqlalchemy = "^2.0.41" +jieba = "^0.42.1" [build-system] requires = ["poetry-core"] build-backend = "poetry.core.masonry.api" diff --git a/rag2_0/demo/intent_recognition_example.py b/rag2_0/demo/intent_recognition_example.py index 75414ad..beebf20 100644 --- a/rag2_0/demo/intent_recognition_example.py +++ b/rag2_0/demo/intent_recognition_example.py @@ -8,6 +8,7 @@ Description: 意图识别和问题改写示例 import os from dotenv import load_dotenv +from regex import F from rag2_0.intent_recognition import IntentRecognizer import pandas as pd import logging @@ -16,6 +17,7 @@ import concurrent.futures from tqdm import tqdm import time import sys +from typing import List, Dict # 加载环境变量 load_dotenv() @@ -42,7 +44,7 @@ def load_questions_from_excel(file_path=None): logging.error(f"读取Excel文件时出错: {e}") return [] -def process_query(recognizer, query): +def process_query(recognizer: IntentRecognizer, query: str, conversation_context: str = "", chat_history: List[Dict[str, str]] = None, previous_slots: Dict[str, str] = None): """ 处理单个查询,支持重试机制,并包含槽位填充 @@ -59,8 +61,8 @@ def process_query(recognizer, query): while retry_count <= max_retries: try: # 使用新的process_query_with_slots方法处理查询 - result = recognizer.process_query_with_slots(query) - + # result = recognizer.process_query_with_slots(query) + result = recognizer.process_query(query, conversation_context=conversation_context, chat_history=chat_history, previous_slots=previous_slots) # 提取分类信息 classification = result["classification"] @@ -101,7 +103,6 @@ def process_query(recognizer, query): "检索的关键词": keywords_str, "槽位填充": slot_filling_str } - except Exception as e: retry_count += 1 @@ -174,10 +175,30 @@ def save_results_to_excel(results, output_file, is_final=False): logging.info(f"已保存{len(valid_results)}条结果至: {temp_output_file}") # 示例查询 -examples_query = """"锁标签号:811621005858, 注册单位:惠州电力勘察设计院有限公司,软件名称:广东迁改导则2022, 注册号:BW278-83834-58155-58339.迁改导则是要另外下载安装软件吗?" - -""" - +examples_query = """那西藏软件呢""" +conversation_context="" +chat_history=[ + { + "user": "怎么新建工程啊" + }, + { + "assistant": "您好!为了更准确地帮助您解决问题,请提供以下信息:\n\n- **软件名称**:请确认您使用的软件名称,可选值如下:\n - 配网工程计价通D3软件\n - 新型储能电站建设计价通C1软件\n - 西藏电力工程计价通Z1软件\n - 技改检修工程计价通T1软件\n - 技改检修清单计价通T1软件\n - 主网电力建设计价通软件\n\n请补充以上信息,以便我们能够更好地指导您如何新建工程。" + }, + { + "user": "配网工程计价通D3软件" + }, + { + "assistant": """好的,针对您使用**配网工程计价通D3软件**新建工程的问题,以下是具体的操作步骤:\n\n## 新建工程\n\n### 功能入口\n【主页】界面——"新建"按钮\n\n### 操作步骤\n1. 在"新建窗口"选择对应工程模板。\n2. 设置工程名称。\n3. 确定后根据新建向导完善"电压等级"、"地区类型"等参数。\n4. 点击"确定"即可完成新建工程。\n\n![进入新建工程窗口](https://172.20.0.145/files/362491a3-69c3-45b0-a037-b5f96b91bf73/image-preview)\n\n希望这些步骤能帮助您顺利完成新建工程。如果还有其他问题,欢迎随时提问!\n""" + } + ] +previous_slots={ + "software_name": "配网工程计价通D3软件", + "function_name": "新建工程", + "operation": "如何新建工程", + "project_type": None, + "software_version": None, + "operation_steps": None + } def main(): """ 意图识别和问题改写示例 @@ -193,18 +214,19 @@ def main(): # 读取提问数据 current_dir = os.path.dirname(os.path.abspath(__file__)) - data_file = os.path.join(current_dir, "..", "..", "data", "excel", "历史提问数据(dislike)_提问明确.xlsx") + data_file = os.path.join(current_dir, "..", "..", "data", "excel", "历史提问数据(like)_提问明确.xlsx") output_file = os.path.join(current_dir, "..", "..", "data", "excel", "测试提问数据_槽位填充结果.xlsx") # 检测是否为调试模式,调试模式下使用examples_query,否则从Excel读取 is_debug = hasattr(sys, 'gettrace') and sys.gettrace() is not None + # is_debug = False if is_debug: examples = examples_query.strip().split("\n") else: examples = load_questions_from_excel(data_file) if not is_debug: - max_workers = 20 # 减少并发数以避免API限制 + max_workers = 40 # 减少并发数以避免API限制 logging.info(f"共有 {len(examples)} 个问题需要处理,使用 {max_workers} 个并发线程") # 创建一个与输入顺序相同的结果列表 @@ -229,9 +251,9 @@ def main(): completed += 1 # 每处理batch_size条数据保存一次 - if completed % batch_size == 0: - logging.info(f"已完成 {completed}/{len(examples)} 条,保存中间结果...") - save_results_to_excel(results, output_file, is_final=False) + # if completed % batch_size == 0: + # logging.info(f"已完成 {completed}/{len(examples)} 条,保存中间结果...") + # save_results_to_excel(results, output_file, is_final=False) # 处理完所有数据后,保存最终结果 save_results_to_excel(results, output_file, is_final=True) @@ -240,7 +262,7 @@ def main(): for idx, query in enumerate(examples): if query.strip() == "": continue - process_query(recognizer, query) + process_query(recognizer, query, conversation_context, chat_history, previous_slots) def setup_logging(): # 配置日志输出到控制台 diff --git a/rag2_0/dify/intent_recognition_api.py b/rag2_0/dify/intent_recognition_api.py index daef05b..4b990e4 100644 --- a/rag2_0/dify/intent_recognition_api.py +++ b/rag2_0/dify/intent_recognition_api.py @@ -36,6 +36,9 @@ def intent_recognize(): try: data = request.get_json(force=True) query = data.get('query') + conversation_context = data.get('conversation_context', "") + chat_history = data.get('chat_history', None) + previous_slots = data.get('previous_slots', None) if not query: return Response(json.dumps({"error": "缺少query参数"}, ensure_ascii=False), content_type='application/json; charset=utf-8', status=400) @@ -43,7 +46,7 @@ def intent_recognize(): # 获取单例实例并使用线程锁保护关键操作 recognizer = RecognizerSingleton.get_instance() - result = recognizer.process_query_with_slots(query) + result = recognizer.process_query(query, conversation_context, chat_history, previous_slots) end_time = time.time() current_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S %z") @@ -60,8 +63,8 @@ def intent_recognize(): for term in keywords["terms"]: term_info = { "名称": term["name"], - "同义词": ";".join(term["synonymous"]) if term["synonymous"] else "", - "描述": term["description"] + # "同义词": ";".join(term["synonymous"]) if term["synonymous"] else "", + # "描述": term["description"] } term_details.append(term_info) keywords_str = term_details diff --git a/rag2_0/intent_recognition/DataModels.py b/rag2_0/intent_recognition/DataModels.py index 85ecc58..f74b7e7 100644 --- a/rag2_0/intent_recognition/DataModels.py +++ b/rag2_0/intent_recognition/DataModels.py @@ -7,8 +7,8 @@ Date: 2025-05-13 Description: 提取和分类的数据模型 """ -from pydantic import BaseModel, Field -from typing import List, Optional, Dict, Tuple +from pydantic import BaseModel, Field, field_validator +from typing import List, Optional, Dict, Tuple, Union, Any from enum import Enum class SoftwareName(str, Enum): @@ -31,6 +31,61 @@ class SoftwareName(str, Enum): MAIN: "别名包括:主网软件、电力建设软件、主网建设软件、博微电力建设计价通等其他类似称呼" } +# 构建别名到标准名称的映射 +def build_alias_mapping() -> Dict[str, SoftwareName]: + """构建从别名到标准软件名称的映射字典""" + alias_map = {} + + # 配网工程计价通D3软件的别名映射 + alias_map["配网D3"] = SoftwareName.D3 + alias_map["D3软件"] = SoftwareName.D3 + alias_map["配网工程软件"] = SoftwareName.D3 + alias_map["配网软件"] = SoftwareName.D3 + + # 新型储能电站建设计价通C1软件的别名映射 + alias_map["储能C1"] = SoftwareName.C1 + alias_map["C1软件"] = SoftwareName.C1 + alias_map["储能电站软件"] = SoftwareName.C1 + alias_map["储能软件"] = SoftwareName.C1 + + # 西藏电力工程计价通Z1软件的别名映射 + alias_map["西藏Z1"] = SoftwareName.Z1 + alias_map["Z1软件"] = SoftwareName.Z1 + alias_map["西藏电力软件"] = SoftwareName.Z1 + + # 技改检修工程计价通T1软件的别名映射 + alias_map["技改T1"] = SoftwareName.T1 + alias_map["T1软件"] = SoftwareName.T1 + alias_map["技改检修软件"] = SoftwareName.T1 + + # 技改检修清单计价通T1软件的别名映射 + alias_map["技改清单T1"] = SoftwareName.T1_LIST + alias_map["T1清单软件"] = SoftwareName.T1_LIST + alias_map["技改检修清单软件"] = SoftwareName.T1_LIST + + # 主网电力建设计价通软件的别名映射 + alias_map["主网软件"] = SoftwareName.MAIN + alias_map["电力建设软件"] = SoftwareName.MAIN + alias_map["主网建设软件"] = SoftwareName.MAIN + alias_map["博微电力建设计价通"] = SoftwareName.MAIN + alias_map["主网计价通"] = SoftwareName.MAIN + alias_map["主网计价通软件"] = SoftwareName.MAIN + alias_map["计价通软件"] = SoftwareName.MAIN + alias_map["电力计价通软件"] = SoftwareName.MAIN + alias_map["计价通"] = SoftwareName.MAIN + # 添加标准名称映射 + alias_map[SoftwareName.D3.value] = SoftwareName.D3 + alias_map[SoftwareName.C1.value] = SoftwareName.C1 + alias_map[SoftwareName.Z1.value] = SoftwareName.Z1 + alias_map[SoftwareName.T1.value] = SoftwareName.T1 + alias_map[SoftwareName.T1_LIST.value] = SoftwareName.T1_LIST + alias_map[SoftwareName.MAIN.value] = SoftwareName.MAIN + + return alias_map + +# 全局别名映射字典 +SOFTWARE_NAME_ALIAS_MAP = build_alias_mapping() + # 定义输出模型 class Term(BaseModel): name: str = Field(description="专业名词") @@ -55,140 +110,201 @@ class Classification(BaseModel): class QueryRewrite(BaseModel): rewrite:str = Field(description="问题改写") +##########################槽位模型########################### +class SlotBase(BaseModel): + """槽位基础模型""" + def check_required_slots(self) -> Tuple[bool, Dict[str, str]]: + """检查必填槽位是否都存在""" + raise NotImplementedError("子类必须实现check_required_slots方法") + + @field_validator('software_name', mode='before', check_fields=False) + @classmethod + def validate_software_name(cls, v): + """验证并转换软件名称,支持别名""" + if v is None or v == "": + return "" + + # 如果已经是枚举类型,直接返回其值 + if isinstance(v, SoftwareName): + return v.value + + # 如果是字符串,尝试转换 + if isinstance(v, str): + # 直接匹配枚举值 + for software in SoftwareName: + if v == software.value: + return software.value + + # 尝试通过别名匹配 + if v in SOFTWARE_NAME_ALIAS_MAP: + return SOFTWARE_NAME_ALIAS_MAP[v].value + + # 如果无法匹配,返回原值用于错误提示 + return v + + return v + # 1. 软件问题 # 1.1 软件功能 -class SoftwareFunction(BaseModel): - software_name: SoftwareName = Field(description="软件名称,只能从给定的范围中取值") - function_name: str = Field(description="具体功能名称") - operation: str = Field(description="用户操作意图(如何使用功能、功能入口、功能使用场景)") - software_version: Optional[str] = Field(None, description="软件版本") - operation_steps: Optional[str] = Field(None, description="操作步骤描述") +class SoftwareFunctionSlots(SlotBase): + software_name: str = Field(default="", description="软件名称") + function_name: str = Field(default="", description="具体功能名称") + operation: str = Field(default="", description="用户操作意图(如何使用功能、功能入口、功能使用场景)") + project_type: Optional[str] = Field(default="单工程", description="工程类型(单工程、多工程、批次工程)") + software_version: Optional[str] = Field(default="", description="软件版本") + operation_steps: Optional[str] = Field(default="", description="操作步骤描述") def check_required_slots(self) -> Tuple[bool, Dict[str, str]]: """检查必填槽位是否都存在""" missing_slots = {} if not self.software_name: - missing_slots["software_name"] = f"{SoftwareFunction.model_fields['software_name'].description},可选值:{', '.join([name.value for name in SoftwareName if name not in [SoftwareName.UNKNOWN, SoftwareName.ALIASES]])}" + missing_slots["software_name"] = f"{SoftwareFunctionSlots.model_fields['software_name'].description},可选值:{', '.join([name.value for name in SoftwareName if name not in [SoftwareName.UNKNOWN, SoftwareName.ALIASES]])}" if not self.function_name: - missing_slots["function_name"] = SoftwareFunction.model_fields["function_name"].description + missing_slots["function_name"] = SoftwareFunctionSlots.model_fields["function_name"].description if not self.operation: - missing_slots["operation"] = SoftwareFunction.model_fields["operation"].description + missing_slots["operation"] = SoftwareFunctionSlots.model_fields["operation"].description return len(missing_slots) == 0, missing_slots # 1.2 故障排查 -class TroubleShooting(BaseModel): - software_name: SoftwareName = Field(description="软件名称,只能从给定的范围中取值") - function_name: str = Field(description="具体功能名称/操作描述") - error_message: str = Field(description="报错信息/异常现象") - software_version: Optional[str] = Field(None, description="软件版本") - os_version: Optional[str] = Field(None, description="操作系统及版本") - reproduction_steps: Optional[str] = Field(None, description="故障重现步骤") +class SoftwareTroubleShootingSlots(SlotBase): + software_name: str = Field(default="", description="软件名称") + function_name: str = Field(default="", description="具体功能名称/操作描述") + error_message: str = Field(default="", description="报错信息/异常现象") + software_version: Optional[str] = Field(default="", description="软件版本") + os_version: Optional[str] = Field(default="", description="操作系统及版本") + reproduction_steps: Optional[str] = Field(default="", description="故障重现步骤") + project_type: Optional[str] = Field(default="单工程", description="工程类型(单工程、多工程、批次工程)") def check_required_slots(self) -> Tuple[bool, Dict[str, str]]: """检查必填槽位是否都存在""" missing_slots = {} if not self.software_name: - missing_slots["software_name"] = f"{TroubleShooting.model_fields['software_name'].description},可选值:{', '.join([name.value for name in SoftwareName if name not in [SoftwareName.UNKNOWN, SoftwareName.ALIASES]])}" + missing_slots["software_name"] = f"{SoftwareTroubleShootingSlots.model_fields['software_name'].description},可选值:{', '.join([name.value for name in SoftwareName if name not in [SoftwareName.UNKNOWN, SoftwareName.ALIASES]])}" if not self.function_name: - missing_slots["function_name"] = TroubleShooting.model_fields["function_name"].description + missing_slots["function_name"] = SoftwareTroubleShootingSlots.model_fields["function_name"].description if not self.error_message: - missing_slots["error_message"] = TroubleShooting.model_fields["error_message"].description + missing_slots["error_message"] = SoftwareTroubleShootingSlots.model_fields["error_message"].description return len(missing_slots) == 0, missing_slots # 2. 业务问题 # 2.1 专业咨询 -class ProfessionalConsulting(BaseModel): - scene_subject: str = Field(description="场景主体") - business_scene: str = Field(description="业务场景描述") - software_name: Optional[SoftwareName] = Field(None, description="软件名称") +class ProfessionalConsultingSlots(SlotBase): + scene_subject: str = Field(default="", description="场景主体") + business_scene: str = Field(default="", description="业务场景描述") + software_name: Optional[str] = Field(default="", description="软件名称") def check_required_slots(self) -> Tuple[bool, Dict[str, str]]: """检查必填槽位是否都存在""" missing_slots = {} if not self.scene_subject: - missing_slots["scene_subject"] = ProfessionalConsulting.model_fields["scene_subject"].description + missing_slots["scene_subject"] = ProfessionalConsultingSlots.model_fields["scene_subject"].description if not self.business_scene: - missing_slots["business_scene"] = ProfessionalConsulting.model_fields["business_scene"].description + missing_slots["business_scene"] = ProfessionalConsultingSlots.model_fields["business_scene"].description return len(missing_slots) == 0, missing_slots # 2.2 数据问题 -class DataProblem(BaseModel): - expense_type: str = Field(description="费用类型") - operation_purpose: str = Field(description="操作目的") - software_name: Optional[SoftwareName] = Field(None, description="软件名称") - project_type: Optional[str] = Field(None, description="工程类型") +class DataProblemSlots(SlotBase): + expense_type: str = Field(default="", description="费用类型") + operation_purpose: str = Field(default="", description="操作目的") + software_name: Optional[str] = Field(default="", description="软件名称") + project_type: Optional[str] = Field(default="", description="工程类型") def check_required_slots(self) -> Tuple[bool, Dict[str, str]]: """检查必填槽位是否都存在""" missing_slots = {} if not self.expense_type: - missing_slots["expense_type"] = DataProblem.model_fields["expense_type"].description + missing_slots["expense_type"] = DataProblemSlots.model_fields["expense_type"].description if not self.operation_purpose: - missing_slots["operation_purpose"] = DataProblem.model_fields["operation_purpose"].description + missing_slots["operation_purpose"] = DataProblemSlots.model_fields["operation_purpose"].description return len(missing_slots) == 0, missing_slots # 3. 安装下载注册 # 3.1 后缀名咨询 -class FileExtensionConsulting(BaseModel): - file_extension: str = Field(description="文件后缀名") - operation_purpose: str = Field(description="操作目的") - file_source: Optional[str] = Field(None, description="文件来源场景") - related_software: Optional[str] = Field(None, description="相关软件名称") +class FileExtensionConsultingSlots(SlotBase): + file_extension: str = Field(default="", description="文件后缀名") + operation_purpose: str = Field(default="", description="操作目的(了解对应软件,对应工程)") + file_source: Optional[str] = Field(default="", description="文件来源场景") + related_software: Optional[str] = Field(default="", description="相关软件名称") def check_required_slots(self) -> Tuple[bool, Dict[str, str]]: """检查必填槽位是否都存在""" missing_slots = {} if not self.file_extension: - missing_slots["file_extension"] = FileExtensionConsulting.model_fields["file_extension"].description + missing_slots["file_extension"] = FileExtensionConsultingSlots.model_fields["file_extension"].description if not self.operation_purpose: - missing_slots["operation_purpose"] = FileExtensionConsulting.model_fields["operation_purpose"].description + missing_slots["operation_purpose"] = FileExtensionConsultingSlots.model_fields["operation_purpose"].description return len(missing_slots) == 0, missing_slots # 3.2 软件锁类 -class SoftwareLock(BaseModel): - lock_type: str = Field(description="锁类型") - operation_purpose: str = Field(description="操作目的") - lock_number: Optional[str] = Field(None, description="软件锁编号/注册号") +class SoftwareLockSlots(SlotBase): + lock_type: str = Field(default="", description="锁类型") + operation_purpose: str = Field(default="", description="操作目的") + lock_number: Optional[str] = Field(default="", description="软件锁编号/注册号") def check_required_slots(self) -> Tuple[bool, Dict[str, str]]: """检查必填槽位是否都存在""" missing_slots = {} if not self.lock_type: - missing_slots["lock_type"] = SoftwareLock.model_fields["lock_type"].description + missing_slots["lock_type"] = SoftwareLockSlots.model_fields["lock_type"].description if not self.operation_purpose: - missing_slots["operation_purpose"] = SoftwareLock.model_fields["operation_purpose"].description + missing_slots["operation_purpose"] = SoftwareLockSlots.model_fields["operation_purpose"].description return len(missing_slots) == 0, missing_slots # 3.3 安装下载类 -class InstallationDownload(BaseModel): +class InstallationDownloadSlots(SlotBase): - software_name: str = Field(description="软件/插件名称,与file_name二选一") - file_name: str = Field(description="文件名,与software_name二选一") - operation_stage: str = Field(description="操作阶段") - os_version: Optional[str] = Field(None, description="操作系统版本") - package_source: Optional[str] = Field(None, description="安装包来源/版本号") + software_name: str = Field(default="", description="软件/插件名称,与file_name二选一") + file_name: str = Field(default="", description="文件名,与software_name二选一") + operation_stage: str = Field(default="", description="操作阶段(下载、安装等)") + os_version: Optional[str] = Field(default="", description="操作系统版本") + package_source: Optional[str] = Field(default="", description="安装包来源/版本号") def check_required_slots(self) -> Tuple[bool, Dict[str, str]]: """检查必填槽位是否都存在""" missing_slots = {} if not self.software_name and not self.file_name: - missing_slots["software_name"] = f"{InstallationDownload.model_fields['software_name'].description}," + missing_slots["software_name"] = f"{InstallationDownloadSlots.model_fields['software_name'].description}," f"可选值:{', '.join([name.value for name in SoftwareName if name not in [SoftwareName.UNKNOWN, SoftwareName.ALIASES]])}" - missing_slots["file_name"] = InstallationDownload.model_fields["file_name"].description + missing_slots["file_name"] = InstallationDownloadSlots.model_fields["file_name"].description if not self.operation_stage: - missing_slots["operation_stage"] = InstallationDownload.model_fields["operation_stage"].description + missing_slots["operation_stage"] = InstallationDownloadSlots.model_fields["operation_stage"].description return len(missing_slots) == 0, missing_slots # 3.4 问题排查类 -class ProblemDiagnosis(BaseModel): - error_message: str = Field(description="报错信息/异常现象") - software_name: Optional[SoftwareName] = Field(None, description="软件名称,只能从给定的范围中取值") - os_version: Optional[str] = Field(None, description="操作系统版本") +class ProblemDiagnosisSlots(SlotBase): + error_message: str = Field(default="", description="报错信息/异常现象") + software_name: Optional[str] = Field(default="", description="软件名称") + os_version: Optional[str] = Field(default="", description="操作系统版本") def check_required_slots(self) -> Tuple[bool, Dict[str, str]]: """检查必填槽位是否都存在""" missing_slots = {} if not self.error_message: - missing_slots["error_message"] = ProblemDiagnosis.model_fields["error_message"].description + missing_slots["error_message"] = ProblemDiagnosisSlots.model_fields["error_message"].description return len(missing_slots) == 0, missing_slots +class OtherSlots(SlotBase): + """其他类型槽位""" + content_type: str = Field(default="", description="内容类型(必填)") + intent: Optional[str] = Field(default="", description="用户意图(选填)") + + def check_required_slots(self) -> Tuple[bool, Dict[str, str]]: + missing_slots = {} + if not self.content_type: + missing_slots["content_type"] = OtherSlots.model_fields["content_type"].description + return len(missing_slots) == 0, missing_slots + +class IntentAndSlotResult(BaseModel): + """意图槽位填充结果""" + classification: Classification + slots: Union[ + SoftwareFunctionSlots, + SoftwareTroubleShootingSlots, + ProfessionalConsultingSlots, + DataProblemSlots, + FileExtensionConsultingSlots, + SoftwareLockSlots, + InstallationDownloadSlots, + ProblemDiagnosisSlots, + OtherSlots + ] \ No newline at end of file diff --git a/rag2_0/intent_recognition/IntentRecognition.py b/rag2_0/intent_recognition/IntentRecognition.py index df66f74..314822d 100644 --- a/rag2_0/intent_recognition/IntentRecognition.py +++ b/rag2_0/intent_recognition/IntentRecognition.py @@ -7,18 +7,27 @@ Date: 2025-05-13 Description: 意图分类、改写核心逻辑 """ +import logging import os -from langchain_openai import ChatOpenAI from langchain.output_parsers import PydanticOutputParser import json -from typing import List, Tuple, Dict, Any, Optional, Union +from typing import List, Tuple, Dict, Any, Optional import re -from .PromptTemplates import classification_prompt, query_rewrite_prompt, extract_nouns_prompt, classification_info, slot_filling_prompt +import jieba +from .PromptTemplates import (classification_prompt, query_rewrite_prompt, + extract_nouns_prompt, classification_info, + slot_filling_prompt) + +from .Multi_PromptTemplates import ( + intent_and_slot_prompt, output_example, + generate_slot_mapping_doc, query_rewrite_prompt_pro, +) + from .DataModels import ( Classification, QueryRewrite, Term, TermList, - SoftwareFunction, TroubleShooting, ProfessionalConsulting, - DataProblem, FileExtensionConsulting, SoftwareLock, - InstallationDownload, ProblemDiagnosis + SoftwareFunctionSlots, SoftwareTroubleShootingSlots, ProfessionalConsultingSlots, + DataProblemSlots, FileExtensionConsultingSlots, SoftwareLockSlots, + InstallationDownloadSlots, ProblemDiagnosisSlots, OtherSlots, IntentAndSlotResult ) from .ProfessionalNounVector import ProfessionalNounRetriever from rag2_0.tool.ModelTool import XinferenceReRankerModel, OpenAiLLM, SiliconFlowReRankerModel @@ -52,22 +61,13 @@ class IntentRecognizer: if base_url: llm_params["base_url"] = base_url - self.llm = OpenAiLLM(**llm_params) - - # 准备分类解析器 - self.classification_parser = PydanticOutputParser(pydantic_object=Classification) - - # 准备问题改写解析器 - self.query_rewrite_parser = PydanticOutputParser(pydantic_object=QueryRewrite) - - # 准备术语列表解析器 - self.terms_list_parser = PydanticOutputParser(pydantic_object=TermList) + self._llm = OpenAiLLM(**llm_params) # 加载suffix关键词 - self.suffix_keywords = self._load_suffix_keywords() + self._suffix_keywords = self._load_suffix_keywords() # 初始化向量检索器 - self.noun_retriever = ProfessionalNounRetriever(api_key=api_key, index_dir=vector_index_dir) + self._noun_retriever = ProfessionalNounRetriever(api_key=api_key, index_dir=vector_index_dir) def _load_suffix_keywords(self, filepath: str = None) -> List[str]: """ @@ -95,7 +95,7 @@ class IntentRecognizer: except Exception as e: raise RuntimeError(f"加载后缀关键词失败: {e}") from e - def classify_intent(self, query: str, keywords: TermList) -> Classification: + def _classify_intent(self, query: str) -> Classification: """ 对用户输入进行意图分类 @@ -106,49 +106,85 @@ class IntentRecognizer: Returns: 分类结果 """ + classification_parser = PydanticOutputParser(pydantic_object=Classification) formatted_prompt = classification_prompt.format(user_input=query, classification_info=classification_info, - output_format=self.classification_parser.get_format_instructions()) - # 将关键词列表转换为JSON字符串 - terms_dict = [term.model_dump() for term in keywords.terms] - keywords_str = json.dumps(terms_dict, ensure_ascii=False) - formatted_prompt = formatted_prompt.replace("{keywords}", keywords_str) + output_format=classification_parser.get_format_instructions()) + # 调用LLM - response = self.llm.invoke(formatted_prompt, False) + response = self._llm.invoke(formatted_prompt, False) # 解析输出 try: # 尝试直接解析JSON响应 - parsed_output = self.classification_parser.parse(response.content.strip()) + parsed_output = classification_parser.parse(response.content.strip()) return parsed_output except Exception as e: raise RuntimeError(f"解析分类结果时出错: {e}") from e - def extract_keywords_with_llm(self, query: str) -> List[Term]: + def _tokenize_with_jieba(self, query: str) -> List[str]: """ - 使用LLM从用户查询中提取专业关键词 + 使用jieba分词器对查询进行分词 Args: query: 用户查询 + Returns: + 分词后的词语列表 + """ + # 使用jieba进行分词 + seg_list = jieba.cut(query, cut_all=False) + + # 过滤掉停用词和标点符号 + filtered_tokens = [] + for token in seg_list: + # 过滤掉空格和标点符号 + if token.strip() and not re.match(r'^[^\w\s]$', token): + filtered_tokens.append(token) + + return filtered_tokens + + def _extract_keywords_with_llm(self, query: str, use_jieba: bool = False) -> List[Term]: + """ + 使用LLM从用户查询中提取专业关键词 + + Args: + query: 用户查询 + use_jieba: 是否使用jieba分词辅助提取关键词 + Returns: 提取的术语列表 """ - # 准备提示词 - formatted_prompt = extract_nouns_prompt.replace("{content}", query) - formatted_prompt = formatted_prompt.replace("{output_format}", self.terms_list_parser.get_format_instructions()) - - # 调用LLM - response = self.llm.invoke(formatted_prompt, False) - try: - # 尝试使用Pydantic解析器解析TermList - parsed_output = self.terms_list_parser.parse(response.content) - return parsed_output.terms + # 如果使用jieba分词 + if use_jieba: + # 先使用jieba分词 + tokens = self._tokenize_with_jieba(query) + + # 构建术语列表 + terms = [] + for token in tokens: + if len(token) > 1: # 过滤掉单字词 + terms.append(Term(name=token, synonymous=[], description="")) + + return terms + else: + # 使用LLM提取关键词 + # 准备提示词 + formatted_prompt = extract_nouns_prompt.replace("{content}", query) + terms_list_parser = PydanticOutputParser(pydantic_object=TermList) + formatted_prompt = formatted_prompt.replace("{output_format}", terms_list_parser.get_format_instructions()) + + # 调用LLM + response = self._llm.invoke(formatted_prompt, False) + + # 尝试使用Pydantic解析器解析TermList + parsed_output = terms_list_parser.parse(response.content) + return parsed_output.terms except Exception as e: raise RuntimeError(f"无法解析LLM关键词提取响应: {e}") from e - - def rerank_matched_terms(self, query_key: str, matched_terms: set, top_k: int = 2) -> List[Term]: + + def _rerank_matched_terms(self, query_key: str, matched_terms: set, top_k: int = 2) -> List[Term]: """ 对召回的专业术语进行重排序,按与用户查询的相关性排序 @@ -182,31 +218,32 @@ class IntentRecognizer: except Exception as e: raise RuntimeError(f"SiliconFlowReRankerModel重排失败:{e}") from e - def match_keywords(self, query: str) -> Tuple[TermList, List[str]]: + def _match_keywords(self, query: str, use_jieba: bool = False) -> Tuple[TermList, List[str]]: """ 从用户问题中匹配关键词,结合LLM提取和向量检索 Args: query: 用户问题 + use_jieba: 是否使用jieba分词辅助提取关键词 Returns: 匹配到的关键词列表 """ query_keys=[] - # 步骤2: 使用LLM提取查询中的关键词 + # 步骤1: 使用LLM提取查询中的关键词 try: - extracted_terms = self.extract_keywords_with_llm(query) + extracted_terms = self._extract_keywords_with_llm(query, use_jieba) for term in extracted_terms: query_keys.append(term.name) except Exception as e: raise RuntimeError(f"LLM关键词提取失败: {e}") from e matched_terms = [] # 存储匹配到的Term对象 - # 步骤3: 使用向量检索找到相似的专业名词 + # 步骤2: 使用向量检索找到相似的专业名词 try: # 对matched_terms中的每个关键字进行向量检索 for current_key in query_keys: - vector_results = self.noun_retriever.query(current_key, top_k=5, use_intersection=False) + vector_results = self._noun_retriever.query(current_key, top_k=5, use_intersection=False) current_key_terms = set() # 添加向量检索结果 for result in vector_results: @@ -218,8 +255,9 @@ class IntentRecognizer: description=result.get('description', '') ) current_key_terms.add(term) - reranked_terms = self.rerank_matched_terms(current_key, current_key_terms) - matched_terms.extend(reranked_terms) + if len(current_key_terms) > 0: + reranked_terms = self._rerank_matched_terms(current_key, current_key_terms) + matched_terms.extend(reranked_terms) except Exception as e: raise RuntimeError(f"向量检索关键词时出错: {e}") from e @@ -228,7 +266,7 @@ class IntentRecognizer: term_list = TermList(terms=list(matched_terms)) return term_list, query_keys - def rewrite_query(self, query: str, keywords: TermList) -> QueryRewrite: + def _rewrite_query(self, query: str, keywords: TermList, chat_history: List[Dict[str, str]] = None, context: str = "") -> QueryRewrite: """ 对用户问题进行改写 @@ -242,23 +280,28 @@ class IntentRecognizer: # 准备问题改写提示 terms_dict = [term.model_dump(exclude={"description"}) for term in keywords.terms] keywords_str = json.dumps(terms_dict, ensure_ascii=False) - formatted_prompt = query_rewrite_prompt.format(query=query, - output_format=self.query_rewrite_parser.get_format_instructions(), - keywords=keywords_str) - + query_rewrite_parser = PydanticOutputParser(pydantic_object=QueryRewrite) + # formatted_prompt = query_rewrite_prompt.format(query=query, + # output_format=query_rewrite_parser.get_format_instructions(), + # keywords=keywords_str) + formatted_prompt = query_rewrite_prompt_pro.format(query=query, + output_format=query_rewrite_parser.get_format_instructions(), + keywords=keywords_str, + chat_history=chat_history, + context=context) # 调用LLM - response = self.llm.invoke(formatted_prompt, False) + response = self._llm.invoke(formatted_prompt, False) # 解析输出 try: # 尝试直接解析JSON响应 - parsed_output = self.query_rewrite_parser.parse(response.content) + parsed_output = query_rewrite_parser.parse(response.content) return parsed_output except Exception as e: raise RuntimeError(f"解析问题改写结果时出错: {e}") from e - def judge_define_suffix(self, input_str: str) -> Tuple[bool, List[str]]: + def _judge_define_suffix(self, input_str: str) -> Tuple[bool, List[str]]: """ 判断输入字符串是否包含定义的后缀,并返回所有匹配到的后缀名列表 @@ -270,7 +313,7 @@ class IntentRecognizer: """ # 构建正则表达式模式,匹配大小写不敏感且前面可能带有. - pattern = r'(?:\.?)(' + '|'.join(re.escape(field.get('name')) for field in self.suffix_keywords) + r')' + pattern = r'(?:\.?)(' + '|'.join(re.escape(field.get('name')) for field in self._suffix_keywords) + r')' # 使用 re.IGNORECASE 标志来忽略大小写,findall找到所有匹配 matches = re.finditer(pattern, input_str, re.IGNORECASE) @@ -278,23 +321,30 @@ class IntentRecognizer: return bool(matched_suffixes), matched_suffixes - def process_query(self, query: str) -> Tuple[Classification, TermList, QueryRewrite, List[str]]: + def process_query(self, query: str, conversation_context: str = "", + chat_history: List[Dict[str, str]] = None, + previous_slots: Dict[str, Any] = None, + use_jieba: bool = False) -> Dict[str, Any]: """ 处理用户问题的完整流程 Args: query: 用户原始问题 + conversation_context: 会话背景信息 + chat_history: 历史对话记录,格式为[{"user": "content"}, {"assistant": "content"}] + previous_slots: 历史槽位信息 + use_jieba: 是否使用jieba分词辅助提取关键词 Returns: - (意图分类结果, 匹配的关键词列表, 问题改写结果)的元组 + 包含分类、关键词、改写和槽位填充结果的字典 """ # 是否是扩展名 - # is_suffix, matched_suffixes = self.judge_define_suffix(query) + # is_suffix, matched_suffixes = self._judge_define_suffix(query) # if is_suffix: # # 将所有匹配到的后缀名作为Term添加到结果中 # suffix_terms = [] # for suffix in matched_suffixes: - # term_dict = next((item for item in self.suffix_keywords if item['name'].lower() == suffix.lower()), None) + # term_dict = next((item for item in self._suffix_keywords if item['name'].lower() == suffix.lower()), None) # if term_dict: # suffix_term = Term( # name=term_dict.get('name'), @@ -306,26 +356,41 @@ class IntentRecognizer: # return Classification(vertical_classification="安装下载", sub_classification="查询"), TermList(terms=suffix_terms), QueryRewrite(rewrite=query), matched_suffixes # 步骤1: 匹配关键词 - keywords_terms, query_keys = self.match_keywords(query) + keywords_terms, query_keys = self._match_keywords(query, use_jieba) # 步骤2: 问题改写 - rewrite = self.rewrite_query( + rewrite = self._rewrite_query( query=query, - keywords=keywords_terms + keywords=keywords_terms, + chat_history=chat_history, + context=conversation_context ) + + # 步骤3: 进行意图识别和槽位填充 + result = self._process_intent_and_slot(query, conversation_context, chat_history, previous_slots) + result.update({"keywords": keywords_terms.model_dump(), + "rewrite": rewrite.model_dump(), + "query_keys": query_keys}) + return result + # # 步骤3: 进行意图分类 + # classification = self._classify_intent(query) - # 步骤3: 进行意图分类 - classification = self.classify_intent(query, keywords_terms) - if classification.vertical_classification == "其他" or classification.sub_classification == "其他": - return classification, TermList(terms=[]), QueryRewrite(rewrite=query), [] + # # 步骤4: 进行槽位填充 + # # 如果是有效分类,进行槽位填充 + # slot_filling_result = {} + # if classification.vertical_classification not in ["其他", "闲聊"] and classification.sub_classification not in ["其他", "闲聊"]: + # slot_filling_result = self._fill_slots(rewrite.rewrite, classification) + + # return { + # "classification": classification.model_dump(), + # "keywords": keywords_terms.model_dump(), + # "rewrite": rewrite.model_dump(), + # "query_keys": query_keys, + # "slot_filling": slot_filling_result + # } - if classification.vertical_classification == "闲聊" or classification.sub_classification == "闲聊": - return classification, TermList(terms=[]), QueryRewrite(rewrite=query),[] - - # rewrite = QueryRewrite(rewrite=query) - return classification, keywords_terms, rewrite, query_keys - def fill_slots(self, query: str, classification: Classification) -> Dict[str, Any]: + def _fill_slots(self, query: str, classification: Classification) -> Dict[str, Any]: """ 根据分类结果对问题进行槽位填充 @@ -340,7 +405,7 @@ class IntentRecognizer: # 根据分类结果选择对应的数据模型 slot_model = self._get_slot_model(classification) if not slot_model: - return {"error": "未找到匹配的槽位模型"} + raise RuntimeError("未找到匹配的槽位模型") # 使用LLM进行槽位填充 filled_slots = self._fill_slots_with_llm(query, classification, slot_model) @@ -356,7 +421,7 @@ class IntentRecognizer: def _get_slot_model(self, classification: Classification) -> Optional[type]: """ - 根据分类结果获取对应的槽位模型类 + 根据分类结果获取对应的槽位模型类,用于统一提示词处理 Args: classification: 意图分类结果 @@ -367,31 +432,33 @@ class IntentRecognizer: # 软件问题 if classification.vertical_classification == "软件问题": if classification.sub_classification == "软件功能": - return SoftwareFunction + return SoftwareFunctionSlots elif classification.sub_classification == "故障排查": - return TroubleShooting + return SoftwareTroubleShootingSlots # 业务问题 elif classification.vertical_classification == "业务问题": if classification.sub_classification == "专业咨询": - return ProfessionalConsulting + return ProfessionalConsultingSlots elif classification.sub_classification == "数据问题": - return DataProblem + return DataProblemSlots # 安装下载注册 elif classification.vertical_classification == "安装下载注册": if classification.sub_classification == "后缀名咨询": - return FileExtensionConsulting + return FileExtensionConsultingSlots elif classification.sub_classification == "软件锁类": - return SoftwareLock + return SoftwareLockSlots elif classification.sub_classification == "安装下载类": - return InstallationDownload + return InstallationDownloadSlots elif classification.sub_classification == "问题排查类": - return ProblemDiagnosis + return ProblemDiagnosisSlots + # 其他 + elif classification.vertical_classification == "其他": + return OtherSlots + return None - - count=1 def _fill_slots_with_llm(self, query: str, classification: Classification, slot_model_class: type) -> Any: """ @@ -416,7 +483,7 @@ class IntentRecognizer: ) # 调用LLM - response = self.llm.invoke(formatted_prompt, False) + response = self._llm.invoke(formatted_prompt, False) try: # 尝试解析LLM响应 @@ -426,29 +493,88 @@ class IntentRecognizer: # 如果解析失败,创建一个空的模型实例 empty_instance = slot_model_class() return empty_instance - - def process_query_with_slots(self, query: str) -> Dict[str, Any]: + + def _process_intent_and_slot(self, user_input: str, conversation_context: str = "", + chat_history: List[Dict[str, str]] = None, + previous_slots: Dict[str, Any] = None) -> Dict[str, Any]: """ - 处理用户问题的完整流程,包括槽位填充 + 使用统一提示词同时进行意图识别和槽位填充 Args: - query: 用户原始问题 + user_input: 当前用户输入 + conversation_context: 会话背景信息 + chat_history: 历史对话记录,格式为[{"role": "user", "content": "..."}, {"role": "assistant", "content": "..."}] + previous_slots: 历史槽位信息 Returns: - 包含分类、关键词、改写和槽位填充结果的字典 + 包含意图分类和槽位填充结果的字典 """ - # 执行基本处理流程 - classification, keywords, rewrite, query_keys = self.process_query(query) + # 初始化默认值 + if chat_history is None: + chat_history = [] - # 如果是有效分类,进行槽位填充 - slot_filling_result = {} - if classification.vertical_classification not in ["其他", "闲聊"] and classification.sub_classification not in ["其他", "闲聊"]: - slot_filling_result = self.fill_slots(rewrite.rewrite, classification) + if previous_slots is None: + previous_slots = {} + + # 生成槽位映射文档 + slot_mapping_doc = generate_slot_mapping_doc() - return { - "classification": classification.model_dump(), - "keywords": keywords.model_dump(), - "rewrite": rewrite.model_dump(), - "query_keys": query_keys, - "slot_filling": slot_filling_result - } \ No newline at end of file + # 准备提示词 + parser = PydanticOutputParser(pydantic_object=IntentAndSlotResult) + formatted_prompt = intent_and_slot_prompt.format( + conversation_context=conversation_context, + chat_history=json.dumps(chat_history, ensure_ascii=False), + previous_slots=json.dumps(previous_slots, ensure_ascii=False), + user_input=user_input, + slot_mapping_doc=slot_mapping_doc, + output_format=parser.get_format_instructions(), + classification_info=classification_info + ) + # 调用LLM + response = self._llm.invoke(formatted_prompt + output_example, False) + + try: + # 解析LLM响应为JSON + result_json = parser.parse(response.content) + classification=result_json.classification + slot_filling=result_json.slots + is_complete, missing_slots = slot_filling.check_required_slots() + expected_slot_model = self._get_slot_model(classification) + + # 添加容错处理,发生概率较低,但仍需处理 + if expected_slot_model is None: + # 添加容错处理,应对LLM返回错误分类信息,一级分类跟二级分类错乱 + # 重新分类 + classification = self._classify_intent(user_input) + fill_slots = self._fill_slots(user_input, classification) + result = { + "classification": classification.model_dump(), + "slot_filling": fill_slots + } + logging.warning(f"重新分类与槽点填充") + return result + elif expected_slot_model.__name__ != type(slot_filling).__name__: + # 添加容错处理,应对LLM槽位与分类不匹配。重新填充槽位 + slot_filling = self._fill_slots(user_input, classification) + result = { + "classification": classification.model_dump(), + "slot_filling": slot_filling + } + logging.warning(f"重新填充槽点") + return result + + # 构建最终结果 + result = { + "classification": classification.model_dump(), + "slot_filling": { + "is_complete": is_complete, + "missing_slots": missing_slots, + "filled_data": slot_filling.model_dump() + } + } + + return result + + except Exception as e: + logging.error(f"process_intent_and_slot error:{e}") + raise RuntimeError(f"process_intent_and_slot error:{e}") from e \ No newline at end of file diff --git a/rag2_0/intent_recognition/Multi_PromptTemplates.py b/rag2_0/intent_recognition/Multi_PromptTemplates.py new file mode 100644 index 0000000..eca6d26 --- /dev/null +++ b/rag2_0/intent_recognition/Multi_PromptTemplates.py @@ -0,0 +1,357 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +File: Multi_PromptTemplates.py +Author: oyyz +Date: 2025-06-13 +Description: 多轮对话下意图分类、改写核心提示词 +""" + + +# 首版重构提示词 +query_rewrite_prompt_pro_old=""" +# 电力造价专业问答优化工程师(升级版) + +你是一名电力造价专业问答优化工程师,负责结合历史对话背景和专业术语库,将用户的原始问题进行规范化重构,以提升知识库检索准确率。 + +## 核心任务 +基于历史对话上下文和专业术语库,将用户的原始问题进行规范化重构,提高知识库检索的准确性和专业性,同时保持对话的连贯性和语境相关性。 + +## 处理流程 +### 第一阶段:输入解析 +1. 解析基础信息 + - 原始问题(需保留核心语义):{query} + - 关键词集合:{keywords} + - 历史对话记录:{chat_history} + - 当前聊天背景:{context} + +2. 背景分析 + - 识别历史对话中的关键主题和专业领域 + - 提取上下文中的隐含信息(如软件版本、地区、具体场景等) + - 分析用户的提问模式和专业水平 + +### 第二阶段:上下文匹配分析 +**背景匹配规则:** +1. 检查当前问题是否与历史对话存在关联性 +2. 识别历史对话中提到的关键信息: + - 软件版本/系统(如Z1、D3等) + - 地区定额(如西藏、山东等) + - 具体功能模块 + - 用户操作习惯 + +**术语匹配规则:** +1. 检查原始问题中是否包含关键词集合中的`name`字段或`synonymous`字段中的任何词汇 +2. 结合历史对话,识别可能的隐含专业术语 +3. 统计匹配的术语数量 +4. 判断执行路径: + - 匹配术语 ≥ 1个 或 存在明显上下文关联 → 执行重构流程 + - 匹配术语 = 0个 且 无明显上下文关联 → 直接输出原始问题 + +### 第三阶段:问题重构 +**重构原则(按优先级排序):** + +1. **语义保真**:严格保持原问题的核心意图和诉求 +2. **上下文继承**: + - 补充历史对话中的隐含信息(如软件名称、版本、地区等) + - 保持对话的连贯性和逻辑性 + - 避免重复已确认的背景信息 +3. **术语规范**: + - 将匹配到的同义词替换为对应的标准术语(name字段) + - 对在关键词中的标准术语使用【】进行标记 + - 保留在原问题中未在关键词库中的专业术语、限定词和修饰词 +4. **结构优化**: + - 保持原问题的语态特征5W2H + - 保持主谓宾结构清晰 + - 保留时间、版本等限定条件 + +**术语处理规则:** +- 优先级1:基于历史对话补充缺失的背景信息 +- 优先级2:保留原问题中的专业术语、限定词和修饰词(即使不在关键词库中) +- 优先级3:将同义词替换为标准术语并用【】标记 +- 优先级4:对原问题中已存在的标准术语添加【】标记 + +**上下文处理策略:** +- 如果当前问题与历史对话高度相关,适当补充背景信息 +- 如果用户使用代词(如"这个"、"那个"),尝试结合历史对话明确指代 +- 如果历史对话中已确定软件或系统,在当前问题中适当体现 + +# 输出规范 +{output_format} + +# 示范案例库 + +▶ 案例1(有效匹配 + 上下文继承) +历史对话:用户之前询问过"西藏定额升级的问题" +输入: +原始问题:怎么把旧版工程转到Z1新版 +关键词:【'老版本定额升级', '批量设置定额', '西藏造价软件Z1'】 +输出: +{{"rewrite":"【西藏造价软件Z1】如何执行【老版本定额升级】操作?"}} + +▶ 案例2(无效匹配 + 无上下文关联) +历史对话:无相关内容 +输入: +原始问题:程序界面文字显示过小如何处理? +关键词:【'定额升级', '工程批量导入'】 +输出: +{{"rewrite":"程序界面文字显示过小如何处理?"}} + +▶ 案例3(部分匹配 + 上下文补充) +历史对话:用户之前询问过"D3软件的功能" +输入: +原始问题:能导出清单的计算公式吗? +关键词:【'配网工程计价通D3软件', '计算式'】 +输出: +{{"rewrite":"【配网工程计价通D3软件】能导出清单的【计算式】吗?"}} + +▶ 案例4(代词替换 + 上下文解析) +历史对话:用户刚询问过"山东定额的问题" +输入: +原始问题:这个定额怎么批量导入? +关键词:【'批量导入定额', '山东定额'】 +输出: +{{"rewrite":"【山东定额】如何进行【批量导入定额】操作?"}} + +## 质量检查清单 +执行前请确认: +- [ ] 是否保持了原问题的核心诉求? +- [ ] 是否合理利用了历史对话中的背景信息? +- [ ] 是否正确执行了同义词替换? +- [ ] 是否保留了原问题中的专业术语和限定条件? +- [ ] 是否正确使用了【】标记? +- [ ] 重构后的问题是否自然流畅? +- [ ] 是否保持了对话的连贯性? +- [ ] 是否避免了过度补充不必要的信息? +""" + +query_rewrite_prompt_pro=""" +# 电力造价问答优化工程师(精简版) +**角色**:基于历史对话和专业术语库重构问题,提升知识库检索准确率。 + +## 核心原则 +1. 语义保真 → 保持问题核心意图 +2. 术语规范 → 同义词转标准词并【】标记 +3. 背景继承 → 补充历史对话的隐含信息 + +## 处理流程 +### 一、输入解析 + - 原始问题(需保留核心语义):{query} + - 关键词集合:{keywords} + - 历史对话记录: + + {chat_history} + + - 当前聊天背景: + + {context} + + +### 二、重构决策树 +```mermaid +graph TD + A[输入问题] --> B{{匹配关键词或上下文?}} + B -- 是 --> C[执行重构] + B -- 否 --> D[直接输出原始问题] + C --> E[补充缺失背景] + E --> F[同义词替换+【】标记] + F --> G[保留原生专业术语] +``` + +### 三、重构优先级 +1. **背景补充** + - 历史对话中确定的软件/地区必须继承(例:"这软件"→"【配网工程D3】") + +2. **术语处理** + - 同义词转标准词 → 批量设置定额 + - 存在即标记 → 【计算式】 + +3. **结构优化** + - 保持原问题的5W2H特征 + - 明确指代关系("该功能"→"【批量导入】功能") + +## 输出规范 +{output_format} + +## 典型案例 +| 场景 | 输入问题 | 输出结果 | +|---------------------|-----------------------------------|------------------------------------------| +| 强上下文关联 | “怎么升级旧版工程” | {{"rewrite":"【西藏Z1】如何执行【老版本定额升级】?"}} | +| 弱术语匹配 | “界面文字太小怎么办” | 原样输出 | +| 代词+背景继承 | “这个定额如何导入” | {{"rewrite":"【山东定额】如何执行【批量导入定额】?"}}| + +## 质量自检 +- [] 核心诉求是否保留? +- [] 背景信息是否合理补充? +- [] 术语标记是否完整【】? +- [] 语句是否自然流畅? +- [] 避免过度补充无关信息 +""" + + + +intent_and_slot_prompt = """ +# 电力造价软件意图分类与槽位填充统一提示词 + +你是一个专业的电力造价领域智能助手,负责对用户输入进行意图分类识别和关键信息槽位填充。 + +{classification_info} + +{slot_mapping_doc} + +## 【软件名称规范】 +支持的软件名称及其别名: +- **配网工程计价通D3软件**:别名包括配网D3、D3软件、配网工程软件等 +- **新型储能电站建设计价通C1软件**:别名包括储能C1、C1软件、储能电站软件、储能软件等 +- **西藏电力工程计价通Z1软件**:别名包括西藏Z1、Z1软件、西藏电力软件等 +- **技改检修工程计价通T1软件**:别名包括技改T1、T1软件、技改检修软件等 +- **技改检修清单计价通T1软件**:别名包括技改清单T1、T1清单软件、技改检修清单软件等 +- **主网电力建设计价通软件**:别名包括主网软件、电力建设软件、主网建设软件、博微电力建设计价通等 + +## 【任务要求】 + +1. **会话理解**:综合考虑会话背景、历史对话和之前的槽位信息来理解当前用户输入 +2. **意图分类**:准确识别用户输入属于哪个垂直领域和子分类 +3. **槽位填充**:从当前用户问题中提取关键信息,并结合历史槽位信息进行补充完善 +4. **信息融合**: + - 优先使用当前用户输入中的明确信息 + - 当前输入缺失但历史槽位存在的信息,可适当继承 + - 历史对话中的上下文信息有助于理解当前输入的真实意图 +5. **槽位处理**: + - 对于必填槽位,必须尽力从当前输入和历史信息中提取 + - 对于选填槽位,如果能从当前输入或历史信息中提取则填写 + - 如果当前输入与历史信息存在冲突,以当前输入为准 +6. **输出格式**:只输出符合格式的JSON数据,不要有任何额外的解释 + +## 【会话背景信息】 +{conversation_context} + +## 【历史对话记录】 +{chat_history} + +## 【历史槽位信息】 +{previous_slots} + +## 【当前用户输入】 +{user_input} + +## 【输出格式】 +{output_format} +""" + +output_example=""" +## 【综合分析示例】 + +**示例1:利用历史对话理解当前输入** +会话背景: 用户正在咨询软件使用问题 +历史对话: +- 用户: "我在使用配网D3软件" +- 助手: "好的,请问您遇到什么问题?" +历史槽位:{"software_name": "配网工程计价通D3软件"} +当前用户输入: "新建工程按钮找不到" + +输出: +```json +{ + "classification": { + "vertical_classification": "软件问题", + "sub_classification": "软件功能" + }, + "slot_filling": { + "software_name": "配网工程计价通D3软件", + "function_name": "新建工程", + "operation": "查找新建工程按钮位置", + "software_version": null, + "operation_steps": null + } +} +``` + +**示例2:继承和更新槽位信息** +会话背景: 用户遇到软件报错,正在进行故障排查 +历史对话: +- 用户: "西藏Z1软件报错了" +- 助手: "请详细描述一下报错信息" +历史槽位: {"software_name": "西藏电力工程计价通Z1软件"} +当前用户输入: "提示找不到许可证,是在新建工程的时候" + +输出: +```json +{ + "classification": { + "vertical_classification": "软件问题", + "sub_classification": "故障排查" + }, + "slot_filling": { + "software_name": "西藏电力工程计价通Z1软件", + "function_name": "新建工程", + "error_message": "提示找不到许可证", + "software_version": null, + "os_version": null, + "reproduction_steps": "新建工程时出现错误" + } +} +``` + +**示例3:信息冲突处理** +会话背景: 用户在多个软件间切换咨询 +历史对话: +- 用户: "配网D3的费用计算" +- 助手: "好的,关于配网D3的费用计算..." +历史槽位: {"software_name": "配网工程计价通D3软件"} +当前用户输入: "不对,我说的是技改T1软件的材料费怎么算" + +输出: +```json +{ + "classification": { + "vertical_classification": "业务问题", + "sub_classification": "数据问题" + }, + "slot_filling": { + "expense_type": "材料费", + "operation_purpose": "了解费用计算方法", + "software_name": "技改检修工程计价通T1软件", + "project_type": null + } +} +``` +""" + +def generate_slot_mapping_doc() -> str: + """ + 生成分类与槽位模型对应关系的文档 + Returns: + str: 格式化的文档字符串 + """ + mapping = { + "软件问题": { + "软件功能": "SoftwareFunctionSlots", + "故障排查": "SoftwareTroubleShootingSlots" + }, + "业务问题": { + "专业咨询": "ProfessionalConsultingSlots", + "数据问题": "DataProblemSlots" + }, + "安装下载注册": { + "后缀名咨询": "FileExtensionConsultingSlots", + "软件锁类": "SoftwareLockSlots", + "安装下载类": "InstallationDownloadSlots", + "问题排查类": "ProblemDiagnosisSlots" + }, + "其他": { + "其他": "OtherSlots" + } + } + + doc = ["## 【分类与槽位模型对应关系】"] + for vertical, sub_classes in mapping.items(): + doc.append(f"\n{vertical}:") + for sub_class, slot_model in sub_classes.items(): + doc.append(f"- {sub_class} -> {slot_model}") + + doc.append("\n## 【注意事项】") + doc.append("1. 分类与槽位模型必须严格对应") + doc.append("2. 每个分类只能使用其对应的槽位模型") + doc.append("3. 不允许混用不同分类的槽位模型") + + return "\n".join(doc) \ No newline at end of file diff --git a/rag2_0/tool/ModelTool.py b/rag2_0/tool/ModelTool.py index 19e6696..c3dfe78 100644 --- a/rag2_0/tool/ModelTool.py +++ b/rag2_0/tool/ModelTool.py @@ -118,7 +118,7 @@ class XinferenceReRankerModel: return [{"document": item["document"]["text"], "score": item["relevance_score"], "index": item["index"]} for item in results["results"]] except requests.exceptions.RequestException as e: - logging.error(f"重排序请求失败: {str(e)}") + logging.error(f"XinferenceReRankerModel重排序请求失败: {str(e)}") return []