Browse Source

Merge branch 'dev' of http://47.109.151.80:15030/CRBC-MaaS-Platform-Project/LQAgentPlatform into dev

WangXuMing 1 month ago
parent
commit
94e3a3e75f

+ 15 - 12
core/construction_review/component/reviewers/prompt/timeliness_basis_reviewer.yaml

@@ -2,26 +2,25 @@ timeliness_basis_reviewer:
   system_prompt: |
     /no_think
     【角色】
-    你是一个【规范性引用文件时效性风险点评审助手】。
+    你是一个【规范性引用文件时效性风险点评审助手】,你只能依据【参考文件】中的内容回答问题
 
     【任务】
     你的任务是基于参考文件对审查文件的时效性进行评估,并提出改进建议。
-    **注意**文件名相同找不到相同编号时为规范版本号错误。
     参考文件格式为《》()状态为。《》中是文件名,()【】是编号。
 
     【字段说明】
-    - **issue_point**:时效性判断
-    - **location**:审查内容,保持与原文一样
-    - **suggestion**:建议(可执行动作)
-    - **reason**:问题的原因分析和依据说明,基于标准规范要求的详细说明
-    - **risk_level**:风险水平,只能是 "无风险" / "高风险"
+    - **issue_point**:选择最符合的问题。
+    - **location**:审查内容,保持与原文一样
+    - **suggestion**:建议(可执行动作)
+    - **reason**:问题的原因分析和依据说明,基于标准规范要求的详细说明
+    - **risk_level**:风险水平,只能是 "无风险" / "高风险"
 
     【时效性判定类型(仅限以下四类)】
-    无参考文件:审查文件与参考中的文件名与编号均不一样,对应 "无风险"
-    规范版本号正确:审查文件与参考中的文件名与编号均一致状态为现行,对应 "无风险"
-    规范版本号错误:审查文件与参考中存在文件名相同但编号不同,对应 "高风险"
-    引用已废止的规范:参考文件中对应文件状态为废止,对应 "高风险"
-    引用已被替代的标准:审查文件与参考中的文件名与编号均一致,状态为废止但对应状态为现行的新文件,对应 "高风险"
+    无参考文件:审查文件与参考中的文件名与编号均不一样,对应 "无风险"
+    规范版本号正确:审查文件与参考中的文件名与编号均一致状态为现行,对应 "无风险"
+    规范版本号错误:审查文件与参考中存在文件名相同但编号不同,对应 "高风险"
+    引用已废止的规范:参考文件中对应文件状态为废止,对应 "高风险"
+    引用已被替代的标准:审查文件与参考中的文件名与编号均一致,状态为废止但对应状态为现行的新文件,对应 "高风险"
   
 
     【输出格式规范】
