mcpField
S
signals

signals

github.com/posthog数据与数据库76401 次安装今天更新
代码仓库

关于 signals

signals 是用于通过 HogQL 查询 ClickHouse 中原始产品观察数据的 PostHog 技能。它支持语义或关键词搜索信号、查看报告关联的观察记录,并分析精选报告工具未涵盖的信号来源。

主要功能

  1. 01语义搜索信号文本
  2. 02使用 ILIKE 关键词搜索
  3. 03获取报告关联的信号
  4. 04探索信号来源和类型
  5. 05查询时去重信号版本

适用场景

  1. 01查找与产品问题相关的近期观察
  2. 02检查 Signal Report 背后的原始信号
  3. 03列出客户可用的信号类型

MCP 相关

通过 PostHog 的 `posthog:execute-sql` MCP 工具,使用 HogQL 查询 `document_embeddings`。

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/posthog/skills --skill signals

安装后在 Claude Code 中输入/signals确认已加载。

详细介绍

查询 Signals

什么是 Signals?

Signals 是 PostHog 通过监控客户在多个来源中的产品数据自动生成的观测结果,包括错误跟踪、Web 分析、实验、会话回放等。每条 signal 都是对值得关注事项的简短自然语言描述(例如:“/checkout 的错误率飙升了 3 倍”)。

Signals 按 Signal Reports 分组。当报告积累了足够的权重后,系统会对其进行摘要并评估其可操作性。Signal report 代表一组相关观测结果,这些结果共同描述了一个有意义的问题或趋势。

Signals 及其嵌入向量存储在 ClickHouse 的 document_embeddings 表中,可通过 posthog:execute-sql MCP 工具使用 HogQL 查询。它们可能有助于以语义方式查询用户产品中最近发生的事件。

何时使用此技能,而不是 inbox-exploration

这两项技能覆盖了同一产品的不同层面:

  • inbox-exploration — 通过专用 MCP 工具(inbox-reports-list、inbox-reports-retrieve、inbox-source-configs-list、inbox-source-configs-retrieve)访问精选报告层。适用于“我的收件箱里有什么?”“哪些事项可采取行动?”按状态 / 来源 / 建议审核人筛选报告,或按 ID 或 URL 查找特定报告。
  • 此技能(signals) — 通过 document_embeddings 上的 HogQL 访问原始 signal 层。当精选报告层无法满足需求时使用:对 signal 文本进行语义搜索,获取对特定报告有贡献的所有 signal,查看有哪些类型的 signal,或进行报告工具未提供的临时分析。

典型做法是先使用 inbox-exploration,获取 report_id 或了解用户关注的领域;然后,当用户想查看原始观测结果时,再使用此技能。

表和列参考

HogQL 表别名为 document_embeddings。HogQL 会自动将查询限制在当前团队范围内,因此无需按 team_id 筛选。Signals 的关键列如下:

列类型说明
productString产品类别 — signals 的值始终为 'signals'
document_typeString文档类型 — signals 的值始终为 'signal'
model_nameString嵌入模型 — 始终为 'text-embedding-3-small-1536'
document_idString唯一的 signal ID(UUID)
timestampDateTime64(3)signal 的创建时间
inserted_atDateTime64(3)此行版本的插入时间(用于去重和软删除)
contentStringsignal 的描述文本
metadataString包含 report_id、来源信息、权重、删除标记等内容的 JSON 字符串
embeddingArray(Float64)1536 维嵌入向量

必需筛选条件

每个 signals 查询都 MUST 包含以下四个筛选条件。缺少其中任何一个,都可能导致查询因模型无效而失败、返回错误数据,或触发不必要的高成本扫描:

WHERE model_name = 'text-embedding-3-small-1536'
  AND product = 'signals'
  AND document_type = 'signal'
  AND timestamp >= now() - INTERVAL 30 DAY

model_name 筛选条件尤其关键——HogQL 引擎会使用它路由到正确的底层 ClickHouse 表。如果缺少 WHERE model_name = ... 等值筛选条件,或使用了未知模型,查询将失败并报错“Invalid model name”(此处不能使用 IN 或其他表达式)。

