Просмотр исходного кода

优化知识库查询工具
默认禁用知识库查询
优化换大模型后,单据识别场景的细节

longjoedyy 11 часов назад
Родитель
Сommit
cfe80e9a86
6 измененных файлов с 384 добавлено и 201 удалено
  1. 5 2
      config/settings.py
  2. 8 3
      config/template_config.json
  3. 39 15
      core/agent.py
  4. 128 0
      tools/base_tool.py
  5. 188 179
      tools/knowledge_tools.py
  6. 16 2
      tools/tool_factory.py

+ 5 - 2
config/settings.py

@@ -30,9 +30,12 @@ class Settings:
         )
 
         # 其他配置
-        self.LLM_MODEL =  os.getenv("LLM_MODEL", "qwen-flash")
-        self.LLM_TEMPERATURE = 0.7
+        self.LLM_MODEL = os.getenv("LLM_MODEL", "qwen-flash")
+        self.LLM_TEMPERATURE = 0.2
         self.LLM_MAX_TOKENS = 2048
+        self.KNOWLEDGE_BASE_ENABLED = (
+            os.getenv("KNOWLEDGE_BASE_ENABLED", "false").lower() == "true"
+        )
 
     def _load_env_file(self):
         """加载对应的.env文件"""

+ 8 - 3
config/template_config.json

@@ -28,10 +28,13 @@
                 "cusname": [
                     "我方/供货商:始终指 华宇,即销售方",
                     "客户:始终指向我方采购产品的公司,即采购方。在单据中,通常是标题或落款中的购买方",
-                    "例:域程家居-华宇兄弟采购明细,域程家居是客户,而不是华宇"
+                    "可能是聊天界面截图,顶部就是客户",
+                    "例子:域程家居-华宇兄弟采购明细,域程家居是客户,而不是华宇"
                 ],
                 "taskdate": [],
-                "requiredate": [],
+                "requiredate": [
+                    "'后天要,明天要,今个月15号'字样"
+                ],
                 "banktype": [],
                 "relcode": [],
                 "otheramt": [],
@@ -39,7 +42,9 @@
                 "cus_tele": [],
                 "rel_rep": [],
                 "dscrp": [
-                    "忽略最底部、大段的、格式化的条款备注"
+                    "一般是简短的描述",
+                    "如果位置底部的格式化条款,就算标明是备注,也忽略",
+                    "不要重复其他已识别的内容"
                 ],
                 "freight": [],
                 "mtrlname": [],

+ 39 - 15
core/agent.py

@@ -23,33 +23,57 @@ def create_system_prompt(
     backend_url: str = "", token: str = "", username: str = "default"
 ) -> str:
     auth_status = "已认证" if token else "未认证"
-    backend_available = "API可用" if backend_url and token else "仅知识库"
-
-    system_prompt = f"""小龙助手(龙嘉软件)- 用户:{username} 认证:{auth_status} 服务:{backend_available}
-
-职责:ERP问题解答,按用户语言回答。
-
+    backend_available = "API可用" if backend_url and token else "仅数据查询"
+    knowledge_status = (
+        "知识库可用" if settings.KNOWLEDGE_BASE_ENABLED else "知识库已禁用"
+    )
+    if settings.KNOWLEDGE_BASE_ENABLED:
+        # 知识库启用时的提示词
+        system_prompt = f"""龙嘉软件助手- 用户:{username} 认证:{auth_status} 服务:{backend_available} 知识库:{knowledge_status}
+职责:ERP数据查询和问题解答,按用户语言回答。所有回答严格按工具提供的数据,不能编造答案。
 工作流:
 1. 分析问题意图,提取模块关键词
-2. {"优先知识库搜索,需要时调用API" if token else "仅使用知识库搜索"}
-3. 关键词要精准,避免无意义词
-
+2. 如果是数据查询类问题,直接调用相关工具查询数据
+3. 如果是其他问题,则通过工具搜索知识库,知识库工具使用流程:a.通过关键字获取相关文章列表,b.判断哪些文章最符合,c.再通过工具获取文章内容.严格按文章内容回复,不能编造答案.
+4. 关键词要精准,避免无意义词
 回答规则:
-- 知识库优先,找不到时提示"正在学习该问题"
-- {"需要个人数据时验证认证状态" if backend_url else "仅提供知识库支持"}
-- 保护隐私,专业准确
-
+- 知识库找不到时提示"正在学习该问题"
+- {"需要个人数据时验证认证状态" if backend_url else "仅提供数据查询和知识库支持"}
+- 保护隐私,专业准确,精炼简要
+{"- 后端地址: " + backend_url if backend_url else ""}
+{"- API用户的认证令牌: " + token if token else ""}
+| :--- | :--- | :--- |
+| 数据1 | 数据2 | 数据3 |
+| 数据4 | 数据5 | 数据6 |
+"""
+    else:
+        # 知识库禁用时的提示词 - 严格限制回答范围
+        system_prompt = f"""龙嘉软件助手- 用户:{username} 认证:{auth_status} 服务:{backend_available} 知识库:{knowledge_status}
+职责:仅处理ERP数据查询类问题。
+严格限制:
+- 知识库功能已禁用,无法回答任何非数据查询类问题
+- 禁止回答:疑问解答、操作流程、功能介绍、知识咨询等非数据查询问题
+- 禁止使用个人知识或经验进行回答
+工作流:
+1. 分析问题意图,判断是否为数据查询类问题
+2. 如果是数据查询类问题,直接调用相关工具查询数据
+3. 如果是非数据查询类问题(包括疑问、流程、操作等),必须明确回复:"知识库正在完善,无法回答该问题"
+回答规则:
+- 非数据查询问题必须回复:"知识库正在完善,无法回答该问题"
+- 禁止尝试回答或提供任何建议
+- 禁止解释原因或提供替代方案
+- 严格按工具提供的数据回答数据查询问题,不能编造答案
+- {"需要个人数据时验证认证状态" if backend_url else "仅提供数据查询支持"}
 {"- 后端地址: " + backend_url if backend_url else ""}
 {"- API用户的认证令牌: " + token if token else ""}
 时间:{datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")}
-
 数据查询结果尽量以 Markdown 表格格式输出,格式如下:
 | 列名1 | 列名2 | 列名3 |
 | :--- | :--- | :--- |
 | 数据1 | 数据2 | 数据3 |
 | 数据4 | 数据5 | 数据6 |
 """
-
+    print(system_prompt)
     return system_prompt
 
 

+ 128 - 0
tools/base_tool.py

@@ -31,6 +31,134 @@ def get_unique_match_count(search_text: str, filter_words: List[str]) -> int:
     return match_count
 
 
+def calculate_relevance_score(
+    doc_name: str, doc_keywords: str, search_keywords: List[str]
+) -> float:
+    """
+    计算文档与搜索关键词的相关性得分
+
+    Args:
+        doc_name: 文档标题
+        doc_keywords: 文档关键词
+        search_keywords: 搜索关键词列表
+
+    Returns:
+        相关性得分(0-100)
+    """
+    # 合并搜索文本
+    search_text = f"{doc_name} {doc_keywords}".lower()
+    search_keywords_lower = [kw.lower() for kw in search_keywords]
+
+    # 权重设置
+    TITLE_WEIGHT = 0.7  # 标题权重(提高)
+    KEYWORD_WEIGHT = 0.3  # 关键词权重
+    EXACT_MATCH_BONUS = 0.5  # 精确匹配奖励(提高)
+    PARTIAL_MATCH_FACTOR = 0.3  # 部分匹配因子
+
+    total_score = 0.0
+
+    # 1. 标题匹配得分(重新设计)
+    title_score = 0.0
+    doc_name_lower = doc_name.lower()
+
+    for keyword in search_keywords_lower:
+        if keyword in doc_name_lower:
+            # 基础得分:基于关键词长度和位置
+            base_score = min(len(keyword) * 2, 10)  # 每个字符2分,最多10分
+
+            # 位置权重:标题开头和结尾的匹配更重要
+            if doc_name_lower.startswith(keyword):
+                base_score *= 1.5
+            elif doc_name_lower.endswith(keyword):
+                base_score *= 1.3
+
+            # 精确匹配奖励:完全包含关键词
+            if f" {keyword} " in f" {doc_name_lower} ":
+                base_score *= 1 + EXACT_MATCH_BONUS
+
+            title_score += base_score
+
+    # 2. 关键词匹配得分
+    keyword_score = 0.0
+    for keyword in search_keywords_lower:
+        if keyword in doc_keywords.lower():
+            keyword_score += min(len(keyword) * 1.5, 8)  # 每个字符1.5分,最多8分
+
+    # 3. 计算总得分
+    total_score = (title_score * TITLE_WEIGHT) + (keyword_score * KEYWORD_WEIGHT)
+
+    # 4. 匹配数量奖励(重要改进)
+    matched_count = sum(1 for kw in search_keywords_lower if kw in search_text)
+    if matched_count > 0:
+        coverage_ratio = matched_count / len(search_keywords_lower)
+        # 匹配越多,奖励越大
+        total_score *= 1 + coverage_ratio * 0.5
+
+    # 5. 特殊关键词优先级(针对你的具体需求)
+    priority_keywords = ["交期", "审核", "修改", "终止", "失败", "错误", "明细", "功能"]
+    for keyword in priority_keywords:
+        if keyword in search_keywords_lower and keyword in search_text:
+            total_score *= 1.2  # 提高优先级关键词奖励
+
+    # 6. 长标题惩罚调整(避免长标题得分过低)
+    if len(doc_name) > 30:
+        # 长标题轻微惩罚,但不要过度惩罚
+        total_score *= 0.9
+
+    # 7. 确保至少匹配一个关键词就有基础分
+    if matched_count == 0:
+        return 0.0
+
+    return min(total_score, 100.0)
+
+
+def find_most_relevant_document(
+    doc_list: List[dict], search_keywords: List[str], max_matches: int = 10
+) -> List[dict]:
+    """
+    找到最相关的文档
+
+    Args:
+        doc_list: 文档列表
+        search_keywords: 搜索关键词
+        max_matches: 最大返回数量(增加到10)
+
+    Returns:
+        按相关性排序的文档列表
+    """
+    scored_docs = []
+
+    for doc in doc_list:
+        doc_id = doc["DocID"]
+        doc_name = doc["DocName"]
+        doc_keywords = doc.get("keyword", "")
+
+        # 计算相关性得分
+        score = calculate_relevance_score(doc_name, doc_keywords, search_keywords)
+
+        # 降低过滤门槛,只要匹配至少一个关键词就考虑
+        match_count = sum(
+            1
+            for kw in search_keywords
+            if kw.lower() in f"{doc_name} {doc_keywords}".lower()
+        )
+        if match_count > 0:
+            scored_docs.append(
+                {
+                    "doc_id": doc_id,
+                    "doc_name": doc_name,
+                    "keywords": doc_keywords,
+                    "relevance_score": score,
+                    "match_count": match_count,
+                }
+            )
+
+    # 按相关性得分降序排序
+    scored_docs.sort(key=lambda x: x["relevance_score"], reverse=True)
+
+    return scored_docs[:max_matches]
+
+
 def call_csharp_api(
     backend_url: str, token: str, uoName: str, functionName: str, SParms: dict
 ) -> str:

+ 188 - 179
tools/knowledge_tools.py

@@ -3,24 +3,138 @@ from typing import List
 import requests
 import json
 import os
-from .base_tool import html_to_text, get_unique_match_count
+from .base_tool import (
+    html_to_text,
+    get_unique_match_count,
+    calculate_relevance_score,
+    find_most_relevant_document,
+)
 from config.settings import settings
 
 
-# @tool
-# def get_knowledge_list(filter_words: List[str], match_limit: int = 3) -> str:
-#     """根据关键词筛选知识库文章列表
+@tool
+def get_knowledge_list(filter_words: List[str], match_limit: int = 3) -> str:
+    """根据关键词筛选知识库文章列表
+    拆分关键词核心原则:
+    #     最小化原则:将用户问题拆分为最小单位的关键词,最好2个字一个关键词
+    #     例如:"销售订单终止数量失败" → ["销售", "订单", "终止", "数量", "失败"],"销售订单提示没有新建权限怎么办" → ["销售", "订单", "新建", "权限"]
+        Args:
+            filter_words: 关键词列表,匹配任一关键词即返回
+            match_limit: 最小匹配数(默认3),无结果时可减少重试(最小1)
+
+        Returns:
+            文章列表,格式:每行"DocID:DocName|keyword",用于后续获取内容
+    """
+    print(f"正在查询知识库列表,筛选关键词:{filter_words} 匹配下限:{match_limit}")
+
+    kms_list_url = settings.KMS_LIST_URL  # os.getenv("KMS_LIST_URL")
+    payload = {
+        "categorycodeList": [],
+        "ignoreTypeSub": False,
+        "ignoreStandardByTopic": True,
+    }
+
+    headers = {
+        "Accept": "application/json, text/plain, */*",
+        "Content-Type": "application/json",
+        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
+    }
+
+    try:
+        response = requests.post(
+            kms_list_url, headers=headers, json=payload, timeout=10
+        )
+        if response.status_code == 200:
+            data = response.json()
+            matched_lines = ""
+            for doc in data["docList"]:
+                doc_id = doc["DocID"]
+                doc_name = doc["DocName"]
+                doc_keywords = doc["keyword"]
+                search_text = f"{doc_name} {doc_keywords}".lower()
+
+                if not filter_words:
+                    line = (
+                        f"{doc_id}:{doc_name}|{doc_keywords}"
+                        if doc_keywords
+                        else f"{doc_id}:{doc_name}"
+                    )
+                    matched_lines += line + "\n"
+                else:
+                    match_count = get_unique_match_count(search_text, filter_words)
+                    if match_count >= match_limit:
+                        line = (
+                            f"{doc_id}:{doc_name}|{doc_keywords}"
+                            if doc_keywords
+                            else f"{doc_id}:{doc_name}"
+                        )
+                        matched_lines += line + "\n"
+
+            return matched_lines
+        else:
+            return f"请求失败,状态码: {response.status_code}"
+    except Exception as e:
+        return f"请求异常: {e}"
+
 
+@tool
+def get_knowledge_content(docid: str) -> str:
+    """获取知识库文章内容
+
+    Args:
+        docid: 知识库文章的DocID
+
+    Returns:
+        知识库文章内容
+    """
+    print(f"正在获取知识库文章内容,DocID: {docid}")
+
+    kms_view_url = settings.KMS_VIEW_URL  # os.getenv("KMS_VIEW_URL")
+    headers = {
+        "Accept": "application/json, text/plain, */*",
+        "Content-Type": "application/json",
+        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
+    }
+
+    try:
+        payload = {"docid": docid}
+        response = requests.post(
+            kms_view_url, headers=headers, json=payload, timeout=10
+        )
+        if response.status_code == 200:
+            data = response.json()
+            doc_html = data.get("DocHtml", "")
+            plain_text = html_to_text(doc_html)
+            print(f"已获取到ID: {docid}的文章内容,长度{len(plain_text)}")
+            return plain_text
+        else:
+            return f"请求失败,状态码: {response.status_code}"
+    except Exception as e:
+        return f"请求异常: {e}"
+
+
+# @tool
+# def search_and_retrieve_knowledge(keywords: List[str], max_matches: int = 10) -> str:
+#     """搜索并获取最相关的知识库文章内容
+
+#     此工具会:
+#     1. 自动搜索知识库文章列表
+#     2. 使用改进的智能算法根据关键词匹配度排序,选择最相关的文章
+#     3. 自动获取并返回该文章的完整内容
+#     拆分关键词核心原则:
+#     最小化原则:将用户问题拆分为最小单位的关键词,最好2个字一个关键词
+#     例如:"销售订单终止数量失败" → ["销售", "订单", "终止", "数量", "失败"],"销售订单提示没有新建权限怎么办" → ["销售", "订单", "新建", "权限"]
 #     Args:
-#         filter_words: 关键词列表,匹配任一关键词即返回
-#         match_limit: 最小匹配数(默认3),无结果时可减少重试(最小1)
+#         keywords: 关键词列表,会尽可能细化匹配
+#         max_matches: 最多考虑的匹配文章数(默认10)
 
 #     Returns:
-#         文章列表,格式:每行"DocID:DocName|keyword",用于后续获取内容
+#         最相关文章的完整内容
 #     """
-#     print(f"正在查询知识库列表,筛选关键词:{filter_words} 匹配下限:{match_limit}")
+#     print(f"正在搜索知识库,关键词:{keywords}")
 
-#     kms_list_url = settings.KMS_LIST_URL  # os.getenv("KMS_LIST_URL")
+#     # 1. 获取文章列表
+#     kms_list_url = settings.KMS_LIST_URL
 #     payload = {
 #         "categorycodeList": [],
 #         "ignoreTypeSub": False,
@@ -34,183 +148,78 @@ from config.settings import settings
 #     }
 
 #     try:
+#         # 获取所有文章
 #         response = requests.post(
 #             kms_list_url, headers=headers, json=payload, timeout=10
 #         )
-#         if response.status_code == 200:
-#             data = response.json()
-#             matched_lines = ""
-#             for doc in data["docList"]:
-#                 doc_id = doc["DocID"]
-#                 doc_name = doc["DocName"]
-#                 doc_keywords = doc["keyword"]
-#                 search_text = f"{doc_name} {doc_keywords}".lower()
-
-#                 if not filter_words:
-#                     line = (
-#                         f"{doc_id}:{doc_name}|{doc_keywords}"
-#                         if doc_keywords
-#                         else f"{doc_id}:{doc_name}"
-#                     )
-#                     matched_lines += line + "\n"
-#                 else:
-#                     match_count = get_unique_match_count(search_text, filter_words)
-#                     if match_count >= match_limit:
-#                         line = (
-#                             f"{doc_id}:{doc_name}|{doc_keywords}"
-#                             if doc_keywords
-#                             else f"{doc_id}:{doc_name}"
-#                         )
-#                         matched_lines += line + "\n"
-
-#             return matched_lines
-#         else:
-#             return f"请求失败,状态码: {response.status_code}"
-#     except Exception as e:
-#         return f"请求异常: {e}"
-
-
-# @tool
-# def get_knowledge_content(docid: str) -> str:
-#     """获取知识库文章内容
-
-#     Args:
-#         docid: 知识库文章的DocID
-
-#     Returns:
-#         知识库文章内容
-#     """
-#     print(f"正在获取知识库文章内容,DocID: {docid}")
-
-#     kms_view_url = settings.KMS_VIEW_URL  # os.getenv("KMS_VIEW_URL")
-#     headers = {
-#         "Accept": "application/json, text/plain, */*",
-#         "Content-Type": "application/json",
-#         "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
-#     }
-
-#     try:
-#         payload = {"docid": docid}
-#         response = requests.post(
-#             kms_view_url, headers=headers, json=payload, timeout=10
+#         if response.status_code != 200:
+#             return f"获取文章列表失败,状态码: {response.status_code}"
+
+#         data = response.json()
+#         doc_list = data.get("docList", [])
+
+#         if not doc_list:
+#             return "知识库中没有找到文章"
+
+#         # 打印所有文章标题用于调试
+#         print(f"知识库中共有 {len(doc_list)} 篇文章")
+#         print("所有文章标题:")
+#         for i, doc in enumerate(doc_list[:20]):  # 只显示前20个
+#             print(f"  {i+1}. {doc['DocName']}")
+
+#         # 2. 使用改进的算法找到最相关的文档
+#         relevant_docs = find_most_relevant_document(doc_list, keywords, max_matches)
+
+#         if not relevant_docs:
+#             # 如果没有找到匹配的文档,尝试使用更宽松的匹配
+#             print("使用宽松匹配重新搜索...")
+#             # 只保留核心关键词重新搜索
+#             core_keywords = [kw for kw in keywords if len(kw) >= 2]
+#             if core_keywords:
+#                 relevant_docs = find_most_relevant_document(
+#                     doc_list, core_keywords, max_matches
+#                 )
+
+#         if not relevant_docs:
+#             return "没有找到与关键词相关的文章"
+
+#         print(f"找到 {len(relevant_docs)} 篇相关文章,按相关性排序")
+#         for i, doc in enumerate(relevant_docs):
+#             print(
+#                 f"  {i+1}. {doc['doc_name']} (得分:{doc['relevance_score']:.2f}, 匹配{doc['match_count']}个关键词)"
+#             )
+
+#         # 3. 获取最相关的文章内容
+#         best_doc = relevant_docs[0]
+#         print(
+#             f"选择最相关的文章: {best_doc['doc_name']} (DocID: {best_doc['doc_id']}, 得分:{best_doc['relevance_score']:.2f})"
 #         )
-#         if response.status_code == 200:
-#             data = response.json()
-#             doc_html = data.get("DocHtml", "")
-#             plain_text = html_to_text(doc_html)
-#             print(f"已获取到ID: {docid}的文章内容,长度{len(plain_text)}")
-#             return plain_text
-#         else:
-#             return f"请求失败,状态码: {response.status_code}"
-#     except Exception as e:
-#         return f"请求异常: {e}"
-
 
-@tool
-def search_and_retrieve_knowledge(keywords: List[str], max_matches: int = 5) -> str:
-    """搜索并获取最相关的知识库文章内容
-
-    此工具会:
-    1. 自动搜索知识库文章列表
-    2. 根据关键词匹配度排序,选择匹配最多关键词的文章
-    3. 自动获取并返回该文章的完整内容
-    拆分关键词核心原则:
-    最小化原则:将用户问题拆分为最小单位的关键词,最好2个字一个关键词
-    例如:"销售订单终止数量失败" → ["销售", "订单", "终止", "数量", "失败"],"销售订单提示没有新建权限怎么办" → ["销售", "订单", "新建", "权限"]
-    Args:
-        keywords: 关键词列表,会尽可能细化匹配
-        max_matches: 最多考虑的匹配文章数(默认5)
-
-    Returns:
-        最相关文章的完整内容
-    """
-    print(f"正在搜索知识库,关键词:{keywords}")
-
-    # 1. 获取文章列表
-    kms_list_url = settings.KMS_LIST_URL
-    payload = {
-        "categorycodeList": [],
-        "ignoreTypeSub": False,
-        "ignoreStandardByTopic": True,
-    }
-
-    headers = {
-        "Accept": "application/json, text/plain, */*",
-        "Content-Type": "application/json",
-        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
-    }
-
-    try:
-        # 获取所有文章
-        response = requests.post(
-            kms_list_url, headers=headers, json=payload, timeout=10
-        )
-        if response.status_code != 200:
-            return f"获取文章列表失败,状态码: {response.status_code}"
-
-        data = response.json()
-        doc_list = data.get("docList", [])
-
-        if not doc_list:
-            return "知识库中没有找到文章"
-
-        # 2. 计算匹配度并排序
-        matched_docs = []
-        for doc in doc_list:
-            doc_id = doc["DocID"]
-            doc_name = doc["DocName"]
-            doc_keywords = doc["keyword"]
-            search_text = f"{doc_name} {doc_keywords}".lower()
-
-            # 计算匹配的关键词数量
-            match_count = get_unique_match_count(search_text, keywords)
-            if match_count > 0:
-                matched_docs.append({
-                    "doc_id": doc_id,
-                    "doc_name": doc_name,
-                    "match_count": match_count,
-                    "keywords": doc_keywords
-                })
-
-        if not matched_docs:
-            return "没有找到与关键词相关的文章"
-
-        # 按匹配数量降序排序,选择前max_matches个
-        matched_docs.sort(key=lambda x: x["match_count"], reverse=True)
-        top_docs = matched_docs[:max_matches]
-
-        print(f"找到 {len(matched_docs)} 篇相关文章,前{len(top_docs)}篇匹配度最高")
-        for i, doc in enumerate(top_docs):
-            print(f"  {i+1}. {doc['doc_name']} (匹配{doc['match_count']}个关键词)")
-
-        # 3. 获取匹配度最高的文章内容
-        best_doc = top_docs[0]
-        print(f"选择最相关的文章: {best_doc['doc_name']} (DocID: {best_doc['doc_id']})")
-
-        # 4. 获取文章内容
-        kms_view_url = settings.KMS_VIEW_URL
-        content_payload = {"docid": best_doc['doc_id']}
-        content_response = requests.post(
-            kms_view_url, headers=headers, json=content_payload, timeout=10
-        )
+#         # 4. 获取文章内容
+#         kms_view_url = settings.KMS_VIEW_URL
+#         content_payload = {"docid": best_doc["doc_id"]}
+#         content_response = requests.post(
+#             kms_view_url, headers=headers, json=content_payload, timeout=10
+#         )
 
-        if content_response.status_code != 200:
-            return f"获取文章内容失败,状态码: {content_response.status_code}"
+#         if content_response.status_code != 200:
+#             return f"获取文章内容失败,状态码: {content_response.status_code}"
 
-        content_data = content_response.json()
-        doc_html = content_data.get("DocHtml", "")
-        plain_text = html_to_text(doc_html)
+#         content_data = content_response.json()
+#         doc_html = content_data.get("DocHtml", "")
+#         plain_text = html_to_text(doc_html)
 
-        # 5. 构建返回结果
-        result = f"【知识库文章】\n"
-        result += f"标题: {best_doc['doc_name']}\n"
-        result += f"匹配关键词数量: {best_doc['match_count']}\n"
-        if best_doc['keywords']:
-            result += f"文章关键词: {best_doc['keywords']}\n"
-        result += f"内容: {plain_text}\n"
+#         # 5. 构建返回结果
+#         result = f"【知识库文章】\n"
+#         result += f"标题: {best_doc['doc_name']}\n"
+#         result += f"相关性得分: {best_doc['relevance_score']:.2f}\n"
+#         result += f"匹配关键词数量: {best_doc['match_count']}\n"
+#         if best_doc["keywords"]:
+#             result += f"文章关键词: {best_doc['keywords']}\n"
+#         result += f"内容: {plain_text}\n"
 
-        print(f"已获取文章内容,长度: {len(plain_text)} 字符")
-        return result
+#         print(f"已获取文章内容,长度: {len(plain_text)} 字符")
+#         return result
 
-    except Exception as e:
-        return f"搜索和获取知识库文章时出错: {e}"
+#     except Exception as e:
+#         return f"搜索和获取知识库文章时出错: {e}"

+ 16 - 2
tools/tool_factory.py

@@ -22,6 +22,8 @@ def get_all_tools() -> List[BaseTool]:
     # 将项目根目录添加到Python路径
     if str(project_root) not in sys.path:
         sys.path.insert(0, str(project_root))
+    # 导入配置以检查知识库开关
+    from config.settings import settings
 
     # 扫描工具文件
     tool_files = []
@@ -29,20 +31,32 @@ def get_all_tools() -> List[BaseTool]:
     for file_path in tools_dir.glob("*_tools.py"):
         if file_path.is_file():
             module_name = file_path.stem
+            # 如果知识库被禁用,跳过知识库工具
+            if module_name == "knowledge_tools" and not settings.KNOWLEDGE_BASE_ENABLED:
+                print("知识库功能已禁用,跳过知识库工具")
+                continue
             tool_files.append(module_name)
-            print(f"📦 发现工具文件: {module_name}")
+            print(f"发现工具文件: {module_name}")
 
     # 模式2: 编译后的.pyd文件
     for file_path in tools_dir.glob("*_tools.cp*.pyd"):
         if file_path.is_file():
             # 从文件名中提取模块名,如: ware_tools.cp313-win_amd64.pyd -> ware_tools
             module_name = file_path.stem.split(".")[0]
+            # 如果知识库被禁用,跳过知识库工具
+            if module_name == "knowledge_tools" and not settings.KNOWLEDGE_BASE_ENABLED:
+                print("知识库功能已禁用,跳过知识库工具")
+                continue
             if module_name not in tool_files:  # 避免重复添加
                 tool_files.append(module_name)
                 print(f"发现编译后工具文件: {module_name}")
 
     if not tool_files:
-        tool_files = ["knowledge_tools", "sale_tools", "ware_tools"]
+        # 如果知识库被禁用,从默认列表中移除知识库工具
+        default_tools = ["sale_tools", "ware_tools", "price_tools", "money_tools"]
+        if settings.KNOWLEDGE_BASE_ENABLED:
+            default_tools.insert(0, "knowledge_tools")
+        tool_files = default_tools
         print("使用默认工具列表")
 
     for module_name in tool_files: