jiyuhang 3 settimane fa
parent
commit
0909bed91a
49 ha cambiato i file con 1003 aggiunte e 52 eliminazioni
  1. 0 0
      api_tools/__init__.py
  2. 49 0
      api_tools/access_plc_api.py
  3. 30 6
      db/water_treatment_chunk_meta.json
  4. BIN
      db/water_treatment_knowledge.faiss
  5. 0 0
      db/water_treatment_text_meta.json
  6. 123 45
      knowledge_robot.py
  7. 0 0
      plc_query/__init__.py
  8. 0 0
      plc_query/plc_dictionary/1181_plc_dictionary/1181_dict_level_1.json
  9. 0 0
      plc_query/plc_dictionary/1181_plc_dictionary/1181_dict_level_2.json
  10. 0 0
      plc_query/plc_dictionary/1181_plc_dictionary/1181_dict_name_2_code.json
  11. BIN
      plc_query/plc_dictionary/1181_plc_dictionary/1181_knowledge.faiss
  12. BIN
      plc_query/plc_dictionary/1181_plc_dictionary/1181_点位.xlsx
  13. 0 0
      plc_query/plc_dictionary/1202_plc_dictionary/1202_dict_level_1.json
  14. 0 0
      plc_query/plc_dictionary/1202_plc_dictionary/1202_dict_level_2.json
  15. 0 0
      plc_query/plc_dictionary/1202_plc_dictionary/1202_dict_name_2_code.json
  16. BIN
      plc_query/plc_dictionary/1202_plc_dictionary/1202_knowledge.faiss
  17. BIN
      plc_query/plc_dictionary/1202_plc_dictionary/1202_点位.xlsx
  18. 0 0
      plc_query/plc_dictionary/1450_plc_dictionary/1450_dict_level_1.json
  19. 0 0
      plc_query/plc_dictionary/1450_plc_dictionary/1450_dict_level_2.json
  20. 0 0
      plc_query/plc_dictionary/1450_plc_dictionary/1450_dict_name_2_code.json
  21. BIN
      plc_query/plc_dictionary/1450_plc_dictionary/1450_knowledge.faiss
  22. BIN
      plc_query/plc_dictionary/1450_plc_dictionary/1450_点位.xlsx
  23. 0 0
      plc_query/plc_dictionary/92_plc_dictionary/92_dict_level_1.json
  24. 0 0
      plc_query/plc_dictionary/92_plc_dictionary/92_dict_level_2.json
  25. 0 0
      plc_query/plc_dictionary/92_plc_dictionary/92_dict_name_2_code.json
  26. BIN
      plc_query/plc_dictionary/92_plc_dictionary/92_knowledge.faiss
  27. BIN
      plc_query/plc_dictionary/92_plc_dictionary/92_点位.xlsx
  28. 345 0
      plc_query/plclib.py
  29. 8 0
      plc_query/url_config.json
  30. 1 0
      plc_query/user_maintain_dictionary/equivalent_words/dict_equivalent_wordmap.json
  31. 9 0
      plc_query/user_maintain_dictionary/equivalent_words/equivalent_wordmap.txt
  32. 40 0
      plc_query/user_maintain_dictionary/jieba_words/A综合.txt
  33. 9 0
      plc_query/user_maintain_dictionary/jieba_words/B产水率.txt
  34. 40 0
      plc_query/user_maintain_dictionary/jieba_words/B回收率.txt
  35. 15 0
      plc_query/user_maintain_dictionary/jieba_words/B电导.txt
  36. 5 0
      plc_query/user_maintain_dictionary/jieba_words/B脱盐率.txt
  37. 18 0
      plc_query/user_maintain_dictionary/jieba_words/C膜渗透率.txt
  38. 53 0
      plc_query/user_maintain_dictionary/jieba_words/C膜通量.txt
  39. 9 0
      plc_query/user_maintain_dictionary/jieba_words/C跨膜压差.txt
  40. 21 0
      plc_query/user_maintain_dictionary/jieba_words/产水压力.txt
  41. 30 0
      plc_query/user_maintain_dictionary/jieba_words/产水流量.txt
  42. 27 0
      plc_query/user_maintain_dictionary/jieba_words/段压差.txt
  43. 18 0
      plc_query/user_maintain_dictionary/jieba_words/浓水压力.txt
  44. 6 0
      plc_query/user_maintain_dictionary/jieba_words/浓水流量.txt
  45. 37 0
      plc_query/user_maintain_dictionary/jieba_words/进水压力.txt
  46. 24 0
      plc_query/user_maintain_dictionary/jieba_words/进水流量.txt
  47. 23 0
      plc_query_tool.py
  48. 1 1
      text_vector_database.py
  49. 62 0
      web_tool.py

+ 0 - 0
api_tools/__init__.py


+ 49 - 0
api_tools/access_plc_api.py

