Convex Agents Human Agents について
Convex Agents Human Agents は、Convex のエージェントスレッドに人間の参加を組み込むスキルです。応答の振り分け、エスカレーション、承認、担当者の追跡を扱います。
主な機能
- 01人間とユーザーのメッセージ保存
- 02AI と人間の応答振り分け
- 03ツール経由で人間に支援を依頼
- 04スレッドの担当者を追跡
- 05承認ゲートと不確実時のエスカレーション
活用シーン
- 01複雑なサポート案件のエスカレーション
- 02送信前のAI応答レビュー
- 03人間の判断とフィードバックの収集
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ sstobo/ convex-skills --skill convex-agents-human-agentsインストール後、Claude Code で/convex-agents-human-agentsを入力して読み込まれたか確認します。
詳細
目的
人間エージェントを利用すると、人間がエージェントのスレッドに参加でき、人間と AI が協力するハイブリッドなワークフローを構築できます。サポート、承認ワークフロー、エスカレーションに最適です。
このスキルを使用する場面
- 人間へのエスカレーションが必要なカスタマーサポート
- 人間が AI の判断を確認する承認ワークフロー
- 人間と AI の協働(例:ブレインストーミング)
- 人間の状況理解や判断を必要とするワークフロー
- AI で解決できない例外への対応
- 継続的な改善のための人間からのフィードバック収集
使用方法
1. ユーザーメッセージを保存する
エンドユーザーからのメッセージを保存します:
// convex/humanAgents.ts
import { mutation } from "./_generated/server";
import { v } from "convex/values";
import { saveMessage } from "@convex-dev/agent";
import { components } from "./_generated/api";
export const saveUserMessage = mutation({
args: { threadId: v.string(), message: v.string() },
handler: async (ctx, { threadId, message }) => {
const { messageId } = await saveMessage(ctx, components.agent, {
threadId,
prompt: message, // User message without agent generation
});
return { messageId };
},
});
2. 人間のエージェントからの応答を保存する
人間(例:サポート担当者)からのメッセージを保存します:
// convex/humanAgents.ts
import { mutation } from "./_generated/server";
import { v } from "convex/values";
import { saveMessage } from "./_generated/api";
import { components } from "./_generated/api";
export const saveHumanResponse = mutation({
args: {
threadId: v.string(),
humanName: v.string(),
response: v.string(),
},
handler: async (ctx, { threadId, humanName, response }) => {
const { messageId } = await saveMessage(ctx, components.agent, {
threadId,
agentName: humanName, // Human's name as the "agent"
message: {
role: "assistant",
content: response,
},
metadata: {
provider: "human",
providerMetadata: {
human: { name: humanName },
},
},
});
return { messageId };
},
});
3. 次に誰が応答するかを決める
AI または人間に振り分けます:
// convex/humanAgents.ts
import { action } from "./_generated/server";
import { v } from "convex/values";
import { myAgent } from "./agents/myAgent";
export const routeResponse = action({
args: { threadId: v.string(), userId: v.string(), question: v.string() },
handler: async (ctx, { threadId, userId, question }) => {
// Strategy 1: Check database for assigned responder
const assignment = await ctx.db
.query("threadAssignments")
.filter((a) => a.threadId === threadId)
.first();
if (assignment?.assignedTo === "human") {
return { responder: "human", requiresApproval: true };
}
// Strategy 2: Use fast LLM to classify
const classification = await myAgent.generateText(
ctx,
{ threadId },
{
prompt: `Should a human or AI respond? Question: ${question}`,
}
);
if (classification.text.includes("human")) {
return { responder: "human", reason: classification.text };
}
// Strategy 3: Use AI to respond
return { responder: "ai" };
},
});
4. ツールを使った人間への振り分け
AI がツールを呼び出して、人間の介入を依頼できるようにします:
// convex/humanAgents.ts
import { tool } from "ai";
import { z } from "zod";
import { action } from "./_generated/server";
import { v } from "convex/values";
import { myAgent } from "./agents/myAgent";
const askHumanTool = tool({
description: "Ask a human agent for help",
parameters: z.object({
question: z.string().describe("Question for the human"),
}),
});
export const generateWithHumanTool = action({
args: { threadId: v.string(), prompt: v.string() },
handler: async (ctx, { threadId, prompt }) => {
const result = await myAgent.generateText(
ctx,
{ threadId },
{
prompt,
tools: { askHuman: askHumanTool },
maxSteps: 5,
}
);
// Check if AI asked for human help
const humanRequests = result.toolCalls.filter(
(tc) => tc.toolName === "askHuman"
);
if (humanRequests.length > 0) {
// Notify human team
await ctx.runMutation(internal.humanAgents.notifyHumanTeam, {
threadId,
requests: humanRequests,
});
}
return result;
},
});
5. ツール呼び出しへの人間の応答
AI がツールを介して人間に支援を依頼した後、人間が応答します:
// convex/humanAgents.ts
import { internalAction } from "./_generated/server";
import { v } from "convex/values";
import { saveMessage } from "@convex-dev/agent";
import { components } from "./_generated/api";
import { myAgent } from "./agents/myAgent";
export const humanRespondToToolCall = internalAction({
args: {
threadId: v.string(),
messageId: v.string(),
toolCallId: v.string(),
humanName: v.string(),
response: v.string(),
},
handler: async (
ctx,
{ threadId, messageId, toolCallId, humanName, response }
) => {
// Save human response as tool result
await saveMessage(ctx, components.agent, {
threadId,
message: {
role: "tool",
content: [
{
type: "tool-result",
toolName: "askHuman",
toolCallId,
result: response,
},
],
},
metadata: {
provider: "human",
providerMetadata: { human: { name: humanName } },
},
});
// Continue AI generation with human's response
const { thread } = await myAgent.continueThread(ctx, { threadId });
await thread.generateText({ promptMessageId: messageId });
},
});
6. 担当者を記録する
スレッドに誰が応答するべきかを保存します:
// convex/humanAgents.ts
import { mutation } from "./_generated/server";
import { v } from "convex/values";
export const assignThread = mutation({
args: {
threadId: v.string(),
assignedTo: v.union(v.literal("ai"), v.literal("human")),
assignedUser: v.optional(v.string()),
},
handler: async (ctx, { threadId, assignedTo, assignedUser }) => {
await ctx.db.insert("threadAssignments", {
threadId,
assignedTo,
assignedUser,
assignedAt: Date.now(),
});
},
});
7. 承認ワークフローを実装する
AI が応答を作成し、送信前に人間が承認します:
// convex/humanAgents.ts
import { action, mutation } from "./_generated/server";
import { v } from "convex/values";
import { saveMessage } from "@convex-dev/agent";
import { components } from "./_generated/api";
import { myAgent } from "./agents/myAgent";
// Step 1: Generate AI response (pending approval)
export const generateForApproval = action({
args: { threadId: v.string(), prompt: v.string() },
handler: async (ctx, { threadId, prompt }) => {
const { thread } = await myAgent.continueThread(ctx, { threadId });
const result = await thread.generateText({ prompt });
// Save as draft (not yet visible to user)
const { messageId } = await saveMessage(ctx, components.agent, {
threadId,
message: { role: "assistant", content: result.text },
metadata: { status: "pending_approval" },
});
// Notify human reviewer
await ctx.runMutation(internal.humanAgents.notifyForApproval, {
threadId,
messageId,
draftText: result.text,
});
return { messageId, draftText: result.text };
},
});
// Step 2: Human approves or rejects
export const approveOrRejectResponse = mutation({
args: {
messageId: v.string(),
approved: v.boolean(),
review: v.optional(v.string()),
},
handler: async (ctx, { messageId, approved, review }) => {
// Update message metadata
const message = await ctx.db.get(messageId);
if (message) {
await ctx.db.patch(messageId, {
metadata: {
...message.metadata,
status: approved ? "approved" : "rejected",
review,
},
});
}
},
});
8. エスカレーションシステム
AI の確信度が低い場合、人間にエスカレーションします:
// convex/humanAgents.ts
import { action } from "./_generated/server";
import { v } from "convex/values";
import { z } from "zod";
import { myAgent } from "./agents/myAgent";
export const generateWithConfidence = action({
args: { threadId: v.string(), prompt: v.string() },
handler: async (ctx, { threadId, prompt }) => {
const result = await myAgent.generateObject(
ctx,
{ threadId },
{
prompt,
schema: z.object({
response: z.string(),
confidence: z.number().min(0).max(1),
requiresHuman: z.boolean(),
}),
}
);
const { response, confidence, requiresHuman } = result.object;
if (requiresHuman || confidence < 0.7) {
// Escalate to human
await ctx.runMutation(internal.humanAgents.escalateToHuman, {
threadId,
reason: `AI confidence: ${confidence}`,
aiSuggestion: response,
});
return { escalated: true };
}
return { response, confidence };
},
});
主な原則
- ハイブリッドなワークフロー:AI の効率性と人間の判断力を組み合わせます
- ツールを使ったエスカレーション:AI はツールを通じて人間に支援を依頼できます
- 承認ゲート:機密性の高い応答は人間を介して振り分けます
- メタデータの追跡:メッセージが人間から提供されたものであることを記録します
- 担当者の追跡:次に誰が応答するべきかを把握します
- 円滑なフォールバック:AI が確信を持てない場合は人間に引き継ぎます
例:エスカレーション付きサポートチャット
// convex/support.ts
import { mutation, action, query } from "./_generated/server";
import { v } from "convex/values";
import { saveMessage } from "@convex-dev/agent";
import { components } from "./_generated/api";
import { supportAgent } from "./agents";
import { z } from "zod";
import { tool } from "ai";
const escalateTool = tool({
description: "Escalate to human support",
parameters: z.object({
reason: z.string(),
}),
});
// User sends message
export const sendSupportMessage = mutation({
args: { threadId: v.string(), message: v.string() },
handler: async (ctx, { threadId, message }) => {
const { messageId } = await saveMessage(ctx, components.agent, {
threadId,
prompt: message,
});
return { messageId };
},
});
// AI or human responds
export const respondToTicket = action({
args: { threadId: v.string(), promptMessageId: v.string() },
handler: async (ctx, { threadId, promptMessageId }) => {
const result = await supportAgent.generateText(
ctx,
{ threadId },
{
promptMessageId,
tools: { escalate: escalateTool },
maxSteps: 3,
}
);
// Check if escalated
if (result.toolCalls.some((tc) => tc.toolName === "escalate")) {
await ctx.runMutation(internal.support.escalateTicket, { threadId });
}
},
});
// Human responds
export const humanReply = mutation({
args: { threadId: v.string(), humanName: v.string(), reply: v.string() },
handler: async (ctx, { threadId, humanName, reply }) => {
await saveMessage(ctx, components.agent, {
threadId,
agentName: humanName,
message: { role: "assistant", content: reply },
metadata: { provider: "human" },
});
},
});
よくあるパターン
- 最初の対応は AI:よくある問題にすばやく応答します
- 確信が持てない場合にエスカレーション:複雑なケースは人間が対応します
- 承認ゲート:送信前に人間が確認します
- ハイブリッドな推論:AI が分析し、人間が判断します
- フィードバックループ:人間が時間をかけて AI を改善します
次のステップ
- ストリーミングを追加する:リアルタイムの人間からの応答については、Convex Agents Streaming を参照してください
- レート制限を実装する:エスカレーションの制限については、Convex Agents Rate Limiting を参照してください
- 使用状況を追跡する:人間の作業に対する請求については、Convex Agents Usage Tracking を参照してください
トラブルシューティング
- エスカレーションが多すぎる:AI への指示を改善するか、ツールを追加します
- 人間の対応が追いつかない:振り分けやキュー管理を改善します
- コンテキストが失われる:人間への通知にスレッドの履歴を含めます
- 応答時間が長い:人間の応答時間に関する SLA を監視します
コメント
Markdown 対応