上一篇 下一篇 分享链接 返回 返回顶部

企业内网搭建AI办公平台:从部署架构到源码实现

发布人:慈云数据-客服中心 发布时间:12小时前 阅读量:2

AI办公 私有化部署方案|附源码

在企业数字化转型过程中,AI办公已经从“锦上添花”的工具,逐渐变成提升组织效率的基础设施。无论是智能文档问答、会议纪要生成、知识库检索、合同审阅、邮件润色,还是自动生成周报、日报、方案大纲,AI都能显著降低重复性劳动成本。

但是,对于很多企业来说,直接使用公有云AI产品会面临一些现实问题:

  • 企业内部文档、合同、财务数据、客户资料不适合上传到第三方平台;
  • 部分行业存在合规要求,例如政务、金融、医疗、教育、制造等;
  • 需要对模型、知识库、权限、日志、审计进行统一管控;
  • 希望将AI能力集成到现有OA、IM、CRM、ERP等系统中;
  • 长期高频使用公有云API,成本不可控。

因此,越来越多企业开始考虑将AI办公能力进行私有化部署。本文将从整体架构、技术选型、部署流程、核心源码、功能扩展、安全策略等方面,完整介绍一套可落地的AI办公私有化部署方案。


一、方案目标

本方案的目标是搭建一套运行在企业内网或私有云环境中的AI办公平台,具备以下能力:

  1. 本地大模型对话

    • 支持接入本地部署的大语言模型;
    • 支持多轮对话、上下文记忆;
    • 可用于写作、总结、翻译、润色、代码辅助等办公场景。
  2. 企业知识库问答

    • 支持上传PDF、Word、Excel、Markdown、TXT等文档;
    • 自动进行文本切分、向量化和入库;
    • 用户提问时,从知识库中检索相关内容,再由大模型生成答案。
  3. 文档智能处理

    • 自动生成会议纪要;
    • 自动提取合同关键条款;
    • 自动生成周报、月报、方案大纲;
    • 支持文档摘要、改写、翻译。
  4. 权限与审计

    • 支持用户登录;
    • 支持不同部门、不同角色访问不同知识库;
    • 记录用户提问、模型回答、文档上传、知识库检索日志。
  5. 私有化运行

    • 所有数据均保存在企业内部;
    • 大模型、向量库、数据库、后端服务、前端页面均可本地部署;
    • 支持Docker一键部署。

二、整体架构设计

整套AI办公系统可以分为六个核心模块:

┌────────────────────────────┐
│          前端页面           │
│  Chat UI / 知识库 / 文档处理 │
└──────────────┬─────────────┘
               │ HTTP / WebSocket
┌──────────────▼─────────────┐
│          后端服务           │
│ FastAPI / Flask / Spring AI │
└───────┬───────────┬────────┘
        │           │
        │           │
┌───────▼───┐   ┌───▼────────┐
│  大模型服务 │   │  向量数据库  │
│ Ollama等   │   │ Milvus/FAISS │
└───────┬───┘   └───┬────────┘
        │           │
┌───────▼───────────▼────────┐
│       企业知识库文档存储     │
│ PDF / Word / Excel / TXT   │
└──────────────┬─────────────┘
               │
┌──────────────▼─────────────┐
│       关系型数据库          │
│ MySQL / PostgreSQL          │
└────────────────────────────┘

1. 前端层

前端主要负责提供用户交互界面,包括:

  • AI对话窗口;
  • 知识库管理;
  • 文档上传;
  • 历史会话查看;
  • 用户权限管理;
  • 提示词模板管理。

技术上可以选择:

  • Vue3 + Element Plus;
  • React + Ant Design;
  • Next.js;
  • 普通HTML页面。

如果企业已有OA系统,也可以将AI办公作为一个独立模块嵌入。

2. 后端服务层

后端负责业务逻辑处理,包括:

  • 用户鉴权;
  • 会话管理;
  • 文档上传与解析;
  • 文本切分;
  • 调用Embedding模型生成向量;
  • 向量检索;
  • 拼接Prompt;
  • 调用大模型生成回答;
  • 日志记录。

推荐使用:

  • Python FastAPI;
  • Java Spring Boot + Spring AI;
  • Node.js NestJS。