product 和 document_type 筛选条件同样重要——同一模型包含来自多个产品的数据(例如错误跟踪、AI memory)。如果没有这些筛选条件,结果中会混入无关数据。

出于性能考虑,必须添加 timestamp 筛选条件——该表按周分区,TTL 为 3 个月。始终使用 now() - INTERVAL N DAY(或 WEEK、MONTH 等)设置时间范围。除非有理由查询更早的数据,否则默认查询最近 30 天。通常,较新的数据更可能相关,除非是在调查长期存在的问题。

去重模式

底层表可能包含同一信号的多个版本(例如软删除后重新发出)。读取数据时 MUST 始终使用子查询包装,并通过按 document_id 分组的 argMax(..., inserted_at) 进行去重。

注意: HogQL 支持对原始 metadata JSON 列使用 metadata.field_name 点号访问,但该列经过 argMax() 等聚合函数后会丢失类型信息。MUST 在内部去重子查询中提取各个 metadata 字段——不要将整个 metadata 数据块传入 argMax,再在外层查询中使用点号访问,否则会因类型错误而失败。

HogQL 的 JSON 点号访问始终会将值提取为 Nullable(String),无论底层 JSON 类型是什么。这意味着 metadata.deleted 是字符串 'true'、'false' 或 null,而不是 Bool。请使用 deleted != 'true'——不要使用 NOT deleted。

SELECT ... FROM (
    SELECT
        document_id,
        argMax(content, inserted_at) as content,
        argMax(metadata.report_id, inserted_at) as report_id,
        argMax(metadata.source_product, inserted_at) as source_product,
        argMax(metadata.source_type, inserted_at) as source_type,
        argMax(metadata.deleted, inserted_at) as deleted,
        argMax(embedding, inserted_at) as embedding,
        argMax(timestamp, inserted_at) as signal_ts
    FROM document_embeddings
    WHERE model_name = 'text-embedding-3-small-1536'
      AND product = 'signals'
      AND document_type = 'signal'
      AND timestamp >= now() - INTERVAL 1 MONTH
    GROUP BY document_id
)
WHERE deleted != 'true'

只有在确实需要进行相似度搜索时,才在内部子查询中选择 embedding 列——它是一个包含 1536 个元素的浮点数组,否则实例化该列的成本很高。

embedText() 函数

embedText() 是一个 HogQL 函数,可在查询编译时将文本字符串转换为 embedding 向量。它会调用 embedding API,并在执行查询前将生成的向量内联为常量。这意味着你无需任何外部 embedding 步骤,就能通过单个查询执行语义搜索。

签名: embedText(text, model_name)

  • text — 要进行 embedding 的字符串。必须是字符串字面量,不能是列引用。
  • model_name — 要使用的 embedding 模型。对于 signals,始终使用 'text-embedding-3-small-1536'。

两个参数都必须是字面量字符串。不能传入列值或表达式——该函数在编译时解析,而不是逐行解析。

使用 cosineDistance() 进行相似度搜索

使用 cosineDistance(embedding, ...) 按语义相似度对信号排序。值越低 = 越相似。始终使用 ORDER BY distance ASC,并添加 LIMIT。

cosineDistance(embedding, embedText('your search text', 'text-embedding-3-small-1536')) as distance

embedding 模型(text-embedding-3-small-1536)采用 matryoshka 表示学习,因此 embedding 维度按重要性排序。这意味着即使在高维情况下,相似度搜索也能很好地工作——维度灾难在这里并非重大问题。

元数据 JSON 字段

metadata 列是一个 JSON 字符串。HogQL 仅支持对原始表列使用 metadata.field_name 点号访问。聚合后(例如使用 argMax),JSON 类型会丢失,点号访问将失败。请始终在去重子查询中提取所需字段。

