返回
RSS Snowflake Engineering (Medium) AI 逐段翻译 发布 2026-09-29 22:01 收录于 09-30

Snowflake交互式数仓上的一体化AI应用与可观测性

DataHot 速览

作者在单个 Snowflake Interactive Warehouse 上同时构建零售运营 AI 应用和其可观测性管线。Cortex Agents 基于 Semantic View 生成 Text-to-SQL,查询约 2500 万行销售数据;AI Observability 自动将 trace、span、工具调用和 token 数写入 SNOWFLAKE.LOCAL.AI_OBSERVABILITY_EVENTS,无需 SDK 或 collector。监控看板与 Agent 共用同一仓库,最终发现了一个被 Agent 100% 成功率掩盖的语义视图缺陷。完整 SQL、代码和 Streamlit 应用已发布在 GitHub。

为什么值得关注:对想落地 Data Agent 并解决其可观测性、语义一致性与评估问题的数据团队有直接参考价值;也展示了 Snowflake 交互式数仓、Cortex Agents 与 AI Observability 的一体化实践。

本文目录 14 节
  1. 简介
  2. 数据
  3. AI 智能体设置
  4. 交互式仓库设置
  5. 查询它
  6. 我测量的内容
  7. 查询在哪里花费时间
  8. 并发
  9. AI 可观测性
  10. 可观测性 Interactive Table
  11. Streamlit 监控器
  12. 监控标签页
  13. 结论
  14. 资源

译文

AI 逐段翻译

简介

运行一个 AI 应用及其可观测性管道通常意味着两个独立的系统。我把两者都构建在一个 Snowflake 交互式仓库上。

交互式仓库为你提供亚秒级、高并发的查询执行,具备自动扩缩容能力,并为繁重的异常查询提供回退仓库。Cortex Agents让你能够基于语义视图构建 text-to-SQL 智能体。AI 可观测性会自动将每条 trace、span、工具调用和 token 计数捕获到 SNOWFLAKE.LOCAL.AI_OBSERVABILITY_EVENTS 中。无需 SDK,无需采集器。智能体查询 2500 万行数据,可观测性仪表板以同样的速度读取同一个仓库,而监控捕获到了一个语义视图缺陷,而这个缺陷被智能体自身 100% 的成功率所掩盖。

我构建的内容:

  • 一个零售运营 AI 智能体,基于 2500 万行销售数据(50 家门店,2 年)
  • 一个为 Cortex Analyst 声明数据模型的语义视图
  • 一个以并发方式运行智能体所生成 SQL 的交互式仓库
  • 一个每小时运行的交互式表,将 AI 可观测性 trace 展平为可查询的列
  • 一个用于监控的 Snowflake 内 Streamlit 仪表板,全部运行在同一个仓库上
  • 基于监控所揭示的问题对语义视图进行的修复

前提条件: 一个已启用Cortex Agents和交互式分析的 Snowflake 账户(企业版)。

完整代码、SQL 和 Streamlit 应用位于 GitHub。

数据

想象你是一家中型零售连锁店的运营经理。50 家门店,5 个区域,两年的交易历史,数据量略低于 3000 万行。你的团队要处理诸如“本月西部哪些门店未达成目标?”和“第四季度哪些产品类别导致了退货?”之类的问题。这些问题过去意味着要向 BI 团队提交工单。目标是提供一个基于数据的自然语言界面,无需分析师介入。

RETAIL_DEMO 中的八张表:

完整 DDL 位于 GitHub 上的 setup/01_create_raw_tables.sql。

AI 智能体设置

运营经理用自然英语输入一个问题。Cortex Agent 使用语义视图作为数据模型将其转换为 SQL,在交互式仓库上针对零售表运行它,并返回答案。无需 BI 工单,无需 SQL 知识。

有两个对象使这成为可能:一个基于零售表的语义视图,然后在其上叠加一个 Cortex Agent。语义视图声明了存在哪些表、它们如何连接、哪些列是度量值还是属性、某个指标的含义是什么,以及用户可能如何称呼每样东西:

CREATE OR REPLACE SEMANTIC VIEW RETAIL_DEMO.APP.RETAIL_OPS_SV
  TABLES (
    daily_sales AS RETAIL_DEMO.RAW.DAILY_SALES PRIMARY KEY (sale_id)
      WITH SYNONYMS ('sales', 'transactions', 'orders'),
    stores AS RETAIL_DEMO.RAW.STORES PRIMARY KEY (store_id),
    products AS RETAIL_DEMO.RAW.PRODUCTS PRIMARY KEY (product_id),
    inventory AS RETAIL_DEMO.RAW.INVENTORY PRIMARY KEY (snapshot_id),
    staff_shifts AS RETAIL_DEMO.RAW.STAFF_SHIFTS PRIMARY KEY (shift_id),
    sales_targets AS RETAIL_DEMO.RAW.SALES_TARGETS PRIMARY KEY (store_id, period_month),
    returns AS RETAIL_DEMO.RAW.RETURNS PRIMARY KEY (return_id)
  )
  RELATIONSHIPS (
    daily_sales_to_stores AS daily_sales (store_id) REFERENCES stores (store_id),
    daily_sales_to_products AS daily_sales (product_id) REFERENCES products (product_id),
    inventory_to_stores AS inventory (store_id) REFERENCES stores (store_id),
    inventory_to_products AS inventory (product_id) REFERENCES products (product_id),
    returns_to_daily_sales AS returns (sale_id) REFERENCES daily_sales (sale_id)
  )
  FACTS (
    daily_sales.revenue_fact AS revenue,
    daily_sales.units_sold_fact AS units_sold,
    inventory.units_on_hand_fact AS units_on_hand,
    sales_targets.target_revenue_fact AS target_revenue
  )
  DIMENSIONS (
    stores.region AS region
      WITH SYNONYMS = ('area', 'territory')
      SAMPLE_VALUES ('West', 'Northeast', 'Midwest', 'South', 'Southwest') IS_ENUM,
    daily_sales.sale_date AS sale_date WITH SYNONYMS = ('transaction date'),
    products.category AS category SAMPLE_VALUES ('Apparel', 'Electronics', 'Home', 'Food') IS_ENUM
  )
  METRICS (
    daily_sales.total_revenue AS SUM(revenue_fact),
    returns.return_count AS COUNT(*),
    return_rate_pct AS returns.return_count / NULLIF(daily_sales.transaction_count, 0) * 100
  );

完整语义视图包含 7 张表、9 个关系、13 个事实、16 个维度和 17 个指标。完整 DDL 位于 GitHub 上的 setup/08_create_semantic_view_raw.sql。

模型不是在猜测列名。它读取一个声明的模型。同义词和示例值看起来比实际更重要。它们决定了“territory”如何解析为 region,以及模型如何得知 regions 是一组封闭的五个值。

然后智能体:

CREATE OR REPLACE AGENT RETAIL_DEMO.APP.RETAIL_OPS_AGENT
  FROM SPECIFICATION $$
  models:
    orchestration: claude-haiku-4-5
  tools:
    - tool_spec:
        type: "cortex_analyst_text_to_sql"
        name: "RetailAnalyst"
  tool_resources:
    RetailAnalyst:
      semantic_view: "RETAIL_DEMO.APP.RETAIL_OPS_SV"
      execution_environment:
        type: warehouse
        warehouse: "RETAIL_AI_WH"
  $$;

RETAIL_AI_WH 是交互式仓库,本文的两部分都在这里汇合。

交互式仓库设置

零拷贝交互式意味着你完全跳过 CREATE INTERACTIVE TABLE 步骤。没有数据副本,没有 TARGET_LAG 刷新,也没有运行该刷新的额外仓库。将你的基表直接附加到交互式仓库,即可对现有数据获得完整的交互式速度。

大多数人听到“交互式”就以为需要将表转换为一种特殊类型。交互式表是一种选择,但速度来自仓库,而不是表类型。你附加到交互式仓库的任何表都会获得缓存预热和亚秒级执行,包括共享表和诸如 AI_OBSERVABILITY_EVENTS 之类的系统表:

-- Cluster the base tables date-first, matching how queries actually filter
ALTER TABLE RETAIL_DEMO.RAW.DAILY_SALES CLUSTER BY (sale_date, store_id, product_id);
ALTER TABLE RETAIL_DEMO.RAW.INVENTORY   CLUSTER BY (snapshot_date, store_id, product_id);
ALTER TABLE RETAIL_DEMO.RAW.RETURNS     CLUSTER BY (return_date, store_id);
ALTER TABLE RETAIL_DEMO.RAW.STAFF_SHIFTS CLUSTER BY (shift_date, store_id);
ALTER TABLE RETAIL_DEMO.RAW.SALES_TARGETS CLUSTER BY (period_month, store_id);

