想飞的鱼 Java Dev Engineer

【AI学习路线 12】AI项目实战 - 从零构建完整应用

2026-04-10

学习顺序说明:本文是AI学习路线的第12篇,也是最终篇,建议按顺序学习:

  • … → 11 AI Agent → 12 项目实战(本文)

本文将综合运用前面学习的知识,从零构建一个完整的AI应用项目。

项目概述

项目目标

构建一个智能文档助手系统,具备以下功能:

  • 文档上传和解析
  • 基于RAG的智能问答
  • 多轮对话记忆
  • Web界面和API接口

技术架构

┌─────────────────────────────────────────────────────────────┐
│                      前端 (Vue/React)                        │
├─────────────────────────────────────────────────────────────┤
│                      API层 (FastAPI)                         │
├─────────────────────────────────────────────────────────────┤
│  文档处理服务  │  RAG检索服务  │  对话服务  │  向量数据库   │
├─────────────────────────────────────────────────────────────┤
│                      大语言模型                              │
└─────────────────────────────────────────────────────────────┘

第一部分:项目结构

1.1 目录结构

ai-doc-assistant/
├── backend/
│   ├── app/
│   │   ├── __init__.py
│   │   ├── main.py           # FastAPI入口
│   │   ├── config.py         # 配置
│   │   ├── routers/          # API路由
│   │   │   ├── chat.py
│   │   │   └── documents.py
│   │   ├── services/         # 业务逻辑
│   │   │   ├── document_processor.py
│   │   │   ├── rag_service.py
│   │   │   └── llm_service.py
│   │   └── models/           # 数据模型
│   ├── requirements.txt
│   └── Dockerfile
├── frontend/
│   ├── src/
│   └── package.json
├── docker-compose.yml
└── README.md

1.2 依赖配置

# requirements.txt
fastapi==0.104.1
uvicorn==0.24.0
python-multipart==0.0.6
langchain==0.1.0
langchain-openai==0.0.2
langchain-community==0.0.12
chromadb==0.4.22
pypdf==3.17.4
python-docx==1.1.0
openai==1.6.1
pydantic==2.5.2
pydantic-settings==2.1.0

第二部分:后端实现

2.1 配置管理

# config.py
from pydantic_settings import BaseSettings

class Settings(BaseSettings):
    # API配置
    API_TITLE: str = "AI文档助手"
    API_VERSION: str = "1.0.0"
    
    # 模型配置
    OPENAI_API_KEY: str
    LLM_MODEL: str = "gpt-4"
    EMBEDDING_MODEL: str = "text-embedding-ada-002"
    
    # 向量数据库
    CHROMA_PERSIST_DIR: str = "./chroma_db"
    
    # 文档配置
    UPLOAD_DIR: str = "./uploads"
    CHUNK_SIZE: int = 500
    CHUNK_OVERLAP: int = 50
    
    class Config:
        env_file = ".env"

settings = Settings()

2.2 文档处理服务

# services/document_processor.py
import os
from typing import List
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.document_loaders import PyPDFLoader, Docx2txtLoader
from langchain.schema import Document

class DocumentProcessor:
    def __init__(self, chunk_size: int = 500, chunk_overlap: int = 50):
        self.text_splitter = RecursiveCharacterTextSplitter(
            chunk_size=chunk_size,
            chunk_overlap=chunk_overlap,
            separators=["\n\n", "\n", "。", "!", "?", ";", " ", ""]
        )
    
    def load_document(self, file_path: str) -> List[Document]:
        """加载文档"""
        ext = os.path.splitext(file_path)[1].lower()
        
        if ext == '.pdf':
            loader = PyPDFLoader(file_path)
        elif ext in ['.docx', '.doc']:
            loader = Docx2txtLoader(file_path)
        else:
            # 默认作为文本处理
            with open(file_path, 'r', encoding='utf-8') as f:
                text = f.read()
            return [Document(page_content=text, metadata={"source": file_path})]
        
        return loader.load()
    
    def split_documents(self, documents: List[Document]) -> List[Document]:
        """切分文档"""
        return self.text_splitter.split_documents(documents)
    
    def process_file(self, file_path: str) -> List[Document]:
        """处理单个文件"""
        documents = self.load_document(file_path)
        return self.split_documents(documents)

