多模态数据处理系统:用AI读PDF的智能助手系统分析
多模态数据处理系统:用AI读PDF的智能助手系统分析
- 设计思路
- 数据流向(从用户操作到最终结果)
- 技术组件协作
- 🔧 模块1:fitz (PyMuPDF) - PDF核心处理器
- 【模块三问】
- 【实际使用示例】
- 🌐 模块2:gradio - 网页界面构建器
- 【模块三问】
- 【实际使用示例】
- 🤖 模块3:openai - AI模型调用客户端
- 【模块三问】
- 【实际使用示例】
- 💾 模块4:pymysql - MySQL数据库连接器
- 【模块三问】
- 【实际使用示例】
- ⚡ 模块5:ThreadPoolExecutor - 并发处理加速器
- 【模块三问】
- 【实际使用示例】
- 🔄 Level 2:模块协作关系 - 它们是如何配合工作的?
- 【锁定】核心问题:这些模块是如何协同工作的?
- 【拆解】模块协作流程图
- 【探究】关键协作点分析
- 解法分析
- 1. 按逻辑关系详细拆解【解法】
- 详细子解法拆解:
- 2. 逻辑链关系(基于代码流程)
- 3. 隐性方法分析
- 4. 隐性特征分析
- 5. 潜在局限性分析(基于代码)
设计思路
双卡 48G 显存可部署,单卡 22 G 只能用 3B多模态模型 + 7B语言模型。
第一个AI(7B多模态模型):专门看图识字
- 输入:PDF页面图片
- 输出:识别出的所有文字
- 就像:有个专门看图说话的AI第二个AI(14B语言模型):专门整理要点
- 输入:第一个AI识别的文字
- 输出:重要知识点和关系
- 就像:有个专门做读书笔记的AI
数据流向(从用户操作到最终结果)
第一阶段:用户交互
1. 用户打开网页界面
2. 上传一个PDF文件
3. 点击"开始工作"按钮第二阶段:PDF处理(process_pdf函数):返回 txt 文件
4. 系统把PDF复制到临时目录
5. 获取PDF总页数
6. 并行处理每一页:- 把每页转成高清图片- 提取页面文字信息(锚点文本)- 调用7B多模态AI识别文字
7. 把所有页面结果合并成完整文档
8. 保存为.txt文件第三阶段:知识抽取(process_KG_extract函数):返回知识图谱
9. 读取刚才生成的.txt文件
10. 把文档按段落结构化(mysql_main)
11. 存入数据库的markdown_data表
12. 调用14B语言AI分析每个段落
13. 提取三元组知识(主体-关系-客体)
14. 返回知识图谱结果第四阶段:结果展示
15. 界面显示文字识别结果
16. 界面显示知识图谱结果
17. 用户可以下载保存文件
技术组件协作
网页界面(Gradio)
├── 文件上传组件 → 接收PDF
├── 进度条组件 → 显示处理进度
├── 文本框组件 → 显示识别结果
└── 下载组件 → 提供文件下载后端Python程序
├── 文件处理模块(fitz库)→ PDF转图片
├── 文本提取模块(anchor.py)→ 提取页面信息
├── AI调用模块(openai客户端)→ 调用模型服务
├── 数据库模块(pymysql)→ 存储和查询
└── 并发处理模块(ThreadPoolExecutor)→ 加速处理数据库(MySQL)
└── markdown_data表 → 存储结构化文档AI服务
├── 多模态模型服务(端口30000)→ 图片识字
└── 语言模型服务(端口8000)→ 知识抽取
🔧 模块1:fitz (PyMuPDF) - PDF核心处理器
【模块三问】
Q1: 这个模块是用来做什么的?
- 功能: PyMuPDF的Python接口,专门处理PDF文档
- 作用: 把PDF页面转成图片,获取页面信息
- 为什么用它: 功能强大,速度快,支持高清渲染
Q2: 最常用的方法有哪些?
# 核心方法解析
fitz.open(pdf_path) # 打开PDF文件
doc.page_count # 获取总页数
doc[page_index] # 获取指定页面
page.rect # 获取页面尺寸
page.get_pixmap() # 页面转图片
pixmap.tobytes() # 图片转字节数据
Q3: 这些方法的参数是什么?
# 详细参数说明
fitz.open(filename)
# filename: PDF文件路径(str)page.get_pixmap(matrix=None, alpha=False)
# matrix: 变换矩阵,控制缩放和旋转(fitz.Matrix)
# alpha: 是否包含透明通道(bool)pixmap.tobytes(format="png")
# format: 输出格式,png/jpeg等(str)
【实际使用示例】
# 完整使用流程
def render_pdf_to_base64png(pdf_path, page_num, target_size=3072):# 1. 打开PDFdoc = fitz.open(pdf_path)# 2. 获取指定页面page = doc[page_num - 1] # 转换为0-based索引# 3. 计算缩放比例rect = page.rectlongest_dim = max(rect.width, rect.height)scale = target_size / longest_dim# 4. 创建变换矩阵matrix = fitz.Matrix(scale, scale).prescale(2.0, 2.0)# 5. 渲染为图片pix = page.get_pixmap(matrix=matrix, alpha=False)# 6. 转换为base64png_bytes = pix.tobytes("png")return base64.b64encode(png_bytes).decode("utf-8")
🌐 模块2:gradio - 网页界面构建器
【模块三问】
Q1: 这个模块是用来做什么的?
- 功能: 快速创建机器学习应用的网页界面
- 作用: 让Python程序有一个漂亮的网页前端
- 为什么用它: 简单易用,不需要写HTML/CSS/JavaScript
Q2: 最常用的方法有哪些?
# 核心组件
gr.Blocks() # 创建应用容器
gr.Row() # 创建行布局
gr.Column() # 创建列布局
gr.Textbox() # 文本输入/输出框
gr.File() # 文件上传组件
gr.Button() # 按钮组件
gr.Tabs() # 选项卡组件
app.launch() # 启动应用
Q3: 这些方法的参数是什么?
# 详细参数说明
gr.Textbox(label="显示标签", # 组件标题(str)lines=10, # 显示行数(int)interactive=True, # 是否可编辑(bool)placeholder="提示文字" # 占位符文字(str)
)gr.File(label="上传文件", # 标题(str)file_types=[".pdf"] # 允许的文件类型(list)
)gr.Button("按钮文字", # 按钮显示文字(str) variant="primary" # 按钮样式(str)
)button.click(fn=处理函数, # 点击时调用的函数inputs=[输入组件], # 输入组件列表(list)outputs=[输出组件] # 输出组件列表(list)
)
【实际使用示例】
# 完整界面构建
with gr.Blocks(title="应用标题") as app:# 创建布局with gr.Row():with gr.Column(scale=4): # 左侧列,占4/12# 输入组件pdf_input = gr.File(label="上传PDF", file_types=[".pdf"])process_btn = gr.Button("开始处理")with gr.Column(scale=8): # 右侧列,占8/12 # 输出组件text_output = gr.Textbox(label="结果", lines=20)# 绑定事件process_btn.click(fn=处理函数,inputs=[pdf_input], outputs=[text_output])# 启动应用app.launch(server_port=7860)
🤖 模块3:openai - AI模型调用客户端
【模块三问】
Q1: 这个模块是用来做什么的?
- 功能: OpenAI API的Python客户端库
- 作用: 调用GPT、DALL-E等AI模型,也支持兼容的本地模型
- 为什么用它: 标准化的API接口,易于使用
Q2: 最常用的方法有哪些?
# 核心方法
OpenAI(base_url, api_key) # 创建客户端
client.chat.completions.create() # 调用聊天模型
client.images.generate() # 调用图像生成模型
Q3: 这些方法的参数是什么?
# 详细参数说明
client = OpenAI(base_url="http://localhost:8000/v1", # 模型服务地址(str)api_key="EMPTY" # API密钥(str)
)response = client.chat.completions.create(model="模型名称", # 使用的模型(str)messages=[ # 对话消息列表(list){"role": "user", "content": "问题内容"},{"role": "assistant", "content": "回答内容"}],temperature=0.3, # 随机性(0-1, float)max_tokens=4096, # 最大输出长度(int)top_p=0.4, # 采样概率(0-1, float)
)
【实际使用示例】
# 调用多模态模型
def call_multimodal_ai(text_prompt, image_base64):client = OpenAI(base_url="http://127.0.0.1:30000/v1",api_key="EMPTY")response = client.chat.completions.create(model="olmOCR-7B-0225-preview",messages=[{"role": "user", "content": [{"type": "text", "text": text_prompt},{"type": "image_url", "image_url": {"url": f"data:image/png;base64,{image_base64}"}}]}],temperature=0.3,max_tokens=4096)return response.choices[0].message.content
💾 模块4:pymysql - MySQL数据库连接器
【模块三问】
Q1: 这个模块是用来做什么的?
- 功能: Python连接MySQL数据库的驱动
- 作用: 执行SQL语句,操作MySQL数据库
- 为什么用它: 纯Python实现,兼容性好,使用简单
Q2: 最常用的方法有哪些?
# 核心方法
pymysql.connect() # 创建数据库连接
connection.cursor() # 创建游标对象
cursor.execute() # 执行SQL语句
cursor.fetchone() # 获取一条记录
cursor.fetchall() # 获取所有记录
connection.commit() # 提交事务
connection.close() # 关闭连接
Q3: 这些方法的参数是什么?
# 详细参数说明
connection = pymysql.connect(host='localhost', # 数据库服务器地址(str)user='root', # 用户名(str) password='123456', # 密码(str)database='olmocr', # 数据库名(str)charset='utf8mb4' # 字符编码(str)
)cursor.execute(sql, params)
# sql: SQL语句,可以有占位符(str)
# params: 参数值,替换占位符(tuple/list)cursor.fetchmany(size)
# size: 获取记录数量(int)
【实际使用示例】
# 完整数据库操作
def insert_data(sections, title, title_unique):# 1. 建立连接conn = pymysql.connect(**db_config)cursor = conn.cursor()try:# 2. 遍历数据并插入for section_title, content in sections.items():# 生成唯一IDunique_id = f"{datetime.now().strftime('%Y%m%d%H%M%S')}-{uuid.uuid4().hex[:8]}"# 处理内容full_content = '\n'.join(content)# 3. 执行插入SQLcursor.execute('''INSERT INTO markdown_data (unique_id, title, content) VALUES (%s, %s, %s)''', (unique_id, title + title_unique, f"\n{section_title}:\n{full_content}"))# 4. 提交事务conn.commit()finally:# 5. 关闭连接cursor.close()conn.close()
⚡ 模块5:ThreadPoolExecutor - 并发处理加速器
【模块三问】
Q1: 这个模块是用来做什么的?
- 功能: 创建和管理线程池,实现并发执行
- 作用: 让多个任务同时执行,提高处理速度
- 为什么用它: IO密集型任务(如网络请求、文件读写)可以大幅提速
Q2: 最常用的方法有哪些?
# 核心方法
ThreadPoolExecutor(max_workers) # 创建线程池
executor.map() # 并发执行函数
executor.submit() # 提交单个任务
future.result() # 获取任务结果
Q3: 这些方法的参数是什么?
# 详细参数说明
ThreadPoolExecutor(max_workers=10)
# max_workers: 最大线程数量(int)executor.map(function, iterable)
# function: 要执行的函数
# iterable: 参数序列,每个元素会作为函数参数executor.submit(function, *args, **kwargs)
# function: 要执行的函数
# args, kwargs: 函数的参数
【实际使用示例】
# 并发处理PDF页面
def process_pdf_concurrent(pdf_path, total_pages):def process_single_page(page_num):"""处理单个页面的函数"""try:# 渲染页面为图片image_base64 = render_pdf_to_base64png(pdf_path, page_num)# 提取锚点文本anchor_text = get_anchor_text(pdf_path, page_num)# 调用AI识别result = call_ai_ocr(image_base64, anchor_text)return resultexcept Exception as e:print(f"处理第{page_num}页出错: {e}")return ""# 使用线程池并发处理with ThreadPoolExecutor(max_workers=10) as executor:# 为每一页创建任务page_numbers = range(1, total_pages + 1)# 并发执行,获取结果results = list(executor.map(process_single_page, page_numbers))return results
🔄 Level 2:模块协作关系 - 它们是如何配合工作的?
【锁定】核心问题:这些模块是如何协同工作的?
【拆解】模块协作流程图
用户交互层 (gradio)↓ 文件上传
PDF处理层 (fitz + pypdf) ↓ 图片+文本
AI调用层 (openai)↓ 识别结果
数据存储层 (pymysql)↓ 结构化数据
并发加速层 (ThreadPoolExecutor) ← 贯穿整个处理过程
【探究】关键协作点分析
🔄 协作点1:PDF → 图片转换
# fitz模块负责转换
with fitz.open(pdf_path) as doc:page = doc[page_num - 1]matrix = fitz.Matrix(scale, scale)pix = page.get_pixmap(matrix=matrix)png_bytes = pix.tobytes("png")# base64模块负责编码
image_base64 = base64.b64encode(png_bytes).decode("utf-8")
🔄 协作点2:AI模型调用
# openai模块构建请求
response = client.chat.completions.create(model="olmOCR-7B-0225-preview",messages=[{"role": "user","content": [{"type": "text", "text": prompt},{"type": "image_url", "image_url": {"url": f"data:image/png;base64,{image_base64}" # 使用fitz生成的图片}}]}]
)# json模块解析结果
result = json.loads(response.choices[0].message.content)
🔄 协作点3:并发处理加速
# ThreadPoolExecutor协调多个模块
with ThreadPoolExecutor(max_workers=10) as executor:results = executor.map(lambda page_num: {# 1. fitz处理PDF页面'image': render_pdf_page(page_num),# 2. openai调用AI模型 'text': call_ai_model(page_num),# 3. pymysql存储结果'saved': save_to_database(page_num)}, range(1, total_pages + 1))
🔄 协作点4:数据库存储
# json处理AI返回的结构化数据
ai_result = json.loads(response.content)# pymysql存储到数据库
conn = pymysql.connect(**db_config)
cursor = conn.cursor()
cursor.execute("INSERT INTO markdown_data (content) VALUES (%s)", (json.dumps(ai_result),) # json模块序列化存储
)
conn.commit()
解法分析
基于原文档和代码内容,我来重新进行解法拆解分析:
1. 按逻辑关系详细拆解【解法】
主解法 = 多模态PDF识别子解法(因为PDF包含图文混合特征) + 锚点文本辅助子解法(因为需要位置信息辅助理解特征) + 文档结构化解析子解法(因为文档存在层级标题结构特征) + 知识图谱三元组抽取子解法(因为需要提取原子事实和关键元素特征) + 并发处理优化子解法(因为多页处理效率特征)
详细子解法拆解:
子解法1:多模态PDF识别
- 之所以用多模态PDF识别子解法,是因为PDF文档包含图像和文本混合特征,需要视觉和文本双重理解。
- 例子:处理《成人糖尿病食养指南(2023年版)》PDF时,包含文字说明和营养成分表格图表。
子解法1.1:Base64图像渲染
def render_pdf_to_base64png(local_pdf_path: str, page_num: int, target_longest_image_dim: int = 3072) -> str:scale = target_longest_image_dim / longest_dimmatrix = fitz.Matrix(scale, scale).prescale(2.0, 2.0)
- 之所以用3072像素高分辨率渲染子解法,是因为需要保证文档细节清晰度特征。
- 例子:识别糖尿病指南中的血糖监测数值表格。
子解法1.2:多模态模型调用
response = client.chat.completions.create(model="olmOCR-7B-0225-preview",messages=[{"role": "user", "content": [{"type": "text", "text": prompt},{"type": "image_url", "image_url": {"url": f"data:image/png;base64,{image_base64}"}},]}]
)
- 之所以用多模态消息格式子解法,是因为需要同时传递文本提示和图像数据特征。
子解法2:锚点文本辅助识别
- 之所以用锚点文本辅助子解法,是因为纯视觉识别存在上下文断裂特征。
子解法2.1:多引擎文本提取
def get_anchor_text(local_pdf_path: str, page: int, pdf_engine: Literal["pdftotext", "pdfium", "pypdf", "topcoherency", "pdfreport"]):if pdf_engine == "topcoherency":options = {"pdftotext": _get_pdftotext(local_pdf_path, page),"pdfium": _get_pdfium(local_pdf_path, page), "pypdf_raw": _get_pypdf_raw(local_pdf_path, page),}scores = {label: get_document_coherency(text) for label, text in options.items()}best_option_label = max(scores, key=scores.get)
- 之所以用多引擎选择子解法,是因为不同PDF存在格式差异特征,单一引擎效果不稳定。
- 例子:处理糖尿病指南时,pdftotext能很好提取标准文本,pypdf更适合处理表格数据。
子解法2.2:页面元素位置标记
@dataclass(frozen=True)
class TextElement(Element):text: str x: float # 文本基线的起始 x 坐标y: float # 文本基线的起始 y 坐标def _linearize_pdf_report(report: PageReport, max_length: int = 4000) -> str:text_str = f"[{element.x:.0f}x{element.y:.0f}]{element_text}\n"
- 之所以用坐标标记子解法,是因为需要保留文本空间位置信息特征。
- 例子:在糖尿病指南中标记"[120x300]血糖控制目标:空腹血糖4.4-7.0mmol/L"。
子解法3:文档结构化解析
- 之所以用结构化解析子解法,是因为文档存在多层级标题和段落嵌套特征。
子解法3.1:多格式标题识别
title_pattern = re.compile(r'(?:^([' + chinese_nums + r']{1,3}[、..])|' # 中文编号r'^(第[' + chinese_nums + r']{1,3}[章节条])|' # 法律条文 r'^(【[\u4e00-\u9fa5]+】)|' # 特殊括号标题r'^((?:\d+\.)+\d*\s)|' # 多级数字编号r'^([A-Z]+:\s)|' # 英文关键词标题r'^([IVX]+\.\s))' # 罗马数字编号
)
- 之所以用12种标题格式识别子解法,是因为文档存在多种标题格式混用特征。
- 例子:糖尿病指南中同时存在"一、总则"、“1.1 目标”、"【注意事项】"等不同格式。
子解法3.2:智能段落合并
if len(' '.join(paragraph + [line])) < 80:paragraph.append(line)
else:_commit_paragraph(paragraph, sections, current_title)paragraph = [line]
- 之所以用80字符阈值合并子解法,是因为需要保证段落逻辑完整性特征。
- 例子:将"糖尿病患者应控制"和"血糖水平在正常范围内"合并为完整段落。
子解法4:知识图谱三元组抽取
- 之所以用三元组抽取子解法,是因为需要从文本中提取结构化的知识关系特征。
子解法4.1:原子事实提取
system_prompt = """
关键元素:对文本叙述至关重要的核心名词(如人物、时间、事件、地点、数字)、动词(如动作)和形容词(如状态、情感)。
原子事实:最小的、不可分割的事实,以简洁的句子形式呈现。
[
{"原子事实": "《成人糖尿病食养指南(2023 年版)》的制定以满足人民健康需求为出发点。","关键元素": ["《成人糖尿病食养指南(2023 年版)》", "制定", "满足", "人民健康需求"]
}]
- 之所以用原子事实拆解子解法,是因为复合句包含多重关系特征,需要拆解为最小单元。
- 例子:将"下颌升支和喙突骨折可导致张口受限"拆解为不可分割的医学事实。
子解法5:并发处理优化
with ThreadPoolExecutor(max_workers=10) as executor:results = list(executor.map(process_page, range(1, total_pages + 1)))
- 之所以用10线程并发子解法,是因为多页PDF处理存在I/O密集型特征。
2. 逻辑链关系(基于代码流程)
PDF文档上传│├─→ main_process(pdf_file) 【主处理入口函数】│ ├─→ process_pdf(pdf_path) 【PDF文本识别模块】│ │ ├─→ 获取总页数: fitz.open(local_pdf_path).page_count 【统计PDF页面数量】│ │ ├─→ 并发处理: ThreadPoolExecutor(max_workers=10) 【10线程并发处理多页】│ │ │ ├─→ render_pdf_to_base64png() 【PDF页面转Base64图像】│ │ │ ├─→ get_anchor_text(pdf_engine="pdfreport") 【提取锚点文本辅助识别】│ │ │ ├─→ build_finetuning_prompt() 【构建多模态模型提示词】│ │ │ └─→ client.chat.completions.create(model="olmOCR-7B-0225-preview") 【调用7B多模态模型OCR识别】│ │ └─→ 结果聚合: json.loads() + 文本拼接 【解析JSON结果并拼接完整文本】│ ││ └─→ process_KG_extract(pdf_path) 【知识图谱抽取模块】│ ├─→ mysql_main(data_dir, title_unique) 【文本结构化处理并入库】│ │ ├─→ extract_text(): 12种标题格式识别 【文档结构化解析】│ │ └─→ insert_data(): 数据库存储 【结构化数据存入MySQL】│ ││ └─→ mysql2_main(title, title_unique) 【从数据库抽取知识图谱】│ ├─→ fetch_text_by_id(): 数据库查询 【从MySQL获取结构化文本】│ ├─→ ThreadPoolExecutor并发调用 【并发处理多条文本记录】│ └─→ check_for_typo(): 调用Qwen2.5-14B-Instruct 【14B大模型抽取三元组】│└─→ Gradio界面更新: [text_output, text1_output] 【将识别结果和知识图谱显示在网页界面】
各模块详细功能说明:
PDF文本识别阶段:
render_pdf_to_base64png()
: 将PDF页面以3072像素高分辨率渲染为PNG图像,再编码为Base64格式get_anchor_text()
: 使用多种PDF解析引擎(pdftotext、pypdf、pdfium)提取文本位置信息作为辅助build_finetuning_prompt()
: 结合锚点文本构建发送给多模态模型的具体指令olmOCR-7B-0225-preview
: 7B参数的多模态模型,能同时理解图像和文本进行OCR识别
知识图谱抽取阶段:
extract_text()
: 使用12种正则表达式识别文档中的标题格式(中文编号、数字编号、括号标题等)insert_data()
: 将识别出的标题-段落结构存储到MySQL的markdown_data表中fetch_text_by_id()
: 根据文档ID从数据库查询之前存储的结构化文本Qwen2.5-14B-Instruct
: 14B参数的大语言模型,专门用于从文本中抽取原子事实和关键元素
界面展示阶段:
text_output
: 显示PDF文本识别结果text1_output
: 显示知识图谱抽取的三元组结果
3. 隐性方法分析
隐性方法1:连贯性导向的引擎选择
scores = {label: get_document_coherency(text) for label, text in options.items()}
best_option_label = max(scores, key=scores.get)
- 关键步骤:通过coherency模块评估不同引擎提取文本的质量,自动选择最优结果
- 定义:
连贯性评分选择法
= 多引擎并行提取 + 连贯性量化评估 + 最优结果自动选择
隐性方法2:空间感知的内容线性化
def _linearize_pdf_report(report: PageReport, max_length: int = 4000):# 识别边缘元素edge_elements = set()min_x0_image = min(images, key=lambda e: e.bbox.x0)# 随机采样剩余元素 random.shuffle(remaining_elements)
- 关键步骤:优先保留页面边缘重要元素,然后随机采样填充到长度限制内
- 定义:
边缘优先的空间线性化法
= 边缘元素识别 + 重要性权重分配 + 随机采样补充
隐性方法3:阈值驱动的段落重组
if len(' '.join(paragraph + [line])) < 80:paragraph.append(line)
else:_commit_paragraph(paragraph, sections, current_title)
- 关键步骤:基于80字符长度阈值和格式变化率动态决定段落边界
- 定义:
双阈值段落合并法
= 长度阈值判断 + 格式变化检测 + 动态边界确定
4. 隐性特征分析
隐性特征1:文档解析质量度量
- 隐藏在
get_document_coherency(text)
函数中,用于评估提取文本的连贯性程度 - 这个特征不在原始PDF中,而是解析过程中产生的质量评估指标
隐性特征2:页面元素空间密度
- 隐藏在
_merge_image_elements()
和边缘元素识别中,体现页面布局的复杂程度 - 通过Union-Find算法合并重叠元素,反映页面内容的空间分布特性
隐性特征3:标题层级深度
- 隐藏在12种正则表达式匹配中,通过中文数字、阿拉伯数字、罗马数字等识别文档结构层次
- 影响知识图谱的层次化组织
5. 潜在局限性分析(基于代码)
技术实现局限性:
- 模型硬件依赖:代码显示需要48G显存运行两个模型,硬件门槛高
- 固定线程数限制:
ThreadPoolExecutor(max_workers=10)
固定10线程,无法根据系统资源动态调整 - 单一文件格式:
file_types=[".pdf"]
只支持PDF格式
算法设计局限性:
- 锚点文本长度限制:
target_length=1500
和max_length=4000
的硬编码限制可能截断重要信息 - 标题格式覆盖局限:12种正则表达式可能无法覆盖所有文档格式变体
- 80字符合并阈值:固定阈值可能不适用于所有文档类型
系统架构局限性:
- 数据库单点依赖:MySQL连接失败会导致整个知识图谱功能失效
- API调用失败处理:代码中缺乏对模型API调用失败的充分容错机制
- 内存管理不足:虽然有
gc.collect()
,但大文档处理时仍可能内存溢出
这些局限性都直接来源于代码实现,体现了当前系统的技术约束。