LLMとINFER
このページは、レコードごとにLLMに判定、分類、抽出、下書きをさせたい作者のためのものです。model taskを定義し、プロバイダーとともに登録し、INFER で呼び出し、型付きの答えを扱う方法を示します。LLMが失敗したとき、再試行されるとき、そもそも呼ばれないときに何が起こるかも含みます。
役割分担
model taskは、LLMに何を頼み、何が返ってくるかを決めます。SQLは、どのレコードに適用し、答えをどう使うかを決めます。この2つを分けておくことで、プロンプトはコードと同じようにバージョン管理とレビューの対象になり、プログラムは読みやすいままになり、ActQLはLLMを1回も呼ぶ前に、残りのSQLを型検査できます。
model taskを定義する
model taskは、バージョン付きのファイルです。
# model-tasks/churn-risk.v1.yaml
task: model-task:churn-risk.v1
model: { provider: openai, name: "<model id>" } # optional
input:
notes: string
active_users: int64
trend: string
instructions: |
From the account notes, the number of active users and the usage trend,
rate the churn risk as low, medium or high and explain why in one line.
Do not assume facts that are not in the input.
output:
level: {type: string, enum: [low, medium, high]}
reason: string
limits:
max_items: 5000
max_output_tokens: 200| フィールド | 意味 |
|---|---|
task | プログラムが使う識別子。指示や型を変えたらバージョンを上げます。Runは、どのバージョンを使ったかを記録します。 |
model | 省略可。このタスクに答えるプロバイダーとLLM。省略すると、登録ファイルの default_provider が使われます。プロバイダーの設定は下の登録ファイルに書き、タスクには書きません。mockプロファイルでは無視されます。 |
input | タスクが受け取る列と、その型。INFER … INPUT (…) は、ちょうどこれらを渡さなければなりません。 |
instructions | プロンプト。一度に1件のレコードの input フィールドだけが見えます。 |
output | 型付きの答え。enum は値を閉じた集合にします。nullable: true を付けない限り、すべてのフィールドが必須です。 |
limits | このタスクの上限。呼び出し箇所ごとの件数、出力トークン数など。Hostとプログラムはこれを下げられますが、上げることはできません。 |
model taskには、ツールも、検索も、ストリーミングもありません。レコードを1件入れると、型付きのオブジェクトが1つ出てくる。それだけです。だからこそ答えを記録し、再現できます。
タスクとプロバイダーを登録する
actql.config.json の model_tasks で指定する model-tasks.json に、タスクのファイルと、使ってよいプロバイダーを並べます。
{
"version": "actql-model-tasks/0.1",
"tasks": {
"model-task:churn-risk.v1": "./model-tasks/churn-risk.v1.yaml",
"model-task:draft-reply.v1": "./model-tasks/draft-reply.v1.yaml"
},
"providers": {
"openai": { "type": "openai", "api_key_env": "OPENAI_API_KEY" },
"anthropic": { "type": "anthropic", "api_key_env": "ANTHROPIC_API_KEY" }
},
"default_provider": "openai"
}APIキーは環境変数で参照し、Hostがプロバイダーを呼ぶときに読みます。actql model-task list と actql model-task show model-task:churn-risk.v1 で、登録されているものと、タスクが何を期待するかを確認できます。
LLMの呼び出しは、読み取りと同じ保護された境界で、プロバイダーの接続とタスクに対して判断されます。ローカルのHostの標準のポリシーでは、登録したタスクはそのまま動きます。アカウント設定で制限することもできます。Hostの外に出るのは INPUT に書いた列だけです。
mockのLLM
--profile mock では、プロバイダーは呼ばれません。各タスクは、設定の mock.answers でそのタスクに指定したファイルから答えます。解約リスクの例には、このファイルも入っています。
# mock/churn-risk.v1.answers.yaml
task: model-task:churn-risk.v1
answers:
default: { level: low, reason: "stable usage" }
by_key:
"001Acme": { level: high, reason: "falling usage and a competitor evaluation" }
"001Globex": { level: high, reason: "support tickets about pricing" }Quickstartがキーなしで動くのはこのためです。絞り込み、JOIN、失敗の扱い、Effectといったプログラムのロジックを、プロンプトの出来とは切り離してテストするときにも使います。答えはレコードのキーで引きますが、mockがそのキーを決めるわけではありません。キーの一意性は、本物のプロバイダーのときとまったく同じく、読み取り元が宣言するキーから証明されます。
プログラムから呼ぶ
SELECT a.id AS id, a.description AS notes, u.active_users, u.trend
FROM salesforce.accounts AS a
JOIN snowflake.usage_30d AS u ON a.id = u.account_id
|> INFER `model-task:churn-risk.v1`
INPUT (notes, active_users, trend) AS risk
|> WHERE risk.outcome = 'ok' AND risk.value.level = 'high'INPUT (…)は、流れ込んでくるレコードの列を、名前でタスクの入力フィールドに対応付けます。それ以外は何もLLMに送られません。AS riskは答えに名前を付けます。ここから先、risk.outcomeとrisk.valueは普通の列です。- レコードには、それを識別するキーが必要です。候補のキーがちょうど1つに証明できれば(ここでは
id)それが使われ、そうでなければITEM KEY (col, …)で指定します。
結果はキーの列を残し、2つの列を足します。
| 列 | 値 |
|---|---|
outcome | ok、または failed:<reason> |
value | ok のときはタスクの出力型のオブジェクト。失敗のときは NULL |
risk.outcome = 'ok' で絞り込んでおくことで、そのあと risk.value.level をNULLでない文字列として使えます。
LLMが失敗したとき
レコードの結果は failed: に続けて、次のいずれかになります。
| 理由 | 意味 | 再試行するか |
|---|---|---|
rate_limit | プロバイダーが呼び出しを制限した | する(上限の範囲内) |
timeout | 時間内に答えが返らなかった | する(上限の範囲内) |
provider_error | プロバイダーがエラーを返した | する(上限の範囲内) |
refusal | LLMが回答を断った | しない |
schema_invalid | 答えが出力の型に合わなかった | しない |
safety_rejected | セーフティフィルターがリクエストか答えを止めた | しない |
truncated | トークンの上限で答えが途中で切れた | しない |
再試行するのは一時的な理由だけで、回数には上限があり、毎回同じリクエストを送ります。出力の型に合わない答えを、一部だけ型に合った値に無理やり直すことはありません。schema_invalid になり、そのレコードの value は NULL です。
既定では、失敗したレコードも含めてすべてのレコードが返るので、どう扱うかはプログラムが決めます。ok だけ残す、失敗をレビュー用のテーブルに回す、数える、などです。失敗がステップ全体を失敗させるかどうかは、タスクではなくプログラムが選びます。タスクは指示、型、LLM、再試行を決め、INFER のステップが失敗したレコードの扱いを決めます。部分的な結果を拒否するには、ON FAILURE FAIL を足します。有限回の再試行のあとに1件でも非成功のレコードが残れば、ステップの結果は成功として公開されず、後ろのステップやEffectには見えず、すでに行った呼び出しはその費用とともに証跡として残ります。
|> INFER `model-task:churn-risk.v1`
INPUT (notes, active_users, trend) AS risk
ON FAILURE FAIL上限とコスト
最初の呼び出しの前に、ActQLは送ることになる異なるキーの数を数えます。それがタスクの max_items、Hostの上限、またはプログラムのガードを超えていれば、LLMは1回も呼ばれず、ステップは上限の名前を示して失敗します。件数、タスクのトークン上限、再試行の契約から、そのステップの呼び出し回数、バイト数、トークン数の最大値も確定するので、Runの処理量は始まる前から有限です。actql explain はこれらの上限を表示します。ただし、処理量が有限であることと料金は別です。料金は、入力と出力のトークン、添付したコンテンツ、再試行に対するプロバイダーの条件で決まるので、金額の予算はタスクとHostの契約から確定するものであって、件数から読み取れるものではありません。
答えは観測である
LLMの答えは、各試行の証跡とともに、外部からの観測としてRunに記録されます。Runを再開したり復旧したりしても、採用した答えが再利用されます。LLMに聞き直すことはなく、プログラムには同じ値が見えます。新しい定義で答えを取り直したいときは、タスクのバージョンを変えてください。
ActQLが保証するのは、答えの型とその扱い方であって、判断が正しいことではありません。間違えたときの影響が大きい場面では、EffectをCheckpointの後ろに置き、書き込まれる前に人が値を見られるようにしてください。
次に読む
- プログラムを書く:
INFERの完全な構文と、推論の前に検索するNEAREST - ローカルとリモートのHost:アカウント設定での
inferの振り分け - 実行時の上限:環境ごとの既定の上限
Source: actql/actql/docs/guides/models.md at d904416674e4 · Edit on GitHub