首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >当 openClaw 遇上 EdgeOne Pages:不只智能问数,更能直接获取BI 数据大屏(附工程落地实战)

当 openClaw 遇上 EdgeOne Pages:不只智能问数,更能直接获取BI 数据大屏(附工程落地实战)

原创
作者头像
fanstuck
发布2026-04-15 09:48:33
发布2026-04-15 09:48:33
520
举报

引言

ChatBI 和智能问数已经很热了。大多数系统能把自然语言转成 SQL,甚至附上一段分析说明——但它们的终点,通常还只是"把数据查出来"。

业务真正需要的不是 SQL,不是一批 rows,而是一个可以直接打开、分享、展示的分析页面。

这次在医疗设备采购数据分析场景里,我让 openClaw 负责问题理解与任务编排,SQL 执行层做受控查询,EdgeOne Pages 把结果直接生成可访问的 BI 页面并返回公开链接。目标从一开始就不是"智能问数",而是从自然语言提问到 BI 页面自动生成的完整工程链路。

第一章:ChatBI 的真正瓶颈不在 NL2SQL,在结果交付

NL2SQL 是整条链路里最显眼的环节,但它解决的是"能不能查到数据",不能自动解决"用户拿到结果之后能不能直接用"。

很多系统的终点停留在这几种形式:生成一条 SQL、返回一批 rows、附上一段文字总结、加一张简单图表。这些都是中间结果,不是最终交付物。

用户拿到这些之后,还得自己:判断哪些指标关键、整理摘要、选图表类型、排版页面,最后才能发给团队去看。系统"会回答问题了",但结果还是没有真正交付出去。

业务真正在意的是:我提了一个问题,你能不能直接给我一个可以看的结果。

有图表、有表格明细、有能直接发给别人打开的页面链接——这三件事同时满足,系统才真正像一个可交付的分析工具,而不只是会查库的助手。

我是 Fanstuck,一位长期活跃在 AI 大模型、知识图谱、智能体(Agent)与数据工程 领域的探索者和实践者。我的写作不止于讲解技术,更关注 如何让复杂的技术知识真正“被理解、被应用”。因此,你会在我的文章里看到不仅有代码与实验,还有贴近行业场景的实战案例和趋势解读。致力于将复杂的技术知识以易懂的方式传递给读者,热衷于分享最新的行业动向和技术趋势。如果你对大模型的创新应用、AI技术发展以及实际落地实践感兴趣的话,敬请关注。

1.1 很多 ChatBI 的终点,仍然只是 SQL 或 rows

现在不少所谓的智能问数方案,最后停留的终点通常是下面几种形式:

要么是系统帮你生成一条 SQL; 要么是执行之后返回一批 rows; 再好一点,会附上一段模型自动总结的分析说明; 更进一步的,会再加一张简单图表。

这些结果当然已经比纯数据库查询工具强很多,但如果从业务交付角度看,它们仍然都有一个共同特征:

它们本质上还是中间结果,而不是最终交付物。

因为业务人员并不会因为你返回了一条 SQL 就真的完成工作。 他也不会因为你给了几十行 rows,就自然地把这些信息理解成一个完整结论。 哪怕系统额外补了一段“分析如下”的文字,用户很多时候依然需要继续往下做几件事:

先看哪些指标最关键, 再整理成适合展示的摘要, 再决定用柱状图还是折线图, 再把表格和结论排版到一个页面里, 最后才有可能把这个结果发给团队、领导或者项目组去看。

也就是说,很多系统虽然已经把“查数”这件事做得很智能了,但整个结果交付过程,后面那一大段工作,依然需要人自己补完。

这就会导致一个很典型的现象: 系统已经会回答问题了,但结果还是没有真正“交付出去”。

1.2 从“回答问题”到“生产 BI 页面”完整工程链

把查询结果变成一个页面,不是"补个前端界面"那么简单,中间至少要跨几层能力边界:

意图识别层:这是明细查询、单值统计、分组统计还是趋势分析?统计的是公告数量还是明细行数?时间范围是精确区间还是模糊描述?定不准,后面整条链路都会偏。

受控执行层:不能让模型自由发挥写 SQL。以设备采购宽表为例,COUNT(*) 统计的是明细行数而非公告数量,业务口径和表结构之间存在大量隐性语义——没有显式约束,模型很容易"看起来会写 SQL,实际上把口径写错"。

页面生成层:即便 SQL 查对了,还需要决定哪些字段做摘要卡、哪些做图表、用柱状图还是折线图、标题怎么生成、查询条件怎么回显,再组装成统一的页面对象渲染为可访问页面。

