关于 signals
signals 是用于通过 HogQL 查询 ClickHouse 中原始产品观察数据的 PostHog 技能。它支持语义或关键词搜索信号、查看报告关联的观察记录,并分析精选报告工具未涵盖的信号来源。
主要功能
- 01语义搜索信号文本
- 02使用 ILIKE 关键词搜索
- 03获取报告关联的信号
- 04探索信号来源和类型
- 05查询时去重信号版本
适用场景
- 01查找与产品问题相关的近期观察
- 02检查 Signal Report 背后的原始信号
- 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 的关键列如下:
| 列 | 类型 | 说明 |
|---|---|---|
product | String | 产品类别 — signals 的值始终为 'signals' |
document_type | String | 文档类型 — signals 的值始终为 'signal' |
model_name | String | 嵌入模型 — 始终为 'text-embedding-3-small-1536' |
document_id | String | 唯一的 signal ID(UUID) |
timestamp | DateTime64(3) | signal 的创建时间 |
inserted_at | DateTime64(3) | 此行版本的插入时间(用于去重和软删除) |
content | String | signal 的描述文本 |
metadata | String | 包含 report_id、来源信息、权重、删除标记等内容的 JSON 字符串 |
embedding | Array(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_id | metadata.report_id | 父级 Signal Report 的 UUID(未分配时为空) |
source_product | metadata.source_product | 来源产品(使用示例 3 查找可用值) |
source_type | metadata.source_type | Signal 类型(使用示例 3 查找可用值) |
source_id | metadata.source_id | 来源实体的 ID |
weight | metadata.weight | Signal 权重(影响报告提升阈值) |
deleted | metadata.deleted | 软删除标记(提取为 String — 使用 != 'true' 进行比较) |
extra | metadata.extra | 来源产品提供的任意 JSON 数据块 |
match_metadata | metadata.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')。
注意事项
- 始终使用
text-embedding-3-small-1536作为模型名称。 这是唯一用于信号的模型。 embedText()的参数必须是字符串字面量。 不能传入列引用或表达式——此函数在编译时解析,而非逐行解析。- 始终为查询设置时间范围。 该表的 TTL 为 3 个月,但无时间范围的扫描成本很高。请使用
timestamp >= now() - INTERVAL 1 MONTH或更短的时间范围。为获得最佳性能,请将时间筛选条件放在内部子查询的WHERE子句中(针对原始timestamp列)。 - 始终去重。 如果没有
argMax(..., inserted_at) GROUP BY document_id子查询,就会看到过时和重复的行。 - 仅在需要时选择
embedding。 它是一个包含 1536 个元素的浮点数数组——不进行相似度搜索时,请在内部子查询中省略该字段。 - 查询末尾不要加分号。 HogQL 不使用分号。
- 每个查询都要添加
LIMIT。 最多允许 500 行。通常只应选择约 10 个信号,并使用语义搜索或全文搜索为其排序。 - 在去重子查询中提取元数据字段。 HogQL 的
metadata.field点访问仅适用于原始表列。经过argMax()聚合后,JSON 类型会丢失,点访问将导致类型错误。请始终在内部查询中使用argMax(metadata.field_name, inserted_at) as field_name。 - 所有 JSON 点访问值都是
Nullable(String)。 HogQL 会将每个 JSON 字段都提取为字符串,包括布尔值和数字。对于metadata.deleted,请使用deleted != 'true'——不要使用NOT deleted。 - 如果同一个内部查询也会筛选原始
timestamp列,请勿将argMax(timestamp, inserted_at)的别名设为timestamp。 HogQL 会优先解析别名,导致出现“WHERE 中存在聚合”错误。请改用signal_ts这样的不同别名,或将时间筛选条件移到外层查询。