@@ -0,0 +1,49 @@
+import requests
+import time
+
+def access_plc_query_api(url: str,headers:dict, data: list, max_retries=2):
+    """调用bge-m3,embedding"""
+    # 类型检查
+    time.sleep(0.010)  # 方式频繁调用接口
+    for attempt in range(max_retries):
+        try:
+            response = requests.post(url=url, headers=headers, json=data)
+            if response.status_code == 200:
+                return response.json()
+        except Exception as e:
+            print('请求PLC点位查询接口失败!', e)
+            time.sleep(1)
+            return None
+    return None
+
+def sparse_plc_api_response(response):
+    data = response.get('data')
+    code = response.get('code')
+    if int(code) != 200 or data is None:
+        print(f"访问plc接口失败,状态码:{code}, 返回数据有效:{bool(data)}")
+        return None
+    # 提取名称,时间和值
+    plc_query_results = []  # 查询结果
+    for item in data:
+        plc_query_results.append(f"PLC点位名称:{item.get('alias')},记录时间:{item.get('htime')}, 值:{item.get('val')}")
+
+    return plc_query_results
+
+def post_req_2_plc_api(project_id:int, database_codes:list):
+    headers = {"Content-Type": "application/json",
+               "JWT-TOKEN": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJJRCI6NywiVXNlcm5hbWUiOiJhZG1pbiIsIkRlcCI6IjEzNSIsImV4cCI6MTc3NjExOTExNCwiaXNzIjoiZ2luLWJsb2cifQ.0HTtzHZjyd2mHo8VCy8icYROxmntRMuQhyoZsAYRL_M"}
+    # 构造数据
+    data = [{"deviceId":"1","deviceItems":db_code,"deviceName":"外供水PH","project_id":project_id} for db_code in database_codes]
+    # 查询结果
+    resp = access_plc_query_api(url='http://120.55.44.4:8900/api/v1/jinke-cloud/device/current-data?time=1739859286292',
+                      headers=headers, data=data)
+    # 解析结果
+    res = sparse_plc_api_response(resp)
+    return res
+if '__main__' == __name__:
+    res = post_req_2_plc_api(1420,['ns=3;s=1#RO_CSDD_O','ns=3;s=2#RO_CSDD_O'])
+    print('请求结果', res)
+
+
+
+

+ 30 - 6
db/water_treatment_chunk_meta.json

@@ -48,15 +48,39 @@
         "father": "RO计算2.md"
     },
     "12": {
-        "content": "温度校正系数数值表。温度从10.0°C到29.9°C,对应的TCF值从1.711到0.866。温度升高时,TCF值逐渐减小,例如10.0°C时TCF为1.711,20.0°C时为1.189,29.9°C时为0.866",
-        "father": "杜邦TCF.md"
+        "content": "。\n反渗透膜技术发展历程。1950年,加州大学洛杉矶分校的Gerald Hassler首次提出了\"盐排斥渗透膜\"概念,提供理论依据(反渗透技术起源)。1959年索里拉金和洛布制备不对成结构反渗透膜,为反渗透工业化应用提供关键技术支持(早期研究)。1965年,世界第一个苦咸水反渗透工厂建立。1968年反渗透膜技术应用于航天领域(初期应用)。1977年,Film Tec公司成立,界面聚合法制成的三层结构复合膜大幅提升膜性能,成为商业化卷式反渗透膜标准工艺(技术突破)。1990年代初,商业化的中低压及超低压高脱盐聚酰胺复合膜产品进入市场,大幅降低了运行压力和能耗 (性能提升) 。截至2022年底,反渗透技术广泛应用于海水淡化,并拓展至市政供水、工业用水、废水处理,航天及医疗领域(广泛应用)。\n反渗透技术特性与广泛应用领域。反渗透膜技术是一种基于半透膜的膜分离技术,通过施加高于溶液渗透压的压力,使水分子逆向通过半透膜,而溶质(如盐分、有机物、微生物等)被截留,从而实现水与溶质的高效分离。能阻挡所有溶解性盐及分子量大于 100 的有机物,但允许水分子透过。从传统的海水淡化、工业纯水和锅炉补给水制备、饮用水制备到新兴的废水会用、盐湖提锂等,以及食品领域和医疗领域。节能高效、高脱盐率与水质稳定性、自动化程度高、环保与可持续性。\n\n",
+        "father": "反渗透基础介绍.md"
     },
     "13": {
-        "content": "。校正流量计算方法。校正后流量等于实测流量乘以对应于进水温度的TCF值。表格数据由杜邦公司提供,相关商标归杜邦公司所有。",
-        "father": "杜邦TCF.md"
+        "content": "。\n进水温度对通量和脱盐率的影响。产水电导对进水温度变化非常敏感,随着水温增加,通量几乎线性地增大,是因为透过膜的水分子粘度下降、扩散能力增加。增加水温会导致脱盐率降低,这是因为盐分透过膜的扩散速率随温度提高加快所致",
+        "father": "反渗透工作机理—膜产水通量和脱盐率的影响机制.md"
     },
     "14": {
-        "content": "本手册介绍反渗透和纳滤膜产品的基本技术信息。",
-        "father": "海德能计算方法.md"
+        "content": "。\n盐浓度对通量和脱盐率的影响。盐浓度增加,渗透压也增加,如果压力保持恒定,含盐量越高,通量就越低,渗透压的增加抵消了进水推动力,水通量降低,增加了透过膜的盐通量(降低了脱盐率)",
+        "father": "反渗透工作机理—膜产水通量和脱盐率的影响机制.md"
+    },
+    "15": {
+        "content": "。\n进水PH对通量和脱盐率的影响。膜脱盐率特性取决于 pH 值,水通量也会受到影响",
+        "father": "反渗透工作机理—膜产水通量和脱盐率的影响机制.md"
+    },
+    "16": {
+        "content": "。\n增加回收率对通量和脱盐率的影响。回收率增加(进水压力恒定),残留在原水中的含盐量更高,自然渗透压将不断增加直至与施加的压力相同,这将抵消进水压力的推动作用,减慢或停止反渗透过程,使渗透通量降低或甚至停止",
+        "father": "反渗透工作机理—膜产水通量和脱盐率的影响机制.md"
+    },
+    "17": {
+        "content": "。\n\n",
+        "father": "反渗透工作机理—膜产水通量和脱盐率的影响机制.md"
+    },
+    "18": {
+        "content": "。\n超滤膜技术发展历程。1748年,德国科学家Schmidt进行棉花胶膜分滤实验,膜分离原理的初步探索(超滤技术起源)。1907年,多孔火棉胶膜为超滤技术提供了材料基础。1963年,工业化非对称性乙酸纤维素膜出现(早期研究)。1960年代初,分子量级概念的提出。1971年,中空纤维超滤膜工业化应用(初期应用)。1980年代初,非对称膜结构的出现。PVDF/PES/PAN膜材料。相转化法制备工艺成为主流(技术突破)。1990年代初,膜材料和膜结构进一步发展。应用水和工业领域广泛应用。(性能提升)。21世纪以来,超滤技术广泛应用于水处理、医药、食品等领域的核心技术之一(广泛应用)",
+        "father": "超滤基础介绍.md"
+    },
+    "19": {
+        "content": "。\n超滤技术特性与广泛应用领域。超滤技术利用半透膜截留大分子物质,有效去除水中悬浮物、胶体、细菌及病毒,实现水质净化。超滤膜孔径介于0.002至0.1微米,能高效拦截悬浮物、胶体、细菌、病毒及大分子有机物,保障饮用水安全。从工业废水处理到食品加工,超滤技术广泛应用于多个行业,提升生产效率,减少环境污染。高通量、低能耗、操作简便,超滤技术成为现代水处理领域的关键技术之一,推动可持续发展",
+        "father": "超滤基础介绍.md"
+    },
+    "20": {
+        "content": "。\n\n",
+        "father": "超滤基础介绍.md"
     }
 }

BIN
db/water_treatment_knowledge.faiss


File diff suppressed because it is too large
+ 0 - 0
db/water_treatment_text_meta.json


+ 123 - 45
knowledge_robot.py

@@ -2,10 +2,11 @@ from openai import OpenAI
 from text_vector_database import knowledge_comp_rule_regulation_db, knowledge_comp_propaganda_db,knowledge_comp_water_treatment_db,knowledge_comp_project_plan_db,knowledge_comp_operation_report_db
 import json
 import time
+from plc_query_tool import plc_query_helper
+from web_tool import web_search_tool
 
-
-class WaterRobot:
-
+class KnowAgent:
+    """致道Agent"""
     def __init__(self):
         # API
         self.client = OpenAI(
@@ -13,9 +14,10 @@ class WaterRobot:
             base_url="https://api.deepseek.com"
         )
         # 参数
-        self.max_iterations = 5
+        self.max_iterations = 8
         self.current_iteration = 0
         self.temperature=1.0
+
         # 工具定义
         self.tool_maps = {
             'knowledge_comp_rule_regulation_db': knowledge_comp_rule_regulation_db.search,
@@ -23,6 +25,8 @@ class WaterRobot:
             'knowledge_comp_water_treatment_db': knowledge_comp_water_treatment_db.search,
             'knowledge_comp_project_plan_db': knowledge_comp_project_plan_db.search,
             'knowledge_comp_operation_report_db': knowledge_comp_operation_report_db.search,
+            'plc_query_helper':plc_query_helper,
+            'web_search_tool':web_search_tool.search
         }
         self.tool_names_map = {
             'knowledge_comp_rule_regulation_db': '公司规章制度知识库',
@@ -30,6 +34,8 @@ class WaterRobot:
             'knowledge_comp_water_treatment_db': '污水处理工艺知识库',
             'knowledge_comp_project_plan_db': '污水处理厂项目方案知识库',
             'knowledge_comp_operation_report_db': '污水处理厂运行报告知识库',
+            'plc_query_helper':'PLC点位查询工具',
+            'web_search_tool': '联网搜索工具',
             ' ': '未知工具',
         }
         self.tools = [
@@ -123,17 +129,55 @@ class WaterRobot:
                     },
                 }
             },
+
+            # 污水处理厂plc点位查询工具
+            {
+                "type": "function",
+                "function": {
+                    "name": "plc_query_helper",
+                    "description": "这是一个污水处理厂PLC点位数据查询工具,当用户需要查询水厂的传感器的监测数据时,如超滤或反渗透过程中的电导、渗透率、流量、设备功率电耗、药耗等,可以尝试使用本工具。如果查询成功,工具会返回一些候选的点位数据,包括名称和数值。",
+                    "parameters": {
+                        "type": "object",
+                        "properties": {
+                            "query": {
+                                "type": "string",
+                                "description": "这个参数是需要查询的点位名称。如反渗透电导、超滤流量、总进水流量等",
+                            }
+                        },
+                        "required": ["query"]
+                    },
+                }
+            },
+            # 联网搜索工具
+            {
+                "type": "function",
+                "function": {
+                    "name": "web_search_tool",
+                    "description": "这是一个网络搜索引擎工具,如果知识库没有合适的参考内容,可以使用此工具联网搜索。",
+                    "parameters": {
+                        "type": "object",
+                        "properties": {
+                            "query": {
+                                "type": "string",
+                                "description": "请输入搜索关键词或问题",
+                            }
+                        },
+                        "required": ["query"]
+                    },
+                }
+            },
         ]
 
         # 系统提示词 - 指导AI如何智能使用工具
