ZengChao 1 месяц назад
Родитель
Сommit
3b9fc0d191

+ 4 - 4
core/construction_review/component/reviewers/prompt/timeliness_basis_reviewer.yaml

@@ -16,10 +16,10 @@ timeliness_basis_reviewer:
     - **risk_level**:风险水平,只能是 "无风险" / "高风险"。
 
     【时效性判定类型(仅限以下四类)】
-    无参考文件:审查文件与参考中的文件名与编号均不一样,对应 "无风险"。  
-    规范版本号正确:审查文件与参考中的文件名与编号均一致状态为现行,对应 "无风险"。  
-    规范版本号错误:审查文件与参考中存在文件名相同但编号不同,对应 "高风险"。  
-    引用已废止的规范:参考文件中对应文件状态为废止,对应 "高风险"。  
+    无参考文件:没有文件名相同的参考文件,对应 "无风险"。
+    规范版本号错误:有文件名相同文件但是没有编号相同的参考文件,对应 "高风险"。    
+    规范版本号正确:有文件名相同编号相同状态为现行的参考文件,对应 "无风险"。   
+    引用已废止的规范:有文件名相同编号相同状态为废止的参考文件,对应 "高风险"。  
     引用已被替代的标准:审查文件与参考中的文件名与编号均一致,状态为废止但对应状态为现行的新文件,对应 "高风险"。  
   
 

+ 20 - 41
core/construction_review/component/reviewers/timeliness_basis_reviewer.py

@@ -62,7 +62,7 @@ class StandardizedResponseProcessor:
         处理LLM响应,返回标准格式
 
         Args:
-            response_text: LLM原始响应文本
+            response_text: LLM原始响应文本(JSON字符串)
             check_name: 检查项名称
 
         Returns:
@@ -73,8 +73,12 @@ class StandardizedResponseProcessor:
             return []
 
         try:
-            # 使用inter_tool提取JSON数据
-            json_data = self.inter_tool._extract_json_data(response_text)
+            json_data = response_text
+
+            # ✅ 只有 str 才提取 JSON;如果已经是 list/dict,直接用
+            if isinstance(response_text, str):
+                json_data = self.inter_tool._extract_json_data(response_text)
+
             parsed_result = []
 
             if json_data and isinstance(json_data, list):
@@ -291,12 +295,15 @@ class BasisReviewService:
                 message = prompt_template.partial(reference_content=grouped_candidates, check_content=basis_items)
                 trace_id = f"prep_basis_batch_{int(time.time())}"
                 llm_out = await self.llm_client.review_basis(message, trace_id)
-                print("LLM输出:\n")
-                print(llm_out)
+                
 
-                # 使用标准化处理器处理响应
+                # from core.construction_review.component.reviewers.utils.timeliness import review_reference_timeliness
+                # llm_out = await review_reference_timeliness(reference_text=grouped_candidates, review_text=basis_items)
+                
+                
                 standardized_result = self.response_processor.process_llm_response(llm_out, "timeliness_check")
-
+                print("标准化处理器处理响应:\n")
+                print(standardized_result)
                 # 统计问题数量
                 issue_count = sum(1 for item in standardized_result if item.get('exist_issue', False))
                 logger.info(f"编制依据批次审查完成:总计 {len(basis_items)} 项,发现问题 {issue_count} 项")
@@ -304,7 +311,7 @@ class BasisReviewService:
                 return standardized_result
 
             except Exception as e:
