logo

SavvyBot Phase 3-4
グループチャット特化とAPI使用量トラッキング

AI / チャットボット開発プロダクトオーナー / フルスタック開発2025年10月2025-11-07

個人向けから「グループチャット特化型AIアシスタント」への戦略的ピボット。OpenAI Function Calling + Web検索統合、デュアルモード設計、API使用量トラッキングシステムを実装し、収益化への基盤を確立。

Technologies Used

OpenAI Function CallingTavily APIPostgreSQLSupabaseTypeScriptDeno

SavvyBot Phase 3-4 ― グループチャット特化とAPI使用量トラッキング

← Phase 1-2へ | Phase 5へ → | プロジェクト概要

Phase 3: 戦略的ピボット - グループチャット特化

背景と課題認識(2025年10月20日)

Phase 2でインフラ基盤を確立した後、プロダクトの方向性について再検討しました。

市場分析の結果:

  • 個人向けチャットボットは無料のChatGPTと差別化が困難
  • グループチャット領域に明確なペインポイントを発見
  • 収益化可能性の探索のため、ニッチ市場への特化を決定

差別化戦略

競合(ChatGPT公式、他社Bot):
❌ 個人利用がメイン
❌ グループでは会話に割り込んでうるさい
❌ 議事録機能が弱い
❌ 最新情報の取得が不十分

SavvyBot:
✅ グループチャット特化
✅ 沈黙がデフォルト(会話を邪魔しない)
✅ 構造化された議事録
✅ 決定事項の自動追跡
✅ 豊富なコマンド体系(/sv:* プレフィックス)
✅ Web検索統合で最新情報に対応

デュアルモード設計

個人モード(1対1チャット):

  • 積極的に会話
  • 会話履歴を保持
  • ChatGPT的な対話体験
  • Web検索統合

グループモード(複数人チャット):

  • 基本は沈黙(裏で記録・分析)
  • コマンド/メンションで呼ばれた時のみ発言
  • 議事録、決定事項、TODO、質問を自動抽出

Phase 3.2: OpenAI Function Calling + Web Search(完了 - 2025年10月16日)

達成事項

  • ✅ Tavily API統合によるWeb検索機能
  • ✅ OpenAI Function Calling実装
  • ✅ 時間表現の正確な解釈(現在日付コンテキスト追加)
  • ✅ デプロイおよび動作確認完了

技術的実装

1. Tavily API統合

Tavily APIを使用して、AIが最新情報にアクセスできるようにしました。

実装内容 (lib/search.ts):

export interface SearchResult {
  title: string;
  url: string;
  content: string;
  score: number;
}

export async function searchWeb(
  query: string
): Promise<Result<SearchResult[], string>> {
  const response = await fetch('https://api.tavily.com/search', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      api_key: TAVILY_API_KEY,
      query,
      search_depth: 'advanced',
      max_results: 5,
    }),
  });

  const data = await response.json();
  return { ok: true, value: data.results };
}

2. OpenAI Function Calling

AIが必要に応じて自動的にWeb検索を実行できるようにしました。

Tool定義:

const tools: ChatCompletionTool[] = [
  {
    type: 'function',
    function: {
      name: 'search_web',
      description: '最新情報、ニュース、イベント結果を検索する',
      parameters: {
        type: 'object',
        properties: {
          query: {
            type: 'string',
            description: '検索クエリ(例: ノーベル物理学賞 2024)',
          },
        },
        required: ['query'],
      },
    },
  },
];

実行フロー:

// 1. AI応答にツール呼び出しが含まれているかチェック
if (response.tool_calls) {
  for (const toolCall of response.tool_calls) {
    if (toolCall.function.name === 'search_web') {
      const args = JSON.parse(toolCall.function.arguments);

      // 2. Web検索実行
      const searchResult = await searchWeb(args.query);

      // 3. 検索結果をAIに返して最終応答を生成
      const finalResponse = await getAIResponseWithToolResults(
        messages,
        toolCall.id,
        searchResult.value
      );

      return finalResponse;
    }
  }
}

3. 時間表現解釈の改善

「去年」が2022年と解釈される問題を解決するため、現在日付コンテキストを追加しました。

Temporal Context Pattern:

const currentDate = new Date().toLocaleString('ja-JP', {
  timeZone: 'Asia/Tokyo',
  year: 'numeric',
  month: 'long',
  day: 'numeric',
  weekday: 'long',
});

const systemPrompt = `あなたはSavvyBotです。
現在の日時: ${currentDate}

時間表現の解釈:
- 「今年」= 2025年
- 「去年」= 2024年
- 「今日」「昨日」は上記の現在日時を基準に計算してください。`;

実機テスト結果(Phase 3.3)

