Skip to Content

AIが書いたコードに動く証拠を

AIエージェントがサービスを変更したとき、その変更が動くことは、AIエージェント自身が書いたコードやテストとは別のもので示す必要があります。ProbeはGitHub ActionsのようなYAMLで定義したWorkflowを実行し、動いているサービスをHTTP、gRPC、データベース、メール、SSH、シェル越しに操作して、結果を検証します。WorkflowはAIエージェントが書き、Probeが実行前に検査し、許可していない書き込みや接続を防いで実行します。

サインアップのシナリオの例

Create accounthttpPost the formDatabasedbRow is pendingMailboximapConfirmation arrivedActivatehttpFollow the link

最初にCreate accountがフォームを送信し、返ってきたidを後続へ渡します。これを待つのがDatabaseとMailboxで、この2つは互いに依存しないため同時に走ります。DatabaseはSQLiteにレコードの状態を問い合わせ、MailboxはIMAPでメールボックスを開いて確認メールを探します。Activateはその両方が終わるのを待ってから、確認リンクをたどります。

signup-flow.yml
1name: Sign-up Flow2 3vars:4  api: https://api.example.com5  mail_user: ada@example.com6  mail_pass: "{{MAIL_PASS}}"7 8jobs:9- name: Create account10  id: signup11  defaults:12    http:13      url: "{{vars.api}}"14  steps:15  - name: Post the form16    id: create17    uses: http18    with:19      post: /signup20    test: res.code == 201 && res.body.status == "pending"21    outputs:22      user_id: res.body.id23 24- name: Database25  id: db26  needs: [signup]27  defaults:28    db:29      dsn: file:./app.db30  steps:31  - name: Row is pending32    uses: db33    with:34      query: SELECT status FROM users WHERE id = ?35      params: ["{{outputs.create.user_id}}"]36    test: res.code == 0 && res.rows[0].status == "pending"37 38- name: Mailbox39  id: mailbox40  needs: [signup]41  defaults:42    imap:43      host: imap.example.com44      username: "{{vars.mail_user}}"45      password: "{{vars.mail_pass}}"46  steps:47  - name: Confirmation arrived48    uses: imap49    with:50      commands:51      - name: select52        mailbox: INBOX53      - name: fetch54        sequence: "1"55        dataitem: ALL56    test: |57      res.code == 0 &&58      res.data.fetch.messages[0].subject == "Confirm your address" &&59      res.data.fetch.messages[0].to == vars.mail_user60 61- name: Activate62  needs: [db, mailbox]63  defaults:64    http:65      url: "{{vars.api}}"66  steps:67  - name: Follow the link68    uses: http69    with:70      get: "/users/{{outputs.create.user_id}}"71    test: res.code == 200 && res.body.status == "active"
$ probe signup-flow.yml
Sign-up Flow ⏺ Create account (Completed in 0.11s)  ⎿ 0. ✓  Post the form ⏺ Database (Completed in 0.02s)  ⎿ 0. ✓  Row is pending ⏺ Mailbox (Completed in 0.03s)  ⎿ 0. ✓  Confirmation arrived ⏺ Activate (Completed in 0.01s)  ⎿ 0. ✓  Follow the link Total workflow time: 0.15s ✓ All jobs succeeded

Workflowの構成要素

WorkflowはJobの集まりで、JobはStepの並びです。Stepは書いた順に1つずつ走ります。Jobは既定で並列に走り、needsを書いたものだけが依存先の完了を待ちます。Jobにrepeatを足せば、同じファイルが一定間隔で走り、成功率を返します。

WorkflowneedsJobStepStepStepJobStepStepJobStep
ジョブとステップを読む

サポートしているアクション

Stepのusesにアクション名を書くと、Probeはそのアクションを別プロセスとして起動し、gRPC越しにやり取りします。ビルトインのアクションも、自作のアクションとまったく同じ手順で接続されます。特別扱いされているものは1つもないので、必要なアクションが無ければ自分で追加できます。

gRPCprobeワークフロー実行ビルトインhttpdbshellsshsmtpimapgrpcembedded自作のアクションgraphqljmap
アクションリファレンスを読む

共通処理は別のファイルに

シナリオの多くはログインから始まり、2つ目以降のシナリオでも同じ手順を書くことになります。その手順をジョブファイルに置けば、どのWorkflowからもuses: embeddedで実行でき、varsを渡してoutputsを受け取れます。呼び出したジョブのStepはレポートでも入れ子で表示されるため、失敗したときにどのStepかがわかります。

orders.yml
1name: Orders2 3vars:4  password: "{{PASSWORD}}"5 6jobs:7- name: Order history8  defaults:9    http:10      url: https://api.example.com11 12  steps:13  - name: Log in14    id: auth15    uses: embedded16    with:17      path: ./login-job.yml18      vars:19        username: ada20        password: "{{vars.password}}"21    test: res.code == 022    outputs:23      token: res.outputs.token24 25  - name: List orders26    uses: http27    with:28      get: /orders29      headers:30        authorization: "Bearer {{outputs.auth.token}}"31    test: res.code == 200
login-job.yml
1name: Log in2 3defaults:4  http:5    url: https://api.example.com6    headers:7      content-type: application/json8 9steps:10- name: Post credentials11  id: login12  uses: http13  with:14    post: /login15    body:16      username: "{{vars.username}}"17      password: "{{vars.password}}"18  test: res.code == 20019  outputs:20    token: res.body.access_token
embeddedアクションのリファレンスを読む