-        self.system_prompt = """你是一个专业的水处理行业AI助手,请遵循以下规则回答问题:
-
+        self.system_prompt = f"""你是一个专业的水处理行业AI助手,请遵循以下规则回答问题:
         1. **知识查询判断**:
-           - 当用户询问公司规章制度、行政管理、人力资源等问题时,使用【公司规章制度知识库】
-           - 当用户询问公司介绍、产品信息(水萝卜智能体、新水岛)、发展历程时,使用【公司宣传资料知识库】  
-           - 当用户询问污水处理工艺、技术标准、行业规范时,使用【污水处理工艺知识库】
-           - 当用户询问项目方案、投标资料时,使用【污水处理项目方案知识库】
-           - 当用户询问运行报告、运营数据时,使用【污水处理厂运行报告知识库】
+           - 当需要查询污水处理厂PLC传感器数据时,请先使用【plc_query_helper】工具。(PLC点位查询工具)
+           - 当用户询问公司规章制度、行政管理、人力资源等问题时,使用【knowledge_comp_rule_regulation_db】工具。(公司规章制度知识库)
+           - 当用户询问公司介绍、产品信息(水萝卜智能体、新水岛)、发展历程时,使用【knowledge_comp_propaganda_db】工具。(公司宣传资料知识库)  
+           - 当用户询问污水处理工艺、技术标准、行业规范时,使用【knowledge_comp_water_treatment_db】工具。(污水处理工艺知识库)
+           - 当用户询问项目方案、投标资料时,使用【knowledge_comp_project_plan_db】工具。(污水处理项目方案知识库)
+           - 当用户询问运行报告、运营数据时,使用【knowledge_comp_operation_report_db】工具(污水处理厂运行报告知识库)
+           
 
         2. **智能工具调用**:
            - 只有当问题确实需要专业知识库支持时才调用工具
@@ -148,9 +192,15 @@ class WaterRobot:
             - 专业问题:先给出结论,再分点解释
             - 操作问题:直接给出步骤说明
             - 知识库查询结果:注明信息来源
+            - 当PLC查询工【plc_query_helper】返回很多候选数据时,请结合用户的需求挑选相关数据进行回复。
+            - 如果需要分析一些深度污水处理领域问题,可以结合PLC查询工具来获取相关数据,然后可以查询相关知识库来检索知识,两者结合进行分析回答。
 
         请根据问题内容智能判断是否需要调用工具,以及调用哪个工具最合适。"""
 
+        # 存放历史对话,key为会话ID,value为会话内容,会话内容为列表
+        self.history_chat = {
+        '400': [{"role": "system", "content": self.system_prompt}]}
+
     def process_stream_response(self, stream_response, on_chunk=None):
         """
         处理流式响应,逐块输出内容
@@ -175,35 +225,38 @@ class WaterRobot:
         """
         发送消息给模型
         """
-        if not messages or messages[0]["role"] != "system":
-            # 深拷贝消息列表避免修改原数据
-            enhanced_messages = [{"role": "system", "content": self.system_prompt}]
-            enhanced_messages.extend(messages)
-        else:
-            enhanced_messages = messages
-        # 构建请求参数
-        request_data = {
-            'model':"deepseek-chat",
-            "messages": enhanced_messages,
-            "stream": stream,
-            "tools": self.tools,
-            "temperature": self.temperature,
-        }
-        if stream:
-            # 流式响应
-            response = self.client.chat.completions.create(**request_data)
-            return response  # 返回流式响应对象
-        else:
-            # 非流式响应
-            response = self.client.chat.completions.create(**request_data)
-            return response.choices[0].message
+        try:
+            if not messages or messages[0]["role"] != "system":
+                # 深拷贝消息列表避免修改原数据
+                enhanced_messages = [{"role": "system", "content": self.system_prompt}]
+                enhanced_messages.extend(messages)
+            else:
+                enhanced_messages = messages
+            # 构建请求参数
+            request_data = {
+                'model':"deepseek-chat",
+                "messages": enhanced_messages,
+                "stream": stream,
+                "tools": self.tools,
+                "temperature": self.temperature,
+            }
+            if stream:
+                # 流式响应
+                response = self.client.chat.completions.create(**request_data)
+                return response  # 返回流式响应对象
+            else:
+                # 非流式响应
+                response = self.client.chat.completions.create(**request_data)
+                return response.choices[0].message
+        except Exception as e:
+            print(f"Error: {e}")
 
-    def function_call(self, function:str, arguments:str):
+    def function_call(self, function:str, arguments:str, project_id:int):
         if function not in self.tool_maps:
             return f"函数名称不在工具列表中,工具列表:{list(self.tool_maps.keys())}"
         if not arguments:
             return f"query参数不能为空"
