从GUI到LUI的进化,报表工具也有了Copilot
报表系统里最烦人的,不是图画不出来,是人已经知道自己要什么,但还得在一堆下拉框里翻半天。
“按区域看一下上周新客转化,顺手拆到渠道。”
这句话在业务嘴里三秒钟。在传统报表工具里,可能要点维度、点指标、选时间、拖字段、配筛选、改图表,再等一次查询。点到最后,业务忘了自己刚才想看什么。
这就是 GUI 的问题。
GUI 不是不好。按钮、菜单、拖拽、配置面板,这套东西把很多复杂能力藏起来了。早年的报表工具如果没有 GUI,普通业务根本用不了。
但 GUI 有个天然缺陷:它要求人按工具的结构思考。
你想看“哪个渠道这周突然掉了”,工具让你先选数据集,再选字段,再选时间,再选图表,再加筛选条件。
人脑想的是问题,系统要的是配置。
中间这层翻译,以前靠人自己完成。现在 LUI 出来了,也就是 Language User Interface,用一句话驱动系统。
报表工具有 Copilot,真正有价值的地方不在“帮你写一句 SQL”。这玩意要是只会把自然语言翻译成 SQL,我一般不敢让它直接上线。SQL 写错了最多报错,写得半对才吓人,查出来一张看起来很合理的错报表,第二天会议上就能吵半小时。
我更愿意把 Copilot 放在一个受控的中间层里。
业务说一句话,Copilot 先拆成一个查询意图,再由系统自己的规则生成 SQL。字段能不能查、指标怎么算、时间口径是什么,全都走白名单。
像下面这种结构就比直接让模型吐 SQL 稳一点:
REPORT_MODEL = {
"orders": {
"table": "dwd_order_day",
"dimensions": {
"region": "region_name",
"channel": "source_channel",
"day": "stat_date"
},
"metrics": {
"pay_amount": "sum(pay_amount)",
"new_users": "count(distinct new_user_id)",
"conversion": "sum(pay_users) / nullif(sum(visit_users), 0)"
}
}
}defbuild_report_sql(plan: dict) -> str:
dataset = REPORT_MODEL.get(plan["dataset"])
ifnot dataset:
raise ValueError("unknown dataset")
dims = []
for d in plan.get("dimensions", []):
if d notin dataset["dimensions"]:
raise ValueError(f"bad dimension: {d}")
dims.append(dataset["dimensions"][d])
metrics = []
for m in plan.get("metrics", []):
if m notin dataset["metrics"]:
raise ValueError(f"bad metric: {m}")
metrics.append(f"{dataset['metrics'][m]} as {m}")
where = ["stat_date between :start_date and :end_date"]
sql = f"""
select
{", ".join(dims + metrics)}
from {dataset["table"]}
where {" and ".join(where)}
group by {", ".join(dims)}
order by {dims[0]} desc
"""
return" ".join(sql.split())
这里有个关键点:Copilot 不直接碰底层表。
它最多生成这种 plan:
plan = {
"dataset": "orders",
"dimensions": ["region", "channel"],
"metrics": ["new_users", "conversion"],
"start_date": "2026-06-03",
"end_date": "2026-06-09"
}print(build_report_sql(plan))
这东西看着啰嗦,但线上系统就该啰嗦一点。尤其是报表,最怕“聪明”。
以前 GUI 时代,用户点错字段,至少自己还知道点了什么。到了 LUI 时代,用户说一句模糊的话,系统要是自作主张补了一堆条件,那问题更隐蔽。
比如业务说:
“看一下最近销售情况。”
最近是几天?销售情况是 GMV、订单数、支付人数,还是客单价?按天看还是按区域看?
这种时候 Copilot 不能装懂。我的习惯是让它反问,或者给一个可确认的计划。
defnormalize_intent(text: str) -> dict:
if"最近"in text and"销售"in text:
return {
"need_confirm": True,
"message": "你要看近7天还是本月?销售情况默认用支付金额和订单数,可以吗?",
"suggest_plan": {
"dataset": "orders",
"dimensions": ["day"],
"metrics": ["pay_amount"],
"time_range": "last_7_days"
}
}return {
"need_confirm": False,
"message": "ok"
}
这个反问能力,比自动生成一张花里胡哨的图更重要。
报表 Copilot 真正该干的活,我觉得有三类。
第一类,帮人少点按钮。
比如“把昨天华东区的转化率按渠道拆开”,它能直接生成筛选条件、维度和指标。这个价值最直接,少点几十下鼠标。
第二类,帮人找异常。
传统报表是人盯着看。LUI 可以反过来问系统:“昨天为什么新客掉了?”系统先跑一组固定排查脚本,不要上来就发挥。
deffind_drop_reason(current, previous):
checks = []for channel in current:
cur = current[channel]["new_users"]
pre = previous.get(channel, {}).get("new_users", 0)
if pre == 0:
continue
drop_rate = (pre - cur) / pre
if drop_rate > 0.2:
checks.append({
"channel": channel,
"drop_rate": round(drop_rate, 4),
"hint": "先看投放、落地页、注册链路"
})
return sorted(checks, key=lambda x: x["drop_rate"], reverse=True)
这段代码没有什么高级算法,但实际排查时就有用。它先把“哪里掉得最明显”拎出来,别让人对着一张总览图瞎猜。
第三类,帮人解释报表。
但解释也要克制。不能一张图出来,旁边自动写八百字小作文。报表解释最好像日志一样,短,带依据。
“华东区新客下降 23%,主要集中在信息流渠道;自然流量变化不大。”
这种解释能用。 “市场环境变化导致整体转化承压”,这种基本没用。
GUI 到 LUI,不是把按钮换成聊天框。
按钮还会在。拖拽也会在。真正变的是入口:以前人先适应工具,现在工具开始适应人的问题。
不过这里别太乐观。报表工具加 Copilot,最容易做成一个“会聊天的取数页面”。看起来很先进,真用起来还是慢、错、虚。
要让它有用,后面必须有几层东西托着:指标口径库、权限模型、查询白名单、异常检测脚本、SQL 成本控制、结果解释模板。
少一层都容易翻车。
尤其权限这块,我一般会先卡死。自然语言不能绕过权限。
defcheck_metric_auth(user, metrics):
allowed = set(user.get("allowed_metrics", []))
denied = [m for m in metrics if m notin allowed]if denied:
raise PermissionError(f"no permission for metrics: {','.join(denied)}")
别小看这几行。很多系统不是被黑掉的,是被“方便一下”方便坏的。
报表工具的 Copilot,最后拼的不是谁会聊天,而是谁能把一句话稳稳落到正确的数据口径上。
GUI 解决的是“怎么操作”。
LUI 解决的是“我想问什么”。
中间那段从问题到数据的路,才是真正值钱的地方。