字段内层查询访问方式描述
report_idmetadata.report_id父级 Signal Report 的 UUID(未分配时为空)
source_productmetadata.source_product来源产品(使用示例 3 查找可用值)
source_typemetadata.source_typeSignal 类型(使用示例 3 查找可用值)
source_idmetadata.source_id来源实体的 ID
weightmetadata.weightSignal 权重(影响报告提升阈值)
deletedmetadata.deleted软删除标记(提取为 String — 使用 != 'true' 进行比较)
extrametadata.extra来源产品提供的任意 JSON 数据块
match_metadatametadata.match_metadata分组期间存储的大模型匹配推理

示例 1:对 Signals 进行语义搜索

查找与自然语言查询最相似的 Signals。这是了解客户产品中正在发生什么最有用的查询:

SELECT
    document_id,
    content,
    report_id,
    source_product,
    source_type,
    cosineDistance(embedding, embedText('users seeing errors on checkout page', 'text-embedding-3-small-1536')) as distance
FROM (
    SELECT
        document_id,
        argMax(content, inserted_at) as content,
        argMax(metadata.report_id, inserted_at) as report_id,
        argMax(metadata.source_product, inserted_at) as source_product,
        argMax(metadata.source_type, inserted_at) as source_type,
        argMax(metadata.deleted, inserted_at) as deleted,
        argMax(embedding, inserted_at) as embedding,
        argMax(timestamp, inserted_at) as signal_ts
    FROM document_embeddings
    WHERE model_name = 'text-embedding-3-small-1536'
      AND product = 'signals'
      AND document_type = 'signal'
      AND timestamp >= now() - INTERVAL 1 MONTH
    GROUP BY document_id
)
WHERE deleted != 'true'
ORDER BY distance ASC
LIMIT 10

将 embedText 的第一个参数调整为你要查找的内容。使用自然语言描述你想查找的问题或观察结果。

若要限制为已归入报告的 Signals,请在外层 WHERE 中添加 AND report_id != ''。

示例 2:获取特定报告的所有 Signals

获取 report_id 后(来自语义搜索或 Signal Reports API),即可提取该报告中的所有 Signals:

SELECT
    document_id,
    content,
    report_id,
    source_product,
    source_type,
    signal_ts
FROM (
    SELECT
        document_id,
        argMax(content, inserted_at) as content,
        argMax(metadata.report_id, inserted_at) as report_id,
        argMax(metadata.source_product, inserted_at) as source_product,
        argMax(metadata.source_type, inserted_at) as source_type,
        argMax(metadata.deleted, inserted_at) as deleted,
        argMax(timestamp, inserted_at) as signal_ts
    FROM document_embeddings
    WHERE model_name = 'text-embedding-3-small-1536'
      AND product = 'signals'
      AND document_type = 'signal'
      AND timestamp >= now() - INTERVAL 3 MONTH
    GROUP BY document_id
)
WHERE report_id = '<report-uuid-here>'
  AND deleted != 'true'
ORDER BY signal_ts ASC
LIMIT 100

示例 3:列出 Signal 类型

查看此客户有哪些类型的 Signals — 返回上个月每个唯一 (source_product, source_type) 组合的一个示例:

SELECT
    source_product,
    source_type,
    count() as cnt,
    max(signal_ts) as latest_timestamp
FROM (
    SELECT
        document_id,
        argMax(metadata.source_product, inserted_at) as source_product,
        argMax(metadata.source_product, inserted_at) as source_product,
        argMax(metadata.source_type, inserted_at) as source_type,
        argMax(metadata.deleted, inserted_at) as deleted,
        argMax(timestamp, inserted_at) as signal_ts
    FROM document_embeddings
    WHERE model_name = 'text-embedding-3-small-1536'
      AND product = 'signals'
      AND document_type = 'signal'
      AND timestamp >= now() - INTERVAL 1 MONTH
    GROUP BY document_id
)
WHERE deleted != 'true'
GROUP BY source_product, source_type
ORDER BY latest_timestamp DESC
LIMIT 100

示例 4:获取特定来源的近期 Signals

查找特定产品来源的最新 Signals(例如所有错误跟踪 Signals):

SELECT
    document_id,
    content,
    source_type,
    report_id,
    signal_ts
FROM (
    SELECT
        document_id,
        argMax(content, inserted_at) as content,
        argMax(metadata.source_product, inserted_at) as source_product,
        argMax(metadata.source_type, inserted_at) as source_type,
        argMax(metadata.report_id, inserted_at) as report_id,
        argMax(metadata.deleted, inserted_at) as deleted,
        argMax(timestamp, inserted_at) as signal_ts
    FROM document_embeddings
    WHERE model_name = 'text-embedding-3-small-1536'
      AND product = 'signals'
      AND document_type = 'signal'
      AND timestamp >= now() - INTERVAL 1 WEEK
    GROUP BY document_id
)
WHERE source_product = 'error_tracking'
  AND deleted != 'true'
ORDER BY signal_ts DESC
LIMIT 100

将 'error_tracking' 替换为任意来源产品,例如:'web_analytics'、'experiments'、'session_replay' 等。使用示例 3 查找现有的来源产品和类型。

示例 5:对 Signals 进行全文搜索

如果你知道要搜索的特定关键词或短语(例如产品名称、错误消息或 URL),使用 ILIKE 进行全文搜索比语义搜索更快、更准确:

SELECT
    document_id,
    content,
    source_product,
    source_type,
    signal_ts
FROM (
    SELECT
        document_id,
        argMax(content, inserted_at) as content,
        argMax(metadata.source_product, inserted_at) as source_product,
        argMax(metadata.source_type, inserted_at) as source_type,
        argMax(metadata.deleted, inserted_at) as deleted,
        argMax(timestamp, inserted_at) as signal_ts
    FROM document_embeddings
    WHERE model_name = 'text-embedding-3-small-1536'
      AND product = 'signals'
      AND document_type = 'signal'
      AND timestamp >= now() - INTERVAL 1 MONTH
    GROUP BY document_id
)
WHERE deleted != 'true'
  AND content ILIKE '%feature flag%'
ORDER BY signal_ts DESC
LIMIT 10

将 '%feature flag%' 替换为你要查找的词语。使用 ILIKE 进行不区分大小写的子字符串匹配。若要精确匹配词元,请改用 hasTokenCaseInsensitive(content, 'token')。

注意事项

  1. 始终使用 text-embedding-3-small-1536 作为模型名称。 这是唯一用于信号的模型。
  2. embedText() 的参数必须是字符串字面量。 不能传入列引用或表达式——此函数在编译时解析,而非逐行解析。
  3. 始终为查询设置时间范围。 该表的 TTL 为 3 个月,但无时间范围的扫描成本很高。请使用 timestamp >= now() - INTERVAL 1 MONTH 或更短的时间范围。为获得最佳性能,请将时间筛选条件放在内部子查询的 WHERE 子句中(针对原始 timestamp 列)。
  4. 始终去重。 如果没有 argMax(..., inserted_at) GROUP BY document_id 子查询,就会看到过时和重复的行。
  5. 仅在需要时选择 embedding。 它是一个包含 1536 个元素的浮点数数组——不进行相似度搜索时,请在内部子查询中省略该字段。
  6. 查询末尾不要加分号。 HogQL 不使用分号。
  7. 每个查询都要添加 LIMIT。 最多允许 500 行。通常只应选择约 10 个信号,并使用语义搜索或全文搜索为其排序。
  8. 在去重子查询中提取元数据字段。 HogQL 的 metadata.field 点访问仅适用于原始表列。经过 argMax() 聚合后,JSON 类型会丢失,点访问将导致类型错误。请始终在内部查询中使用 argMax(metadata.field_name, inserted_at) as field_name。
  9. 所有 JSON 点访问值都是 Nullable(String)。 HogQL 会将每个 JSON 字段都提取为字符串,包括布尔值和数字。对于 metadata.deleted,请使用 deleted != 'true'——不要使用 NOT deleted。
  10. 如果同一个内部查询也会筛选原始 timestamp 列,请勿将 argMax(timestamp, inserted_at) 的别名设为 timestamp。 HogQL 会优先解析别名,导致出现“WHERE 中存在聚合”错误。请改用 signal_ts 这样的不同别名,或将时间筛选条件移到外层查询。

评论

支持 Markdown