-        return str(self.tool_maps[function](arguments))
+        return str(self.tool_maps[function](arguments, project_id=project_id))
 
     def simulate_stream_response(self, content, delay=0.01):
         """
@@ -265,14 +318,20 @@ class WaterRobot:
 
         print()  # 换行
 
-    def chat(self, user_input:str, stream:bool=True):
+    def chat(self, user_input:str,project_id:int, stream:bool=True, conversion_id='400'):
         # 初始化对话历史
         print('='*50)
         print("开始对话...")
         print(f"User>\t {user_input}")
-        history_messages = [{"role": "user", "content": user_input}]
-
+        # 检查历史会话
+        if conversion_id in self.history_chat:
+            history_messages = self.history_chat[conversion_id]
+            history_messages.extend([{"role": "user", "content": user_input}])
+        else:
+            history_messages = [{"role": "user", "content": user_input}]
+            self.history_chat[conversion_id] = history_messages
 
+        tools_history = ['<think>']
         while self.current_iteration < self.max_iterations:
             water_robot_message = self.send_messages(history_messages, stream=False)
             # 检查是否由工具调用
@@ -293,11 +352,13 @@ class WaterRobot:
                     ]
                 })
                 # 处理每个工具调用
-                for tool_call in water_robot_message.tool_calls:
-                    print(f"***正在调用工具:【{self.tool_names_map.get(tool_call.function.name, ' ')}】")
+                for i, tool_call in enumerate(water_robot_message.tool_calls):
+                    print(f"🔍 调用工具{i}:【{self.tool_names_map.get(tool_call.function.name, ' ')}】  ")
+                    tools_history.append(f"🔍 调用工具{i}:【{self.tool_names_map.get(tool_call.function.name, ' ')}】  ")
                     arguments = json.loads(tool_call.function.arguments)
-                    print(f"***工具参数:{arguments}")
-                    tool_result = water_robot.function_call(tool_call.function.name, arguments.get("query", " "))
+                    print(f"📋 工具{i}参数:{arguments}  ")
+                    tools_history.append(f"📋 工具{i}参数:{arguments}  ")
+                    tool_result = know_agent.function_call(tool_call.function.name, arguments.get("query", " "), project_id=project_id)
 
                     # 添加工具响应
                     history_messages.append({
@@ -316,15 +377,32 @@ class WaterRobot:
                     "content": water_robot_message.content
                 })
                 break
-        if self.current_iteration >= self.max_iterations:
+        if self.current_iteration >= self.max_iterations:  # 处理迭代次数超限的情况
+            # 告诉模型迭代次数超限
+            history_messages.append({
+                "role": "tool",
+                "tool_call_id": 0,
+                "content": "当前工具调用次数达到上限,请根据现有结果进行分析回复。"
+            })
+            water_robot_message = self.send_messages(history_messages, stream=False)
+            # 添加最后一次模型给出的结果
+            history_messages.append({
+                "role": "assistant",
+                "content": water_robot_message.content
+            })
             print("达到最大迭代次数,对话结束。")
         # 重置迭代计数器
         self.current_iteration = 0
-water_robot = WaterRobot()
+        # 保存对话历史
+        self.history_chat[conversion_id].extend(history_messages)
+        tools_history.append('</think>')
+        return ('\n'.join(tools_history) + history_messages[-1].get('content', 'null')) if history_messages[-1].get('role', ' ')=='assistant' else 'null'
+know_agent = KnowAgent()
 
 if __name__ == "__main__":
     while True:
         user_input = input("\n用户输入: ")
         if user_input.lower() == "exit":
             break
-        water_robot.chat(user_input)
+        res = know_agent.chat(user_input, 92)
+        pass

+ 0 - 0
plc_query/__init__.py


File diff suppressed because it is too large
+ 0 - 0
plc_query/plc_dictionary/1181_plc_dictionary/1181_dict_level_1.json


File diff suppressed because it is too large
+ 0 - 0
plc_query/plc_dictionary/1181_plc_dictionary/1181_dict_level_2.json


File diff suppressed because it is too large
+ 0 - 0
plc_query/plc_dictionary/1181_plc_dictionary/1181_dict_name_2_code.json


BIN
plc_query/plc_dictionary/1181_plc_dictionary/1181_knowledge.faiss


BIN
plc_query/plc_dictionary/1181_plc_dictionary/1181_点位.xlsx


File diff suppressed because it is too large
+ 0 - 0
plc_query/plc_dictionary/1202_plc_dictionary/1202_dict_level_1.json


File diff suppressed because it is too large
+ 0 - 0
plc_query/plc_dictionary/1202_plc_dictionary/1202_dict_level_2.json


File diff suppressed because it is too large
+ 0 - 0
plc_query/plc_dictionary/1202_plc_dictionary/1202_dict_name_2_code.json


BIN
plc_query/plc_dictionary/1202_plc_dictionary/1202_knowledge.faiss


BIN
plc_query/plc_dictionary/1202_plc_dictionary/1202_点位.xlsx


File diff suppressed because it is too large
+ 0 - 0
plc_query/plc_dictionary/1450_plc_dictionary/1450_dict_level_1.json


File diff suppressed because it is too large
+ 0 - 0
plc_query/plc_dictionary/1450_plc_dictionary/1450_dict_level_2.json


File diff suppressed because it is too large
+ 0 - 0
plc_query/plc_dictionary/1450_plc_dictionary/1450_dict_name_2_code.json


BIN
plc_query/plc_dictionary/1450_plc_dictionary/1450_knowledge.faiss


BIN
plc_query/plc_dictionary/1450_plc_dictionary/1450_点位.xlsx


File diff suppressed because it is too large
+ 0 - 0
plc_query/plc_dictionary/92_plc_dictionary/92_dict_level_1.json


File diff suppressed because it is too large
+ 0 - 0
plc_query/plc_dictionary/92_plc_dictionary/92_dict_level_2.json


File diff suppressed because it is too large
+ 0 - 0
plc_query/plc_dictionary/92_plc_dictionary/92_dict_name_2_code.json


BIN
plc_query/plc_dictionary/92_plc_dictionary/92_knowledge.faiss


BIN
plc_query/plc_dictionary/92_plc_dictionary/92_点位.xlsx


+ 345 - 0
plc_query/plclib.py

@@ -0,0 +1,345 @@
+import os
+script_dir = os.path.dirname(os.path.abspath(__file__))
+import sys
+sys.path.append(script_dir)
+import jieba
+import jieba.posseg as pseg
+import re
+import os
+import json
+import textdistance
+import warnings
+import numpy as np
+import faiss
+from bge.remote_model import RemoteBGEModel
+
+
+class PLCLib:
+
+    def __init__(self):
+        """缓存待实现"""
+        self.project_id = None
+        self.plc_dict_root_dir = None
+        self.name_2_code_dict = None
+        self.plc_database_name_template_list = None
+        self.dict_equivalent_wordmap = None
+        self.dict_level_2 = None
+        self.dict_level_1 = None
+        self.user_dict_list = None
+        self.knowledge = None
+
+        # 加载bge-m3和bge-reranker远程模型
+        self.model = RemoteBGEModel('dev')
+
+        # 加载用户自定义词典,添加到jieba词库, 不依赖水厂id
+        self.script_dir = os.path.dirname(os.path.abspath(__file__)) # 脚本绝对路径
+        user_dictionary_dir = os.path.join(self.script_dir, 'user_maintain_dictionary', 'jieba_words')
+        if not os.path.exists(user_dictionary_dir):
+            warnings.warn(f'用户分词词典不存在,严重影响匹配成功率,请检查路径{user_dictionary_dir}是否存在!', UserWarning)
+        else:
+            self.user_dict_list = [os.path.join(user_dictionary_dir, _) for _ in os.listdir(user_dictionary_dir) if _.split('.')[-1] == 'txt']  # 用户词典
+            self.__load_user_dict()
+
+
+    def load(self, project_id):
+        """加载词典"""
+        self.project_id = project_id
+        self.plc_dict_root_dir = os.path.join(self.script_dir, 'plc_dictionary',f'{self.project_id}_plc_dictionary')
+        # 加载name2code
+        self.name_2_code_dict = self.__read_pcl()
+        self.plc_database_name_template_list = list(self.name_2_code_dict.keys())
+        # 加载等价词表
+        self.dict_equivalent_wordmap = self.__construct_equivalent_wordmap()
+        # 加载二级词典
+        self.dict_level_2 =self.__make_level_two_dictionary()
+        # 加载一级词典
+        self.dict_level_1 = self.__make_level_one_dictionary()
+        # 加载本地知识库
+        self.knowledge = self.__load_faiss_database()
+
+    def __load_faiss_database(self):
+        """从本地加载向量数据库"""
+        # 水厂的数据库字段知识库
+        faiss_path = os.path.join(self.plc_dict_root_dir, f'{self.project_id}_knowledge.faiss')
+        # 尝试从本地加载
+        if os.path.exists(faiss_path):
+            # print('PLC点位查询功能从本地加载点位字段向量知识库...')
+            local_faiss = faiss.read_index(faiss_path)
+        else:
+            raise FileNotFoundError('file not found!', faiss_path)
+        return local_faiss
+
+
+    @staticmethod
+    def field_align(input_str:str)->str:
+        """按照锡山中荷命名规范对齐字段,1#UF替换为UF1,1#RO替换为RO1,保持统一"""
+        sources_uf = re.findall(r'\d+#UF', input_str, re.IGNORECASE)  # 匹配1#UF
+        sources_ro = re.findall(r'\d+#RO', input_str, re.IGNORECASE)  # 匹配1#RO
+        sources = sources_uf + sources_ro
+        for sou in sources:
+            number_, flag_ = sou.split('#')
+            input_str = input_str.replace(sou, flag_.upper() + number_) # 统一转为大写
+        return input_str
+
+    def __construct_equivalent_wordmap(self):
+        """构建等价词汇映射表"""
+        # 检查文件是否存在
+        equivalent_wordmap_path = os.path.join(self.script_dir, 'user_maintain_dictionary','equivalent_words', 'dict_equivalent_wordmap.json')
+        if os.path.exists(equivalent_wordmap_path):
+            with open(equivalent_wordmap_path, 'r', encoding='utf-8') as f:
+                equivalent_wordmap = json.load(f)
+        else:
+            raise FileNotFoundError('file not found!', equivalent_wordmap_path)
+        return equivalent_wordmap
+
+    def __make_level_one_dictionary(self):
+        """创建一级字典"""
+        # 尝试从本地加载一级字典
+        dict_level_1_path = os.path.join(self.plc_dict_root_dir, f'{self.project_id}_dict_level_1.json')
+        if os.path.exists(dict_level_1_path):
+            with open(dict_level_1_path, 'r', encoding='utf-8') as f:
+                group_dict = json.load(f)
+        else:
+            raise FileNotFoundError('file not found!', dict_level_1_path)
+        return group_dict
+
+    def __make_level_two_dictionary(self):
+        """创建二级字典,对点位所有字段进行正则匹配中文,将中文一样的字段聚合为同一个字典键值对,键为正则提取的中文字符"""
+        # 尝试从本地加载二级字典
+        dict_level2_dict_path = os.path.join(self.plc_dict_root_dir, f'{self.project_id}_dict_level_2.json')
+        if os.path.exists(dict_level2_dict_path):
+            with open(dict_level2_dict_path, 'r', encoding='utf-8') as f:
+                group_dict = json.load(f)
+        else:
+            raise FileNotFoundError('file not found!', dict_level2_dict_path)
+        return group_dict
+
+    def __read_pcl(self):
+        """
+        读取pcl文件,生成name2code词典
+        :return:
+        """
+        # 尝试从本地加载name-code映射字典
+        dict_name2code_path = os.path.join(self.plc_dict_root_dir, f'{self.project_id}_dict_name_2_code.json')
+        if os.path.exists(dict_name2code_path):
+            with open(dict_name2code_path, 'r', encoding='utf-8') as f:
+                dict_name2code = json.load(f)
+        else:
+            raise FileNotFoundError('file not found!', dict_name2code_path)
+        return dict_name2code
+
+    def __load_user_dict(self):
+        """加载用户词典,添加到jieba词库"""
+        # 删除
+        jieba.del_word('反渗透')
+        jieba.del_word('超滤')
+        for user_dict_txt in self.user_dict_list:
+            # # 检查文件是否存在
+            # if not os.path.exists(user_dict_txt):
+            #     raise FileNotFoundError(f'{user_dict_txt} does not exist')
+            # # 检查文件后缀名是否合法
+            # if os.path.splitext(user_dict_txt)[1] != '.txt':
+            #     continue
+            # 分词库加载用户字典
+            jieba.load_userdict(user_dict_txt)
+    @ staticmethod
+    def quicksort_up_part(arr:list, start:int, end:int)-> int:
+        """升序排序"""
+        # 双指针
+        low = start
+        high = end
+        pivot = arr[start][1] # 基准值
+        # 大数放在基准值右边,小数放在基准值左边
+        while low < high:
+            # 先从右向左找比基准值小的
+            while low< high and arr[high][1] >= pivot:
+                high -= 1
+            # 此时high指向值小于基准值,交换
+            if low < high:
+                arr[low], arr[high] = arr[high], arr[low]
+                low +=1
+            # 现在开始从左向右找,比基准值大的数
+            while low < high and arr[low][1] <= pivot:
+                low += 1
+            # 此时low指向值大于基准值,交换
+            if low < high:
+                arr[high], arr[low] = arr[low], arr[high]
+                high -= 1
+        return low
+    def quicksort_up(self, arr:list, start:int, end:int):
+        """按照元组第二个元素值大小进行升序排序"""
+        if start >= end:
+            return
+        # 先排一次获得基准值位置
+        mid = self.quicksort_up_part(arr, start, end)
+        # 排左面
+        self.quicksort_up(arr, start, mid - 1)
+        # 排右面
+        self.quicksort_up(arr, mid + 1, end)
+    def words_similarity_score_sorted(self, query:str, candidates:list)->list:
+        """计算输入语句与候选词的相似度并按照相似度分值进行排序"""
+        # 选择算法(示例使用Levenshtein,归一化到0-1)
+        candidates = candidates.copy()
+        jarowinkler = textdistance.JaroWinkler()
+        key_score_list = [(candidate, jarowinkler.normalized_similarity(query, candidate)) for candidate in candidates]
+        self.quicksort_up(key_score_list, 0, len(key_score_list) - 1)  # 升序排序
+        key_sorted_list = [tuple_element[0] for tuple_element in key_score_list]  # 取出key
+        key_sorted_list = key_sorted_list[::-1]  # 反转,变为降序
+        return key_sorted_list
+
+    def words_similarity_score_sorted_v2(self, query:str, candidates:list)->list:
+        """通过rerank的方式为候选词进行相似度排序"""
+        # 调用远程reranker模型
+        n = len(candidates)  # 候选词数量
+        group_query = [(query, i) for i in candidates]
+        score = self.model.compute_score(group_query)
+        key_score_list = [(candidates[i], score[i]) for i in range(n)]
+        self.quicksort_up(key_score_list, 0, len(key_score_list) - 1)  # 升序排序
+        key_sorted_list = [tuple_element[0] for tuple_element in key_score_list]  # 取出key
+        key_sorted_list = key_sorted_list[::-1]  # 反转,变为降序
+        return key_sorted_list
+
+    def query(self, promt, is_agent:bool=False):
+        """直接拷贝PLCMatch_match_v2_on函数"""
+        """
+        模糊匹配v2
+        :param is_agent:
+        :param promt:
+        :return:
+        """
+
+        # print("=" * 50)
+        # 命名风格转换
+        # print("原始查询:", promt)
+        promt = promt.replace('超滤', 'UF').replace('反渗透', 'RO').replace('号', '#').replace('组', '#')
+        promt = self.field_align(promt)
+        # print("转换查询:", promt)
+        # 输入分词
+        nz_words = []
+        for w, f in pseg.lcut(promt):
+            # print(f'{w}({f})', end="")
+            if f == 'nz':
+                nz_words.append(w)
+        # print('\n备查nz词:', nz_words)
+
+        # 处理专有名词的等价词,为了保证高召回率,我们将备查词的所有等价说法都放入备查序列
+        equivalent_words = []
+        for nz_idx, nz in enumerate(nz_words):
+            # 首先判断nz词是否在等价词汇表中,如果不在根本无法替换
+            if nz in self.dict_equivalent_wordmap.keys():
+                # 然后把等价的说法都添加进去就好了
+                equivalent_words = self.dict_equivalent_wordmap.get(nz, [])
+        if equivalent_words:
+            nz_words += equivalent_words
+            nz_words = list(set(nz_words))
+        # print('等价备查nz词:', nz_words)
+        del equivalent_words
+
+        # 进行一级查询,根据nz词是否包含于词典
+        query_level_one = []
+        for i in range(len(nz_words)):  # 为第i个nz词进行初次匹配
+            result = []
+            # 如果nz词包含在一级词典中就算匹配成功
+            for dict_level_1_key in self.dict_level_1.keys():
+                if nz_words[i] in dict_level_1_key:  # 如果nz词包含在一级词典内
+                    result+= self.dict_level_1.get(dict_level_1_key)
+            query_level_one.append(result)  # 放入一级查询结果中
+
+        # 进行二级查询
+        query_level_two = []
+        for idx_nz, i_nz_query_result in enumerate(query_level_one):  # 遍历每个nz词的查询结果
+            result = []  # 为第i个nz词进行二次匹配
+            # 如果第i个nz词一级查询不为空
+            if i_nz_query_result: # 第i个nz词的查询结果list
+                for res_word_level_one in i_nz_query_result:
+                    if res_word_level_one in self.dict_level_2.keys():
+                        result += self.dict_level_2.get(res_word_level_one)  # self.dict_level_2的value本身就是字典,所以用+=拼接
+            # 虽然一级查询失败,但是并不意味着映射词典里没有,因为一级词典忽略英文。
+            else:  # 如果一级查询失败,就直接在name2code字典中查询
+                if nz_words[idx_nz] in self.name_2_code_dict.keys():# 如果第i个nz词在2级词典,就直接添加到结果中
+                    result.append(nz_words[idx_nz])
+            # 如果第i个nz词的一级查询结果为空,则添加空列表占位
+            query_level_two.append(result)
+
+        # 常规精确匹配结束,如果匹配成功,结构为二维列表,否则为空列表
+        matched_keys = query_level_two  # 获取已匹配的字段
+        # 备查词合并,我们约定所有备查词进行统一的查询,后面怎么用这些结果取决于外部的应用,对于agent模式,将会输出许多结果,对月非agent只会输出概率最高的结果
+        tem_matched_keys = []
+        for item in matched_keys:
+            tem_matched_keys += item
+        matched_keys = [list(set(tem_matched_keys))]
+        del tem_matched_keys
+
+        # 当同一个nz词存在多个检索结果时,按照相似度对检索结果排序
+        # 该步骤可以省略,因为后面由reranker统一排序
+        # for idx, i_nz_keys in enumerate(matched_keys):
+        #     if len(i_nz_keys) > 1:
+        #         # matched_keys[idx] = self.words_similarity_score_sorted(query=promt, candidates=i_nz_keys)
+        #         # 通过rerank的方式对候选词进行重新排序
+        #         matched_keys[idx] = self.words_similarity_score_sorted_v2(query=promt, candidates=i_nz_keys)
+
+        # 如果精确匹配失败,没有匹配到任何结果则按照语义进行模糊匹配,返回满足条件的置信度最高的结果
+        # if not nz_words or ([] in matched_keys):
+        # 比起手动维护词典,我们更相信语义相似度
+        top_k = 5
+        confi = 0.2 # 置信度阈值
+        # print(f'进入模糊匹配,召回Top:{top_k} 置信度阈值:{confi}...')
+        # 调用远程bge-m3模型进行embedding
+        query_embedding = np.array(self.model.encode([promt], normalize=True), dtype=np.float32) # 要求query_embedding是一个二维矩阵,形状为(1, 1024)
+        distances, indices = self.knowledge.search(query_embedding, top_k)
+        group_query = [(promt, self.plc_database_name_template_list[indices[0][i]]) for i in range(top_k)]
+        # 我们更愿意相信bge,因此把词典关键词匹配的结果一并放进去重排序
+        group_query_manuel = [(promt, k) for keys in matched_keys for k in keys]
+        group_query += group_query_manuel
+        del group_query_manuel
+        group_query = list(set(group_query))  # 去重
+        # 调用远程bge-reranker模型
+        score = self.model.compute_score(group_query)
+        rerank_result = sorted([(group_query[i][1], score[i]) for i in range(len(group_query))], key=lambda x: x[1], reverse=True)
+        # print(F'打印前top{top_k}候选词结果:', rerank_result[:top_k])
+        # print(f'首元素模糊匹配到{rerank_result[0][0]}, 置信度为{rerank_result[0][1]}')
+        # matched_keys 为最终结果,保持形状为二维列表
+        matched_keys = [[i[0] for i in rerank_result]]
+        # 每个匹配结果的置信度
+        matched_keys_score = [[i[1] for i in rerank_result]]
+
+
+        # 为结果创建映射字典
+        result_list = []
+        for i_nz_keys in matched_keys:
+            result_list.append([{key: self.name_2_code_dict.get(key)} for key in i_nz_keys])
+        # print(f"查询到{len([_ for _ in result_list if _])}个结果:")
+
+        if not is_agent:
+            # 非agent模式每个匹配结果只取第一个元素的英文
+            tem_list = []
+            for res in result_list:
+                if res:
+                    for k, v in res[0].items():  # 每个nz词的查询结果都是一个list,每个list可能包含多个字典
+                        tem_list.append(f'{k}:{v}')
+            result_list = tem_list
+            # print('以非agent模式返回:', result_list)
+            return result_list
+
+        # print('以agent模式返回:', result_list)
+        # print('='*50)
+        return result_list, matched_keys_score
+
+# 步骤1:实例化,单例模式
+helper = PLCLib()
+
+if __name__ == '__main__':
+    # demo
+    # 步骤2:按照水厂加载数据库
+    helper.load(92)
+    # 步骤3:根据查询匹配水厂
+    # helper.query("查询RO1回收率、RO2回收率、...")
+    helper.query("查询中荷水厂产水电导率", is_agent=False)
+    # agent 模式
+    # 输出格式:list, [RO1回收率查询结果, RO2回收率查询结果, ...]
+    # RO1回收率查询结果:list, [{'RO1回收率': 'RO1HSL'}]
+    # RO2回收率查询结果:list, [{'RO2回收率': 'RO2HSL'}]
+    # ...
+    # 完整查询格式: [[{'RO1回收率': 'RO1HSL'}], [{'RO2回收率': 'RO2HSL'}]]
+    # 非agent 模式,每个结果取首个元素,直接返回英文code
+    #

