mcpField
C
Convex Agents Human Agents

Convex Agents Human Agents

github.com/sstoboAgent 工作流24198 次安装40 周前更新
代码仓库

关于 Convex Agents Human Agents

Convex Agents Human Agents 是一项为 Convex Agent 对话加入人工参与的技能,涵盖响应路由、升级处理、审批和任务分配跟踪,适用于人工与 AI 协作流程。

主要功能

  1. 01保存用户与人工消息
  2. 02在 AI 和人工间路由响应
  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确认已加载。

详细介绍

用途

人类 Agent 允许人类参与 Agent 线程,从而创建人类与 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. 保存人类 Agent 响应

存储来自人类(例如支持 Agent)的消息:

// 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