本文源码示例采用 Python FastAPI,因为它开发效率高,和AI生态兼容性好。

3. 大模型服务层

私有化部署大模型可以使用以下方式:

部署方式 说明
Ollama 部署简单,适合快速落地
vLLM 推理性能好,适合高并发
LM Studio 图形化部署,适合测试
Text Generation WebUI 功能丰富
TensorRT-LLM 适合高性能GPU环境

企业初期建议使用 Ollama,因为它安装简单、模型管理方便、API调用友好。

可选模型包括:

  • Qwen2.5;
  • DeepSeek-R1-Distill;
  • Llama3;
  • ChatGLM;
  • Baichuan;
  • Yi。

例如部署Qwen模型:

ollama pull qwen2.5:7b
ollama run qwen2.5:7b

4. 向量数据库

知识库问答的核心是RAG,即检索增强生成。常见向量数据库有:

向量库 特点
FAISS 本地轻量,适合中小型知识库
Milvus 功能强大,适合企业级场景
Chroma 简单易用,适合原型开发
Weaviate 支持丰富数据结构
pgvector 与PostgreSQL结合,适合数据库统一管理

本文示例使用 Chroma,因为部署简单,适合快速验证。如果生产环境中文档数量较大,可以切换为Milvus或pgvector。


三、核心功能流程

1. 普通AI对话流程

用户输入问题
    ↓
后端接收请求
    ↓
加载历史上下文
    ↓
拼接Prompt
    ↓
调用本地大模型
    ↓
返回结果给前端
    ↓
保存会话日志

普通对话不依赖知识库,适合写作、翻译、润色、邮件生成等通用办公场景。

2. 知识库问答流程

用户提出问题
    ↓
对问题进行向量化
    ↓
在向量数据库中检索相似文档片段
    ↓
取Top-K相关内容
    ↓
将相关内容与用户问题拼接成Prompt
    ↓
调用大模型生成回答
    ↓
返回答案和引用来源

这种方式的优势是,大模型不需要重新训练,只需要把企业内部资料放入知识库,就能实现“基于企业资料的智能问答”。

3. 文档入库流程

上传文档
    ↓
解析文档文本
    ↓
清洗文本
    ↓
按固定长度切分
    ↓
生成Embedding向量
    ↓
写入向量数据库
    ↓
记录文档元数据

文档切分非常重要。如果切分太短,语义不完整;如果切分太长,检索不精准。通常建议每段控制在500到1000个中文字符左右,并设置一定重叠区域。


四、Docker私有化部署方案

下面给出一个最小可运行的部署方案,包含:

  • Ollama大模型服务;
  • FastAPI后端;
  • Chroma向量库;
  • Web前端;
  • PostgreSQL数据库。

1. 项目目录结构

ai-office-private/
├── docker-compose.yml
├── backend/
│   ├── main.py
│   ├── requirements.txt
│   ├── rag.py
│   ├── llm.py
│   ├── document_loader.py
│   └── Dockerfile
├── frontend/
│   ├── index.html
│   └── nginx.conf
└── data/
    ├── uploads/
    └── chroma/

五、Docker Compose源码

下面是 docker-compose.yml 示例:

version: "3.9"

services:
  ollama:
    image: ollama/ollama:latest
    container_name: ai-office-ollama
    ports:
      - "11434:11434"
    volumes:
      - ./data/ollama:/root/.ollama
    restart: always

  postgres:
    image: postgres:15
    container_name: ai-office-postgres
    environment:
      POSTGRES_DB: ai_office
      POSTGRES_USER: ai_user
      POSTGRES_PASSWORD: ai_password
    ports:
      - "5432:5432"
    volumes:
      - ./data/postgres:/var/lib/postgresql/data
    restart: always

  backend:
    build: ./backend
    container_name: ai-office-backend
    ports:
      - "8000:8000"
    environment:
      OLLAMA_BASE_URL: http://ollama:11434
      OLLAMA_MODEL: qwen2.5:7b
      CHROMA_DIR: /app/data/chroma
      UPLOAD_DIR: /app/data/uploads
    volumes:
      - ./data:/app/data
    depends_on:
      - ollama
      - postgres
    restart: always

  frontend:
    image: nginx:latest
    container_name: ai-office-frontend
    ports:
      - "8080:80"
    volumes:
      - ./frontend/index.html:/usr/share/nginx/html/index.html
      - ./frontend/nginx.conf:/etc/nginx/conf.d/default.conf
    depends_on:
      - backend
    restart: always

