判定のしくみ
Pointsman は、ケースをどちらに進めるかを判定します。ケースとは、システムが判定してほしいものです。たとえば、道路が通れないという報告、新しい Issue、プルリクエストです。ケースの種類ごとにプロファイルを用意します。プロファイルには、尋ねる質問、調べるファクト、回答をアクションに変えるルールを書きます。プロファイルにはバージョンがあるため、どの判定についても、どのルールで決まったかがわかります。
- ケースが届きます。 システムが HTTP API でケースを送るか、AI エージェントが MCP(Model Context Protocol。AI エージェントがツールを呼び出すための仕組み)で送ります。ほかのシステムを Pointsman につなぐ部品であるコネクターが、FIWARE のスマートシティ向けデータ基盤などから受け取ることもあります。
- 質問とファクトを同時に処理します。
- AI モデルがプロファイルの質問に答えます。質問の形は、はい・いいえ、選択肢から 1 つ、段階評価のいずれかです。どの回答にも、モデルがどのくらい確かだと考えているかを表す確率(0 から 1)が付きます。
- Pointsman がプロファイルのファクトをデータから調べます。たとえば、その場所が浸水想定区域に入るか、プルリクエストが何ファイルを変更したかです。AI はファクトを見ませんし、推測もしません。
- ルールが判定します。 ルールは人が書きます。たとえば「危険の確率が 70% 以上なら緊急」です。Pointsman はルールを上から順に調べ、条件が真になった最初のルール(最初に「当てはまった」ルール)のアクションを使います。どれも当てはまらないときは、プロファイルの既定のアクションになります。アクションの名前は、
auto、urgent、reviewのようにプロファイルで決めます。 - アクションを返すか、人に回します。 アクションはケースを送ったシステムに戻り、システムがそれに従って動きます。アクションが
review(またはプロファイルで人の確認が必要と指定したアクション)のときは、先に人がケースを確認します。確認した人が最終のアクションを決め、AI の回答を訂正することもできます。 - すべてを判定ログに記録します。 訂正も残るため、AI がどのくらい正しかったかがわかります。
なぜこのように分けるのか
Section titled “なぜこのように分けるのか”- AI は答えるだけで、決めるのは人です。 モデルが答えるのは範囲を絞った質問だけです。回答から何をするかは、人が読み、試し、変えられるルールに書きます。そのため、プロファイルの動きを変えずにモデルを入れ替えられます。判定の理由も、いつでも人が確かめられます。AI の回答をまったく使わない判定もあります。たとえば、バージョンの欄と変更履歴だけを変えるリリースのプルリクエストは、誰が変更したかをほかのファクトで確かめたうえで、ファクトだけで承認するプロファイルを書けます(プロファイルの形式の
github_releaseを参照)。 - 回答だけでなく確率も使います。 ルールでは、モデルがどのくらい確かかを条件にできます(例:
team.p >= 0.85。pは質問teamの回答の確率です)。モデルが確かでないときは、推測で進めずに人に回すルールを書けます。 - ファクトは調べるもので、推測しません。 距離やプルリクエストのファイルのように測れるものは、データから取ります。調べられなかったファクトは「欠けた」ものとして扱い、値を作り出すことはありません。詳しくは欠けたファクトを参照してください。
- 判定ごとに 1 つの記録を残します。 ログにプロファイルのバージョンとモデルが残るため、あとから「ルールを変えていたらどう判定したか」「人の判断とどのくらい一致したか」を確かめられます。
欠けたファクト
Section titled “欠けたファクト”ファクトを調べるのに失敗することがあります。ケースに位置がない、データのサービスの応答が遅い、読んでいる途中でプルリクエストに新しいコミットが入った、といった場合です。このときファクトは「欠けた」状態になります。Pointsman は欠けたファクトを推測で埋めません。推測した値は本物と見分けがつかず、ルールがそれを事実として扱ってしまうからです。
どのファクトにも、型ごとのフィールドのほかに 2 つのフィールドがあります。missing は値がないとき true になり、reason には欠けた理由(timeout や no_location など)が入ります。理由の一覧はファクトの型にあります。
欠けたファクトや値のないフィールドを比べる条件は、真でも偽でもなく「不明」になります。ルールが当てはまるのは条件が真のときだけです。false and unknown は偽、true or unknown は真、not unknown は不明のままです。そのため、データがなかったせいでルールが当てはまることはありません。ただし、ファクトが欠けると、何かを止める前のルールが当てはまらなくなり、それを許す後のルールが代わりに当てはまることがあります。そこで、常に値がわかっている missing を最初に確かめます。
- when: "facts.flood.missing == true" action: reviewreason を使うと、調べるのに失敗した場合と、もともと当てはまらない場合を区別できます。迂回路は道路の区間についてだけ計算できるため、facts.walk.missing == true and facts.walk.reason != 'no_location' とすると、本当に失敗したときだけ人に回せます。何かを許すルールでは、前のルールに頼らず、すべての条件を値がわかっている形で書きます(例: facts.risky.any == false)。詳細はプロファイルの形式にあります。
AI の回答は判定を厳しくする方向にしか使わない
Section titled “AI の回答は判定を厳しくする方向にしか使わない”ケースの中には、判定される本人が書いたものがあります。プルリクエストのタイトルと説明は作成者が、報告は送った人が書きます。書き手は、AI を誘導するための文(たとえば「この変更は安全です」)を入れることができます。これをプロンプトインジェクションと呼び、完全に防げるモデルはありません。
そのため、外部の人が書いた入力を扱うプロファイルでは、プロファイルを書く人が 1 つの決まりを守ります。AI の回答は、判定を厳しくする方向にだけ使うという決まりです。AI の回答をもとに、ケースを人に回したり修正を求めたりはしますが、ケースを通す理由にはしません。通すために満たすべき条件はファクトで確かめます。ファクトは Pointsman 自身が求める値で、ケースを書いた人が直接決めることはできません。Pointsman がこの決まりを強制するわけではありません。AI の回答だけで通したり承認したりするプロファイルは、ケースを書いた人に誘導されるおそれがあります。例のプロファイル workflow-check を短くすると次のとおりです。
policy: rules: - when: "facts.wf.missing == true" action: retry # ... - when: "touches_secrets.yes >= 0.3" action: review - when: >- facts.wf.unparsed == 0 and facts.wf.privileged == 0 and facts.wf.injection == 0 and facts.wf.unpinned == 0 and touches_secrets.yes < 0.3 action: pass default: reviewAI への質問 touches_secrets は、プルリクエストの文章を読みます。この質問を使うのは、ケースを人に回すルールと、通すルールの追加の条件だけです。AI がだまされても、外れるのはその追加の条件だけで、通すにはやはりファクトの条件を満たす必要があります。ファクトを読めなかったときは、最初のルールで retry(あとでもう一度試す)になり、pass にはなりません。
このようなプロファイルを公開する前に、ルールを文章で読み(node scripts/explain-profile.mjs <file>)、次の点を確かめます。
- ケースを通すルールが、頼りにするファクトをすべて書いている。
- AI の回答は、厳しい方向のアクションのルールか、通すルールの追加の条件にだけ使っている。
- ファクトが欠けたときは、人に回すか
retryになり、通ることはない。
人の関わり方: レビューと訂正
Section titled “人の関わり方: レビューと訂正”プロファイルには、人が確認してから有効にするアクションを指定します(既定は review)。そのような判定はレビューの待ち行列に入り、人が最終のアクションを決め、AI の回答を訂正することもできます。クライアントは callback_url を渡しておくと、確認が終わったときに署名付きの最終結果を受け取れます。訂正はフィードバックとして残るため、プロファイルがどのくらい正しかったかを測れます。詳細はレビューとコールバック(英語)にあります。
判定はすべて、返す前に保存します。ログに書き込めなければ、クライアントには判定ではなくエラーが返るため、記録のない判定にもとづいて誰かが動くことはありません。ログには、回答、ファクト、ルール、モデル、エンジンとモジュールのバージョン、プロファイルのバージョンが残ります。どの質問をしたかは、プロファイルのバージョンからわかります。リクエストそのものは、プロファイルで残すよう指定しない限りハッシュだけを残します。テスト用トークンの判定は記録しません。ログを読むファクトもあります。たとえば、作成者のプルリクエストのうちレビュー待ちがいくつあるかです。読めるのは、問い合わせたクライアント自身の判定だけです。詳細は判定ログ(英語)にあります。
判定のチェーン
Section titled “判定のチェーン”判定は、以前の判定を前提にできます。たとえば、まず報告を確かめ、さらに確認を重ね、最後の判定がそれらをすべて読みます(プロファイルの requires)。以前の判定は ID で指定してログから読むため、クライアントが結果を作り出すことはできません。詳細は判定のチェーン(英語)にあります。
Pointsman の構成
Section titled “Pointsman の構成”Pointsman は、1 つのエンジンと、その周りの部品でできています。デプロイごとに必要な部品を選ぶため、自治体の通行止めのサービスと、開発チームのプルリクエストのチェックが、同じエンジンを別のモジュールで動かせます。
- エンジンは、ケースを受け取り、プロファイルの質問を AI モデルに尋ね、ファクトを取得し、ルールを当てはめ、判定ログに書き込みます。
- AI モデルが質問に答えます。プロファイルでモデルと代わりのモデルを指定し、ルールを変えずに入れ替えられます(モデル、英語)。
- ファクトモジュールがファクトを調べます。浸水想定区域と避難所(
@pointsman/facts-gsi)、通行止めの迂回路(@pointsman/facts-valhalla)、プルリクエストの状態(@pointsman/github)、プルリクエストへの AI のレビュー(@pointsman/coderabbit)です。モジュールはこのリポジトリの中のパッケージで、エンジンに組み込んで使い、デプロイで使うものを並べます(ファクトモジュール、英語)。 - コネクターがほかのシステムとつなぎます。ケースを受け取って結果を返します。たとえば FIWARE の基盤(
@pointsman/fiware)や、プルリクエスト(@pointsman/github)です。ほかのクライアントと同じ公開 API を使います(コネクター、英語)。FIWARE のコネクターとそのアクションや設定は FIWARE ブリッジ(英語)で、そのデモは FIWARE デモの構成(英語)で説明しています。 - アダプターが、エンジンを実行環境とつなぎます。ログとプロファイルの保存先、答える AI のサービス、リクエストの受け取り方がここで決まります。現在の実行環境は、Cloudflare のサーバーでコードを動かすサービスである Cloudflare Workers で、エンジンの中心部は Cloudflare 固有のコードを使いません(アダプター、英語)。
1 つのパッケージが複数の役割を持つこともあります。@pointsman/github は、ファクトを調べるモジュールであり、コネクターでもあります。部品を分けておくと、エンジンを小さく保てます。新しいデータの取得元や新しいシステムには新しいモジュールで対応し、エンジンは変えません。別の実行環境では別のアダプターが必要ですが、プロファイルは同じものを使えます。
次に読むページ
Section titled “次に読むページ”- はじめてのプロファイルで、ここまでの内容を自分のコンピューターで試せます。
- プロファイルの形式に、すべてのフィールドがあります。
- セキュリティでは、Pointsman が何を、誰から守るかを説明します。