构建 RAG + SQL 混合查询路由 Agent:基于 LlamaIndex、Milvus 与 Cleanlab Codex 的可信输出实践
2026/9/10 3:38:27 网站建设 项目流程

构建 RAG + SQL 混合查询路由 Agent:基于 LlamaIndex、Milvus 与 Cleanlab Codex 的可信输出实践

【免费下载链接】ai-engineering-hubIn-depth tutorials on LLMs, RAGs and real-world AI agent applications.项目地址: https://gitcode.com/GitHub_Trending/ai/ai-engineering-hub

本篇技术指南基于仓库 rag-sql-router 的完整实现,讲解如何构建一个既能查询向量数据库(RAG 检索)、又能执行结构化 SQL 查询的自定义路由 Agent。文章将从环境搭建、工具封装、路由工作流到 Streamlit 交互应用逐层展开,并结合仓库源码揭示底层调用链,重点剖析 Cleanlab Codex 如何为 AI 输出提供自动响应验证与可信度评分,帮助读者掌握"Text2SQL + RAG"混合智能体从 0 到 1 的实战方案。

一、系统概览:为什么需要"路由"而不是"全都要"

传统方案往往让一个 Agent 同时绑定多个数据源,但不同类型的问题本质需要不同的查询路径:

  • 结构化数据问题(如"休斯顿的人口是多少?")需要精确的 SQL 聚合与过滤,容错空间极小;
  • 非结构化文档问题(如"这份报告里关于营收的结论是什么?")需要语义检索与片段合成,答案天然具有模糊性。

本项目的核心思想是把两类能力封装为两个独立工具——sql_tooldocument_tool,再由 LLM 依据用户问题的语义自动选择(路由)调用哪一个,从而兼顾精确性与灵活性。仓库 README.md 明确指出:该系统引导你创建一个自定义 Agent,它可以查询Vector DB 索引(RAG 检索),也可以查询独立的 SQL 查询引擎

与此同时,README 强调了整个系统最关键的组件——响应验证(Response Validation):"当所有人都在构建 Agent 时,没有人告诉你如何确保它们的输出是可靠的。" 这正是 Cleanlab Codex 在本项目中的角色:对每一次查询与回答进行自动校验,为输出提供可信度评分,并允许领域专家(SME)介入改进。

二、技术栈一览

根据 README.md 与 pyproject.toml,本项目的核心依赖如下:

组件用途仓库中的证据
LlamaIndex(llama-index>=0.12.52Agent 编排、Query Engine、Workflow 框架workflow.py
Docling(llama-index-readers-docling简化 PDF/DOCX/PPTX 文档解析tools.py
Milvus + PyMilvus(pymilvus>=2.5.14自托管向量数据库tools.py
Cleanlab Codex(cleanlab-codex>=1.0.26响应验证与可信度保障tools.py
OpenRouter AI(llama-index-llms-openrouter访问阿里 Qwen 模型app.py
SQLAlchemy(sqlalchemy>=2.0.42SQLite 连接与查询tools.py
Streamlit(streamlit>=1.47.1交互式 Web 界面app.py
HuggingFace Embeddings(llama-index-embeddings-huggingface语义向量化(BAAI/bge-small-en-v1.5)app.py

项目要求 Python>=3.12。此外还依赖nest-asyncio(让 Streamlit 环境下可运行 asyncio)、pandasplotly(数据库可视化)、torch(嵌入模型推理)。

三、环境搭建:三步跑通

3.1 启动 Milvus 向量数据库

Milvus 官方提供了 Docker 容器安装脚本。按 README.md 的操作,执行:

curl -sfL https://raw.githubusercontent.com/milvus-io/milvus/master/scripts/standalone_embed.sh -o standalone_embed.sh bash standalone_embed.sh start

脚本会以 Docker 方式拉起 Milvus standalone 实例,默认监听http://localhost:19530——这也是 tools.py 中setup_document_tool默认的milvus_uri参数值。如果 Milvus 未启动,文档向量化与索引写入将无法完成。

3.2 安装依赖

仓库使用 uv 管理依赖:

uv sync

该命令会依据 pyproject.toml 与uv.lock锁定文件创建虚拟环境并安装全部依赖。

3.3 两条运行路径

  • Jupyter Notebook 体验:运行 notebook.ipynb,该笔记本完整演示了路由(routing)、工具调用(tool calling)与响应验证(validating responses)三大主题;
  • Streamlit 应用:执行以下命令启动交互界面:
streamlit run app.py

然后在浏览器访问http://localhost:8501

四、Cleanlab Codex:让 AI 输出"可信"的关键组件

README 将 Cleanlab Codex 定位为系统最关键的组件,并总结其五大价值:

  • 自动检测:自动识别 AI 产生的不准确/无帮助响应;
  • 持续改进:领域专家(SME)无需工程介入即可直接改进响应;
  • 可信度评分:为每次响应提供可靠性指标;
  • 实时验证:实时校验查询与响应;
  • 分析:追踪改进率与响应质量随时间的变化。

4.1 在本系统中的工作流程

README 描述了四步闭环:

  1. 查询处理:你的查询由 Cleanlab Codex 自动验证;
  2. 响应验证:AI 响应被评分,衡量可靠性与准确性;
  3. SME 介入:领域专家可通过 Codex 界面改进响应;
  4. 持续学习:系统从已验证的响应中学习,服务后续查询。

4.2 源码中的验证调用链

从 tools.py 可以看到完整的落地实现。首先,create_codex_project(L24-L44)通过环境变量CODEX_API_KEY初始化客户端,创建命名项目并生成访问密钥;若缺少 API Key,则打印警告并优雅降级(Codex 验证被禁用,RAG 返回基础答案)。

document_query_tool(L191-L260)是验证的核心:

result = codex_project.validate( messages=messages, query=query, context=context_str, response=initial_response, )

validate接收完整上下文(检索到的源片段拼接)、用户问题与初始回答,返回一个包含多维度评估结果的对象。随后代码执行最终响应选择逻辑:

final_response = ( result.expert_answer if result.expert_answer and result.escalated_to_sme else ( fallback_response if result.should_guardrail else initial_response ) ) trust_score = result.model_dump()["eval_scores"]["trustworthiness"]["score"]

也就是说:若 Codex 判断需要升级给专家(escalated_to_sme)且专家已给出答案,则采用专家答案;若触发了护栏(should_guardrail),则返回兜底文案;否则保留原始 RAG 响应。可信度分数从eval_scores.trustworthiness.score中提取,与最终响应一并打包成字典返回,供上层工作流展示。

五、双工具封装:SQL 引擎与文档检索引擎的源码级解析

所有工具定义集中在 tools.py 中,并通过 LlamaIndex 的QueryEngineToolFunctionTool统一暴露给 Agent。

5.1setup_sql_tool:自然语言 → SQL

def setup_sql_tool(db_path="city_database.sqlite", table_name="city_stats"): engine = create_engine(f"sqlite:///{db_path}") sql_database = SQLDatabase(engine) sql_query_engine = NLSQLTableQueryEngine( sql_database=sql_database, tables=[table_name], ) sql_tool = QueryEngineTool.from_defaults( query_engine=sql_query_engine, name="sql_tool", description=( "Useful for translating a natural language query into a SQL query over" " a table containing: city_stats, containing the population/state of" " each city located in the USA." ), ) return sql_tool

要点:

  • 数据源为仓库自带的 city_database.sqlite,其中city_stats表包含city_namepopulationstate三列(前几条数据如 New York City / 8,336,000 / New York);
  • NLSQLTableQueryEngine负责把自然语言翻译成 SQL 并执行;
  • 工具描述(description)是路由成败的关键:LLM 正是依据这段描述判断"人口/州"类问题应交给sql_tool

5.2setup_document_tool:Docling + Milvus + Codex 三合一

def setup_document_tool(file_dir, session_id=None, milvus_uri="http://localhost:19530"): reader, node_parser = DoclingReader(), MarkdownNodeParser() loader = SimpleDirectoryReader( input_dir=file_dir, file_extractor={".pdf": reader, ".docx": reader, ".pptx": reader, ".txt": reader}, ) docs = loader.load_data() unique_collection_id = uuid.uuid4().hex collection_name = f"rag_with_sql_{unique_collection_id}" vector_store = MilvusVectorStore(uri=milvus_uri, dim=384, overwrite=True, collection_name=collection_name) storage_context = StorageContext.from_defaults(vector_store=vector_store) vector_index = VectorStoreIndex.from_documents( docs, show_progress=True, transformations=[node_parser], storage_context=storage_context, )

关键实现细节:

环节实现说明
文档解析DoclingReader统一处理 PDF/DOCX/PPTX/TXT 四种格式
节点切分MarkdownNodeParser按 Markdown 结构切分节点
向量存储MilvusVectorStore每个会话生成唯一 collection(rag_with_sql_<uuid>),overwrite=True,向量维度dim=384(与 bge-small-en-v1.5 输出维度一致)
检索策略similarity_top_k=3取最相关的 3 个片段

检索后使用自定义 QA 提示模板约束生成——要求模型"严格基于上下文作答、不引用先验知识、上下文不足时明确说明",随后进入上一节的 Codex 验证流程。最终通过FunctionTool.from_defaults封装为document_tool,其描述明确引导 LLM:"如果用户问题与 US 城市统计(人口和州)无关,请使用本文档检索工具。"

5.3 Codex 项目的会话级复用

get_or_create_codex_project(L54-L69)通过全局变量缓存 Codex 项目:同一会话内复用,新会话(不同session_id)才重新创建,避免频繁创建项目造成资源浪费。

六、RouterOutputAgentWorkflow:事件驱动的路由 Agent

路由编排位于 workflow.py,它继承 LlamaIndex 的Workflow基类,是一个基于事件(Event)与步骤(Step)的异步工作流。

6.1 事件与步骤拓扑

事件作用
InputEvent输入事件,触发 LLM 决策
GatherToolsEvent携带 LLM 选中的工具调用列表
ToolCallEvent单个工具调用任务
ToolCallEventResult单个工具调用的结果消息

四个核心步骤构成循环:

  1. prepare_chat:从StartEvent取出message,追加到聊天历史;
  2. chat:调用self.llm.achat_with_tools(self.tools, ...),把两个工具交给 LLM 决策。allow_parallel_tool_calls=True允许并行调用;若没有工具调用则直接返回StopEvent
  3. dispatch_calls:把每个ToolSelection广播为独立的ToolCallEvent(通过ctx.send_event支持并发执行);
  4. call_tool:从tools_dict找到对应工具并执行await tool.acall(**tool_call.tool_kwargs)。特别地,当工具返回包含responsetrust_score的字典时(文档工具),会将其拆解并写入ChatMessage.additional_kwargs,把可信度分数透传到上层 UI;
  5. gather:使用ctx.collect_events聚合所有工具结果,追加回聊天历史,然后重新触发InputEvent,让 Agent 基于工具结果进行下一轮推理——直到 LLM 认为无需再调用工具,输出最终答案。

从 workflow.py 的构造函数可见,工作流支持timeout(默认 10s,应用层传 120s)、disable_validationverbose等参数,并可从Settings.llm兜底获取 LLM。

工作流的运行轨迹可通过draw_all_possible_flows可视化(见 notebook.ipynb),产物即为仓库中的 workflow_all_flows.html。

七、Streamlit 应用:从配置到交互的完整闭环

app.py 提供了完整的图形化界面,结构清晰可拆解为三部分。

7.1 侧边栏配置面板

用户需在侧边栏填入两个密钥(均为密码输入框):

  • Codex API Key:写入os.environ["CODEX_API_KEY"],用于响应验证;
  • OpenRouter API Key:用于初始化 LLM。

模型初始化逻辑(L372-L383)为:

llm = OpenRouter(model="qwen/qwen-turbo", api_key=_api_key) embed_model = HuggingFaceEmbedding(model_name="BAAI/bge-small-en-v1.5")

随后赋值给全局Settings.llmSettings.embed_model,供工作流与查询引擎使用。

7.2 文档上传与工作流组装

应用支持一次上传多个文档(pdf/docx/pptx/txt),保存到临时目录后调用setup_document_tool构建文档工具。工具组装遵循"SQL 优先"原则(L670-L686):

tools = [setup_sql_tool()] # ... 若已上传文档,则追加 document_tool

之后用RouterOutputAgentWorkflow(tools=tools, verbose=False, timeout=120)初始化工作流,并缓存于st.session_state中;当密钥或文档变化时置workflow_needs_update标记以触发重建。

7.3 聊天界面与可信度可视化

process_query(L438-L520)通过asyncio.wait_for(..., timeout=60.0)包裹工作流运行,防止请求挂起。返回结果时,会从聊天历史中提取工具消息,判断走的是document_tool还是sql_tool,并展示信任度:

  • ≥70%显示 🟢 绿色;
  • ≥50%显示 🟡 黄色;
  • <50%显示 🔴 红色。

信任度按round(trust_score * 100, 1)换算为百分比。聊天区还提供"Reset Chat"按钮,一键清空历史并重建工作流。

7.4 数据库可视化看板

点击"View Database"可展开完整的数据洞察面板(render_database_tab),包括:

  • 指标卡:城市总数、总人口、州数量、平均人口;
  • 图表:Top 10 人口柱状图、州分布环形图、人口散点图(Plotly 实现);
  • Schema 展示:通过PRAGMA table_info读取表结构;
  • 自定义 SQL:内置 6 条预置查询(如 "Top 10 cities by population"、"Cities with population > 1M"),也支持手写 SQL,结果可下载为 CSV。

八、运行验证:路由决策的实际表现

notebook.ipynb 内置了两个典型测试用例,展示了路由与验证的实际效果:

用例 1:结构化问题 → 路由到 sql_tool

Calling function sql_tool with msg {'input': 'What is the population of Houston, Texas?'} Chat message: The population of Houston, Texas is 2,303,000.

city_stats表中 Houston 的记录(2,303,000)完全吻合,验证了 Text2SQL 路径的精确性。

用例 2:非结构化问题 → 路由到 document_tool

Calling function document_tool with msg {'query': 'What is the weather in California?'} Chat message: The provided context does not have enough information to answer the question about the weather in California.

由于文档上下文中没有天气信息,自定义 QA 模板的第三条规则触发——模型如实声明"上下文不足以回答",而不是编造答案。这正是响应验证与护栏机制的价值所在:宁可拒绝,也不幻觉

九、扩展思考与最佳实践

从本仓库实现可以提炼出以下可复用的工程经验:

  1. 工具描述即路由策略:LLM 完全依赖description决定调用哪个工具,务必用清晰、互斥的语言描述每个工具的适用边界(本项目中 SQL 工具明确限定于"美国城市人口/州",文档工具则声明"与城市统计无关的问题用我");
  2. 验证层与推理层解耦:RAG 检索 → Codex 验证 → 最终响应的三段式设计,让"生成"与"把关"分离,专家答案与护栏可以随时介入,无需改动检索链路;
  3. 优雅降级CODEX_API_KEY缺失时系统自动退回基础 RAG,Milvus 不可用时查询引擎同样会报错提示,保证任何环节故障都不至于静默产出低质量答案;
  4. 会话隔离:Milvus collection 与 Codex project 均按会话(uuid)隔离,避免多用户数据串扰。

需要说明的是:本项目展示的是单表city_stats的 SQL 路由与通用文档 RAG 的组合;若需扩展到多表数据库或更多知识源,可在此基础上按同样的"工具封装 + 事件工作流"模式继续叠加,而验证层(Cleanlab Codex)保持不变。

十、结语

rag-sql-router提供了一个完整可运行的"Text2SQL + RAG"混合 Agent 参考实现:LlamaIndex 负责编排与检索,Docling 负责文档解析,Milvus 提供自托管向量存储,OpenRouter 接入 Qwen 模型,而 Cleanlab Codex 则补齐了多数教程缺失的一环——输出可靠性保障。无论你是要构建企业级文档问答,还是希望在现有 RAG 系统上叠加结构化查询能力,本仓库的 tools.py、workflow.py 与 app.py 都是值得直接参考的工程范本。

【免费下载链接】ai-engineering-hubIn-depth tutorials on LLMs, RAGs and real-world AI agent applications.项目地址: https://gitcode.com/GitHub_Trending/ai/ai-engineering-hub

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询