@@ -49,6 +48,10 @@ timeliness_basis_reviewer:
       }}
     ]
     ```
+    【限制】  
+    - 文件名相同找不到相同编号时为规范版本号错误。
+    - suggestion一定要基于参考文件回答。
+
     
   user_prompt_template: |
     请审查以下内容的编制依据时效性和相关性: 

+ 37 - 2
core/construction_review/component/reviewers/timeliness_basis_reviewer.py

@@ -343,7 +343,9 @@ class BasisReviewService:
     async def review_all(self, text: str, collection_name: str = "first_bfp_collection_status",
                         progress_manager=None, callback_task_id: str = None) -> List[List[Dict[str, Any]]]:
         """异步批量审查所有编制依据"""
-        items = self.text_processor.extract_basis(text)
+        from core.construction_review.component.reviewers.utils.directory_extraction import extract_basis_with_langchain_qwen
+        items = [item.raw for item in extract_basis_with_langchain_qwen(text).items]
+        #items = self.text_processor.extract_basis(text)
         if not items:
             return []
 
@@ -506,5 +508,38 @@ async def review_all_basis_async(text: str, max_concurrent: int = 4) -> List[Lis
 
 if __name__ == "__main__":
     # 简单测试
-    test_text = "《中华人民共和国特种设备安全法》"
+    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)本合同段工程现场踏勘、调查所获得的现场情况、自然环境、人文环境、市场环境等参考资料;
+    """
     result = asyncio.run(review_all_basis_async(test_text))

+ 201 - 0
core/construction_review/component/reviewers/utils/directory_extraction.py

@@ -0,0 +1,201 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""
+@Project   : LQAgentPlatform
+@File      : directory_extraction.py
+@IDE       : VSCode
+@Author    :
+@Date      : 2025-12-30
+@Description: 编制依据信息抽取工具,从文本中提取法规/标准名称
+"""
+
+from __future__ import annotations
+
+import re
+from typing import List
+
+from pydantic import BaseModel, Field
+from langchain_core.prompts import ChatPromptTemplate
+from langchain_core.output_parsers import PydanticOutputParser
+
+from foundation.observability.logger.loggering import server_logger as logger
+
+
+# --------- 1) 结构定义 ---------
+class BasisItem(BaseModel):
+    """编制依据条目"""
+    title: str = Field(..., description="法规/标准名称,不含书名号《》")
+    suffix: str = Field("", description="《》后面的版本/日期/修订说明,可为空")
+    raw: str = Field(..., description="该条目原文(尽量原样保留)")
+
+
+class BasisItems(BaseModel):
+    """编制依据条目列表"""
+    items: List[BasisItem]
+
+
+# --------- 2) Prompt(强约束:只抽《》条目,输出可解析结构)---------
+SYSTEM = """/no_think
+你是信息抽取助手。请从中文文本中抽取"编制依据/法律法规/规范标准"等条目。
+规则:
+1) 只抽取包含书名号《 》的条目。
+2) 每条条目包括:title(《》内名称,去掉书名号)、suffix(《》后面的版本/日期/修订说明,可为空)、raw(该条目原文)。
+3) 忽略标题行、段落说明、无《》的行。
+4) 输出必须严格符合格式要求,不要输出任何额外文字。"""
+
+prompt = ChatPromptTemplate.from_messages([
+    ("system", SYSTEM),
+    ("human", "文本如下:\n---\n{input_text}\n---\n\n{format_instructions}")
+])
+
+
+# --------- 3) 兜底:模型失败时,用简单规则捞《》行 ---------
+def fallback_regex(text: str) -> BasisItems:
+    """
+    兜底方案:使用正则表达式提取编制依据
+    """
+    items: List[BasisItem] = []
+    for line in text.replace("\r\n", "\n").replace("\r", "\n").split("\n"):
+        s = line.strip()
+        if not s or "《" not in s or "》" not in s:
+            continue
+        m = re.search(r'《([^《》]+)》\s*(.*)$', s)
+        if not m:
+            continue
+        items.append(BasisItem(
+            title=m.group(1).strip(),
+            suffix=m.group(2).strip(),
+            raw=s
+        ))
+
+    logger.info(f"[编制依据提取] 兜底方案提取到 {len(items)} 条")
+    return BasisItems(items=items)
+
+
+def _parse_with_retry(llm, parser: PydanticOutputParser, raw_out: str) -> BasisItems:
+    """
+    替代 OutputFixingParser:先解析;失败则让模型按格式“重写为合法JSON”再解析。
+    """
+    # 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}"
+    )
+
+    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
+
+
+# --------- 4) 主函数:使用 LangChain 抽取 ---------
+def extract_basis_with_langchain_qwen(text: str) -> BasisItems:
+    """
+    使用 LangChain + LLM 提取编制依据信息
+    """
+    # 标准化文本(无论是否走LLM,都先做)
+    text = text.replace("\r\n", "\n").replace("\r", "\n")
+
+    try:
+        from foundation.ai.models.model_handler import model_handler
+
+        # 获取模型实例
+        llm = model_handler._get_qwen_model()
+
+        # 创建解析器
+        parser = PydanticOutputParser(pydantic_object=BasisItems)
+
+        # 构建链
+        chain = prompt | llm
+
+        logger.info(f"[编制依据提取] 开始使用 LLM 提取,文本长度: {len(text)}")
+
+        # 调用模型
+        msg = chain.invoke({
+            "input_text": text,
+            "format_instructions": parser.get_format_instructions()
+        })
+        raw_out = msg.content
+
+        # 解析输出(含一次修复回合)
+        parsed = _parse_with_retry(llm=llm, parser=parser, raw_out=raw_out)
+
+        # 清洗:title 去书名号,补 raw
+        cleaned: List[BasisItem] = []
+        for it in parsed.items:
+            title = (it.title or "").replace("《", "").replace("》", "").strip()
+            suffix = (it.suffix or "").strip()
+            raw = (it.raw or "").strip()
+
+            if not title:
+                continue
+            if not raw:
+                raw = f"《{title}》{suffix}".strip()
+
+            cleaned.append(BasisItem(title=title, suffix=suffix, raw=raw))
+
+        if cleaned:
+            logger.info(f"[编制依据提取] LLM 提取成功,共 {len(cleaned)} 条")
+            return BasisItems(items=cleaned)
+
+        logger.warning("[编制依据提取] LLM 未提取到内容,使用兜底方案")
+        return fallback_regex(text)
+
+    except Exception as e:
+        logger.error(f"[编制依据提取] LLM 提取失败: {str(e)},使用兜底方案")
+        return fallback_regex(text)
+
+
+if __name__ == "__main__":
+    demo = """一)编制依据
+(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)本合同段工程现场踏勘、调查所获得的现场情况、自然环境、人文环境、市场环境等参考资料;
+"""
+    result = extract_basis_with_langchain_qwen(demo)
+    print(f"\n提取到 {len(result.items)} 条编制依据:")
+    for idx, item in enumerate(result.items, 1):
+        print(f"\n{idx}. {item.model_dump()}")