From 68e3677c3434b879308cf13eabbf9c68fd3a2385 Mon Sep 17 00:00:00 2001 From: ouyangyouzhang Date: Thu, 3 Jul 2025 11:34:31 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0riper-5.mdc=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E4=BB=A5=E5=90=AF=E7=94=A8=E8=87=AA=E5=8A=A8=E5=BA=94=E7=94=A8?= =?UTF-8?q?=EF=BC=8C=E4=BC=98=E5=8C=96DifyApi=E7=B1=BB=EF=BC=8C=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E6=96=87=E6=A1=A3=E5=AD=90=E5=88=86=E6=AE=B5=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E5=8A=9F=E8=83=BD=EF=BC=8C=E5=8C=85=E6=8B=AC=E8=8E=B7?= =?UTF-8?q?=E5=8F=96=E3=80=81=E6=B7=BB=E5=8A=A0=E3=80=81=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E5=92=8C=E5=88=A0=E9=99=A4=E5=AD=90=E5=88=86=E6=AE=B5=E7=9A=84?= =?UTF-8?q?=E6=96=B9=E6=B3=95=EF=BC=8C=E5=90=8C=E6=97=B6=E6=94=B9=E8=BF=9B?= =?UTF-8?q?OpenAiLLM=E7=B1=BB=E7=9A=84=E5=88=9D=E5=A7=8B=E5=8C=96=E9=80=BB?= =?UTF-8?q?=E8=BE=91=E4=BB=A5=E6=94=AF=E6=8C=81=E7=8E=AF=E5=A2=83=E5=8F=98?= =?UTF-8?q?=E9=87=8F=E9=85=8D=E7=BD=AE=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .cursor/rules/riper-5.mdc | 11 +- rag2_0/dify/dify_client/dify_api.py | 178 +++++++++++++++++++++++++++- rag2_0/tool/ModelTool.py | 22 ++-- 3 files changed, 190 insertions(+), 21 deletions(-) diff --git a/.cursor/rules/riper-5.mdc b/.cursor/rules/riper-5.mdc index f45266d..bc383f1 100644 --- a/.cursor/rules/riper-5.mdc +++ b/.cursor/rules/riper-5.mdc @@ -1,7 +1,7 @@ --- description: globs: -alwaysApply: false +alwaysApply: true --- 背景入门 您是 Claude 3.7,并且已集成到 Cursor IDE(一个基于 AI 的 VS Code 分支)。由于您拥有强大的功能,您往往过于急躁,经常在没有明确请求的情况下实施更改,并自以为比我更了解代码,从而破坏了现有逻辑。这会导致代码出现不可接受的灾难。在我的代码库上工作时——无论是 Web 应用程序、数据管道、嵌入式系统还是任何其他软件项目——您未经授权的修改都可能引入细微的 bug 并破坏关键功能。为了避免这种情况,您必须遵循以下严格协议: @@ -17,7 +17,6 @@ RIPER-5 模式 允许:阅读文件、提出澄清问题、理解代码结构 禁止:建议、实施、计划或任何行动暗示 要求:你只能试图了解存在什么,而不是可能是什么 -持续时间:直到我明确发出信号进入下一个模式 输出格式:以[模式:研究]开头,然后仅观察和问题 模式二:创新 [模式:创新] @@ -26,7 +25,6 @@ RIPER-5 模式 允许:讨论想法、优点/缺点、寻求反馈 禁止:具体规划、实施细节或任何代码编写 要求:所有想法都必须以可能性而非决定的形式呈现 -持续时间:直到我明确发出信号进入下一个模式 输出格式:以[模式:创新]开头,然后仅包含可能性和考虑因素 模式 3:计划 [模式:计划] @@ -44,7 +42,6 @@ IMPLEMENTATION CHECKLIST: 2. [Specific action 2] ... n. [Final action] -持续时间:直到我明确批准计划并发出进入下一模式的信号 输出格式:以 [MODE: PLAN] 开头,然后仅包含规范和实施细节 模式 4:执行 [模式:执行] @@ -52,7 +49,6 @@ n. [Final action] 目的:准确执行模式 3 中的计划 允许:仅执行批准计划中明确详述的内容 禁止:任何不在计划内的偏差、改进或创造性添加 -进入要求:仅在我明确发出“进入执行模式”命令后才能进入 偏差处理:如果发现任何需要偏差的问题,立即返回计划模式 输出格式:以 [MODE: EXECUTE] 开头,然后仅执行与计划匹配的执行 模式五:回顾 @@ -72,5 +68,6 @@ n. [Final action] 在审查模式下,你必须标记哪怕是最小的偏差 您无权在声明模式之外做出独立决定 不遵守此协议将给我的代码库带来灾难性的后果 -模式转换信号 -按照顺序依次执行研究模式->计划模式->执行模式->审核模式 \ No newline at end of file + + +请依次执行:研究模式->创新模式->计划模式->执行模式->回顾模式->审查模式 \ No newline at end of file diff --git a/rag2_0/dify/dify_client/dify_api.py b/rag2_0/dify/dify_client/dify_api.py index a9aaa88..49c4d04 100644 --- a/rag2_0/dify/dify_client/dify_api.py +++ b/rag2_0/dify/dify_client/dify_api.py @@ -112,19 +112,23 @@ class DifyApi: return response.json().get("document", {}).get("id", "") - def get_or_create_dataset_by_name(self, dataset_name: str) -> str: + def get_or_create_dataset_by_name(self, dataset_name: str, create_if_not_exist: bool=True) -> str: """ 通过名称获取或创建数据集。 :param dataset_name: 数据集名称。 + :param create_if_not_exist: 如果数据集不存在是否创建。 :return: 数据集ID。 """ list_dataset = self.get_all_dataset_list() for dataset in list_dataset: if dataset["name"] == dataset_name: return dataset["id"] - logging.info(f"数据集不存在,创建数据集: {dataset_name}") - return self.create_dataset(dataset_name) + if create_if_not_exist: + logging.info(f"数据集不存在,创建数据集: {dataset_name}") + return self.create_dataset(dataset_name) + else: + raise Exception(f"数据集不存在: {dataset_name}") def get_documents(self, dataset_id: str, keyword: str = None) -> Dict[str, dict]: """ @@ -364,10 +368,28 @@ class DifyApi: 'Content-Type': 'application/json' } + limit = 100 + page = 0 + all_segments = [] + try: - response = requests.get(url, headers=headers, verify=False) - response.raise_for_status() # 如果响应状态码不是200,抛出异常 - return response.json().get("data", []) # 返回分段信息列表 + while True: + page += 1 + params = { + 'page': page, + 'limit': limit + } + + response = requests.get(url, headers=headers, params=params, verify=False) + response.raise_for_status() # 如果响应状态码不是200,抛出异常 + + data = response.json() + all_segments.extend(data.get("data", [])) + + if not data.get("has_more", False): + break + + return all_segments except Exception as e: logging.error(f"获取文档分段失败: {e}") raise @@ -464,6 +486,150 @@ class DifyApi: logging.warning(f"第{attempt + 1}次尝试失败: {e}") time.sleep(1) # 重试前等待1秒 + def add_document_child_chunk( + self, + dataset_id: str, + document_id: str, + segment_id: str, + content: str + ) -> Dict: + """ + 新增文档子分段。 + + :param dataset_id: 数据集ID。 + :param document_id: 文档ID。 + :param segment_id: 分段ID。 + :param content: 子分段内容。 + :return: 新增的子分段信息。 + :raises: Exception 如果请求失败。 + """ + url = f"{self.dify_url}/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}/child_chunks" + headers = { + 'Authorization': f'Bearer {self.dify_dataset_api_key}', + 'Content-Type': 'application/json' + } + + # 构造请求数据 + data = { + "content": content + } + + try: + response = requests.post(url, headers=headers, data=json.dumps(data), verify=False) + response.raise_for_status() # 如果响应状态码不是200,抛出异常 + return response.json().get("data", {}) # 返回新增的子分段信息 + except Exception as e: + logging.error(f"新增文档子分段失败: {e}") + raise + + def get_document_child_chunks( + self, + dataset_id: str, + document_id: str, + segment_id: str, + page: int = 1, + limit: int = 100 + ) -> Dict: + """ + 获取文档子分段列表。 + + :param dataset_id: 数据集ID。 + :param document_id: 文档ID。 + :param segment_id: 分段ID。 + :param page: 页码,默认为1。 + :param limit: 每页数量,默认为20。 + :return: 子分段列表信息,包含分页信息。 + :raises: Exception 如果请求失败。 + """ + url = f"{self.dify_url}/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}/child_chunks" + headers = { + 'Authorization': f'Bearer {self.dify_dataset_api_key}' + } + + params = { + 'page': page, + 'limit': limit + } + + try: + response = requests.get(url, headers=headers, params=params, verify=False) + response.raise_for_status() # 如果响应状态码不是200,抛出异常 + return response.json() # 返回子分段列表信息,包含分页信息 + except Exception as e: + logging.error(f"获取文档子分段列表失败: {e}") + raise + + def del_document_child_chunk( + self, + dataset_id: str, + document_id: str, + segment_id: str, + child_chunk_id: str + ) -> bool: + """ + 删除文档子分段。 + + :param dataset_id: 数据集ID。 + :param document_id: 文档ID。 + :param segment_id: 分段ID。 + :param child_chunk_id: 子分段ID。 + :return: 如果删除成功返回True,否则返回False。 + """ + url = f"{self.dify_url}/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}/child_chunks/{child_chunk_id}" + headers = { + 'Authorization': f'Bearer {self.dify_dataset_api_key}' + } + + try: + response = requests.delete(url, headers=headers, verify=False) + if response.status_code == 200: + logging.info(f"删除子分段成功: {child_chunk_id}") + return True + else: + logging.error(f"删除子分段失败,状态码: {response.status_code}, 响应: {response.text}") + return False + except Exception as e: + logging.error(f"删除子分段失败: {e}") + return False + + def update_document_child_chunk( + self, + dataset_id: str, + document_id: str, + segment_id: str, + child_chunk_id: str, + content: str + ) -> Dict: + """ + 更新文档子分段内容。 + + :param dataset_id: 数据集ID。 + :param document_id: 文档ID。 + :param segment_id: 分段ID。 + :param child_chunk_id: 子分段ID。 + :param content: 更新的子分段内容。 + :return: 更新后的子分段信息。 + :raises: Exception 如果请求失败。 + """ + url = f"{self.dify_url}/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}/child_chunks/{child_chunk_id}" + headers = { + 'Authorization': f'Bearer {self.dify_dataset_api_key}', + 'Content-Type': 'application/json' + } + + # 构造请求数据 + data = { + "content": content + } + + try: + response = requests.patch(url, headers=headers, data=json.dumps(data), verify=False) + response.raise_for_status() # 如果响应状态码不是200,抛出异常 + return response.json().get("data", {}) # 返回更新后的子分段信息 + except Exception as e: + logging.error(f"更新文档子分段失败: {e}") + raise + if __name__ == '__main__': from dotenv import load_dotenv diff --git a/rag2_0/tool/ModelTool.py b/rag2_0/tool/ModelTool.py index 698f434..d5121dd 100755 --- a/rag2_0/tool/ModelTool.py +++ b/rag2_0/tool/ModelTool.py @@ -127,16 +127,22 @@ class XinferenceReRankerModel: class OpenAiLLM: def __init__(self, **kwargs): - if kwargs.get("api_key") == None or kwargs.get("base_url") == None or kwargs.get("model") == None: - raise ValueError("api_key, base_url, model 不能为空") + if "api_key" in kwargs: + self._api_key = kwargs.get("api_key") + kwargs.pop("api_key") - self._api_key = kwargs.get("api_key") - self._url = kwargs.get("base_url") - self._model = kwargs.get("model") + if "base_url" in kwargs: + self._url = kwargs.get("base_url") + kwargs.pop("base_url") + else: + self._url = os.getenv("OPENAI_API_BASE") + + if "model" in kwargs: + self._model = kwargs.get("model") + kwargs.pop("model") + else: + self._model = os.getenv("LLM_MODEL_NAME") - kwargs.pop("api_key") - kwargs.pop("base_url") - kwargs.pop("model") self._kwargs = kwargs def invoke(self, user_prompt="你是谁?", need_retry=True):