chatbot-analytics について
chatbot-analyticsは、AIコーチングチャットの分析機能を実装するスキルです。メッセージ内容などの禁止対象データを保存せず、メタデータ指標、集計ダッシュボード、アラート、テストを扱います。
主な機能
- 01HIPAAに配慮した会話指標
- 02メタデータに基づくカテゴリ検出
- 03セッションとトークン使用量の集計
- 04危機、エラー、離脱増加のアラート
- 05禁止データを確認するテスト
活用シーン
- 01チャットの利用状況と完了率の測定
- 02トークン使用量と応答時間の監視
- 03メッセージ内容を使わないカテゴリ傾向の追跡
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ curiositech/ some_claude_skills --skill chatbot-analyticsインストール後、Claude Code で/chatbot-analyticsを入力して読み込まれたか確認します。
同梱ファイル.claude-plugin/plugin.json
詳細
AI チャットボット分析
このスキルでは、HIPAA 準拠を維持しながら、AI コーチングチャット機能の分析機能を実装できます。
追跡する主要な指標
業界のベストプラクティスに基づき、次の 13 の主要な指標を追跡します。
| 指標 | 説明 | HIPAA に準拠していますか? |
|---|---|---|
| セッション総数 | チャットセッション数 | はい |
| セッションあたりの平均メッセージ数 | 会話あたりのメッセージ数 | はい |
| 平均セッション時間 | チャットに費やした時間 | はい |
| エンゲージメント率 | チャットを利用するユーザーの割合 | はい |
| 完了率 | 自然に終了したセッション | はい |
| 離脱率 | 早期に終了したセッション | はい |
| 応答時間 | AI の応答レイテンシ | はい |
| トークン使用量 | 消費されたトークンの合計/平均 | はい |
| エラー率 | 応答の失敗 | はい |
| フォールバック率 | 「理解できません」という応答 | はい |
| トピックのカテゴリ | ユーザーが話し合う内容 | メタデータのみ |
| 感情の傾向 | 感情の方向性 | 推定値のみ |
| 危機のトリガー | 緊急事態の検出 | メタデータのみ |
HIPAA 準拠の分析
追跡する項目
// Conversation metadata (SAFE)
interface ConversationAnalytics {
id: string;
conversationId: string;
userId: string; // For aggregation, not individual tracking
startedAt: Date;
endedAt: Date | null;
messageCount: number;
userMessageCount: number;
aiMessageCount: number;
totalTokens: number;
inputTokens: number;
outputTokens: number;
category: string; // Derived from metadata flags
outcome: 'completed' | 'abandoned' | 'error' | 'crisis_escalated';
avgResponseTime: number;
hadFallback: boolean;
}
追跡しない項目
// NEVER store these in analytics
interface PROHIBITED {
messageContent: string; // PHI
userQuery: string; // PHI
aiResponse: string; // PHI
specificTopics: string[]; // Could reveal health info
exactSentiment: 'sad'; // Could reveal mental state
}
実装パターン
会話開始の追跡
// src/lib/ai/analytics.ts
export async function trackConversationStart(
conversationId: string,
userId: string
): Promise<void> {
await db.insert(conversationAnalytics).values({
id: generateId(),
conversationId,
userId,
startedAt: new Date(),
messageCount: 0,
totalTokens: 0,
category: 'unknown',
outcome: 'in_progress'
});
}
メッセージのやり取りの追跡
export async function trackMessageExchange(
conversationId: string,
tokens: { input: number; output: number },
responseTimeMs: number,
flags: { hadFallback: boolean; hasCrisisIndicator: boolean }
): Promise<void> {
await db
.update(conversationAnalytics)
.set({
messageCount: sql`message_count + 1`,
totalTokens: sql`total_tokens + ${tokens.input + tokens.output}`,
inputTokens: sql`input_tokens + ${tokens.input}`,
outputTokens: sql`output_tokens + ${tokens.output}`,
avgResponseTime: sql`(avg_response_time * (message_count - 1) + ${responseTimeMs}) / message_count`,
hadFallback: flags.hadFallback,
...(flags.hasCrisisIndicator && { outcome: 'crisis_escalated' })
})
.where(eq(conversationAnalytics.conversationId, conversationId));
}
会話終了の追跡
export async function trackConversationEnd(
conversationId: string,
outcome: 'completed' | 'abandoned' | 'error'
): Promise<void> {
await db
.update(conversationAnalytics)
.set({
endedAt: new Date(),
outcome
})
.where(eq(conversationAnalytics.conversationId, conversationId));
}
カテゴリの検出(メタデータベース)
会話の内容を読まずに、会話のカテゴリを検出します。
// Categories based on metadata flags from AI response
interface AIResponseMetadata {
usedCopingStrategies: boolean;
usedCrisisProtocol: boolean;
usedCheckInSupport: boolean;
usedGeneralChat: boolean;
requestedClarification: boolean;
}
function deriveCategory(metadata: AIResponseMetadata): string {
if (metadata.usedCrisisProtocol) return 'crisis_support';
if (metadata.usedCopingStrategies) return 'coping_strategies';
if (metadata.usedCheckInSupport) return 'checkin_support';
if (metadata.requestedClarification) return 'clarification';
return 'general_chat';
}
ダッシュボードの集計
セッション指標
// Get aggregated session stats (HIPAA safe - no individual data)
async function getSessionStats(days: number = 30) {
const since = subDays(new Date(), days);
return db
.select({
totalSessions: count(),
avgMessages: avg(conversationAnalytics.messageCount),
avgDuration: avg(
sql`JULIANDAY(ended_at) - JULIANDAY(started_at)) * 24 * 60`
),
completionRate: sql`
CAST(SUM(CASE WHEN outcome = 'completed' THEN 1 ELSE 0 END) AS FLOAT) /
CAST(COUNT(*) AS FLOAT)
`,
crisisEscalations: sql`
SUM(CASE WHEN outcome = 'crisis_escalated' THEN 1 ELSE 0 END)
`
})
.from(conversationAnalytics)
.where(gte(conversationAnalytics.startedAt, since));
}
コスト追跡のためのトークン使用量
async function getTokenUsage(days: number = 30) {
const since = subDays(new Date(), days);
const result = await db
.select({
totalTokens: sum(conversationAnalytics.totalTokens),
inputTokens: sum(conversationAnalytics.inputTokens),
outputTokens: sum(conversationAnalytics.outputTokens),
avgTokensPerSession: avg(conversationAnalytics.totalTokens)
})
.from(conversationAnalytics)
.where(gte(conversationAnalytics.startedAt, since));
// Estimate cost (Claude pricing)
const inputCost = (result.inputTokens / 1_000_000) * 3.00; // $3/M input
const outputCost = (result.outputTokens / 1_000_000) * 15.00; // $15/M output
return {
...result,
estimatedCost: inputCost + outputCost
};
}
カテゴリの内訳
async function getCategoryBreakdown(days: number = 30) {
const since = subDays(new Date(), days);
return db
.select({
category: conversationAnalytics.category,
count: count(),
percentage: sql`
CAST(COUNT(*) AS FLOAT) * 100.0 /
(SELECT COUNT(*) FROM conversation_analytics WHERE started_at >= ${since})
`
})
.from(conversationAnalytics)
.where(gte(conversationAnalytics.startedAt, since))
.groupBy(conversationAnalytics.category)
.orderBy(desc(count()));
}
アラートの設定
懸念されるパターンに対するアラートを設定します。
interface AnalyticsAlert {
type: 'crisis_spike' | 'error_spike' | 'abandonment_spike';
threshold: number;
windowHours: number;
action: 'log' | 'email' | 'slack';
}
const alerts: AnalyticsAlert[] = [
{
type: 'crisis_spike',
threshold: 5, // 5+ crisis escalations
windowHours: 24,
action: 'email'
},
{
type: 'error_spike',
threshold: 10, // 10+ errors
windowHours: 1,
action: 'slack'
},
{
type: 'abandonment_spike',
threshold: 0.5, // 50%+ abandonment rate
windowHours: 24,
action: 'log'
}
];
データベーススキーマ
CREATE TABLE conversation_analytics (
id TEXT PRIMARY KEY,
conversation_id TEXT NOT NULL,
user_id TEXT NOT NULL,
started_at TEXT NOT NULL,
ended_at TEXT,
message_count INTEGER DEFAULT 0,
user_message_count INTEGER DEFAULT 0,
ai_message_count INTEGER DEFAULT 0,
total_tokens INTEGER DEFAULT 0,
input_tokens INTEGER DEFAULT 0,
output_tokens INTEGER DEFAULT 0,
category TEXT DEFAULT 'unknown',
outcome TEXT DEFAULT 'in_progress',
avg_response_time REAL DEFAULT 0,
had_fallback INTEGER DEFAULT 0,
FOREIGN KEY (conversation_id) REFERENCES conversations(id),
FOREIGN KEY (user_id) REFERENCES users(id)
);
CREATE INDEX idx_conv_analytics_started ON conversation_analytics(started_at);
CREATE INDEX idx_conv_analytics_user ON conversation_analytics(user_id);
CREATE INDEX idx_conv_analytics_outcome ON conversation_analytics(outcome);
分析機能のテスト
describe('Conversation Analytics', () => {
it('tracks session without PHI', async () => {
const analytics = await trackConversationStart('conv-123', 'user-456');
// Verify no PHI is stored
expect(analytics).not.toHaveProperty('messageContent');
expect(analytics).not.toHaveProperty('userQuery');
// Verify metadata is stored
expect(analytics.conversationId).toBe('conv-123');
expect(analytics.messageCount).toBe(0);
});
it('calculates aggregates correctly', async () => {
const stats = await getSessionStats(30);
expect(stats.totalSessions).toBeGreaterThanOrEqual(0);
expect(stats.completionRate).toBeBetween(0, 1);
});
});
リソース
- チャットボット分析ガイド
- Botpress の分析
- 13 の主要な指標
- 管理スイートの設計:
docs/ADMIN-DEVELOPER-SUITE.md
コメント
Markdown 対応