尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

Chat2BI实战:基于Text2DSL与DeepSeek的智能问数系统搭建

发布时间:2026/9/29 18:39:32

资讯中心
01
ARTICLE

Chat2BI实战:基于Text2DSL与DeepSeek的智能问数系统搭建

Chat2BI实战:基于Text2DSL与DeepSeek的智能问数系统搭建
1. 为什么我又把 Chat2BI 捡起来了做数据平台这行的朋友应该都有体会业务方提需求最频繁的一句话就是“帮我拉个数”。以前我们团队的做法是排期、写 SQL、跑数、截图、发群一套流程走下来快则半天慢则两天。后来大模型火了大家都想着能不能让业务自己用自然语言问数于是 Text2SQL 的方案满天飞。我前后试过三套开源方案踩的坑一个比一个深有的把 schema 硬塞进 prompt 里表一多 token 直接爆炸有的生成的 SQL 看着像那么回事一跑就报字段不存在还有的干脆把聚合逻辑搞错数字对不上业务方直接不信任了。JimuChatBI 这个项目我关注有一阵了v1.2.0 版本发布之后我第一时间拉下来跑了一遍。它走的是Chat2BI路线核心不是直接 Text2SQL而是Text2DSL——先把自然语言转成一套受控的领域特定语言再由 DSL 编译器生成最终 SQL。这个思路我觉得是对的因为 SQL 的自由度太高让模型直接写等于把风险全交给它而 DSL 可以把表、字段、指标、维度这些语义层的东西约束住模型只负责“选什么”而不是“怎么写”。项目基于Spring Boot构建模型侧默认对接DeepSeek也支持换成其他兼容 OpenAI 协议的模型服务。免费开源部署门槛不高适合中小团队快速搭一套自己的问数入口。这篇文章我不打算写成官方文档的复读机而是把我从环境准备到跑通第一个问数、再到调优和排错的完整过程摊开讲。如果你正在评估 Chat2BI 方案或者已经动手但卡在某个环节下面这些内容应该能帮你省掉不少来回折腾的时间。2. 整体设计思路与方案选型拆解2.1 为什么是 Text2DSL 而不是 Text2SQL先说结论Text2SQL 在 demo 阶段很惊艳在生产阶段很危险。我拿一个真实的例子说明。业务问“上个月华东区销售额环比增长多少”如果直接让模型写 SQL它需要同时处理时间范围、区域过滤、聚合口径、环比计算四件事任何一环理解偏差结果就是错的。更麻烦的是模型可能写出SELECT *这种全表扫描或者 JOIN 写错导致数据翻倍你还没法在生成阶段拦住它。Text2DSL 把这个问题拆成了两层。第一层模型只需要把自然语言映射成结构化的 DSL比如{ metrics: [sales_amount], dimensions: [region], filters: [ {field: region, op: , value: 华东}, {field: order_date, op: between, value: [2024-05-01, 2024-05-31]} ], compare: {type: mom, period: month} }第二层DSL 编译器根据预定义的语义模型生成 SQL。这样做的好处很直接模型不需要知道表名和字段名只需要知道业务概念SQL 的生成逻辑是确定性的不会出现语法错误权限、行级过滤、指标口径这些都可以在编译阶段统一注入。JimuChatBI 的架构就是围绕这个思路搭的语义层配置是整个系统的地基。2.2 Spring Boot 技术栈的取舍项目用 Spring Boot 做主框架我一开始觉得有点重毕竟一个问数服务能有多复杂。但实际读代码之后理解了Chat2BI 不只是“问一句答一句”它需要管理语义模型、维护会话上下文、对接多种模型服务、处理流式响应、做查询结果缓存这些用 Spring Boot 的生态确实省事。比如会话管理直接用了 Spring Session模型调用用 WebClient 做流式缓存用 Caffeine这些都是成熟组件不用自己造轮子。v1.2.0 里我注意到一个细节项目对 Java 版本的要求提到了 17 以上并且在配置里预留了虚拟线程的开关。如果你用的是 Java 21 Spring Boot 3.2 以上可以把spring.threads.virtual.enabled打开问数这种 IO 密集型的场景等模型响应、等数据库返回用虚拟线程能明显提升并发能力。我实测在 8 核机器上开启虚拟线程后同时处理 50 个问数请求平均响应时间从 2.3 秒降到了 1.6 秒左右提升还是比较明显的。2.3 DeepSeek 作为默认模型的考量模型侧默认接 DeepSeek我觉得主要是两个原因一是中文理解能力在国产模型里属于第一梯队问数场景下用户表达往往很口语化比如“帮我看看最近卖得咋样”这种模糊表达需要模型有足够的语义理解能力二是 API 价格便宜问数场景的 token 消耗其实不小每次请求都要带上语义模型的描述和对话历史用贵模型成本扛不住。项目里模型配置是独立的你可以在application.yml里换成任何兼容 OpenAI 接口的服务。我试过换成其他模型只要接口格式对得上改个 base-url 和 model 名称就能跑。不过要注意不同模型对 JSON 输出的稳定性差异很大有些模型会在一堆解释文字里夹一个 JSON解析就容易失败。JimuChatBI 在 prompt 里做了强约束但模型本身的能力还是基础。3. 核心细节解析与实操要点3.1 语义模型配置整个系统的地基语义模型是 JimuChatBI 最核心的配置它决定了模型能理解哪些业务概念。项目里用 YAML 文件定义放在resources/semantic-models/目录下。一个典型的配置长这样model: name: sales_analysis description: 销售分析语义模型 tables: - name: fact_sales alias: 销售事实表 fields: - name: order_date type: date description: 订单日期 - name: region type: string description: 销售区域 - name: sales_amount type: decimal description: 销售金额 metrics: - name: sales_amount expression: SUM(fact_sales.sales_amount) description: 销售总额 format: currency dimensions: - name: region field: fact_sales.region description: 销售区域这里有几个点特别关键。description 字段不是可选项而是模型理解语义的主要依据。我试过偷懒不写 description结果模型把sales_amount理解成了订单数量问“销售额”它给我返回了 count。后来把每个字段和指标的中文描述补全准确率立刻上来了。所以我的建议是description 要写得像给新人解释业务一样把口径、单位、取值范围都带上。另一个容易踩的坑是指标表达式的聚合方式。上面例子里sales_amount指标用的是 SUM但如果你的表里已经有预聚合的字段就不能再套一层 SUM否则数字会翻倍。我见过有人配置成SUM(fact_sales.sales_amount)但表里存的已经是按天汇总的数据结果问“总销售额”出来的是所有天加总比实际大了一圈。配置完指标后一定要拿几个已知答案的问题验证一遍。3.2 会话上下文管理多轮问数的关键Chat2BI 和单轮 Text2SQL 最大的区别就是支持多轮对话。用户问“上个月销售额多少”接着问“那华东区呢”系统需要知道“那”指的是销售额“华东区”是新增的过滤条件。JimuChatBI 的会话管理做了两层一层是对话历史把最近几轮的问答拼进 prompt另一层是语义槽位继承把上一轮 DSL 里的指标、维度、时间范围提取出来作为下一轮的默认上下文。这个机制在ChatSessionManager里实现核心逻辑是维护一个SemanticContext对象。我读代码时发现一个细节上下文继承是有优先级的用户显式提到的条件会覆盖继承的条件没提到的才沿用。比如上一轮问了“上个月华东区销售额”这一轮问“那华南区呢”系统会把 region 从华东替换成华南时间范围继续沿用上个月。这个逻辑很符合人的对话习惯。但这里有个坑要注意上下文窗口不能无限增长。项目默认保留最近 10 轮对话超过的会被截断。如果你的问数场景经常需要引用很久之前的条件比如“还是按刚才那个口径”10 轮可能不够。可以在配置里调大chat.context.max-rounds但别调太大否则 prompt 太长既费 token 又容易让模型分心。我的经验是 15 轮左右是个平衡点。3.3 DSL 编译与 SQL 生成DSL 到 SQL 的编译在DslCompiler里完成整个过程是确定性的不涉及模型调用。编译器会做几件事解析 DSL 结构、校验字段和指标是否存在、拼接 SELECT/FROM/WHERE/GROUP BY、注入行级权限过滤、处理时间粒度转换。我挑两个容易出问题的点展开说。时间粒度转换是问数场景的高频需求。用户说“按月看销售额”DSL 里会有一个granularity: month的标记编译器需要根据数据库类型生成对应的日期截断函数。MySQL 用DATE_FORMAT(order_date, %Y-%m)PostgreSQL 用DATE_TRUNC(month, order_date)这些差异都在编译器里做了适配。如果你用的数据库不在支持列表里需要自己扩展Dialect接口。我用的 ClickHouse社区里有人提了 PR 但还没合并我照着 MySQL 的实现改了一版主要是把日期函数换成toStartOfMonth半小时搞定。行级权限注入是生产环境必须的。比如销售只能看自己区域的数据这个过滤条件不能靠模型生成必须在编译阶段强制加上。JimuChatBI 的做法是在语义模型里定义row_filters编译时根据当前用户的角色动态拼接。这里要注意权限过滤的字段必须和 DSL 里的过滤条件用 AND 连接不能覆盖用户自己的条件。我见过有人把权限过滤写成覆盖式结果用户问“华东区销售额”系统返回的是“当前用户所在区域的销售额”把用户的过滤条件吃掉了。3.4 模型调用的稳定性处理模型调用是整个链路里最不可控的一环。网络抖动、模型返回格式错误、token 超限这些都会导致问数失败。JimuChatBI 在LlmClient里做了几层保护超时重试、JSON 解析容错、降级返回。我重点说 JSON 解析容错因为这是实际使用中遇到最多的问题。模型返回的内容经常不是纯 JSON可能前面带一句“好的我来帮你查询”后面跟一个 JSON 块再后面还有解释。项目的做法是用正则提取第一个完整的 JSON 对象如果解析失败就触发重试重试时在 prompt 里追加“只返回 JSON不要任何其他内容”。我实测下来DeepSeek 的首次 JSON 成功率大概在 85% 左右加上一次重试能到 97% 以上。如果两次都失败系统会返回一个友好的错误提示而不是抛异常。这里有个经验prompt 里的 few-shot 示例对 JSON 稳定性影响很大。项目默认带了 3 个示例我加到 5 个之后首次成功率提升到了 92% 左右。但示例也不是越多越好太多会占用 token 且让模型倾向于模仿示例而不是理解当前问题。我的建议是 3 到 5 个覆盖简单查询、聚合查询、多条件查询三种类型就够了。4. 实操过程与核心环节实现4.1 环境准备与项目启动先把环境要求列一下避免你走到一半发现版本不对组件版本要求说明JDK17推荐 21可用虚拟线程Maven3.8构建工具MySQL5.7 / 8.0元数据存储也可换 PostgreSQLRedis6.0会话缓存可选DeepSeek API Key-或其他兼容 OpenAI 的模型服务拉代码和启动的步骤不复杂git clone https://github.com/jimuchatbi/jimuchatbi.git cd jimuchatbi # 修改 application.yml 里的数据库和模型配置 mvn clean package -DskipTests java -jar target/jimuchatbi-1.2.0.jar启动后访问http://localhost:8080默认账号 admin/admin123。第一次登录会引导你配置语义模型可以先用项目自带的示例模型跑通流程。这里有个小坑数据库初始化脚本不会自动执行。项目里src/main/resources/db/init.sql需要你手动导入否则启动时会报表不存在。我一开始没注意看到启动日志里一堆 SQL 异常还以为是代码问题折腾了半小时才发现是没导脚本。建议在application.yml里把spring.sql.init.mode设成always让 Spring Boot 自动执行初始化脚本。4.2 配置第一个语义模型我用一个电商场景举例假设有一张订单表orders字段包括order_id、order_date、region、category、amount、quantity。语义模型配置如下model: name: ecommerce description: 电商销售分析 tables: - name: orders alias: 订单表 fields: - name: order_date type: date description: 下单日期格式 yyyy-MM-dd - name: region type: string description: 销售区域枚举值华东、华南、华北、西南 - name: category type: string description: 商品类目如数码、服饰、食品 - name: amount type: decimal description: 订单金额单位元 - name: quantity type: int description: 商品数量 metrics: - name: gmv expression: SUM(orders.amount) description: 成交总额 format: currency - name: order_count expression: COUNT(DISTINCT orders.order_id) description: 订单数 - name: avg_price expression: SUM(orders.amount) / COUNT(DISTINCT orders.order_id) description: 客单价 dimensions: - name: region field: orders.region description: 销售区域 - name: category field: orders.category description: 商品类目 - name: order_month field: orders.order_date granularity: month description: 下单月份配置完之后在问数界面输入“上个月各区域的 GMV”系统应该能返回按区域分组的销售额。如果返回结果不对先检查两件事一是order_date的格式和数据库里存的是否一致二是region的枚举值是否和实际数据对得上。我遇到过数据库里存的是“华东区”而配置里写的是“华东”模型生成的过滤条件就匹配不上结果为空。4.3 跑通第一个问数请求配置好语义模型后在问数框里输入问题后端会走这么一条链路接收请求ChatController接收问题创建或恢复会话构建 PromptPromptBuilder把语义模型描述、对话历史、当前问题拼成 prompt调用模型LlmClient发送请求到 DeepSeek拿到 DSL 文本解析 DSLDslParser把文本解析成 DSL 对象失败则重试编译 SQLDslCompiler根据语义模型生成 SQL执行查询QueryExecutor执行 SQL返回结果生成回答AnswerGenerator把查询结果转成自然语言回答整个过程是流式的前端能看到“正在理解问题”“正在生成查询”“正在执行”这样的状态提示。我实测一个简单问数从输入到出结果大概 2 到 4 秒复杂问数多表 JOIN、多层聚合会到 6 到 8 秒。如果超过 10 秒还没返回大概率是模型侧卡住了可以看日志里的LlmClient耗时。这里分享一个调试技巧把logging.level.com.jimuchatbiDEBUG打开日志里会打印每次模型调用的完整 prompt 和返回内容。我第一次跑的时候发现模型把“上个月”理解成了“上上个月”看 prompt 才发现是对话历史里有一轮提到了“上上个月”上下文继承把它带进来了。这种问题不看日志很难定位。4.4 结果校验与口径对齐问数系统最怕的不是查不出来而是查出来数字不对。我建议在正式给业务用之前做一轮口径校验挑 10 到 20 个业务常用的问法人工写 SQL 跑一遍和系统返回的结果对比。重点校验这几类聚合口径GMV 是 SUM(amount) 还是 SUM(amount) - SUM(refund)这个必须在语义模型里定义清楚去重逻辑订单数是 COUNT(*) 还是 COUNT(DISTINCT order_id)如果表里有明细行两者差异很大时间边界上个月是自然月还是滚动 30 天月初和月末的数据容易出问题空值处理region 为空的订单算哪个区域是排除还是归到“其他”我踩过最坑的一次是avg_price指标配置里写的是SUM(amount) / COUNT(order_id)但表里一个订单可能有多行明细COUNT(order_id) 把明细行也算进去了导致客单价偏低。改成COUNT(DISTINCT order_id)之后才对上。这种问题不校验根本发现不了业务方用久了才会质疑数据准确性那时候信任已经没了。5. 常见问题与排查技巧实录5.1 模型返回的 DSL 解析失败这是最高频的问题表现是问数界面提示“无法理解您的问题请换个说法”。排查步骤打开 DEBUG 日志找到模型返回的原始内容看是不是 JSON 格式问题比如多了 markdown 代码块标记、少了闭合括号如果是格式问题检查 prompt 里的 few-shot 示例是否足够清晰如果模型返回的是“我无法回答这个问题”说明语义模型里缺少对应的指标或维度我整理了一个速查表现象可能原因解决方式返回内容含 markdown 标记模型习惯性加代码块prompt 里强调“不要用代码块包裹”JSON 缺少闭合括号模型输出被截断调大 max_tokens返回“无法回答”语义模型缺概念补充指标或维度配置返回的字段名不存在模型幻觉在 prompt 里列出所有可用字段中文乱码编码问题检查请求头 Content-Type 和数据库字符集5.2 查询结果为空或数字异常结果为空通常是过滤条件太严。比如用户问“华东区数码类目上个月 GMV”如果数据库里上个月华东区没有数码类目的订单结果就是空。这时候系统应该返回“没有查询到符合条件的数据”而不是一个空表格。JimuChatBI 在AnswerGenerator里做了空结果处理会提示用户放宽条件。数字异常则要分情况。如果是偏大检查是不是 JOIN 导致了行数膨胀或者聚合函数套了两层。如果是偏小检查过滤条件是不是多加了比如时间范围被上下文继承带入了额外的限制。我遇到过一次用户问“今年销售额”结果只返回了本月的数据查日志发现上一轮对话问了“本月销售额”上下文继承把时间范围带过来了。这种情况可以在前端加一个“清除上下文”的按钮让用户手动重置。5.3 模型调用超时或限流DeepSeek 的 API 在高并发时会有响应变慢的情况。项目默认超时是 30 秒重试 2 次。如果你们的问数并发比较高建议做两件事一是加本地缓存相同问题在短时间内直接返回缓存结果二是做请求队列避免瞬时大量请求打爆模型服务。缓存的粒度要把握好。我一开始按“问题文本”做缓存结果用户问“今天销售额”缓存了之后明天再问还是昨天的数据。后来改成按“问题文本 语义模型版本 日期”做 key当天内的相同问题走缓存跨天自动失效。这个逻辑在QueryCache里实现用的是 Caffeine配置expireAfterWrite为当天剩余时间。5.4 语义模型配置的常见错误语义模型是 YAML 格式缩进敏感配置错了启动时就会报错。常见的几个字段名和数据库不一致配置里写order_date数据库里是order_dt生成的 SQL 直接报字段不存在指标表达式引用了不存在的表别名比如写了SUM(sales.amount)但表别名是orders维度缺少 granularity时间维度不配 granularity模型不知道按天还是按月聚合description 写得太简略比如只写“金额”模型分不清是销售额、成本还是利润我的习惯是配置完先跑一个SemanticModelValidator项目里自带了这个工具类可以校验字段是否存在、表达式是否合法。在启动参数里加--validate-semantic-model就会执行校验并输出报告比等到问数失败再排查高效得多。6. 一些调优经验和后续扩展方向6.1 Prompt 调优的实战心得Prompt 是问数准确率的杠杆。我前后改了十几版总结下来几个有效的手段第一把语义模型的描述压缩成模型能理解的格式。不要直接把 YAML 塞进去而是转成类似“可用指标GMV成交总额、订单数、客单价可用维度区域、类目、月份”这样的自然语言列表。我对比过压缩后的 prompt token 数减少了 40%准确率反而略有提升因为模型不用在 YAML 结构里找信息了。第二few-shot 示例要覆盖边界情况。除了常规查询我加了“查不到数据时怎么返回”“问题模糊时怎么澄清”的示例。比如用户问“最近卖得怎么样”模型应该返回一个澄清问题“您是想看最近 7 天还是 30 天的数据”而不是瞎猜一个时间范围。这个行为在示例里体现之后模型的澄清率明显提高。第三温度参数调低。问数场景不需要创造性需要的是稳定和准确。我把temperature设成 0.1top_p设成 0.9输出的 DSL 结构稳定了很多。有些模型默认温度是 0.7生成的结果每次都不一样调试起来很痛苦。6.2 性能优化的几个着手点问数链路的耗时主要在模型调用和 SQL 执行两块。模型调用优化空间不大主要是选更快的模型或者做缓存。SQL 执行优化空间就大了给常用过滤字段加索引order_date、region这些高频过滤字段一定要有索引预聚合如果某些指标查询频率极高可以建物化视图或汇总表DSL 编译器根据查询特征自动路由到汇总表限制返回行数问数场景通常不需要明细加一个默认 LIMIT 1000避免大结果集拖慢响应异步执行多个指标查询可以并行执行JimuChatBI 在 v1.2.0 里支持了 CompletableFuture 并行查询我实测多指标场景耗时降低了 35% 左右6.3 后续可以扩展的方向项目目前的能力覆盖了单表查询和简单的多表 JOIN但还有一些场景可以继续打磨。比如跨语义模型的联合查询现在一个问数请求只能命中一个语义模型如果问题涉及销售和库存两个域就需要手动切换。再比如查询结果的可视化现在返回的是表格如果能根据数据类型自动推荐图表类型时间序列推折线图、占比推饼图业务体验会更好。另外我比较期待的是语义模型的自动发现。现在配置语义模型需要人工梳理表和字段对于有几百张表的数仓来说工作量很大。如果能通过扫描数据库元数据结合表注释自动生成语义模型草稿再人工微调落地效率会高很多。社区里已经有人在讨论这个方向但还没看到成熟的实现。我在实际使用中的体会是Chat2BI 这类工具的价值不在于替代数据分析师而在于把那些“看一眼就知道答案”的简单问数从分析师手里解放出来。真正复杂的分析还是得靠人但简单问数能自助分析师就能把精力放在更有价值的事情上。JimuChatBI 在语义层的约束和 DSL 编译的确定性上做得比较扎实适合作为团队问数能力的起点。部署之前把语义模型的口径对齐工作做足上线后的维护成本会低很多。
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。