"回答一个数据问题"和"自动生产一个 BI 页面"根本不是同一层事情。 前者是查询,后者是一次完整的数据产品交付。

1.3 为什么目标不能停在 NL2SQL

这个项目真正有价值的地方,不在于模型生成了一条 SQL,而在于系统能把这次分析最后变成一个真实可访问、可分享、可展示的 BI 结果。

和普通 ChatBI demo 真正拉开差距的,是这件事:用户输入一句自然语言问题,系统返回的是一个真实可访问的 BI 页面链接。

一旦做到这一步,系统的意义就变了——它不再只是辅助分析的对话工具,而开始具备更接近业务交付平台的能力。产出的不是中间结果,而是可以直接拿去展示、分享、复用的分析页面,甚至可以进一步演化成数据大屏。

这也是为什么我把这次实践的核心放在工程落地上:openClaw 负责任务编排,EdgeOne Pages 负责页面交付。问数是入口,交付才是终点。

第二章:openClaw、sql_runner、EdgeOne Pages三层架构

这套方案我没有做成"大模型全包"的形式,而是明确拆成了三层。这个决定不是一开始就想清楚的,而是在调试过程中越来越确定的——只要有一层职责模糊,整条链路就会开始出现各种说不清楚的问题。

2.1 openClaw:负责理解问题和组织任务

openClaw 在这条链路里的定位很克制:它不查库,也不管页面怎么渲染,它只负责把用户的自然语言问题翻译成后续两层能够处理的任务。

它的职责主要有四个:

  • 判断用户问题是不是数据分析请求
  • 将自然语言转换成结构化查询意图
  • 生成 SQL 并调用 sql_runner
  • 将查询结果组织成页面生成请求,发给 EdgeOne Pages

具体来说,它要判断用户问的是不是数据分析请求,如果是,就把问题拆解成结构化的查询意图——时间范围是什么、统计口径是什么、过滤条件有哪些——再据此生成 SQL 发给 sql_runner,拿到结果之后再组织成页面生成请求发给 EdgeOne Pages。

我在这里踩过一个坑:最开始想让 openClaw 顺带把图表类型也决定好、把页面 HTML 也生成出来,结果发现一旦它承担的自由度太多,整条链路就很难稳定。调试的时候也说不清楚到底是问题理解错了,还是页面生成出了问题。后来把这些职责拆出去,openClaw 只管编排,反而清爽很多。

2.2 sql_runner:只做受控 SQL 执行

第二层是本地的 sql_runner 插件,我刻意让它保持得非常窄:只允许单条 SELECT,只允许访问白名单内的表,非聚合明细查询必须带 LIMIT,返回格式固定为包含 status、sql、rows、row_count、error 的 JSON 结构。

它只做一件事:执行受控 SQL,并返回统一结构结果。

这一层的原则很明确:

  • 只允许单条 SELECT
  • 只允许白名单表
  • 非聚合明细必须带 LIMIT
  • 返回统一 JSON:status/sql/rows/row_count/error

这种限制看起来有点保守,但背后的逻辑是:SQL 执行层一旦开始掺入业务判断,它就不再是一个可预期的执行器,而变成了一个不透明的黑盒。之前有过一版设计是让模型在执行前"自己判断一下要不要加 LIMIT",结果在某些聚合场景下反而把查询结构搞乱了。

保持纯净的好处是问题非常好定位——SQL 有没有写对,去看生成逻辑;查得出查不出,去看执行器日志;页面展示有没有问题,去看页面层。每一层的问题都在自己的边界里。

2.3 EdgeOne Pages:负责把结果真正变成 BI 页面

前两层把数据拿到之后,最后一公里是怎么把它变成一个真实可访问的页面。这是整套方案里我觉得最有意思的部分,也是和很多普通 ChatBI 方案差异最大的地方。

很多系统到这里就停了——返回一个 JSON 结果,或者在对话框里渲染一张图表。但对业务来说,这两种形式都很难真正"交付出去"。JSON 没人能直接看,对话里的图表也没法发给别人单独打开。

EdgeOne Pages 解决的就是这个问题:它负责把查询结果渲染成完整的 HTML 页面,发布出去,生成一个公开可访问的 URL,然后把这个链接返回给用户。系统的输出从此不再是一段文字或一块图表,而是一个真正的页面链接——可以发给同事、可以嵌入报告、可以直接在浏览器里打开展示。

这一层在整条链路里不是附属能力,而是让整套方案从"智能问数"升级成"结果交付"的关键所在。

