こんにちは。ウォンテッドリーでバックエンドエンジニアをしている小室(@nekorush14)です。2026年9月28日に、パーソナル転職AIエージェント「Mendy」をリリースしました。今回はMendyのLLM処理においてAgent LoopとWorkflowをどう使い分けたかを紹介します。
目次
はじめに
Mendyは転職エージェントから届く求人をAIが解析し、求人ごとにマッチ度と根拠を示すモバイルアプリです。ユーザーはMendyが発行した専用のメールアドレスを普段やり取りしている転職エージェントへ連絡先として伝えます。転職エージェントからメールが届くと、Mendyはメールから求人を抽出し、足りない情報を補ったうえでマッチ度を診断します。
新アプリ「Mendy」を提供開始|一人ひとりに寄り添う「転職AIエージェント」 | Wantedly, Inc.
これらの処理では、「LLMを含む処理順をコードで定義するWorkflow」と「次の動作をLLM自身が決めるAgent Loop」を使い分けています。本稿では、Agent Loopの挙動をHooksで制御する実装と、Agent LoopとWorkflowを使い分ける観点を紹介します。
MendyのAI機能をどう実現しているか
現在のMendyには対話型のチャットUIが存在しません。Mendyが処理を始めるイベントはメールの受信です。このようなイベント駆動で動作するエージェントを「アンビエントエージェント(Ambient agents)」と呼んでいます。

