
从“写代码”到“描述意图”,AI 辅助开发正在重塑我们构建智能应用的方式。本文以构建一个生产级 RAG 知识库问答系统为例,深度剖析 Vibe Coding 理念下的 AI 应用定制化全流程,涵盖技术选型、链式编排、性能调优与部署策略。
2025 年初,Andrej Karpathy 提出 Vibe Coding 一词,迅速在开发者社区引发热议。它描述的是一种全新的编程形态:开发者不再逐行敲击语法,而是通过自然语言向 AI 描述意图、氛围和约束,由大模型生成代码,人类则负责审查、运行和迭代反馈。这并非“低代码”的翻版,而是一种人机协同的快速原型—验证—重构工作流。
与此同时,AI 应用本身正从“套壳聊天”走向深度定制化——企业需要基于私有数据、特定业务逻辑和合规要求,构建专属的智能体。两者结合,产生了奇妙的化学反应:Vibe Coding 极大降低了定制化 AI 应用的门槛,而定制化需求又倒逼开发者深入理解 RAG、Agent、微调等底层技术,从而形成“描述→生成→理解→优化”的正循环。
本文将带您从零开始,使用 Vibe Coding 风格(AI 辅助 + 人工把关)构建一个生产级智能文档问答系统。我们会重点讨论:
在通用大模型(如 GPT-4、Claude 3.5)之上构建定制化应用,必须直面以下问题:
挑战维度 | 具体表现 | 常规解法 |
|---|---|---|
知识时效与私有性 | 基础模型未包含企业最新产品手册、内部制度 | RAG(检索增强生成) |
上下文窗口限制 | 长文档无法完整放入 prompt,信息丢失 | 智能分块 + 重排序 |
推理可控性 | 模型输出幻觉、格式不统一 | 结构化输出(JSON mode)+ 约束解码 |
性能与成本 | API 调用延迟高、Token 费用膨胀 | 缓存、小模型兜底、异步批处理 |
安全与权限 | 多租户数据隔离、敏感内容过滤 | 向量库权限过滤 + 输入/输出审查 |
上述挑战要求我们在开发中不仅“描述意图”,更要对检索策略、prompt 工程、链路追踪有深刻理解。这正是 Vibe Coding 的用武之地——AI 可以帮助快速生成脚手架,但我们仍需掌握核心设计模式。
我们选择一套轻量但可扩展的“LLM 全栈”组合:
text-embedding-3-small(OpenAI)或本地 BAAI/bge-m3(通过 Ollama)选型理由:LangChain 虽被诟病“抽象过多”,但其回调机制和 LCEL(LangChain Expression Language)非常适合定制化流程的快速迭代。Qdrant 的 gRPC 接口性能优越,且支持 payload 索引,便于实现多租户。
我们采用“三步走”的 Vibe Coding 循环:
这种循环不是“一键生成”,而是加速决策。例如,我们可以在 10 分钟内获得一个可运行的 RAG 链原型,再用 2 小时打磨成生产级代码。
┌─────────────┐ ┌─────────────────┐ ┌──────────────┐
│ 用户输入 │────▶│ FastAPI 网关 │────▶│ 检索链 │
│ (query) │ │ + 身份认证 │ │ (LCEL) │
└─────────────┘ └─────────────────┘ └──────┬───────┘
│
▼
┌──────────────────┐
│ 向量检索(Qdrant)│
│ + 重排序 │
└────────┬─────────┘
│
▼
┌──────────────────┐
│ LLM 生成 │
│ + 引用标注 │
└────────┬─────────┘
│
▼
┌──────────────────┐
│ 流式响应 │
└──────────────────┘我们使用一份虚构的《产品售后服务手册》(PDF),首先解析为 Markdown,然后进行语义分块。
关键代码(分块 + 嵌入):
from langchain_community.document_loaders import PyPDFLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_openai import OpenAIEmbeddings
from langchain_qdrant import QdrantVectorStore
from qdrant_client import QdrantClient
import os
# 1. 加载
loader = PyPDFLoader("service_manual.pdf")
docs = loader.load()
# 2. 智能分块(保留标题层级)
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=800,
chunk_overlap=150,
separators=["\n\n", "\n", "。", "!", "?", ";", ",", " ", ""],
keep_separator=True,
)
chunks = text_splitter.split_documents(docs)
# 3. 添加元数据(用于后续过滤)
for i, chunk in enumerate(chunks):
chunk.metadata["chunk_id"] = i
chunk.metadata["source"] = "service_manual_v2"
# 4. 初始化 Qdrant(本地运行)
client = QdrantClient(host="localhost", port=6333)
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
vector_store = QdrantVectorStore.from_documents(
documents=chunks,
embedding=embeddings,
client=client,
collection_name="service_kb",
batch_size=64,
)
print(f"已索引 {len(chunks)} 个片段")Vibe Coding 要点:这里我们让 AI 生成分块参数(chunk_size, overlap),但人工根据文档平均段落长度调整为 800/150,并加入了keep_separator保留标点,确保语义完整。
LangChain 的 LCEL 让我们以声明式方式构建检索-生成管道。核心组件包括:
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser, JsonOutputParser
from langchain_openai import ChatOpenAI
from langchain_core.runnables import RunnablePassthrough, RunnableLambda
from operator import itemgetter
# 定制化 prompt 模板(强调引用来源)
prompt = ChatPromptTemplate.from_messages([
("system", """你是一位专业的售后顾问。请基于以下上下文片段回答用户问题。
如果上下文不足,请明确告知“根据现有资料无法回答”。
在回答末尾,必须列出所引用的片段编号(如 [1][3])。
上下文:
{context}"""),
("human", "{question}")
])
# 检索器(返回 top-5,且过滤 product_line="A系列")
retriever = vector_store.as_retriever(
search_type="similarity",
search_kwargs={"k": 5, "filter": {"product_line": "A系列"}}
)
# 重排序函数(使用交叉编码器)
from sentence_transformers import CrossEncoder
reranker = CrossEncoder("BAAI/bge-reranker-v2-m3")
def rerank_documents(query, docs):
pairs = [[query, doc.page_content] for doc in docs]
scores = reranker.predict(pairs)
sorted_docs = sorted(zip(docs, scores), key=lambda x: x[1], reverse=True)
return [doc for doc, _ in sorted_docs[:3]] # 保留前3
# LCEL 链
def format_docs(docs):
return "\n\n".join(f"[{i+1}] {doc.page_content}" for i, doc in enumerate(docs))
chain = (
{
"context": (itemgetter("question")
| retriever
| RunnableLambda(lambda docs: rerank_documents(query, docs))
| format_docs),
"question": itemgetter("question")
}
| prompt
| ChatOpenAI(model="gpt-4o-mini", temperature=0.1)
| JsonOutputParser() # 强制输出 { "answer": "...", "citations": [...] }
)
# 异步调用
async def answer_question(question: str):
result = await chain.ainvoke({"question": question})
return result设计决策:使用 RunnableLambda 包装重排序逻辑,保持链式调用的可读性。同时借助 JsonOutputParser 确保模型输出结构化,便于前端渲染。
将链封装为异步端点,支持流式响应(SSE)和普通 JSON。
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from sse_starlette.sse import EventSourceResponse
import json
app = FastAPI(title="定制化知识库问答")
class QueryRequest(BaseModel):
question: str
stream: bool = False
@app.post("/ask")
async def ask_endpoint(req: QueryRequest):
try:
if req.stream:
# 流式生成(使用 async for 迭代 token)
async def event_generator():
async for chunk in chain.astream({"question": req.question}):
yield {"data": json.dumps(chunk)}
return EventSourceResponse(event_generator())
else:
result = await chain.ainvoke({"question": req.question})
return result
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))同时添加健康检查、版本号、CORS 中间件等基础配置,用 @app.on_event("startup") 预热模型和向量连接。
为了快速验证,我们使用 Streamlit 编写一个 50 行的界面,支持上传新文档(触发重新索引)和提问。
import streamlit as st
import requests
st.set_page_config(page_title="售后智能问答")
st.title("📚 定制化知识库")
if "messages" not in st.session_state:
st.session_state.messages = []
for msg in st.session_state.messages:
with st.chat_message(msg["role"]):
st.markdown(msg["content"])
if prompt := st.chat_input("请输入您的问题"):
st.session_state.messages.append({"role": "user", "content": prompt})
with st.chat_message("user"):
st.markdown(prompt)
with st.chat_message("assistant"):
resp = requests.post("http://localhost:8000/ask", json={"question": prompt})
data = resp.json()
st.markdown(data["answer"])
st.caption(f"引用: {', '.join(data.get('citations', []))}")
st.session_state.messages.append({"role": "assistant", "content": data["answer"]})对于高频问题(如“保修期多久?”),使用 语义缓存 避免重复检索和 LLM 调用。我们采用 langchain.cache 配合 Redis:
from langchain.globals import set_llm_cache
from langchain.cache import RedisCache
import redis
redis_client = redis.Redis(host="localhost", port=6379)
set_llm_cache(RedisCache(redis_client))同时可以在检索层添加 Qdrant 的 scroll 预取,减少网络往返。
整个链使用 async/await,FastAPI 配合 uvicorn 多 worker,确保高并发下吞吐量。注意重排序器 CrossEncoder 是 CPU 密集操作,应使用 run_in_executor 避免阻塞事件循环:
import asyncio
from concurrent.futures import ThreadPoolExecutor
executor = ThreadPoolExecutor(max_workers=4)
async def rerank_documents_async(query, docs):
loop = asyncio.get_event_loop()
return await loop.run_in_executor(executor, rerank_documents, query, docs)通过 payload 中的 tenant_id 过滤,确保不同客户数据隔离。在检索时添加 filter 参数,同时配合 API 网关的 JWT 解析。
集成 LangSmith 或 Arize 进行 trace 记录,观察每个步骤的耗时和 token 消耗。设置告警规则:当检索空结果比例 > 10% 或平均延迟 > 3s 时触发。
RAG 系统的评估不能仅靠主观感受,我们引入以下指标:
ragas 库的 ContextRelevancy 指标我们准备 200 条测试集(包含问题、标准答案、支持文档),运行评估脚本:
from ragas import evaluate
from ragas.metrics import faithfulness, answer_relevancy, context_relevancy
dataset = ... # 包含 question, answer, contexts, ground_truth
result = evaluate(dataset, metrics=[faithfulness, answer_relevancy, context_relevancy])
print(result)根据评估结果调整分块大小、检索 k 值和重排序阈值。Vibe Coding 在这里表现为:让 AI 根据评估报告自动建议调参方向,我们人工实验验证。
使用 Docker Compose 编排所有服务:
version: '3.8'
services:
qdrant:
image: qdrant/qdrant:latest
ports:
- "6333:6333"
volumes:
- qdrant_storage:/qdrant/storage
redis:
image: redis:7-alpine
ports:
- "6379:6379"
api:
build: ./api
ports:
- "8000:8000"
environment:
- OPENAI_API_KEY=${OPENAI_API_KEY}
- QDRANT_HOST=qdrant
- REDIS_HOST=redis
depends_on:
- qdrant
- redis
command: uvicorn main:app --host 0.0.0.0 --port 8000 --workers 4
streamlit:
build: ./frontend
ports:
- "8501:8501"
depends_on:
- api生产环境建议使用 Kubernetes,配置 HPA(Horizontal Pod Autoscaler)基于 CPU/自定义指标弹性伸缩。
通过本文,我们完整走过了 Vibe Coding 理念下的定制化 AI 应用开发之旅。关键收获:
未来,随着 Agentic 工作流和多模态模型的成熟,Vibe Coding 将扩展至更复杂的场景——例如自动生成测试用例、自动修复 bug、甚至自我演化架构。但不变的是,开发者始终要扮演“架构师”与“评估者”的角色,用批判性思维驾驭 AI 的生成能力。
思考题:如果让您用 Vibe Coding 方式构建一个多 Agent 协作系统,您会如何拆分任务和定义 Agent 间的通信协议?欢迎在评论区交流。
本文所有代码已开源(附 GitHub 链接),可在此基础上快速搭建您自己的定制化知识库。技术迭代日新月异,愿我们始终保持好奇,Vibe on!
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。