CI のエージェントの段階とは
継続的インテグレーション(CI)は、リポジトリに送られた変更のたびに確認を動かします。チームは、エージェントを一つの段階として足せます。たとえば、文書の誤字を直す段階です。
パイプラインの段階は誰も見ていないので、その限度を先に書いておきます。一回の実行を限るだけでは足りません。このレッスンでは、そのまわりのパイプライン全体を設計します。
このレッスンのゴール
メモ
マージの前に人の承認を置いた、エージェントの段階を設計することができる。
キーワード
| ことば | いみ | れい |
|---|---|---|
| パイプライン | 変更のたびに順に動く段階の並び | |
| 権限 | 段階に許されていること | write-branch |
| 最小の権限 | 段階に必要な権限だけ | |
| 承認の関門 | 人が「よい」と言わなければならない所 | |
| 記録 | 段階がしたことの書き残し |
学生が入力するセルは、このプレビューでは空白です。
エージェントの段階には最小の権限
段階には、コードを読む、ブランチに書く、main ブランチに書く、チームの秘密の情報を読む、といった権限を与えられます。エージェントの段階に要るのは、読むこととブランチに書くことです。main や秘密の情報は、決して要りません。
さらに、変えてよいフォルダーと時間の上限も書きます。星印は「何でも」という意味で、限度がまったくありません。
例: エージェントの段階の権限
| 権限 | エージェントの段階 | 理由 |
|---|---|---|
| read | あり | コードを読まなければならない |
| write-branch | あり | 変更は自分のブランチに置く |
| write-main | なし | main に書くのは、人が承認したマージだけ |
| secrets | なし | プロンプトの中の秘密の情報は漏れうる |
| 変えてよいもの | docs/ だけ | タスクに要るフォルダー |
| 時間の上限 | 30 分以下 | 行き詰まったエージェントは止まる |
学生が入力するセルは、このプレビューでは空白です。
テスト、人、そしてマージ
エージェントの段階のあと、そのブランチでテストがもう一度動きます。次に人が変更を読んで承認し、そこで初めてマージが動きます。
段階は記録を残します。与えられたタスク、動かしたコマンド、変えたファイルです。一か月後に何かがおかしくなったとき、記録が何が起きたかを教えてくれます。
よくある間違い
注意
CI のエージェントは、ブランチを作るだけなので安全だと考える人が多いです。秘密の情報を読めたり、main に書けたりする段階は、害を与えることがあります。権限は最小にし、どのマージの前にもテストと人を置きましょう。
助っ人 model.js を書く(1)
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const base = process.env.AI_URL || \"http://ai:4000\";' '// 応答のトークン数。なければ null。' 'function count(data, name) {' ' return data.usage && typeof data.usage[name] === \"number\" ? data.usage[name] : null;' '}' > model.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
助っ人 model.js を書く(2)
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function ask(messages, sample, max) {' ' try {' ' const res = await fetch(base + \"/v1/chat/completions\", {' ' method: \"POST\",' ' headers: { \"content-type\": \"application/json\", authorization: \"Bearer lab\" },' ' body: JSON.stringify({ model: \"lab\", messages: messages, max_tokens: max || 100 }),' ' signal: AbortSignal.timeout(30000),' ' });' ' if (res.ok === false) throw new Error(\"status \" + res.status);' ' const data = await res.json();' ' const text = data.choices[0].message.content;' ' return { text, inTokens: count(data, \"prompt_tokens\"), outTokens: count(data, \"completion_tokens\"), recorded: false };' ' } catch (error) {' ' console.log(\"記録済みサンプル: モデルが答えませんでした (\" + error.message + \")。\");' ' return { text: sample, inTokens: null, outTokens: null, recorded: true };' ' }' '}' 'module.exports = { ask };' >> model.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
パイプラインの確認を書く(1)
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const { ask } = require(\"./model\");' 'const pipelines = {' ' \"文書ボット\": [' ' { name: \"checkout\", kind: \"setup\", permissions: [\"read\"] },' ' { name: \"agent\", kind: \"agent\", permissions: [\"read\", \"write-branch\"], may_change: [\"docs/\"],' ' keeps_log: true, max_minutes: 20 },' ' { name: \"tests\", kind: \"test\", permissions: [\"read\"] },' ' { name: \"merge\", kind: \"merge\", permissions: [\"write-main\"], needs_approval: true },' ' ],' ' \"自動修正\": [' ' { name: \"agent\", kind: \"agent\", permissions: [\"read\", \"write-main\", \"secrets\"], may_change: [\"*\"],' ' keeps_log: false, max_minutes: 90 },' ' { name: \"merge\", kind: \"merge\", permissions: [\"write-main\"], needs_approval: false },' ' ],' '};' > ci.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
パイプラインの確認を書く(2)
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function stepProblems(s) {' ' const out = [];' ' if (s.permissions.indexOf(\"write-main\") >= 0) out.push(\"main に直接書ける\");' ' if (s.permissions.indexOf(\"secrets\") >= 0) out.push(\"秘密の情報を読める\");' ' if (s.may_change.length === 0 || s.may_change.indexOf(\"*\") >= 0) out.push(\"何でも変えられる\");' ' if (s.keeps_log === false) out.push(\"記録を残さない\");' ' if (s.max_minutes > 30) out.push(\"長く動きすぎる\");' ' return out.map(function (p) { return s.name + \": \" + p; });' '}' >> ci.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
パイプラインの確認を書く(3)
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function problems(steps) {' ' let out = [];' ' let agent = false;' ' let tested = false;' ' steps.forEach(function (s) {' ' if (s.kind === \"agent\") { out = out.concat(stepProblems(s)); agent = true; tested = false; }' ' if (s.kind === \"test\") tested = true;' ' if (s.kind === \"merge\" && agent && tested === false) out.push(s.name + \": エージェントのあとにテストがない\");' ' if (s.kind === \"merge\" && agent && s.needs_approval === false) out.push(s.name + \": 承認がない\");' ' });' ' return out;' '}' >> ci.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
パイプラインの確認を書く(4)
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function main() {' ' Object.keys(pipelines).forEach(function (name) {' ' const found = problems(pipelines[name]);' ' console.log(name + \": \" + (found.length === 0 ? \"安全\" : \"危険\"));' ' found.forEach(function (p) { console.log(\" \" + p); });' ' });' ' const q = \"短く一文で答えてください。エージェントの変更をマージする前に、人が承認するのは、なぜですか。\";' ' const sample = \"変更が何を意味するかは、テストにもエージェントにも判断できず、人なら見られるからです。\";' ' const reply = await ask([{ role: \"user\", content: q }], sample, 80);' ' console.log(\"モデル: \" + reply.text);' '}' 'main();' >> ci.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
パイプラインの確認を動かす
docker exec -w /home/node/project agent node ci.jsここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
Run の出力
| 行 | 意味 |
|---|---|
| 文書ボット: 安全 | 最小の権限、テスト、そして承認 |
| 自動修正: 危険 | その下に七つの問題が並ぶ |
| agent: main に直接書ける | 人の承認の前に main が変わる |
| agent: 秘密の情報を読める | プロンプトから秘密の情報が漏れうる |
| agent: 何でも変えられる | 変えてよい場所に限りがない |
| agent: 記録を残さない | あとで何が起きたか調べられない |
| agent: 長く動きすぎる | 時間の上限 30 分を超える(90 分) |
| merge: エージェントのあとにテストがない | 変更を何も確かめない |
| merge: 承認がない | マージの前に誰も「よい」と言わない |
| モデル: ... | ラボからの一文。モデルが答えなければ記録済みサンプル |
学生が入力するセルは、このプレビューでは空白です。