signals について
signalsは、HogQLを通じてClickHouse内の生のプロダクト観測データを検索するPostHogスキルです。シグナルの意味検索やキーワード検索、レポートに紐づく観測の確認、レポート用ツールでは扱えないシグナルソースの分析に使えます。
主な機能
- 01シグナルテキストの意味検索
- 02ILIKEによるキーワード検索
- 03レポートに紐づくシグナルの取得
- 04シグナルのソースと種類の確認
- 05クエリ内でのシグナル版の重複排除
活用シーン
- 01プロダクトの問題に関する最近の観測を検索
- 02Signal Reportの元となるシグナルを確認
- 03顧客に存在するシグナル種類を一覧
MCP 対応
PostHogの`posthog:execute-sql` MCPツールを使い、HogQL経由で`document_embeddings`を検索します。
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ posthog/ skills --skill signalsインストール後、Claude Code で/signalsを入力して読み込まれたか確認します。
詳細
シグナルのクエリ
シグナルとは?
シグナルとは、PostHog がエラー追跡、ウェブ分析、実験、セッションリプレイなど複数のソースにわたって顧客のプロダクトデータを監視して生成する、自動化された観測結果です。各シグナルは、注目すべき事柄を自然言語で短く説明したものです(例:「/checkout でエラー率が 3 倍に急増」)。
シグナルはシグナルレポートにまとめられます。レポートに十分な重みが蓄積されると、要約され、アクションにつなげられるかどうかが評価されます。シグナルレポートは、関連する観測結果の集まりを表し、それらが合わさって意味のある問題や傾向を示します。
シグナルとその埋め込みは ClickHouse の document_embeddings テーブルに保存されており、posthog:execute-sql MCP ツールを通じて HogQL でクエリできます。ユーザーのプロダクトで最近起きたことを、意味的にクエリするのに役立つ場合があります。
このスキルと inbox-exploration の使い分け
この 2 つのスキルは、同じプロダクトの異なるレイヤーを扱います。
inbox-exploration— 専用の MCP ツール(inbox-reports-list、inbox-reports-retrieve、inbox-source-configs-list、inbox-source-configs-retrieve)を使用する、キュレーション済みのレポートレイヤーです。「受信トレイには何がありますか?」「どれがアクションにつながりますか?」といった質問や、ステータス/ソース/推奨レビュアーによるレポートの絞り込み、ID や URL による特定のレポートの検索に使用します。- このスキル(
signals) —document_embeddingsに対する HogQL を使用する、生のシグナルレイヤーです。キュレーション済みのレポートレイヤーでは不十分な場合に使用します。シグナルのテキストに対する意味検索、特定のレポートに寄与したすべてのシグナルの取得、存在するシグナルの種類の一覧表示、またはレポートツールでは参照できないアドホックな分析に使います。
一般的な手順は、まず inbox-exploration を使って report_id を取得するか、ユーザーが関心を持っている領域を把握し、その後、ユーザーが生の観測結果を見たい場合にこのスキルを使用することです。
テーブルとカラムのリファレンス
HogQL のテーブルエイリアスは document_embeddings です。HogQL はクエリを自動的に現在のチームに制限するため、team_id で絞り込む必要はありません。シグナルに関する主なカラムは次のとおりです。
| カラム | 型 | 説明 |
|---|---|---|
product | String | プロダクトの分類。シグナルの場合は常に 'signals' |
document_type | String | ドキュメントの種類。シグナルの場合は常に 'signal' |
model_name | String | 埋め込みモデル。常に 'text-embedding-3-small-1536' |
document_id | String | 一意のシグナル ID(UUID) |
timestamp | DateTime64(3) | シグナルが作成された日時 |
inserted_at | DateTime64(3) | この行バージョンが挿入された日時(重複排除とソフト削除に使用) |
content | String | シグナルの説明テキスト |
metadata | String | report_id、ソース情報、重み、削除フラグなどを含む JSON 文字列 |
embedding | Array(Float64) | 1536 次元の埋め込みベクトル |
必須フィルター
signals クエリには、以下の 4 つのフィルターをすべて必ず含めてください。いずれかが欠けていると、クエリが無効なモデルエラーで失敗したり、誤ったデータが返されたり、不必要に高コストなスキャンが発生したりする可能性があります。
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 メモリ)のデータが含まれています。これらのフィルターがないと、無関係なデータが混在して返されます。
timestamp フィルターはパフォーマンス上必要です。テーブルは週単位でパーティション化されており、TTL は 3 か月です。必ず now() - INTERVAL N DAY(または WEEK、MONTH など)を使って時間の範囲を指定してください。さらに遡って調べる理由がなければ、デフォルトは 30 日にしてください。長期間続いている問題を調査する場合を除き、一般に新しいデータほど関連性が高い可能性があります。
重複排除のパターン
基盤のテーブルには、同じシグナルの複数のバージョン(例:ソフト削除後の再出力)が含まれる場合があります。読み取りは必ず、document_id でグループ化した argMax(..., inserted_at) を使うサブクエリでラップして重複を排除してください。
注: HogQL は、生の metadata JSON カラムに対する metadata.field_name のドットアクセスをサポートしていますが、この型情報は argMax() などの集約関数を通すと失われます。内側の重複排除サブクエリ内で、個々の metadata フィールドを必ず抽出してください。metadata 全体を argMax に渡して外側のクエリでドットアクセスする方法は、型エラーになるため使用しないでください。
HogQL の JSON ドットアクセスでは、元の JSON の型にかかわらず、常に値が Nullable(String) として抽出されます。つまり、metadata.deleted は Bool ではなく、文字列の 'true'/'false'/null です。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 関数です。埋め込み API を呼び出し、クエリの実行前に結果のベクトルを定数として埋め込みます。そのため、外部で埋め込み処理を行わずに、1 つのクエリでセマンティック検索を実行できます。
シグネチャ: embedText(text, model_name)
text— 埋め込みに変換する文字列です。カラム参照ではなく、文字列リテラルでなければなりません。model_name— 使用する埋め込みモデルです。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
埋め込みモデル(text-embedding-3-small-1536)は Matryoshka 表現学習を採用しているため、埋め込み次元は重要度順に並んでいます。そのため、高次元でも類似検索は良好に機能し、ここでは次元の呪いは大きな懸念事項ではありません。
メタデータ 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 | シグナルの種類(利用可能な値を調べるには例 3 を使用) |
source_id | metadata.source_id | ソースエンティティの ID |
weight | metadata.weight | シグナルの重み(レポート昇格のしきい値に影響します) |
deleted | metadata.deleted | 論理削除フラグ(String として抽出されるため、!= 'true' と比較します) |
extra | metadata.extra | ソース製品から取得した任意の JSON データ |
match_metadata | metadata.match_metadata | グルーピング時に保存された LLM のマッチング理由 |
例 1:シグナルのセマンティック検索
自然言語のクエリに最も類似したシグナルを検索します。顧客の製品で何が起きているかを把握するのに、最も役立つクエリです。
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 の最初の引数を調整してください。見つけたい問題や観察事項の種類を、自然言語で記述します。
すでにレポートにグループ化されているシグナルに絞り込むには、外側の WHERE に AND report_id != '' を追加します。
例 2:特定のレポートに属するすべてのシグナルを取得する
report_id を取得したら(セマンティック検索または Signal Reports API を使用)、そのレポートに属するすべてのシグナルを取得します。
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:シグナルの種類を一覧表示する
この顧客にどのような種類のシグナルがあるかを確認します。直近 1 か月の一意な (source_product, source_type) の組み合わせごとに、例を 1 件返します。
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:特定のソースからの最近のシグナル
特定の製品ソースからの最新のシグナルを検索します(例:すべてのエラートラッキングシグナル)。
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:シグナルの全文検索
検索する特定のキーワードや語句(例:製品名、エラーメッセージ、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などの別名を使うか、時間フィルターを外側のクエリに移動してください。