-- Search optimization for point lookups on UUID keys
ALTER TABLE RETAIL_DEMO.RAW.DAILY_SALES ADD SEARCH OPTIMIZATION ON EQUALITY(sale_id);

-- The Interactive Warehouse
CREATE OR REPLACE INTERACTIVE WAREHOUSE RETAIL_AI_WH
  WAREHOUSE_SIZE = 'MEDIUM'
  MIN_CLUSTER_COUNT = 1
  MAX_CLUSTER_COUNT = 3;

-- Attach the base tables (triggers cache warming)
ALTER WAREHOUSE RETAIL_AI_WH ADD TABLES (
  RETAIL_DEMO.RAW.DAILY_SALES, RETAIL_DEMO.RAW.INVENTORY,
  RETAIL_DEMO.RAW.RETURNS, RETAIL_DEMO.RAW.STAFF_SHIFTS,
  RETAIL_DEMO.RAW.SALES_TARGETS, RETAIL_DEMO.RAW.STORES,
  RETAIL_DEMO.RAW.PRODUCTS
);

-- Fallback for occasional heavy queries
ALTER WAREHOUSE RETAIL_AI_WH SET FALLBACK_WAREHOUSE = RETAIL_FALLBACK_WH;

聚类是实现分区裁剪的关键。语义视图必须指向所附加的表。如果它指向其他地方,生成的 SQL 就无法在交互式仓库上执行,整个设置在整体查询运行时间方面也就不会产生任何收益。

完整聚类 DDL 位于 setup/09_cluster_raw_tables.sql。

查询它

运营经理输入:“2025 年第四季度各区域的目标达成率是多少?”智能体读取语义视图,编写一个连接 DAILY_SALES、SALES_TARGETS 和 STORES 的查询,在交互式仓库上运行它,并返回答案。

以下是智能体针对该问题实际生成的 SQL 的末尾部分(完整查询在 GitHub 仓库中):

-- 3 CTEs above: one per table (SALES_TARGETS, STORES, DAILY_SALES),
-- each selecting only the columns needed, then aggregated into
-- q4_targets and q4_sales by region.

SELECT
  COALESCE(t.region, a.region) AS region,
  t.target_revenue,
  a.actual_revenue,
  ROUND(a.actual_revenue / NULLIF(t.target_revenue, 0) * 100, 1)
    AS revenue_attainment_pct
FROM q4_targets AS t
FULL OUTER JOIN q4_sales AS a ON t.region = a.region
ORDER BY region;

那段 SQL 是智能体直接编写的。智能体读取了语义视图中声明的关系、事实和维度,并据此组合了查询。

我测量的内容

接下来两节中的数字来自一个基准测试工具。完整脚本位于 GitHub 上的 setup/agent_latency_test.py。它直接调用智能体的 REST agent:run 端点,解析 SSE 事件流,并记录每轮的挂钟延迟、token 计数、SQL 扇出以及智能体执行的每条语句的查询 ID。这些查询 ID 是关键部分,因为它们让我之后能够将基准测试的各轮次关联回 QUERY_HISTORY 和 AI_OBSERVABILITY_EVENTS。

该基准测试有两层:

  • 单用户延迟运行。 一组固定的五个问题——每个表集群一个(收入排名、库存警报、目标达成率、周环比趋势、人员配置与收入)——每个问题问两次。这些运行的中位轮次时间为 18.2 秒。
  • 并发运行。 同一工具以 1、5 和 10 个模拟用户同时提交问题进行运行,每次运行 10 到 20 个问题。这产生了 53 个总轮次和 132 条执行的 SQL 语句,每一条都成功了。三个级别的延迟保持平稳(p50:19.1 秒、19.0 秒、23.2 秒),这正是接下来两节要剖析的结果。

在基准测试之前我还做了一个单独的流量模拟:151 轮混合即席问题按顺序运行。这些轮次稍后会在可观测性部分出现。

查询在哪里花费时间

这些是单用户运行的结果:

SQL 执行并不是瓶颈。可观测性 span 将中位数 19.1 秒的一轮 agent 交互拆分为四个部分:

  • 规划(约 10,600ms,55%)——agent 读取语义视图、选择工具并编写 SQL。这是模型推理:平均每轮 3.3 个规划步骤,每一步都是一次对 LLM 的往返调用。第一个规划步骤(读取完整的语义视图上下文)大约需要 2.1 秒;后续步骤随着对话的累积而增长。
  • 响应生成(约 4,400ms,23%)——模型根据 SQL 结果组织自然语言回答。同样受推理限制。
  • SQL 执行(约 1,300ms,7%)——在 Interactive Warehouse 上运行的实际查询。在单条语句层面,p50 为 96ms(p95:438ms),平均扫描 63 MB。每轮总耗时更高,因为一轮平均包含 2.3 条 SQL 语句。
  • 其他(约 2,700ms,15%)——路由、SSE 分帧、工具分派以及 span 之间的网络开销。

Interactive Warehouse 只占每轮时间的 7%,并且已经将其完全处理好。其余 93% 是模型推理。

并发

一个自然语言问题平均生成 2.4 条 SQL 语句(尾部可达 13 条)。乘以并发用户数,数字就会迅速增长。一个十人的运维团队同时提问,几秒内就可能有 25 条查询在途。

我在并发度为 1、5 和 10 的情况下运行了基准测试,每次运行包含 10 到 20 个问题。p50 每轮延迟保持平稳:19.1 秒、19.0 秒和 23.2 秒。所有运行中的全部 53 轮均成功,共生成 132 条 SQL 语句。更重要的是,看看 Interactive Warehouse 在底层做了什么:在三个并发级别下,SQL p50 都保持在 80–95ms,即使在 10 个并发用户下,SQL p95 也保持在 410ms 以下。随着并发度提高,仓库从 1 个集群自动扩展到 3 个集群,吸收了查询突发,而 SQL 执行时间没有任何下降。Interactive Warehouse 正是为此而构建的:高并发、短查询的大量负载,同时保持一致的响应时间。

这就是执行侧的正常工作。一位运维经理用通俗英语提问,agent 跨三张表编写 SQL,Interactive Warehouse 在 100ms 以内运行完成,十个人可以同时这样做而互不干扰。下一个问题是,agent 是否在做正确的事。

AI 可观测性

这个应用 agent 运行得非常好!现在我需要看看 AI 究竟做了什么才得出这些答案。

每次 Cortex Agent 轮次都会自动写入 SNOWFLAKE.LOCAL.AI_OBSERVABILITY_EVENTS。trace、span、工具调用、延迟、状态、token 计数,以及生成的 SQL 本身。无需 SDK,无需 collector,由已有的 RBAC 进行治理。数据以原始 VARIANT 形式落地:

RECORD_ATTRIBUTES:"snow.ai.observability.agent.tool.sql_execution.status"::STRING
RECORD_ATTRIBUTES:"snow.ai.observability.agent.planning.token_count.total"::STRING
DATEDIFF('millisecond', START_TIMESTAMP, TIMESTAMP)  -- no native DURATION_MS column

Span 属性嵌套在长长的点分键下。Duration 每次都必须派生计算。每次仪表板刷新都要对每一行重新进行 JSON 提取。与那些已经位于可查询列中的零售表不同,这些数据需要一个物化步骤,将 VARIANT 一次性展平为带类型的列,而不是在每次读取时展平。

可观测性 Interactive Table

一条 DDL 语句即可将原始 VARIANT 展平为带类型的列,为仪表板查询进行聚簇,并每小时刷新:

CREATE OR REPLACE INTERACTIVE TABLE RETAIL_DEMO.APP.AGENT_TRACES_IT
  CLUSTER BY (event_date, span_name, trace_id)
  TARGET_LAG = '1 hour'
  WAREHOUSE = RETAIL_REFRESH_WH
AS SELECT
  TRACE:trace_id::STRING                    AS trace_id,
  RECORD:name::STRING                       AS span_name,
  DATE(TIMESTAMP)                           AS event_date,
  DATEDIFF('millisecond', START_TIMESTAMP, TIMESTAMP) AS duration_ms,
  RECORD_ATTRIBUTES:"snow.ai.observability.agent.thread_id"::STRING AS thread_id,
  RECORD_ATTRIBUTES:"snow.ai.observability.agent.status"::STRING    AS status,
  RECORD_ATTRIBUTES:"snow.ai.observability.agent.tool.sql_execution.status"::STRING AS sql_status,
  RECORD_ATTRIBUTES:"snow.ai.observability.agent.tool.sql_execution.final_sql"::STRING AS final_sql,
  TRY_CAST(RECORD_ATTRIBUTES:"snow.ai.observability.agent.planning.token_count.total"::STRING AS NUMBER) AS tokens_total
  -- 26 columns total, full DDL in setup/07