+ 8 - 0
plc_query/url_config.json

@@ -0,0 +1,8 @@
+{
+  "dev_embed_url": "http://101.200.76.30:8002",
+  "dev_reranker_url": "http://101.200.76.30:8003",
+  "master_embed_url": "http://101.200.76.30:8002",
+  "master_reranker_url": "http://101.200.76.30:8003",
+  "local_embed_url": "http://101.200.76.30:8002",
+  "local_reranker_url": "http://101.200.76.30:8003"
+}

+ 1 - 0
plc_query/user_maintain_dictionary/equivalent_words/dict_equivalent_wordmap.json

@@ -0,0 +1 @@
+{"总回收率": ["总回收率", "回收率"], "回收率": ["总回收率", "回收率"], "总进水量": ["总进水量", "进水量"], "进水量": ["总进水量", "进水量"], "总产水电导": ["总产水电导", "产水电导"], "产水电导": ["总产水电导", "产水电导"], "总进水电导": ["总进水电导", "进水电导"], "进水电导": ["总进水电导", "进水电导"], "总产水压力": ["总产水压力", "产水压力"], "产水压力": ["总产水压力", "产水压力"], "总产水流量": ["总产水流量", "产水流量"], "产水流量": ["总产水流量", "产水流量"], "总进水流量": ["总进水流量", "进水流量"], "进水流量": ["总进水流量", "进水流量"], "电导": ["电导", "电导率"], "电导率": ["电导", "电导率"], "": [""]}

