端到端产品级通用智能体--JoyAgent总结之智能问数

目录

  • 前置文章
  • 背景
  • 整体架构
  • 整体流程介绍
  • 代码级别原理说明
    • nl2sql核心流程
      • 并发执行精排 + 重写
      • 流式思考过程 (_collect_think_results)
      • SQL生成 (_nl2sql_convert)
      • 关键设计价值解析
    • 开启qdrant + es的TableRag代码细节
      • 输入与预处理阶段
      • 多路检索并行阶段
      • 结果融合阶段
      • 结果筛选阶段
      • 列过滤阶段
      • 关键数据流说明

前置文章


背景

  • 在当今数据驱动的商业环境中,企业面临数据爆炸式增长与利用效率低下的矛盾。数据往往分散在多个系统中,格式复杂且专业门槛高,业务人员需依赖技术团队编写SQL或使用复杂工具进行查询,导致决策延迟、响应滞后。尤其对于非技术用户,获取实时业务洞察(如销售趋势、库存分析)变得耗时且低效,错失市场机遇。同时,随着AI与云计算的普及,市场亟需更智能、低代码的解决方案,以释放数据价值。

  • 京东云推出JoyAgent的“智能问数”功能,正是为了解决这一痛点。它通过自然语言处理(NLP)技术,让用户以日常语言提问(如“上月华东区销售额是多少?”),即可自动生成可视化报表和精准数据洞察,无需编程基础。这不仅大幅降低数据使用门槛,还加速了业务决策闭环,助力企业实现敏捷运营。在数字化转型浪潮下,该功能成为连接数据与行动的关键桥梁,赋能企业高效挖掘数据金矿,提升竞争力。


整体架构

  • 引用官方的架构图


    官方架构图.png

整体流程介绍

  • 拿一个问题举例,说明下智能问数功能如何将自然语言转换为sql, 我这边开启了qdrant + es已完整体验对应功能
  • 问genie以及给的答复


    问答.png

代码级别原理说明

  • 说明下上面整体流程介绍中前后端交互,后端逻辑代码
  • 入口genie-bakcend: /data/chatQuery
  • 下面截图红字1部分是qdrant + es实现的TableRag增强功能


    代码部分.png
  • 在调用enrichNl2Sql之前,对应的baseNl2SqlReq结构入下,注意schemaList为空,如果没开启向量数据搜索和es搜索增强,这个schemaList会是这张表的所有字段


    说明1.png
  • 这个是没开启qdrant + es则20个字段全部返回


    说明2.png
  • 在genie-tool项目中根目录.env项目底下优化qdrant和es配置
TR_QD_THRESHHOLD=0.55
TR_QD_RECALL_TOP_K=20
  • 优化配置之后重启发现只有14个字段被召回了, 少了6个字段


    说明3.png

nl2sql核心流程

  • 调用nl2sql之前会先用TableRag增强技术,减少llm歧义,后面就是核心nl2sql流程,genie-backend会调用python写的genie-tool项目
# url是
/v1/tool/nl2sql
  • genie-tool的nl2sql整体流程如下:


    nl2sql整体流程.png
  • 我们拿prompt 上个月销售额最高的产品举例
body = NL2SQLRequest(
    request_id="req_12345",
    query="上个月销售额最高的产品",
    current_date_info="2025年11月23日 星期日",
    table_id_list=["sales_orders", "products"],
    column_info={...},  # 原始列元数据
    dialect="BigQuery"
)
并发执行精排 + 重写
并发执行精排 + 重写.png
  • 精排模块输入:查询 + 2张表的原始元数据处理:
    • 识别关键实体:销售额 → sales_amount,产品 → product_name
    • 时间范围分析:上个月 → 2025-10-01 到 2025-10-31
    • 过滤无关字段(如 customer_email, shipping_address)
    • 输出 rank_result:
[
    {
        "modelCode": "sales_orders",
        "schemaList": [
            {"columnId": "sale_amt", "columnName": "sales_amount", "columnComment": "含税销售额", "dataType": "DECIMAL"},
            {"columnId": "order_date", "columnName": "create_time", "columnComment": "订单创建时间", "dataType": "TIMESTAMP"},
            {"columnId": "prod_id", "columnName": "product_id", "columnComment": "产品ID", "dataType": "STRING"}
        ],
        "businessPrompt": ""
    },
    {
        "modelCode": "products",
        "schemaList": [
            {"columnId": "prod_id", "columnName": "product_id", "dataType": "STRING"},
            {"columnId": "prod_name", "columnName": "product_name", "columnComment": "产品标准名称", "dataType": "STRING"}
        ]
    }
]
  • 重写模块 (_text_to_rewrite)LLM提示:
