Lightdash MCP Filter Expressions 全指南:为 run_metric_query 与 search_field_values 编写过滤器表达式
【免费下载链接】lightdashAgentic BI. Analytics at the speed of code ⚡️项目地址: https://gitcode.com/GitHub_Trending/li/lightdash
导读
Lightdash 的 AI Agent 系统在通过 MCP(Model Context Protocol)调用run_metric_query、search_field_values等工具时,过滤器不再使用结构化的 JSON 对象,而是采用一种紧凑的**过滤器表达式(Filter Expression)**字符串语法。本文以仓库内置技能文档 SKILL.md 为骨架,深入讲解该语法的放置规则、四类字段操作符、字面量引号规则、硬性限制,并结合packages/common与packages/backend中的解析器、Zod schema 与测试源码,说明这一套机制在底层是如何被生成、解析和校验的。读完本文,你将能准确为 AI 工具编写可被 Lightdash 后端正确解析的过滤器表达式,并理解其与结构化过滤器对象的边界。
一、这个 Skill 是什么:生成与加载机制
filter-expressions是 Lightdash 内置技能(Built-in Skill)之一,其前端定义位于 filterExpressionSkill.ts,而实际下发到 MCP 的 Markdown 文件是 builtInSkills/filter-expressions/SKILL.md。文件头部的注释明确说明它由命令生成:
pnpm -F backend generate:filter-expression-skill即该 Markdown 并非手写维护的静态文案,而是由源码模板(filterExpressionSkill.ts拼接MCP_FILTER_EXPRESSION_GUIDANCE_SECTION)程序化生成,保证文档与实现永不脱节。
Skill 的 frontmatter 声明了它的元信息:
--- name: filter-expressions description: Author filter expressions for run_metric_query and search_field_values, including conditional custom metric filters. availability: [mcp] ---其中availability: [mcp]表示这是一个MCP 专用技能。在 builtInSkills.test.ts 中可以看到两条针对性断言:filter-expressions与table-calculations这两个 skill永远不会被暴露给 AI Agent 运行时,并且 Agent 直接读取也会被拦截(getAiAgentSkill('filter-expressions')返回undefined)。这意味着这套过滤器表达式语法只服务 MCP 场景下的工具调用。
从加载机制看,builtInSkills.ts 中BuiltInSkills类会扫描builtInSkills目录下的每个子目录,读取其中的SKILL.md,解析 frontmatter(matter),并计算sha256内容摘要;每个技能通过skill://lightdash/<name>/SKILL.md这样的命名空间 URI 以 MCP Resource 形式暴露。测试(builtInSkills.test.ts)验证了通过原生资源(getMcpResourceBody(uri))与回退工具(readSkillTool)拿到的内容逐行一致,且skill://index.json中注册了该技能名。
适用边界:本技能只适用于使用表达式语法的工具(如
run_metric_query、search_field_values)。对于 schema 中使用结构化过滤器对象(structured filter objects)的工具,本文语法不适用。写作过滤器前务必先查看每个工具的输入 schema——技能本身不能启用本不可用的工具或过滤模式。
二、核心概念:三个过滤器类别与放置规则
对run_metric_query而言,过滤器的入口是queryConfig.filters。其 Zod schema 定义于 expressionSchemas.ts:
export const filterExpressionsSchema = z .object({ dimensions: filterExpressionInputSchema .nullable() .describe('Flat filter expression for dimension fields.'), metrics: filterExpressionInputSchema .nullable() .describe('Flat filter expression for metric fields.'), tableCalculations: filterExpressionInputSchema .nullable() .describe('Flat filter expression for table calculations.'), }) .strict()由此可以提炼出四条铁律:
- 无需过滤时:将
queryConfig.filters整体设为null;否则dimensions、metrics、tableCalculations三个类别各自独立,可以是字符串表达式,也可以是null。 - 非 null 的类别只包含一条扁平字符串表达式。例如
dimensions只能填一个字符串,不能填数组。 - 类别由字段元数据决定:一个字段放进
dimensions还是metrics,取决于它在探查(field discovery)元数据中的 kind(dimension/metric),绝不能凭字段名或"数字看起来像指标"来臆断。裸的数字维度(raw numeric dimension)应放在dimensions;只有指标和自定义指标(custom metrics)才放在metrics。 - 每个类别内部是扁平的,只用 AND 或只用 OR,二者不可混用;而三个类别之间隐式地用 AND 组合。例如 schema 描述中给出的语义:
dimensions为D1 AND D2、metrics为M1 OR M2时,整体等价于(D1 AND D2) AND (M1 OR M2)。
放置示例
filterGuidance.ts(filterGuidance.ts)中的placementExamples与FILTER_EXPRESSION_PLACEMENT_EXPRESSIONS提供了被直接注入 SKILL.md 的原始示例:
dimensions: orders_status equals=completed,shipped AND orders_order_date inThePast=2{unit:weeks,completed:true} dimensions (alternatives): orders_promo_code startsWith=VIP OR orders_promo_code endsWith=25 metrics: orders_total_order_amount greaterThan=100 tableCalculations: rank lessThanOrEqual=10这些示例还揭示了几条重要细节:
dimensions内部用 AND 连接多个规则(状态等于 completed/shipped并且订单日期在过去两周内);- 备选条件用 OR(促销码以 VIP 开头或以 25 结尾);
- 表计算(table calculations)只能出现在
tableCalculations类别,即使它的底层字段类型是数字维度,也不能塞进dimensions或metrics; - 一个仅用于限制行数、用户并没有要求分组或展示的字段,应当只出现在过滤器表达式里,不要额外加进
queryConfig.dimensions——多选的 dimension 会改变聚合粒度和表计算粒度(grain),导致结果错位。
聚合自定义指标的过滤器
对自定义指标(custom metrics)中的聚合类指标(aggregation custom metric),其条件过滤(conditional filter)是扁平的 AND 表达式。这在 expressionSchemas.ts 中有明确实现:
export const aggregationCustomMetricExpressionSchema = aggregationCustomMetricSchema.extend({ filters: filterExpressionInputSchema .nullable() .describe( 'Optional flat AND expression for conditional metric filters.', ), });即filters是一个可空的扁平 AND 表达式字符串,用于给自定义指标附加行级条件。
search_field_values 的过滤器
对search_field_values工具:无范围搜索时应省略filters字段;当filters存在时,它是一段扁平且仅含维度(dimension-only)的 AND 表达式,用于收窄候选值的搜索范围。相关指导文本定义在 filterGuidance.ts 的EXPRESSION_SEARCH_FIELD_VALUES_FILTER_GUIDANCE常量中。
三、操作符语法全表:string / number / date / boolean
每个类别中的一条"规则(rule)"遵循统一语法:<field> <operator form>,其中 field 是字段 ID,operator form 由操作符及其参数构成。操作符按字段类型分四组定义,其权威来源是 operators.ts 中的filterExpressionOperatorDefinitions数组,以及 expressionSchemas.ts 中按argumentCountByFilterType生成的语法描述。下表完整覆盖 SKILL.md 中给出的全部操作符:
string(字符串维度)
语法:<field> <operator form>
| 操作符 | 参数形式 | 值数量 |
|---|---|---|
isNull | <field> isNull | 0 个值 |
notNull | <field> notNull | 0 个值 |
equals | <field> equals=<value>[,<value>...] | 1+ 个值 |
notEquals | <field> notEquals=<value>[,<value>...] | 1+ 个值 |
startsWith | <field> startsWith=<value>[,<value>...] | 1+ 个值 |
endsWith | <field> endsWith=<value>[,<value>...] | 1+ 个值 |
include | <field> include=<value>[,<value>...] | 1+ 个值 |
doesNotInclude | <field> doesNotInclude=<value>[,<value>...] | 1+ 个值 |
number(数字维度/指标)
语法:<field> <operator form>
| 操作符 | 参数形式 | 值数量 |
|---|---|---|
isNull/notNull | 同 string | 0 个值 |
equals/notEquals | <field> equals=<value>[,<value>...] | 1+ 个值 |
lessThan | <field> lessThan=<value> | 1 个值 |
lessThanOrEqual | <field> lessThanOrEqual=<value> | 1 个值 |
greaterThan | <field> greaterThan=<value> | 1 个值 |
greaterThanOrEqual | <field> greaterThanOrEqual=<value> | 1 个值 |
inBetween | <field> inBetween=<first>,<second> | 2 个值 |
notInBetween | <field> notInBetween=<first>,<second> | 2 个值 |
date(日期维度)
语法:<field> <operator form>
| 操作符 | 参数形式 | 值数量 |
|---|---|---|
isNull/notNull | 同 string | 0 个值 |
equals/notEquals | <field> equals=<value>[,<value>...] | 1+ 个值 |
lessThan/lessThanOrEqual/greaterThan/greaterThanOrEqual | 各 1 个值 | 1 个值 |
inThePast | <field> inThePast=<count>{unit:<unit>,completed:<bool>} | 1 个 count;settings 必填 |
notInThePast | 同上 | 1 个 count;settings 必填 |
inTheNext | 同上 | 1 个 count;settings 必填 |
inTheCurrent | <field> inTheCurrent=<unit> | 1 个 unit |
notInTheCurrent | <field> notInTheCurrent=<unit> | 1 个 unit |
inBetween | <field> inBetween=<first>,<second> | 2 个值 |
日期单位(units)限定为:days、weeks、months、quarters、years(常量filterExpressionDateUnits定义于 operators.ts)。其中completed语义为:completed=false表示包含未满的、部分经过的周期(如"过去 2 周"含当前这一周),completed=true表示只统计完整周期。
boolean(布尔维度)
语法:<field> <operator form>
| 操作符 | 参数形式 | 值数量 |
|---|---|---|
isNull/notNull | 同 string | 0 个值 |
equals | <field> equals=<value> | 1 个值 |
notEquals | <field> notEquals=<value> | 1 个值 |
从 operators.ts 可以看到布尔类型的equals/notEquals的argumentCount被固定为1(不像 string/number/date 那样是oneOrMore),且布尔值直接写成true/false即可。
操作符 × 类型矩阵的实现依据
上述矩阵并非文档臆造,而是由argumentCountByFilterType这张表驱动生成的:
isNull/notNull(presence 类)对四种类型全部可用,参数个数为 0;equals/notEquals四种类型都可用,但布尔仅限 1 值;startsWith/endsWith/include/doesNotInclude通过stringOperators数组映射,仅 string 类型可用,其余类型在unsupportedTypes中被置为null;- 四个比较操作符(
lessThan等)经comparisonOperators映射,仅 number 与 date 可用,且固定 1 个值; inThePast等相对日期操作符、inTheCurrent等当前周期操作符,仅 date 可用;inBetween仅 number/date(各 2 值),notInBetween仅 number(2 值)。
getFilterTypeGrammar(expressionSchemas.ts)正是遍历该定义数组,把上述表格逐条渲染成 SKILL.md 中的### string/### number/### date/### boolean小节——文档与代码由同一份数据驱动,这也是"以代码为唯一事实源"的体现。
四、字面量规则:裸标量、引号与转义
表达式中的字段 ID 与值(标量)遵循一套格式规则,其精确实现位于 examples.ts:
裸标量(bare scalar)可用。一个值若匹配正则/^[^\s,{}=()\\'"]+$/(不含空白、逗号、花括号、等号、括号、反斜杠、引号)且不是保留字and、or、null(不区分大小写),就可以不带引号直接书写。
需要加双引号的场景:
- 包含保留字的值(如字段或值恰好叫
and/or/null); - 包含空白、标点的字符串(典型如撇号
'、括号()),例如 SKILL.md 中给出的示例:orders_product_name equals="Coffee Filters (100pk)"(该示例由常量FILTER_EXPRESSION_PUNCTUATED_STRING_EXAMPLE生成于 examples.ts); - 不确定时,一律加引号。
引号与转义语义:
- 双引号内的逗号、花括号按字面量处理(不再作为参数分隔符或 settings 结构解析);
- 反斜杠
\用于转义(如\"、\\); - 字段 ID 同理:若字段名包含特殊字符或恰好是
and/or,用反引号包裹并转义(见formatFieldId,examples.ts)。
四种操作符参数形态(argumentSyntax)
在 expressionSchemas.ts 中,参数形态按语法类型区分为四种:
| 形态 | 输出示例 | 说明 |
|---|---|---|
none | <operator> [0 values] | 如isNull,不带= |
values | <operator>=<value>[,<value>...] | 普通值列表,=连接 |
relativeDate | <operator>=<count>{unit:<unit>,completed:<bool>} [1 count; settings required] | 相对日期,count 必填、settings 必填 |
currentDate | <operator>=<unit> [1 unit] | 当前周期,只填一个单位 |
例如inThePast=2{unit:weeks,completed:true}中:2是 count,{unit:weeks,completed:true}是 settings(花括号结构)。settings 中的值会与 arguments 一起计入规则值总数(见下节限制)。
五、硬性限制:四条边界
parse.ts 定义了四条防失控边界,SKILL.md 中的 "Limits" 一行即来源于此:
export const FILTER_EXPRESSION_MAX_LENGTH = 16_384; // 整个表达式最长 16384 字符 export const FILTER_EXPRESSION_MAX_RULES = 256; // 最多 256 条规则 export const FILTER_EXPRESSION_MAX_VALUES_PER_RULE = 256; // 每条规则最多 256 个值(含 settings 值) export const FILTER_EXPRESSION_MAX_LITERAL_LENGTH = 256; // 每个字面量(字段名/值/setting 名/值)最长 256 字符这些限制在validateParsedFilterExpression(parse.ts)中被逐一强制执行,任何超限都会返回带span(出错位置)的FILTER_EXPRESSION_BOUNDS_EXCEEDED错误;表达式整体超长则会在解析前直接拦截(parse.ts)。此外,输入 schema 层也做了z.string().min(1).max(FILTER_EXPRESSION_MAX_LENGTH)的预校验(expressionSchemas.ts)。
六、底层解析:PEG 语法、AST 与连接符约束
解析器与 AST
过滤器表达式不是正则硬匹配,而是由PEG(Parsing Expression Grammar)语法生成的解析器解析。语法源文件为 grammar.ts(filterExpressionGrammar,共 219 行),配合 parser.ts 使用,AST 类型定义于 ast.ts。
解析入口parseFilterExpression(input)(parse.ts)的流程为:
- 检查整体长度是否超过
FILTER_EXPRESSION_MAX_LENGTH; - 调用生成的 PEG 解析器,捕获语法错误并映射为
FILTER_EXPRESSION_SYNTAX错误(带行号/列号定位,getPositionAtOffset会把偏移量换算为{line, column}); - 对解析出的 AST 执行四条边界校验;
- 返回
{ success: true, expression }或带错误码、消息、span 的失败结果。
连接符约束:"AND 与 OR 不可混用"
PEG 语法中EXPRESSION产生式(grammar.ts)在遍历规则间的连接符时,一旦发现前后连接符不一致,就立即返回专用错误:
FILTER_EXPRESSION_MIXED_CONNECTORS A flat filter expression cannot mix AND and OR connectors.因此dimensions: a equals=1 OR b equals=2 AND c equals=3这类混合写法必然解析失败。同时需要注意:schema 对不同的工具/类别有**连接符策略(connector policy)**差异——FilterExpressionConnectorPolicy分为andOnly与andOr两种(expressionSchemas.ts):
andOr:规则可用 AND 或 OR 连接,但同一表达式内不可混用(run_metric_query的dimensions/metrics/tableCalculations即此策略,对应FILTER_EXPRESSION_GRAMMAR_DESCRIPTION);andOnly:只允许 AND,OR 不受支持(对应FILTER_EXPRESSION_AND_ONLY_GRAMMAR_DESCRIPTION,适用于search_field_values.filters等 AND-only 场景)。
两条语法描述字符串分别由getFilterExpressionGrammarDescription('andOr')与getFilterExpressionGrammarDescription('andOnly')生成,SKILL.md 中写入的是 andOr 版本。
错误信息的工程化
所有解析错误都携带span(起止 offset、行、列),便于 MCP 客户端把错误定位回表达式原文;语法错误统一归并为FILTER_EXPRESSION_SYNTAX,配合生成器原生错误信息(parse.ts)一起返回,方便 LLM 在下一轮修正自己的输出。
七、运行时差异:MCP 与 Agent 的指导内容并不相同
filterGuidance.ts(filterGuidance.ts)中定义了filterExpressionGuidanceByRuntime,为agent与mcp两种运行时生成不同的指导段落:
| 维度 | agent 运行时 | mcp 运行时(本 SKILL.md) |
|---|---|---|
| 适用工具 | generateVisualization | run_metric_query |
| 额外规则 | 无 | "聚合自定义指标过滤器是扁平 AND 表达式" |
| searchFieldValues | searchFieldValues.filters(驼峰) | search_field_values.filters(下划线) |
此外,agent 运行时还额外附加了时间过滤(Time-based filtering)指导(filterGuidance.ts 与结构化过滤器版本的STRUCTURED_FILTER_GUIDANCE_SECTION):只要用户提到时间窗口("last 3 months"、"this quarter"、"since March"),就必须在维度表达式里显式加入日期规则,描述性文字、排序、limit 或结果数据中观察到的日期都不能替代真正的过滤器;相对窗口用inThePast,显式区间用inBetween;相对窗口以提示词顶部声明的"今天"为基准解析,绝不能锚定字段元数据或查询结果里的日期;多个粒度高对齐的周期(如 2025-03 与 2025-05)优先用一条多值equals规则,让所有条件保持在 AND 之下;limit只能用于用户明确要求的 "top N" 场景,不能用来近似时间窗口。MCP 版本的 SKILL.md 虽未内联这段长文,但inThePast/inBetween的操作符语义与之一致。
八、实战要点速查
- 先看工具 schema:
run_metric_query用queryConfig.filters,search_field_values用顶层filters;若工具 schema 是结构化过滤器对象,则本语法不适用。 - 无过滤就写
null(queryConfig.filters: null;search_field_values直接省略filters)。 - 类别归属看元数据 kind:维度进
dimensions,指标/自定义指标进metrics,表计算进tableCalculations。 - 每类别一条扁平字符串;内部统一 AND 或统一 OR;类别间隐式 AND。
- 值写法:裸标量优先;含保留字(and/or/null)、空白或标点时用双引号;不确定就加引号;引号内逗号、花括号为字面量,
\转义。 - 日期:相对窗口
inThePast=2{unit:weeks,completed:true}(settings 必填),当前周期inTheCurrent=month,显式区间inBetween=2025-01-01,2025-01-31。 - 别越界:≤256 条规则、每条 ≤256 值(含 settings)、每个字面量 ≤256 字符、整个表达式 ≤16384 字符;不要混用 AND/OR。
- 修错看 span:解析错误会带行/列定位,据此精确修改表达式,而不是整段重写。
延伸阅读
- 技能文档本体:filter-expressions/SKILL.md
- 生成该文档的模板与指导段落:filterExpressionSkill.ts、filterGuidance.ts
- 操作符定义与语法渲染:operators.ts、expressionSchemas.ts
- 解析与校验:parse.ts、grammar.ts、ast.ts
- 字面量格式与示例生成:examples.ts
- 技能加载与 MCP 资源暴露:builtInSkills.ts、builtInSkills.test.ts
【免费下载链接】lightdashAgentic BI. Analytics at the speed of code ⚡️项目地址: https://gitcode.com/GitHub_Trending/li/lightdash
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考