+ 9 - 0
plc_query/user_maintain_dictionary/equivalent_words/equivalent_wordmap.txt

@@ -0,0 +1,9 @@
+总回收率=回收率
+总进水量=进水量
+总产水电导=产水电导
+总进水电导=进水电导
+总产水压力=产水压力
+总产水流量=产水流量
+总进水流量=进水流量
+电导=电导率
+

+ 40 - 0
plc_query/user_maintain_dictionary/jieba_words/A综合.txt

@@ -0,0 +1,40 @@
+ph 1000 nz
+CIP 1000 nz
+液位 1000 nz
+温度 1000 nz
+UF进水浊度 1000 nz
+UF产水浊度 1000 nz
+
+供水泵 1000 nz
+UF泵 1000 nz
+反洗泵 1000 nz
+UF反洗水泵 1000 nz
+清水外供泵 1000 nz
+清洗水泵 1000 nz
+高压泵 1000 nz
+加药泵 1000 nz
+段间泵 1000 nz
+卸料泵 1000 nz
+
+反洗膜通量 1000 nz
+清洗膜通量 1000 nz
+
+自清洗过滤器 1000 nz
+脱碳风机 1000 nz
+
+还原剂 1000 nz
+阻垢剂 1000 nz
+絮凝剂 1000 nz
+盐酸 1000 nz
+
+清水池 1000 nz
+中和池 1000 nz
+
+水温校正因子 1000 nz
+
+外供水 1000 nz
+UF总产水 1000 nz
+进水池 1000 nz
+反洗水池 1000 nz
+中荷废水 1000 nz
+

+ 9 - 0
plc_query/user_maintain_dictionary/jieba_words/B产水率.txt

@@ -0,0 +1,9 @@
+产水率 1000 nz
+RO1产水率 1000 nz
+RO2产水率 1000 nz
+RO3产水率 1000 nz
+RO4产水率 1000 nz
+RO5产水率 1000 nz
+RO6产水率 1000 nz
+RO7产水率 1000 nz
+RO8产水率 1000 nz

+ 40 - 0
plc_query/user_maintain_dictionary/jieba_words/B回收率.txt

