{"repo": "2326815976/icvn-project", "files": {"Agent/Second_Merger.py": {"f153333925ffa0b120166a292cb13e26b7ef3a3f": {"t": 1785745438.9567351, "n": 7239, "h": "d70d1e43fbf11de4", "c": "import os\nimport glob\nimport json\nimport math\nfrom openai import OpenAI\n\n# ================= 配置读取 =================\nCONFIG_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), \"config.json\")\nwith open(CONFIG_PATH, 'r', encoding='utf-8') as f:\n config = json.load(f)\n\nDEEPSEEK_API_KEY = config[\"API\"][\"DEEPSEEK_API_KEY\"]\nBASE_URL = config[\"API\"][\"BASE_URL\"]\n\nINPUT_DIR = config[\"PATHS\"][\"STAGE2_INPUT_DIR\"]\nOUTPUT_DIR = config[\"PATHS\"][\"STAGE2_OUTPUT_DIR\"]\n\nos.makedirs(OUTPUT_DIR, exist_ok=True)\n\n# 初始化 DeepSeek 客户端\nclient = OpenAI(\n api_key=DEEPSEEK_API_KEY,\n base_url=BASE_URL\n)\n\n# ================= 提示词区域 =================\nSYSTEM_PROMPT = \"\"\"你需要对多份口供的事件进行结构化整理,根据里面所提到的人物、事件信息完成人物信息抽取、人物统一(实体对齐)以及关系整理。\n一、任务目标\n1.将所有口供整理为案件事件三元组,进行跨事件人物合并,合并时需将“角色称呼”与后文具体姓名的参与人进行关联比对。若同一人既以角色称呼出现,又以具体姓名出现,则合并为同一人物,统一使用具体姓名(如无姓名则使用最明确称呼);\n2.若同一角色在不同口供中承担相同职能且行为链相同、作案工具相同(大致类似),也应合并;\n3.可能出现在不同口供描述中的一个事件,事件三元组中一个描述为是人物A,一个描述为是人物B,这种情况也需要进行合并。\n二、输出格式\n必须严格按照以下JSON结构输出,不得添加任何额外说明或标记:\n{\n \"案件事件三元组\": [\n {\n \"人物A名称\": \"\",\"人物A所扮演角色\": \"\",\n \"人物B名称\": \"\",\"人物B所扮演角色\": \"\",\n \"行为序列\": [\n \"\",\n \"\",\n \"\"\n ]\n }\n ]\n}\n\"\"\"\n\n\ndef call_llm_to_merge(content_list, merge_index):\n data_str = json.dumps(content_list, ensure_ascii=False, indent=2)\n user_content = f\"{SYSTEM_PROMPT}\\n具体口供内容为:\\n{data_str}\\n以上为全部内容。\"\n\n # 【重要调整】恢复了强制输出合法 JSON 的系统提示词\n messages = [\n {\"role\": \"system\",\n \"content\": \"你必须严格按照提供的 JSON 格式输出。如果数据量过大,你可以分批输出。每次输出都必须是一个包含 '案件事件三元组' 键的合法完整 JSON 对象。不要包含 markdown 代码块。\"},\n {\"role\": \"user\", \"content\": user_content}\n ]\n\n all_merged_triples = []\n max_retries = 5\n full_result_str_log = \"\" # 仅用于报错调试留存\n\n try:\n for attempt in range(max_retries):\n response = client.chat.completions.create(\n model=\"deepseek-chat\",\n messages=messages,\n response_format={\"type\": \"json_object\"}, # 重新开启强约束,逼迫模型保证格式\n temperature=0.1,\n max_tokens=8192\n )\n\n result_chunk = response.choices[0].message.content\n finish_reason = response.choices[0].finish_reason\n full_result_str_log += result_chunk + \"\\n\\n=== 截断分割线 ===\\n\\n\"\n\n # 提取纯净文本\n clean_chunk = result_chunk.strip()\n if clean_chunk.startswith(\"```json\"):\n clean_chunk = clean_chunk[7:]\n if clean_chunk.startswith(\"```\"):\n clean_chunk = clean_chunk[3:]\n if clean_chunk.endswith(\"```\"):\n clean_chunk = clean_chunk[:-3]\n clean_chunk = clean_chunk.strip()\n\n if finish_reason == \"length\":\n print(f\" [提示] {merge_index} 输出触达长度上限,执行【结构化断点保护】 (第 {attempt + 1} 次续写)...\")\n\n # ==== 核心黑科技:大括号回溯强行闭合解析 ====\n valid_str = clean_chunk\n rescued = False\n while True:\n last_brace_idx = valid_str.rfind('}')\n if last_brace_idx == -1:\n break # 找不到括号了,放弃抢救这一块\n\n test_str = valid_str[:last_brace_idx + 1]\n # 强行给它补齐尾部的数组和对象闭合\n if \"案件事件三元组\" in test_str:\n test_str += \"\\n ]\\n}\"\n\n try:\n parsed_json = json.loads(test_str)\n triples = parsed_json.get(\"案件事件三元组\", [])\n all_merged_triples.extend(triples)\n print(f\" -> 成功抢救并入库 {len(triples)} 条完整三元组。\")\n rescued = True\n break # 解析成功,跳出回溯\n except json.JSONDecodeError:\n # 解析失败说明这个 '}' 是在文本内部或者损坏的,丢弃最后一个 '}' 继续往前找\n valid_str = valid_str[:last_brace_idx]\n\n if not rescued:\n print(\" -> [警告] 截断部分抢救失败,未找到完整的对象结构。\")\n\n # 引导模型将剩下的内容放在一个全新的完整 JSON 中输出\n messages.append({\"role\": \"assistant\", \"content\": result_chunk})\n messages.append({\n \"role\": \"user\",\n \"content\": \"你的输出由于长度限制被截断了。请**接着刚才输出的最后一个完整的三元组之后**,继续整理并输出剩余的部分。\\n要求:必须输出一个全新的、格式合法的完整 JSON 对象:`{\\\"案件事件三元组\\\": [ ... ] }`。千万不要包含刚才已经输出过的三元组,直接输出下一条。\"\n })\n else:\n # 正常结束,完整解析\n try:\n parsed_json = json.loads(clean_chunk)\n triples = parsed_json.get(\"案件事件三元组\", [])\n all_merged_triples.extend(triples)\n except json.JSONDecodeError as je:\n print(f\" [错误] 正常结束时的 JSON 解析失败: {je}\")\n error_file = os.path.join(OUTPUT_DIR, f\"error_json_{merge_index}.txt\")\n with open(error_file, \"w\", encoding=\"utf-8\") as f:\n f.write(full_result_str_log)\n break\n\n print(f\" [成功] {merge_index} 最终共完美提取 {len(all_merged_triples)} 条三元组。\")\n return all_merged_triples\n\n except Exception as e:\n print(f\" [错误] 窗口合并过程发生意外错误 ({merge_index}): {e}\")\n return []\n\ndef main():\n json_files = glob.glob(os.path.join(INPUT_DIR, \"*.json\"))\n if not json_files:\n print(f\"在目录 {INPUT_DIR} 中没有找到任何 .json 文件,请检查第一阶段是否执行成功。\")\n return\n\n print(f\"共找到 {len(json_files)} 个 JSON 文件,准备读取数据...\")\n\n all_extracted_triples = []\n for filepath in json_files:\n try:\n with open(filepath, 'r', encoding='utf-8') as f:\n data = json.load(f)\n triples = data.get(\"案件事件三元组\", [])\n if triples:\n all_extracted_triples.append(triples)\n except Exception as e:\n print(f\"读取文件 {filepath} 失败: {e}\")\n\n if not all_extracted_triples:\n print(\"未能从文件中提取出任何 '案件事件三元组' 数据。\")\n return\n\n print(f\"成功提取 {len(all_extracted_triples)} 份口供的三元组数据,开始执行窗口合并策略...\")\n\n current_layer_data = all_extracted_triples\n layer_num = 1\n\n while len(current_layer_data) > 1:\n print(f\"\\n--- 开始第 {layer_num} 层级合并,当前共有 {len(current_layer_data)} 份数据块 ---\")\n next_layer_data = []\n window_size = 4\n\n num_batches = math.ceil(len(current_layer_data) / window_size)\n\n for i in range(num_batches):\n start_idx = i * window_size\n end_idx = start_idx + window_size\n chunk_to_merge = current_layer_data[start_idx:end_idx]\n\n print(f\" -> 正在合并第 {i + 1}/{num_batches} 批次 (包含 {len(chunk_to_merge)} 份数据)...\")\n\n merged_result = call_llm_to_merge(chunk_to_merge, f\"层级{layer_num}-批次{i + 1}\")\n\n if merged_result:\n next_layer_data.append(merged_result)\n else:\n print(f\" [警告] 第 {i + 1} 批次合并失败,已跳过,可能会导致部分数据丢失。\")\n\n current_layer_data = next_layer_data\n layer_num += 1\n\n print(\"\\n================ 合并全部完成 ================\")\n final_output = {\n \"案件事件三元组\": current_layer_data[0] if current_layer_data else []\n }\n\n final_filepath = os.path.join(OUTPUT_DIR, \"Final_Global_Merged.json\")\n with open(final_filepath, 'w', encoding='utf-8') as f:\n json.dump(final_output, f, ensure_ascii=False, indent=2)\n\n print(f\"最终合并结果已保存至: {final_filepath}\")\n\nif __name__ == \"__main__\":\n main()"}, "aab4fc77d9273d278fa3973508448db321b47051": {"t": 1785753226.0176325, "n": 7239, "h": "d70d1e43fbf11de4", "c": "import os\nimport glob\nimport json\nimport math\nfrom openai import OpenAI\n\n# ================= 配置读取 =================\nCONFIG_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), \"config.json\")\nwith open(CONFIG_PATH, 'r', encoding='utf-8') as f:\n config = json.load(f)\n\nDEEPSEEK_API_KEY = config[\"API\"][\"DEEPSEEK_API_KEY\"]\nBASE_URL = config[\"API\"][\"BASE_URL\"]\n\nINPUT_DIR = config[\"PATHS\"][\"STAGE2_INPUT_DIR\"]\nOUTPUT_DIR = config[\"PATHS\"][\"STAGE2_OUTPUT_DIR\"]\n\nos.makedirs(OUTPUT_DIR, exist_ok=True)\n\n# 初始化 DeepSeek 客户端\nclient = OpenAI(\n api_key=DEEPSEEK_API_KEY,\n base_url=BASE_URL\n)\n\n# ================= 提示词区域 =================\nSYSTEM_PROMPT = \"\"\"你需要对多份口供的事件进行结构化整理,根据里面所提到的人物、事件信息完成人物信息抽取、人物统一(实体对齐)以及关系整理。\n一、任务目标\n1.将所有口供整理为案件事件三元组,进行跨事件人物合并,合并时需将“角色称呼”与后文具体姓名的参与人进行关联比对。若同一人既以角色称呼出现,又以具体姓名出现,则合并为同一人物,统一使用具体姓名(如无姓名则使用最明确称呼);\n2.若同一角色在不同口供中承担相同职能且行为链相同、作案工具相同(大致类似),也应合并;\n3.可能出现在不同口供描述中的一个事件,事件三元组中一个描述为是人物A,一个描述为是人物B,这种情况也需要进行合并。\n二、输出格式\n必须严格按照以下JSON结构输出,不得添加任何额外说明或标记:\n{\n \"案件事件三元组\": [\n {\n \"人物A名称\": \"\",\"人物A所扮演角色\": \"\",\n \"人物B名称\": \"\",\"人物B所扮演角色\": \"\",\n \"行为序列\": [\n \"\",\n \"\",\n \"\"\n ]\n }\n ]\n}\n\"\"\"\n\n\ndef call_llm_to_merge(content_list, merge_index):\n data_str = json.dumps(content_list, ensure_ascii=False, indent=2)\n user_content = f\"{SYSTEM_PROMPT}\\n具体口供内容为:\\n{data_str}\\n以上为全部内容。\"\n\n # 【重要调整】恢复了强制输出合法 JSON 的系统提示词\n messages = [\n {\"role\": \"system\",\n \"content\": \"你必须严格按照提供的 JSON 格式输出。如果数据量过大,你可以分批输出。每次输出都必须是一个包含 '案件事件三元组' 键的合法完整 JSON 对象。不要包含 markdown 代码块。\"},\n {\"role\": \"user\", \"content\": user_content}\n ]\n\n all_merged_triples = []\n max_retries = 5\n full_result_str_log = \"\" # 仅用于报错调试留存\n\n try:\n for attempt in range(max_retries):\n response = client.chat.completions.create(\n model=\"deepseek-chat\",\n messages=messages,\n response_format={\"type\": \"json_object\"}, # 重新开启强约束,逼迫模型保证格式\n temperature=0.1,\n max_tokens=8192\n )\n\n result_chunk = response.choices[0].message.content\n finish_reason = response.choices[0].finish_reason\n full_result_str_log += result_chunk + \"\\n\\n=== 截断分割线 ===\\n\\n\"\n\n # 提取纯净文本\n clean_chunk = result_chunk.strip()\n if clean_chunk.startswith(\"```json\"):\n clean_chunk = clean_chunk[7:]\n if clean_chunk.startswith(\"```\"):\n clean_chunk = clean_chunk[3:]\n if clean_chunk.endswith(\"```\"):\n clean_chunk = clean_chunk[:-3]\n clean_chunk = clean_chunk.strip()\n\n if finish_reason == \"length\":\n print(f\" [提示] {merge_index} 输出触达长度上限,执行【结构化断点保护】 (第 {attempt + 1} 次续写)...\")\n\n # ==== 核心黑科技:大括号回溯强行闭合解析 ====\n valid_str = clean_chunk\n rescued = False\n while True:\n last_brace_idx = valid_str.rfind('}')\n if last_brace_idx == -1:\n break # 找不到括号了,放弃抢救这一块\n\n test_str = valid_str[:last_brace_idx + 1]\n # 强行给它补齐尾部的数组和对象闭合\n if \"案件事件三元组\" in test_str:\n test_str += \"\\n ]\\n}\"\n\n try:\n parsed_json = json.loads(test_str)\n triples = parsed_json.get(\"案件事件三元组\", [])\n all_merged_triples.extend(triples)\n print(f\" -> 成功抢救并入库 {len(triples)} 条完整三元组。\")\n rescued = True\n break # 解析成功,跳出回溯\n except json.JSONDecodeError:\n # 解析失败说明这个 '}' 是在文本内部或者损坏的,丢弃最后一个 '}' 继续往前找\n valid_str = valid_str[:last_brace_idx]\n\n if not rescued:\n print(\" -> [警告] 截断部分抢救失败,未找到完整的对象结构。\")\n\n # 引导模型将剩下的内容放在一个全新的完整 JSON 中输出\n messages.append({\"role\": \"assistant\", \"content\": result_chunk})\n messages.append({\n \"role\": \"user\",\n \"content\": \"你的输出由于长度限制被截断了。请**接着刚才输出的最后一个完整的三元组之后**,继续整理并输出剩余的部分。\\n要求:必须输出一个全新的、格式合法的完整 JSON 对象:`{\\\"案件事件三元组\\\": [ ... ] }`。千万不要包含刚才已经输出过的三元组,直接输出下一条。\"\n })\n else:\n # 正常结束,完整解析\n try:\n parsed_json = json.loads(clean_chunk)\n triples = parsed_json.get(\"案件事件三元组\", [])\n all_merged_triples.extend(triples)\n except json.JSONDecodeError as je:\n print(f\" [错误] 正常结束时的 JSON 解析失败: {je}\")\n error_file = os.path.join(OUTPUT_DIR, f\"error_json_{merge_index}.txt\")\n with open(error_file, \"w\", encoding=\"utf-8\") as f:\n f.write(full_result_str_log)\n break\n\n print(f\" [成功] {merge_index} 最终共完美提取 {len(all_merged_triples)} 条三元组。\")\n return all_merged_triples\n\n except Exception as e:\n print(f\" [错误] 窗口合并过程发生意外错误 ({merge_index}): {e}\")\n return []\n\ndef main():\n json_files = glob.glob(os.path.join(INPUT_DIR, \"*.json\"))\n if not json_files:\n print(f\"在目录 {INPUT_DIR} 中没有找到任何 .json 文件,请检查第一阶段是否执行成功。\")\n return\n\n print(f\"共找到 {len(json_files)} 个 JSON 文件,准备读取数据...\")\n\n all_extracted_triples = []\n for filepath in json_files:\n try:\n with open(filepath, 'r', encoding='utf-8') as f:\n data = json.load(f)\n triples = data.get(\"案件事件三元组\", [])\n if triples:\n all_extracted_triples.append(triples)\n except Exception as e:\n print(f\"读取文件 {filepath} 失败: {e}\")\n\n if not all_extracted_triples:\n print(\"未能从文件中提取出任何 '案件事件三元组' 数据。\")\n return\n\n print(f\"成功提取 {len(all_extracted_triples)} 份口供的三元组数据,开始执行窗口合并策略...\")\n\n current_layer_data = all_extracted_triples\n layer_num = 1\n\n while len(current_layer_data) > 1:\n print(f\"\\n--- 开始第 {layer_num} 层级合并,当前共有 {len(current_layer_data)} 份数据块 ---\")\n next_layer_data = []\n window_size = 4\n\n num_batches = math.ceil(len(current_layer_data) / window_size)\n\n for i in range(num_batches):\n start_idx = i * window_size\n end_idx = start_idx + window_size\n chunk_to_merge = current_layer_data[start_idx:end_idx]\n\n print(f\" -> 正在合并第 {i + 1}/{num_batches} 批次 (包含 {len(chunk_to_merge)} 份数据)...\")\n\n merged_result = call_llm_to_merge(chunk_to_merge, f\"层级{layer_num}-批次{i + 1}\")\n\n if merged_result:\n next_layer_data.append(merged_result)\n else:\n print(f\" [警告] 第 {i + 1} 批次合并失败,已跳过,可能会导致部分数据丢失。\")\n\n current_layer_data = next_layer_data\n layer_num += 1\n\n print(\"\\n================ 合并全部完成 ================\")\n final_output = {\n \"案件事件三元组\": current_layer_data[0] if current_layer_data else []\n }\n\n final_filepath = os.path.join(OUTPUT_DIR, \"Final_Global_Merged.json\")\n with open(final_filepath, 'w', encoding='utf-8') as f:\n json.dump(final_output, f, ensure_ascii=False, indent=2)\n\n print(f\"最终合并结果已保存至: {final_filepath}\")\n\nif __name__ == \"__main__\":\n main()"}}, "Agent/config.json": {"f153333925ffa0b120166a292cb13e26b7ef3a3f": {"t": 1785746776.3083944, "n": 1123, "h": "6a4733a32b2efd31", "c": "{\n \"API\": {\n \"DEEPSEEK_API_KEY\": \"sk-eac2cc112ccf4151adf0eb76bfca19e5\",\n \"BASE_URL\": \"https://api.deepseek.com/v1\"\n },\n \"PATHS\": {\n \"STAGE1_INPUT_FILE\": \"D:\\\\DeepLeaning\\\\Pyprojects\\\\PyBullet\\\\Interrogation_Project\\\\Agent\\\\Original_Data\\\\input.json\",\n \"STAGE1_OUTPUT_DIR\": \"D:\\\\DeepLeaning\\\\Pyprojects\\\\PyBullet\\\\Interrogation_Project\\\\Agent\\\\First_Extraction_Out\",\n\n \"STAGE2_INPUT_DIR\": \"D:\\\\DeepLeaning\\\\Pyprojects\\\\PyBullet\\\\Interrogation_Project\\\\Agent\\\\First_Extraction_Out\",\n \"STAGE2_OUTPUT_DIR\": \"D:\\\\DeepLeaning\\\\Pyprojects\\\\PyBullet\\\\Interrogation_Project\\\\Agent\\\\Second_Merge_Out\",\n\n \"STAGE3_INPUT_FILE\": \"D:\\\\DeepLeaning\\\\Pyprojects\\\\PyBullet\\\\Interrogation_Project\\\\Agent\\\\Second_Merge_Out\\\\Final_Global_Merged.json\",\n \"STAGE3_OUTPUT_DIR\": \"D:\\\\DeepLeaning\\\\Pyprojects\\\\PyBullet\\\\Interrogation_Project\\\\Agent\\\\Last_Out\",\n\n \"STAGE4_INPUT_FILE\": \"D:\\\\DeepLeaning\\\\Pyprojects\\\\PyBullet\\\\Interrogation_Project\\\\Agent\\\\Last_Out\\\\Logic_Checked_Merged.json\",\n \"STAGE4_OUTPUT_FILE\": \"D:\\\\DeepLeaning\\\\Pyprojects\\\\PyBullet\\\\Interrogation_Project\\\\Agent\\\\Last_Out\\\\Last_output.json\"\n }\n}"}, "aab4fc77d9273d278fa3973508448db321b47051": {"t": 1785753225.4148278, "n": 1123, "h": "6a4733a32b2efd31", "c": "{\n \"API\": {\n \"DEEPSEEK_API_KEY\": \"sk-eac2cc112ccf4151adf0eb76bfca19e5\",\n \"BASE_URL\": \"https://api.deepseek.com/v1\"\n },\n \"PATHS\": {\n \"STAGE1_INPUT_FILE\": \"D:\\\\DeepLeaning\\\\Pyprojects\\\\PyBullet\\\\Interrogation_Project\\\\Agent\\\\Original_Data\\\\input.json\",\n \"STAGE1_OUTPUT_DIR\": \"D:\\\\DeepLeaning\\\\Pyprojects\\\\PyBullet\\\\Interrogation_Project\\\\Agent\\\\First_Extraction_Out\",\n\n \"STAGE2_INPUT_DIR\": \"D:\\\\DeepLeaning\\\\Pyprojects\\\\PyBullet\\\\Interrogation_Project\\\\Agent\\\\First_Extraction_Out\",\n \"STAGE2_OUTPUT_DIR\": \"D:\\\\DeepLeaning\\\\Pyprojects\\\\PyBullet\\\\Interrogation_Project\\\\Agent\\\\Second_Merge_Out\",\n\n \"STAGE3_INPUT_FILE\": \"D:\\\\DeepLeaning\\\\Pyprojects\\\\PyBullet\\\\Interrogation_Project\\\\Agent\\\\Second_Merge_Out\\\\Final_Global_Merged.json\",\n \"STAGE3_OUTPUT_DIR\": \"D:\\\\DeepLeaning\\\\Pyprojects\\\\PyBullet\\\\Interrogation_Project\\\\Agent\\\\Last_Out\",\n\n \"STAGE4_INPUT_FILE\": \"D:\\\\DeepLeaning\\\\Pyprojects\\\\PyBullet\\\\Interrogation_Project\\\\Agent\\\\Last_Out\\\\Logic_Checked_Merged.json\",\n \"STAGE4_OUTPUT_FILE\": \"D:\\\\DeepLeaning\\\\Pyprojects\\\\PyBullet\\\\Interrogation_Project\\\\Agent\\\\Last_Out\\\\Last_output.json\"\n }\n}"}}}}