言語の設計
ActQLの土台はSQLです。このページでは、構文をこう決めた理由と、あえて言語に入れていないものを説明します。
上から下へ読むSQL
ActQLは、GoogleSQL(BigQueryで使われているSQLの方言)と、そのPipe構文を土台にしています。パイプラインは読み取り元から始まり、処理を1ステップずつ足していきます。2つ目以降のステップは |> で始めます。
FROM support.cases AS c
|> WHERE c.status = 'open'
|> AGGREGATE COUNT(*) AS open_cases GROUP BY c.account_id
|> ORDER BY open_cases DESC
|> LIMIT 10人もエージェントも、クエリは「まずデータを選び、そこに変換を1つずつ足す」という順で組み立てます。Pipe構文なら、その順番のまま書けます。ステップごとに読めて、検査でき、説明できます。SELECT … FROM … WHERE …、WITH、JOIN といった普通のSQLもそのまま使え、途中からPipeに切り替えることもできます。既存のSQLの知識も、SQLを生成する能力も、そのまま活かせます。
パイプラインが表すのは、ステップ同士の論理的なつながりです。物理的にその順番で実行されるという意味ではありません。ActQLは、結果、権限、失敗時の振る舞いといった意味を保ったまま、ステップを並べ替えたり、Connectorに任せたりできます。たとえばJOINの後ろに書いた絞り込みが、JOINより前に、読み取り元のシステムの中で実行されることもあります。上から下へ読みやすいまま、最適化の自由度を失わないのはこのためです。
標準の書き方は、標準の意味のまま
SQLに書き方がすでにあるものは、標準の意味そのままで使います。LEFT JOIN は左外部結合で、NULL はSQLの NULL と同じように振る舞います。グループ化、並べ替え、集合演算は、選んだGoogleSQLの範囲(プロファイル)に従います。GoogleSQL、SQL/PGQ、ISO GQLに完全準拠しているとは主張しません。主張するのは、ActQLが受け付ける範囲については、それらと同じ意味を持つということです。
既存の構文の意味を変えることはありません。足すのは、ActQL固有の機能と境界に必要な拡張だけです。
-
グラフ —
GRAPH (…)で表からグラフを構築し、GQL風のMATCHでパターンを問い合わせます。パターン照会に加えて、到達可能性、経路探索、連結成分、グラフ同士の合成・比較・差分といった演算を、独自の型付きグラフ演算として定義しています。結果は明示的にRelationに変換でき、通常のSQLと組み合わせられます。 -
ベクトルとイベント — 型の付いたベクトルやイベント列に対する、名前付きの演算です。書き方は、ベクトルや時系列を扱う既存のSQL拡張にならっています。
-
LLM —
INFERは、バージョン付きのmodel taskに沿ってLLMを呼び、型の付いた結果を返します。 -
Effect — 外部システムへの明示的な変更です。すべてのEffectはポリシーの判定を受け、ポリシーが求めるときは承認を待ちます。コンセプトの例のように
EFFECT句として書くことも、見慣れたSQLの形で書くこともできます。UPDATE salesforce.Account SET Churn_Risk__c = 'high' WHERE Id = @account_id;どちらで書いても同じEffectで、同じようにプレビュー、判定、記録されます。
ひとつの型と演算の体系
ActQLは、データ処理を型付きの代数的な演算として定義しています。4種類の値、Relation、Graph、Vectors、Eventsのそれぞれについて、使える演算と、その結果の意味が決まっています。SQLに機能を継ぎ足した言語ではありません。グラフのマッチも、近傍検索も、イベントのウィンドウも、JOINと同じ体系の中の演算です。
種類をまたぐときは、変換を明示します。グラフのマッチ結果をRelationに変換すれば、あとは普通のSQLです。JOINも、集計も、INFER に渡すことも、EFFECT で書き戻すこともできます。
これらの演算はすべて、同じ型検査と同じ計画の仕組みで扱われます。エンジンが演算の場所や実行方法を変えられるのは、各演算の意味が、どこで実行するかとは独立に定義されているからです。
計算と、外部からの観測と、外部への変更を分ける
ActQLは、3種類の処理を区別します。
| 種類 | 例 | 意味 |
|---|---|---|
| 純粋な計算 | SELECT、JOIN、AGGREGATE | 手元にある入力から値を計算する |
| 外部からの観測 | Connectorを通した読み取り、INFER | 外部システムやLLMから、その時点の新しい値を取り込む |
| 外部への変更 | EFFECT | 外部システムの状態を変える |
計算は入力の関数です。観測はそうではなく、「その時点で見た事実」として記録されます。だからRunは、復旧のときにLLMに聞き直さず、記録した答えを再利用できます。変更は、コンセプトで説明したポリシー、プレビュー、結果の仕組みを通ります。
観測と変更は、型、権限、上限、結果を確認する境界を通ります。この境界が明示されているから、エンジンは境界の内側の計算を自由に最適化しながら、境界の外側のすべてについて承認と復旧を正確に扱えます。
これはまた、SQLがテキストのまま外部システムに転送されることはない、という意味でもあります。プログラムは共通の型付き表現に変換され、そこで検査されます。その一部をConnectorに任せるのは、ConnectorのCapabilityと、意味が保たれることを確認してからです。
途中の結果には、ひとつの書き方で名前を付ける
WITH 名前 AS (…) で、テーブル、グラフ、ベクトル、イベントのどの種類の値にも名前を付けられます。後ろのステップからは、その名前で参照します。
WITH open_cases AS (
FROM support.cases |> WHERE status = 'open'
)
SELECT account_id, COUNT(*) AS n
FROM open_cases
GROUP BY account_id;名前を付けること、出力すること、外部を変えることは、それぞれ別のことです。
WITHは、プログラムの中の値に名前を付けます。名前を付けただけでは、何も読まず、何も出力しません。実行時にその値が必要になれば、そのときに取得します。RESULTは、値をプログラムの名前付きの出力として公開します。EFFECTは、外部システムへの変更を宣言します。
大事なところは明示的に、明らかなところは短く
ActQLが省略を補うのは、型と関係する定義から意味が一意に決まる場合だけです。INFER は入力の列を残したまま、その横に答えの列を足すので、残したい列を書き直す必要はありません。キーも、候補が1つしかないと証明できるときは省略できます。解釈が複数ありうる場面では、ActQLは勝手に選ばず、明示的な指定を求めます。列名からキーを推測したり、頼まれていないのに値の種類を変換したり、JOINの種類を勝手に変えたりはしません。明示的に書いたものは常に既定より優先されます。
対応していない機能は、検査の段階で、名前の決まったエラーとして拒否します。範囲外の入力値や上限超過のように、データを見てはじめて分かるものは実行時に捕まえ、処理をねじ曲げるのではなく止めます。どちらの場合も、コンパイラが次のことをすることはありません。
- 存在しない演算を作り出す
- 入力や項目を黙って捨てる
- 正確な計算を近似に置き換える
- イベントの時刻を、処理した時刻にすり替える
- 対応していない処理の代わりに、空の結果を返す
プログラムの意味を、書き方とは別に識別する
ActQLは、書かれたSQLを共通の型付き表現に変換し、決まった規則で正規化します。プログラムの意味、1回の実行の具体的な入力、その実行での観測、Runの記録には、それぞれ別の識別子が付きます。
だから「何を実行したのか」「このResultはどの入力から来たのか」「どの変更を承認したのか」を、別々の問いとして答えられます。プログラムの意味は、テキストの書き方や実行場所とは切り離して定義されています。
抜け道を作らない
ActQLには、ユーザー定義関数、ラムダ、インラインのJavaScriptやPython、生のHTTPがありません。これは意図的な設計です。ActQLはチューリング完全ではありません。任意のループや再帰、実行時に意味が決まるコードを許さず、すべての演算に、検査できる定義と有限のリソース上限を求めます。そのため、プログラム自身が際限のない処理を生み出すことはできません。ただしこれは、すべてのRunが必ず正常に終わるという約束ではありません。外部システムが応答しないことも、承認が来ないことも、上限を超えることもあります。何が起きても、記録され、復旧できる、というのが約束です。
すべての演算について、型、上限、順序の意味が定まっています。だからプランナーは処理を他のシステムに任せられ、Runは処理を再現でき、レビューする人は、どの変更を承認するのかをプレビューから正確に確認できます。ActQLは適用のときにも条件を検査し直すので、古い承認が使い回されることはありません。中身の見えないコードを許せば、そのどれも成り立たなくなります。
新しい機能は、Connectorとmodel taskを通して追加します。どちらも、型とバージョンの決まった定義を持ち、検査器とプランナーがその中身を理解できます。
上限は実行前に確定し、実行中に守る
実行できるプログラムには、件数、バイト数、LLMの呼び出し、Effectなど、量に関わるものすべてに上限があります。上限の出どころは、関係するシステム、Hostのポリシー、そして作者が自分で足す上限(ガード)の3つで、作者がすべての上限をプログラムに書く必要はありません。どの上限も、それが適用される処理を始める前に確定し、実行中に守られます。上限を超えた処理はエラーとして失敗させ、データを黙って切り詰めることはありません。長時間動き続ける処理、対話的な処理、終わりのない連続処理はプログラムの範囲外です。プログラムとは、検査し、計画し、承認し、復旧できる、終わりのあるひとまとまりの処理です。
次に読む
- コンセプト:プログラム、Run、Effect、Checkpoint
- アーキテクチャ:プログラムがどう検査、計画、実行されるか
- クエリテキストの仕様:SQL / Pipeの正確な文法
Source: actql/actql/docs/concepts/language-design.md at d904416674e4 · Edit on GitHub