0%

在Mac mini M4上搭建智能文档问答助手


在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 # 轻量又高效的现代Web框架
- SQLAlchemy # Python界的ORM扛把子
- PostgreSQL # 老牌关系型数据库,支持数组字段
- Uvicorn # 让FastAPI飞起来的ASGI服务器

2.2 文档处理工具

1
2
3
4
# 文档处理三剑客
- python-docx # 专治各种docx文件
- mammoth # 文档转换小能手(最初尝试用markdown转换工具,但成功率太低弃用了)
- 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):
# 专注处理docx文件(测试4906个文件,成功导入2578个)
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里的所有文本内容(处理各种复杂格式)
docx_doc = DocxDocument(file_path)
if docx_doc is None:
print(f"解析失败: {file_path}") # 记录失败日志便于排查
return False

# 1. 收割段落文本
all_text = [para.text.strip() for para in docx_doc.paragraphs if para.text.strip()]

# 2. 提取表格内容
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))

# 3. 获取页眉页脚
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)

# 1. 智能关键词提取
keywords = llm.extract_keywords(question)

# 2. 精确匹配 + 模糊匹配双重策略
query = db.query(DocumentContent).filter(
DocumentContent.keywords.op('&&')(cast(keywords, ARRAY(String)))
)
docs = query.order_by(DocumentContent.id.desc()).limit(10).all()

# 3. 构建丰富上下文
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 "未找到相关文档内容"

# 4. 生成专业回答
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
-- 使用PostgreSQL数组字段进行高效的关键词匹配
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
# 使用本地Ollama模型
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 技术收获

  1. 文档解析比想象中复杂:各种格式的兼容性问题层出不穷
  2. 云端模型效果确实好很多:但需要平衡成本和使用体验
  3. 本地模型作为备胎还算合格:在断网情况下依然能提供基本服务

7.2 项目价值

  • 提高工作效率:从手动检索N小时缩短到AI问答N秒
  • 数据安全保障:可以本地运行,敏感数据不外泄
  • 技术学习价值:深入了解了AI应用开发的全流程

期待未来能解决所有格式兼容问题,让这个小助手真正成为文档处理的全能选手!同时,也希望这个项目能为其他开发者提供一些参考和启发。