在Mac mini M4(配置M4芯片/16GB内存/2TB存储)上开发本地化智能文档问答系统。
1. 项目背景 1.1 我的Mac mini M4配置 如前所述,几个月前我买了丐版Mac,配置如下:
处理器 : Apple M4
内存 : 16GB统一内存
存储 : 256GB内置SSD + 2TB外置硬盘
1.2 本地AI模型部署 在Mac mini上部署了本地大模型工具ollama:
ollama版本 : 0.9.0
安装的模型 : llama3:latest、qwen3:4b、deepseek-r1:8b、gemma3:4b
1.3 项目动机 工作中积累了几千个文档。如何最大程度地发挥这些文档的作用,这是个问题。于是决定自己动手打造一个智能文档助手:
自动文档处理 : 批量消化大量文档的小能手
智能信息提取 : 能提炼关键信息的智能助手
即时问答 : 随问随答的文档专家
数据安全 : 完全本地运行,数据安全有保障
2. 技术选型 2.1 后端技术栈 1 2 3 4 5 - FastAPI - SQLAlchemy - PostgreSQL - Uvicorn
2.2 文档处理工具 1 2 3 4 - python-docx - mammoth - python-magic
2.3 AI模型搭配 双模型组合拳,确保系统稳定可靠:
云端模型 : 阿里云的Qwen-Plus(回答质量明显优于本地模型)
本地模型 : Ollama(断网时的备胎方案)
1 2 3 4 5 6 class LLMClient : def __init__ (self, model_type="openai" ) : if model_type == "ollama" : self.ollama_client = OllamaClient(model_name="llama3:latest" ) else : self.client = OpenAI(api_key=api_key)
3. 核心功能揭秘 3.1 文档处理流水线 批量处理docx文档 1 2 3 4 5 6 7 8 9 10 async def process_single_document (file_path: str) : if not file_path.lower().endswith('.docx' ): return False file_hash = calc_file_hash(file_path) if db.query(Document).filter_by(file_hash=file_hash).first(): print(f"文件已经处理过啦: {file_path} " ) return True
完整文本提取术 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 docx_doc = DocxDocument(file_path) if docx_doc is None : print(f"解析失败: {file_path} " ) return False all_text = [para.text.strip() for para in docx_doc.paragraphs if para.text.strip()] for table in docx_doc.tables: for row in table.rows: row_texts = [cell.text.strip() for cell in row.cells if cell.text.strip()] if row_texts: all_text.append(" | " .join(row_texts)) for section in docx_doc.sections: if section.header: for para in section.header.paragraphs: if para.text.strip(): all_text.append(para.text.strip())
3.2 智能内容分析 关键词提取优化 1 2 3 4 5 6 7 8 9 10 11 def extract_keywords (self, content: str) -> List[str]: prompt = f"""请从以下问题中提取关键词,要求: 1. 提取问题中的核心概念、实体名称、时间、地点等关键信息 2. 重点关注:企业名称、风险类型、监管措施、时间范围、具体数据等 3. 每个关键词用空格分隔,只返回关键词 4. 不要添加问题中没有的词 5. 关键词要具体,避免过于宽泛的词 问题:{content} 关键词:"""
智能问答系统 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 @router.post("/qa", summary="知识库问答") def knowledge_qa ( question: str = Body(...) , model_type: str = Query("ollama" , description="大模型类型(openai/ollama)" ) , model_name: str = Query("llama3:latest" , description="模型名称" ) , db: Session = Depends(get_db) ) : llm = LLMClient(model_type=model_type, model_name=model_name) keywords = llm.extract_keywords(question) query = db.query(DocumentContent).filter( DocumentContent.keywords.op('&&' )(cast(keywords, ARRAY(String))) ) docs = query.order_by(DocumentContent.id.desc()).limit(10 ).all() context_parts = [] for i, doc in enumerate(docs, 1 ): doc_info = f"文档{i} :\n" if doc.summary: doc_info += f"摘要:{doc.summary} \n" doc_info += f"关键词:{', ' .join(doc.keywords) if doc.keywords else '无' } \n" doc_info += f"内容片段:{doc.content[:3000 ]} ...\n" context_parts.append(doc_info) context = "\n" .join(context_parts) if context_parts else "未找到相关文档内容" prompt = f"""你是一个专业的知识库助手。请根据以下知识库内容,准确回答用户的问题。 知识库内容: {context} 用户问题:{question} 回答要求: 1. 基于知识库内容回答,如果知识库中没有相关信息,请明确说明 2. 回答要具体、准确,包含具体的企业名称、数据、时间等信息 3. 如果问题涉及多个方面,请分点回答 4. 保持专业性和客观性 5. 如果知识库内容不足以完整回答问题,请说明已知信息和未知信息 6. 优先使用文档中的具体数据和事实,避免泛泛而谈 7. 回答的最后附上答案的来源,即文档名称 请回答:""" answer = llm.generate_answer(prompt) return {"question" : question, "keywords" : keywords, "answer" : answer, "model_used" : f"{model_type} :{model_name} " }
4. 技术挑战与解决方案 4.1 文件格式兼容性问题 经过大量测试发现:
docx文件 : 4906个文件中成功导入2578个,成功率约52.5%
Excel文件 : 暂时未处理(计划未来支持)
旧版doc文件 : 无法解析
WPS文件 : 无法解析
4.2 模型效果对比 经过测试发现:
云端模型(Qwen-Plus) : 回答质量明显优于本地模型,理解准确度高
本地模型(Llama3) : 云端模型需要联网且有费用问题,本地模型完全免费
4.3 数据库优化 1 2 3 4 5 6 7 8 9 10 11 12 CREATE TABLE document_contents ( id SERIAL PRIMARY KEY , document_id INTEGER REFERENCES documents(id ), content TEXT NOT NULL , keywords VARCHAR [] NOT NULL , summary TEXT ); SELECT * FROM document_contents WHERE keywords && ARRAY ['监管' , '风险较大企业' , '企业名称' ];
5. 实际应用效果 5.1 性能表现
文档处理速度 : 使用本地模型处理文档,2578个文件用了大约24小时
问答响应时间 : 云端模型1-3秒,本地模型3-8秒
5.2 使用体验 1 2 3 4 5 6 curl -X POST "http://127.0.0.1:8000/api/v1/document/qa?model_type=ollama&model_name=gemma3:4b" \ -H "Content-Type: application/json" \ -d '{"question": "近年来行业协会做了哪些工作?请以序号简短列出。"}' {"question" :"近年来行业协会做了哪些工作?请以序号简短列出。" ,"keywords" :["行业协会" ,"工作" ],"answer" :"根据提供的文档内容,以下是行业协会在近年来可能做出的工作,以及它们所依据的证据:\n\n1. **组织⬛⬛⬛⬛⬛实训:** 行业协会组织了“⬛⬛⬛、⬛⬛⬛、⬛⬛⬛”等企业参加了“⬛⬛⬛⬛⬛⬛实训活动”。(文档9)\n2. **组织⬛⬛⬛参加培训:** 行业协会组织了⬛⬛⬛参加了“⬛⬛⬛⬛培训”。(文档9)\n3. **协助企业⬛⬛⬛:** 行业协会协助⬛⬛⬛⬛⬛。(文档10)\n4. **组织⬛⬛:** 行业协会参与了“⬛⬛⬛⬛⬛⬛”等⬛⬛工作。(文档9, 10)\n\n**答案来源:** 文档9, 文档10" ,"model_used" :"ollama:gemma3:4b" }
1 2 3 4 5 6 # 使用云端模型 curl -X POST "http://127.0.0.1:8000/api/v1/document/qa?model_type=openai" \ -H "Content-Type: application/json" \ -d '{"question": "近年来行业协会做了哪些工作?请以序号简短列出。"}' {"question":"近年来行业协会做了哪些工作?请以序号简短列出。","keywords":["行业协会","工作","时间范围"],"answer":"近年来行业协会在药品生产领域做了以下工作:\n\n1. **组织⬛⬛⬛⬛实训活动**:2024年4月8日至2024年4月14日期间,行业协会组织⬛⬛⬛⬛实训活动,以提升从业人员的专业技能。(来源:文档9)\n\n2. **开展针对性培训**:2024年,行业协会根据⬛⬛⬛⬛履职能力考核中发现的共性问题和薄弱环节,制定了培训计划,并已确定3个培训主题,旨在提高从业人员的履职能力。(来源:文档7)\n\n以上是基于知识库内容能够明确列出的工作。其他具体工作细节未在知识库中提及,因此无法补充更多信息。","model_used":"openai:qwen-plus"}
6. 未来计划 6.1 文件格式支持
✅ 已支持 : docx(部分)
⏳ 计划支持 : Excel表格解析
❓ 待解决 : 旧版doc和WPS文件
6.2 模型优化
继续调教本地模型提示词,提高回答质量
尝试更大的本地模型(如果有显存的话)
优化云端模型调用成本,实现智能切换
6.3 功能增强
支持向量数据库,提高语义搜索精度
添加用户权限管理
实现多语言支持
7. 总结与感悟 在Mac mini M4上搭建的这个文档问答小助手,虽然还有些不完美,但已经能解决大部分实际需求了。最大的感悟是:
7.1 技术收获
文档解析比想象中复杂 :各种格式的兼容性问题层出不穷
云端模型效果确实好很多 :但需要平衡成本和使用体验
本地模型作为备胎还算合格 :在断网情况下依然能提供基本服务
7.2 项目价值
提高工作效率 :从手动检索N小时缩短到AI问答N秒
数据安全保障 :可以本地运行,敏感数据不外泄
技术学习价值 :深入了解了AI应用开发的全流程
期待未来能解决所有格式兼容问题,让这个小助手真正成为文档处理的全能选手!同时,也希望这个项目能为其他开发者提供一些参考和启发。