六、后端源码

1. requirements.txt

fastapi==0.115.0
uvicorn==0.30.6
requests==2.32.3
python-multipart==0.0.9
chromadb==0.5.5
pypdf==4.3.1
python-docx==1.1.2
sentence-transformers==3.0.1

2. Dockerfile

FROM python:3.10-slim

WORKDIR /app

COPY requirements.txt /app/requirements.txt

RUN pip install --no-cache-dir -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple

COPY . /app

EXPOSE 8000

CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]

3. llm.py

该文件用于调用Ollama本地大模型。

import os
import requests

OLLAMA_BASE_URL = os.getenv("OLLAMA_BASE_URL", "http://localhost:11434")
OLLAMA_MODEL = os.getenv("OLLAMA_MODEL", "qwen2.5:7b")


def chat_with_ollama(prompt: str) -> str:
    url = f"{OLLAMA_BASE_URL}/api/generate"

    payload = {
        "model": OLLAMA_MODEL,
        "prompt": prompt,
        "stream": False
    }

    response = requests.post(url, json=payload, timeout=300)
    response.raise_for_status()

    data = response.json()
    return data.get("response", "")

4. document_loader.py

该文件用于解析上传的文档。

import os
from pypdf import PdfReader
from docx import Document


def load_pdf(file_path: str) -> str:
    reader = PdfReader(file_path)
    texts = []

    for page in reader.pages:
        text = page.extract_text()
        if text:
            texts.append(text)

    return "\n".join(texts)


def load_docx(file_path: str) -> str:
    doc = Document(file_path)
    texts = []

    for paragraph in doc.paragraphs:
        if paragraph.text.strip():
            texts.append(paragraph.text.strip())

    return "\n".join(texts)


def load_txt(file_path: str) -> str:
    with open(file_path, "r", encoding="utf-8") as f:
        return f.read()


def load_document(file_path: str) -> str:
    ext = os.path.splitext(file_path)[1].lower()

    if ext == ".pdf":
        return load_pdf(file_path)

    if ext == ".docx":
        return load_docx(file_path)

    if ext in [".txt", ".md"]:
        return load_txt(file_path)

    raise ValueError(f"暂不支持的文件类型:{ext}")


def split_text(text: str, chunk_size: int = 800, overlap: int = 120):
    chunks = []
    start = 0

    while start < len(text):
        end = start + chunk_size
        chunk = text[start:end]

        if chunk.strip():
            chunks.append(chunk.strip())

        start = end - overlap

    return chunks

5. rag.py

该文件用于实现知识库向量化与检索。

import os
import chromadb
from sentence_transformers import SentenceTransformer

CHROMA_DIR = os.getenv("CHROMA_DIR", "./data/chroma")

client = chromadb.PersistentClient(path=CHROMA_DIR)
collection = client.get_or_create_collection(name="office_knowledge")

embedding_model = SentenceTransformer("BAAI/bge-small-zh-v1.5")


def embed_texts(texts):
    vectors = embedding_model.encode(texts, normalize_embeddings=True)
    return vectors.tolist()


def add_documents(chunks, filename: str):
    embeddings = embed_texts(chunks)

    ids = []
    metadatas = []

    for index, chunk in enumerate(chunks):
        ids.append(f"{filename}_{index}")
        metadatas.append({
            "filename": filename,
            "chunk_index": index
        })

    collection.add(
        ids=ids,
        documents=chunks,
        embeddings=embeddings,
        metadatas=metadatas
    )

    return len(chunks)


def search_documents(query: str, top_k: int = 5):
    query_embedding = embed_texts([query])[0]

    result = collection.query(
        query_embeddings=[query_embedding],
        n_results=top_k
    )

    documents = result.get("documents", [[]])[0]
    metadatas = result.get("metadatas", [[]])[0]

    return [
        {
            "content": doc,
            "metadata": meta
        }
        for doc, meta in zip(documents, metadatas)
    ]