2.3 RAG服务

# services/rag_service.py
from typing import List, Optional
from langchain_openai import OpenAIEmbeddings, ChatOpenAI
from langchain_community.vectorstores import Chroma
from langchain.chains import RetrievalQA
from langchain.prompts import PromptTemplate
from langchain.memory import ConversationBufferMemory

class RAGService:
    def __init__(self, persist_directory: str, embedding_model: str, llm_model: str):
        self.embeddings = OpenAIEmbeddings(model=embedding_model)
        self.llm = ChatOpenAI(model=llm_model, temperature=0.7)
        
        self.vectorstore = Chroma(
            persist_directory=persist_directory,
            embedding_function=self.embeddings
        )
        
        self.memory = ConversationBufferMemory(
            memory_key="chat_history",
            return_messages=True
        )
        
        self.prompt_template = """
        你是一个专业的文档助手。请根据以下上下文回答问题。
        如果上下文中没有相关信息,请明确说明,不要编造答案。
        
        上下文:
        {context}
        
        问题:{question}
        
        回答:
        """
    
    def add_documents(self, documents: List) -> None:
        """添加文档到向量库"""
        self.vectorstore.add_documents(documents)
    
    def search(self, query: str, k: int = 4) -> List:
        """检索相关文档"""
        return self.vectorstore.similarity_search(query, k=k)
    
    def query(self, question: str) -> dict:
        """问答"""
        # 检索相关文档
        docs = self.search(question)
        context = "\n\n".join([doc.page_content for doc in docs])
        
        # 构建prompt
        prompt = PromptTemplate.from_template(self.prompt_template)
        
        # 生成回答
        chain = prompt | self.llm
        response = chain.invoke({"context": context, "question": question})
        
        return {
            "answer": response.content,
            "sources": [{"content": doc.page_content[:200], 
                        "source": doc.metadata.get("source", "unknown")} 
                       for doc in docs]
        }
    
    def chat(self, question: str) -> dict:
        """带记忆的对话"""
        # 获取对话历史
        chat_history = self.memory.load_memory_variables({}).get("chat_history", [])
        
        # 检索
        docs = self.search(question)
        context = "\n\n".join([doc.page_content for doc in docs])
        
        # 生成回答
        prompt = PromptTemplate.from_template(self.prompt_template)
        chain = prompt | self.llm
        response = chain.invoke({"context": context, "question": question})
        
        # 保存到记忆
        self.memory.save_context({"input": question}, {"output": response.content})
        
        return {
            "answer": response.content,
            "sources": [{"content": doc.page_content[:200]} for doc in docs]
        }

2.4 API路由

# routers/documents.py
from fastapi import APIRouter, UploadFile, File, HTTPException
from typing import List
import os
import uuid

router = APIRouter(prefix="/documents", tags=["documents"])

@router.post("/upload")
async def upload_document(file: UploadFile = File(...)):
    """上传文档"""
    # 保存文件
    file_id = str(uuid.uuid4())
    file_path = f"./uploads/{file_id}_{file.filename}"
    
    os.makedirs("./uploads", exist_ok=True)
    
    with open(file_path, "wb") as f:
        content = await file.read()
        f.write(content)
    
    # 处理文档
    from ..services.document_processor import DocumentProcessor
    from ..services.rag_service import RAGService
    from ..config import settings
    
    processor = DocumentProcessor(
        chunk_size=settings.CHUNK_SIZE,
        chunk_overlap=settings.CHUNK_OVERLAP
    )
    
    documents = processor.process_file(file_path)
    
    # 添加到向量库
    rag = RAGService(
        persist_directory=settings.CHROMA_PERSIST_DIR,
        embedding_model=settings.EMBEDDING_MODEL,
        llm_model=settings.LLM_MODEL
    )
    rag.add_documents(documents)
    
    return {
        "file_id": file_id,
        "filename": file.filename,
        "chunks": len(documents)
    }

# routers/chat.py
from fastapi import APIRouter
from pydantic import BaseModel

router = APIRouter(prefix="/chat", tags=["chat"])

class ChatRequest(BaseModel):
    question: str
    use_memory: bool = True

class ChatResponse(BaseModel):
    answer: str
    sources: list

