You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

462 lines
27 KiB

4 months ago
import uuid
from langchain_community.embeddings import DashScopeEmbeddings
from langchain_community.document_loaders import TextLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
5 months ago
from qwen_agent.agents import Assistant
import json_repair
4 months ago
import json
embeddings = DashScopeEmbeddings(dashscope_api_key="sk-ea89cf04431645b185990b8af8c9bb13")
device_id=0
import re
import time
from docx import Document
import shutil
5 months ago
from docx.opc.pkgreader import _SerializedRelationships, _SerializedRelationship
from docx.opc.oxml import parse_xml
4 months ago
import logging
import logging.config
import requests
from collections import defaultdict
userLog=None
5 months ago
def load_from_xml_v2(baseURI, rels_item_xml):
"""
Return |_SerializedRelationships| instance loaded with the
relationships contained in *rels_item_xml*. Returns an empty
collection if *rels_item_xml* is |None|.
"""
srels = _SerializedRelationships()
if rels_item_xml is not None:
rels_elm = parse_xml(rels_item_xml)
for rel_elm in rels_elm.Relationship_lst:
if rel_elm.target_ref in ('../NULL', 'NULL'):
continue
srels._srels.append(_SerializedRelationship(baseURI, rel_elm))
return srels
_SerializedRelationships.load_from_xml = load_from_xml_v2
# 记录程序开始的时间戳
def getOutlineLevel(inputXml):
"""
功能 从xml字段中提取出<w:outlineLvl w:val="number"/>中的数字number
参数 inputXml
返回 number
"""
start_index = inputXml.find('<w:outlineLvl')
end_index = inputXml.find('>', start_index)
number = inputXml[start_index:end_index + 1]
number = re.search("\d+", number).group()
return number
def isTitle(paragraph):
"""
功能 判断该段落是否设置了大纲等级
参数 paragraph:段落
返回 None:普通正文没有大纲级别 0:一级标题 1:二级标题 2:三级标题
"""
# 如果是空行,直接返回None
if paragraph.text.strip() == '':
return None
# 如果该段落是直接在段落里设置大纲级别的,根据xml判断大纲级别
paragraphXml = paragraph._p.xml
if paragraphXml.find('<w:outlineLvl') >= 0:
return getOutlineLevel(paragraphXml)
# 如果该段落是通过样式设置大纲级别的,逐级检索样式及其父样式,判断大纲级别
targetStyle = paragraph.style
while targetStyle is not None:
# 如果在该级style中找到了大纲级别,返回
if targetStyle.element.xml.find('<w:outlineLvl') >= 0:
return getOutlineLevel(targetStyle.element.xml)
else:
targetStyle = targetStyle.base_style
# 如果在段落、样式里都没有找到大纲级别,返回None
return None
4 months ago
#寻找标题名称
def findTitleName(docxPath):
yield '文档相似性检查----检查是否存在详细设计方案'
loopCount = 0
while True:
loopCount+=1
if(loopCount>=15):
raise Exception("文档读取超时,或文档存在问题无法读取")
break
try:
document = Document(docxPath)
break
except Exception as e:
time.sleep(1)
pass
5 months ago
# 逐段读取docx文档的内容
4 months ago
titleWords=[]
firstTitle = 0
secondTitle = 0
sanjiTitle = 0
5 months ago
for paragraph in document.paragraphs:
# 判断该段落的标题级别
# 这里用isTitle()临时代表,具体见下文介绍的方法
text = paragraph.text
if text.strip():#非空判断
level = isTitle(paragraph)
if level=="0":
4 months ago
firstTitle+=1
secondTitle = 0
if(text.find("附件")>=0):
continue
titleWords.append("一级标题:".format(firstTitle)+text)
elif level=="1":
secondTitle+=1
sanjiTitle=0
# words.append("\t"+"{}.{}".format(firstTitle,secondTitle)+text)
# titleWords.append("第{}章的二级标题:".format(firstTitle,firstTitle,secondTitle)+text)
elif level=="2":
sanjiTitle += 1
# words.append("\t"+"{}.{}".format(firstTitle,secondTitle)+text)
# titleWords.append("第{}章的三级标题".format(firstTitle, secondTitle,firstTitle, secondTitle,sanjiTitle) + text)
findTitleName_llm_cfg = {
#'model': 'qwen1.5-72b-chat',
'model':"qwen2-72b",
'model_server': 'http://127.0.0.1:1025/v1', # base_url, also known as api_base
# 'api_key': 'sk-ea89cf04431645b185990b8af8c9bb13',
}
findTitleName_bot = Assistant(llm=findTitleName_llm_cfg,
name='Assistant',
# system_message='1:这样的是一级标题。1.1:这样的是二级标题。1.1.1:这样的是三级标题'
)
prompt='''\n是文档的大纲,一级标题组成,哪一章存在与方案相关的内容
类似详细设计方案,详细服务方案详细建设方案为最相关的优先选择
类似设计方案服务方案建设方案为次相关次级选择
类似方案是最后选择
按照这样的顺序选择最合适的
你只能从这两个答案中选择一个{"name":"一级标题名称","answer":"存在"}{"name":"","answer":"不存在"}不做过多的解释,严格按回答格式作答
'''
# print("\n".join(titleWords)+prompt)
messages = [({'role': 'user', 'content': "\n".join(titleWords)+prompt})]
runList=[]
for rsp in findTitleName_bot.run(messages):
5 months ago
runList.append(rsp)
data = runList[len(runList) - 1][0]["content"]
parsed_data = json_repair.loads(data.replace('`', ''))
4 months ago
if(parsed_data["answer"]=="存在"):
yield parsed_data["name"]
else:
yield "文档相似性检查----未找到与详细设计方案相关内容,无法进行相似性比较"
5 months ago
4 months ago
def merge_chapters(words):
merged_text = {}
for line in words:
if "" in line:
key, value = line.split("", 1) # 根据第一个冒号分割
if key in merged_text:
merged_text[key].append(value.strip()) # 添加到列表
else:
merged_text[key] = [value.strip()] # 初始化列表
else:
logging.warning(f"Skipping line without key-value pair: {line}")
5 months ago
4 months ago
# 合并结果格式化为列表输出
merged_words = []
for key, values in merged_text.items():
combined_value = "".join(values) # 将内容合并
merged_words.append(f"{key}{combined_value}")
return merged_words
#获取文档中 详细设计方案 章节的所有内容
def getDocxToText(docxPath, titleName, vector_store_path):
loopCount = 0
while True:
loopCount += 1
if loopCount >= 15:
raise Exception("文档读取超时,或文档存在问题无法读取")
break
try:
document = Document(docxPath)
break
except Exception as e:
time.sleep(1)
pass
# 逐段读取docx文档的内容
levelList = []
words = []
addStart = False
title_counter = [] # 用于存储当前标题的计数
title_texts = [] # 用于存储当前各级标题的文本
i = 0
for paragraph in document.paragraphs:
text = paragraph.text.strip()
if text: # 非空判断
level = isTitle(paragraph) # 确保这个函数在代码中定义
# 当前标题的层级
current_level = int(level) if level is not None else -1
if current_level >= 0: # 标题段落
# 确保标题计数器足够长
while len(title_counter) <= current_level:
title_counter.append(0) # 初始化新级别的标题计数
title_texts.append('') # 初始化对应的标题文本
# 更新当前级别及以下的标题计数和标题文本
title_counter[current_level] += 1 # 当前级别计数加1
title_counter = title_counter[:current_level+1]
title_texts[current_level] = text # 保存当前级别的标题文本
title_texts = title_texts[:current_level+1]
# 重置更低级别的计数和标题文本
for idx in range(current_level + 1, len(title_counter)):
title_counter[idx] = 0
title_texts[idx] = ''
# 检查是否与 titleName 匹配
if current_level == 0:
addStart = titleName in text # 检查是否与 titleName 匹配
else: # 非标题段落
if addStart:
if len(text) > 30: # 仅记录长度大于30的内容
i += 1
# 获取当前完整的标题编号和标题名称
levelText = ".".join(map(str, title_counter))
# 使用非空的标题名称
current_title = title_texts[-1] if title_texts else ''
words.append(f"{levelText}-{current_title}{text}")
if len(words) == 0:
raise Exception("checkRepeatText,获取长度为0")
# 使用封装的合并函数
merged_words = merge_chapters(words)
# 将合并后的内容写入 txt 文件
with open("checkRepeatText.txt", 'w') as txt_file:
for line in merged_words:
txt_file.write(f"{line}\n")
time.sleep(3)
# 加载文本
loader = TextLoader(file_path='checkRepeatText.txt')
docs = loader.load()
# 创建唯一标识符
uuids = []
for _ in range(len(merged_words)):
uuids.append(str(uuid.uuid4()))
logging.info(f"checkRepeatTextuuidLen{len(uuids)}")
return merged_words, uuids
# @app.route('/checkRepeatText/<filename>', methods=['GET'])
def checkRepeatText(filename):
yield "文档相似性检查---启动中...."
vector_store_path="vector_store"+str(uuid.uuid4())
for titleName in findTitleName(filename):
yield titleName
if(titleName!="文档相似性检查----未找到与详细设计方案相关内容,无法进行相似性比较"):
yield "文档相似性检查----文档内容解析中"
words,uuids=getDocxToText(filename,titleName,vector_store_path)
# 记录程序开始的时间戳‘
reslist = []
count = 0
standard = {
"清晰性": """对软件功能描述的完整性主要体现在以下两个方面:
a. 功能描述是否简洁明了避免使用过于复杂或专业的术语使得用户能够轻松理解
b. 是否明确指出了功能的具体作用没有模糊不清或含糊其辞的表述
如果要将软件功能描述的清晰性划分为优秀良好一般差四个从高到低的等级每个等级的评判标准是什么
将软件功能描述的清晰性划分为优秀良好一般差四个等级时每个等级的评判标准可以如下定义
优秀90~100
简洁明了功能描述极其精炼没有多余的词汇每个字都承载着必要的信息
通俗易懂完全避免了专业术语或行业黑话即使是非专业用户也能轻松理解
具体明确功能的作用范围限制以及用户期望的结果都被清晰准确地阐述没有任何模糊或含糊的表述
良好70~90不包含90分
较为简洁功能描述相对简短但可能包含一些必要的细节或背景信息
易于理解大部分术语都是通俗易懂的对于少数专业术语提供了简短的解释或上下文
明确具体功能的主要作用范围和用户期望的结果都被明确阐述但可能在某些细节上稍显模糊
一般60~70不包含70分
稍显冗长功能描述可能包含一些不必要的细节或重复信息导致用户需要花费更多时间来理解
有一定难度使用了一些专业术语或行业黑话但没有提供足够的解释或上下文导致非专业用户可能难以理解
基本明确功能的主要作用被阐述但在范围限制或用户期望的结果上可能存在一些模糊或含糊的表述
60分以下不包含60分
冗长复杂功能描述过于详细和复杂包含大量不必要的细节和背景信息导致用户难以抓住重点
难以理解大量使用专业术语或行业黑话且没有提供任何解释或上下文使得大部分用户都难以理解
模糊不清功能的作用范围限制以及用户期望的结果都没有被明确阐述存在大量的模糊和含糊表述
评估的提示词举例
根据这些评判标准对下面的软件功能描述的清晰性进行客观的评价给出优秀良好一般差四个等级之一的评价并给出具体得分并在此基础上润色和完善使之达到优秀的等级
""",
"完整性": """对软件功能描述的完整性主要体现在以下两个方面:
a. 是否涵盖了功能的所有重要方面包括输入输出处理过程等
b. 是否提供了足够的信息以便用户能够全面了解功能的工作原理和用途
如果要将软件功能描述的完整性划分为优秀良好一般差四个从高到低的等级每个等级的评判标准是什么
将软件功能描述的完整性划分为优秀良好一般差四个等级时每个等级的评判标准可以如下定义
优秀90~100
描述全面涵盖了功能的所有重要方面包括但不限于输入输出处理过程异常处理等
提供了详尽的信息用户能够清晰地了解功能的工作原理用途以及在不同场景下的表现
包含了必要的示例图表或流程图以直观展示功能的工作流程和效果
没有遗漏任何对用户理解和使用功能至关重要的信息
良好70~90不包含90分
描述基本涵盖了功能的主要方面但可能有个别不太重要的细节未提及
提供了足够的信息用户能够较好地理解功能的工作原理和用途但在某些复杂场景下可能需要额外说明
可能包含一些示例或图表但可能不如优秀等级那么全面或详细
一般60~70不包含70分
描述涵盖了功能的一部分重要方面但存在较明显的遗漏或不足
提供的信息有限用户可能只能对功能有一个大致的了解无法深入了解其工作原理和详细用途
可能缺乏示例图表或流程图等辅助材料导致用户难以理解功能的某些复杂部分
60分以下不包含60分
描述严重缺失未涵盖功能的关键方面甚至可能误导用户
提供的信息极少用户无法全面了解功能的工作原理和用途
可能存在错误或矛盾的信息导致用户无法准确理解功能
根据这些评判标准对下面的软件功能描述的完整性进行客观的评价给出优秀良好一般差四个等级之一的评价并在此基础上润色和完善使之达到优秀的等级
""",
"可测试性": """软件功能描述的可测试性主要体现为以下方面:
a. 功能描述是否具体明确以便能够进行功能测试和验证
b. 是否提供了足够的细节以便开发人员和测试人员能够准确理解和实现功能
如果要将软件功能描述的可测试性划分为优秀良好一般差四个从高到低的等级每个等级的评判标准是什么
将软件功能描述的可测试性划分为优秀良好一般差四个等级时每个等级的评判标准可以如下定义
优秀90~100
功能描述非常具体和明确能够直接转化为测试用例
提供了详尽的细节包括输入输出边界条件异常处理等
开发人员和测试人员能够轻松理解和实现功能无需额外澄清或假设
功能描述中包含了预期的行为和非预期的行为有助于全面覆盖测试场景
良好70~90不包含90分
功能描述相对具体和明确大部分内容可以直接用于测试
提供了足够的细节但可能需要一些额外的解释或澄清才能完全理解
开发人员和测试人员能够基于描述实现和测试功能但可能需要一些额外的沟通和协调
功能描述中基本涵盖了主要的行为和边界条件但可能缺少对某些异常情况的详细描述
一般60~70不包含70分
功能描述较为笼统需要较多的解释和澄清才能用于测试和开发
细节不够充分可能导致开发人员和测试人员在实现和测试过程中产生误解或遗漏
需要较多的沟通和协调来确保功能的正确实现和测试
功能描述中可能只涵盖了主要的行为对边界条件和异常情况的描述较为模糊或缺失
60分以下不包含60分
功能描述非常模糊和笼统无法直接用于测试和开发
缺乏必要的细节导致开发人员和测试人员无法准确理解和实现功能
需要大量的沟通和协调甚至可能需要重新编写功能描述才能进行有效的测试和开发
功能描述中可能只提到了大致的目标或意图没有具体的行为描述边界条件或异常处理
根据这些评判标准对下面的软件功能描述的可测试性进行客观的评价给出优秀良好一般差四个等级之一的评价并在此基础上润色和完善使之达到优秀的等级
""",
"详细性": """软件功能详细性主要体现在:
a. 功能描述是否详细可以根据功能描述进行功能点评价计算出ILFEIFEIEOEQ的数量
如果要将软件功能描述的详细性划分为优秀良好一般差四个从高到低的等级每个等级的评判标准是什么
将软件功能描述的详细性划分为优秀良好一般差四个等级时每个等级的评判标准可以如下定义
优秀90~100
功能描述非常详尽包含了所有必要的信息使得评估者能够轻松地根据描述进行功能点评价
ILFEIFEIEOEQ的数量可以明确且无误地计算出来没有遗漏或模糊之处
描述中不仅包含了功能的正常操作还涵盖了异常处理边界条件等特殊情况
使用了具体的例子流程图或伪代码来进一步阐明功能
良好70~90不包含90分
功能描述相对详细提供了足够的信息来进行功能点评价
ILFEIFEIEOEQ的数量可以大致计算出来但可能需要一些额外的解释或澄清
描述中基本涵盖了功能的各个方面但对某些细节或特殊情况可能描述不够充分
整体而言描述是清晰和准确的但还有改进的空间
一般60~70不包含70分
功能描述较为笼统缺乏具体的细节
ILFEIFEIEOEQ的数量计算可能存在一定的困难或不确定性需要较多的假设或推测
描述中只涵盖了功能的主要方面对细节和特殊情况的处理描述不足
可能需要额外的沟通或澄清才能准确理解功能需求
60分以下不包含60分
功能描述非常模糊缺乏必要的信息和细节
无法根据描述进行准确的功能点评价ILFEIFEIEOEQ的数量无法确定
描述中可能只提到了功能的大致目标或意图没有具体的实现细节或操作步骤
需要大量的额外信息或澄清才能理解功能需求甚至可能需要重新编写功能描述
根据这些评判标准对下面的软件功能描述的详细性进行客观的评价给出优秀良好一般差四个等级之一的评价并在此基础上润色和完善使之达到优秀的等级
""",
}
weight = {
"清晰性" : 0.4,
"完整性" : 0.3,
"可测试性" : 0.2,
"详细性" : 0.1,
}
findTitleName_llm_cfg = {
'model': "qwen2-72b",
'model_server': 'http://127.0.0.1:1025/v1',
}
findTitleName_bot = Assistant(llm=findTitleName_llm_cfg, name='Assistant')
for i in words:
count += 1
yield f"文档相似性检查--对{titleName}章节,进行文档内容检查中{count}/{len(words)}"
chapter, rest = i.split('-', 1)
title, text = rest.split('', 1)
# 生成字典
example = {
"chapter": chapter.strip(),
"title": title.strip(),
"text": text.strip()
}
result = {
"title": title.strip(),
"text": text.strip()
}
# 循环提取键和值
weighted_score = 0
for key, value in standard.items():
prompt_score = f"""对软件功能{key}的定义:
{value}
模块名称{example['title']}
模块描述{example['text']}
回答格式为{{"模块名称""{example['text']}",
"等级":"优秀/良好/一般/差",
"得分":"0~100",
"理由及扣分原因":"理由及扣分原因",
}}不做过多的解释,严格按回答格式作答,只给出一个回答
"""
messages = [({'role': 'user', 'content': prompt_score})]
runList = []
for rsp in findTitleName_bot.run(messages):
runList.append(rsp)
data = runList[len(runList) - 1][0]["content"]
parsed_data = json_repair.loads(data.replace('`', ''))
if isinstance(parsed_data, list): # 检查parsed_data是否为列表
parsed_data = parsed_data[0] # 取第一个元素
else:
parsed_data = parsed_data
result[f"{key}等级"] = parsed_data['等级']
result[f"{key}得分"] = parsed_data['得分']
score = int(parsed_data['得分']) # 假设 '得分' 是字符串,需要转换为整数
key_weight = weight.get(key, 0) # 根据键获取权重,如果没有匹配的权重,默认为 0
# 计算加权得分并累加
weighted_score += score * key_weight
result["加权得分"] = round(weighted_score, 2) # 保留两位小数
answer = f"{example['text']}"
for key, value in standard.items():
prompt_answer = f"""对软件功能{key}的定义:\n
{value}\n
模块名称{example['title']}\n
模块描述f{answer}\n
回答格式为{{"模块名称""{example['text']}",
"改进后的描述":"改进后的描述",
}}不做过多的解释,严格按回答格式作答
"""
messages = [({'role': 'user', 'content': prompt_answer})]
runList = []
for rsp in findTitleName_bot.run(messages):
runList.append(rsp)
data = runList[len(runList) - 1][0]["content"]
parsed_data = json_repair.loads(data.replace('`', ''))
answer = parsed_data['改进后的描述']
result["改进后的描述"] = answer
textTag = i.split("")[0]
breakpoint()
# vectorstore.delete(ids=uuids)
shutil.rmtree(vector_store_path)
resInfo=f"{titleName}章节,发现相似内容:<br>"
if(len(reslist)>0):
for res in reslist:
resInfo+="【在**"+res["yuanwen1"][:res["yuanwen1"].find('')]+"**下包含:"+res["yuanwen1"][res["yuanwen1"].find('') + 1:]+"<br>在**"+res["yuanwen2"][:res["yuanwen2"].find('')]+"**下包含:"+res["yuanwen2"][res["yuanwen2"].find('') + 1:]+"<br>以上两段内容***相似度***:"+'{:.2f}'.format(res['similarity'])+"】<br>"
yield resInfo
else:
yield "**未发现相似内容**"
userLog.info("文档相似性检查----未发现相似内容**")
5 months ago
4 months ago
for i in checkRepeatText("./北仑区综合行政执法局协同监管系统项目建设方案_20240824.docx"):
print(i)