|
|
@@ -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()}")
|