2.4 三层拆开之后,工程才真正稳

如果让一个 Agent 从头负责到尾,表面上链路更短,但实际调试会非常痛苦。问题出现的时候,你说不清楚是模型理解偏了、SQL 写错了、还是页面生成逻辑有问题——因为这些都混在一起,很难单独验证。

更麻烦的是稳定性。模型一旦同时承担太多自由度,输出会随着问法、上下文、模型版本微妙地漂移,今天跑通的,明天换个问法可能就不对了。把 SQL 执行和页面发布都收到受控接口里,稳定性会提升很多。

三层拆开之后,我自己最直接的感受是:整条链路变成了一个可以逐步验证的东西。可以先单独跑通 SQL 闭环,确认查询口径没问题,再接页面生成,最后再把 openClaw 接进来做端到端测试。每一步都有明确的输入输出,出问题也知道去哪里找。

第三章:工程化实现—从 SQL 插件到 BI 页面生成闭环

这一章不再停留在架构层,而是直接看这套方案是怎么落地的。 我在实现时,没有一上来就追求“openClaw 全自动对话直出大屏”,而是先按工程上最稳的方式,把整条链路拆成几个可验证的步骤,逐步打通。

整体实现顺序很明确:

先定数据语义,再打通 SQL 查询闭环,再把查询结果组织成页面数据对象,最后接入 EdgeOne Pages 生成公开访问链接。

这样做的好处很直接:每一层都能单独验证,哪里有问题就查哪里,不会一开始就陷入“系统看起来很智能,但不知道哪一环出了问题”的状态。

3.1固定数据语义

后面无论是 openClaw 的查询规划、SQL 生成,还是页面展示,都是建立在同一个前提上:数据语义字典。

比如同样是“数量”:

  • COUNT(*) 统计的是明细行数
  • COUNT(DISTINCT notice_id) 才是公告数量

再比如金额和数量的口径:

  • 总金额优先用 SUM(COALESCE(total_price, 0))
  • 总数量优先用 SUM(COALESCE(quantity, 0))

所以我在代码之前,先把几个基础文档补齐了,包括:

  • schema.md:定义表粒度和核心字段
  • metrics.md:定义指标口径
  • query_patterns.md:定义常见问题模式
  • page_schema.json:定义页面数据对象格式

这一步的目的不是“写文档”,而是先把系统的语言统一掉。 否则后面模型会按自己的理解写 SQL,SQL 生成器会按另一套逻辑拼查询,页面层又会按第三种口径做展示,整个系统很快就会失控。

我们可以用skill模块化固定语义。

3.2SQL执行层

之前在Openjiuwen的插件开发我已经详细介绍了具体开发流程,这里简单讲述一下。

第一,负责数据库连接。 这里通过环境变量读取连接参数:

代码语言:javascript
复制
def _get_conn():
    return pymysql.connect(
        host=os.getenv("DB_HOST", "127.0.0.1"),
        port=int(os.getenv("DB_PORT", "3306")),
        user=os.getenv("DB_USER", "root"),
        password=os.getenv("DB_PASSWORD", ""),
        database=os.getenv("DB_NAME", os.getenv("AGENT_DB_NAME", "local_bash")),
        charset="utf8mb4",
        cursorclass=pymysql.cursors.DictCursor,
    )

第二,负责 SQL 提取。 因为 openClaw 节点之间传值时,不一定永远传纯 SQL 字符串,有时会传整包 JSON,有时是 {"output":{"result":"SELECT ...;"}},有时又是 {"output:":"SELECT ...;"}。 所以这里专门做了一层 _extract_sql,把不同格式统一抽成最终 SQL。

代码语言:javascript
复制
def _extract_sql(x: Any) -> str:
    """
    兼容:
    1) 纯 SQL 字符串
    2) JSON 字符串:{"output":{"result":"SELECT ...;"}}
    3) JSON 字符串:{"output:":{"result":"SELECT ...;"}}
    4) dict:{"result":"..."} / {"output":{...}} / {"output:":{...}}
    5) 你现在测试用的:{"output:":"SELECT ...;"} (注意 output: 是字符串)
    """
    if x is None:
        return ""

    if isinstance(x, dict):
        if isinstance(x.get("result"), str):
            return x["result"].strip()

        if isinstance(x.get("output"), dict) and isinstance(x["output"].get("result"), str):
            return x["output"]["result"].strip()

        if isinstance(x.get("output:"), dict) and isinstance(x["output:"].get("result"), str):
            return x["output:"]["result"].strip()

        #  兼容:{"output:":"SELECT ...;"}
        if isinstance(x.get("output:"), str):
            return x["output:"].strip()

        return ""

