Sfoglia il codice sorgente

-dev:更新了词句语法、语义逻辑的提示词,修改了敏感词审查的机制,增加了文本切分模块。先暂时提交,准备修改模型调用底层,实现不同任务可以调用不同模型。

Diamond_ore 1 mese fa
parent
commit
68b986cbd3

+ 11 - 0
core/construction_review/component/ai_review_engine.py

@@ -59,6 +59,7 @@ from foundation.ai.rag.retrieval.query_rewrite import query_rewrite_manager
 from foundation.ai.rag.retrieval.entities_enhance import entity_enhance
 from core.construction_review.component.reviewers.base_reviewer import BaseReviewer
 from core.construction_review.component.reviewers.outline_reviewer import OutlineReviewer
+from core.construction_review.component.reviewers.utils.text_split import split_text
 
 
 
@@ -406,9 +407,19 @@ class AIReviewEngine(BaseReviewer):
         reviewer_type = Stage.BASIC.value['reviewer_type']
         prompt_name = Stage.BASIC.value['grammar']
         trace_id = prompt_name+trace_id_idx
+        
+        # 使用文本切割工具将长文本切分为150-250字的片段
+        text_segments = split_text(review_content, min_length=150, target_length=250)
+        
+        # TODO: 这里可以对每个片段进行分批审查
+        # 目前先使用原始的完整内容进行审查
+        # 后续可以遍历 text_segments 进行分段审查并汇总结果
+        
         return await self.review("sensitive_word_check", trace_id, reviewer_type, prompt_name, review_content, review_references,
                                None, review_location_label, state, stage_name)
 
+    
+
     async def check_semantic_logic(self, trace_id_idx: str, review_content: str, review_references: str,
                                  review_location_label: str, state: str, stage_name: str) -> Dict[str, Any]:
         """

+ 94 - 28
core/construction_review/component/reviewers/prompt/basic_reviewers.yaml

@@ -76,44 +76,110 @@ grammar_check:
 # 1.2 语义逻辑检查功能
 semantic_logic_check:
   system_prompt: |
-    你是施工方案语义逻辑审查专家,负责检查表述一致性和逻辑清晰度。
-
-    审查要求:
-    - 重点关注语义矛盾、逻辑混乱、表述不清问题
-    - 简明扼要指出问题位置和修改建议
-    - 风险等级分类:
-      * 高风险:影响审查结论、可能导致法律问题或严重安全隐患
-      * 中风险:影响专业表达、可能导致理解偏差或一般性问题
-      * 低风险:形式问题、不影响实质内容和安全
-
-    注意事项:
-    1. 务必结合语境进行分析检查
-    2. 对于表格制表符、不需要检查
-    3. 对于术语概念不得曲解
-    4. 没有明显语义逻辑问题的内容不予检查,输出无明显问题
-    5. 已检查出的问题项仅输出一次检查结果,禁止对同一内容重复检查
+    # 角色定位 (Role)
+    你是建筑施工方案语义逻辑审查专家,专注于识别文本中的逻辑矛盾、语义冲突和表述混乱问题。
+    你的职责是发现真实存在的逻辑错误,而非挑剔文字表达或提出主观性建议。
+
+    # 审查范围
+    仅审查以下三类明确的语义逻辑问题:
+    
+    1. **逻辑矛盾** - 同一文档内出现相互冲突的陈述
+       示例:前文说"采用A方法",后文说"不采用A方法"
+       
+    2. **因果关系错误** - 原因与结果之间不存在合理的逻辑关联
+       示例:"因为天气晴朗,所以混凝土强度不足"(因果无关)
+       
+    3. **条件与结论不匹配** - 给定条件无法推导出所述结论
+       示例:"当温度低于5℃时,可正常施工"(违背常识)
+
+    # 工作流程 (Workflow)
+    
+    步骤1:通读全文,理解整体语境和专业背景
+    步骤2:识别关键陈述、条件判断、因果关系
+    步骤3:检查是否存在上述三类明确的逻辑问题
+    步骤4:对于疑似问题,进行二次验证:
+           - 是否确实存在逻辑冲突?
+           - 是否可能是专业术语的正常表达?
+           - 是否可能是上下文理解不足导致的误判?
+    步骤5:仅输出确认无误的问题
+
+    # 严格规则 (Strict Rules)
+    
+    禁止行为:
+    1. 禁止对正确内容提出修改建议
+    2. 禁止对专业术语的标准表达提出质疑
+    3. 禁止对文字风格、表达习惯提出意见
+    4. 禁止对表格格式、制表符进行检查
+    5. 禁止对同一问题重复输出
+    6. 禁止在没有明确逻辑错误时输出问题
+    7. 禁止将"表达不够完美"当作"逻辑错误"
+    8. 禁止将"可以优化"当作"必须修改"
+    
+    必须遵守:
+    1. 必须基于客观事实和逻辑规则判断
+    2. 必须确保问题的真实性和严重性
+    3. 必须结合建筑施工专业知识理解内容
+    4. 必须区分"逻辑错误"与"表达习惯"
+    5. 必须在不确定时选择"无明显问题"
+    6. 必须保持高标准:宁缺毋滥
+    
+    # 风险等级判定标准 (Risk Level)
+    
+    - **高风险**:存在明显逻辑矛盾,可能导致施工错误或安全隐患
+      示例:安全措施前后矛盾、施工顺序逻辑错误
+      
+    - **中风险**:存在因果关系错误,可能导致理解偏差
+      示例:原因分析不合理、条件判断不准确
+      
+    - **低风险**:存在轻微的表述不一致,不影响实质理解
+      示例:用词前后略有差异但不影响理解
+    
+    # 判断原则 (Principles)
+    
+    1. **疑罪从无原则**:当无法确定是否为错误时,判定为无问题
+    2. **专业优先原则**:尊重建筑施工行业的专业术语和表达习惯
+    3. **语境理解原则**:必须在完整语境中理解句子含义
+    4. **实质审查原则**:关注实质性逻辑错误,忽略形式问题
+    5. **严格证据原则**:必须有明确证据证明存在逻辑错误
 
     审查参考:
     {review_references}
 
   user_prompt_template: |
-    请审查以下内容的语义逻辑:
-
+    # 审查任务
+    请对以下施工方案内容进行语义逻辑审查,严格按照系统提示词中的三类问题范围进行检查。
+    
+    ## 待审查内容:
     {review_content}
 
-    输出格式:务必须严格按照以下标准JSON格式输出审查结果:
-    如果未发现问题,请输出:无明显问题
-    如果发现问题,请按以下格式输出:
-    location字段直接输出原字段内容,不得猜测
+    ## 审查要求:
+    1. 仅识别明确的逻辑矛盾、因果错误、条件结论不匹配问题
+    2. 必须确保问题的真实性,不得对正确内容提出修改
+    3. 不确定是否为问题时,必须输出"无明显问题"
+    4. 必须结合建筑施工专业知识和完整语境判断
+    
+    ## 输出格式:
+    
+    **情况1:未发现明确的语义逻辑问题**
+    直接输出:无明显问题
+    
+    **情况2:发现明确的语义逻辑问题**
+    严格按照以下JSON格式输出(仅输出确认无误的问题):
     ```json
     {{
-      "issue_point": "问题标题描述",
-      "location": "当前问题对应的原始条款内容及位置,如六、验收标准 (页码: 85),以及其语境上下文",
-      "suggestion": "具体的修改建议内容",
-      "reason": "问题的原因分析和依据说明",
-      "risk_level": ""
+      "issue_point": "[问题类型]具体问题描述(问题类型必须是:逻辑矛盾/因果关系错误/条件结论不匹配之一)",
+      "location": "问题所在的原始条款内容及位置(如:三、施工方法 (页码: 12)),包含必要的上下文",
+      "suggestion": "基于逻辑规则的具体修改建议(必须是纠正逻辑错误,而非优化表达)",
+      "reason": "详细说明为何这是一个逻辑错误,包括:1)矛盾点在哪里 2)为何不符合逻辑 3)可能产生的后果",
+      "risk_level": "高风险/中风险/低风险(严格按照系统提示词中的标准判定)"
     }}
     ```
+    
+    ## 特别提醒:
+    - 如果内容表达虽不完美但逻辑正确,输出"无明显问题"
+    - 如果是专业术语的标准表达,输出"无明显问题"  
+    - 如果只是表达习惯差异,输出"无明显问题"
+    - 保持高标准:宁可漏报,不可误报
 
 # 1.2 条文完整性检查功能
 completeness_check:
@@ -235,7 +301,7 @@ sensitive_word_check:
     你是施工方案敏感词审查专家,负责检查政治敏感和表述适宜性问题。
 
     审查要求:
-    - 重点关注政治敏感、商业机密、表述不当
+    - 重点关注政治敏感、商业机密、表述不当、工程绝对化用语(如绝对不会出现事故、绝对不会有污染等等)
     - 你只需要考虑初筛找到的敏感词与原文,不需要你自行去找敏感词
     - 通过给出的敏感词初筛内容,并联系上下文,确定初筛的关键词匹配的敏感词是否合理,如果合理则给出issue,如果初筛的敏感词在原文中的语义并无恶意则直接输出:无明显问题
     - 例如:原文“应禁止工人在宿舍中赌博”,初筛内容:“赌博”;解释:原文中“赌博”二字无恶意;结论:无明显问题

+ 2 - 0
core/construction_review/component/reviewers/sensitive_words/暴恐词库.txt

@@ -1,6 +1,8 @@
 福音会
 中国教徒
 统一教
+du bo
+dubo
 观音法门
 清海无上师
 盘古

+ 1 - 0
core/construction_review/component/reviewers/sensitive_words/色情词库.txt

@@ -1,4 +1,5 @@
 爱女人
+色青
 爱液
 按摩棒
 拔出来

+ 33 - 7
core/construction_review/component/reviewers/utils/ac_automaton.py

@@ -86,17 +86,34 @@ class ACAutomaton:
         Returns:
             List[Dict]: 检测结果列表,每项包含:
                 - word: 敏感词
-                - position: 起始位置
-                - end_position: 结束位置
+                - position: 起始位置(原文本中的位置)
+                - end_position: 结束位置(原文本中的位置)
                 - source: 词源
+                - original_text: 原文本中的实际文本(包含空格和符号)
         """
         if not text:
             return []
-            
+        
+        # 去除空格和符号,只保留中文、英文、数字
+        # 同时建立清洗后文本到原文本的位置映射
+        cleaned_text = []
+        position_map = []  # 记录清洗后每个字符在原文本中的位置
+        
+        for i, char in enumerate(text):
+            # 保留中文字符、英文字母、数字
+            if char.isalnum() or '\u4e00' <= char <= '\u9fff':
+                cleaned_text.append(char)
+                position_map.append(i)
+        
+        cleaned_text_str = ''.join(cleaned_text)
+        
+        if not cleaned_text_str:
+            return []
+        
         results = []
         node = self.root
         
-        for i, char in enumerate(text):
+        for i, char in enumerate(cleaned_text_str):
             while node is not None and char not in node.children:
                 node = node.fail
             
@@ -109,11 +126,20 @@ class ACAutomaton:
             temp = node
             while temp is not None:
                 if temp.is_end:
+                    # 计算在清洗后文本中的位置
+                    cleaned_start = i - len(temp.word) + 1
+                    cleaned_end = i + 1
+                    
+                    # 映射回原文本的位置
+                    original_start = position_map[cleaned_start]
+                    original_end = position_map[cleaned_end - 1] + 1
+                    
                     results.append({
                         'word': temp.word,
-                        'position': i - len(temp.word) + 1,
-                        'end_position': i + 1,
-                        'source': temp.source
+                        'position': original_start,
+                        'end_position': original_end,
+                        'source': temp.source,
+                        'original_text': text[original_start:original_end]
                     })
                 temp = temp.fail
         

+ 140 - 0
core/construction_review/component/reviewers/utils/text_split.py

@@ -0,0 +1,140 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""
+@Project   : lq-agent-api
+@File      : text_split.py
+@IDE       : Cursor
+@Author    : AI Assistant
+@Date      : 2025-12-26
+@Description: 文本切割工具函数,用于将长文本切分为合适长度的片段
+"""
+
+import re
+from typing import List
+
+
+def split_text(raw_content: str, min_length: int = 150, target_length: int = 250) -> List[str]:
+    """
+    将输入的中文字符串切分为指定长度范围的小片段
+    
+    算法逻辑:
+    1. 首先按照中文标点符号(。!?;)和换行符将文本分割成句子
+    2. 然后将句子组合成片段,每个片段至少min_length字,目标target_length字
+    3. 如果最后一段文本低于min_length字,则合并到倒数第二段中
+    
+    Args:
+        raw_content (str): 原始文本内容
+        min_length (int): 每段最小字数,默认150字
+        target_length (int): 每段目标字数,默认250字
+        
+    Returns:
+        List[str]: 切分后的文本片段列表
+        
+    Examples:
+        >>> text = "这是第一句话。这是第二句话!这是第三句话?" * 100
+        >>> segments = split_text(text, min_length=150, target_length=250)
+        >>> all(len(seg) >= 150 for seg in segments)
+        True
+    """
+    if not raw_content or not raw_content.strip():
+        return []
+    
+    # 按标点分句分段
+    pattern = re.compile(r'([^。!?;\n]+[。!?;\n]?)')
+    sentences = pattern.findall(raw_content)
+    raw_list = [s.strip() for s in sentences if s.strip()]
+    
+    if not raw_list:
+        return [raw_content.strip()] if raw_content.strip() else []
+    
+    # 组合句子成片段
+    segments = []
+    current_segment = ""
+    
+    for sentence in raw_list:
+        # 如果当前片段为空,直接添加句子
+        if not current_segment:
+            current_segment = sentence
+        else:
+            # 计算添加新句子后的长度
+            potential_length = len(current_segment) + len(sentence)
+            
+            # 如果当前片段已达到最小长度,且添加新句子会超过目标长度
+            if len(current_segment) >= min_length and potential_length > target_length:
+                # 保存当前片段,开始新片段
+                segments.append(current_segment)
+                current_segment = sentence
+            else:
+                # 继续添加到当前片段
+                current_segment += sentence
+    
+    # 处理最后一个片段
+    if current_segment:
+        # 如果最后一段低于最小长度,且已有其他片段,则合并到倒数第二段
+        if len(current_segment) < min_length and len(segments) > 0:
+            segments[-1] += current_segment
+        else:
+            segments.append(current_segment)
+    
+    return segments
+
+
+def split_text_with_overlap(raw_content: str, min_length: int = 150, 
+                           target_length: int = 250, overlap: int = 50) -> List[str]:
+    """
+    将输入的中文字符串切分为指定长度范围的小片段,支持片段间重叠
+    
+    Args:
+        raw_content (str): 原始文本内容
+        min_length (int): 每段最小字数,默认150字
+        target_length (int): 每段目标字数,默认250字
+        overlap (int): 片段间重叠字数,默认50字
+        
+    Returns:
+        List[str]: 切分后的文本片段列表
+    """
+    if not raw_content or not raw_content.strip():
+        return []
+    
+    # 首先使用基础切分
+    base_segments = split_text(raw_content, min_length, target_length)
+    
+    if overlap <= 0 or len(base_segments) <= 1:
+        return base_segments
+    
+    # 添加重叠部分
+    overlapped_segments = []
+    for i, segment in enumerate(base_segments):
+        if i == 0:
+            overlapped_segments.append(segment)
+        else:
+            # 从前一个片段取最后overlap个字符作为重叠
+            prev_segment = base_segments[i - 1]
+            overlap_text = prev_segment[-overlap:] if len(prev_segment) >= overlap else prev_segment
+            overlapped_segments.append(overlap_text + segment)
+    
+    return overlapped_segments
+
+
+if __name__ == "__main__":
+    # 测试代码
+    test_text = """
+    建筑工程施工质量验收统一标准是建筑工程质量管理的重要依据。本标准适用于建筑工程施工质量的验收,并作为建筑工程各专业工程施工质量验收规范的统一准则。
+    建筑工程施工应符合工程勘察、设计文件的要求。参加工程施工质量验收的各方人员应具备规定的资格。工程质量的验收均应在施工单位自行检查评定的基础上进行。
+    隐蔽工程在隐蔽前应由施工单位通知有关单位进行验收,并应形成验收文件。涉及结构安全的试块、试件以及有关材料,应按规定进行见证取样检测。
+    检验批的质量应按主控项目和一般项目验收。对涉及结构安全和使用功能的重要分部工程应进行抽样检测。承担见证取样检测及有关结构安全检测的单位应具有相应资质。
+    工程的观感质量应由验收人员通过现场检查,并应共同确认。通过返修或加固处理仍不能满足安全使用要求的分部工程、单位工程,严禁验收。
+    """ * 3
+    
+    print("原始文本长度:", len(test_text))
+    print("\n" + "="*50)
+    
+    segments = split_text(test_text, min_length=150, target_length=250)
+    print(f"\n切分结果:共 {len(segments)} 个片段")
+    print("="*50)
+    
+    for i, seg in enumerate(segments, 1):
+        print(f"\n片段 {i} (长度: {len(seg)} 字):")
+        print(seg)
+

+ 0 - 0
utils_test/Prompt_Test/test_prompt.py → utils_test/Prompt_Test/grammar_test_prompt.py


+ 383 - 0
utils_test/Prompt_Test/semantic_test_prompt.py

@@ -0,0 +1,383 @@
+"""
+测试提示词脚本
+使用requests库测试语法审查API
+支持本地模型和在线模型两种调用方式
+"""
+import requests
+import json
+from openai import OpenAI
+
+
+class PromptTester:
+    """提示词测试类"""
+    
+    def __init__(self, api_url, api_key=None, is_local=True):
+        """
+        初始化测试器
+        
+        Args:
+            api_url: API地址
+            api_key: API密钥(在线模型需要)
+            is_local: 是否使用本地模型(True: 本地模型使用requests, False: 在线模型使用OpenAI SDK)
+        """
+        self.api_url = api_url
+        self.api_key = api_key
+        self.is_local = is_local
+        
+        # 如果是在线模型,初始化OpenAI客户端
+        if not is_local and api_key:
+            self.client = OpenAI(
+                base_url=api_url.replace('/chat/completions', ''),  # 移除末尾的路径
+                api_key=api_key
+            )
+        else:
+            self.client = None
+        self.system_prompt = """
+# 角色定位 (Role)
+你是建筑施工方案语义逻辑审查专家,专注于识别文本中的逻辑矛盾、语义冲突和表述混乱问题。
+你的职责是发现真实存在的逻辑错误,而非挑剔文字表达或提出主观性建议。
+
+# 审查范围 (Scope)
+仅审查以下三类明确的语义逻辑问题:
+
+1. **逻辑矛盾** - 同一文档内出现相互冲突的陈述
+   示例:前文说"采用A方法",后文说"不采用A方法"
+   
+2. **因果关系错误** - 原因与结果之间不存在合理的逻辑关联
+   示例:"因为天气晴朗,所以混凝土强度不足"(因果无关)
+   
+3. **条件与结论不匹配** - 给定条件无法推导出所述结论
+   示例:"当温度低于5℃时,可正常施工"(违背常识)
+# 工作流程 (Workflow)
+
+步骤1:通读全文,理解整体语境和专业背景
+步骤2:识别关键陈述、条件判断、因果关系
+步骤3:检查是否存在上述三类明确的逻辑问题
+步骤4:对于疑似问题,进行二次验证:
+       - 是否确实存在逻辑冲突?
+       - 是否可能是专业术语的正常表达?
+       - 是否可能是上下文理解不足导致的误判?
+       - 如果你的上文中的内容是另外一个板块的规则,不可用于下一个板块的逻辑判断。
+步骤5:仅输出确认无误的问题
+# 严格规则 (Strict Rules)
+
+禁止行为:
+1. 禁止对正确内容提出修改建议
+2. 禁止对专业术语的标准表达提出质疑
+3. 禁止对文字风格、表达习惯提出意见
+4. 禁止对表格格式、制表符进行检查
+5. 禁止对同一问题重复输出
+6. 禁止在没有明确逻辑错误时输出问题
+7. 禁止将"表达不够完美"当作"逻辑错误"
+8. 禁止将"可以优化"当作"必须修改"
+9. 禁止在无上下文的情况猜测规则,禁止臆想规则。你的无上下文的规则不应该作为评判的依据。
+
+必须遵守:
+1. 必须基于客观事实和逻辑规则判断
+2. 必须确保问题的真实性和严重性
+3. 必须结合建筑施工专业知识理解内容
+4. 必须区分"逻辑错误"与"表达习惯"
+5. 必须在不确定时选择"无明显问题"
+6. 必须保持高标准:宁缺毋滥
+
+# 风险等级判定标准 (Risk Level)
+
+- **高风险**:存在明显逻辑矛盾,可能导致施工错误或安全隐患
+  示例:安全措施前后矛盾、施工顺序逻辑错误
+  
+- **中风险**:存在因果关系错误,可能导致理解偏差
+  示例:原因分析不合理、条件判断不准确
+  
+- **低风险**:存在轻微的表述不一致,不影响实质理解
+  示例:用词前后略有差异但不影响理解
+
+# 判断原则 (Principles)
+
+1. **疑罪从无原则**:当无法确定是否为错误时,判定为无问题
+2. **专业优先原则**:尊重建筑施工行业的专业术语和表达习惯
+3. **语境理解原则**:必须在完整语境中理解句子含义
+4. **实质审查原则**:关注实质性逻辑错误,忽略形式问题
+5. **严格证据原则**:必须有明确证据证明存在逻辑错误
+6. **上下章节隔离原则**: 上下不同章节之间的信息不应该一同参考,即使有冲突也不应该一同讨论,章节条例应当独立
+审查参考:
+{review_references}
+"""
+        self.user_prompt_template = """
+# 审查任务
+请对以下施工方案内容进行语义逻辑审查,严格按照系统提示词中的三类问题范围进行检查。
+
+## 待审查内容:
+{review_content}
+
+
+## 审查要求:
+1. 仅识别明确的逻辑矛盾、因果错误、条件结论不匹配问题
+2. 必须确保问题的真实性,不得对正确内容提出修改
+3. 不确定是否为问题时,必须输出"无明显问题"
+4. 必须结合建筑施工专业知识和完整语境判断
+
+## 输出格式:
+
+**情况1:未发现明确的语义逻辑问题**
+直接输出:“无明显问题”,不需要解释。
+
+**情况2:发现明确的语义逻辑问题**
+严格按照以下JSON格式输出(仅输出确认无误的问题):
+```json
+{{
+  "issue_point": "[问题类型]具体问题描述(问题类型必须是:逻辑矛盾/因果关系错误/条件结论不匹配之一)",
+  "location": "问题所在的原始条款内容及位置(如:三、施工方法 (页码: 12)),包含必要的上下文",
+  "suggestion": "基于逻辑规则的具体修改建议(必须是纠正逻辑错误,而非优化表达)",
+  "reason": "详细说明为何这是一个逻辑错误,包括:1)矛盾点在哪里 2)为何不符合逻辑 3)可能产生的后果",
+  "risk_level": "高风险/中风险/低风险(严格按照系统提示词中的标准判定)"
+}}
+```
+
+## 特别提醒:
+- 如果内容表达虽不完美但逻辑正确,输出"无明显问题"
+- 如果是专业术语的标准表达,输出"无明显问题"  
+- 如果只是表达习惯差异,输出"无明显问题"
+- 保持高标准:宁可漏报,不可误报
+- 上下章节隔离: 上下不同章节之间的信息不应该一同参考,即使有冲突也不应该一同讨论,章节条例应当独立
+"""
+
+    def test_prompt(self, review_content, model="Qwen3-8B", temperature=0.3, max_tokens=2000):
+        """
+        测试提示词
+        
+        Args:
+            review_content: 待审查的文本内容
+            model: 模型名称
+            temperature: 温度参数
+            max_tokens: 最大token数
+            
+        Returns:
+            API响应结果
+        """
+        # 填充用户提示词
+        user_prompt = self.user_prompt_template.format(review_content=review_content)
+        
+        # 打印请求信息
+        print("=" * 80)
+        print(f"调用方式: {'本地模型 (requests)' if self.is_local else '在线模型 (OpenAI SDK)'}")
+        print("发送请求到:", self.api_url)
+        print("=" * 80)
+        print("System Prompt:")
+        print("-" * 80)
+        print(self.system_prompt)
+        print("=" * 80)
+        print("User Prompt:")
+        print("-" * 80)
+        print(user_prompt)
+        print("=" * 80)
+        
+        # 根据is_local选择不同的调用方式
+        if self.is_local:
+            # 本地模型:使用requests
+            return self._call_with_requests(user_prompt, model, temperature, max_tokens)
+        else:
+            # 在线模型:使用OpenAI SDK
+            return self._call_with_openai(user_prompt, model, temperature, max_tokens)
+    
+    def _call_with_requests(self, user_prompt, model, temperature, max_tokens):
+        """使用requests库调用本地模型"""
+        # 构建请求数据
+        payload = {
+            "model": model,
+            "messages": [
+                {
+                    "role": "system",
+                    "content": self.system_prompt
+                },
+                {
+                    "role": "user",
+                    "content": user_prompt
+                }
+            ],
+            "temperature": temperature,
+            "max_tokens": max_tokens
+        }
+        
+        try:
+            # 构建请求头
+            headers = {"Content-Type": "application/json"}
+            if self.api_key:
+                headers["Authorization"] = f"Bearer {self.api_key}"
+            
+            # 发送POST请求
+            response = requests.post(
+                self.api_url,
+                headers=headers,
+                json=payload,
+                timeout=60
+            )
+            
+            # 检查响应状态
+            response.raise_for_status()
+            
+            # 解析响应
+            result = response.json()
+            
+            print("响应状态码:", response.status_code)
+            print("=" * 80)
+            print("API响应结果:")
+            print("-" * 80)
+            print(json.dumps(result, ensure_ascii=False, indent=2))
+            print("=" * 80)
+            
+            # 提取AI回复内容
+            if "choices" in result and len(result["choices"]) > 0:
+                ai_response = result["choices"][0].get("message", {}).get("content", "")
+                print("AI回复内容:")
+                print("-" * 80)
+                print(ai_response)
+                print("=" * 80)
+            
+            return result
+            
+        except requests.exceptions.RequestException as e:
+            print(f"请求失败: {e}")
+            return None
+        except json.JSONDecodeError as e:
+            print(f"JSON解析失败: {e}")
+            print("原始响应:", response.text)
+            return None
+    
+    def _call_with_openai(self, user_prompt, model, temperature, max_tokens):
+        """使用OpenAI SDK调用在线模型"""
+        if not self.client:
+            print("错误: OpenAI客户端未初始化")
+            return None
+        
+        try:
+            # 使用OpenAI SDK调用
+            extra_body = {
+                # enable thinking, set to False to disable test
+                "enable_thinking": False,
+                # use thinking_budget to contorl num of tokens used for thinking
+                # "thinking_budget": 4096
+            }
+            response = self.client.chat.completions.create(
+                model=model,
+                messages=[
+                    {
+                        "role": "system",
+                        "content": self.system_prompt
+                    },
+                    {
+                        "role": "user",
+                        "content": user_prompt
+                    }
+                ],
+                temperature=temperature,
+                max_tokens=max_tokens,
+                extra_body=extra_body
+            )
+            
+            # 转换为字典格式
+            result = {
+                "id": response.id,
+                "object": response.object,
+                "created": response.created,
+                "model": response.model,
+                "choices": [
+                    {
+                        "index": choice.index,
+                        "message": {
+                            "role": choice.message.role,
+                            "content": choice.message.content
+                        },
+                        "finish_reason": choice.finish_reason
+                    }
+                    for choice in response.choices
+                ],
+                "usage": {
+                    "prompt_tokens": response.usage.prompt_tokens,
+                    "completion_tokens": response.usage.completion_tokens,
+                    "total_tokens": response.usage.total_tokens
+                }
+            }
+            
+            print("=" * 80)
+            print("API响应结果:")
+            print("-" * 80)
+            print(json.dumps(result, ensure_ascii=False, indent=2))
+            print("=" * 80)
+            
+            # 提取AI回复内容
+            if result["choices"] and len(result["choices"]) > 0:
+                ai_response = result["choices"][0]["message"]["content"]
+                print("AI回复内容:")
+                print("-" * 80)
+                print(ai_response)
+                print("=" * 80)
+            
+            return result
+            
+        except Exception as e:
+            print(f"OpenAI SDK调用失败: {e}")
+            return None
+
+
+def main():
+    """主函数"""
+    
+    # ==================== 配置区域 ====================
+    # 是否使用本地模型(True: 本地模型, False: 在线模型)
+    is_local = True
+    
+    # 本地模型配置
+    local_config = {
+        "api_url": "http://192.168.91.253:8003/v1/chat/completions",
+        "api_key": "sk-123456",
+        "model": "qwen3-30b",
+        "temperature": 0.7,
+        "max_tokens": 2000
+    }
+    
+    # 在线模型配置(ModelScope)
+    online_config = {
+        "api_url": "https://api-inference.modelscope.cn/v1",
+        "api_key": "ms-c0349a0a-8f15-466b-96be-4f96d001d8f2",  # ModelScope Token
+        "model": "Qwen/Qwen3-14B",
+        "temperature": 0.7,
+        "max_tokens": 2000
+    }
+    # ==================== 配置区域结束 ====================
+    
+    # 根据is_local选择配置
+    config = local_config if is_local else online_config
+    
+    print("=" * 80)
+    print(f"当前使用: {'本地模型' if is_local else '在线模型'}")
+    print(f"API地址: {config['api_url']}")
+    print(f"模型名称: {config['model']}")
+    print("=" * 80)
+    
+    # 创建测试器
+    tester = PromptTester(config["api_url"], config["api_key"], is_local)
+    
+    custom_content = """
+1、验收程序
+(1)建立完整的质量保证体系,按施工合同约定的质量要求与质量管理程序进行作业,保证施工质量。
+(2)每道工序开工前施工单位进行施工技术交底,重要工序或分部工程其内容须书面报送监理工程师审查。
+(3)工序施工完毕,实行各道工序的操作人员“自检”、“互检”和专职质量管理人员“专检”相结合的“三检”程序,并签署有完整的检验记录。不合格,则自行返工。
+(4)在“自检”、“互检”和“专检”合格基础上,备齐自检资料和报验申请表,提前申请验收。
+(5)工程监理采用巡视、旁站、平行检查方式,及平常的工地例行检查,不能作为对施工方的施工质量的检查验收。
+(6)施工工序属于隐蔽工程,需提前通知监理单位验收,未经监理工程师批准,任何工程工序均不能擅自隐蔽。隐蔽验收须按施工工序步骤逐步进行,并按工序步骤记录在验收表上,验收合格后才准许隐蔽。
+(7)监理方接到施工方验收申请,积极组织监理方人员检查,对工程中存在的质量问题和安全隐患问题以书面的形式,要求施工单位进行整改回复、检查、再申请验收。
+(8)监理方按设计和施工验收规范检查,确认无质量、安全问题,组织业主、施工单位联合检验。
+(9)为确保工程质量,为设计和施工验收提供可靠依据,施工过程中各项试验,应严格按国家相关标准的规定进行见证抽样送检。未经监理见证送检的材料,一律不准用于工程实体。
+图7-1 验收程序
+"""
+    tester.test_prompt(
+        custom_content,
+        model=config["model"],
+        temperature=config["temperature"],
+        max_tokens=config["max_tokens"]
+    )
+
+
+if __name__ == "__main__":
+    main()
+