6. main.py

这是后端主入口。

import os
import shutil
from fastapi import FastAPI, UploadFile, File
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel

from llm import chat_with_ollama
from rag import add_documents, search_documents
from document_loader import load_document, split_text

UPLOAD_DIR = os.getenv("UPLOAD_DIR", "./data/uploads")
os.makedirs(UPLOAD_DIR, exist_ok=True)

app = FastAPI(title="AI办公私有化平台")

app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)


class ChatRequest(BaseModel):
    message: str


class RagRequest(BaseModel):
    question: str
    top_k: int = 5


@app.get("/")
def health_check():
    return {
        "status": "ok",
        "message": "AI办公私有化平台运行中"
    }


@app.post("/api/chat")
def chat(req: ChatRequest):
    prompt = f"""
你是企业内部AI办公助手,请使用专业、准确、简洁的方式回答用户问题。

用户问题:
{req.message}
"""
    answer = chat_with_ollama(prompt)

    return {
        "answer": answer
    }


@app.post("/api/upload")
async def upload_file(file: UploadFile = File(...)):
    file_path = os.path.join(UPLOAD_DIR, file.filename)

    with open(file_path, "wb") as buffer:
        shutil.copyfileobj(file.file, buffer)

    text = load_document(file_path)
    chunks = split_text(text)

    count = add_documents(chunks, file.filename)

    return {
        "filename": file.filename,
        "chunks": count,
        "message": "文档已成功入库"
    }


@app.post("/api/rag")
def rag_chat(req: RagRequest):
    docs = search_documents(req.question, req.top_k)

    context = "\n\n".join([
        f"资料来源:{item['metadata'].get('filename')}\n内容:{item['content']}"
        for item in docs
    ])

    prompt = f"""
你是企业内部知识库问答助手。
请严格基于以下资料回答用户问题。
如果资料中没有相关信息,请明确说明“知识库中未找到相关依据”,不要编造答案。

【知识库资料】
{context}

【用户问题】
{req.question}

请输出:
1. 简洁答案;
2. 依据说明;
3. 引用的资料来源。
"""

    answer = chat_with_ollama(prompt)

    return {
        "answer": answer,
        "references": docs
    }

七、前端页面源码

为了方便演示,下面提供一个极简HTML页面。生产环境可以替换成Vue或React。

index.html




  
  AI办公私有化平台
  


AI办公私有化平台

一、普通AI对话

二、上传知识库文档


三、知识库问答

nginx.conf

