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\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 []