Agent を使うと、LLM の扱い方が変わります。ステートレスな Worker では、リクエストのたびに最初からやり直します。コンテキストを組み立て直し、モデルを呼び出し、応答を返して、すべてを忘れます。Agent は呼び出しのあいだ状態を保持し、WebSocket でクライアントと接続したままにでき、ユーザーがいなくても独自のスケジュールでモデルを呼び出せます。
このページでは、状態を持つ Agent の内部で LLM を呼び出すときに可能になるパターンを説明します。プロバイダーの設定とコード例は、AI モデルの利用 を参照してください。
すべての Agent には、組み込みの SQL データベース とキーバリュー状態があります。会話履歴全体をリクエストのたびにクライアントから渡す代わりに、Agent が保存し、自前のストレージからプロンプトを組み立てます。
import { Agent } from "agents";
export class ResearchAgent extends Agent {
async buildPrompt(userMessage) {
const history = this.sql`
SELECT role, content FROM messages
ORDER BY timestamp DESC LIMIT 50`;
const preferences = this.sql`
SELECT key, value FROM user_preferences`;
return [
{ role: "system", content: this.systemPrompt(preferences) },
...history.reverse(),
{ role: "user", content: userMessage },
];
}
}import { Agent } from "agents";
export class ResearchAgent extends Agent<Env> {
async buildPrompt(userMessage: string) {
const history = this.sql<{ role: string; content: string }>`
SELECT role, content FROM messages
ORDER BY timestamp DESC LIMIT 50`;
const preferences = this.sql<{ key: string; value: string }>`
SELECT key, value FROM user_preferences`;
return [
{ role: "system", content: this.systemPrompt(preferences) },
...history.reverse(),
{ role: "user", content: userMessage },
];
}
}つまり、クライアントはメッセージのたびに会話全体を送る必要がありません。履歴は Agent が持ち、刈り込み、取得した文書で補強し、古いターンを要約してからモデルへ送れます。
DeepSeek R1 や GLM-4 のような推論モデルは、応答まで 30 秒から数分かかることがあります。ステートレスなリクエスト/レスポンス構成では、そのあいだクライアントは接続を維持しなければなりません。接続が切れると、応答は失われます。
Agent はクライアントが切断したあとも動き続けます。応答が届いたら状態に保存し、クライアントが再接続したときに渡せます。数時間後や数日後でも構いません。
import { Agent } from "agents";
import { streamText } from "ai";
import { createWorkersAI } from "workers-ai-provider";
export class MyAgent extends Agent {
async onMessage(connection, message) {
const { prompt } = JSON.parse(message);
const workersai = createWorkersAI({ binding: this.env.AI });
const result = streamText({
model: workersai("@cf/zai-org/glm-4.7-flash"),
prompt,
});
for await (const chunk of result.textStream) {
connection.send(JSON.stringify({ type: "chunk", content: chunk }));
}
this.sql`INSERT INTO responses (prompt, response, timestamp)
VALUES (${prompt}, ${await result.text}, ${Date.now()})`;
}
}import { Agent } from "agents";
import { streamText } from "ai";
import { createWorkersAI } from "workers-ai-provider";
export class MyAgent extends Agent<Env> {
async onMessage(connection: Connection, message: WSMessage) {
const { prompt } = JSON.parse(message as string);
const workersai = createWorkersAI({ binding: this.env.AI });
const result = streamText({
model: workersai("@cf/zai-org/glm-4.7-flash"),
prompt,
});
for await (const chunk of result.textStream) {
connection.send(JSON.stringify({ type: "chunk", content: chunk }));
}
this.sql`INSERT INTO responses (prompt, response, timestamp)
VALUES (${prompt}, ${await result.text}, ${Date.now()})`;
}
}AIChatAgent を使うと、この処理は自動です。メッセージは SQLite に永続化され、再接続時にストリームが再開します。
Agent は、モデルを呼ぶためにユーザーリクエストを必要としません。夜間の要約、定期的な分類、監視など、人手を介さずに動かしたい処理を、バックグラウンドでスケジュールできます。
import { Agent } from "agents";
export class DigestAgent extends Agent {
async onStart() {
this.schedule("0 8 * * *", "generateDailyDigest", {});
}
async generateDailyDigest() {
const articles = this.sql`
SELECT title, body FROM articles
WHERE created_at > datetime('now', '-1 day')`;
const workersai = createWorkersAI({ binding: this.env.AI });
const { text } = await generateText({
model: workersai("@cf/zai-org/glm-4.7-flash"),
prompt: `Summarize these articles:\n${articles.map((a) => a.title + ": " + a.body).join("\n\n")}`,
});
this.sql`INSERT INTO digests (summary, created_at)
VALUES (${text}, ${Date.now()})`;
this.broadcast(JSON.stringify({ type: "digest", summary: text }));
}
}import { Agent } from "agents";
export class DigestAgent extends Agent<Env> {
async onStart() {
this.schedule("0 8 * * *", "generateDailyDigest", {});
}
async generateDailyDigest() {
const articles = this.sql<{ title: string; body: string }>`
SELECT title, body FROM articles
WHERE created_at > datetime('now', '-1 day')`;
const workersai = createWorkersAI({ binding: this.env.AI });
const { text } = await generateText({
model: workersai("@cf/zai-org/glm-4.7-flash"),
prompt: `Summarize these articles:\n${articles.map((a) => a.title + ": " + a.body).join("\n\n")}`,
});
this.sql`INSERT INTO digests (summary, created_at)
VALUES (${text}, ${Date.now()})`;
this.broadcast(JSON.stringify({ type: "digest", summary: text }));
}
}Agent は呼び出しをまたいで状態を保持するため、1 つのメソッド内で複数モデルを連鎖できます。分類には高速モデル、計画には推論モデル、検索には埋め込みモデルを使い、ステップ間でコンテキストを失いません。
import { Agent } from "agents";
import { generateText, embed } from "ai";
import { createWorkersAI } from "workers-ai-provider";
export class TriageAgent extends Agent {
async triage(ticket) {
const workersai = createWorkersAI({ binding: this.env.AI });
const { text: category } = await generateText({
model: workersai("@cf/zai-org/glm-4.7-flash"),
prompt: `Classify this support ticket into one of: billing, technical, account. Ticket: ${ticket}`,
});
const { embedding } = await embed({
model: workersai("@cf/baai/bge-base-en-v1.5"),
value: ticket,
});
const similar = await this.env.VECTOR_DB.query(embedding, { topK: 5 });
const { text: response } = await generateText({
model: workersai("@cf/zai-org/glm-4.7-flash"),
prompt: `Draft a response for this ${category} ticket. Similar resolved tickets: ${JSON.stringify(similar)}. Ticket: ${ticket}`,
});
this.sql`INSERT INTO tickets (content, category, response, created_at)
VALUES (${ticket}, ${category}, ${response}, ${Date.now()})`;
return { category, response };
}
}import { Agent } from "agents";
import { generateText, embed } from "ai";
import { createWorkersAI } from "workers-ai-provider";
export class TriageAgent extends Agent<Env> {
async triage(ticket: string) {
const workersai = createWorkersAI({ binding: this.env.AI });
const { text: category } = await generateText({
model: workersai("@cf/zai-org/glm-4.7-flash"),
prompt: `Classify this support ticket into one of: billing, technical, account. Ticket: ${ticket}`,
});
const { embedding } = await embed({
model: workersai("@cf/baai/bge-base-en-v1.5"),
value: ticket,
});
const similar = await this.env.VECTOR_DB.query(embedding, { topK: 5 });
const { text: response } = await generateText({
model: workersai("@cf/zai-org/glm-4.7-flash"),
prompt: `Draft a response for this ${category} ticket. Similar resolved tickets: ${JSON.stringify(similar)}. Ticket: ${ticket}`,
});
this.sql`INSERT INTO tickets (content, category, response, created_at)
VALUES (${ticket}, ${category}, ${response}, ${Date.now()})`;
return { category, response };
}
}中間結果はメソッド実行中、Agent のメモリに残ります。最終結果は SQL に永続化し、あとから参照できます。
永続ストレージがあるため、モデル応答をキャッシュして重複呼び出しを避けられます。埋め込みや長い推論チェーンなど、コストの高い処理で特に有効です。
import { Agent } from "agents";
export class CachingAgent extends Agent {
async cachedGenerate(prompt) {
const cached = this.sql`
SELECT response FROM llm_cache WHERE prompt = ${prompt}`;
if (cached.length > 0) {
return cached[0].response;
}
const workersai = createWorkersAI({ binding: this.env.AI });
const { text } = await generateText({
model: workersai("@cf/zai-org/glm-4.7-flash"),
prompt,
});
this.sql`INSERT INTO llm_cache (prompt, response, created_at)
VALUES (${prompt}, ${text}, ${Date.now()})`;
return text;
}
}import { Agent } from "agents";
export class CachingAgent extends Agent<Env> {
async cachedGenerate(prompt: string) {
const cached = this.sql<{ response: string }>`
SELECT response FROM llm_cache WHERE prompt = ${prompt}`;
if (cached.length > 0) {
return cached[0].response;
}
const workersai = createWorkersAI({ binding: this.env.AI });
const { text } = await generateText({
model: workersai("@cf/zai-org/glm-4.7-flash"),
prompt,
});
this.sql`INSERT INTO llm_cache (prompt, response, created_at)
VALUES (${prompt}, ${text}, ${Date.now()})`;
return text;
}
}複数エージェントをまたぐプロバイダーレベルのキャッシュとレート制限の管理には、AI Gateway を使います。