AI搜索从入门到落地:常见问题、架构思路与代码示例
AI搜索 常见问题汇总|附源码
本文围绕“AI搜索(AI Search)”的核心概念、常见问题、技术架构、落地实践与源码示例进行系统整理,适合正在做智能问答、企业知识库、RAG应用、AI客服、文档检索增强系统的开发者阅读。
一、什么是AI搜索?
AI搜索,通常指结合大语言模型(LLM)、向量检索(Vector Search)、语义理解、重排序(Rerank)、知识增强生成(RAG)等技术的新一代搜索系统。
传统搜索通常依赖关键词匹配,例如用户搜索“如何申请报销”,系统可能只查找包含“申请”“报销”这些关键词的文档。而AI搜索更关注用户真实意图,即使文档中写的是“费用 reimbursement 流程”或“差旅费用提交规范”,系统也能理解其与“报销申请”高度相关。
简单来说,AI搜索解决的是:
- 用户问得不标准,也能搜到;
- 文档写法不统一,也能理解;
- 搜索结果不只是链接,还能直接生成答案;
- 多篇文档内容可以被整合成一个自然语言回答;
- 回答时可以附带来源,方便用户追溯依据。
二、AI搜索和传统搜索有什么区别?
| 对比项 | 传统搜索 | AI搜索 |
|---|---|---|
| 核心方式 | 关键词匹配 | 语义理解 + 向量检索 + 生成式回答 |
| 用户输入 | 依赖准确关键词 | 支持自然语言提问 |
| 结果形式 | 文档列表、网页链接 | 直接答案 + 引用来源 |
| 召回能力 | 对同义词、改写较弱 | 对相似语义理解更强 |
| 适用场景 | 商品搜索、网页搜索、日志搜索 | 企业知识库、智能客服、文档问答、研究助手 |
| 主要技术 | 倒排索引、BM25 | Embedding、RAG、LLM、Rerank |
不过,AI搜索并不是要完全取代传统搜索。实际工程中,效果最好的方案往往是混合搜索(Hybrid Search):同时使用关键词检索和向量检索,再通过重排序模型融合结果。
三、AI搜索常见架构是什么?
一个典型AI搜索系统通常包含以下模块:
用户问题
↓
问题改写 / 意图识别
↓
关键词检索 + 向量检索
↓
结果合并与去重
↓
Rerank重排序
↓
上下文拼接
↓
大模型生成答案
↓
返回答案 + 引用来源
更完整的企业级AI搜索还可能包含:
-
数据接入层
支持PDF、Word、Excel、网页、数据库、接口、Notion、飞书文档、Confluence等数据源。 -
文档解析层
将不同格式的内容解析为统一文本,并保留标题、段落、页码、表格、图片OCR等信息。 -
文本切分层
将长文档切分为适合向量化和大模型处理的文本块。 -
向量化层
使用Embedding模型将文本转换为向量。 -
索引存储层
存储向量、原文、元数据、权限信息。 -
检索层
根据用户问题召回相关文本块。 -
生成层
将检索结果作为上下文,让大模型基于资料生成答案。 -
权限与安全层
确保用户只能搜索和查看自己有权限的数据。
四、AI搜索一定要用RAG吗?
大多数企业知识库类AI搜索都需要RAG。
RAG是Retrieval-Augmented Generation,即“检索增强生成”。它的核心思想是:
不要让大模型凭记忆回答,而是先检索相关资料,再基于资料回答。
为什么需要RAG?
- 大模型训练数据不是实时的;
- 大模型不知道企业内部私有知识;
- 直接让模型回答容易产生幻觉;
- 企业回答需要可追溯来源;
- 文档经常更新,需要动态生效。
例如用户问:
公司差旅住宿标准是多少?
如果直接问通用大模型,它可能会编造一个标准。但如果通过RAG,系统会先从企业制度库中检索“差旅管理办法”“住宿费标准表”等内容,再让模型基于这些资料回答,并附带来源。
五、AI搜索为什么会回答错误?
AI搜索回答错误通常来自以下几个环节:
1. 文档本身质量差
如果原始文档内容过旧、表述混乱、格式复杂、数据冲突,那么AI搜索很难给出准确答案。
例如:
- 同一制度有多个版本;
- 文档没有标题层级;
- 扫描PDF OCR识别错误;
- 表格数据被解析成乱序文本;
- 新旧政策同时存在但未标记有效期。
2. 切分策略不合理
如果文本块切得太小,关键信息可能被拆散;如果切得太大,检索不精准,还会浪费上下文窗口。
例如一条制度包含:
员工国内出差住宿标准如下:
一线城市:600元/晚
二线城市:450元/晚
三线城市:350元/晚
如果切分时只保留了“一线城市:600元/晚”,而丢失了上文“住宿标准”,模型可能难以理解上下文。
3. 向量模型不适合
Embedding模型直接影响语义检索效果。中文场景下,如果使用对中文支持较弱的模型,召回质量会明显下降。
4. 检索召回不足
如果相关文档没有被召回,后面的LLM再强也无法正确回答。
5. Prompt设计不严谨
如果提示词没有明确要求“只能基于上下文回答”,模型可能会使用自身知识补全,从而产生幻觉。
6. 缺少Rerank
向量检索召回的结果不一定排序最优。很多时候,最相关的文本块排在第5、第8甚至更后面。如果直接取Top 3,可能漏掉关键内容。
六、AI搜索如何降低幻觉?
可以从以下几个方面优化:
1. 强约束Prompt
提示词中明确要求:
- 只能基于给定上下文回答;
- 如果上下文不足,必须回答“不知道”或“资料中未提及”;
- 不允许编造制度、金额、日期、联系人;
- 回答必须引用来源。
示例:
你是企业知识库助手。请严格基于【参考资料】回答用户问题。
如果参考资料中没有答案,请回答“根据现有资料无法确定”。
不要编造不存在的政策、金额、日期或流程。
回答后请列出引用来源。
2. 使用引用来源
让模型在答案中标明:
- 文档名称;
- 章节标题;
- 页码;
- 原文片段;
- 更新时间。
这既能增强可信度,也方便用户人工核验。
3. 增加拒答机制
对于资料不足、权限不足、问题超范围的情况,系统应明确拒答,而不是强行生成。
4. 检索结果打分过滤
如果最高相关度分数太低,可以不进入生成环节,直接提示“未找到相关资料”。
5. 引入Rerank模型
通过Rerank模型对候选文本重新排序,提升最终上下文质量。
七、AI搜索的核心技术有哪些?
1. Embedding向量化
Embedding是将文本转换为数字向量的过程。语义相近的文本在向量空间中距离更近。
例如:
- “如何申请报销”
- “费用报销流程是什么”
- “差旅费用怎么提交”
这三句话关键词不同,但语义接近,对应向量也会更接近。
2. 向量数据库
向量数据库用于存储和检索Embedding向量。常见方案包括:
- FAISS
- Milvus
- Qdrant
- Weaviate
- Chroma
- Elasticsearch Vector Search
- PostgreSQL + pgvector
小型项目可以从FAISS或Chroma开始,企业生产环境可以考虑Milvus、Qdrant或Elasticsearch。
3. BM25关键词检索
BM25是传统搜索中的经典算法。它对精确关键词、编号、专有名词、产品型号等特别有效。
例如:
- “合同编号 HT-2024-001”
- “iPhone 15 Pro”
- “GB/T 35273”
- “API 500错误”
这些场景中,关键词检索往往比纯向量检索更可靠。
4. Hybrid Search混合检索
混合检索结合BM25和向量检索,兼顾关键词准确性和语义召回能力。
常见融合方式:
- 分数加权;
- Reciprocal Rank Fusion;
- 先分别召回,再合并去重;
- 使用Rerank统一排序。
5. Rerank重排序
Rerank模型会对“问题-文档片段”进行更精细的相关性判断。相比Embedding检索,Rerank通常更准,但计算成本更高,因此一般用于对Top 20或Top 50候选结果进行二次排序。
6. LLM生成答案
最后,大语言模型根据检索到的上下文生成自然语言回答。优秀的生成阶段应做到:
- 答案简洁准确;
- 不超出资料范围;
- 结构清晰;
- 支持引用;
- 必要时给出操作步骤;
- 对不确定信息明确说明。
八、文档应该如何切分?
文档切分是AI搜索效果的关键环节之一。
常见切分方式
-
固定长度切分
按字符数或Token数切分,例如每块500字,重叠100字。 -
按标题层级切分
按一级标题、二级标题、三级标题切分,更适合制度文档、说明书。 -
按语义切分
根据段落语义和主题变化自动切分。 -
表格特殊处理
表格不能简单按行乱切,需要保留表头、单位、字段含义。
推荐策略
对于中文企业知识库,可以采用:
- 每个Chunk控制在300~800中文字符;
- 相邻Chunk保留50~150字符重叠;
- 保留文档标题、章节标题作为元数据;
- 表格转成结构化文本;
- 对特别长的制度文档按章节切分;
- 对FAQ类内容保持“问题+答案”完整。
九、AI搜索是否需要联网?
不一定。
AI搜索可以分为两类:
1. 私有知识库搜索
数据来自企业内部文档,不需要联网。系统只在内部知识库中检索并回答。
适合:
- 企业制度问答;
- 产品手册问答;
- 内部运维知识库;
- 法务合同条款检索;
- 医疗、金融、政务私有资料问答。
2. 联网搜索增强
当需要实时信息时,可以接入搜索引擎或网页爬虫。
适合:
- 新闻查询;
- 股票行情;
- 学术动态;
- 竞品分析;
- 招投标信息;
- 市场报告。
需要注意的是,联网搜索必须考虑信息可信度、来源过滤、内容时效性与合规风险。
十、AI搜索如何做权限控制?
企业级AI搜索必须处理权限问题。否则,用户可能通过自然语言问答获取本不该看到的内容。
常见权限控制方式:
1. 索引时写入权限元数据
每个文档或文本块写入:
{
"doc_id": "doc_001",
"title": "薪酬管理制度",
"department": "HR",
"acl": ["user_001", "user_002", "role_hr"]
}
2. 检索时过滤权限
用户发起查询时,系统先获取用户身份和角色,只召回其有权限访问的文本块。
3. 生成前再次校验
即使检索层过滤过,生成前也应检查上下文是否都属于用户可访问范围。
4. 答案中避免泄露无权限来源
不要让模型看到无权限内容,否则即使最后不展示来源,也可能在答案中泄露信息。
十一、AI搜索性能如何优化?
1. 缓存常见问题
对于高频问题,可以缓存最终答案或检索结果。
2. 异步处理文档入库
文档解析、切分、向量化通常较耗时,应使用队列异步处理。
3. 控制Top K
召回过多会增加Rerank和LLM成本。一般可以:
- 向量召回Top 20;
- BM25召回Top 20;
- 合并后Rerank Top 10;
- 最终取Top 3~5进入LLM。
4. 使用流式输出
答案生成可能需要数秒,流式输出能显著改善用户体验。
5. 分层检索
先检索文档级别,再检索章节级别,最后定位段落,可提升大规模知识库下的性能和准确率。
十二、AI搜索源码示例
下面给出一个简化版AI搜索系统源码,使用:
FastAPI提供接口;sentence-transformers生成Embedding;FAISS做向量检索;- 本地内存保存文本块;
- 示例中LLM部分用函数模拟,实际可替换为OpenAI、通义千问、DeepSeek、Claude、GLM等模型API。
1. 安装依赖
pip install fastapi uvicorn sentence-transformers faiss-cpu numpy
2. 项目结构
ai-search-demo/
├── main.py
├── requirements.txt
└── README.md
3. requirements.txt
fastapi
uvicorn
sentence-transformers
faiss-cpu
numpy
4. main.py
from fastapi import FastAPI
from pydantic import BaseModel
from sentence_transformers import SentenceTransformer
import faiss
import numpy as np
from typing import List, Dict, Any
app = FastAPI(title="AI Search Demo")
# 1. 加载Embedding模型
# 中文场景可以替换为更适合中文的模型,例如 bge-small-zh-v1.5
model = SentenceTransformer("sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2")
# 2. 模拟知识库文档
documents = [
{
"id": "doc_001",
"title": "差旅管理制度",
"content": "员工国内出差住宿标准如下:一线城市每晚不超过600元,二线城市每晚不超过450元,三线城市每晚不超过350元。报销时需提交发票、行程单和审批单。"
},
{
"id": "doc_002",
"title": "费用报销流程",
"content": "员工完成费用支出后,应在30天内通过财务系统提交报销申请。报销材料包括正规发票、付款凭证、业务说明及直属上级审批记录。"
},
{
"id": "doc_003",
"title": "请假管理办法",
"content": "员工申请年假需提前3个工作日在OA系统提交申请。病假需提供医院证明。连续请假超过5天的,应由部门负责人审批。"
},
{
"id": "doc_004",
"title": "信息安全规范",
"content": "员工不得将公司内部资料上传至未经批准的外部平台。涉及客户数据、合同、财务信息的文件,应按照公司数据分级制度进行保护。"
}
]
# 3. 简单切分函数
def split_text(text: str, chunk_size: int = 120, overlap: int = 30) -> List[str]:
chunks = []
start = 0
while start < len(text):
end = start + chunk_size
chunk = text[start:end]
chunks.append(chunk)
if end >= len(text):
break
start = end - overlap
return chunks
# 4. 构建文本块
chunks: List[Dict[str, Any]] = []
for doc in documents:
text_chunks = split_text(doc["content"])
for index, chunk in enumerate(text_chunks):
chunks.append({
"chunk_id": f"{doc['id']}_chunk_{index}",
"doc_id": doc["id"],
"title": doc["title"],
"content": chunk
})
# 5. 构建向量索引
texts = [item["content"] for item in chunks]
embeddings = model.encode(texts, normalize_embeddings=True)
embeddings = np.array(embeddings).astype("float32")
dimension = embeddings.shape[1]
index = faiss.IndexFlatIP(dimension)
index.add(embeddings)
class SearchRequest(BaseModel):
query: str
top_k: int = 3
class SearchResult(BaseModel):
title: str
content: str
score: float
def retrieve(query: str, top_k: int = 3) -> List[Dict[str, Any]]:
"""
根据用户问题进行向量检索
"""
query_embedding = model.encode([query], normalize_embeddings=True)
query_embedding = np.array(query_embedding).astype("float32")
scores, indices = index.search(query_embedding, top_k)
results = []
for score, idx in zip(scores[0], indices[0]):
if idx == -1:
continue
item = chunks[idx]
results.append({
"title": item["title"],
"content": item["content"],
"score": float(score),
"doc_id": item["doc_id"],
"chunk_id": item["chunk_id"]
})
return results
def build_prompt(query: str, contexts: List[Dict[str, Any]]) -> str:
"""
构造给大模型的Prompt
"""
context_text = ""
for i, item in enumerate(contexts, start=1):
context_text += f"\n[资料{i}]\n"
context_text += f"标题:{item['title']}\n"
context_text += f"内容:{item['content']}\n"
prompt = f"""
你是企业知识库问答助手。请严格基于【参考资料】回答用户问题。
要求:
1. 如果参考资料中没有答案,请回答“根据现有资料无法确定”;
2. 不要编造不存在的制度、金额、日期或流程;
3. 回答要简洁、准确、有条理;
4. 最后列出引用来源标题。
【用户问题】
{query}
【参考资料】
{context_text}
【回答】
"""
return prompt
def fake_llm_generate(prompt: str, query: str, contexts: List[Dict[str, Any]]) -> str:
"""
示例函数:模拟大模型生成。
生产环境中应替换为真实LLM API调用。
"""
if not contexts:
return "根据现有资料无法确定。"
combined_context = "\n".join([item["content"] for item in contexts])
source_titles = list(dict.fromkeys([item["title"] for item in contexts]))
# 非常简化的规则模拟,仅用于演示
if "住宿" in query or "差旅" in query:
if "一线城市" in combined_context:
return (
"根据资料,员工国内出差住宿标准为:"
"一线城市每晚不超过600元,二线城市每晚不超过450元,"
"三线城市每晚不超过350元。报销时需提交发票、行程单和审批单。\n\n"
f"引用来源:{', '.join(source_titles)}"
)
if "报销" in query:
return (
"员工完成费用支出后,应在30天内通过财务系统提交报销申请。"
"报销材料包括正规发票、付款凭证、业务说明及直属上级审批记录。\n\n"
f"引用来源:{', '.join(source_titles)}"
)
if "请假" in query or "年假" in query:
return (
"员工申请年假需提前3个工作日在OA系统提交申请。"
"病假需提供医院证明。连续请假超过5天的,应由部门负责人审批。\n\n"
f"引用来源:{', '.join(source_titles)}"
)
return (
"根据检索到的资料,暂未找到足够明确的信息回答该问题。"
f"\n\n引用来源:{', '.join(source_titles)}"
)
@app.post("/search")
def search(req: SearchRequest):
"""
只返回检索结果
"""
results = retrieve(req.query, req.top_k)
return {
"query": req.query,
"results": results
}
@app.post("/ask")
def ask(req: SearchRequest):
"""
检索 + 生成答案
"""
contexts = retrieve(req.query, req.top_k)
# 简单相关度阈值过滤
contexts = [item for item in contexts if item["score"] >= 0.3]
prompt = build_prompt(req.query, contexts)
# 实际项目中,将prompt发送给大模型
answer = fake_llm_generate(prompt, req.query, contexts)
return {
"query": req.query,
"answer": answer,
"contexts": contexts
}
@app.get("/")
def root():
return {
"message": "AI Search Demo is running.",
"endpoints": ["/search", "/ask"]
}
5. 启动服务
uvicorn main:app --reload --port 8000
6. 测试搜索接口
curl -X POST "http://127.0.0.1:8000/search" \
-H "Content-Type: application/json" \
-d '{"query":"出差住酒店可以报销多少钱?","top_k":3}'
7. 测试问答接口
curl -X POST "http://127.0.0.1:8000/ask" \
-H "Content-Type: application/json" \
-d '{"query":"一线城市出差住宿标准是多少?","top_k":3}'
可能返回:
{
"query": "一线城市出差住宿标准是多少?",
"answer": "根据资料,员工国内出差住宿标准为:一线城市每晚不超过600元,二线城市每晚不超过450元,三线城市每晚不超过350元。报销时需提交发票、行程单和审批单。\n\n引用来源:差旅管理制度",
"contexts": [
{
"title": "差旅管理制度",
"content": "员工国内出差住宿标准如下:一线城市每晚不超过600元,二线城市每晚不超过450元,三线城市每晚不超过350元。报销时需提交发票、行程单和审批单。",
"score": 0.82,
"doc_id": "doc_001",
"chunk_id": "doc_001_chunk_0"
}
]
}
十三、如何接入真实大模型?
上面的示例中,fake_llm_generate只是模拟函数。在真实项目中,可以替换为大模型API。
伪代码如下:
def call_llm(prompt: str) -> str:
response = client.chat.completions.create(
model="your-model-name",
messages=[
{"role": "system", "content": "你是严谨的企业知识库助手。"},
{"role": "user", "content": prompt}
],
temperature=0.2
)
return response.choices[0].message.content
然后在/ask接口中替换:
answer = call_llm(prompt)
建议参数:
temperature设置为0~0.3,降低随机性;- 开启流式输出,提升体验;
- 对答案做敏感词和权限校验;
- 对超长上下文做截断或摘要;
- 记录问题、召回文档、模型输出,便于评估和调优。
十四、AI搜索上线前需要做哪些评估?
AI搜索不是“接上大模型就能上线”,上线前建议至少做以下评估。
1. 召回率评估
准备一批标准问题,人工标注每个问题应该命中的文档,检查系统是否能召回。
指标包括:
- Recall@3;
- Recall@5;
- Recall@10;
- MRR;
- NDCG。
2. 答案准确率评估
人工判断模型答案是否正确、完整、是否有幻觉。
可以分为:
- 完全正确;
- 部分正确;
- 答非所问;
- 编造内容;
- 无法回答但强行回答。
3. 引用准确性评估
检查答案引用的资料是否真的支持答案内容。
4. 权限测试
使用不同角色账号测试:
- 普通员工能否看到薪酬信息;
- 部门员工能否看到其他部门文档;
- 离职员工账号是否失效;
- 外部用户是否能搜索内部资料。
5. 压力测试
评估并发能力、响应时间和模型调用成本。
十五、AI搜索常见问题汇总
Q1:AI搜索适合哪些业务场景?
适合企业知识库问答、客服机器人、法律文档检索、合同审查、产品手册问答、售前方案助手、运维故障排查、研发文档搜索、财务制度问答、HR政策咨询等场景。
Q2:数据量很少,还需要向量数据库吗?
如果数据量很小,例如只有几十篇文档,可以先使用内存向量索引或FAISS。等数据增长、需要权限管理、高可用和持久化时,再迁移到专业向量数据库。
Q3:只用大模型,不做检索可以吗?
不建议。大模型不知道企业私有数据,也无法保证信息实时准确。对于企业知识库场景,RAG几乎是必需的。
Q4:为什么搜出来的内容看起来相关,但答案还是不对?
可能是检索到了相似但不包含答案的内容,也可能是多个文本块之间存在冲突。需要优化切分、增加Rerank、提高上下文质量,并让模型严格基于资料回答。
Q5:Embedding模型怎么选?
中文场景建议选择对中文语义支持较好的模型,例如BGE中文系列、多语言Embedding模型或企业自训练模型。选择时要结合自己的测试集评估,不要只看公开榜单。
Q6:Top K设置多少合适?
没有固定答案。一般可以先向量召回Top 10~20,再Rerank取Top 3~5进入大模型。Top K太小容易漏召回,太大则增加噪声和成本。
Q7:AI搜索如何处理表格?
表格要尽量保留结构,例如把表头和每一行拼成完整描述。不要只抽取单元格文本,否则模型可能不知道数字对应的字段含义。
Q8:AI搜索可以支持图片和扫描件吗?
可以,但需要OCR或多模态模型。扫描PDF需要先进行文字识别,再进入文档解析和向量化流程。
Q9:AI搜索如何保证答案可追溯?
需要在文档入库时保存元数据,例如文档ID、标题、章节、页码、URL、更新时间。生成答案时要求模型附带引用来源。
Q10:AI搜索成本高吗?
成本主要来自Embedding、Rerank和LLM调用。优化方式包括缓存、批量向量化、本地部署Embedding模型、控制上下文长度、使用更小模型处理简单问题等。
十六、总结
AI搜索的本质不是简单地“给搜索框接一个大模型”,而是构建一套从数据接入、文档解析、文本切分、向量化、检索召回、重排序、上下文构造到答案生成的完整系统。
要做好AI搜索,关键在于:
- 数据质量要高;
- 切分策略要合理;
- 检索召回要充分;
- Rerank要提升排序质量;
- Prompt要严格约束;
- 答案要有引用来源;
- 权限控制必须前置;
- 上线前必须评估。
如果只是做Demo,几十行代码就能实现一个基础AI搜索;但如果要做企业级可用系统,则需要在准确性、稳定性、安全性、成本和可观测性上持续优化。
AI搜索不是一次性项目,而是一个持续迭代的工程体系。只有把“搜索”和“生成”两个环节都做好,才能真正让用户获得可信、准确、可追溯的智能问答体验。