mcpField
C
Convex Agents Human Agents

Convex Agents Human Agents

github.com/sstoboエージェントワークフロー24198 回インストール40 週間前に更新
リポジトリ

Convex Agents Human Agents について

Convex Agents Human Agents は、Convex のエージェントスレッドに人間の参加を組み込むスキルです。応答の振り分け、エスカレーション、承認、担当者の追跡を扱います。

主な機能

  1. 01人間とユーザーのメッセージ保存
  2. 02AI と人間の応答振り分け
  3. 03ツール経由で人間に支援を依頼
  4. 04スレッドの担当者を追跡
  5. 05承認ゲートと不確実時のエスカレーション

活用シーン

  1. 01複雑なサポート案件のエスカレーション
  2. 02送信前のAI応答レビュー
  3. 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 を監視します

コメント