@router.post("/query", response_model=ChatResponse)
async def query(request: ChatRequest):
    """问答接口"""
    from ..services.rag_service import RAGService
    from ..config import settings
    
    rag = RAGService(
        persist_directory=settings.CHROMA_PERSIST_DIR,
        embedding_model=settings.EMBEDDING_MODEL,
        llm_model=settings.LLM_MODEL
    )
    
    if request.use_memory:
        result = rag.chat(request.question)
    else:
        result = rag.query(request.question)
    
    return ChatResponse(**result)

2.5 主应用

# main.py
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from .config import settings
from .routers import documents, chat

app = FastAPI(
    title=settings.API_TITLE,
    version=settings.API_VERSION
)

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

# 注册路由
app.include_router(documents.router)
app.include_router(chat.router)

@app.get("/")
async def root():
    return {"message": "AI文档助手API", "version": settings.API_VERSION}

@app.get("/health")
async def health():
    return {"status": "healthy"}

第三部分:前端实现

3.1 Vue组件示例

<!-- ChatView.vue -->
<template>
  <div class="chat-container">
    <div class="messages">
      <div v-for="msg in messages" :key="msg.id" 
           :class="['message', msg.role]">
        <div class="content"></div>
        <div v-if="msg.sources" class="sources">
          <div v-for="source in msg.sources" :key="source">
            
          </div>
        </div>
      </div>
    </div>
    
    <div class="input-area">
      <el-input
        v-model="question"
        placeholder="输入您的问题..."
        @keyup.enter="sendMessage"
      />
      <el-button type="primary" @click="sendMessage">发送</el-button>
    </div>
  </div>
</template>

<script setup>
import { ref } from 'vue'
import axios from 'axios'

const messages = ref([])
const question = ref('')

const sendMessage = async () => {
  if (!question.value.trim()) return
  
  // 添加用户消息
  messages.value.push({
    id: Date.now(),
    role: 'user',
    content: question.value
  })
  
  const userQuestion = question.value
  question.value = ''
  
  try {
    const response = await axios.post('/api/chat/query', {
      question: userQuestion,
      use_memory: true
    })
    
    messages.value.push({
      id: Date.now(),
      role: 'assistant',
      content: response.data.answer,
      sources: response.data.sources
    })
  } catch (error) {
    console.error('Error:', error)
  }
}
</script>

第四部分:部署

4.1 Docker配置

# Dockerfile
FROM python:3.10-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

EXPOSE 8000

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

4.2 Docker Compose

# docker-compose.yml
version: '3.8'

services:
  backend:
    build: ./backend
    ports:
      - "8000:8000"
    environment:
      - OPENAI_API_KEY=${OPENAI_API_KEY}
    volumes:
      - ./uploads:/app/uploads
      - ./chroma_db:/app/chroma_db
  
  frontend:
    build: ./frontend
    ports:
      - "80:80"
    depends_on:
      - backend

4.3 启动服务

# 开发环境
uvicorn app.main:app --reload

# Docker部署
docker-compose up -d

项目扩展方向

功能增强

  • 支持更多文档格式(Excel、PPT等)
  • 多用户和权限管理
  • 文档版本管理
  • 流式输出支持

性能优化

  • 文档处理异步化
  • 向量检索优化
  • 缓存机制
  • 负载均衡

高级功能

  • 多模态支持(图片理解)
  • Agent自动问答
  • 知识图谱集成

学习总结

恭喜你完成了AI学习路线的全部内容!

学习路线回顾

01 入门基础 → 02 机器学习 → 03 深度学习 → 04 NLP基础
     → 05 Transformer进阶 → 06 大模型应用 → 07 RAG系统
     → 08 AI工具链 → 09 计算机视觉 → 10 多模态大模型
     → 11 AI Agent → 12 项目实战

后续发展建议

  1. 深入专精:选择一个方向深入研究
  2. 开源贡献:参与开源项目
  3. 持续学习:关注最新论文和技术
  4. 实践项目:持续构建个人项目作品集

上一篇11 AI Agent智能体 - 自主决策与工具调用

最后更新: 2026年4月10日

本文综合运用了前面所有文档的知识,祝你在AI领域取得成功!


Similar Posts

Comments