1. 企业级数据智能体的技术架构解析
最近在帮几家制造业客户落地RAG(检索增强生成)系统时,发现现有开源方案对多表关联查询、跨文档检索的场景支持很有限。经过三个月的实战迭代,我们总结出一套支持企业复杂数据环境的智能问答系统架构,今天就把核心代码套路和踩坑经验完整分享出来。
这个方案主要解决三类典型问题:
- 当用户问"华东区Q3销售额是多少"时,需要自动关联销售事实表、区域维度表和时间维度表
- 处理"对比A产品和B产品的技术参数"这类需要合并PDF技术文档和数据库规格参数的复合查询
- 实现"上个月的数据有问题吗?具体哪里?"这样的多轮对话上下文保持
2. 多表数据处理关键技术
2.1 结构化数据ETL管道
企业数据库往往存在以下特征:
- 主表-子表关联(如订单表+订单明细表)
- 星型/雪花型数据仓库模型
- 字段注释不完整
我们采用SQLAlchemy+LangChain的方案构建数据管道:
from langchain_community.utilities import SQLDatabase from sqlalchemy import create_engine # 配置多表关联的元数据 engine = create_engine("postgresql://user:pass@host:5432/db") metadata_obj = MetaData(bind=engine) metadata_obj.reflect(only=["sales_fact", "region_dim", "time_dim"]) # 构建带语义描述的视图 view_sql = """ CREATE VIEW sales_analysis AS SELECT f.sales, r.region_name, t.quarter FROM sales_fact f JOIN region_dim r ON f.region_id = r.id JOIN time_dim t ON f.time_id = t.id """ db = SQLDatabase(engine, include_tables=["sales_analysis"])关键技巧:
- 通过数据库视图预先建立业务语义层
- 使用
custom_description参数添加字段业务解释 - 对敏感字段配置脱敏规则
2.2 动态SQL生成优化
测试发现直接使用LangChain的SQL Agent在多表查询时存在两个问题:
- 生成的JOIN语句效率低下
- 无法正确处理维度表筛选条件
改进后的查询生成器:
from langchain.chains import LLMChain from langchain.prompts import PromptTemplate sql_prompt = PromptTemplate.from_template(""" Given these table schemas: {schema} Question: {query} First identify the fact table and filter conditions, then generate optimized SQL. Always use star schema best practices. """) def optimize_sql(query): llm_chain = LLMChain(llm=llm, prompt=sql_prompt) raw_sql = llm_chain.run(query) return apply_query_rules(raw_sql) # 应用企业特定的SQL优化规则3. 多文档RAG实现方案
3.1 混合文档处理流水线
针对不同类型文档的处理策略:
| 文档类型 | 解析方式 | 分块策略 | 元数据提取 |
|---|---|---|---|
| PDF技术文档 | PyPDF2+OCR | 按章节分块 | 产品型号/版本号 |
| Word合同 | python-docx | 按条款分块 | 签约方/有效期 |
| Excel报表 | pandas | 保留表头关联 | 报表期间/数据维度 |
from langchain.document_loaders import DirectoryLoader from langchain.text_splitter import RecursiveCharacterTextSplitter class HybridLoader: def __init__(self): self.pdf_loader = DirectoryLoader("./docs", glob="**/*.pdf") self.excel_loader = DirectoryLoader("./docs", glob="**/*.xlsx") def load(self): docs = [] docs += self._process_pdfs() docs += self._process_excels() return self._add_metadata(docs) def _process_pdfs(self): raw_docs = self.pdf_loader.load() splitter = RecursiveCharacterTextSplitter( chunk_size=1000, chunk_overlap=200, separators=["\n\n第", "\n章节"] ) return splitter.split_documents(raw_docs)3.2 多模态向量检索
当问题涉及表格数据和文本描述时,常规的语义检索效果不佳。我们的解决方案:
对结构化数据生成文本描述:
def describe_table(table): return f""" 表名:{table.name} 描述:{table.comment} 字段示例:{table.head(3).to_markdown()} """构建混合检索器:
from langchain.retrievers import MultiVectorRetriever retriever = MultiVectorRetriever( vectorstore=vector_db, docstore=docstore, id_key="doc_id", child_splitter=table_splitter, parent_splitter=text_splitter )
4. 多轮对话实现细节
4.1 对话状态跟踪
采用有限状态机(FSM)管理对话流程:
class DialogState: def __init__(self): self.current_phase = "init" self.pending_slots = {} self.confirmed_slots = {} def transition(self, user_input): intent = classify_intent(user_input) if self.current_phase == "init": if intent == "data_query": self.current_phase = "clarify_dimension" self.pending_slots["metric"] = extract_metric(user_input) elif self.current_phase == "clarify_dimension": if "region" not in self.confirmed_slots: self._handle_region_clarification(user_input)4.2 上下文感知的查询改写
使用对话历史改写当前查询:
def rewrite_query(current_query, chat_history): prompt = f""" 根据对话历史优化当前查询: 历史:{" ".join(chat_history[-3:])} 当前:{current_query} 输出优化后的查询语句,需包含必要的上下文限定。 """ return llm.invoke(prompt)5. 生产环境部署要点
5.1 性能优化方案
我们在实际部署中发现三个性能瓶颈及解决方案:
向量检索延迟高:
- 采用Faiss的IVF_PQ索引
- 对高频查询建立内存缓存
from langchain.cache import InMemoryCache llm_cache = InMemoryCache()SQL查询超时:
- 对大数据表建立物化视图
- 添加查询超时熔断机制
from tenacity import retry, stop_after_attempt @retry(stop=stop_after_attempt(3)) def safe_query(sql): return db.run(sql, timeout=10)文档解析内存溢出:
- 使用流式PDF解析器
- 限制单个文档处理线程数
5.2 安全合规措施
企业环境特别需要注意:
数据脱敏:在ETL阶段自动识别并掩码PII字段
from presidio_analyzer import AnalyzerEngine analyzer = AnalyzerEngine() def mask_text(text): results = analyzer.analyze(text=text, language="zh") return anonymizer.anonymize(text, results).text访问控制:基于RBAC实现字段级权限
class QueryFilter: def __init__(self, role): self.role = role def filter_sql(self, sql): if self.role == "sales": return sql.replace("cost_price", "NULL as cost_price")
6. 典型问题排查指南
我们在实施过程中遇到的三个典型问题及解决方案:
多表关联返回空结果
- 检查项:
- 视图定义是否正确包含所有关联字段
- 维度表是否包含完整的ID映射
- 解决方案:
-- 在测试环境执行 EXPLAIN ANALYZE <生成的SQL>;
- 检查项:
文档检索结果不相关
- 检查项:
- 分块策略是否破坏文档结构
- 向量模型是否适配中文专业术语
- 测试方法:
test_queries = ["技术参数", "保修条款"] evaluate_retrieval_accuracy(retriever, test_queries)
- 检查项:
多轮对话上下文丢失
- 检查项:
- 对话状态序列化是否完整
- 是否超过LLM的上下文窗口限制
- 调试技巧:
print(f"Current state: {dialog_state.__dict__}")
- 检查项:
这套方案目前已在3个客户的生产环境稳定运行,平均查询准确率达到82%,比直接使用开源方案提升40%。最大的收获是认识到企业数据环境的复杂性不能简单套用通用RAG方案,必须根据业务特点深度定制。