Datawhale干货
作者:肖鸿儒,Datawhale成员
Datawhale 开源项目 happy-llm 的 extra-chapter 更新了!重磅更新「建筑文档智能 RAG 审查系统」,从零开始一步步带大家实现建筑领域 RAG 系统!
Happy-LLM
开源地址:https://github.com/datawhalechina/happy-llm
本项目地址:
https://github.com/datawhalechina/happy-llm/tree/main/Extra-Chapter/CDDRS
本项目是一个从零开始实现的建筑文档智能审查系统,旨在帮助开发者理解知识引导检索在专业领域文档审查中的核心原理和实现细节。由于篇幅限制,我们无法展示完整的整个实现过程,但是,我们将在本文中讲解整个企业级 RAG 系统的每个必要的实现步骤以及背后的思考,大家可以通过这些内容快速理解如何实现一个建筑文档智能审查系统。
🔍 说明:本文聚焦于企业级 RAG 系统中的核心环节(如文档处理、索引构建、评估流程等)的搭建,并引用了 llama-index 等第三方库的部分能力,适合希望从零实现文档智能审查系统的开发者参考。文中未覆盖如并发服务部署等完整工程化内容,读者可据此自由扩展实现,灵活落地于实际项目中。完整源码在文末开源。
一、项目动机
建筑施工交底文档的合规性审查是保障施工项目安全性、经济性的关键环节。在施工项目全周期中,各项操作必须符合相关规范条文要求,才能确保建设项目的安全性与可持续性。然而,相关查询参考往往分散在各个项目文件中,传统基于人工的审查方法难以处理庞大复杂的建筑条文,其审查过程需要基于审查人员的经验与专业知识,具有主观性强,耗时长且易出错等弊端。
随着大语言模型技术的发展,LLM 为自动化建筑文档审查带来了新的希望。然而,大语言模型通常使用通用语料进行训练,缺乏建筑相关背景知识,在处理建造背景下的复杂推理问题中会产生严重的幻觉现象。通过使用基于向量相似匹配的 RAG 方法,可以为 LLMs 提供初步的相似参考知识,从而减轻基于人工或规则的审查方法难以处理庞大建筑文本所带来的错误率高的问题。
因此,本项目提出了一个生成式知识引导的建筑文档审查系统,旨在提升审查的可靠性和准确性。系统具有两大核心创新:首先提出动态语义知识分块策略,构建具有更优语义连贯性和完整性的知识库;其次基于增强的知识表示,提出生成式知识引导检索框架,在语义嵌入检索过程中增强对细粒度信息的关注,从而提高知识参考检索的准确性和建筑文档审查任务中修正的可靠性。
二、前置实现
接下来,我们将带领大家,从 0 开始,实现一个建筑文档智能审查系统。首先,我们将完成一些基本的准备过程。
1. 实现 LLM 模块
首先我们需要实现 LLM 模块,这是系统中最基本的模块,我们将利用大模型完成文档的清洗,信息提取等工作,可以说本系统的一部分精髓即为使用大模型预先处理文档信息,方便后续进行检索,这里我们使用 DeepSeek 的 api 来实现。
classBaseLLM( ABC): """Interface for large language models."""
def__init__( self,model_name: str, model_params: Optional[ dict[ str, Any]] = None, **kwargs: Any, ):self.model_name = model_nameself.model_params = model_params or{}
@abstractmethoddefpredict( self, input: str) -> str: """Sends a text input to the LLM and retrieves a response."""
如上是一个调用大模型的抽象接口,这可以帮助我们统一调用大模型的格式,我们继承这个基类,实现调用大模型的接口。
classDeepSeekLLM( BaseLLM): """Implementation of the BaseLLM interface using DeepSeek API."""
def__init__( self,model_name: str, api_key: str, base_url: str= "https://api.deepseek.com/v1", model_params: Optional[ dict[ str, Any]] = None, **kwargs: Any, ):super.__init__(model_name, model_params, **kwargs) self.client = OpenAI(api_key=api_key, base_url=base_url)
defpredict( self, input: str) -> str: response = self.client.chat.completions.create(model=self.model_name,messages=[{ "role": "user", "content": input}], )returnresponse.choices[ 0].message.content
完成搭建后,我们可以通过尝试调用 predict 方法来测试是否成功。
当观察到 LLM 正确回复后,我们这一模块的构建就完成了。
2. 实现 Embedding 模块
除了调用大模型,我们还需要实现 Embedding 模块,Embedding 模块用于将文本转换为向量,我们将使用向量来表示文档中的信息,这样的好处是,我们可以通过向量的相似度来衡量文档与查询之间的相似度,从而召回对回复用户问题最有帮助的文档。
构建 Embedding 模块的方法与构建 LLM 模块类似。
classBaseEmb( ABC): def__init__( self,model_name: str, model_params: Optional[ dict[ str, Any]] = None, **kwargs: Any, ):self.model_name = model_nameself.model_params = model_params or{}
@abstractmethoddefget_emb( self, input: str) -> List[ float]: """Sends a text input to the embedding model and retrieves the embedding."""pass
fromllama_index.embeddings.huggingface importHuggingFaceEmbedding from.base importBaseEmb
classBGEEmbedding( BaseEmb): def__init__( self, model_name: str= "BAAI/bge-m3", **kwargs): super.__init__(model_name=model_name, **kwargs) self.embed_model = HuggingFaceEmbedding(model_name=model_name,trust_remote_code= True, cache_folder= "./model_cache")
defget_emb( self, text: str) -> List[ float]: embedding = self.embed_model.get_text_embedding(text)returnembedding
完成搭建后,我们可以通过尝试调用 get_emb 方法来测试是否成功。
当观察到 Embedding 正确给出了编码后的向量,我们这一模块的构建就完成了。
3. 实现文档预处理模块
为了处理建筑文档,我们需要预先准备好文档读取模块。本系统假设所有建筑规范和标准已经转换为 Markdown 格式,便于后续的文本处理和分析。
classDocumentProcessor: def__init__( self): pass
defload_documents( self, directory_path: str) -> List[ str]: documents = []
forfile_path inPath(directory_path).rglob( '*.md'): try: withopen(file_path, 'r', encoding= 'utf-8') asf: content = f.readdocuments.append(content)exceptException ase: print( f"Error reading {file_path}: {e}")
returndocuments
完成文档预处理模块的设置后,我们就可以采用下面的方法来加载建筑规范文档了。
三、核心实现
动态语义知识分块
在传统RAG流程中,文本通过设置固定的token数量划分文本区块。然而,固定token数量会在句子中间截断,导致信息缺失。为此,本系统使用基于建筑文本语义动态划分的方式,通过双重语义聚类的方式,完成考虑建筑语义连贯性的知识chunk划分。
基于语义差异度分布自动确定动态阈值:
确保最终的分块既保持语义连贯性又满足长度约束:
classDynamicSemanticChunker: def__init__( self, embedding_model: str= "BAAI/bge-m3", max_chunk_length: int= 512, min_chunk_length: int= 50): self.embedding_model = SentenceTransformer(embedding_model)self.max_chunk_length = max_chunk_lengthself.min_chunk_length = min_chunk_length
defsplit_text( self, text: str) -> Dict[ str, str]: sentences = self._split_into_sentences(text)iflen(sentences) == 0: return{}
sentence_embeddings = self.embedding_model.encode(sentences)gamma_values = self._compute_semantic_discrepancy(sentence_embeddings)
total_tokens = sum( len(s.split) fors insentences) baseline_chunks = max( 1, total_tokens // self.max_chunk_length) alpha = max( 0.1, ( len(sentences) - baseline_chunks) / len(sentences)) threshold = np.quantile(gamma_values, alpha) iflen(gamma_values) > 0else0.5
boundaries = self._identify_boundaries(gamma_values, threshold)initial_chunks = self._create_initial_chunks(sentences, boundaries)final_chunks = self._enforce_length_constraints(initial_chunks)
chunks_dict = {}fori, chunk inenumerate(final_chunks): chunk_id = f"chunk-{i+1:03d}"chunks_dict[chunk_id] = chunk
returnchunks_dict
def_split_into_sentences( self, text: str) -> List[ str]: sentence_pattern = r'[。!?;\n]+'sentences = re.split(sentence_pattern, text)
cleaned_sentences = []forsentence insentences: sentence = sentence.stripiflen(sentence) > 5: cleaned_sentences.append(sentence)
returncleaned_sentences
def_compute_semantic_discrepancy( self, embeddings: np.ndarray) -> List[ float]: gamma_values = []
fori inrange( 1, len(embeddings)): similarity = cosine_similarity(embeddings[i- 1].reshape( 1, - 1), embeddings[i].reshape( 1, - 1) )[ 0][ 0]
gamma = 1- similarity gamma_values.append(gamma)
returngamma_values
def_identify_boundaries( self, gamma_values: List[float], threshold: float) -> List[ int]: boundaries = [ 0]
fori, gamma inenumerate(gamma_values): ifgamma > threshold: boundaries.append(i + 1)
boundaries.append( len(gamma_values) + 1) returnsorted( set(boundaries))
def_create_initial_chunks( self, sentences: List[str], boundaries: List[int]) -> List[ str]: chunks = []
fori inrange( len(boundaries) - 1): start = boundaries[i]end = boundaries[i + 1]
chunk_sentences = sentences[start:end]chunk_text = ' '.join(chunk_sentences) chunks.append(chunk_text)
returnchunks
def_enforce_length_constraints( self, chunks: List[str]) -> List[ str]: final_chunks = []
forchunk inchunks: chunk_length = len(chunk.split)
ifchunk_length <= self.max_chunk_length: ifchunk_length >= self.min_chunk_length: final_chunks.append(chunk)else: sub_chunks = self._split_long_chunk(chunk)final_chunks.extend(sub_chunks)
returnfinal_chunks
def_split_long_chunk( self, chunk: str) -> List[ str]: sentences = chunk.split( '。') sub_chunks = []current_chunk = ""
forsentence insentences: ifsentence.strip: test_chunk = current_chunk + sentence + "。"iflen(test_chunk.split) <= self.max_chunk_length: current_chunk = test_chunkelse: ifcurrent_chunk: sub_chunks.append(current_chunk.strip)current_chunk = sentence + "。"
ifcurrent_chunk: sub_chunks.append(current_chunk.strip)
returnsub_chunks
建筑文档审查系统
整体的审查过程如下图所示。系统获取需要审查的区域后,依据提示生成审查问题推荐,此部分也可供工程师进行相关问题输入或推荐问题选择,生成待审查问题。随后,系统通过生成式知识引导检索框架,依据审查问题在所建文本知识库中检索出相应的知识参考。最终,依据检索的部分与审查原文,进行问题分析与审查修正,完成最终的审查流程。
审查问题生成
在文档审查流程中,系统引入了双阶段 Prompt 工程驱动的智能化问询生成机制,旨在对建筑施工交底文档进行预见性分析与风险挖掘,实现对文档潜在问题的高效、精准定位。
阶段 1 为待查文档主旨目标解构,模型被指示从文本中提炼核心事件、关键技术、工艺流程等要素,结构化地总结文档的核心内容,由此界定本次审查的靶向目标,为后续的精细化问询奠定基础。阶段 2 为多维度风险探测与定制化问询生成,基于第一阶段提炼的核心要素,通过 few-shot 等方式引导 LLM 从合规性、安全性、可操作性等多维度对文档进行风险探测。Prompt 指示模型围绕潜在的限制条件、操作流程、以及可能存在的合规性隐患等方面,进行细粒度、多角度的审查提问。
CORE_COMPONENTS_PROMPT = """Task: Your task involves the extraction of crucial information components from a designated text segment. The purpose of this extraction is to assist in uncovering hidden deions indicative of regulatory non-compliance. Key information components encompass, but are not limited to, core deive events, essential construction techniques, technologies, and associated limitations and restrictions.
Input: {document_chunk}Answer:"""
REVIEW_QUERIES_PROMPT = """Task: Your task is to generate relevant search queries based on the text under review and provided core deive references. These queries should target potential areas of non-compliance within the text, facilitating the subsequent retrieval of original regulatory documents for detailed examination.
Input: {document_chunk}Core components: {core_components}Queries:"""
defgenerate_review_queries( llm, document_chunk: str) -> List[ str]: core_prompt = CORE_COMPONENTS_PROMPT. format(document_chunk=document_chunk) core_response = llm.predict(core_prompt)
# 生成审查查询queries_prompt = REVIEW_QUERIES_PROMPT. format( document_chunk=document_chunk,core_components=core_response)queries_response = llm.predict(queries_prompt)
# 从响应中提取查询列表queries = re.findall( r"'([^']*)'", queries_response)
returnqueries[: 5]
知识引导生成式检索
系统的核心创新在于知识引导的检索框架,整个过程分为三个关键步骤。步骤1为句子级编码,主要负责输入查询句子的初始表示学习,计算查询与知识库 chunks 间的句子级相似度分数。步骤 2 为知识引导检索,进一步从查询中提取关键信息,利用这些信息结合文档长度自适应加权等机制,对每个知识库 chunk 进行更详细的评分。步骤 3 为重排序与增强,使用大语言模型对步骤 2 检索的结果进行进一步重排序,并利用精炼的知识来增强原始查询。
首先建立专门针对建筑领域文本分析的深度提取模块,集成领域预训练BERT进行上下文编码,结合双向 LSTM 进行建筑法规依赖建模。建立三级重要性分类层次:max(最高)、mid(中等)、lit(字面)优先级。本项目直接通过大语言模型进行关键信息提取,如果需要更精准的效果,可以自行训练 BERT 模型进行专门的关键信息提取。
KEY_INFO_EXTRACTION_PROMPT = """Your task is to extract key information from the query with three different priority levels:
Maximum priority (max): The most important core concepts or entitiesMedium priority (mid): Important modifiers or qualifying conditions Literal priority (lit): Specific values, standards or specifications
Query: {query}max:mid:lit:"""
classKeyInfoExtractor: def__init__( self, llm): self.llm = llm
defextract_key_info( self, query: str) -> Dict[ str, Tuple[ str, float]]: prompt = KEY_INFO_EXTRACTION_PROMPT. format(query=query) response = self.llm.predict(prompt)
lines = response.strip.split( '\n') key_info = {}weights = { 'max': 0.5, 'mid': 0.3, 'lit': 0.2}
forline inlines: ifline.startswith( 'max:'): key_info[ 'max'] = (line[ 4:].strip, weights[ 'max']) elifline.startswith( 'mid:'): key_info[ 'mid'] = (line[ 4:].strip, weights[ 'mid']) elifline.startswith( 'lit:'): key_info[ 'lit'] = (line[ 4:].strip, weights[ 'lit'])
returnkey_info
文档长度自适应因子
在知识引导检索过程中,文档长度自适应因子用于调整不同长度文档的权重分配,确保长短文档都能得到公平的评分机会。该因子的计算考虑了当前文档chunk的长度与平均文档长度的关系。
其中 表示当前文档chunk的长度,表示平均文档长度。通过这种归一化处理,可以避免因文档长度差异导致的评分偏差。
术语重要性计算
术语重要性指标衡量术语在文档中的显著程度,结合术语频率和文档长度自适应因子,能够更准确地评估术语在当前文档中的重要性。计算公式考虑了术语频率的非线性增长特性。
其中 表示术语在文档chunk中的出现频率,为文档长度自适应因子。这种计算方式能够防止高频术语过度影响评分。
术语稀有度计算
术语稀有度用于衡量术语在整个知识库中的稀缺程度,稀有度越高的术语在检索中的权重越大。计算采用了改进的IDF公式,增加了平滑处理以避免零除问题。
其中 表示文档总数,表示包含该术语的文档数量。加一操作确保了对数值始终为正数。
defcompute_term_rarity( doc_freq: int, total_docs: int) -> float: rarity = np.log((total_docs - doc_freq + 0.5) / (doc_freq + 0.5) + 1) returnrarity
连贯性指数评估
连贯性指数反映术语在文档中的分布连贯性,通过滑动窗口技术分析术语在文档中的局部分布情况。连贯性高的术语往往在文档的特定区域集中出现,表明其与文档主题的强相关性。
其中 表示文档中的滑动窗口集合,为指示函数,当窗口中包含该术语时为1,否则为0。
ifchunk_length == 0: return0.0
max_coherence = 0.0
fori inrange( 0, chunk_length - window_size + 1, 10): window = chunk_tokens[i:i + window_size]term_count = window.count(term.lower)
ifterm_count > 0: coherence = (term_count * window_size) / chunk_lengthmax_coherence = max(max_coherence, coherence)
returnmax_coherence
评分融合与检索
将句子级相似度评分与知识级评分进行融合,形成最终的文档相关性评分。融合过程采用加权平均的方式,平衡参数λ控制两种评分方式的重要性。
其中 为平衡参数,为知识级评分,为句子级评分。通过调整λ值,可以控制系统更偏向语义相似还是知识匹配。当λ=0时,系统完全依赖句子级语义相似度;当λ=1时,系统完全依赖知识匹配评分;λ=0.5时,两种评分方式权重相等。在建筑文档审查场景中,通常设置λ=0.5以平衡专业知识匹配和语义理解。
classGKGRRetriever: def__init__( self, knowledge_base: List[ str], embedding_model,key_info_extractor: KeyInfoExtractor,llm,config: Dict[ str, Any] = None): self.knowledge_base = knowledge_baseself.embedding_model = embedding_modelself.key_info_extractor = key_info_extractorself.llm = llm
default_config = {"lambda_param": 0.5, "top_k": 5, "rerank_enabled": True, "query_expansion": True, "similarity_threshold": 0.1}self.config = {**default_config, **(config or{})}
self.kb_embeddings = self._precompute_embeddings
def_precompute_embeddings( self) -> np.ndarray: embeddings = self.embedding_model.encode(self.knowledge_base, show_progress_bar= True) returnembeddings
defretrieve_with_scores( self, query: str) -> List[ Tuple[ str, float, Dict[ str, float]]]: query_embedding = self.embedding_model.encode([query])[ 0] sentence_scores = cosine_similarity(query_embedding.reshape( 1, - 1), self.kb_embeddings)[ 0]
key_info = self.key_info_extractor.extract_key_info(query)knowledge_scores = self._compute_knowledge_scores(key_info)
final_scores = []fori inrange( len(self.knowledge_base)): norm_sent = sentence_scores[i]norm_know = knowledge_scores[i] / max(knowledge_scores) ifmax(knowledge_scores) > 0else0
final_score = (self.config[ "lambda_param"] * norm_know + ( 1- self.config[ "lambda_param"]) * norm_sent) final_scores.append(final_score)
results_with_scores = []fori, final_score inenumerate(final_scores): iffinal_score > self.config[ "similarity_threshold"]: score_details = {"sentence_score": float(sentence_scores[i]), "knowledge_score": float(knowledge_scores[i]), "final_score": float(final_score) }results_with_scores.append((self.knowledge_base[i], final_score, score_details))
results_with_scores.sort(key= lambdax: x[ 1], reverse= True) returnresults_with_scores[:self.config[ "top_k"]]
def_compute_knowledge_scores( self, key_info: Dict[str, Tuple[str, float]]) -> List[ float]: scores = []avg_length = sum( len(chunk.split) forchunk inself.knowledge_base) / len(self.knowledge_base)
forchunk inself.knowledge_base: chunk_score = 0.0chunk_tokens = chunk.lower.splitchunk_length = len(chunk_tokens)
lambda_dl = compute_document_length_factor(chunk_length, avg_length)
forpriority, (info_text, weight) inkey_info.items: ifnotinfo_text.strip: continue
terms = info_text.lower.splitforterm interms: ifterm inchunk_tokens: tf = chunk_tokens.count(term)
significance = compute_term_significance(tf, lambda_dl)
segments_with_term = sum( 1forkb_chunk inself.knowledge_base ifterm inkb_chunk.lower) rarity = compute_term_rarity(segments_with_term, len(self.knowledge_base))
coherence = compute_coherence_index(term, chunk)
term_score = significance * rarity * ( 1+ coherence) * weight chunk_score += term_score
scores.append(chunk_score)
returnscores
defretrieve( self, query: str) -> Tuple[ List[ str], str]: results_with_scores = self.retrieve_with_scores(query)
documents = [doc fordoc, _, _ inresults_with_scores]
ifself.config[ "rerank_enabled"] andlen(documents) > 1: documents = self._llm_rerank(query, documents)
augmented_query = queryifself.config[ "query_expansion"]: augmented_query = self._augment_query(query, documents[: 3])
returndocuments, augmented_query
重排序优化
系统使用大语言模型对检索结果进行进一步重排序,通过 LLM 的语义理解能力优化文档的相关性排序。重排序过程中,系统会构造包含查询和候选文档的提示,要求 LLM 根据相关性对文档进行重新排序。
rerank_prompt = f"""Task: A list of documents is shown below. Each document has a number next to it. A question is also provided. Your task is to return the numbers of ALL documents in order of relevance from MOST to LEAST relevant. MUST include EVERY document number exactly once.
Example format:Document 1: <document 1>Document 2: <document 2>Document 3: <document 3>Question: <question>Answer: 3,1,2
Now here are the actual documents and question.
"""fori, doc inenumerate(documents): rerank_prompt += f"Document {i+1}: {doc[:150]}...\n"
rerank_prompt += f"Question: {query}\nAnswer:"
try: response = self.llm.predict(rerank_prompt)order_nums = [ int(x.strip) - 1forx inresponse.split( ',') ifx.strip.isdigit and0<= int(x.strip) - 1< len(documents)]
reranked = [documents[i] fori inorder_nums ifi < len(documents)]
# 添加遗漏的文档used_indices = set(order_nums) fori, doc inenumerate(documents): ifi notinused_indices: reranked.append(doc)
returnreranked[: len(documents)] except: returndocuments
查询增强
document_list = ""fori, doc inenumerate(top_results): document_list += f"Document {i+1}: {doc[:100]}...\n"
augment_prompt = f"""Task: Your task is to generate a detailed answer to the question by synthesizing information from ALL provided documents. Prioritize relevance, cite document numbers, and structure your response as follows:
Question: {original_query}{document_list}Answer:"""
try: augmented = self.llm.predict(augment_prompt)returnaugmented.strip except: returnoriginal_query
偏差检测分析
在先期知识增强检索阶段获取领域知识后,系统随即进入误差辨析模块。该模块基于检索得到的知识参考,并结合预设的审阅问题,对原文进行细致的偏差检测与评估。
defanalyze_errors( self, document_chunk: str, query: str, retrieved_knowledge: List[str]) -> Dict[ str, Any]:
analysis_prompt = f"""Task: Your task is to conduct an error analysis on a given review document, based on a provided review query and relevant reference specifications. This analysis MUST strictly adhere to the provided reference and focus specifically on reviewing and analyzing the original deive sections within the review document.
Review document: {document_chunk}Query: {query}Reference: {chr(10).join([f"{i+1}. {ref}"fori, ref inenumerate(retrieved_knowledge)])}Analysis:"""
analysis = self.llm.predict(analysis_prompt)
return{ "analysis": analysis, "reference_support": retrieved_knowledge }
修订建议生成
误差辨析模块完成后,系统将输出标记偏差区域以及相关知识佐证。随后,系统进入修订策略生成模块。该模块依据误差分析结果和知识参考,对标记区域进行针对性的修订建议生成,最终实现对原文的知识驱动型自动修正。
defgenerate_revisions( self, document_chunk: str, analysis: Dict[str, Any]) -> Dict[ str, str]: revision_prompt = f"""Task: Your task is to review and revise the provided document based on the given analysis and corresponding reference specifications. STRICT adherence to the provided reference specifications is required. If the review document aligns with the analysis and reference specifications WITHOUT discrepancies, revision is not necessary.
Review document: {document_chunk}Analysis: {analysis['analysis']}Reference: {chr(10).join([f"- {ref}"forref inanalysis['reference_support']])}Revision:"""
revision = self.llm.predict(revision_prompt)
return{ "original_text": document_chunk, "revision_suggestions": revision, "modified_regions": analysis.get( "error_regions", []), "confidence": self._calculate_confidence(analysis) }
def_calculate_confidence( self, analysis: Dict[str, Any]) -> float: ref_count = len(analysis.get( "reference_support", [])) error_count = len(analysis.get( "error_regions", []))
confidence = min( 0.9, 0.5+ (ref_count * 0.1) + (error_count * 0.05)) returnconfidence
完整审查流程
将上述所有模块整合,形成完整的文档审查流程。系统首先生成审查问题,然后进行知识引导检索,接着执行错误分析,最后生成修订建议。
results = {}for query in review_queries [:3]: retrieved_docs, augmented_query = gkgr_framework. retrieve(query)
knowledge_refs = retrieved_docsanalysis = error_analyzer. analyze_errors(document_chunk, query, knowledge_refs)
revision = revision_generator. generate_revisions(document_chunk, analysis)
results[query] = {"retrieved_knowledge": retrieved_docs,"augmented_query": augmented_query, "analysis": analysis, "revision": revision }
return results
至此,我们就完成了建筑文档智能审查系统的核心实现。
四、实际应用示例
让我们通过一个完整的示例来展示系统的使用:
embedding = BGEEmbedding(model_name= "BAAI/bge-m3") key_extractor = KeyInfoExtractor(llm)
# 从markdown文档构建知识库processor = DocumentProcessordocuments = processor.load_documents( "./construction_standards")
# 对文档进行动态语义分块chunker = DynamicSemanticChunkerknowledge_base = []fordoc indocuments: chunks = chunker.split_text(doc)knowledge_base.extend(chunks.values)
# 初始化检索器gkgr_retriever = GKGRRetriever(knowledge_base=knowledge_base,embedding_model=embedding,key_info_extractor=key_extractor,llm=llm)
# 初始化分析器error_analyzer = ErrorAnalyzer(llm)revision_generator = RevisionGenerator(llm)
# 待审查的文档内容sample_document = """钢筋混凝土柱的施工应符合以下要求:1. 混凝土强度等级不低于C252. 钢筋保护层厚度为25mm3. 混凝土浇筑应连续进行,间歇时间不超过1小时4. 养护期间应保持混凝土表面湿润"""
# 执行审查result = complete_review_process(sample_document, gkgr_retriever, error_analyzer, revision_generator)
# 查看审查结果forquery, analysis inresult.items: print( f"审查问题: {query}") print( f"修订建议: {analysis['revision']['revision_suggestions']}") print( "-"* 50)
五、扩展性说明
系统可以通过更换知识库轻松适应其他领域。对于特定企业或项目,可以通过微调关键信息提取模型来提升准确性。在性能优化方面,使用动态语义分块可以提升检索质量,预计算并缓存知识库嵌入以提升检索速度,对于大量文档可使用批量处理模式,根据具体应用场景调整 λ 参数和 top-k 值。
六、写在最后
恭喜你阅读完此文,你已经充分了解了如何实现一个建筑文档智能审查系统以及其背后的思考。这个系统展示了如何将动态语义分块、知识引导检索和大语言模型有机结合,为建筑行业的文档审查提供了一个实用的解决方案。
虽然当前系统已经取得了不错的效果,但仍有改进空间。全局关联增强方面,当前基于文本块的检索可以进一步结合知识图谱等技术。多模态支持方面,未来可以扩展支持 CAD 图纸、施工图等视觉信息。实时更新方面,支持知识库的增量更新和动态维护。个性化定制方面,根据不同企业和项目特点进行系统定制。
读者们可以运行项目中的示例代码,体验完整的建筑文档智能审查流程。我们相信这个系统不仅能够提升审查效率,更能为建筑行业的数字化转型贡献力量。
七、致谢
本项目的开发过程中,我们深入研究了建筑工程领域的专业知识和最新的自然语言处理技术。特别感谢建筑行业专家提供的宝贵建议,以及开源社区在技术实现方面的支持。项目代码实现参考了 LlamaIndex、Transformers 等优秀开源项目的设计理念。
需要说明的是,本项目专门针对建筑施工领域的文档审查场景进行了深度优化。如果您需要处理其他领域的文档,建议根据具体需求对系统进行相应调整。
八、源码获取
本项目的源码以及实例数据存放在 GitHub 仓库。
地址:https://github.com/Hongru0306/CDDRS