FROM SNOWFLAKE.LOCAL.AI_OBSERVABILITY_EVENTS
WHERE RECORD_ATTRIBUTES:"snow.ai.observability.object.name" = 'RETAIL_OPS_AGENT';

-- Attach to the SAME Interactive Warehouse
ALTER WAREHOUSE RETAIL_AI_WH ADD TABLES (RETAIL_DEMO.APP.AGENT_TRACES_IT);

Interactive Table 通过 TARGET_LAG 每小时自行刷新。无需定时任务,无需 INSERT OVERWRITE。而且它挂载在 RETAIL_AI_WH 上,也就是运行 agent 生成 SQL 的同一个 Interactive Warehouse。可观测性仪表板和 AI 应用共用一个仓库。

Streamlit 监控器

我在 Snowflake 中部署了一个 Streamlit 应用,通过同一个 Interactive Warehouse 读取 AGENT_TRACES_IT。四个标签页:

  • 突发与延迟——一个问题触发多少条 SQL 查询?时间花在哪里?并发:每秒启动的 SQL 数。
  • 可靠性——轮次级成功与 SQL 级失败。agent 凭空捏造的列。错误分类与自我修正模式。
  • 成本与 Token——输入与输出 token 的拆解。最昂贵的轮次。缓存命中率。
  • Trace 浏览器——选取任意单个轮次,查看完整的 span 瀑布图,阅读 agent 实际编写的 SQL。

我查看了可靠性标签页,发现了一些有趣的东西……

监控标签页

我构建的第一个指标是轮次级状态:151 轮中 151 次成功。这看起来不错,直到我检查了 span 级数据:

agent 在大约三分之一的 SQL 语句上进行了静默重试和自我修正。每一轮最终仍然得到了成功的回答,因此轮次级指标从未标记出任何问题。

这些失败遵循一种模式。116 次中有 115 次是错误 000904,无效标识符,集中在一小组模型凭空捏造的列名上。最常见的捏造列是 ST.TOTAL_TARGET_REVENUE,出现了 28 次:

我追溯到了语义视图中。SALES_TARGETS 是唯一没有注册 fact 的度量表。它的指标聚合了一个裸物理列,而其他六张表都投影了一个 _FACT 别名。模型在其他所有地方都学会了 _FACT 模式,在这张表上却找不到落点,于是回退到了指标名称。这一处不一致导致了所有标识符错误的 39%。修复只需两行:

FACTS (
  ...
  sales_targets.target_revenue_fact AS target_revenue,
  sales_targets.target_units_fact   AS target_units
)

该视图自身的 SQL 生成指令已经警告过这种确切情况。它仍然失败了。文字说明无法弥补结构上不一致的 schema。这就是可观测性的论据。失败只是一个列的注册问题,而它在 span 中清晰可见。

结论

AI 应用只有快到人们真正愿意使用,并且可靠到足以托付真实决策,才算有用。一位运维经理问“这个月哪些门店没有完成目标?”需要在几秒内得到答案,而不是几分钟。而当答案错误时,你需要在用户发现之前知道原因。

我已经向你展示了 Interactive Warehouses 如何解决速度问题,让 Cortex Agent 能在超过 2500 万行数据上以亚秒级执行 SQL。以及 AI Observability 如何解决信任问题,自动捕获每一条追踪、每一条失败的 SQL 语句,以及 agent 做出的每一次自我修正。监控发现了一个 semantic view bug,它在 40% 的轮次中静默消耗 token 并增加延迟。

以下是你如何在自己的账户中进行设置:克隆 repo 并运行 setup/01 到 setup/09。下面的 Resources 部分链接了相关文档。

资源

感谢你阅读这篇博客,请告诉我你计划如何在你的组织中使用 Interactive Warehouses 和 AI Observability。谢谢!

免责声明:本文中表达的观点仅代表我个人,不一定代表我的雇主(Snowflake)的观点。

在一个 Snowflake Interactive Warehouse 上运行 AI App 及其 Observability最初发表于 Snowflake Builders Blog: Data Engineers, App Developers, AI, & Data Science 在 Medium 上,人们在那里通过高亮和回应这个故事来继续对话。

这篇内容对你有用吗?

反馈只用于改善内容筛选,不等同于收藏

分享这条资讯
分享海报
保存图片
iOS 也可以长按图片保存