server {
    listen 80;

    server_name localhost;

    location / {
        root /usr/share/nginx/html;
        index index.html;
    }

    location /api/ {
        proxy_pass http://backend:8000/api/;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

八、部署步骤

1. 安装Docker和Docker Compose

在Linux服务器上执行:

curl -fsSL https://get.docker.com | bash
systemctl enable docker
systemctl start docker

安装Docker Compose插件:

docker compose version

如果未安装,可根据系统版本安装对应插件。

2. 启动服务

进入项目目录:

cd ai-office-private
docker compose up -d

查看服务状态:

docker ps

3. 拉取本地模型

进入Ollama容器:

docker exec -it ai-office-ollama bash

拉取模型:

ollama pull qwen2.5:7b

如果服务器GPU资源充足,可以使用更大的模型,例如:

ollama pull qwen2.5:14b

如果服务器资源有限,可以使用轻量模型:

ollama pull qwen2.5:3b

4. 访问系统

浏览器访问:

http://服务器IP:8080

后端接口地址:

http://服务器IP:8000

Ollama接口地址:

http://服务器IP:11434

九、服务器配置建议

AI办公私有化部署对硬件有一定要求,尤其是大模型推理。

1. 小型团队配置

适合10到30人内部试用:

CPU:8核以上
内存:32GB
GPU:可选,推荐RTX 3060 12GB或以上
硬盘:500GB SSD
模型:Qwen2.5 7B / DeepSeek蒸馏7B

2. 中型企业配置

适合50到300人使用:

CPU:16核以上
内存:64GB以上
GPU:RTX 4090 24GB / A5000 / L40S
硬盘:1TB NVMe SSD
模型:Qwen2.5 14B / 32B量化模型

3. 大型企业配置

适合高并发、多部门、多知识库:

CPU:32核以上
内存:128GB以上
GPU:A100 / H100 / L40S多卡
硬盘:企业级NVMe SSD
推理框架:vLLM
向量库:Milvus / pgvector
数据库:PostgreSQL高可用集群

如果只是文档问答和办公写作,7B到14B模型基本可以满足大多数需求。如果需要复杂推理、代码生成、合同审查等,则建议选择更大模型或接入混合模型路由。


十、生产环境优化建议

上面的源码可以实现最小可用系统,但如果要用于企业生产环境,还需要做进一步优化。

1. 用户认证与权限控制

建议增加以下能力:

  • 用户登录;
  • JWT Token鉴权;
  • 部门权限;
  • 知识库权限;
  • 管理员角色;
  • 操作审计。

例如,销售部门只能访问销售资料,财务部门只能访问财务制度,法务部门可以访问合同模板和法律文档。

2. 文档权限隔离

知识库不仅要能检索,还要能控制“谁可以检索”。可以在向量数据metadata中增加字段:

{
    "filename": "财务报销制度.pdf",
    "department": "finance",
    "permission": "finance_only"
}

查询时根据当前用户角色过滤:

where={
    "department": user.department
}

这样可以避免越权访问。

3. 日志与审计

企业内部AI系统必须具备日志能力,包括:

  • 用户提问内容;
  • 模型返回内容;
  • 检索到的知识库片段;
  • 用户上传的文件;
  • 操作时间;
  • IP地址;
  • 用户ID。

日志的价值不仅在于安全审计,也可以用于后续分析哪些知识被频繁查询,从而优化知识库建设。

4. Prompt模板管理

不同办公场景可以配置不同Prompt模板,例如:

周报生成模板

你是专业的企业办公助手。
请根据用户提供的工作内容,生成一份结构清晰的周报。
要求包括:
1. 本周完成工作;
2. 重点成果;
3. 存在问题;
4. 下周计划;
5. 需要协同的事项。

合同审查模板

你是企业法务审查助手。
请从以下角度审查合同:
1. 付款条款;
2. 违约责任;
3. 交付周期;
4. 保密条款;
5. 争议解决方式;
6. 对我方不利的风险点。

通过模板化管理,可以让AI输出更加稳定,也方便企业沉淀办公方法论。

5. 流式输出

当前示例中采用非流式返回,用户需要等待模型完整生成。生产环境中建议改成流式输出,提升体验。

FastAPI可以使用 StreamingResponse,Ollama也支持流式接口。这样用户可以像使用ChatGPT一样,看到答案逐字生成。

6. 多模型路由

不同任务可以使用不同模型:

场景 推荐模型
日常写作 7B模型
知识库问答 7B或14B模型
代码生成 Code模型
合同审查 更大参数模型
快速分类 小模型
向量化 BGE / M3E

通过多模型路由,可以平衡效果、成本和响应速度。

7. 文档解析增强

真实企业文档格式复杂,可能包含:

  • 表格;
  • 图片;
  • 扫描件;
  • 页眉页脚;
  • 水印;
  • 多栏排版;
  • 附件;
  • 复杂编号。

因此可以增加:

  • OCR识别;
  • 表格解析;
  • 版面分析;
  • 文档去噪;
  • Excel结构化解析;
  • 图片内容识别。

对于扫描PDF,可以接入PaddleOCR或其他OCR服务。


十一、安全与合规策略

私有化部署的核心价值之一就是安全,但“部署在内网”并不等于安全。建议从以下几个方面建设。

1. 网络隔离

  • AI服务部署在内网;
  • 不开放不必要端口;
  • Ollama服务不直接暴露公网;
  • 前端通过网关访问后端;
  • 后端再访问模型服务和数据库。

2. 数据加密

  • 数据库存储加密;
  • 文档存储加密;
  • HTTPS访问;
  • 敏感字段脱敏;
  • 定期备份。

3. 权限最小化

  • 普通用户只能访问自己的会话;
  • 部门用户只能访问部门知识库;
  • 管理员操作需要审计;
  • 禁止匿名上传敏感文档。

4. 内容安全

可以增加敏感词检测和输出过滤,例如:

  • 防止泄露客户隐私;
  • 防止输出内部机密;
  • 防止生成违规内容;
  • 对高风险问题进行拦截或转人工审核。

5. 模型幻觉控制

知识库问答场景下,应要求模型“基于资料回答”,并在Prompt中明确:

如果知识库资料中没有相关依据,请回答:知识库中未找到相关依据。
不要编造制度、条款、金额、日期、人员信息。

同时返回引用来源,让用户可以追溯答案依据。


十二、常见问题

1. 为什么文档上传后问不到答案?

可能原因包括:

  • 文档解析失败;
  • 文档内容为空;
  • 切分粒度不合理;
  • Embedding模型效果不好;
  • 问题表达与文档内容差异太大;
  • top_k设置过小;
  • 向量库没有持久化成功。

可以先打印检索结果,确认是否能召回相关片段。

2. 模型回答很慢怎么办?

可以从以下方面优化:

  • 使用GPU推理;
  • 换用更小模型;
  • 使用量化模型;
  • 改用vLLM;
  • 开启流式输出;
  • 降低上下文长度;
  • 降低top_k数量;
  • 增加缓存。

3. 是否必须使用GPU?

不是必须。小模型可以在CPU上运行,但速度较慢。如果企业希望多人同时使用,建议至少配置一张具备较大显存的GPU。

4. 私有化部署是否需要训练模型?

多数办公场景不需要重新训练。推荐优先使用RAG知识库方案,通过文档检索增强模型回答。只有在特定行业术语、特定格式输出、复杂业务流程中,才考虑微调模型。

5. 能不能接入现有OA系统?

可以。后端提供REST API后,OA系统可以直接调用。例如:

  • 在OA审批页面增加“AI总结”按钮;
  • 在会议系统中增加“生成会议纪要”;
  • 在知识库页面增加“问AI”;
  • 在邮件系统中增加“邮件润色”。

十三、后续扩展方向

这套AI办公私有化系统可以继续扩展为企业级AI中台。

1. AI Agent办公助手

可以让AI不仅回答问题,还能执行任务,例如:

  • 查询日程;
  • 创建待办;
  • 调用OA审批接口;
  • 检索CRM客户信息;
  • 生成并发送邮件;
  • 自动汇总项目进度。

2. 多知识库管理

支持按部门、项目、业务线建立知识库:

  • 公司制度知识库;
  • 财务报销知识库;
  • 法务合同知识库;
  • 产品资料知识库;
  • 技术文档知识库;
  • 客户服务知识库。

3. 企业微信/钉钉/飞书集成

将AI办公接入IM系统,可以显著提升使用率。例如员工在企业微信群中直接提问:

@AI助手 请总结一下本周项目风险

AI自动查询项目文档并返回结果。

4. 工作流自动化

结合工作流引擎,可以实现:

  • 文档上传后自动摘要;
  • 合同提交后自动风险审查;
  • 会议录音结束后自动生成纪要;
  • 客户反馈自动分类;
  • 工单自动推荐解决方案。

十四、总结

AI办公私有化部署的本质,不是简单安装一个聊天机器人,而是把大模型、企业知识库、办公流程、权限体系和数据安全结合起来,形成企业内部可控、可用、可持续迭代的智能办公平台。

本文提供了一套完整的最小可运行方案,包括:

  • Ollama本地大模型服务;
  • FastAPI后端接口;
  • Chroma向量数据库;
  • 文档上传与解析;
  • RAG知识库问答;
  • 简易前端页面;
  • Docker Compose一键部署源码。

对于中小企业,可以先基于这套方案快速搭建原型,用于知识库问答、文档摘要、办公写作等场景。对于大型企业,则可以在此基础上进一步增加权限控制、日志审计、多模型路由、流式输出、文档解析增强、统一网关和高可用架构。

最终,AI办公私有化系统的价值不只是“能聊天”,而是让企业内部知识真正流动起来,让员工更快找到答案,让管理者更快掌握信息,让组织整体效率持续提升。

目录结构
全文