从零开始搭建本地安全 AI 大模型攻防知识库
字数 1758 2025-08-19 12:40:36
本地安全AI大模型攻防知识库搭建教程
1. 概述
本教程将详细介绍如何从零开始搭建一个本地大模型问答知识库,重点解决在搭建过程中遇到的各种问题及其解决方案。
1.1 搭建方案选择
目前搭建大语言问答知识库主要有三种方案:
- 微调模型(Fine-tuning):在预训练模型基础上进行特定任务的调整
- 再次训练模型(Retraining):从零开始训练模型
- 增强检索生成(RAG, Retrieval Augmented Generation):结合检索和生成的技术
推荐方案:考虑到成本和响应速度,建议使用开源本地大型语言模型结合RAG方案。经过测试,llama3:8b和qwen2:7b这类体量的模型响应速度快,适合搭建问答知识库。
2. RAG原理详解
2.1 RAG基本工作原理
RAG(检索增强生成)结合了信息检索和文本生成技术,主要步骤如下:
- 对知识库内容进行分片处理
- 使用embedding模型将分片后的知识库向量化
- 使用embedding模型将用户问题向量化
- 计算问题向量与知识库向量的相似度,找出最相似的k个结果
- 将这k个结果作为上下文放入prompt中,连同用户问题一起提交给大模型
2.2 向量相似度计算示例
import numpy as np
def cosine_similarity(a, b):
return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))
# 示例数据
context = ["北京,上海,杭州", "苹果,橘子,桃子", "太阳,月亮,星星"]
questions = ["城市", "水果", "天体"]
# 计算相似度
for i in range(3):
for j in range(3):
similar = cosine_similarity(qVector[i], vector[j])
print(f"{questions[i]}和{context[j]}的相似度为:{similar}")
2.3 RAG的必要性
- 私有数据处理:当知识库包含私有数据(不在大模型训练数据中)时,RAG是比微调更经济的方案
- 上下文长度限制:直接放入全部知识库内容会超过token限制(如qwen2:7b最大128k tokens)
- 成本效益:相比微调或重新训练,RAG成本更低,实现更快
3. 技术实现细节
3.1 向量存储与检索方案
对于大规模知识库,需要专门的向量数据库来存储和快速检索:
推荐工具:
- redis-search
- chroma
- elasticsearch
- opensearch
- lancedb
- pinecone
- qdrant
- weaviate
- zilliz
3.1.1 使用redis-search实现
import redis
from redis.commands.search.query import Query
from redis.commands.search.field import TextField, VectorField
class RedisCache:
def __init__(self, host: str = "localhost", port: int = 6379):
self.cache = redis.Redis(host=host, port=port)
# 其他方法省略...
# 初始化
redis = RedisCache()
# 定义字段
info = TextField("info")
name = "embedding"
algorithm = "HNSW"
attributes = {
"TYPE": "FLOAT32",
"DIM": DIM, # 向量维度
"DISTANCE_METRIC": "COSINE"
}
embed = VectorField(name=name, algorithm=algorithm, attributes=attributes)
# 创建索引
scheme = (info, embed)
index = redis.getSchema(self.redisIndexName)
redis.createIndex(index, scheme)
# 插入数据
def insertData(self, model):
j = 0
for file in self.filesPath:
for i in file.content:
embed = self.engine.embeddings(i, model=model)
emb = numpy.array(embed, dtype=numpy.float32).tobytes()
im = {
"info": i,
"embedding": emb,
}
name = f"{self.redisIndexName}-{j}"
j += 1
self.redis.hset(name, im)
# 查询
k = 10
base_query = f"* => [KNN {k} @embedding $query_embedding AS similarity]"
return_fields = ["info", "similarity"]
qr = self.engine.embeddings(question, model=model)
params_dict = {"query_embedding": np.array(qr, dtype=np.float32).tobytes()}
index = self.redis.getSchema(self.redisIndexName)
result = self.redis.query(index, base_query, return_fields, params_dict, k)
3.2 Prompt设计示例
prompt = [
{
"role": "user",
"content": f"""当你收到用户的问题时,请编写清晰、简洁、准确的回答。你会收到一组与问题相关的上下文,请使用这些上下文,请使用中文回答用户的提问。不允许在答案中添加编造成分,如果给定的上下文没有提供足够的信息,就回答"##no##"。不要提供与问题无关的信息,也不要重复。
> 上下文:
>>>
{context}
>>>
> 问题:
{question}"""
}
]
4. 实际应用中的难点与解决方案
4.1 难点一:大语言模型能力不足
问题表现:
- 本地小模型(如qwen2:7b)能力有限,回答质量不如GPT-4
- Embedding模型准确率低(约50-60%)
解决方案:
- 优化提问方式和上下文顺序
- 使用rerank模型提高准确率
4.1.1 Rerank模型实现
from BCEmbedding import RerankerModel
# 初始化rerank模型
rerankModel = RerankerModel(model_name_or_path="maidalun1020/bce-reranker-base_v1", local_files_only=True)
# 搜索出前20相似内容
k = 20
result = self.redis.query(index, base_query, return_fields, params_dict, k)
passages = [doc.info for doc in result.docs]
# 重打分
rerank_results = rerankModel.rerank(question, passages)
info = rerank_results["rerank_passages"]
last_result = info[:10] # 取前10高分结果
4.2 难点二:提问的复杂性
4.2.1 范围搜索性提问
问题示例:
- "2024年的文章有哪些?"
- "CTF相关的文章有哪些?"
挑战:
- 难以确定合适的k值
- 可能遗漏相关内容或浪费计算资源
解决方案:
- 设置较大的初始k值(如1000)
- 根据相似度阈值过滤结果
- 使用上下文压缩技术
4.2.2 多轮对话处理
问题示例:
用户: 2024年的文章有哪些?
助手: (列出部分文章)
用户: 还有吗?
解决方案:
- 检测低相似度分数判断为多轮对话
- 维护对话状态和已显示结果
4.3 难点三:文本处理
4.3.1 图片处理方案
- 使用OCR识别图片文字(效果有限)
- 使用llava或GPT-4等模型描述图片
- 直接返回图片链接并在prompt中说明
4.3.2 文本分片策略
问题:
- 简单按长度分片会割裂相关上下文(如代码段)
- 重叠分片效果提升有限
推荐方案:
- 根据文档结构分片(如按一级标题)
- 每个chunk包含文章基础信息
- 对长代码段特殊处理
- 针对不同格式文档设计专门分片策略
示例:
from llama_index import SimpleDirectoryReader
from llama_index.node_parser import SimpleNodeParser
documents = SimpleDirectoryReader(input_dir="./Documents").load_data()
node_parser = SimpleNodeParser.from_defaults(chunk_size=514, chunk_overlap=80)
nodes = node_parser.get_nodes_from_documents(documents)
4.4 上下文压缩技术
def compress(self, question: str, context: list[str], maxToken: int = 1024) -> list[str]:
template = f"下面将会提供问题和上下文,请判断上下文信息是否和问题相关,如果不相关,请回复##no##,如果相关,请提取出和上下文相关的内容。*注意*:请直接提取出上下文的关键内容,请*不要*自行发挥,*不要*进行任何修改或者压缩删减操作。\n\n> 问题:{question}\n> 上下文:\n>>>\n%s\n>>>"
result = []
for c in context:
qs = template % c
answer = self.engine.chat(qs)
if "##no##" not in answer:
result += [answer]
newContent = "\n".join(result)
question = f"你是一个去重机器人,下面将会提供一组上下文,请你对上下文进行去重处理。*注意*,请*不要*自行发挥,*不要*进行任何添加修改,请直接在上下文内容中进行去重。\n上下文:>>>\n{newContent}\n>>>"
answer = self.engine.chat(question)
return answer
5. 优化策略总结
- 相似度过滤:设置相似度阈值(如>0.4)和rerank分数阈值(如>0.5)
- 动态k值:根据查询类型动态调整k值大小
- 上下文压缩:去除无关内容,增加有效上下文量
- 分片优化:根据文档结构而非简单长度分片
- 多模型协作:结合embedding模型、rerank模型和生成模型
6. 参考资源
7. 结论
当前本地问答知识库的搭建仍面临诸多挑战,主要受限于大语言模型的能力。最佳实践是构建灵活框架,便于替换不同组件和模型。随着大语言模型技术的发展,未来有望实现更快速、更精准的问答系统。