第三,负责 SQL 安全约束。 这里不是简单执行用户传入内容,而是先做约束检查:

  • 只允许 SELECT
  • 禁止多语句
  • 最后一条分号允许,但中间不能再拼接其他语句

这部分逻辑是:

第四,统一输出结构。 执行成功或失败,最终都返回统一 JSON:

代码语言:javascript
复制
@sql_runner_router.router.post("/run")
async def run_sql_post(req: Request, body: RunSQLBody = Body(...)):
    """
    POST /sql_runner/run
    body:
      {
        "sql": "<SQL 或 节点2整包output(JSON字符串/JSON对象)>",
        "limit": 100
      }
    return:
      {status, sql, rows, row_count, error}
    """
  • status
  • sql
  • rows
  • row_count
  • error

这样做的好处很明显: 上层不用去猜底层执行器到底返回了什么格式,后面的 page_builder 也能直接消费。

3.3查询规划

SQL 层跑通之后,下一步不是立刻去接页面,而是先把“查询规划 → SQL 生成”这段单独收敛下来。我这里没有采用“自然语言直接生成 SQL”的做法,而是中间插了一层结构化意图。 比如一个典型问题,会先被组织成这样:

代码语言:javascript
复制
{
  "analysis_type": "group_stat",
  "metric": "公告数量",
  "dimension": "city",
  "filters": {
    "province": "江西",
    "device_name": "CT"
  },
  "time_range": {
    "start": "2025-01-01",
    "end": "2025-12-31"
  },
  "limit": 20
}

有了这一层之后,SQL 生成就不再是随意发挥,而是按明确口径往下拼。

3.4BI生产层

SQL 查出来之后,我没有直接让页面层去吃原始 rows,也没有直接让模型自由生成 HTML。中间又加了一层页面数据对象。

这部分主要由 page_builder.py 完成。 它做的事情是把 SQL 结果转成一个统一结构,比如:

代码语言:javascript
复制
{
  "title": "2025年江西省CT设备采购公告统计",
  "summary_cards": [
    {"label": "公告数", "value": 128},
    {"label": "涉及城市数", "value": 11}
  ],
  "chart": {
    "type": "bar",
    "x_field": "city",
    "y_field": "notice_cnt",
    "data": [
      {"city": "南昌", "notice_cnt": 28},
      {"city": "赣州", "notice_cnt": 19}
    ]
  },
  "table": {
    "columns": ["city", "notice_cnt"],
    "rows": [
      {"city": "南昌", "notice_cnt": 28},
      {"city": "赣州", "notice_cnt": 19}
    ]
  }
}

这一层非常关键。 因为它把“数据结果”和“页面渲染”之间做了一次解耦。

也就是说:

  • SQL 层只负责把数据查对
  • page_builder 负责把数据整理成展示结构
  • EdgeOne Pages 只负责把结构化结果渲染成最终页面

这样一来,页面风格就能统一,输入输出也很稳定。 后面你想改模板,不用碰 SQL;想加新的查询模式,也不用直接动渲染层。

3.5 EdgeOne Pages 页面生产平面

很多人容易把 EdgeOne Pages 理解成“托管网页的地方”,但在这套方案里,它承担的角色更重。

从工程视角看,它至少有三层价值:

第一,它是全栈部署平台。 前端页面、动态 API、无服务器能力都可以在这里统一承载。

第二,它是函数运行时。 你现在实际在用的 mcp-servermcp-client/v1/chat/completions,本质上都在用 Pages Functions / Edge Functions 这一层能力。

第三,它具备页面分享和发布能力。 这点对 ChatBI 特别关键,因为这里不是简单“渲染一下”,而是最终要把 HTML 或页面对象生产成一个可公开访问的链接。

所以在我的方案里,EdgeOne Pages 不是末端附属能力,而是:面向网页生成与分发的云端执行平面。

这一定义其实很贴合整条链路。 前面的 openClaw 和 sql_runner 解决的是“怎么得到正确数据”,而 EdgeOne Pages 解决的是“怎么把这次分析真正交付出去”。

3.6 skill 层和服务层

在具体工程组织上,我没有把所有逻辑都堆到 skill 里,而是拆成两部分:

一部分是 skills/medical-chatbi/,主要负责 openClaw 侧的调用说明和桥接脚本; 另一部分是 services/chatbi_orchestrator/,主要负责真正的中台逻辑。

