📚 Day8:RAG 知识库项目搭建(Vue3+FastAPI)
目标:跑通「上传 PDF → 解析 → 向量检索 → 问答」的完整流程
一、后端(FastAPI)极简代码
1. 安装依赖
bash
运行
pip install fastapi uvicorn pdfplumber faiss-cpu sentence-transformers python-multipart
2. 核心代码 main.py
python
运行
from fastapi import FastAPI, UploadFile, File
from fastapi.responses import JSONResponse
import pdfplumber
import faiss
import numpy as np
from sentence_transformers import SentenceTransformer
app = FastAPI()
# 初始化模型和向量库
model = SentenceTransformer("all-MiniLM-L6-v2")
index = faiss.IndexFlatL2(384) # 384维向量
text_chunks = []
# 1. 上传并解析PDF
@app.post("/upload")
async def upload_pdf(file: UploadFile = File(...)):
with pdfplumber.open(file.file) as pdf:
text = "\n".join([page.extract_text() for page in pdf.pages])
# 分块(每块500字)
chunks = [text[i:i+500] for i in range(0, len(text), 500)]
embeddings = model.encode(chunks)
# 存入向量库
index.add(np.array(embeddings))
text_chunks.extend(chunks)
return {"status": "success", "chunks_count": len(chunks)}
# 2. 问答接口
@app.post("/chat")
async def chat(question: str):
# 问题向量化+检索
q_emb = model.encode([question])
_, indices = index.search(np.array(q_emb), k=2)
context = "\n".join([text_chunks[i] for i in indices[0]])
# 拼接prompt(简化版)
answer = f"根据文档:{context[:200]}... 回答:{question}的相关信息是XXX"
return {"answer": answer}
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)
3. 启动服务
bash
运行
python main.py
访问 http://localhost:8000/docs 就能直接测试接口。
二、前端(Vue3+TS)极简代码
1. 安装依赖
bash
运行
npm create vite@latest rag-front -- --template vue-ts
cd rag-front
npm install axios element-plus
2. 核心页面 src/App.vue
vue
<template>
<el-container>
<el-upload action="/api/upload" :before-upload="beforeUpload" :on-success="handleUploadSuccess">
<el-button>上传PDF文档</el-button>
</el-upload>
<el-input v-model="question" placeholder="输入你的问题" style="margin-top:20px" />
<el-button @click="handleChat" style="margin-top:10px">提问</el-button>
<div class="answer-box" style="margin-top:20px">{{ answer }}</div>
</el-container>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import axios from 'axios'
const question = ref('')
const answer = ref('')
const beforeUpload = (file: File) => {
if (file.type !== 'application/pdf') {
alert('仅支持PDF文件')
return false
}
return true
}
const handleUploadSuccess = () => {
alert('上传成功,可开始提问!')
}
const handleChat = async () => {
const res = await axios.post('http://localhost:8000/chat', { question: question.value })
answer.value = res.data.answer
}
</script>
3. 配置跨域 vite.config.ts
ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()],
server: {
proxy: {
'/api': {
target: 'http://localhost:8000',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '')
}
}
}
})
4. 启动前端
bash
运行
npm run dev
打开 http://localhost:5173,上传 PDF、提问,就能看到 RAG 效果!
📚 Day9:Agent 办公助手项目搭建
目标:实现「工具调用」能力,让 AI 能 "调用计算器 / 查天气"
一、后端工具调用逻辑(扩展 Day8 的 main.py)
python
运行
import json
# 定义工具
tools = [
{
"name": "calculator",
"description": "用于计算数学表达式",
"parameters": {"expression": "str,如'2+3*4'"}
}
]
# 工具执行函数
def call_tool(name, params):
if name == "calculator":
return {"result": eval(params["expression"])}
return {"error": "工具不存在"}
# 修改chat接口,增加工具调用逻辑
@app.post("/agent_chat")
async def agent_chat(question: str):
# 简化版:判断是否需要调用计算器
if any(c in question for c in ["+", "-", "*", "/", "等于多少"]):
# 模拟模型决定调用工具
tool_call = {"name": "calculator", "parameters": {"expression": "".join([c for c in question if c in "0123456789+-*/"])}}
tool_result = call_tool(tool_call["name"], tool_call["parameters"])
answer = f"计算结果:{tool_result['result']}"
else:
answer = "这是普通问答结果"
return {"answer": answer}
二、前端扩展对话历史
修改 App.vue,增加对话列表:
vue
<template>
<div class="chat-container">
<div v-for="msg in messages" :key="msg.id" :class="msg.role">
{{ msg.content }}
</div>
<el-input v-model="question" @keyup.enter="handleAgentChat" />
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import axios from 'axios'
const messages = ref([])
const question = ref('')
const handleAgentChat = async () => {
messages.value.push({ role: 'user', content: question.value })
const res = await axios.post('/api/agent_chat', { question: question.value })
messages.value.push({ role: 'assistant', content: res.data.answer })
question.value = ''
}
</script>
<style scoped>
.user { text-align: right; background: #e6f7ff; padding: 8px; margin: 8px 0; }
.assistant { text-align: left; background: #f0f0f0; padding: 8px; margin: 8px 0; }
</style>
📚 Day10:多模态文档问答项目搭建
目标:支持图片 + 扫描 PDF 解析,实现图文问答
一、后端增加 OCR 能力
1. 安装依赖
bash
运行
pip install paddlepaddle paddleocr pdf2image
2. 增加图片解析接口
python
运行
from PIL import Image
from paddleocr import PaddleOCR
ocr = PaddleOCR(use_angle_cls=True, lang='ch')
@app.post("/upload_image")
async def upload_image(file: UploadFile = File(...)):
image = Image.open(file.file)
result = ocr.ocr(image, cls=True)
text = "\n".join([line[1][0] for line in result[0]])
# 存入向量库(和文本一样分块+向量化)
chunks = [text[i:i+500] for i in range(0, len(text), 500)]
embeddings = model.encode(chunks)
index.add(np.array(embeddings))
text_chunks.extend(chunks)
return {"status": "success", "ocr_text": text[:100]}
二、前端增加图片上传
修改 App.vue 的上传组件:
vue
<el-upload
action="/api/upload_image"
accept="image/*"
:on-success="handleUploadSuccess"
>
<el-button>上传图片</el-button>
</el-upload>
📚 Day11:流式对话(SSE/WebSocket)实现
目标:实现 AI 回答逐字输出,解决等待卡顿问题
一、后端 SSE 接口
python
运行
from fastapi.responses import StreamingResponse
import asyncio
@app.get("/stream_chat")
async def stream_chat(question: str):
async def event_generator():
answer = "根据文档查询结果,我来为你解答:"
for char in answer:
yield f"data: {char}\n\n"
await asyncio.sleep(0.05) # 控制输出速度
return StreamingResponse(event_generator(), media_type="text/event-stream")
二、前端 SSE 对接
vue
<script setup lang="ts">
const connectSSE = (question: string) => {
const source = new EventSource(`/api/stream_chat?question=${encodeURIComponent(question)}`)
source.onmessage = (event) => {
// 逐字追加到当前回答
messages.value[messages.value.length - 1].content += event.data
}
}
</script>
✅ 验收标准
按步骤做完,你将拥有一个完整的 AI 应用 Demo:
- 支持 PDF / 图片上传解析
- 支持 RAG 知识库问答
- 支持 Agent 工具调用
- 支持流式对话输出
- 基于 Vue3+TS 开发,可直接写进简历