Stepで使用できる便利な機能

Stepにはアクションの設定以外に、以下の共通機能があります。

test

式が真ならそのStepは成功です。レスポンスの中身はすべて参照できます。

yaml
1test: res.code == 200 && res.body.status == "ok"

outputs

値に名前をつけておくと、あとのStepやJobから読めます。

yaml
1outputs:2  token: res.body.access_token

iteration

変数の組ごとに、同じStepをコピーせず繰り返します。

yaml
1iteration:2- {name: Alice, role: admin}3- {name: Bob, role: user}

retry

失敗したときに、間隔をあけてやり直します。

yaml
1retry:2  max_attempts: 33  interval: 5s

skipif

条件に当てはまるときは、そのStepを飛ばします。

yaml
1skipif: vars.env == "local"

wait

先に落ち着くのを待ちたいとき、実行前に待機します。

yaml
1wait: 5s

AIエージェントのために

AIエージェントがコードとテストを書くなら、その両方を確かめる仕組みが要ります。Probeは動いているバージョンの使い方をAIエージェントに教え、Workflowの誤りを実行前に見つけ、レスポンスの正しさをAIエージェント自身のtestではなく仕様で判定し、書き込みや許可していないホストへの接続を送信前に拒否します。

agent-workflow.yml
1name: Profile2 3jobs:4- name: Profile5  defaults:6    http:7      url: https://api.example.com8  steps:9  - name: Log in10    id: login11    uses: http12    with:13      post: /login14      bdoy:15        username: ada16    test: res.code == 20017    outputs:18      token: res.body.token19 20  - name: Read profile21    uses: http22    with:23      get: /me24      headers:25        authorization: "Bearer {{outputs.login.tokn}}"26    tset: res.code == 200
$ probe check agent-workflow.yml
agent-workflow.yml:14: error: job 0 "Profile", step 0 "Log in": with: unknown key "bdoy" for the http action; did you mean "body"?agent-workflow.yml:20: warning: job 0 "Profile", step 1 "Read profile": nothing checks this step: it has no testagent-workflow.yml:25: error: job 0 "Profile", step 1 "Read profile": with: outputs.login.tokn is not published: step "login" publishes tokenagent-workflow.yml:26: error: job 0 "Profile", step 1 "Read profile": unknown key "tset"; did you mean "test"? 3 errors, 1 warning

probe checkはWorkflowを実行せずに読みます。Stepやwithのキーの綴りを間違えると、実行時には無視されて別の理由で通ってしまうことがあります。outputsの名前の誤りは、実行してそのStepに来るまでわかりません。ここではどれも何かを送る前に行番号つきのエラーになり、何も確かめていないStepは警告になります。

skill

Workflowの書き方、実行、失敗の読み方を教えるスキルを入れます。AIエージェントはバイナリに組み込まれたリファレンスをprobe guideで読むので、動いているバージョンに合ったYAMLを書きます。

shell
1probe skill install2probe guide http

openapi

リクエストとレスポンスをOpenAPIのドキュメントと照合します。正しさを決めるのはAIエージェントが書いたtestではなく仕様なので、間違ったtestが間違ったレスポンスを通すことはありません。

yaml
1uses: http2with:3  get: /users/14  openapi:5    spec: ./openapi.yml

proto

gRPCでも同じく.protoファイルと照合します。buf.validateのルールとgoogle.api.field_behaviorも確かめます。

yaml
1uses: grpc2with:3  method: GetUser4  proto:5    files: [./proto/user/v1/user.proto]

coverage

実行したStepが仕様のどのオペレーション、レスポンス、メソッドを確かめたか、まだどれが残っているかを示します。

shell
1probe --report json workflow.yml2probe coverage openapi.yml \3  probe-report.json

--read-only

AIエージェントが書いたWorkflowを、書き込ませずに走らせます。読み取り以外の操作と、許可していないホストへの接続は、送る前に拒否します。

shell
1probe --read-only \2  --allow-host api.staging.example.com \3  workflow.yml

exit code

終了コードで何を調べればよいかがわかります。testや仕様の違反か、Workflow自体の誤りか、相手が応答しなかったかを区別します。

shell
1probe workflow.yml2echo $?  # 1 test, 2 workflow, 3 action
AIエージェント向けにプロジェクトを設定する

インストール

Probeは実行時の依存がない、単体のバイナリです。Homebrewなら1つのコマンドで入ります。

$ brew install mozership/tap/probe

Goでインストールする場合

go install github.com/mozership/probe/cmd/probe@latest
インストールガイドを読む