否则一旦所有内容都塞进 skill,后面 prompt、SQL 生成、页面组织、云端调用混在一起,工程很快就会乱。

第四章:在 openClaw 中完成一次完整链路实现

前面几章把架构、分层和工程化实现拆开讲完之后,真正最关键的一步,其实还是要回到实战本身:这套链路到底能不能在 openClaw 里真正跑通一次。

也就是说,不再停留在“模块已经准备好了”“本地 SQL 插件可用”“EdgeOne Pages 能发布页面”这种局部能力上,而是直接让用户提一个真实问题,然后看系统是否能完整走完下面这条链路:

用户提问 → openClaw 理解问题 → 生成查询意图 → 生成 SQL → 调用 sql_runner → 构建页面数据 → 调用 EdgeOne Pages → 返回 BI 页面链接

输入问题:请帮我统计 2024 年江西省各地市医疗设备采购公告数量,并生成一个 BI 页面:顶部展示关键摘要卡片,中间使用柱状图展示各市公告数对比,底部展示数据明细表,结果按公告数降序排列。

当这句话输入给 openClaw 之后,它不应该立刻自由生成 SQL。 如果直接跳 SQL,系统虽然看起来更“聪明”,但实际很容易在口径、维度、时间边界这些地方漂掉。

openClaw 接到问题后的第一步,不是写 SQL,而是先把它收敛成一个结构化查询意图:https://mcp.edgeone.site/share/A9Xf-_fKG1DwLMMH4Tlzb

第五章:从一次工程实践,到一种新的 BI 交付方式

回头看这整条链路,我真正想说清楚的只有一件事:AI 在数据场景里的价值,已经不只是帮你把数据查出来,而是开始直接生产 BI 页面。

过去很多 ChatBI 系统的终点停在 SQL、rows 或者一段分析说明。这些当然比传统查询方式先进,但从业务交付角度看,它们仍然是中间结果——用户拿到之后还要自己整理、排版、分享,真正的交付并没有完成。这次实践不一样的地方,是我把链路往前多推了一步:用户提一个问题,系统理解、查询、分析,最后把结果组织成页面,返回一个真实可访问的链接。到这一步,系统输出的才真正算一个交付物,而不只是一次回答。

从工程上看,这套方案没有依赖什么特别复杂的东西。真正起作用的就三件事:把数据语义和统计口径写死,不让模型在最基础的业务定义上漂;把查询理解、SQL 执行、页面生成拆成独立层,保证链路受控;把最终结果从数据推进到页面,让系统真正具备交付能力。三件事都不难理解,但每一件落地都需要认真对待,少一件整条链路就容易出问题。

这次验证让我越来越确信一个方向:BI 的终点不应该停在 SQL,而应该继续走到页面,甚至数据大屏。至少在医疗设备采购这个场景里,这件事已经不再只是想法——从一句自然语言出发,系统确实可以最终生成一个可访问、可分享的 BI 页面。

当然,这套方案还有很多值得继续补强的地方——更稳定的 planner、更丰富的报表模板、更标准化的页面 schema,乃至后续扩展成行业分析大屏、区域采购监测大屏。但这些都是下一阶段的事。当前阶段最关键的一步已经迈过去了:我已经不只是在让 AI 帮我查数据,而是开始让 AI 直接把 BI 结果生产出来。

这,是我觉得这次工程实践最值得写下来的地方。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 引言
  • 第一章:ChatBI 的真正瓶颈不在 NL2SQL,在结果交付
    • 1.1 很多 ChatBI 的终点,仍然只是 SQL 或 rows
    • 1.2 从“回答问题”到“生产 BI 页面”完整工程链
    • 1.3 为什么目标不能停在 NL2SQL
    • 第二章:openClaw、sql_runner、EdgeOne Pages三层架构
    • 2.1 openClaw:负责理解问题和组织任务
    • 2.2 sql_runner:只做受控 SQL 执行
    • 2.3 EdgeOne Pages:负责把结果真正变成 BI 页面
    • 2.4 三层拆开之后,工程才真正稳
  • 第三章:工程化实现—从 SQL 插件到 BI 页面生成闭环
    • 3.1固定数据语义
    • 3.2SQL执行层
    • 3.3查询规划
    • 3.4BI生产层
    • 3.5 EdgeOne Pages 页面生产平面
    • 3.6 skill 层和服务层
  • 第四章:在 openClaw 中完成一次完整链路实现
  • 第五章:从一次工程实践,到一种新的 BI 交付方式
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档