关于 chatbot-analytics
chatbot-analytics 是用于为 AI 辅导聊天实现分析功能的技能,强调避免存储消息内容等禁止的敏感数据,介绍基于元数据的指标、聚合仪表盘、告警和测试。
主要功能
- 01注重 HIPAA 合规的对话指标
- 02基于元数据的类别检测
- 03会话与 Token 使用量汇总
- 04危机、错误和放弃率激增告警
- 05检查禁止数据的测试
适用场景
- 01衡量聊天参与度与完成情况
- 02监控 Token 用量和响应延迟
- 03不读取消息内容而跟踪类别趋势
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ curiositech/ some_claude_skills --skill chatbot-analytics安装后在 Claude Code 中输入/chatbot-analytics确认已加载。
附带文件.claude-plugin/plugin.json
详细介绍
AI 聊天机器人分析
此技能可帮助你为 AI 教练聊天功能实现分析,同时确保符合 HIPAA 要求。
要跟踪的核心指标
根据行业最佳实践,跟踪以下 13 项关键指标:
| 指标 | 描述 | 符合 HIPAA 要求? |
|---|---|---|
| 总会话数 | 聊天会话数量 | 是 |
| 平均每次会话的消息数 | 每次对话的消息数 | 是 |
| 平均会话时长 | 聊天花费的时间 | 是 |
| 参与率 | 使用聊天功能的用户占比 | 是 |
| 完成率 | 自然结束的会话 | 是 |
| 放弃率 | 提前结束的会话 | 是 |
| 响应时间 | AI 响应延迟 | 是 |
| Token 使用量 | 消耗的 Token 总数/平均数 | 是 |
| 错误率 | 响应失败率 | 是 |
| 回退率 | “我不明白”类响应的比率 | 是 |
| 话题类别 | 用户讨论的内容 | 仅元数据 |
| 情绪趋势 | 情绪变化方向 | 仅衍生数据 |
| 危机触发 | 紧急情况检测 | 仅元数据 |
符合 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));
}
用于成本跟踪的 Token 使用量
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