-                logger.error(f" 批次处理失败: {e}")
+                logger.error(f" 批次处理失败1: {e}")
                 return [{
                     "check_item": "reference_check",
                     "check_result": {"error": str(e), "basis_items": basis_items},
@@ -409,7 +416,7 @@ class BasisReviewService:
 
             except Exception as e:
                 logger.error(f" 批次 {batch_index} 处理失败: {e}")
-                error_result = [{"name": name, "is_standard": False, "status": "", "meg": f"批次处理失败: {str(e)}"}
+                error_result = [{"name": name, "is_standard": False, "status": "", "meg": f"批次处理失败2: {str(e)}"}
                                 for name in batch]
 
                 # 即使失败也要推送结果
@@ -491,6 +498,7 @@ class BasisReviewService:
 
         logger.info(f" 异步审查完成,耗时: {elapsed_time:.4f} 秒")
         logger.info(f" 总编制依据: {total_items}, 问题项: {issue_items}, 成功批次: {successful_batches}/{total_batches}")
+        print(final_results)
         return final_results
 
 
@@ -509,37 +517,8 @@ async def review_all_basis_async(text: str, max_concurrent: int = 4) -> List[Lis
 if __name__ == "__main__":
     # 简单测试
     test_text = """
-(1)相关法律法规
-1)《中华人民共和国安全生产法》2021年修订版
-2)《中华人民共和国环境保护法》2021年修订版
-3)《建设工程安全生产管理条例》2023年最新修正
-4)《中华人民共和国道路交通安全法》2021年4月29日修订
-5)《中华人民共和国水土保持法》2010年12月25日修订
-6)《公路水运危险性较大工程专项施工方案编制审查规程》(JT/T 1495-2024)
-7)《公路水运工程临时用电技术规程》(JT/T 1499—2024)
-8)《建设工程质量管理条例》2019年4月23日修订
-9)《建设工程安全生产管理条例》2023年最新修正
-10)《四川省安全生产条例》2023年5月25日修订
-11)《危险性较大的分部分项工程安全管理规定》住建部令第37号
-12)《建筑机械使用安全技术规程》JGJ 33—2012
-13)《起重机—手势信号》GB/T 5082—2019
-14)《架桥机通用技术条件》GB/T 26470—2011
-15)《施工现场机械设备检查技术规范》JGJ 160—2016
-16)《粗直径钢丝绳》GB/T 20067—2017
-17)《建筑施工起重吊装工程安全技术规范》JGJ 276—2012
-18)《架桥机安全规程》GB 26469—2011
-19)《起重机械安全规程》GB 6067.1—2010
-20)《电气装置安装工程起重机电气装置施工及验收规范》GB 50256—2014
-21)《起重设备安装工程施工及验收规范》GB 50278—2010
-22)《公路工程施工安全技术规范》JTG F90—2015
-23)《建筑施工高处作业安全技术规范》JGJ 80—2016
-24)《电力高处作业防坠器》DL/T 1147-2009
-(2)项目文件
-1)《S81线会理至禄劝(四川境)高速公路两阶段施工图设计》;
-2)《S81线会理至禄劝(四川境)高速公路ZCB1-3标段实施性施工组织设计》;
-3)《S81线会理至禄劝(四川境)高速公路ZCB1-3标段T梁预制、运输及安装专项施工方案》。
-(3)我公司现有可投入工程的施工技术力量和机械设备;
-(4)近年来,我公司参加类似工程的经验;
-(5)本合同段工程现场踏勘、调查所获得的现场情况、自然环境、人文环境、市场环境等参考资料;
+(16)《公路工程施工现场安全防护技术要求》(JTT1508-2024);
+(17)《公路水运工程临时用电技术 规程》(JTT1499-2024);
+(18)《坠落防护 水平生命线装置》(GB 38454-2019);
     """
     result = asyncio.run(review_all_basis_async(test_text))

+ 52 - 46
core/construction_review/component/reviewers/utils/directory_extraction.py

@@ -1,23 +1,23 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
 
-"""
-@Project   : LQAgentPlatform
-@File      : directory_extraction.py
-@IDE       : VSCode
-@Author    :
-@Date      : 2025-12-30
-@Description: 编制依据信息抽取工具,从文本中提取法规/标准名称
-"""
+# ===============================================
+# 编制依据目录抽取
+#
+# @author :ZengChao
+# @date   :2025-12-30 15:13
+# @logo   :(=^・w・^=)
+# ===============================================
 
 from __future__ import annotations
 
+import json  # ✅ 最小修改:新增
 import re
 from typing import List
 
-from pydantic import BaseModel, Field
+from pydantic import BaseModel, Field, ValidationError  # ✅ 最小修改:新增 ValidationError
 from langchain_core.prompts import ChatPromptTemplate
-from langchain_core.output_parsers import PydanticOutputParser
+from langchain_core.output_parsers import PydanticOutputParser, StrOutputParser  # ✅ 最小修改:新增 StrOutputParser
 
 from foundation.observability.logger.loggering import server_logger as logger
 
@@ -36,17 +36,25 @@ class BasisItems(BaseModel):
 
 
 # --------- 2) Prompt(强约束:只抽《》条目,输出可解析结构)---------
-SYSTEM = """/no_think
+SYSTEM = """
 你是信息抽取助手。请从中文文本中抽取"编制依据/法律法规/规范标准"等条目。
 规则:
 1) 只抽取包含书名号《 》的条目。
 2) 每条条目包括:title(《》内名称,去掉书名号)、suffix(《》后面的版本/日期/修订说明,可为空)、raw(该条目原文)。
 3) 忽略标题行、段落说明、无《》的行。
-4) 输出必须严格符合格式要求,不要输出任何额外文字。"""
+4) 输出必须严格符合格式要求,不要输出任何额外文字。
+"""
+HUMAN ="""
+文本如下:
+{input_text}
+【输出格式要求】
+{format_instructions}
+/no_think
+"""
 
 prompt = ChatPromptTemplate.from_messages([
     ("system", SYSTEM),
-    ("human", "文本如下:\n---\n{input_text}\n---\n\n{format_instructions}")
+    ("human", HUMAN)
 ])
 
 
