写了21篇文章,从入门到微调,从RAG到Agent到部署。但一直有个问题:这些知识怎么串起来?
今天用一篇完整的项目实战,把前面所有知识串起来------从0到1搭一个生产级的企业知识库RAG系统。 不只是"能跑",而是"能上线"。
项目目标
搭一个企业级智能客服系统,需求:
| 需求 | 对应前面的知识 |
|---|---|
| 知识库问答 | #3 RAG + #5 Embedding |
| 多轮对话 | #12 结构化输出 |
| 订单查询 | #18 Function Calling |
| 意图路由 | #9 LangGraph |
| 效果评估 | #19 RAGAS评估 |
| 安全防护 | #20 AI安全 |
| 部署上线 | #15 Docker部署 |
| 成本优化 | #16 分层模型+缓存 |
| 效果不够时升级 | #11 GraphRAG / #21 微调 |
架构总览
less
用户请求
↓
[API网关] → 安全过滤(#20)
↓
[LangGraph工作流](#9)
├→ [意图分类] → 简单问答 → 缓存(#16)→ 直接返回
│ ↓ 未命中
│ [便宜模型](#16分层)
│
├→ [知识库问答] → Qdrant检索(#17)→ Rerank → LLM生成(#13 Prompt)
│
├→ [订单查询] → Function Calling(#18)→ 安全执行
│
└→ [拒绝回答] → 返回"无法回答"
↓
[输出过滤] → 脱敏(#20)→ 返回用户
第1步:项目初始化
arduino
mkdir smart-customer-service && cd smart-customer-service
mkdir -p app data tests config
bash
smart-customer-service/
├── app/
│ ├── main.py # FastAPI入口
│ ├── workflow.py # LangGraph工作流
│ ├── retriever.py # RAG检索
│ ├── llm.py # 分层LLM
│ ├── cache.py # 语义缓存
│ ├── safety.py # 安全过滤
│ ├── function_call.py # Function Calling
│ └── config.py # 配置
├── data/
│ ├── documents/ # 原始文档
│ ├── eval_dataset.json # 评估数据集
│ └── golden_dataset.json # 黄金数据集
├── tests/
│ ├── test_workflow.py
│ ├── test_safety.py
│ └── test_retriever.py
├── Dockerfile
├── docker-compose.yml
├── requirements.txt
└── .env.example
第2步:分层LLM(第16篇)
python
# app/llm.py
from langchain_openai import ChatOpenAI
import os
class TieredLLM:
"""分层模型策略:简单问题用便宜模型,复杂问题用贵模型"""
def __init__(self):
self.turbo = ChatOpenAI(
model="qwen-turbo",
api_key=os.getenv("DASHSCOPE_API_KEY"),
)
self.plus = ChatOpenAI(
model="qwen-plus",
api_key=os.getenv("DASHSCOPE_API_KEY"),
)
def get_llm(self, complexity: str = "medium"):
if complexity == "simple":
return self.turbo
return self.plus
第3步:RAG检索 + Rerank(第3、5、17篇)
python
# app/retriever.py
from langchain_community.vectorstores import Qdrant
from langchain_openai import OpenAIEmbeddings
class SmartRetriever:
"""智能检索:向量检索 + 相似度过滤"""
def __init__(self, qdrant_url: str, collection_name: str):
self.embeddings = OpenAIEmbeddings(model="text-embedding-v3")
self.vectorstore = Qdrant(
url=qdrant_url,
collection_name=collection_name,
embeddings=self.embeddings,
)
def retrieve(self, query: str, k: int = 5, min_score: float = 0.7, filter_dict: dict = None):
"""检索+过滤"""
# 多检索一些,再按相似度过滤
docs_with_scores = self.vectorstore.similarity_search_with_score(
query, k=k * 2, filter=filter_dict,
)
# 过滤低相关度结果
filtered = [(doc, score) for doc, score in docs_with_scores if score >= min_score]
# 取top-k
filtered = sorted(filtered, key=lambda x: x[1], reverse=True)[:k]
return [doc for doc, _ in filtered]
第4步:Function Calling(第18篇)
python
# app/function_call.py
import json
# 函数定义
TOOLS = [
{
"type": "function",
"function": {
"name": "query_order",
"description": "查询订单状态",
"parameters": {
"type": "object",
"properties": {
"order_id": {"type": "string", "description": "订单号ORD+数字"}
},
"required": ["order_id"],
},
},
},
]
# 白名单+参数校验
ALLOWED = {"query_order"}
VALIDATORS = {
"query_order": {"order_id": lambda x: x.startswith("ORD") and x[3:].isdigit()},
}
def safe_execute(name: str, args: dict) -> dict:
"""安全函数执行"""
if name not in ALLOWED:
return {"error": f"不允许调用:{name}"}
for param, validator in VALIDATORS.get(name, {}).items():
if not validator(args.get(param, "")):
return {"error": f"参数校验失败:{param}"}
# 业务逻辑
if name == "query_order":
return _query_order_impl(args["order_id"])
return {"error": "未知函数"}
def _query_order_impl(order_id: str) -> dict:
"""订单查询实现"""
# 实际项目:调订单微服务
orders = {
"ORD123": {"status": "已发货", "tracking": "SF123456", "eta": "6月20日"},
"ORD456": {"status": "待支付"},
}
return orders.get(order_id, {"error": "订单不存在"})
第5步:安全过滤(第20篇)
python
# app/safety.py
import re
class InputGuard:
INJECTION_PATTERNS = [
r"忽略.{0,5}(上面|之前|以上).{0,5}(所有|一切).{0,5}指令",
r"ignore.{0,5}(all|previous).{0,5}instructions",
r"system:",
]
def check(self, text: str) -> dict:
if len(text) > 500:
return {"safe": False, "reason": "输入过长"}
for p in self.INJECTION_PATTERNS:
if re.search(p, text, re.IGNORECASE):
return {"safe": False, "reason": "疑似Prompt注入"}
return {"safe": True}
class OutputGuard:
PII_PATTERNS = {
"手机号": (r"1[3-9]\d{9}", lambda m: m.group()[:3] + "****" + m.group()[-4:]),
}
def filter(self, text: str) -> str:
for name, (pattern, replacer) in self.PII_PATTERNS.items():
text = re.sub(pattern, replacer, text)
return text
第6步:缓存(第16篇)
python
# app/cache.py
import hashlib
import time
class SimpleCache:
def __init__(self, ttl: int = 3600):
self.cache = {}
self.ttl = ttl
def get(self, question: str):
key = hashlib.md5(question.encode()).hexdigest()
if key in self.cache:
entry = self.cache[key]
if time.time() - entry["time"] < self.ttl:
return entry["answer"]
del self.cache[key]
return None
def set(self, question: str, answer: str):
key = hashlib.md5(question.encode()).hexdigest()
self.cache[key] = {"answer": answer, "time": time.time()}
第7步:LangGraph工作流(第9篇)
python
# app/workflow.py
from typing import TypedDict, Literal, Optional
from langgraph.graph import StateGraph, START, END
class ChatState(TypedDict):
question: str
intent: str
knowledge_answer: str
order_answer: str
final_answer: str
# 节点:意图分类
def classify_intent(state: ChatState) -> ChatState:
question = state["question"]
if any(kw in question for kw in ["订单", "到哪了", "发货", "ORD"]):
return {"intent": "order"}
elif any(kw in question for kw in ["退货", "退款", "流程", "换货"]):
return {"intent": "knowledge"}
else:
return {"intent": "unknown"}
# 节点:知识库问答
def knowledge_qa(state: ChatState, retriever, llm) -> ChatState:
docs = retriever.retrieve(state["question"], k=3)
context = "\n".join([d.page_content for d in docs])
prompt = f"知识库:{context}\n\n问题:{state['question']}\n\n基于知识库回答,不确定时说'我帮您确认':"
answer = llm.invoke(prompt).content
return {"knowledge_answer": answer}
# 节点:订单查询
def order_query(state: ChatState, llm) -> ChatState:
# 用Function Calling
response = llm.invoke(state["question"], tools=TOOLS)
tool_calls = response.additional_kwargs.get("tool_calls", [])
if tool_calls:
name = tool_calls[0]["function"]["name"]
args = json.loads(tool_calls[0]["function"]["arguments"])
result = safe_execute(name, args)
answer = llm.invoke(f"查询结果:{result}\n\n回答:{state['question']}").content
else:
answer = response.content
return {"order_answer": answer}
# 节点:汇总回答
def compose_answer(state: ChatState) -> ChatState:
if state.get("knowledge_answer"):
return {"final_answer": state["knowledge_answer"]}
elif state.get("order_answer"):
return {"final_answer": state["order_answer"]}
else:
return {"final_answer": "抱歉,我暂时无法回答这个问题,建议联系人工客服。"}
# 路由
def route_intent(state: ChatState) -> Literal["knowledge", "order", "answer"]:
if state["intent"] == "knowledge":
return "knowledge"
elif state["intent"] == "order":
return "order"
return "answer"
# 构建图
def build_workflow(retriever, llm):
graph = StateGraph(ChatState)
graph.add_node("classify", lambda s: classify_intent(s))
graph.add_node("knowledge", lambda s: knowledge_qa(s, retriever, llm.plus))
graph.add_node("order", lambda s: order_query(s, llm.plus))
graph.add_node("answer", lambda s: compose_answer(s))
graph.add_edge(START, "classify")
graph.add_conditional_edges("classify", route_intent)
graph.add_edge("knowledge", "answer")
graph.add_edge("order", "answer")
graph.add_edge("answer", END)
return graph.compile()
第8步:FastAPI入口 + 全流程串联
python
# app/main.py
from fastapi import FastAPI
from pydantic import BaseModel
from app.llm import TieredLLM
from app.retriever import SmartRetriever
from app.safety import InputGuard, OutputGuard
from app.cache import SimpleCache
from app.workflow import build_workflow
app = FastAPI(title="智能客服系统")
# 初始化组件
llm = TieredLLM()
retriever = SmartRetriever("http://localhost:6333", "knowledge_base")
input_guard = InputGuard()
output_guard = OutputGuard()
cache = SimpleCache(ttl=3600)
workflow = build_workflow(retriever, llm)
class ChatRequest(BaseModel):
question: str
user_role: str = "客服"
@app.post("/chat")
async def chat(request: ChatRequest):
# 1. 输入安全检查
check = input_guard.check(request.question)
if not check["safe"]:
return {"error": check["reason"]}
# 2. 查缓存
cached = cache.get(request.question)
if cached:
return {"answer": cached, "source": "cache"}
# 3. LangGraph工作流
result = workflow.invoke({"question": request.question})
answer = result.get("final_answer", "抱歉,暂时无法回答")
# 4. 输出脱敏
safe_answer = output_guard.filter(answer)
# 5. 存缓存
cache.set(request.question, safe_answer)
return {"answer": safe_answer, "source": "workflow"}
@app.get("/health")
async def health():
return {"status": "ok"}
第9步:Docker部署(第15篇)
sql
# Dockerfile
FROM python:3.11-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"]
yaml
# docker-compose.yml
version: '3.8'
services:
app:
build: .
ports: ["8000:8000"]
env_file: .env
depends_on: [qdrant]
restart: always
qdrant:
image: qdrant/qdrant:latest
ports: ["6333:6333"]
volumes: [qdrant_data:/qdrant/storage]
volumes:
qdrant_data:
第10步:评估+持续优化(第14、19篇)
bash
# 运行测试
pytest tests/
# RAGAS评估
python -m app.evaluate --version v1.0
# 黄金数据集测试
python -m tests.test_workflow
全系列知识串联
| 文章 | 知识点 | 本项目中的应用 |
|---|---|---|
| #1-2 | 入门 | 项目的起点 |
| #3 | RAG | 核心检索能力 |
| #4 | Ollama | 本地模型备选 |
| #5 | Embedding | text-embedding-v3 |
| #6 | Agent | 验证阶段用Agent快速Demo |
| #7 | Dify | 快速验证需求 |
| #8 | Streamlit | 管理后台界面 |
| #9 | LangGraph | 意图路由工作流 |
| #10 | 多Agent | 未来扩展方向 |
| #11 | GraphRAG | 文档量大时的升级方向 |
| #12 | 结构化输出 | Function Calling参数 |
| #13 | Prompt工程 | 所有Prompt的设计规范 |
| #14 | AI测试 | 黄金数据集+LLM评估 |
| #15 | 部署 | Docker部署方案 |
| #16 | 成本优化 | 分层模型+缓存 |
| #17 | 向量数据库 | Qdrant选型 |
| #18 | Function Calling | 订单查询 |
| #19 | RAG评估 | RAGAS指标监控 |
| #20 | AI安全 | 输入输出过滤 |
| #21 | 微调 | 话术一致性升级方向 |
| #22 | 完整项目 | 串联所有知识 |
这就是从0到1搭一个生产级RAG系统的完整过程。22篇文章,从入门到上线,一条线串到底。
你的AI应用是怎么搭的?评论区聊聊 👇