oh-langfuse 0.1.32 → 0.1.33

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  `oh-langfuse` 是用于给 Claude Code、OpenCode 和 Codex 配置 Langfuse 追踪的命令行工具。它提供交互式安装向导,也支持 `setup` / `check` 直接命令,方便在用户机器上安装、修复和校验配置。
4
4
 
5
- 当前 npm 版本:`0.1.32`
5
+ 当前 npm 版本:`0.1.33`
6
6
 
7
7
  ## 能做什么
8
8
 
@@ -51,6 +51,25 @@ npx oh-langfuse@latest auto-update claude
51
51
  npx oh-langfuse@latest auto-update codex
52
52
  ```
53
53
 
54
+ ## Skill 使用量统计
55
+
56
+ 工具会为每次识别到的 skill 使用额外写入一条 observation:
57
+
58
+ ```text
59
+ Skill Use / <skill_name>
60
+ ```
61
+
62
+ Dashboard 中统计各 skill 使用量可配置:
63
+
64
+ ```text
65
+ View: Observations
66
+ Metric: Count
67
+ Filter: Observation Name contains Skill Use /
68
+ Breakdown Dimension: Observation Name
69
+ ```
70
+
71
+ `AI Interaction` 仍然保留 `skill_use_count`、`skill_names`、`skill_names_json` 和 `skill_names_csv`,用于查看单次交互的效率汇总。为避免极端情况下产生过多 observation,OpenCode 每次交互最多记录 20 个去重后的 skill 使用事件。
72
+
54
73
  本地开发运行:
55
74
 
56
75
  ```bash
@@ -655,13 +655,14 @@ def emit_codex_turn(
655
655
 
656
656
  for skill in skill_usages:
657
657
  with langfuse.start_as_current_observation(
658
- name=f"Skill Use: {skill['name']}",
658
+ name=f"Skill Use / {skill['name']}",
659
659
  metadata={
660
660
  "source": "codex",
661
661
  "user_id": user_id or "",
662
662
  "session_id": session_id,
663
663
  "interaction_id": interaction_meta["interaction_id"],
664
664
  "skill_name": skill["name"],
665
+ "skill_use_count": 1,
665
666
  "skill_namespace": skill["skill_namespace"],
666
667
  "detected_by": skill["detected_by"],
667
668
  "turn_number": turn_num,
package/langfuse_hook.py CHANGED
@@ -689,13 +689,14 @@ def emit_turn(
689
689
 
690
690
  for skill in skill_usages:
691
691
  with langfuse.start_as_current_observation(
692
- name=f"Skill Use: {skill['name']}",
692
+ name=f"Skill Use / {skill['name']}",
693
693
  metadata={
694
694
  "source": "claude",
695
695
  "user_id": user_id or "",
696
696
  "session_id": session_id,
697
697
  "interaction_id": interaction_meta["interaction_id"],
698
698
  "skill_name": skill["name"],
699
+ "skill_use_count": 1,
699
700
  "skill_namespace": skill["skill_namespace"],
700
701
  "detected_by": skill["detected_by"],
701
702
  "turn_number": turn_num,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oh-langfuse",
3
- "version": "0.1.32",
3
+ "version": "0.1.33",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Use npm scripts to configure Claude Code / OpenCode / Codex with Langfuse tracing.",
@@ -300,6 +300,7 @@ function getPatchedLangfuseDistIndexJs() {
300
300
  " }",
301
301
  " return out;",
302
302
  "};",
303
+ "const MAX_SKILL_USE_SPANS_PER_INTERACTION = 20;",
303
304
  "",
304
305
  "const escapeRegExp = (value) => String(value).replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');",
305
306
  "",
@@ -460,6 +461,23 @@ function getPatchedLangfuseDistIndexJs() {
460
461
  " }",
461
462
  " set.add(activity.toolCallId || `${kind}:${set.size + 1}`);",
462
463
  " };",
464
+ " const emitSkillUseSpans = ({ skillNames, sessionId, messageId, interactionId }) => {",
465
+ " for (const skillName of skillNames.slice(0, MAX_SKILL_USE_SPANS_PER_INTERACTION)) {",
466
+ " const span = metricsTracer.startSpan(`Skill Use / ${skillName}`);",
467
+ ' span.setAttribute("oh.langfuse.source", "opencode");',
468
+ ' span.setAttribute("oh.langfuse.user_id", userId || "");',
469
+ ' span.setAttribute("oh.langfuse.metrics_schema_version", "1.0");',
470
+ ' span.setAttribute("langfuse.observation.metadata.source", "opencode");',
471
+ ' span.setAttribute("langfuse.observation.metadata.user_id", userId || "");',
472
+ ' span.setAttribute("langfuse.observation.metadata.session_id", sessionId || "unknown");',
473
+ ' span.setAttribute("langfuse.observation.metadata.message_id", messageId || "unknown");',
474
+ ' span.setAttribute("langfuse.observation.metadata.interaction_id", interactionId);',
475
+ ' span.setAttribute("langfuse.observation.metadata.metrics_schema_version", "1.0");',
476
+ ' span.setAttribute("langfuse.observation.metadata.skill_name", skillName);',
477
+ ' span.setAttribute("langfuse.observation.metadata.skill_use_count", 1);',
478
+ " span.end();",
479
+ " }",
480
+ " };",
463
481
  "",
464
482
  " const recordInteractionMetric = (event) => {",
465
483
  " const payload = eventPayload(event);",
@@ -496,7 +514,8 @@ function getPatchedLangfuseDistIndexJs() {
496
514
  ' span.setAttribute("langfuse.observation.metadata.source", "opencode");',
497
515
  ' span.setAttribute("langfuse.observation.metadata.user_id", userId || "");',
498
516
  ' span.setAttribute("langfuse.observation.metadata.session_id", sessionId || "unknown");',
499
- ' span.setAttribute("langfuse.observation.metadata.interaction_id", `opencode:${userId || "unknown"}:${sessionId || "unknown"}:${messageId}`);',
517
+ ' const interactionId = `opencode:${userId || "unknown"}:${sessionId || "unknown"}:${messageId}`;',
518
+ ' span.setAttribute("langfuse.observation.metadata.interaction_id", interactionId);',
500
519
  ' span.setAttribute("langfuse.observation.metadata.metrics_schema_version", "1.0");',
501
520
  ' span.setAttribute("langfuse.observation.metadata.interaction_count", 1);',
502
521
  ' span.setAttribute("langfuse.observation.metadata.user_message_count", 1);',
@@ -515,6 +534,7 @@ function getPatchedLangfuseDistIndexJs() {
515
534
  ' if (tokenMetrics.reasoning !== undefined) span.setAttribute("langfuse.observation.metadata.reasoning_tokens", tokenMetrics.reasoning);',
516
535
  ' if (text) span.setAttribute("langfuse.observation.metadata.output_text_preview", text.slice(0, 512));',
517
536
  " span.end();",
537
+ " emitSkillUseSpans({ skillNames, sessionId, messageId, interactionId });",
518
538
  " messageTextById.delete(messageId);",
519
539
  " skillNamesByMessageId.delete(messageId);",
520
540
  " skillNamesBySessionId.delete(sessionId);",