Quellcode durchsuchen

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

WangXuMing vor 1 Monat
Ursprung
Commit
db4f9b7b58

+ 13 - 9
core/construction_review/component/ai_review_engine.py

@@ -732,15 +732,20 @@ class AIReviewEngine(BaseReviewer):
                 max_concurrent=concurrent_workers
             )
             logger.info("  组件初始化完成")
-            
+
 
             # 3. 执行审查
             logger.info("\n[4/5] 开始执行审查...")
             logger.info(f"  使用模型: {llm_client.model_type}")
             logger.info(f"  最大并发数: {concurrent_workers}")
-            
+
             review_results = await review_pipeline.review(documents, specification)
-            review_results_flag = pd.DataFrame(review_results)["chapter_classification"].unique().tolist()
+            review_results_df = pd.DataFrame(review_results)
+            df_section_label = review_results_df['section_label'].str.split('->').str[0]
+            review_results_df['title'] = df_section_label
+            review_results_df.to_csv(Path('temp') / 'document_temp' / '2_spec_review_results.csv', encoding='utf-8-sig', index=False)
+            review_results_flag = review_results_df["chapter_classification"].unique().tolist()
+
             # with open(r'temp\document_temp\1_spec_review_results.json', 'w', encoding='utf-8') as f:
             #     json.dump(review_results, f, ensure_ascii=False, indent=4)
             # 统计结果
@@ -753,17 +758,16 @@ class AIReviewEngine(BaseReviewer):
             analyzer = ResultAnalyzer(str(csv_path))
             processed_results = analyzer.process_results(review_results)
             spec_summary_csv_path = Path('temp') / 'document_temp' / '3_spec_review_summary.csv'
-            summary_rows = analyzer.build_spec_summary(processed_results,spec_summary_csv_path)
-            logger.info(f"  规范覆盖汇总结果已保存至: {spec_summary_csv_path}")
+            summary_rows = analyzer.build_spec_summary(processed_results)
+            # logger.info(f"  规范覆盖汇总结果已保存至: {spec_summary_csv_path}")
             summary_rows = pd.DataFrame(summary_rows)
             summary_rows = summary_rows[summary_rows['标签'].isin(review_results_flag)]
             # summary_rows.to_csv(str(spec_summary_csv_path), encoding='utf-8-sig', index=False)
             summary_rows = summary_rows.to_dict('records')
             # 生成缺失要点 JSON 列表,便于前端消费
-            #missing_issue_json_path = Path(r'temp\document_temp') / 'spec_review_missing_issues.json'
+
             issues = analyzer.build_missing_issue_list(summary_rows)
-            # with open(r'temp\document_temp\4_spec_review_missing_issues.json', 'w', encoding='utf-8') as f:
-            #     json.dump(issues, f, ensure_ascii=False, indent=4)
+
             # 包装成外层格式化期望的结构
             execution_time = time.time() - start_time
             return {
@@ -776,7 +780,7 @@ class AIReviewEngine(BaseReviewer):
                 },
                 "success": True,
                 "execution_time": execution_time
-            }
+            } 
         except Exception as e:
             execution_time = time.time() - start_time
             error_msg = f"{name} 审查失败: {str(e)}"

+ 5 - 2
core/construction_review/component/doc_worker/classification/hierarchy_classifier.py

@@ -8,6 +8,7 @@ from __future__ import annotations
 
 from collections import Counter
 import asyncio
+import json
 from typing import Any, Dict, List, Optional
 
 from ..interfaces import HierarchyClassifier as IHierarchyClassifier
@@ -99,7 +100,8 @@ class HierarchyClassifier(IHierarchyClassifier):
                 level1_title=level1_item["title"],
                 level2_titles=level2_titles
             )
-            
+            # with open('temp/document_temp/prompt.txt', "w", encoding="utf-8") as f:
+            #     f.write(prompt["user"])
             # 构建消息列表
             messages = [
                 {"role": "system", "content": prompt["system"]},
@@ -110,7 +112,8 @@ class HierarchyClassifier(IHierarchyClassifier):
         
         # 批量异步调用LLM API
         llm_results = await self.llm_client.batch_call_async(llm_requests)
-        
+        # with open('temp/document_temp/llm_results.json', "w", encoding="utf-8") as f:
+        #     json.dump(llm_results, f, ensure_ascii=False, indent=4)
         # 处理分类结果
         classified_items = []
         category_stats = Counter()

+ 4 - 2
core/construction_review/component/doc_worker/config/prompt.yaml

@@ -6,6 +6,7 @@ toc_classification:
     - 一级目录名称本身是重要的分类依据,即使没有二级目录,也要根据一级目录名称进行分类;
     - 必须从提供的标准类别中选择一个,所有标准类别都是平等的,没有偏好,不能创建新类别;
     - 如果待分类的目录与多个标准类别都相关,选择最匹配的一个;
+    - 注意:其他资料类别有自己标准,要严格符合其他资料类别的标准才能分到这个类别;
 
     - /no_think
   user_template: |
@@ -17,8 +18,9 @@ toc_classification:
     {{ level2_titles }}
 
     分类标准(一级目录名称及其包含的二级目录集合):
+
     {{ classification_standards }}
-    
+        - 十一、非标准项(用于接收不符合前十项类别的目录项)
 
     输出要求(只输出 JSON):
     {
@@ -38,7 +40,7 @@ toc_classification:
     - 施工管理及作业人员配备与分工 -> management
     - 验收要求 -> acceptance
     - 其他资料 -> other
-
+    - 非标准项 -> no_standard
 
 
 

+ 3 - 4
core/construction_review/component/doc_worker/utils/prompt_loader.py

@@ -103,15 +103,14 @@ class PromptLoader:
             level2_count = len(level2_list)
             level2_text = "、".join(level2_list)
             
-            # 将一级目录名称和二级目录集合都包含在分类标准中
-            # 强调:匹配时只看核心标题名称,忽略编号前缀
+            # 简化格式,只包含核心标题和二级目录列表
             if level2_count > 0:
                 standards_lines.append(
-                    f"    - {number_prefix}、{level1}(核心标题名称:{level1};包含的二级目录:{level2_text}等{level2_count}个方面):匹配核心标题「{level1}」,包含{level2_text}等{level2_count}个方面。"
+                    f"    - {number_prefix}、{level1}(包含{level2_text}等{level2_count}个方面)"
                 )
             else:
                 standards_lines.append(
-                    f"    - {number_prefix}、{level1}(核心标题名称:{level1}):匹配核心标题「{level1}」。"
+                    f"    - {number_prefix}、{level1}"
                 )
         
         self._classification_standards = "\n".join(standards_lines)

BIN
core/construction_review/component/reviewers/check_completeness/config/Construction_Plan_Content_Specification.csv


+ 8 - 16
core/construction_review/component/reviewers/prompt/reference_basis_reviewer.yaml

@@ -1,25 +1,17 @@
 reference_basis_reviewer:
   system_prompt: |
     忘掉你之前所有的内容,完成下面的任务。
-    你是一个“格式校验专家(validator)”,只检查格式是否正确,对内容不做任何检查和修改
+    你是一个“格式校验专家(validator)”,只检查格式是否正确,对内容不做任何检查、建议、修改,忽略全半角符号的区别
 
-    =========================
-    【正确格式】
-    《名称》(编号)
-    =========================
-
-    【格式规则(只检查格式)】
-    1) 名称部分必须被中文书名号《》包裹
+    【检查内容】
+    1) 名称部分必须被书名号《》包裹
 
     2) 编号部分必须使用括号包裹
 
-    3) 《名称》与(编号)一一对应
-       - 一个《名称》应对应一个(编号)
-    
-    4) 不考虑任何空格问题 
+    3) 一个《名称》应对应一个(编号)
 
-    【判定优先级(必须按顺序执行)
-    1) 只要违反任意规则 => issue_point="编制依据格式错误" 且 risk_level="风险"
+    【判定过程】
+    1) 只要违反任意规则 => issue_point="编制依据格式错误" 且 risk_level="中风险"
     2) 否则 => issue_point="编制依据格式正确" 且 risk_level="无风险"
 
     【输出硬约束】
@@ -40,13 +32,13 @@ reference_basis_reviewer:
       - 必须与原输入文本完全一致(原样复制)
 
     - suggestion:
-      - 告诉添加或者修改,不能编造内容,格式正确时可填写 "无"
+      - 对错误内容提出修改建议,格式正确时可填写 "无"
 
     - reason(只能描述格式,不得涉及语义):
       - 简洁的说明存在的问题,格式正确时可填写 "无"
 
     - risk_level:
-      - 只能是 "无风险" 或 "风险"
+      - 只能是 "无风险" 或 "风险"
 
     【输出格式规范】
     - 只输出 JSON 数组

+ 20 - 11
core/construction_review/component/reviewers/reference_basis_reviewer.py

@@ -8,6 +8,8 @@ from typing import Any, Dict, List, Optional
 from core.construction_review.component.reviewers.utils.directory_extraction import BasisItem, BasisItems
 from core.construction_review.component.reviewers.utils.inter_tool import InterTool
 from core.construction_review.component.reviewers.utils.prompt_loader import PromptLoader
+from core.construction_review.component.reviewers.utils.punctuation_checker import check_punctuation
+from core.construction_review.component.reviewers.utils.punctuation_result_processor import process_punctuation_results
 from foundation.observability.logger.loggering import server_logger as logger
 from langchain_core.prompts import ChatPromptTemplate
 from langchain_openai import ChatOpenAI
@@ -84,7 +86,7 @@ class LLMReviewClient:
             model="qwen3-30b",
             base_url="http://192.168.91.253:8003/v1",
             api_key="sk-123456",
-            temperature=0.3,
+            temperature=0.7,
         )
 
     async def review_basis(self, Message: str, trace_id: str = None) -> str:
@@ -145,16 +147,23 @@ class BasisReviewService:
 
         async with self._semaphore:
             try:
-                # 构建提示词模板和用户内容
-                prompt_template = self.message_builder.get_prompt_template()
-                message = prompt_template.partial(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)
+                # 第一步:调用标点符号检查器
                 
-                # 使用标准化处理器处理响应
-                standardized_result = self.response_processor.process_llm_response(llm_out, "reference_check", "basis","basis_reference_check")
+                checker_result = await check_punctuation(basis_items)
+                print(checker_result)
+                
+                # 第二步:调用结果处理器,生成详细的问题分析报告
+                processor_result = await process_punctuation_results(checker_result)
+                print("\n【第二步】问题分析报告输出:")
+                print(processor_result)
+                
+                # 第三步:转换为标准格式
+                standardized_result = self.response_processor.process_llm_response(
+                    processor_result, 
+                    "reference_check", 
+                    "basis",
+                    "basis_reference_check"
+                )
 
                 # 统计问题数量
                 issue_count = sum(1 for item in standardized_result if item.get('exist_issue', False))
@@ -347,6 +356,6 @@ async def review_all_basis_async(basis_items: BasisItems, max_concurrent: int =
 if __name__ == "__main__":
     # 简单测试
     test_basis_items = BasisItems(items=[
-        BasisItem(title="中华人民共和国特种设备安全法", suffix="2023", raw="《起重机用钢丝绳》(GB T 34198-2017)")
+        BasisItem(title="中华人民共和国特种设备安全法", suffix="2023", raw="《公路桥涵施工技术规范》JTG/T 3650-2020);")
     ])
     result = asyncio.run(review_all_basis_async(test_basis_items))

+ 272 - 0
core/construction_review/component/reviewers/utils/punctuation_checker.py

@@ -0,0 +1,272 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+import json
+from typing import List, Optional
+
+from pydantic import BaseModel, Field, ValidationError
+from langchain_core.prompts import ChatPromptTemplate
+from langchain_core.output_parsers import PydanticOutputParser, StrOutputParser
+from langchain_openai import ChatOpenAI
+
+
+# ===== 1) 定义结构 =====
+class PunctuationResult(BaseModel):
+    original_text: str = Field(..., description="审查的规范原文,与输入完全一致")
+    title_mark_status: bool = Field(..., description="书名号使用是否正确,true表示正确,false表示错误")
+    bracket_status: Optional[bool] = Field(..., description="括号使用是否正确,true表示正确,false表示错误,null表示没有编号")
+
+
+class PunctuationResults(BaseModel):
+    items: List[PunctuationResult]
+
+
+# ===== 2) SYSTEM Prompt =====
+SYSTEM = """
+你是【标点符号规范性检查助手】。
+
+【任务】
+仅对已通过“成对出现”预检的文本,检查书名号和括号是否**包裹完整且位置正确**。
+(预检已经保证:
+    - 书名号《》至少各出现一次且数量相等
+    - 括号(/( )/)至少各出现一次且数量相等(中文、英文括号视为同类)
+    因此你只需判断包裹范围是否正确、是否遗漏内容。)
+
+【判断标准】
+- title_mark_status:书名号需完全包裹规范名称,且不多包/漏包
+- bracket_status:括号需完全包裹规范编号,且不多包/漏包;编号可能是各种形式,如果文本中没有编号,设置为null
+
+【输出要求】
+- 为每个输入文本输出一个检查结果
+- 确保输出数量与输入一致
+- original_text 必须与输入完全一致
+- title_mark_status 必须是布尔值:true表示正确,false表示错误
+- bracket_status 必须是布尔值或null:true表示正确,false表示错误,null表示没有编号
+"""
+
+HUMAN = """
+请检查以下文本中书名号和括号的**内容是否全部被包裹**,以及是否有编号。
+(所有文本已通过成对出现的预检,至少各有一对《》且数量相等。)
+
+【判断原则】
+- 仅检查包裹的**完整性**:书名号是否包裹了规范名称的全部内容;括号是否包裹了编号的全部内容
+- 中文括号()和英文括号()混用视为正常,不区分
+- 若内容在符号外遗漏,或符号包裹了多余内容,则判定为false
+- **重要**:如果文本中没有编号(完全没有任何()或()符号),则bracket_status设置为null
+
+【简单示例】
+示例1:《建筑抗震设》计规范 (GB 50011-2001)
+- 规范名称是"建筑抗震设计规范",但只有"建筑抗震设"被包裹,"计规范"在外 → title_mark_status=false
+- 编号被完整包裹 → bracket_status=true
+
+示例2:《建筑抗震设计规范》
+- 书名号包裹了完整的规范名称 → title_mark_status=true
+- 没有编号 → bracket_status=null
+
+示例3:《建筑抗震设计规范》(GB 50011-2001)
+- 书名号包裹了完整的规范名称 → title_mark_status=true
+- 英文括号包裹了完整的编号 → bracket_status=true(混用不算错)
+
+【待检查文本】
+{items}
+
+【输出格式要求】
+{format_instructions}
+/no_think
+"""
+
+# ===== 3) Output Parser =====
+parser = PydanticOutputParser(pydantic_object=PunctuationResults)
+
+# ===== 4) Prompt =====
+prompt = ChatPromptTemplate.from_messages([
+    ("system", SYSTEM),
+    ("human", HUMAN)
+])
+
+# ===== 5) LLM =====
+llm = ChatOpenAI(
+    model="qwen3-30b",
+    base_url="http://192.168.91.253:8003/v1",
+    api_key="sk-123456",
+    temperature=0.7,
+)
+
+
+# ===== 6) 提取第一个 JSON =====
+def extract_first_json(text: str) -> dict:
+    """从任意模型输出中提取第一个完整 JSON 对象 { ... }"""
+    start = text.find("{")
+    if start == -1:
+        raise ValueError("未找到 JSON 起始 '{'")
+
+    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 花括号未闭合")
+
+
+# ===== 7) 核心方法 =====
+async def check_punctuation(items: List[str]) -> str:
+    """
+    检查规范文本中的书名号和括号使用是否正确,先进行成对预检,再用LLM判断包裹完整性
+    
+    Args:
+        items: 待检查的规范文本列表
+        
+    Returns:
+        检查结果的JSON字符串,包含三个字段:
+        - original_text: 原文
+        - title_mark_status: 书名号使用是否正确(true/false)
+        - bracket_status: 括号使用是否正确(true/false/null,null表示没有编号)
+    """
+    # 1) 预检:是否存在且成对出现
+    pre_results = []  # 预填结果,若需LLM再补充
+    llm_inputs = []   # 需要LLM判定包裹完整性的文本
+
+    for text in items:
+        # 书名号成对判定
+        left_title = text.count("《")
+        right_title = text.count("》")
+        title_pair_ok = left_title == right_title and left_title > 0
+
+        # 括号成对判定(中英文括号混用视为同类)
+        left_br = text.count("(") + text.count("(")
+        right_br = text.count(")") + text.count(")")
+        bracket_pair_ok = left_br == right_br and left_br > 0
+        
+        # 只有书名号和括号都存在时,才判断一一对应
+        # 情况1:都不存在 → 都为False
+        if left_title == 0 and left_br == 0:
+            pre_results.append({
+                "original_text": text,
+                "title_mark_status": False,
+                "bracket_status": None
+            })
+            continue
+        
+        # 情况2:只有书名号,没有括号 → bracket_status为None
+        if left_title > 0 and left_br == 0:
+            if title_pair_ok:
+                llm_inputs.append(text)
+            else:
+                pre_results.append({
+                    "original_text": text,
+                    "title_mark_status": False,
+                    "bracket_status": None
+                })
+            continue
+        
+        # 情况3:只有括号,没有书名号 → title_mark_status为False
+        if left_title == 0 and left_br > 0:
+            pre_results.append({
+                "original_text": text,
+                "title_mark_status": False,
+                "bracket_status": bool(bracket_pair_ok)
+            })
+            continue
+        
+        # 情况4:两者都存在,判断一一对应
+        if left_title != left_br:
+            # 数量不对应,两个都为False
+            pre_results.append({
+                "original_text": text,
+                "title_mark_status": False,
+                "bracket_status": False
+            })
+            continue
+        
+        # 检查括号是否在书名号之后
+        bracket_after_title = True
+        if bracket_pair_ok and title_pair_ok:
+            # 找最后一个书名号的位置
+            last_title_pos = max(text.rfind("《"), text.rfind("》"))
+            # 找第一个括号的位置
+            first_bracket_pos = min(
+                text.find("(") if "(" in text else float('inf'),
+                text.find("(") if "(" in text else float('inf')
+            )
+            bracket_after_title = last_title_pos < first_bracket_pos
+
+        if not title_pair_ok or not bracket_pair_ok or not bracket_after_title:
+            # 预检失败或位置不正确,直接判定对应项为False,无需LLM
+            pre_results.append({
+                "original_text": text,
+                "title_mark_status": bool(title_pair_ok),
+                "bracket_status": bool(bracket_pair_ok and bracket_after_title)
+            })
+        else:
+            # 成对且位置正确通过,交给LLM判断包裹是否完整和是否有编号
+            llm_inputs.append(text)
+
+    # 若无需要LLM的,直接返回预检结果
+    if not llm_inputs:
+        return json.dumps(pre_results, ensure_ascii=False, indent=2)
+
+    chain = prompt | llm | StrOutputParser()
+    format_instructions = parser.get_format_instructions()
+
+    payload = {
+        "items": json.dumps(llm_inputs, ensure_ascii=False, indent=2),
+        "format_instructions": format_instructions
+    }
+
+    last_err = None
+
+    llm_result: List[dict] = []
+    for _ in range(2):
+        try:
+            raw = await chain.ainvoke(payload)
+            data = extract_first_json(raw)
+            findings = PunctuationResults.model_validate(data)
+            llm_result = [x.model_dump() for x in findings.items]
+            break
+        except (Exception, ValidationError, json.JSONDecodeError) as e:
+            last_err = e
+            print(f"[标点符号检查] 解析失败,重试中: {e}")
+
+    if last_err and not llm_result:
+        raise RuntimeError(f"标点符号检查失败:{last_err}") from last_err
+
+    # 合并预检与LLM结果,按原输入顺序输出
+    merged = []
+    llm_map = {item["original_text"]: item for item in llm_result}
+    for text in items:
+        # 先看预检是否已有
+        found = next((r for r in pre_results if r["original_text"] == text), None)
+        if found:
+            merged.append(found)
+        else:
+            merged.append(llm_map.get(text, {
+                "original_text": text,
+                "title_mark_status": False,
+                "bracket_status": None
+            }))
+
+    return json.dumps(merged, ensure_ascii=False, indent=2)
+
+
+# ===== 8) 示例 =====
+if __name__ == "__main__":
+    import asyncio
+
+    # 测试用例
+    test_items = [
+        "(4)《中华人民共和国突发事件应对法》【主席令〔2007〕第 69 号】;",  # 正确
+        "《混》凝土结构设计规范(GB 50010-2010)",      # 缺少书名号
+        "建筑施工组织设计规范GB/T 50502-2015",  # 缺少括号
+        "《建筑抗震设计规范》(GB 50011)-2001",       # 括号不成对
+        "《城市道路工程设计规范(CJJ 37-2012)",    # 书名号不成对
+        "《公路工程技术标准》(JTG B01-2014)",     # 正确
+    ]
+
+    result = asyncio.run(check_punctuation(test_items))
+    print("\n标点符号检查结果:")
+    print(result)

+ 202 - 0
core/construction_review/component/reviewers/utils/punctuation_result_processor.py

@@ -0,0 +1,202 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+import json
+from typing import List, Literal
+
+from pydantic import BaseModel, Field, ValidationError
+from langchain_core.prompts import ChatPromptTemplate
+from langchain_core.output_parsers import PydanticOutputParser, StrOutputParser
+from langchain_openai import ChatOpenAI
+
+
+# ===== 1) 定义结构 =====
+RiskLevel = Literal["无风险", "中风险"]
+
+
+class PunctuationIssueResult(BaseModel):
+    issue_point: str = Field(..., description="问题点描述")
+    location: str = Field(..., description="审查内容,与输入完全一致")
+    suggestion: str = Field(..., description="修改建议,可执行动作")
+    reason: str = Field(..., description="问题的原因分析")
+    risk_level: RiskLevel = Field(..., description='风险水平,只能是 "无风险" / "中风险"')
+
+
+class PunctuationIssueResults(BaseModel):
+    items: List[PunctuationIssueResult]
+
+
+# ===== 2) SYSTEM Prompt =====
+SYSTEM = """
+你是【编制依据格式问题分析专家】。
+
+【任务】
+根据标点符号检查结果,生成详细的问题分析报告。
+
+【重要说明(必须严格遵守)】
+- location 字段必须与输入的 original_text 完全一致(一字不差)
+- 根据 title_mark_status 和 bracket_status 的值判断问题类型
+- 提供具体的修改建议和原因分析
+
+【输出要求】
+- 为每个检查结果输出一个详细的问题分析
+- 确保输出数量与输入一致
+- location 必须与 original_text 完全一致
+- 严格按照判定规则生成内容
+"""
+
+HUMAN = """
+请根据以下标点符号检查结果,生成详细的问题分析报告:
+
+【判定规则】
+
+当 title_mark_status = true 且 bracket_status = true:
+- issue_point:编制依据格式正确
+- reason:规范名称和编号的标点符号使用规范
+- suggestion:无
+- risk_level:无风险
+
+当 title_mark_status != true 时:
+- issue_point:编制依据格式错误
+- reason:从以下三种情况中选择最符合实际的问题描述:
+    1. 规范名称未被书名号包裹
+    2. 书名号不成对
+    3. 规范名称未完全被书名号包裹
+- suggestion:将规范名称用书名号《》包裹,正确格式:《规范名称》(编号)
+- risk_level:中风险
+
+当 bracket_status != true 时:
+- issue_point:编制依据格式错误
+- reason:如果bracket_status = null,则问题原因是"编号缺失";
+    否则从以下三种情况中从上到下选择符合的问题描述:
+    1. 规范编号未被括号包裹
+    2. 规范编号未完全被括号包裹
+    3. 括号不成对
+- suggestion:
+  * 如果是"编号缺失":补充编号,格式为(编号)
+  * 否则:将编号用括号()包裹,正确格式:《规范名称》(编号)
+- risk_level:中风险
+
+当 title_mark_status != true 且 bracket_status != true:
+- issue_point:编制依据格式错误
+- reason:引用不符合正确格式:《规范名称》(编号)
+- suggestion:请将引用调正为正确格式:《规范名称》(编号)并保证名称与编号一一对应
+- risk_level:中风险
+
+【标点符号检查结果】
+{check_results}
+
+【输出格式要求】
+{format_instructions}
+/no_think
+"""
+
+# ===== 3) Output Parser =====
+parser = PydanticOutputParser(pydantic_object=PunctuationIssueResults)
+
+# ===== 4) Prompt =====
+prompt = ChatPromptTemplate.from_messages([
+    ("system", SYSTEM),
+    ("human", HUMAN)
+])
+
+# ===== 5) LLM =====
+llm = ChatOpenAI(
+    model="qwen3-30b",
+    base_url="http://192.168.91.253:8003/v1",
+    api_key="sk-123456",
+    temperature=0,
+)
+
+
+# ===== 6) 提取第一个 JSON =====
+def extract_first_json(text: str) -> dict:
+    """从任意模型输出中提取第一个完整 JSON 对象 { ... }"""
+    start = text.find("{")
+    if start == -1:
+        raise ValueError("未找到 JSON 起始 '{'")
+
+    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 花括号未闭合")
+
+
+# ===== 7) 核心方法 =====
+async def process_punctuation_results(check_results: str) -> str:
+    """
+    根据标点符号检查结果生成详细的问题分析报告
+    
+    Args:
+        check_results: punctuation_checker 的返回结果(JSON字符串)
+        
+    Returns:
+        问题分析报告的JSON字符串,包含五个字段:
+        - issue_point: 问题点描述
+        - location: 审查内容(与原文一致)
+        - suggestion: 修改建议
+        - reason: 问题原因分析
+        - risk_level: 风险水平
+    """
+    chain = prompt | llm | StrOutputParser()
+    format_instructions = parser.get_format_instructions()
+
+    payload = {
+        "check_results": check_results,
+        "format_instructions": format_instructions
+    }
+
+    last_err = None
+
+    for _ in range(2):
+        try:
+            raw = await chain.ainvoke(payload)
+            #print(f"[标点符号问题分析] 模型输出: {raw}...")
+            data = extract_first_json(raw)
+            findings = PunctuationIssueResults.model_validate(data)
+            result = [x.model_dump() for x in findings.items]
+            return json.dumps(result, ensure_ascii=False, indent=2)
+        except (Exception, ValidationError, json.JSONDecodeError) as e:
+            last_err = e
+
+    raise RuntimeError(f"标点符号问题分析失败:{last_err}") from last_err
+
+
+# ===== 8) 示例 =====
+if __name__ == "__main__":
+    import asyncio
+
+    # 模拟 punctuation_checker 的返回结果
+    check_results = json.dumps([
+        {
+            "original_text": "《混凝土结构设计规范》",
+            "title_mark_status": True,
+            "bracket_status": "null"
+        },
+        {
+            "original_text": "《混凝土结构设计规范》【GB 50010-2010】",
+            "title_mark_status": True,
+            "bracket_status": False
+        },
+        {
+            "original_text": "《建筑施工组织设计规范》(GB/T 50502-2015)",
+            "title_mark_status": True,
+            "bracket_status": True
+        },
+        {
+            "original_text": "建筑抗震设计规范 GB 50011-2010",
+            "title_mark_status": False,
+            "bracket_status": False
+        }
+    ], ensure_ascii=False)
+
+    result = asyncio.run(process_punctuation_results(check_results))
+    print("\n标点符号问题分析结果:")
+    print(result)

+ 2 - 2
core/construction_review/component/reviewers/utils/reference_matcher.py

@@ -55,7 +55,7 @@ HUMAN = """
    - 完全找不到任何相关文件,返回 false
 
 3. **has_exact_match**(是否有名称编号都相同的文件)
-   - 找到名称且编号相同的文件,返回 true
+   - 忽略书写格式不同,找到名称且编号相同的文件,返回 true
    - 否则返回 false
 
 4. **exact_match_info**(名称编号相同的文件及状态)
@@ -96,7 +96,7 @@ llm = ChatOpenAI(
     model="qwen3-30b",
     base_url="http://192.168.91.253:8003/v1",
     api_key="sk-123456",
-    temperature=0,
+    temperature=0.7,
 )
 
 # ===== 6) 提取第一个 JSON =====

+ 2 - 2
core/construction_review/workflows/ai_review_workflow.py

@@ -494,8 +494,8 @@ class AIReviewWorkflow:
                     state = state,
                     stage_name = state.get("stage_name", "完整性审查")
                 )
-                with open(r"temp\document_temp\4_check_completeness_result.json", "w", encoding="utf-8") as f:
-                    json.dump(check_completeness_result, f, ensure_ascii=False, indent=4)
+                # with open(r"temp\document_temp\4_check_completeness_result.json", "w", encoding="utf-8") as f:
+                #     json.dump(check_completeness_result, f, ensure_ascii=False, indent=4)
 
 
             # # 4. 执行编制依据审查