テストケース:

  1. ノーベル物理学賞2024: ✅ Hopfield & Hinton(正確な受賞者情報)
  2. 最新iPhone価格: ✅ iPhone 17シリーズ価格(知識カットオフ8ヶ月後の製品情報)
  3. 大谷翔平ニュース: ✅ 50-50達成、プレーオフ勝利(最新スポーツ情報)
  4. 第二次世界大戦終結: ✅ 1945年(学習データから回答、Web検索不使用)

重要な発見:

  • iPhone 17ケース: AI知識カットオフ(2025年1月)の8ヶ月後にリリースされた製品の正確な価格情報を取得
  • Web検索の価値を完璧に実証(学習データのみでは不可能な情報提供)
  • ツール呼び出し判定の精度確認(「最新」→検索実行、歴史的事実→学習データ使用)

Phase 3.4: Group Mode実機テスト + /sv:questionsコマンド(完了 - 2025年10月19日)

達成事項

  • ✅ グループチャット設定問題の解決(LINE設定でグループ参加許可が必要)
  • ✅ 全コマンドの実機テスト成功(/sv:help, /sv:minutes, /sv:decisions, /sv:todo
  • ✅ グループモードのサイレント動作確認(通常メッセージには反応しない)
  • ✅ 自動抽出機能の検証(100%精度で決定事項・TODO抽出)
  • ✅ 構造化議事録生成の動作確認
  • /sv:questionsコマンド実装完了

主要コマンド

会話管理:

  • /sv:minutes [時間範囲] - 議事録生成(basic/structured)
  • /sv:search <キーワード> [期間] - 過去の会話からキーワード検索

情報管理:

  • /sv:decisions [期間] - 決定事項確認
  • /sv:todo [@メンション] [filter] - TODOリスト表示
  • /sv:questions [filter] - 質問リスト表示(unanswered/all)

ヘルプ:

  • /sv:help [コマンド名] - ヘルプ表示

自動抽出機能(OpenAI Function Calling活用)

実装内容:

const tools: ChatCompletionTool[] = [
  {
    type: 'function',
    function: {
      name: 'extract_decision',
      description: '会話から決定事項を抽出',
      parameters: {
        type: 'object',
        properties: {
          decision: { type: 'string', description: '決定内容' },
          confidence: { type: 'number', description: '信頼度(0-1)' },
        },
        required: ['decision', 'confidence'],
      },
    },
  },
  {
    type: 'function',
    function: {
      name: 'extract_todo',
      description: '会話からTODOを抽出',
      parameters: {
        type: 'object',
        properties: {
          task: { type: 'string', description: 'タスク内容' },
          assignee: { type: 'string', description: '担当者' },
          deadline: { type: 'string', description: '期限(YYYY-MM-DD)' },
        },
        required: ['task'],
      },
    },
  },
];

抽出精度:

  • 決定事項: 100%(信頼度0.7以上のみ保存)
  • TODO: 100%(担当者・期限も正確に抽出)
  • 質問: 100%(重複チェック機能付き、1時間以内の同一質問をスキップ)

Phase 4: API使用量トラッキング実装(完了 - 2025年10月19日)

達成事項

  • ✅ OpenAI API使用量の自動トラッキング
  • ✅ トークン数とコスト計算の記録
  • ✅ グループごとの使用量集計
  • ✅ Personal mode自動検出バグ修正
  • ✅ 実機テスト成功(複数ユーザーで動作確認)

データベーススキーマ

migration: 20251019000000_usage_tracking.sql

-- 使用量ログテーブル
CREATE TABLE usage_logs (
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
  group_id TEXT NOT NULL,
  operation_type TEXT NOT NULL,
  model TEXT NOT NULL,
  prompt_tokens INTEGER NOT NULL,
  completion_tokens INTEGER NOT NULL,
  total_tokens INTEGER NOT NULL,
  estimated_cost DECIMAL(10, 6) NOT NULL,
  created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
  ttl TIMESTAMPTZ NOT NULL DEFAULT (NOW() + INTERVAL '365 days')
);

-- 日次集計ビュー
CREATE VIEW daily_usage_summary AS
SELECT
  DATE(created_at) AS date,
  operation_type,
  SUM(total_tokens) AS total_tokens,
  SUM(estimated_cost) AS total_cost,
  COUNT(*) AS request_count
FROM usage_logs
WHERE created_at >= NOW() - INTERVAL '90 days'
GROUP BY DATE(created_at), operation_type;

-- グループ別集計ビュー
CREATE VIEW group_usage_summary AS
SELECT
  group_id,
  SUM(total_tokens) AS total_tokens,
  SUM(estimated_cost) AS total_cost,
  COUNT(*) AS request_count
FROM usage_logs
WHERE created_at >= NOW() - INTERVAL '30 days'
GROUP BY group_id;

-- コスト計算関数
CREATE FUNCTION calculate_openai_cost(
  model TEXT,
  prompt_tokens INTEGER,
  completion_tokens INTEGER
) RETURNS DECIMAL(10, 6) AS $$
  -- gpt-4o: $2.50 / 1M input, $10.00 / 1M output
  -- gpt-4o-mini: $0.15 / 1M input, $0.60 / 1M output
  SELECT CASE
    WHEN model = 'gpt-4o' THEN
      (prompt_tokens * 2.50 / 1000000.0) +
      (completion_tokens * 10.00 / 1000000.0)
    WHEN model = 'gpt-4o-mini' THEN
      (prompt_tokens * 0.15 / 1000000.0) +
      (completion_tokens * 0.60 / 1000000.0)
    ELSE 0
  END;
$$ LANGUAGE SQL;

使用量トラッキングロジック

実装 (lib/usage.ts):

export interface UsageInfo {
  prompt_tokens: number;
  completion_tokens: number;
  total_tokens: number;
}

export async function logUsage(
  groupId: string,
  operationType: string,
  model: string,
  usage: UsageInfo
): Promise<Result<void, string>> {
  const cost = calculateCost(model, usage);

  const { error } = await supabase
    .from('usage_logs')
    .insert({
      group_id: groupId,
      operation_type: operationType,
      model,
      prompt_tokens: usage.prompt_tokens,
      completion_tokens: usage.completion_tokens,
      total_tokens: usage.total_tokens,
      estimated_cost: cost,
    });

  if (error) {
    return { ok: false, error: error.message };
  }

  return { ok: true, value: undefined };
}

function calculateCost(model: string, usage: UsageInfo): number {
  const prices = {
    'gpt-4o': { input: 2.50 / 1_000_000, output: 10.00 / 1_000_000 },
    'gpt-4o-mini': { input: 0.15 / 1_000_000, output: 0.60 / 1_000_000 },
  };

  const price = prices[model] || prices['gpt-4o-mini'];
  return (
    usage.prompt_tokens * price.input +
    usage.completion_tokens * price.output
  );
}

実機テスト結果

Personal Chat:

  • operation_type: personal_chat
  • model: gpt-4o
  • total_tokens: 414, 706
  • estimated_cost: 0.001155,0.001155, 0.001923
  • ✅ 正常に記録

Auto Extraction:

  • operation_type: auto_extraction
  • model: gpt-4o
  • total_tokens: 485-506
  • estimated_cost: 0.0013170.001317-0.001490
  • ✅ 正常に記録

Group Summary:

  • 2ユーザー、9回の操作
  • 合計4,572トークン
  • 推定コスト: $0.012795(約1.9円)
  • ✅ 集計ビューが正常に動作

バグ修正: Personal Mode自動検出

問題: 新規ユーザーが1対1チャットでメッセージを送っても返信がない(group_mode.silentと認識される)

根本原因: getOrCreateGroupSettings()がデフォルトでmode: 'group'を設定していた

修正内容:

  • getOrCreateGroupSettings()defaultModeパラメータを追加
  • モード検出を2段階に分離:
    1. 自動検出(settings なし)→ autoMode
    2. settings取得時にautoMode.modeをデフォルトとして渡す
    3. 最終的なモード決定(settingsを含めて再検出)
  • 結果: source.type === 'user'mode: 'personal'で作成、source.type === 'group'mode: 'group'で作成

Phase 3-4 の成果

技術的成果

  • OpenAI Function Callingによる自動ツール選択
  • Temporal Context Patternによる時間認識精度の改善
  • PostgreSQLビューとTTLによる効率的なデータ管理
  • モード自動検出ロジックの段階的設計
  • リアルタイムコスト推定(トークン数からUSDコストを自動計算)

ビジネス的成果

  • グループチャット特化による明確な差別化
  • Web検索統合で最新情報に対応
  • API使用量トラッキングによる収益化準備
  • デュアルモード設計でユースケース拡大

データベース設計

conversations (90日TTL)
  ├─ group_id
  ├─ user_id
  ├─ message
  └─ created_at

decisions
  ├─ group_id
  ├─ decision
  ├─ confidence
  └─ created_at

todos
  ├─ group_id
  ├─ task
  ├─ assignee
  ├─ deadline
  └─ status

questions
  ├─ group_id
  ├─ question
  ├─ answered
  └─ created_at

usage_logs (365日TTL)
  ├─ group_id
  ├─ operation_type
  ├─ tokens
  └─ estimated_cost

→ Phase 5: 管理ダッシュボードへ


Phase 3-4完了日: 2025年10月19日 主な技術的成果: OpenAI Function Calling、Tavily API統合、デュアルモード設計、API使用量トラッキング

#OpenAI Function Calling#Tavily API#Web検索#グループチャット#API使用量トラッキング#PostgreSQL

©2025 Natsuki Hayashida. All Rights Reserved.