wywincl/data-analysis-agent ↗★ 0

dsh-data-analysis-agent

提供多数据源连接、语义层及图表导出的工作台 适合需要进行 Text2SQL、数据可视化及看板导出的分析任务。

包名
dsh-data-analysis-agent
兼容性
待验证
Harness 依赖范围
*
Cordis 依赖范围
*
版本
0.1.1
最近更新
2026年9月19日

安装

$npx -p @deepseek-ai/dsh dsh plugin --profile web add github:wywincl/data-analysis-agent

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):

条目作用
entitiesmeaning:表/列的业务含义,叠加进 inspect_schema 的输出
termsterm:业务术语与别名,注入系统提示词统一口径
metricsmetric:可执行指标定义,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-measureagg: 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):

条目作用
entitiesmeaning:表/列的业务含义,叠加进 inspect_schema 的输出
termsterm:业务术语与别名,注入系统提示词统一口径
metricsmetric:可执行指标定义,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-measureagg: 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 (…) 的比较才被校验——这是精确匹配,不是猜测。