184 lines
6.2 KiB
Python
184 lines
6.2 KiB
Python
import json
|
|
import os
|
|
from typing import List, Dict, Any
|
|
from pymilvus import MilvusClient, FieldSchema, CollectionSchema, DataType
|
|
from pymilvus.model.hybrid import BGEM3EmbeddingFunction
|
|
|
|
|
|
class SimpleKnowledgeBase:
|
|
"""知识库"""
|
|
|
|
def __init__(self, milvus_uri: str = "http://localhost:19530"):
|
|
self.milvus_uri = milvus_uri
|
|
self.client = MilvusClient(uri=milvus_uri)
|
|
self.embedding_function = BGEM3EmbeddingFunction(use_fp16=False, device="cpu")
|
|
self.collection_name = "text2sql_kb"
|
|
self._setup_collection()
|
|
|
|
def _setup_collection(self):
|
|
"""设置集合"""
|
|
if self.client.has_collection(self.collection_name):
|
|
self.client.drop_collection(self.collection_name)
|
|
|
|
# 定义字段
|
|
fields = [
|
|
FieldSchema(name="pk", dtype=DataType.VARCHAR, is_primary=True, auto_id=True, max_length=100),
|
|
FieldSchema(name="content", dtype=DataType.VARCHAR, max_length=4096),
|
|
FieldSchema(name="type", dtype=DataType.VARCHAR, max_length=32), # ddl, qsql, description
|
|
FieldSchema(name="dense_vector", dtype=DataType.FLOAT_VECTOR, dim=self.embedding_function.dim["dense"])
|
|
]
|
|
|
|
schema = CollectionSchema(fields, description="Text2SQL知识库")
|
|
|
|
# 创建集合
|
|
self.client.create_collection(
|
|
collection_name=self.collection_name,
|
|
schema=schema,
|
|
consistency_level="Strong"
|
|
)
|
|
|
|
# 创建索引
|
|
index_params = self.client.prepare_index_params()
|
|
index_params.add_index(
|
|
field_name="dense_vector",
|
|
index_type="AUTOINDEX",
|
|
metric_type="IP"
|
|
)
|
|
|
|
self.client.create_index(
|
|
collection_name=self.collection_name,
|
|
index_params=index_params
|
|
)
|
|
|
|
def load_data(self):
|
|
"""加载所有知识库数据"""
|
|
data_dir = os.path.join(os.path.dirname(__file__), "data")
|
|
|
|
# 加载DDL数据
|
|
ddl_path = os.path.join(data_dir, "ddl_examples.json")
|
|
if os.path.exists(ddl_path):
|
|
with open(ddl_path, 'r', encoding='utf-8') as f:
|
|
ddl_data = json.load(f)
|
|
self._add_ddl_data(ddl_data)
|
|
|
|
# 加载Q->SQL数据
|
|
qsql_path = os.path.join(data_dir, "qsql_examples.json")
|
|
if os.path.exists(qsql_path):
|
|
with open(qsql_path, 'r', encoding='utf-8') as f:
|
|
qsql_data = json.load(f)
|
|
self._add_qsql_data(qsql_data)
|
|
|
|
# 加载描述数据
|
|
desc_path = os.path.join(data_dir, "db_descriptions.json")
|
|
if os.path.exists(desc_path):
|
|
with open(desc_path, 'r', encoding='utf-8') as f:
|
|
desc_data = json.load(f)
|
|
self._add_description_data(desc_data)
|
|
|
|
# 加载集合到内存
|
|
self.client.load_collection(collection_name=self.collection_name)
|
|
print("知识库数据加载完成")
|
|
|
|
def _add_ddl_data(self, data: List[Dict]):
|
|
"""添加DDL数据"""
|
|
contents = []
|
|
types = []
|
|
|
|
for item in data:
|
|
content = f"表名: {item.get('table_name', '')}\n"
|
|
content += f"DDL: {item.get('ddl_statement', '')}\n"
|
|
content += f"描述: {item.get('description', '')}"
|
|
|
|
contents.append(content)
|
|
types.append("ddl")
|
|
|
|
self._insert_data(contents, types)
|
|
|
|
def _add_qsql_data(self, data: List[Dict]):
|
|
"""添加Q->SQL数据"""
|
|
contents = []
|
|
types = []
|
|
|
|
for item in data:
|
|
content = f"问题: {item.get('question', '')}\n"
|
|
content += f"SQL: {item.get('sql', '')}"
|
|
|
|
contents.append(content)
|
|
types.append("qsql")
|
|
|
|
self._insert_data(contents, types)
|
|
|
|
def _add_description_data(self, data: List[Dict]):
|
|
"""添加描述数据"""
|
|
contents = []
|
|
types = []
|
|
|
|
for item in data:
|
|
content = f"表名: {item.get('table_name', '')}\n"
|
|
content += f"表描述: {item.get('table_description', '')}\n"
|
|
|
|
columns = item.get('columns', [])
|
|
if columns:
|
|
content += "字段信息:\n"
|
|
for col in columns:
|
|
content += f" - {col.get('name', '')}: {col.get('description', '')} ({col.get('type', '')})\n"
|
|
|
|
contents.append(content)
|
|
types.append("description")
|
|
|
|
self._insert_data(contents, types)
|
|
|
|
def _insert_data(self, contents: List[str], types: List[str]):
|
|
"""插入数据"""
|
|
if not contents:
|
|
return
|
|
|
|
# 生成嵌入
|
|
embeddings = self.embedding_function(contents)
|
|
|
|
# 构建插入数据,每一行是一个字典
|
|
data_to_insert = []
|
|
for i in range(len(contents)):
|
|
data_to_insert.append({
|
|
"content": contents[i],
|
|
"type": types[i],
|
|
"dense_vector": embeddings["dense"][i]
|
|
})
|
|
|
|
# 插入数据
|
|
result = self.client.insert(
|
|
collection_name=self.collection_name,
|
|
data=data_to_insert
|
|
)
|
|
|
|
def search(self, query: str, top_k: int = 5) -> List[Dict[str, Any]]:
|
|
"""搜索相关内容"""
|
|
self.client.load_collection(collection_name=self.collection_name)
|
|
|
|
query_embeddings = self.embedding_function([query])
|
|
|
|
search_results = self.client.search(
|
|
collection_name=self.collection_name,
|
|
data=query_embeddings["dense"],
|
|
anns_field="dense_vector",
|
|
search_params={"metric_type": "IP"},
|
|
limit=top_k,
|
|
output_fields=["content", "type"]
|
|
)
|
|
|
|
results = []
|
|
for hit in search_results[0]:
|
|
results.append({
|
|
"content": hit["entity"]["content"],
|
|
"type": hit["entity"]["type"],
|
|
"score": hit["distance"]
|
|
})
|
|
|
|
return results
|
|
|
|
def cleanup(self):
|
|
"""清理资源"""
|
|
try:
|
|
self.client.drop_collection(self.collection_name)
|
|
except:
|
|
pass |