Excel知识库与LLM结合的解决方案详细分析
在数据分析和智能问答系统的构建过程中,如何有效地结合结构化数据(如Excel表格)与非结构化数据(如文本文档)成为一个关键挑战。近期接触到的pandas+pandasql解决方案为此提供了一种优雅的处理方式,下面我将对这一方案进行深入分析和详细说明。
一、知识库构建的详细设计
1. Excel元数据构建
知识库中关于Excel的元数据需要包含以下详细信息:
json
{
"excel_files": [
{
"file_path": "/path/to/sales_data.xlsx",
"description": "销售数据统计表,包含2020-2024年各季度销售情况",
"sheets": [
{
"sheet_name": "2023销售数据",
"description": "2023年各产品线销售数据",
"columns": [
{
"column_name": "日期",
"description": "销售记录日期,格式为YYYY-MM-DD",
"data_type": "datetime",
"sample_values": ["2023-01-15", "2023-02-28"]
},
{
"column_name": "产品ID",
"description": "产品唯一标识符",
"data_type": "string",
"sample_values": ["P001", "P002"]
},
{
"column_name": "销售额",
"description": "单次销售金额,单位为元",
"data_type": "float",
"sample_values": [1500.00, 2400.50]
},
{
"column_name": "销售区域",
"description": "销售发生的地理区域",
"data_type": "string",
"sample_values": ["华东", "华北", "华南"]
}
],
"relationships": [
{
"description": "可通过产品ID关联产品信息表"
}
]
}
]
}
]
}
2. 向量化存储设计
为提高检索效率,可将Excel元数据转化为向量并存储在向量数据库中:
- 使用sentence-transformer模型将每个sheet的描述、列名及其描述转化为向量
- 为不同粒度的信息(文件级、sheet级、列级)分别建立索引
- 在存储向量的同时保留原始JSON结构以便检索后使用
二、查询处理流程详解
1. 用户问题解析
当用户提出问题如"2023年第一季度华东地区销售额最高的产品是什么?"时,系统进行以下处理:
python
# 伪代码示例
def process_user_query(query):
# 1. 问题理解与意图分类
query_embedding = embedding_model.encode(query)
# 2. 分别查询两个知识分支
excel_info = query_excel_metadata(query_embedding)
text_info = query_text_knowledge(query_embedding)
# 3. 路由决策
if excel_info['relevance_score'] > THRESHOLD:
# 需要查询Excel数据
sql_query = generate_sql_query(query, excel_info)
excel_result = execute_sql_query(sql_query, excel_info)
# 4. 结果综合
final_answer = generate_combined_answer(query, excel_result, text_info)
return final_answer
2. Excel元数据检索细节
检索相关Excel文件的过程包含以下步骤:
- 使用向量相似度搜索找出与问题最相关的Excel文件及sheet
- 提取相关度分数超过阈值的候选项
- 构建包含完整元信息的上下文,供LLM使用
python
def query_excel_metadata(query_embedding):
# 使用向量检索找出最相关的Excel元数据
results = vector_db.search(
collection_name="excel_metadata",
query_vector=query_embedding,
top_k=3
)
# 提取并整合相关元数据
relevant_metadata = []
for item in results:
if item.score > 0.75: # 相关度阈值
metadata = item.metadata
relevant_metadata.append({
"file_path": metadata["file_path"],
"sheet_name": metadata["sheet_name"],
"columns": metadata["columns"],
"description": metadata["description"],
"relevance_score": item.score
})
return {
"relevant_items": relevant_metadata,
"relevance_score": max([item.score for item in results]) if results else 0
}
3. SQL生成的精确实现
LLM生成SQL的过程需要特别注意以下细节:
python
def generate_sql_query(query, excel_info):
# 构建提示模板
prompt = f"""
基于用户问题:"{query}"
可用的Excel表格信息如下:
文件路径:{excel_info['relevant_items'][0]['file_path']}
表格名称:{excel_info['relevant_items'][0]['sheet_name']}
表格描述:{excel_info['relevant_items'][0]['description']}
可用列信息:
{format_columns_info(excel_info['relevant_items'][0]['columns'])}
请生成一个SQL查询语句,使用pandasql语法,查询表名应使用sheet名称。
仅返回SQL语句,不要包含任何额外说明。
"""
# 调用LLM生成SQL
sql_query = llm.generate(prompt).strip()
# SQL语法验证
validate_sql_syntax(sql_query)
return sql_query
4. SQL执行与数据获取
使用pandas和pandasql执行生成的SQL查询:
python
def execute_sql_query(sql_query, excel_info):
file_path = excel_info['relevant_items'][0]['file_path']
sheet_name = excel_info['relevant_items'][0]['sheet_name']
# 加载Excel数据
df = pd.read_excel(file_path, sheet_name=sheet_name)
try:
# 执行SQL查询
result = sqldf(sql_query, locals())
return {
"success": True,
"data": result.to_dict('records'),
"sql": sql_query
}
except Exception as e:
# 错误处理
return {
"success": False,
"error": str(e),
"sql": sql_query
}
三、系统集成与优化
1. 缓存机制
为提高性能,实现多级缓存:
- Excel文件数据缓存:避免重复读取同一文件
- SQL查询结果缓存:相似问题可复用查询结果
- 向量检索结果缓存:加速频繁查询的响应时间
python
# Excel数据缓存实现示例
excel_cache = {}
def get_excel_dataframe(file_path, sheet_name):
cache_key = f"{file_path}:{sheet_name}"
if cache_key in excel_cache:
# 检查文件是否被修改
file_mtime = os.path.getmtime(file_path)
if file_mtime <= excel_cache[cache_key]['timestamp']:
return excel_cache[cache_key]['data']
# 加载Excel并缓存
df = pd.read_excel(file_path, sheet_name=sheet_name)
excel_cache[cache_key] = {
'data': df,
'timestamp': time.time()
}
return df
2. 错误处理与回退策略
实现完善的错误处理机制:
- SQL语法错误:使用正则表达式预检查常见语法错误
- Excel文件不可访问:提供明确错误信息并从文本知识库获取相关信息
- 数据类型不匹配:在SQL生成时确保类型兼容性
- 多种回退策略:如SQL失败时尝试直接pandas操作,或降级为纯文本回答
python
def fallback_strategy(query, excel_info, error):
# 记录错误日志
log_error(query, excel_info, error)
if "syntax error" in str(error).lower():
# 尝试修复SQL语法
corrected_sql = try_correct_sql(error.sql)
if corrected_sql:
return execute_sql_query(corrected_sql, excel_info)
# 降级为直接DataFrame操作
try:
df = get_excel_dataframe(excel_info['file_path'], excel_info['sheet_name'])
# 基于问题特征执行过滤和聚合
result = direct_dataframe_operation(df, query)
return result
except:
# 最终降级为纯文本回答
return {"success": False, "fallback": "text_only"}
3. 结果呈现优化
根据查询结果类型优化呈现方式:
- 表格数据:格式化为Markdown表格
- 聚合结果:生成简洁的自然语言描述
- 时间序列数据:推荐加入可视化描述
- 异常值检测:突出显示异常数据点
python
def format_query_result(excel_result, text_info, query_type):
if not excel_result['success']:
return format_text_only_result(text_info)
data = excel_result['data']
if query_type == 'comparison':
# 创建比较分析
return format_comparison_result(data)
elif query_type == 'trend':
# 创建趋势分析
return format_trend_result(data)
elif query_type == 'ranking':
# 创建排名结果
return format_ranking_result(data)
else:
# 默认格式化
return format_default_result(data, text_info)
四、实际应用案例与代码示例
1. 完整处理流程示例
假设用户问题:"2023年第一季度销售额最高的是哪个产品,具体销售了多少?"
python
# 示例实现流程
def process_example_query():
query = "2023年第一季度销售额最高的是哪个产品,具体销售了多少?"
# 1. 知识库检索阶段
query_embedding = embedding_model.encode(query)
excel_info = query_excel_metadata(query_embedding)
text_info = query_text_knowledge(query_embedding)
# 2. SQL生成阶段
sql = """
SELECT 产品ID, 产品名称, SUM(销售额) as 总销售额
FROM `2023销售数据`
WHERE 日期 BETWEEN '2023-01-01' AND '2023-03-31'
GROUP BY 产品ID, 产品名称
ORDER BY 总销售额 DESC
LIMIT 1
"""
# 3. 数据获取阶段
df = pd.read_excel("/path/to/sales_data.xlsx", sheet_name="2023销售数据")
result = sqldf(sql, locals())
# 4. 结果整合阶段
product_id = result['产品ID'].iloc[0]
product_name = result['产品名称'].iloc[0]
total_sales = result['总销售额'].iloc[0]
# 5. 从文本知识库获取产品补充信息
product_info = get_product_info_from_text_kb(product_id)
# 6. 生成最终答复
answer = f"""
2023年第一季度销售额最高的产品是{product_name}(产品ID:{product_id}),
总销售额达到了{total_sales:,.2f}元。
{product_info['description'] if product_info else ''}
"""
return answer
2. pandasql语法细节
使用pandasql时需要注意以下特殊语法:
python
# pandasql使用细节
from pandasql import sqldf
# 表名需要使用反引号括起来,尤其是带空格或特殊字符的sheet名
sql = """
SELECT *
FROM `2023销售数据`
WHERE `销售区域` = '华东'
"""
# 表别名使用
sql = """
SELECT a.`产品ID`, a.`销售额`, b.`产品名称`
FROM `2023销售数据` a
JOIN `产品信息` b ON a.`产品ID` = b.`产品ID`
"""
# 日期处理(使用字符串比较)
sql = """
SELECT *
FROM `2023销售数据`
WHERE `日期` >= '2023-01-01' AND `日期` <= '2023-03-31'
"""
# 聚合函数
sql = """
SELECT `销售区域`, SUM(`销售额`) as 区域总销售额,
AVG(`销售额`) as 平均单次销售额,
COUNT(*) as 销售次数
FROM `2023销售数据`
GROUP BY `销售区域`
"""
# 结果限制
result = sqldf(sql, locals())
五、性能优化与扩展性
1. 大型Excel文件处理
对于超大型Excel文件(>100MB),采用以下优化策略:
- 使用
pd.read_excel(..., usecols=[])
仅加载需要的列 - 实现分块读取:
pd.read_excel(..., chunksize=10000)
- 考虑将常用Excel预先转换为SQLite数据库提高查询性能
python
def optimize_large_excel_loading(file_path, sheet_name, needed_columns, sql_query):
# 分析SQL确定所需列
required_columns = extract_columns_from_sql(sql_query)
# 仅加载必要的列
df = pd.read_excel(
file_path,
sheet_name=sheet_name,
usecols=required_columns
)
return df
2. 多Excel关联查询
实现跨Excel文件的关联查询:
python
def cross_excel_query(query, excel_infos):
# 加载相关Excel文件
dataframes = {}
for info in excel_infos:
df = pd.read_excel(info['file_path'], sheet_name=info['sheet_name'])
dataframes[info['sheet_name']] = df
# 生成跨表SQL查询
sql = generate_cross_excel_sql(query, excel_infos)
# 执行查询
result = sqldf(sql, dataframes)
return result
3. 动态适应Excel结构变化
实现Excel结构变更的自动检测与适应:
python
def verify_excel_structure(file_path, sheet_name, expected_columns):
# 读取Excel头部
df_header = pd.read_excel(file_path, sheet_name=sheet_name, nrows=0)
actual_columns = set(df_header.columns)
expected_columns_set = set(expected_columns)
# 检查列变化
missing_columns = expected_columns_set - actual_columns
new_columns = actual_columns - expected_columns_set
if missing_columns or new_columns:
# 更新知识库中的Excel元数据
update_excel_metadata(file_path, sheet_name, actual_columns)
# 记录变更日志
log_structure_change(file_path, sheet_name, missing_columns, new_columns)
return {
"structure_changed": bool(missing_columns or new_columns),
"missing_columns": list(missing_columns),
"new_columns": list(new_columns)
}
六、部署与工程实践
1. 系统架构设计
┌─────────────────┐ ┌─────────────────┐
│ │ │ │
│ 用户查询接口 │◄────────┤ 前端应用 │
│ │ │ │
└────────┬────────┘ └─────────────────┘
│
▼
┌────────────────────────────────────────────┐
│ │
│ 查询处理服务 │
│ │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ │ │ │ │
│ │ LLM服务 │◄────────┤ 查询路由器 │ │
│ │ │ │ │ │
│ └─────┬───────┘ └──────┬──────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ │ │ │ │
│ │ SQL生成器 │ │ 知识检索器 │ │
│ │ │ │ │ │
│ └─────┬───────┘ └──────┬──────┘ │
│ │ │ │
└────────┼────────────────────────┼──────────┘
│ │
▼ ▼
┌────────────────┐ ┌───────────────────┐
│ │ │ │
│ Excel数据处理器 │ │ 向量知识库 │
│ │ │ │
└────────┬───────┘ └─────────┬─────────┘
│ │
▼ ▼
┌────────────────┐ ┌───────────────────┐
│ │ │ │
│ Excel文件存储 │ │ 文本知识库 │
│ │ │ │
└────────────────┘ └───────────────────┘
2. API设计
python
# FastAPI实现示例
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
app = FastAPI()
class QueryRequest(BaseModel):
query: str
user_id: str = None
context_id: str = None
class QueryResponse(BaseModel):
answer: str
excel_data: list = None
confidence: float
sources: list = None
@app.post("/api/query", response_model=QueryResponse)
async def process_query(request: QueryRequest):
try:
# 处理查询
result = knowledge_engine.process_query(
request.query,
user_id=request.user_id,
context_id=request.context_id
)
return result
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
3. 错误监控与日志系统
python
# 日志系统设计
import logging
from elasticsearch import Elasticsearch
class QueryLogger:
def __init__(self):
self.es_client = Elasticsearch()
self.logger = logging.getLogger("query_processor")
def log_query(self, query, user_id, excel_info, sql, result):
# 详细日志记录
log_entry = {
"timestamp": datetime.now().isoformat(),
"user_id": user_id,
"query": query,
"excel_files": [info["file_path"] for info in excel_info],
"sql_generated": sql,
"result_status": "success" if result["success"] else "failure",
"execution_time_ms": result["execution_time"]
}
# 记录到Elasticsearch
self.es_client.index(index="query_logs", document=log_entry)
# 记录到本地日志
self.logger.info(f"Query processed: {query}")
if not result["success"]:
self.logger.error(f"Query failed: {query}, Error: {result.get('error')}")
七、最佳实践与注意事项
1. 元数据维护
Excel知识库的元数据维护是整个系统的基础,需要特别注意:
- 建立自动化工具定期扫描Excel文件变化并更新元数据
- 提供友好的元数据编辑界面,支持业务人员直接维护
- 实现元数据质量检查,确保描述准确、完整
- 设计版本控制机制,跟踪元数据变更历史
2. 安全性考虑
在处理Excel数据时需要注意以下安全事项:
- 实现严格的访问控制,确保只有授权用户可以查询特定Excel
- 对SQL注入进行防护,验证和清理LLM生成的SQL
- 敏感数据脱敏处理,确保返回结果不包含敏感信息
- 实现完整的操作审计日志
3. 扩展思路
该方案还可以进一步扩展:
- 支持其他结构化数据源(如CSV、数据库、API等)
- 添加数据可视化组件,自动生成图表
- 实现交互式查询精化,允许用户对初始结果进行深入探索
- 集成数据变更监控,当数据更新时主动推送相关信息
八、总结与展望
pandas+pandasql结合LLM的解决方案为企业知识库构建提供了一种既实用又高效的方法。通过将Excel文件的结构信息纳入知识库,然后利用LLM生成SQL查询并通过pandas执行,最终将结构化数据查询与非结构化文本查询结果相结合,形成了一个强大的智能问答系统。
这种方案的优势在于:
- 降低实现门槛:利用现有的pandas和SQL技术栈,降低了开发难度
- 提高查询精度:通过结构化查询提高了数据查询的准确性
- 减轻LLM负担:避免将大量表格数据直接输入LLM,节省令牌消耗
- 增强系统灵活性:易于与现有知识库系统集成
- 提升用户体验:能够精确回答涉及结构化数据的复杂问题
随着LLM能力的进一步提升和工具使用能力的增强,这种结合方式有望发展出更加智能化的企业数据应用系统,为数据驱动决策提供更强大的支持。