你是一个数据分析师,请重写用户查询使其更清晰明确,包含:
1. 具体时间范围(当前日期:2025年11月23日)
2. 业务规则细节
3. 需要返回的字段

原始查询:上个月销售额最高的产品
重写结果:
  • LLM输出:
"查询2025年10月1日至2025年10月31日期间,销售额总和最高的产品名称及其销售额金额。仅统计支付成功且无退款的订单,按总销售额降序排列取第一名。"
流式思考过程 (_collect_think_results)
  • llm提示,这个思考过程也是调用大模型, query是用户原始query, m_schema_formatted是精排后格式化的llm可读数据
think_prompt: |-
  # 角色
  你是一名专注于「业务数据解读」的自身分析师,核心任务是基于提供的「字段信息」,对「用户问题」进行任务拆解和逻辑分析,输出分析思考过程。

  # 任务流程
  基于用户输入的问题(query)和提供的「数据源信息」,按以下步骤执行:
  1)拆解问题核心
  明确用户问题涉及的核心业务指标(如销量、用户数)、分析维度(如时间、地区)或业务关系(如因果、对比),同时识别潜在业务场景或需求。
  **注意** 用户问题中可能会隐含一些关键的查询逻辑,需要结合用户问题和数据源信息,仔细分析用户真实意图
  
  2)匹配字段信息
  从「数据源信息」中筛选与问题直接相关的字段,结合字段的业务含义说明其与问题的关联(例如:用户问 “某产品销量变化”,需关联 “产品 ID”“销售数量”“销售日期” 字段,因这些字段可定位特定产品的销量及时间维度数据)。
  
  3)推导回答逻辑
  基于字段的业务含义,说明字段间如何关联、通过哪些分析步骤(如筛选、对比、推导等)可支撑问题解答(无需具体数据,仅描述逻辑框架)。

  # 用户信息
  {{user_info}}

  # 系统时间
  {{current_date_info}}

  # 数据源信息
  {{m_schema_formatted}}

  # 严格禁止
  1.绝对禁止出现任何暗示信息不足的表述,包括但不限于「数据表内容未提供」「无法给出具体日期」「无法查询数据」「没有相关数据」「信息不足」「无法确定」等。即使,字段信息有限,也需围绕问题拆解可能的分析角度。
  2.禁止使用任何结论性语句,包括但不限于「综上」「因此」「答案是」「总之」「所以」等引导的内容,仅保留对 “字段关联方式、问题拆解逻辑” 的描述。

  # 输出要求
  1.输出「思考过程」,严格按任务流程的 3 个步骤用中文有序呈现。
  2.纯文本,不使用任何格式标记(如加粗、标题、符号等),仅用数字 + 点(1. 2. 3.)区分步骤。
  3.总字数小于300字,语言简洁、逻辑连贯,每一步分析需紧密结合字段信息。
  4.输出的内容禁止使用markdown

  用户问题
  {{query}}
  输出:
  • 思考输出结果
// T=400ms 推送第一块
{"status": "nl2sql_think", "nl2sql_think": "1. 需要关联sales_orders和products表,通过product_id字段连接。", "request_id":"req_12345"}

// T=450ms 推送第二块
{"status": "nl2sql_think", "nl2sql_think": "1. 需要关联...\n2. 时间范围:create_time BETWEEN '2025-10-01' AND '2025-10-31'", "request_id":"req_12345"}

1. 需要关联sales_orders和products表,通过product_id字段连接。
2. 时间范围:create_time BETWEEN TIMESTAMP('2025-10-01') AND TIMESTAMP('2025-10-31')
3. 业务规则:WHERE is_paid=true AND refund_status=0
4. 按SUM(sales_amount) DESC排序,LIMIT 1获取最高销售额产品
5. 需要返回product_name和总销售额
SQL生成 (_nl2sql_convert)
  • prompt如下, 这里的query是rewritten_query重写之后的query, thinking_result是精排之后思考结果