@@ -0,0 +1,40 @@
+回收率 1000 nz
+产水率 1000 nz
+运行回收率 1000 nz
+RO回收率 1000 nz
+RO总回收率 1000 nz
+UF回收率 1000 nz
+UF总回收率 1000 nz
+RO1回收率 1000 nz
+RO2回收率 1000 nz
+RO3回收率 1000 nz
+RO4回收率 1000 nz
+RO5回收率 1000 nz
+RO6回收率 1000 nz
+RO7回收率 1000 nz
+RO8回收率 1000 nz
+RO1运行回收率 1000 nz
+RO2运行回收率 1000 nz
+RO3运行回收率 1000 nz
+RO4运行回收率 1000 nz
+RO5运行回收率 1000 nz
+RO6运行回收率 1000 nz
+RO7运行回收率 1000 nz
+RO8运行回收率 1000 nz
+UF1回收率 1000 nz
+UF2回收率 1000 nz
+UF3回收率 1000 nz
+UF4回收率 1000 nz
+UF5回收率 1000 nz
+UF6回收率 1000 nz
+UF7回收率 1000 nz
+UF8回收率 1000 nz
+UF1运行回收率 1000 nz
+UF2运行回收率 1000 nz
+UF3运行回收率 1000 nz
+UF4运行回收率 1000 nz
+UF5运行回收率 1000 nz
+UF6运行回收率 1000 nz
+UF7运行回收率 1000 nz
+UF8运行回收率 1000 nz
+

+ 15 - 0
plc_query/user_maintain_dictionary/jieba_words/B电导.txt

@@ -0,0 +1,15 @@
+电导 1000 nz
+电导率 1000 nz
+产水电导 1000 nz
+RO1产水电导 1000 nz
+RO2产水电导 1000 nz
+RO3产水电导 1000 nz
+RO4产水电导 1000 nz
+RO5产水电导 1000 nz
+RO6产水电导 1000 nz
+RO7产水电导 1000 nz
+RO8产水电导 1000 nz
+RO总产水电导 1000 nz
+RO产水电导 1000 nz
+RO总进水电导 1000 nz
+RO进水电导 1000 nz

+ 5 - 0
plc_query/user_maintain_dictionary/jieba_words/B脱盐率.txt

@@ -0,0 +1,5 @@
+脱盐率 1000 nz
+RO1脱盐率 1000 nz
+RO2脱盐率 1000 nz
+RO3脱盐率 1000 nz
+RO4脱盐率 1000 nz

+ 18 - 0
plc_query/user_maintain_dictionary/jieba_words/C膜渗透率.txt

@@ -0,0 +1,18 @@
+膜渗透率 1000 nz
+渗透率 1000 nz
+UF1膜渗透率 1000 nz
+UF2膜渗透率 1000 nz
+UF3膜渗透率 1000 nz
+UF4膜渗透率 1000 nz
+UF5膜渗透率 1000 nz
+UF6膜渗透率 1000 nz
+UF7膜渗透率 1000 nz
+UF8膜渗透率 1000 nz
+UF1渗透率 1000 nz
+UF2渗透率 1000 nz
+UF3渗透率 1000 nz
+UF4渗透率 1000 nz
+UF5渗透率 1000 nz
+UF6渗透率 1000 nz
+UF7渗透率 1000 nz
+UF8渗透率 1000 nz

+ 53 - 0
plc_query/user_maintain_dictionary/jieba_words/C膜通量.txt

@@ -0,0 +1,53 @@
+膜通量 1000 nz
+膜运行通量 1000 nz
+UF1膜运行通量 1000 nz
+UF2膜运行通量 1000 nz
+UF3膜运行通量 1000 nz
+UF4膜运行通量 1000 nz
+UF5膜运行通量 1000 nz
+UF6膜运行通量 1000 nz
+UF7膜运行通量 1000 nz
+UF8膜运行通量 1000 nz
+RO1膜运行通量 1000 nz
+RO2膜运行通量 1000 nz
+RO3膜运行通量 1000 nz
+RO4膜运行通量 1000 nz
+RO5膜运行通量 1000 nz
+RO6膜运行通量 1000 nz
+RO7膜运行通量 1000 nz
+RO8膜运行通量 1000 nz
+通量 1000 nz
+UF1通量 1000 nz
+UF2通量 1000 nz
+UF3通量 1000 nz
+UF4通量 1000 nz
+UF5通量 1000 nz
+UF6通量 1000 nz
+UF7通量 1000 nz
+UF8通量 1000 nz
+RO1通量 1000 nz
+RO2通量 1000 nz
+RO3通量 1000 nz
+RO4通量 1000 nz
+RO5通量 1000 nz
+RO6通量 1000 nz
+RO7通量 1000 nz
+RO8通量 1000 nz
+UF1膜通量 1000 nz
+UF2膜通量 1000 nz
+UF3膜通量 1000 nz
+UF4膜通量 1000 nz
+UF5膜通量 1000 nz
+UF6膜通量 1000 nz
+UF7膜通量 1000 nz
+UF8膜通量 1000 nz
+RO1膜通量 1000 nz
+RO2膜通量 1000 nz
+RO3膜通量 1000 nz
+RO4膜通量 1000 nz
+RO5膜通量 1000 nz
+RO6膜通量 1000 nz
+RO7膜通量 1000 nz
+RO8膜通量 1000 nz
+
+

+ 9 - 0
plc_query/user_maintain_dictionary/jieba_words/C跨膜压差.txt

@@ -0,0 +1,9 @@
+跨膜压差 1000 nz
+UF1跨膜压差 1000 nz
+UF2跨膜压差 1000 nz
+UF3跨膜压差 1000 nz
+UF4跨膜压差 1000 nz
+UF5跨膜压差 1000 nz
+UF6跨膜压差 1000 nz
+UF7跨膜压差 1000 nz
+UF8跨膜压差 1000 nz

+ 21 - 0
plc_query/user_maintain_dictionary/jieba_words/产水压力.txt

@@ -0,0 +1,21 @@
+产水压力 1000
+RO总产水压力 1000 nz
+RO产水压力 1000 nz
+RO1产水压力 1000 nz
+RO2产水压力 1000 nz
+RO3产水压力 1000 nz
+RO4产水压力 1000 nz
+RO5产水压力 1000 nz
+RO6产水压力 1000 nz
+RO7产水压力 1000 nz
+RO8产水压力 1000 nz
+UF总产水压力 1000 nz
+UF产水压力 1000 nz
+UF1产水压力 1000 nz
+UF2产水压力 1000 nz
+UF3产水压力 1000 nz
+UF4产水压力 1000 nz
+UF5产水压力 1000 nz
+UF6产水压力 1000 nz
+UF7产水压力 1000 nz
+UF8产水压力 1000 nz

+ 30 - 0
plc_query/user_maintain_dictionary/jieba_words/产水流量.txt

@@ -0,0 +1,30 @@
+产水流量 1000 nz
+UF产水流量 1000 nz
+UF总产水流量 1000 nz
+RO产水流量 1000 nz
+RO总产水流量 1000 nz
+RO4一二段产水流量比值 1000 nz
+RO3一二段产水流量比值 1000 nz
+RO2一二段产水流量比值 1000 nz
+RO1一二段产水流量比值 1000 nz
+RO5一二段产水流量比值 1000 nz
+RO6一二段产水流量比值 1000 nz
+RO7一二段产水流量比值 1000 nz
+RO8一二段产水流量比值 1000 nz
+RO1产水流量 1000 nz
+RO2产水流量 1000 nz
+RO3产水流量 1000 nz
+RO4产水流量 1000 nz
+RO5产水流量 1000 nz
+RO6产水流量 1000 nz
+RO7产水流量 1000 nz
+RO8产水流量 1000 nz
+RO1反渗透二段产水流量 1000 nz
+RO2反渗透二段产水流量 1000 nz
+RO3反渗透二段产水流量 1000 nz
+RO4反渗透二段产水流量 1000 nz
+RO5反渗透二段产水流量 1000 nz
+RO6反渗透二段产水流量 1000 nz
+RO7反渗透二段产水流量 1000 nz
+RO8反渗透二段产水流量 1000 nz
+

+ 27 - 0
plc_query/user_maintain_dictionary/jieba_words/段压差.txt

