从模型 API 到企业知识库与智能助手
从模型 API 到企业知识库与智能助手本文默认已经完成本地大模型部署,并且可以通过 vLLM、Ollama 或其他 OpenAI 兼容服务正常调用模型。本文重点讨论模型“能运行”之后,如何逐步建设企业知识库、业务工具、质量评估、权限控制和可观测能力。前言很多人在完成本地大模型部署后,会立即遇到一个问题:模型已经能正常回答问题了,下一步应该做什么?此时往往会出现几种常见误区:立刻开始微调模型;把公司全部文件一次性导入知识库;直接让模型连接数据库;一开始就建设“什么都能做”的企业智能助手;只关注模型回答效果,不记录知识来源和执行过程。实际上,本地模型能够返回一段文字,只说明最底层的推理服务已经启动成功,并不代表它已经成为一个可用于企业生产环境的系统。一个真正可用的企业 AI 应用,至少还需要解决以下问题:模型服务是否稳定;如何让模型使用企业资料;如何保证回答有真实依据;如何控制不同用户可访问的知识范围;如何调用企业内部接口;如何判断答案是否正确;如何定位错误、循环和性能问题;如何持续更新知识,而不是频繁重新训练模型。本文将搭建一个最小但完整的企业知识助手,实现:本地大模型 + 企业文档解析 + BGE-M3 向量模型 + Qdrant 向量数据库 + FastAPI 问答接口 + 文档来源引用 + 部门权限过滤 + 后续工具调用与评估扩展一、本地模型部署成功,不等于企业 AI 已经建设完成本地大模型系统可以分成五层:第五层:业务应用 智能问答、运维助手、客服助手、研发助手 第四层:Agent 与工具 查询数据库、调用接口、执行工作流、提交审批 第三层:知识与检索 文档解析、切分、Embedding、检索、重排、引用 第二层:模型服务 vLLM、Ollama、推理 API、并发、显存、上下文 第一层:基础设施 GPU、驱动、CUDA、Linux、Docker、存储、网络完成本地模型部署,只代表第二层基本可用。接下来最重要的工作,不是继续寻找更大的模型,而是把上层能力逐步补齐。二、先判断问题应该用哪种技术解决企业在使用大模型时,经常把所有问题都归结为“需要训练模型”。实际上,不同问题应该使用不同方案。需求更适合的方案回答公司制度、产品文档和项目资料RAG 企业知识库查询订单、库存、设备和服务状态工具调用或内部 API固定回答格式和输出结构Prompt 或结构化输出统一语言风格和表达方式Prompt,必要时再微调学习稳定的专业任务模式SFT 或 LoRA 微调获取经常变化的数据RAG 或实时工具,不应写进模型参数限制用户访问范围身份认证、权限过滤和数据隔离判断回答质量测试集、自动评估和人工反馈一个比较合理的建设顺序是:模型 API ↓ Prompt 约束 ↓ 企业知识库 RAG ↓ 工具调用 ↓ 质量评估 ↓ 可观测与审计 ↓ 确定仍有必要后再微调2.1 为什么不建议第一步就微调微调更适合改变模型的行为方式,例如:固定分类规则;固定业务流程;固定输出格式;学习某类专业任务的示例;统一术语和语言风格。但公司制度、产品参数、项目进度、客户资料和接口数据经常变化。如果把这些动态知识全部通过微调写进模型,会产生几个问题:资料更新后需要重新制作训练数据;很难删除已经写入参数的旧知识;很难证明答案来自哪份资料;不同部门权限难以隔离;模型仍然可能把旧知识和新知识混在一起。因此,企业建设的第一阶段通常应该优先使用 RAG,而不是微调。三、先给模型服务做一次验收在接入知识库之前,先确认模型 API 本身稳定。假设 vLLM 地址为:http://127.0.0.1:8000/v13.1 检查模型列表curlhttp://127.0.0.1:8000/v1/models3.2 检查普通问答curlhttp://127.0.0.1:8000/v1/chat/completions\-H"Content-Type: application/json"\-d'{ "model": "qwen-local", "messages": [ { "role": "user", "content": "只回答OK" } ], "temperature": 0.1, "max_tokens": 16, "stream": false }'预期结果中应包含:{"choices":[{"message":{"content":"OK"},"finish_reason":"stop"}]}3.3 建立基础验收指标至少记录:模型名称 启动参数 最大上下文 首 Token 延迟 总响应耗时 输入 Token 输出 Token 并发请求数 请求成功率 GPU 显存使用量 异常退出次数不要只测试一次curl成功,就认为服务已经可以交付。正式接入业务前,至少准备 20~50 个固定问题作为模型基线测试集。每次修改模型、Prompt、量化方式或推理参数后,都重新执行这些问题。四、选择第一个业务场景企业 AI 的第一版不应该从“全能助手”开始。建议选择同时满足以下条件的场景:用户问题比较集中;公司已经有可用资料;答案能够被人工验证;即使回答失败,也不会直接造成严重业务后果;使用频率足够高;能够明确统计节省的时间。适合作为第一阶段的场景包括:公司制度问答 产品资料问答 项目实施手册问答 开发规范助手 运维手册助手 售后知识助手 标准方案生成助手不建议第一阶段直接开放:自动修改生产数据库 自动执行服务器命令 自动审批付款 自动删除文件 不经确认调用高风险接口第一阶段的目标应该是:让模型基于指定资料,稳定回答一个边界清晰的问题,并且能够显示引用来源。五、企业知识库最重要的不是模型,而是资料治理知识库效果不好,很多时候不是模型能力不足,而是资料本身存在问题。常见情况包括:同一个制度存在多个版本;文件名无法说明内容;扫描 PDF 无法提取文本;表格没有标题和字段说明;重要结论隐藏在聊天记录中;文档没有部门和权限标识;已废止的制度仍然被检索到;产品名称和内部简称不统一;一份文件同时包含多个完全不同的主题。5.1 推荐的资料目录knowledge/ ├── raw/ # 原始资料 │ ├── hr/ │ ├── product/ │ ├── project/ │ ├── development/ │ └── operations/ ├── normalized/ # 清洗后的标准文本 ├── metadata.json # 文档元数据 ├── rejected/ # 暂不入库的文件 └── evaluation/ # 测试问题和标准答案5.2 每份资料至少需要这些元数据{"doc_id":"HR-EMPLOYEE-HANDBOOK","title":"员工手册","department":"hr","version":"2026.07","status":"active","permission":"internal","owner":"人力资源部","updated_at":"2026-07-20","source":"员工手册V3.docx"}最关键的字段是:唯一文档编号 标题 版本 状态 所属部门 权限范围 更新时间 原始来源5.3 资料进入知识库前的检查清单[ ] 是否为当前有效版本 [ ] 是否存在重复文件 [ ] 是否包含敏感信息 [ ] 是否明确所属部门 [ ] 是否明确可访问范围 [ ] 是否能够提取正文 [ ] 标题是否能表达文档内容 [ ] 表格是否有字段名称 [ ] 图片中的重要文字是否已经转成文本 [ ] 是否指定资料维护负责人六、本文使用的最小 RAG 架构本文实现以下链路:┌─────────────────┐ │ 本地 Qwen/vLLM │ │ OpenAI 兼容接口 │ └────────▲────────┘ │ │ 组合 Prompt │ ┌────────┐ HTTP ┌───────┴────────┐ │ 用户端 │ ─────────────→ │ FastAPI RAG API │ └────────┘ └───────┬────────┘ │ 问题向量化与权限过滤 │ ┌────────▼────────┐ │ Qdrant │ │ 向量 + 文本元数据 │ └────────▲────────┘ │ ┌────────┴────────┐ │ BGE-M3 │ │ Embedding 模型 │ └─────────────────┘vLLM 可以提供 OpenAI 兼容的 Chat Completions 等接口,因此上层应用可以使用 OpenAI Python SDK,并将base_url指向本地服务。BGE-M3 输出 1024 维向量,支持多语言,并能够用于密集、稀疏和多向量检索。本文为了降低第一版复杂度,先使用密集向量检索,后续再增加混合检索和重排。Qdrant 中每个知识片段保存为一个 Point:Point ├── id ├── vector └── payload ├── text ├── title ├── source ├── department ├── version └── chunk_indexPayload 不只是展示来源,还可以用于部门、状态、版本和权限过滤。七、创建项目目录mkdir-pcompany-rag/appmkdir-pcompany-rag/scriptsmkdir-pcompany-rag/knowledge/rawcdcompany-rag目录结构:company-rag/ ├── docker-compose.yml ├── requirements.txt ├── .env ├── metadata.json ├── knowledge/ │ └── raw/ ├── scripts/ │ └── ingest.py └── app/ ├── __init__.py └── main.py八、使用 Docker 启动 Qdrant创建docker-compose.yml:services:qdrant:image:qdrant/qdrant:latestcontainer_name:company-rag-qdrantrestart:unless-stoppedports:-"6333:6333"-"6334:6334"volumes:-qdrant-data:/qdrant/storagevolumes:qdrant-data:启动:dockercompose up-d查看状态:dockercomposeps访问管理界面:http://127.0.0.1:6333/dashboard正式环境建议:固定经过验证的 Qdrant 镜像版本;不直接把管理端口暴露到公网;配置备份;为需要过滤的 Payload 字段建立索引;对不同环境使用独立 Collection。九、准备 Python 环境创建虚拟环境:python3.11-mvenv .venvsource.venv/bin/activate创建requirements.txt:fastapi uvicorn[standard] openai=1.26 qdrant-client sentence-transformers python-dotenv pydantic pypdf python-docx安装:pipinstall-rrequirements.txt9.1 双 3090 环境中的资源安排如果本地大模型已经通过 Tensor Parallel 占用了两张 RTX 3090,不要默认再把 Embedding 模型放到 GPU。第一版可以这样安排:GPU 0 + GPU 1:Qwen/vLLM CPU + 内存:BGE-M3 Embedding你的机器如果有较强 CPU 和较大内存,这种方案可以先运行起来,只是批量导入资料时速度会慢一些。后续可以选择:为 Embedding 单独预留一张 GPU;在模型低峰期离线生成文档向量;将 Embedding 独立部署成服务;使用更轻量的中文或多语言 Embedding 模型;对查询向量增加缓存。十、配置环境变量创建.env:LLM_BASE_URL=http://127.0.0.1:8000/v1LLM_API_KEY=EMPTYLLM_MODEL=qwen-localQDRANT_URL=http://127.0.0.1:6333QDRANT_COLLECTION=company_knowledgeEMBEDDING_MODEL=BAAI/bge-m3EMBEDDING_DEVICE=cpuRETRIEVAL_TOP_K=8CONTEXT_TOP_K=4如果模型服务在宿主机,而 RAG 应用运行在 Docker 中,不能继续使用容器自身的127.0.0.1,应根据部署环境改成宿主机地址或同一 Docker 网络中的服务名。十一、准备示例资料和元数据创建:knowledge/raw/hr/employee_handbook.md内容示例:# 员工请假制度 员工申请年假,应至少提前一个工作日提交申请。 连续请假三天及以上,需要由部门负责人审批。 紧急情况下可以先通过电话或即时通信工具告知直属负责人,并在返岗后补交申请。创建metadata.json:{"hr/employee_handbook.md":{"doc_id":"HR-EMPLOYEE-HANDBOOK","title":"员工手册","department":"hr","version":"2026.07","status":"active","permission":"internal","owner":"人力资源部"}}十二、实现文档导入程序创建scripts/ingest.py:importhashlibimportjsonimportosimportsysimportuuidfrompathlibimportPathfromtypingimportAnyfromdocximportDocumentfromdotenvimportload_dotenvfrompypdfimportPdfReaderfromqdrant_clientimportQdrantClient,modelsfromsentence_transformersimportSentenceTransformer load_dotenv()BASE_DIR=Path(__file__).resolve().parent.parent KNOWLEDGE_DIR=BASE_DIR/"knowledge"/"raw"METADATA_FILE=BASE_DIR/"metadata.json"QDRANT_URL=os.getenv("QDRANT_URL","http://127.0.0.1:6333",)QDRANT_COLLECTION=os.getenv("QDRANT_COLLECTION","company_knowledge",)EMBEDDING_MODEL=os.getenv("EMBEDDING_MODEL","BAAI/bge-m3",)EMBEDDING_DEVICE=os.getenv("EMBEDDING_DEVICE","cpu",)VECTOR_SIZE=1024CHUNK_SIZE=800CHUNK_OVERLAP=120defread_file(path:Path)-