Introducing ambient agents
Mendyでは届いたメールをきっかけにバックグラウンドでAI処理を進め、ユーザーが意識せずAIの補助を享受できる仕組みとしています。また、モデルの判断に委ねる範囲を可能な限り必要な箇所に絞り、残りの処理は手続的なコード定義とするようにしました。この方針を、Agent LoopとWorkflowの使い分けとして実装しています。
- Agent Loop
- LLMがツールの実行結果を受け取りながら次の動作を決定する
- Workflow
- LLMの呼び出しとツールの実行をあらかじめコードで決めた処理順で実行する
「どのツールを何回使うか」や「ループを完了させるか」はモデルが保持するその時のコンテキストによって決定します。
Agent Loopで自律度を高める
MendyでAgent Loopを採用した理由はWorkflowで定義不可能な終了条件が存在したためです。Mendyには求人のメールから不足している情報を補う処理が存在しますが、求人メールは自然言語で書かれており、事前に形式予測して計算しておくことは不可能です。このため、LLMが自律的にツールを使用した補完処理の継続、または終了を判断する必要がありました。
Agent Loopの実装には、AWSがOSSとして公開しているStrands Agents (TypeScript)を採用しました。
GitHub - strands-agents/harness-sdk: Build an agent harness and control it end-to-end. Open-source SDK for production AI agents in Python & TypeScript - any model, any cloud.
Strands Agentsではモデルやシステムプロンプト、ツール、出力のスキーマを定義することでツール呼び出し付きのAgent Loopを簡単に実装できます。ツールの呼び出しや結果の受け渡し、会話状態の保持、出力スキーマの検証はSDKが受け持ちます。MendyにはWorkflow処理のためにAmazon BedrockのConverse APIを直接呼び出す処理が存在しました。一方で、このクライアントの上でツール呼び出しのループまで自前で書くと、会話状態の管理やスキーマ検証のコードを機能ごとに作成する必要があります。今回はこれを避回避するため、Strands Agentsを採用しました。
以下は現在日時を返すツールを持つエージェントをStrands Agentsで実装した例です。
import { Agent, BedrockModel, tool } from "@strands-agents/sdk";
import { z } from "zod";
// tool() に名前、説明、zod の入力スキーマ、コールバックを渡してツールを定義する
const getCurrentTime = tool({
name: "get_current_time",
description: "指定したタイムゾーンの現在日時を返す。",
inputSchema: z.object({
timeZone: z.string().describe("IANA 形式のタイムゾーン (例: Asia/Tokyo)"),
}),
callback: async ({ timeZone }) => {
return new Date().toLocaleString("ja-JP", { timeZone });
},
});
const outputSchema = z.object({
timeZone: z.string().describe("回答に使った IANA 形式のタイムゾーン"),
currentTime: z.string().describe("get_current_time が返した日時文字列"),
answer: z.string().describe("質問への回答本文"),
});
const agent = new Agent({
model: new BedrockModel({ modelId, region }),
systemPrompt: "あなたは...",
tools: [getCurrentTime],
// ループを終えたら、このスキーマに沿った JSON で結果を返させる
structuredOutputSchema: outputSchema,
});
const result = await agent.invoke(prompt);
const output = result.structuredOutput;
*注: このコードは説明用のサンプルコードであり、実際に動いているコードとは異なります。
Hooksでツール呼び出しに介入する
Agent Loopによる処理ではプロンプトの指示から外れた動きをする場合があります。システムプロンプトに禁止事項を書いてもモデルが守るとは限りません。そこでMendyでは、プロンプトで指示したうえで、守らせたい制約をStrands AgentsのHooksとしてコードに書いています。
Hooksはエージェントのライフサイクルイベントにコールバックを登録する仕組みです。例えば、BeforeToolCallEventのコールバックでevent.cancelに理由を代入すると、ツールの実行を取り消せます。また、AfterToolCallEventのコールバックでevent.resultを書き換えると、モデルに渡るツールの結果を差し替えられます。
次のコードは、ツール呼び出しの回数に上限を設けるHooksの例です。
import { Agent, BeforeToolCallEvent } from "@strands-agents/sdk";
const limit = 10;
let calls = 0;
const agent = new Agent({
//...
});
agent.addHook(BeforeToolCallEvent, (event) => {
if (calls >= limit) {
event.cancel = `ツール呼び出しの上限 (${limit} 回) に達したため打ち切りました`;
return;
}
calls += 1;
});
*注: このコードは説明用のサンプルコードであり、実際に動いているコードとは異なります。
これらの実装により、ツール実行の取り消しや結果の差し替え結果がモデルのコンテキストに含まれるようになるため、ループの中で意図しない挙動を防止することができます。なお、SDKに同梱されたツールを自前のツールでラップする方法もありますが、同梱ツールが持つタイムアウトやキャンセルの処理を再実装する必要があります。Hooksを用いることでツール実装を変更せずに、整形や制約ロジックを集約できます。
Agent LoopとWorkflowの使い分けで確実性と実行時間を両立する
「メールから求人を抽出し、足りない情報を補ったうえでマッチ度を診断する」処理全体を1つのAgent Loopにする設計も考えられます。しかしながら、Mendyではこの設計を採用せず、補完以外の手順をWorkflowで構成する設計としました。これは、「本当にLLMが必要な箇所はどこか」を分解しながら明らかにして行った際、自然言語の解釈が必要な箇所だけLLMで処理すれば良いという結論に至ったためです。また、Agent Loopとしなかった理由は実行時間を抑えるためでもあります。
Agent Loopでは処理の終了判断をLLMが行うため、モデルやツールの呼び出し回数を予測することが難しく、実行時間も想定しずらいです。WorkflowであればLLMの呼び出し回数はコードで定義した通りとなり、ループ回数や実行時間を予測しやすくなります。
Mendyの開発を通して得られたAgent LoopとWorkflowの使い分け方は以下の通りです。
- 処理の手順と終了条件をコードで定義可能な場合、挙動を決定的に制御することが要件となる場合はWorkflowを採用する
- 終了条件を事前に定義、計算できず、必要な情報が揃ったかをモデルの推論で判断させる必要がある場合はAgent Loopを採用する
- Agent Loopを採用した場合でも意図した挙動となるような制約をHooksで実装する
まとめ
Mendyでは、メールから求人を抽出してマッチ度を診断するまでの処理をWorkflowで組み、終了条件を事前に決められない補完処理だけをAgent Loopとしました。また、Strands Agentsを採用することでAgent Loopをより少ないコードでプロダクトに組み込むことができました。
LLMをプロダクトに組み込む際に、まず実現したい機能のどこでLLMが必要となり、それは推論が必要なのか言語処理のみで良いのかを検討してみると良いでしょう。それにより、WorkflowとAgent Loopを適切に使い分けることができるようになります。
Mendyの全体のアーキテクチャと技術選定については市古さん(@sora_ichigo_x)が書いた別の記事で紹介していますので、ぜひ読んでみてください。
新アプリ Mendy のアーキテクチャと技術選定 | Wantedly Engineer Blog
参考

Building Effective AI Agents
