mcpField
C
Convex Agents Rate Limiting

Convex Agents Rate Limiting

github.com/sstobo開発24197 回インストール40 週間前に更新
リポジトリ

Convex Agents Rate Limiting について

Convex Agents 向けに、メッセージ頻度とトークン使用量の制限方法を解説するスキルです。ユーザー単位・全体のクォータ、バースト容量、使用量の記録を扱います。

主な機能

  1. 01固定ウィンドウのメッセージ制限
  2. 02トークンバケットによるバースト対応
  3. 03ユーザー単位・全体のクォータ
  4. 04トークン使用量の推定と記録
  5. 05クライアント側の制限状況確認

活用シーン

  1. 01チャットメッセージ頻度の制限
  2. 02ユーザーごとのトークン枠設定
  3. 03API クォータ内での利用
  4. 04課金用トークン使用量の記録

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/sstobo/convex-skills --skill convex-agents-rate-limiting

インストール後、Claude Code で/convex-agents-rate-limitingを入力して読み込まれたか確認します。

詳細

目的

レート制限は、不正利用を防ぎ、LLM のコストを管理し、公平なリソース配分を実現します。メッセージ頻度の制限とトークン使用量のクォータを扱います。

このスキルを使用する場面

  • 短時間に大量のメッセージが送信されるのを防ぐ
  • ユーザーごとの合計トークン数を制限する
  • バースト容量を実装する
  • プロバイダーのクォータを超えないよう、API 全体の制限を設定する
  • マルチユーザーシステムでリソースを公平に配分する
  • トークン使用量に基づいて課金する

レートリミッターの設定

import { RateLimiter, MINUTE, SECOND } from "@convex-dev/rate-limiter";

export const rateLimiter = new RateLimiter(components.rateLimiter, {
  sendMessage: {
    kind: "fixed window",
    period: 5 * SECOND,
    rate: 1,
    capacity: 2,
  },
  globalSendMessage: {
    kind: "token bucket",
    period: MINUTE,
    rate: 1_000,
  },
  tokenUsagePerUser: {
    kind: "token bucket",
    period: MINUTE,
    rate: 2000,
    capacity: 10000,
  },
  globalTokenUsage: {
    kind: "token bucket",
    period: MINUTE,
    rate: 100_000,
  },
});

メッセージのレート制限を確認する

export const sendMessage = mutation({
  args: { threadId: v.string(), message: v.string(), userId: v.string() },
  handler: async (ctx, { threadId, message, userId }) => {
    try {
      await rateLimiter.limit(ctx, "sendMessage", {
        key: userId,
        throws: true,
      });

      await rateLimiter.limit(ctx, "globalSendMessage", { throws: true });

      const { messageId } = await saveMessage(ctx, components.agent, {
        threadId,
        prompt: message,
      });

      return { success: true, messageId };
    } catch (error) {
      if (isRateLimitError(error)) {
        return {
          success: false,
          error: "Rate limit exceeded",
          retryAfter: error.data.retryAfter,
        };
      }
      throw error;
    }
  },
});

トークン使用量を確認する

export const checkTokenUsage = action({
  args: { threadId: v.string(), question: v.string(), userId: v.string() },
  handler: async (ctx, { threadId, question, userId }) => {
    const estimatedTokens = await estimateTokens(ctx, threadId, question);

    try {
      await rateLimiter.check(ctx, "tokenUsagePerUser", {
        key: userId,
        count: estimatedTokens,
        throws: true,
      });

      // Proceed with generation
      const { thread } = await myAgent.continueThread(ctx, { threadId });
      const result = await thread.generateText({ prompt: question });

      return { success: true, response: result.text };
    } catch (error) {
      if (isRateLimitError(error)) {
        return {
          success: false,
          error: "Token limit exceeded",
          retryAfter: error.data.retryAfter,
        };
      }
      throw error;
    }
  },
});

async function estimateTokens(
  ctx: QueryCtx,
  threadId: string,
  question: string
): Promise<number> {
  const questionTokens = Math.ceil(question.length / 4);
  const responseTokens = Math.ceil(questionTokens * 3);
  return questionTokens + responseTokens;
}

実際の使用量を追跡する

const myAgent = new Agent(components.agent, {
  name: "My Agent",
  languageModel: openai.chat("gpt-4o-mini"),
  usageHandler: async (ctx, { usage, userId }) => {
    if (!userId) return;

    await rateLimiter.limit(ctx, "tokenUsagePerUser", {
      key: userId,
      count: usage.totalTokens,
      reserve: true,
    });
  },
});

クライアント側でレート制限を確認する

import { useRateLimit } from "@convex-dev/rate-limiter/react";
import { isRateLimitError } from "@convex-dev/rate-limiter";

function ChatInput() {
  const { status } = useRateLimit(api.rateLimiting.getRateLimit);

  if (status && !status.ok) {
    return (
      <div className="text-red-500">
        Rate limit exceeded. Retry after{" "}
        {new Date(status.retryAt).toLocaleTimeString()}
      </div>
    );
  }

  return <input type="text" placeholder="Send a message..." />;
}

主な原則

  • 頻度には固定ウィンドウ:一定期間あたりの回数を単純に制限する場合に使用します
  • 容量にはトークンバケット:バーストに対応した制限に使用します
  • 事前に推定し、事後に追跡する:早期に防止し、実際の使用量を記録します
  • 全体制限とユーザーごとの制限:公平なアクセスとリソース上限のバランスを取ります
  • 再試行可能なエラー:クライアントはバックオフして再試行できます

次のステップ

  • トークン使用量に基づく課金については、usage-tracking を参照してください
  • エージェントのセットアップについては、fundamentals を参照してください
  • トラブルシューティングについては、debugging を参照してください

コメント