wywincl/data-analysis-agent ↗★ 0
dsh-data-analysis-agent
提供多数据源连接、语义层及图表导出的工作台 适合需要进行 Text2SQL、数据可视化及看板导出的分析任务。
安裝
npx -p @deepseek-ai/dsh dsh plugin --profile web add github:wywincl/data-analysis-agent說明文件
閱讀完整 README ↗4. 配置数据源:编辑 ~/.dsh-rd/profiles/rd/cordis.patch.yml
配置
插件配置
- id: data-analysis
config:
semanticFile: /path/to/semantic.yaml # 语义层配置(可选,热加载)
defaultMaxRows: 500 # 单查询行上限(注入 LIMIT + 硬截断)
defaultTimeoutMs: 20000 # 语句超时
modelRowCap: 50 # 模型可见行数(其余经 resultId 引用)
chartDataCap: 500 # 单图最大数据点
schemaCacheTtlMs: 300000 # Schema 缓存
exportDir: '' # /data-dashboard 输出目录,默认 ~/Downloads/dsh-exports
dataSources:
- name: demo
type: sqlite
file: /path/to/demo.db
approvalMode: auto # auto | ask
- name: shop-mysql
type: mysql
host: 10.0.0.5
port: 3306
database: shop
user: analytics_ro
password: !!js process.env.MYSQL_ANALYTICS_PASSWORD
approvalMode: ask
- name: ck-log
type: clickhouse
host: http://ck-prod
database: logs
user: readonly
password: !!js process.env.CK_PASSWORD
- name: spark-lake
type: spark # v1 为 Mock,真实后端见下方路线
以上全部字段也可在 设置 → 插件 → 数据库工作台 卡片里在线编辑(保存即热生效,密码留空保持不变)。
语义层配置
三类条目(参考 dsh-data-analysis-agent Catalog 的 meaning/term/metric 设计,落成声明式 YAML):
| 条目 | 作用 |
|---|---|
entities | meaning:表/列的业务含义,叠加进 inspect_schema 的输出 |
terms | term:业务术语与别名,注入系统提示词统一口径 |
metrics | metric:可执行指标定义,query_metric 按此生成受治理的 SQL |
这组构件构成一个轻量分析本体(OBDA 风格:本体是虚拟视图层,实例留在数据库里,查询经本体编译成 SQL):entities 是概念、columns 是属性、relationships 是具名的对象关系、terms 是词汇层、lint 规则是轻量公理。刻意保持最小承诺——数据类型不重复声明(来自内省),只承诺 text2SQL 治理所需要的部分。
完整示例见 demo/semantic.yaml(组合根)与 demo/semantic/(拆分后的实体/术语/指标文件)。
多文件拆分:include
一个文件塞几十个指标会变得没法 review。根文件用 include 按域拆开:
include:
- ./semantic/entities.yaml # 具体路径
- ./semantic/metrics # 目录简写(只取该层的 *.yaml)
- ./semantic/domains/**/*.yaml # 递归 glob(* / ** / ? 均支持,零依赖实现)
defaults:
datasource: demo
- 合并顺序:被 include 的文件在前、include 它的文件在后,所以后加载的覆盖先加载的(同
table/ 同name视为同一条目)。刻意覆盖共享 base 是合法用法,但同名冲突会记一条duplicate-definition告警,并点名被丢弃的那个文件。 - 环安全:
a → b → a不会死循环,每个文件只贡献一次。 - 拼错即报错:
include一个都匹配不到时直接加载失败(沿用上一次有效配置),而不是静默丢掉半个目录。 - 热加载覆盖全图:include 进来的每个文件及其所在目录都在监听范围内 —— 改任意一个文件会重载,glob 目录里新增文件同样会触发(文件级 watch 看不到新文件,所以目录也在监听集合里)。
复用:三层继承(defaults → entity → extends → metric)
defaults:
datasource: demo
entities:
- table: daily_revenue
timeField: dt # 该实体下所有指标默认按 dt 看时间
dimensions: [tenant]
metrics:
- name: paid_amount # 基础口径:只统计已支付金额
entity: orders
measure: amount
agg: sum
filters: ["status = 'paid'"]
- name: daily_revenue
extends: paid_amount # 只声明自己要改的字段
label: 每日收入
timeField: created_at
dimensions: [status, user_id]
- 标量字段(
datasource/entity/measure/agg/timeField/dimensions/unit/label…):最近的声明生效,优先级为defaults→entity→extends链 → 指标自身。 filters是唯一例外:逐级累加(AND)。子指标声明自己的过滤条件不会顶掉基础口径 —— 口径是约束,不该被"重写"掉。完全相同的谓词会去重。extends支持多层;链的根节点(没有extends的那一个)必须自己声明entity与agg。extends在组合阶段就被解析掉,下游(SQL 构建 / 指标目录 / 提示词)拿到的永远是自包含指标。
本体构件:relationships / key / values / 实体继承
entities:
- table: orders
key: id # 主键列(inspect_schema / catalog 标注 PK)
relationships: # 具名关系:订单 → 下单用户(join 路径)
- entity: users
on: [user_id, id]
name: 下单用户
cardinality: many-to-one # 默认 many-to-one
columns:
- name: status
label: 订单状态
values: # 枚举值域:模型可见,lint 校验 filters 取值
- { value: paid, label: 已支付 }
- { value: refunded, label: 已退款 }
metrics:
- name: daily_city_revenue
entity: orders
joins: [users] # 通过 relationships 联表(支持多跳,≤3 跳)
dimensions: [users.city] # 联表后可用 Entity.column 形式引用
measure: amount
agg: sum
relationships是实体间的对象关系:on是 join 列对,name/cardinality给模型可读的语义。指标用joins: [目标实体]联表后,维度/过滤/度量即可引用Entity.column。key声明主键列,让模型知道"一行是什么"。values声明枚举列的值域,叠加进inspect_schema;lint 会校验指标filters里的= 'x'/IN (…)取值是否在值域内(enum-filter-value-unknown)。- 实体也能
extends:子实体按字段合并继承列标注(子覆盖同名列的对应字段)、继承关系与主键;filters/rowFilter同样逐级累加。继承关系也参与joins可达性判定。 - scaffold 自动推断:工作台"从数据源生成"会按命名约定推断
X_id外键关系(many-to-one)和id主键,生成的起步层天然支持跨表指标。 - 编辑器保全:工作台语义编辑器保存时,无表单控件的字段(relationships/values/ratio 两侧/joins/expression…)原样写回,不会因为改了一个指标就丢掉手写的结构。
体检:/data-semantic-lint
语法与结构错误会让加载直接失败(沿用上一次有效配置);"能加载、但大概率是笔误"的语义问题记为告警,不阻塞查询,在 list_semantic 输出和 /data-semantic-lint 里可见:
| code | 含义 |
|---|---|
duplicate-definition | 同名 entity/term/metric 被覆盖,点名被丢弃的来源文件 |
unknown-dimension-column | 维度未在该 entity 的 columns 中声明(拼错会在 GROUP BY 时直接报错) |
unknown-measure-column / unknown-timefield-column | 度量列 / 时间列未声明 |
duplicate-dimension | 同一维度在 dimensions 里重复 |
count-with-measure | agg: count 却写了 measure(生成的是 COUNT(*),该字段被忽略) |
term-alias-collision | 两个术语的名称/别名撞车,模型会选错口径 |
metric-shadows-term | 指标名与术语同名,提示词中出现歧义 |
unbounded-metric | 既无 timeField 也无 filters,查询会全表聚合 |
missing-label | 缺 label 的指标数(汇总成一条),模型只能看到 id |
relationship-column-missing | 关系的 join 列未在实体 columns 中声明 |
unknown-key-column | 实体 key 主键列未在 columns 中声明 |
enum-filter-value-unknown | 指标 filters 对枚举列使用了 values 值域之外的取值 |
有意不做的一件事:不检查 filters 里的任意列名。它是刻意保留的自由 SQL 谓词(从 status = 'paid' 到 dt >= date_sub(now(), interval 7 day)),用正则去猜列名只会产出更多误报。唯一的例外是声明了 values 值域的枚举列:配置明确承诺过取值集合,此时 = 'x' / IN (…) 的比较才被校验——这是精确匹配,不是猜测。
插件配置
- id: data-analysis
config:
semanticFile: /path/to/semantic.yaml # 语义层配置(可选,热加载)
defaultMaxRows: 500 # 单查询行上限(注入 LIMIT + 硬截断)
defaultTimeoutMs: 20000 # 语句超时
modelRowCap: 50 # 模型可见行数(其余经 resultId 引用)
chartDataCap: 500 # 单图最大数据点
schemaCacheTtlMs: 300000 # Schema 缓存
exportDir: '' # /data-dashboard 输出目录,默认 ~/Downloads/dsh-exports
dataSources:
- name: demo
type: sqlite
file: /path/to/demo.db
approvalMode: auto # auto | ask
- name: shop-mysql
type: mysql
host: 10.0.0.5
port: 3306
database: shop
user: analytics_ro
password: !!js process.env.MYSQL_ANALYTICS_PASSWORD
approvalMode: ask
- name: ck-log
type: clickhouse
host: http://ck-prod
database: logs
user: readonly
password: !!js process.env.CK_PASSWORD
- name: spark-lake
type: spark # v1 为 Mock,真实后端见下方路线
以上全部字段也可在 设置 → 插件 → 数据库工作台 卡片里在线编辑(保存即热生效,密码留空保持不变)。
语义层配置
三类条目(参考 dsh-data-analysis-agent Catalog 的 meaning/term/metric 设计,落成声明式 YAML):
| 条目 | 作用 |
|---|---|
entities | meaning:表/列的业务含义,叠加进 inspect_schema 的输出 |
terms | term:业务术语与别名,注入系统提示词统一口径 |
metrics | metric:可执行指标定义,query_metric 按此生成受治理的 SQL |
这组构件构成一个轻量分析本体(OBDA 风格:本体是虚拟视图层,实例留在数据库里,查询经本体编译成 SQL):entities 是概念、columns 是属性、relationships 是具名的对象关系、terms 是词汇层、lint 规则是轻量公理。刻意保持最小承诺——数据类型不重复声明(来自内省),只承诺 text2SQL 治理所需要的部分。
完整示例见 demo/semantic.yaml(组合根)与 demo/semantic/(拆分后的实体/术语/指标文件)。
多文件拆分:include
一个文件塞几十个指标会变得没法 review。根文件用 include 按域拆开:
include:
- ./semantic/entities.yaml # 具体路径
- ./semantic/metrics # 目录简写(只取该层的 *.yaml)
- ./semantic/domains/**/*.yaml # 递归 glob(* / ** / ? 均支持,零依赖实现)
defaults:
datasource: demo
- 合并顺序:被 include 的文件在前、include 它的文件在后,所以后加载的覆盖先加载的(同
table/ 同name视为同一条目)。刻意覆盖共享 base 是合法用法,但同名冲突会记一条duplicate-definition告警,并点名被丢弃的那个文件。 - 环安全:
a → b → a不会死循环,每个文件只贡献一次。 - 拼错即报错:
include一个都匹配不到时直接加载失败(沿用上一次有效配置),而不是静默丢掉半个目录。 - 热加载覆盖全图:include 进来的每个文件及其所在目录都在监听范围内 —— 改任意一个文件会重载,glob 目录里新增文件同样会触发(文件级 watch 看不到新文件,所以目录也在监听集合里)。
复用:三层继承(defaults → entity → extends → metric)
defaults:
datasource: demo
entities:
- table: daily_revenue
timeField: dt # 该实体下所有指标默认按 dt 看时间
dimensions: [tenant]
metrics:
- name: paid_amount # 基础口径:只统计已支付金额
entity: orders
measure: amount
agg: sum
filters: ["status = 'paid'"]
- name: daily_revenue
extends: paid_amount # 只声明自己要改的字段
label: 每日收入
timeField: created_at
dimensions: [status, user_id]
- 标量字段(
datasource/entity/measure/agg/timeField/dimensions/unit/label…):最近的声明生效,优先级为defaults→entity→extends链 → 指标自身。 filters是唯一例外:逐级累加(AND)。子指标声明自己的过滤条件不会顶掉基础口径 —— 口径是约束,不该被"重写"掉。完全相同的谓词会去重。extends支持多层;链的根节点(没有extends的那一个)必须自己声明entity与agg。extends在组合阶段就被解析掉,下游(SQL 构建 / 指标目录 / 提示词)拿到的永远是自包含指标。
本体构件:relationships / key / values / 实体继承
entities:
- table: orders
key: id # 主键列(inspect_schema / catalog 标注 PK)
relationships: # 具名关系:订单 → 下单用户(join 路径)
- entity: users
on: [user_id, id]
name: 下单用户
cardinality: many-to-one # 默认 many-to-one
columns:
- name: status
label: 订单状态
values: # 枚举值域:模型可见,lint 校验 filters 取值
- { value: paid, label: 已支付 }
- { value: refunded, label: 已退款 }
metrics:
- name: daily_city_revenue
entity: orders
joins: [users] # 通过 relationships 联表(支持多跳,≤3 跳)
dimensions: [users.city] # 联表后可用 Entity.column 形式引用
measure: amount
agg: sum
relationships是实体间的对象关系:on是 join 列对,name/cardinality给模型可读的语义。指标用joins: [目标实体]联表后,维度/过滤/度量即可引用Entity.column。key声明主键列,让模型知道"一行是什么"。values声明枚举列的值域,叠加进inspect_schema;lint 会校验指标filters里的= 'x'/IN (…)取值是否在值域内(enum-filter-value-unknown)。- 实体也能
extends:子实体按字段合并继承列标注(子覆盖同名列的对应字段)、继承关系与主键;filters/rowFilter同样逐级累加。继承关系也参与joins可达性判定。 - scaffold 自动推断:工作台"从数据源生成"会按命名约定推断
X_id外键关系(many-to-one)和id主键,生成的起步层天然支持跨表指标。 - 编辑器保全:工作台语义编辑器保存时,无表单控件的字段(relationships/values/ratio 两侧/joins/expression…)原样写回,不会因为改了一个指标就丢掉手写的结构。
体检:/data-semantic-lint
语法与结构错误会让加载直接失败(沿用上一次有效配置);"能加载、但大概率是笔误"的语义问题记为告警,不阻塞查询,在 list_semantic 输出和 /data-semantic-lint 里可见:
| code | 含义 |
|---|---|
duplicate-definition | 同名 entity/term/metric 被覆盖,点名被丢弃的来源文件 |
unknown-dimension-column | 维度未在该 entity 的 columns 中声明(拼错会在 GROUP BY 时直接报错) |
unknown-measure-column / unknown-timefield-column | 度量列 / 时间列未声明 |
duplicate-dimension | 同一维度在 dimensions 里重复 |
count-with-measure | agg: count 却写了 measure(生成的是 COUNT(*),该字段被忽略) |
term-alias-collision | 两个术语的名称/别名撞车,模型会选错口径 |
metric-shadows-term | 指标名与术语同名,提示词中出现歧义 |
unbounded-metric | 既无 timeField 也无 filters,查询会全表聚合 |
missing-label | 缺 label 的指标数(汇总成一条),模型只能看到 id |
relationship-column-missing | 关系的 join 列未在实体 columns 中声明 |
unknown-key-column | 实体 key 主键列未在 columns 中声明 |
enum-filter-value-unknown | 指标 filters 对枚举列使用了 values 值域之外的取值 |
有意不做的一件事:不检查 filters 里的任意列名。它是刻意保留的自由 SQL 谓词(从 status = 'paid' 到 dt >= date_sub(now(), interval 7 day)),用正则去猜列名只会产出更多误报。唯一的例外是声明了 values 值域的枚举列:配置明确承诺过取值集合,此时 = 'x' / IN (…) 的比较才被校验——这是精确匹配,不是猜测。