@@ -73,36 +81,30 @@ def fallback_regex(text: str) -> BasisItems:
     return BasisItems(items=items)
 
 
-def _parse_with_retry(llm, parser: PydanticOutputParser, raw_out: str) -> BasisItems:
+# --------- 4) 最小修改:只做一件事,提取第一个 JSON ---------
+def extract_first_json(text: str) -> dict:
     """
-    替代 OutputFixingParser:先解析;失败则让模型按格式“重写为合法JSON”再解析。
+    从任意模型输出中提取第一个完整 JSON 对象 { ... }
+    不关心 think、不关心多余文本
     """
-    # 1) 首次直接解析
-    try:
-        return parser.parse(raw_out)
-    except Exception as e1:
-        logger.warning(f"[编制依据提取] 解析失败,尝试修复输出: {e1}")
-
-    # 2) 修复回合:让模型严格改写为可解析结果
-    format_instructions = parser.get_format_instructions()
-    fix_prompt = (
-        "你刚才的输出无法被程序解析。"
-        "请将下面【原始输出】严格改写成完全符合【输出格式】的内容。"
-        "只输出最终结果,不要任何解释。\n\n"
-        f"【输出格式】\n{format_instructions}\n\n"
-        f"【原始输出】\n{raw_out}"
-    )
+    start = text.find("{")
+    if start == -1:
+        raise ValueError("未找到 JSON 起始 '{'")
 
-    try:
-        fixed_msg = llm.invoke(fix_prompt)
-        fixed_out = getattr(fixed_msg, "content", str(fixed_msg))
-        return parser.parse(fixed_out)
-    except Exception as e2:
-        logger.error(f"[编制依据提取] 修复后仍解析失败: {e2}")
-        raise
+    depth = 0
+    for i in range(start, len(text)):
+        ch = text[i]
+        if ch == "{":
+            depth += 1
+        elif ch == "}":
+            depth -= 1
+            if depth == 0:
+                return json.loads(text[start:i + 1])
 
+    raise ValueError("JSON 花括号未闭合")
 
-# --------- 4) 主函数:使用 LangChain 抽取 ---------
+
+# --------- 5) 主函数:使用 LangChain 抽取(最小改动版) ---------
 def extract_basis_with_langchain_qwen(text: str) -> BasisItems:
     """
     使用 LangChain + LLM 提取编制依据信息
@@ -116,23 +118,24 @@ def extract_basis_with_langchain_qwen(text: str) -> BasisItems:
         # 获取模型实例
         llm = model_handler._get_qwen_model()
 
-        # 创建解析器
+        # 创建解析器(✅ 保留:仅用于 format_instructions)
         parser = PydanticOutputParser(pydantic_object=BasisItems)
 
-        # 构建链
-        chain = prompt | llm
+        # 构建链(✅ 最小修改:加 StrOutputParser,直接拿字符串)
+        chain = prompt | llm | StrOutputParser()
 
         logger.info(f"[编制依据提取] 开始使用 LLM 提取,文本长度: {len(text)}")
 
-        # 调用模型
-        msg = chain.invoke({
+        # 调用模型 -> raw_out 是 str
+        raw_out = chain.invoke({
             "input_text": text,
             "format_instructions": parser.get_format_instructions()
         })
-        raw_out = msg.content
+        print(raw_out)
 
-        # 解析输出(含一次修复回合)
-        parsed = _parse_with_retry(llm=llm, parser=parser, raw_out=raw_out)
+        # ✅ 最小修改:不再 parser.parse / 不再 _parse_with_retry
+        data = extract_first_json(raw_out)
+        parsed = BasisItems.model_validate(data)
 
         # 清洗:title 去书名号,补 raw
         cleaned: List[BasisItem] = []
@@ -155,6 +158,9 @@ def extract_basis_with_langchain_qwen(text: str) -> BasisItems:
         logger.warning("[编制依据提取] LLM 未提取到内容,使用兜底方案")
         return fallback_regex(text)
 
+    except (json.JSONDecodeError, ValidationError, ValueError) as e:
+        logger.error(f"[编制依据提取] LLM 输出解析失败: {e},使用兜底方案")
+        return fallback_regex(text)
     except Exception as e:
         logger.error(f"[编制依据提取] LLM 提取失败: {str(e)},使用兜底方案")
         return fallback_regex(text)