nl2sql_prompt: |-
  # 角色
  你是一个高级、精确的 SQL 查询生成器。你的任务是从「表信息以及相关字段信息」找出与用户问题最相关的表及其字段,将其转换为符合 ANSI SQL 标准的、语法正确的、高效的 SQL 查询语句。你精通各种表连接、聚合函数、子查询和窗口函数。

  # 要求
  1.绝对准确:必须严格遵循提供的数据库结构(表名、列名、关系)。绝不能臆造不存在的表或列。
  2.性能优先:编写简洁、高效的SQL查询语句。
  3.语法规范:使用标准的 SQL 语法,需要遵循 {{dialect}} 规范。

  # 任务流程
  你需要按照如下要求,一步一步完成并输出最终的结果:
  1.用户问题拆解
  - 将用户问题分解为独立且无歧义的子问题,每个子问题仅对应单一查询目标(如:统计人数/计算平均值)。
  - 拆解结果用@@@分隔,例如:查询A表应届生人数@@@统计B表年龄分布。
  - 如果用户输入的query需要多个sql的问题(用户问题的答案在sql中的限制条件不一致),那么需要将其拆解为简单query。例子:我们公司有多少新入职的应届生?他们的年龄分布是怎么样的(这个用户问题需要拆解)
  - 如果用户输入的query只需要单个sql的问题(用户问题的答案在sql中的限制条件一致),那么不需要将其拆解为简单query。例子:我们公司90后员工的姓名、年龄以及职级是什么(这个用户问题不需要拆解)
  2.表和字段的召回
  - 充分参考【思考伪代码】中的信息,结合【表信息以及相关字段信息】获取与用户问题最相关的数据表和字段信息
  - 禁止臆想不存在的字段
  3.生成SQL
  基于用户拆解的问题和召回的数据表、字段,结合【思考伪代码】的计算过程,生成最终的查询SQL

  # 输出
  1.用户问题拆解query之间使用@@@分割,每个拆解后的问题和sql之间使用###分割,一个子问题只能有一个sql
  形如:问题1###sql1@@@问题2###sql2
  - 问题1对应的sql1仅能使用「表信息以及相关字段信息」中的某一张表,禁止多表之间使用。
  2.拒绝一切推理、解释、注释、标记(如 ```sql)或任何额外的文本。
  3.输出 SQL 中表、字段、别名均需要增加``进行标识

  # 上下文信息
  ## 用户信息
  {{user_info}}

  ## 当前日期
  {{current_date_info}}

  # 思考伪代码(记录用户问题的分析过程,字段筛选和计算逻辑)
  {{thinking_result}}

  # 表信息以及相关字段信息
  {{m_schema_formatted}}

  # SQL生成规范
  1. 禁止使用JOIN的多表关联
  2. 生成的SQL禁止使用字段名称,必须使用字段ID
  3. 非统计类的查询SQL(如:排序类、明细类),需要对查询的字段使用DISTINCT进行结果去重
  4. 聚合统计类的查询SQL,需要对聚合或运算字段添加别名,如 SUM(column_id) AS `new_column`
  5. 当某张表完全满足用户问题时,使用该表的优先级最高,禁止使用其他的表生成SQL
  6. 根据具体需求选择最合适的语法:
  - 条件聚合可使用CASE WHEN或FILTER子句
  - 比率计算可选择COUNT配合条件判断或直接除法运算
  - 选择性能最优且符合ANSI标准的语法结构

  # 示例
  ## case1:
  用户问题:查询所有来自上海的用户的姓名和年龄。
  输出:查询所有来自上海的用户的姓名和年龄###SELECT DISTINCT `name`, `age` FROM `users_table` WHERE `city` = '上海'

  ## case2:
  用户问题:统计2023年每个月的订单总额。
  输出:统计2023年每个月的订单总额###SELECT month AS `month`, SUM(`total_amount`) AS `total_sales` FROM `orders` where date_time_year = '2023' GROUP BY `month` ORDER BY `month`

  用户问题
  {{query}}
  输出:
  • 最终转换sql返回结果:
llm_post_result = [{
    "query": "2025年10月销售额最高的产品",
    "nl2sql": "SELECT p.prod_name AS product_name, SUM(s.sale_amt) AS total_sales FROM `sales_orders` s JOIN `products` p ON s.prod_id = p.prod_id WHERE s.create_time BETWEEN TIMESTAMP('2025-10-01') AND TIMESTAMP('2025-10-31') AND s.is_paid = true AND s.refund_status = 0 GROUP BY p.prod_name ORDER BY total_sales DESC LIMIT 1"
}]
关键设计价值解析
  • 实时性体验
    • 用户在400ms内看到第一条思考过程(远早于最终SQL生成)
    • 消除"白屏等待"焦虑,符合人类认知节奏
  • 准确性保障
    • 精排模块过滤90%+无关字段(如原始表有50+字段,只保留5个关键字段)
    • 重写模块明确时间边界(2025-10-01至2025-10-31)和业务规则
    • 思考链显式验证逻辑,避免LLM幻觉
  • 错误防御机制
    • 业务规则显式注入SQL:WHERE is_paid=true AND refund_status=0
    • 严格字段ID使用(sale_amt而非模糊的sales_amount)
    • 方言适配(BigQuery的table和TIMESTAMP函数)
  • 性能优化
    • 精排+重写并发执行,节省200ms+
    • 思考过程流式传输,首字节响应<500ms
    • SQL生成使用非流式确保准确性

开启qdrant + es的TableRag代码细节

  • 这段代码只要在genie-tool里面对应逻辑
/v1/tool/table_rag
  • 主要流程氛围流程1,流程2:


    流程1.png

    流程2.png
  • 流程详解(配合流程图)
输入与预处理阶段
  • 用户查询:如"最近一周北京的iPhone销量"
  • 关键词提取:通过Jieba分词提取关键实体("最近一周"、"北京"、"iPhone"、"销量")
  • 配置参数:决定使用哪些检索路径(向量/ES)
多路检索并行阶段
  • 向量检索路径(Schema层面)
    • Jieba关键词向量检索:将关键词与表/列描述进行语义匹配
      • "销量" → sales_count列(相似度0.92)
      • "最近一周" → sale_date列(相似度0.85)
    • LLM增强向量检索:
      • LLM提示:根据提供的表格信息和用户问题,分析问题语义,并结合已抽取的关键词,输出一组最可能用于回答该问题的字段名列表。
      • LLM响应:["sale_date", "region", "product_name", "sales_count"]
      • 检索这些字段的元数据
  • ES检索路径(数据值层面)
    • Jieba关键词ES检索:
      • "北京" → 匹配region列中值为"北京"的记录
      • "iPhone" → 匹配product_name列包含"iPhone"的记录
    • LLM增强ES检索:
      • LLM提示:"提取查询中需要精确匹配的值"
      • LLM响应:["北京", "iPhone", "2023-11-22", "2023-11-28"]
      • ES检索这些值在表中的分布
结果融合阶段
  • Schema与值关联:将ES检索到的值映射回相关列

    • 例:"北京"的高频匹配 → 提升region列的相关性分数
  • 多源分数融合:向量分数(70%) + ES匹配分(30%)

  • 拿之前图片的query举例


    拿之前图片的query举例.png
  • qdrant召回的字段


    qdrant召回的字段.png
  • es召回的字段, es精简很多,直接匹配到许安这个列的数据对应值


    es召回的字段.png
结果筛选阶段
  • 按表分组:将列按所属表分组
  • 表级别排序:按表内所有列的总分排序
    • sales_table: 0.88(region)+0.92(sales_count)+0.85(sale_date) = 2.65
    • product_table: 0.75(product_name)+0.4(category) = 1.15
  • 列级别排序:每表内按列分排序
  • Top-K截断:
    • 保留Top 1表(sales_table)
    • 每表保留Top 3列(region, sales_count, sale_date)
列过滤阶段
  • 时间表达式解析:将"最近一周"转换为日期范围
  • 业务规则应用:应用"热销产品定义为销量>1000"等规则
  • 权限过滤:只保留用户有权限访问的列
  • 语义消歧:解决"收入"可能对应revenue或income的歧义
  • 最终Schema:过滤后的表结构
{
  "modelCode": "sales_table",
  "schemaList": [
    {"columnName": "sale_date", "filter_context": "2023-11-22至2023-11-28"},
    {"columnName": "region", "filter_context": "='北京'"},
    {"columnName": "sales_count", "filter_context": "聚合函数: SUM"}
  ]
}
关键数据流说明
  • 双向增强:向量检索结果指导ES检索范围(只在相关列中搜索值),ES检索结果提升向量检索置信度
  • 动态权重:根据查询类型自动调整向量/ES权重
    • 含具体值的查询("北京"):ES权重↑
    • 抽象概念查询("趋势"):向量权重↑
  • 上下文感知:
    • 当前日期用于解析相对时间("最近一周"→具体日期范围)
    • 用户信息用于权限过滤
    • 业务规则库用于语义消歧
  • 降级策略:
    • 当Qdrant不可用时,依靠LLM+ES路径
    • 当ES不可用时,依靠向量+业务规则映射
  • 案例: "华东区销售额"(华东区包含上海、江苏、浙江), 华东区包含的城市如果es找不到就是llm辅助解析

参考文章

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容