@@ -0,0 +1,27 @@
+一段压差 1000 nz
+二段压差 1000 nz
+三段压差 1000 nz
+RO1一段压差 1000 nz
+RO1二段压差 1000 nz
+RO1三段压差 1000 nz
+RO2一段压差 1000 nz
+RO2二段压差 1000 nz
+RO2三段压差 1000 nz
+RO3一段压差 1000 nz
+RO3二段压差 1000 nz
+RO3三段压差 1000 nz
+RO4一段压差 1000 nz
+RO4二段压差 1000 nz
+RO4三段压差 1000 nz
+RO5一段压差 1000 nz
+RO5二段压差 1000 nz
+RO5三段压差 1000 nz
+RO6一段压差 1000 nz
+RO6二段压差 1000 nz
+RO6三段压差 1000 nz
+RO7一段压差 1000 nz
+RO7二段压差 1000 nz
+RO7三段压差 1000 nz
+RO8一段压差 1000 nz
+RO8二段压差 1000 nz
+RO8三段压差 1000 nz

+ 18 - 0
plc_query/user_maintain_dictionary/jieba_words/浓水压力.txt

@@ -0,0 +1,18 @@
+浓水压力 1000 nz
+RO1一段浓水压力 1000 nz
+RO1二段浓水压力 1000 nz
+RO2一段浓水压力 1000 nz
+RO2二段浓水压力 1000 nz
+RO3一段浓水压力 1000 nz
+RO3二段浓水压力 1000 nz
+RO4一段浓水压力 1000 nz
+RO4二段浓水压力 1000 nz
+RO5一段浓水压力 1000 nz
+RO5二段浓水压力 1000 nz
+RO6一段浓水压力 1000 nz
+RO6二段浓水压力 1000 nz
+RO7一段浓水压力 1000 nz
+RO7二段浓水压力 1000 nz
+RO8一段浓水压力 1000 nz
+RO8二段浓水压力 1000 nz
+

+ 6 - 0
plc_query/user_maintain_dictionary/jieba_words/浓水流量.txt

@@ -0,0 +1,6 @@
+浓水流量 1000 nz
+RO浓水流量 1000 nz
+RO1反渗透浓水流量 1000 nz
+RO2反渗透浓水流量 1000 nz
+RO3反渗透浓水流量 1000 nz
+RO4反渗透浓水流量 1000 nz

+ 37 - 0
plc_query/user_maintain_dictionary/jieba_words/进水压力.txt

@@ -0,0 +1,37 @@
+进水压力 1000 nz
+一段进水压力 1000 nz
+二段进水压力 1000 nz
+三段进水压力 1000 nz
+RO1一段进水压力 1000 nz
+RO2一段进水压力 1000 nz
+RO3一段进水压力 1000 nz
+RO4一段进水压力 1000 nz
+RO5一段进水压力 1000 nz
+RO6一段进水压力 1000 nz
+RO7一段进水压力 1000 nz
+RO8一段进水压力 1000 nz
+RO1二段进水压力 1000 nz
+RO2二段进水压力 1000 nz
+RO3二段进水压力 1000 nz
+RO4二段进水压力 1000 nz
+RO5二段进水压力 1000 nz
+RO6二段进水压力 1000 nz
+RO7二段进水压力 1000 nz
+RO8二段进水压力 1000 nz
+RO1三段进水压力 1000 nz
+RO2三段进水压力 1000 nz
+RO3三段进水压力 1000 nz
+RO4三段进水压力 1000 nz
+RO5三段进水压力 1000 nz
+RO6三段进水压力 1000 nz
+RO7三段进水压力 1000 nz
+RO8三段进水压力 1000 nz
+
+UF1进水压力 1000 nz
+UF2进水压力 1000 nz
+UF3进水压力 1000 nz
+UF4进水压力 1000 nz
+UF5进水压力 1000 nz
+UF6进水压力 1000 nz
+UF7进水压力 1000 nz
+UF8进水压力 1000 nz

+ 24 - 0
plc_query/user_maintain_dictionary/jieba_words/进水流量.txt

@@ -0,0 +1,24 @@
+UF总进水量 1000 nz
+UF进水量 1000 nz
+进水流量 1000 nz
+RO总进水流量 1000 nz
+RO进水流量 1000 nz
+总进水流量 1000 nz
+UF总进水流量 1000 nz
+UF进水流量 1000 nz
+RO1反渗透进水流量 1000 nz
+RO2反渗透进水流量 1000 nz
+RO3反渗透进水流量 1000 nz
+RO4反渗透进水流量 1000 nz
+RO5反渗透进水流量 1000 nz
+RO6反渗透进水流量 1000 nz
+RO7反渗透进水流量 1000 nz
+RO8反渗透进水流量 1000 nz
+UF1进水流量 1000 nz
+UF2进水流量 1000 nz
+UF3进水流量 1000 nz
+UF4进水流量 1000 nz
+UF5进水流量 1000 nz
+UF6进水流量 1000 nz
+UF7进水流量 1000 nz
+UF8进水流量 1000 nz

+ 23 - 0
plc_query_tool.py

@@ -0,0 +1,23 @@
+from plc_query import plclib
+from api_tools.access_plc_api import *
+
+def plc_query_helper(query:str, project_id:int):
+    """
+    PLC点位数据查询工具
+    :param query: 需要查询的语句
+    :return:
+    """
+    plclib.helper.load(project_id)  # 演示demo 默认中荷
+    tmp_res = plclib.helper.query(query, is_agent=True)
+    res_info = tmp_res[0][0]
+    res_score = tmp_res[1][0]
+    # 数据库字段
+    db_codes = [list(item.values())[0] for item in res_info]
+    # 开始调用plc
+    api_response_info = post_req_2_plc_api(project_id=92, database_codes=db_codes)
+    api_response_info = [api_response_info[i] for i in range(len(api_response_info))]  # 暂时不要置信度
+    return ';'.join(api_response_info)
+
+if __name__ == '__main__':
+    res = plc_query_helper(query="中荷水厂超滤总进水流量")
+    print(res)

+ 1 - 1
text_vector_database.py

@@ -108,7 +108,7 @@ class TextVectorDatabase:
             json.dump(self.chunk_meta, f, ensure_ascii=False, indent=4)
             print(f"元数据保存成功,保存至{self.chunk_meta_path}")
 
-    def search(self, query:str, top_k:int=3, confidence_threshold:float=0.20):
+    def search(self, query:str, top_k:int=3, confidence_threshold:float=0.20, *args,**kwargs):
         """从知识库中检索"""
         if not isinstance(query, str) or query == '':
             return None

+ 62 - 0
web_tool.py

@@ -0,0 +1,62 @@
+from ddgs import DDGS
+
+
+class WebSearchTool:
+    def __init__(self, max_results=2):
+        """
+        初始化免费网络搜索工具
+        Args:
+            max_results: 最大返回结果数
+        """
+        self.ddgs = DDGS()
+        self.max_results = max_results
+    
+    def search(self, query, *args, **kwargs):
+        """
+        执行搜索
+        
+        Args:
+            query: 搜索查询字符串
+            
+        Returns:
+            搜索结果
+        """
+        try:
+
+            results = list(self.ddgs.text(query, max_results=self.max_results))
+            formatted_results = []
+            for result in results:
+                formatted_results.append({
+                    "title": result.get("title", ""),
+                    "link": result.get("href", ""),
+                    "snippet": result.get("body", "")
+                })
+            
+            return str({"results": formatted_results})
+        except Exception as e:
+            return str({"results": "null"})
+
+web_search_tool = WebSearchTool()
+def create_search_demo():
+    """
+    创建免费搜索工具的简洁 demo
+    """
+
+    # 示例搜索查询
+    queries = [
+        "2026年的农历新年是什么",
+        "Python机器学习最佳实践",
+        "污水处理新技术发展"
+    ]
+
+    print("=== FreeWebSearchTool 搜索 Demo ===")
+    for query in queries:
+        print(f"\n查询: {query}")
+        response = web_search_tool.search(query)
+        print(f"结果: {response}")
+
+    print("\n=== Demo 结束 ===")
+
+
+if __name__ == "__main__":
+    create_search_demo()

Some files were not shown because too many files changed in this diff