empathy-framework 4.9.0__py3-none-any.whl → 5.0.0__py3-none-any.whl

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.
Files changed (47) hide show
  1. {empathy_framework-4.9.0.dist-info → empathy_framework-5.0.0.dist-info}/METADATA +64 -25
  2. {empathy_framework-4.9.0.dist-info → empathy_framework-5.0.0.dist-info}/RECORD +47 -26
  3. empathy_os/__init__.py +2 -2
  4. empathy_os/cache/hash_only.py +6 -3
  5. empathy_os/cache/hybrid.py +6 -3
  6. empathy_os/cli_legacy.py +27 -1
  7. empathy_os/cli_minimal.py +512 -15
  8. empathy_os/cli_router.py +145 -113
  9. empathy_os/cli_unified.py +25 -0
  10. empathy_os/dashboard/__init__.py +42 -0
  11. empathy_os/dashboard/app.py +512 -0
  12. empathy_os/dashboard/simple_server.py +403 -0
  13. empathy_os/dashboard/standalone_server.py +536 -0
  14. empathy_os/memory/__init__.py +19 -5
  15. empathy_os/memory/short_term.py +4 -70
  16. empathy_os/memory/types.py +2 -2
  17. empathy_os/models/__init__.py +3 -0
  18. empathy_os/models/adaptive_routing.py +437 -0
  19. empathy_os/models/registry.py +4 -4
  20. empathy_os/socratic/ab_testing.py +1 -1
  21. empathy_os/telemetry/__init__.py +29 -1
  22. empathy_os/telemetry/agent_coordination.py +478 -0
  23. empathy_os/telemetry/agent_tracking.py +350 -0
  24. empathy_os/telemetry/approval_gates.py +563 -0
  25. empathy_os/telemetry/event_streaming.py +405 -0
  26. empathy_os/telemetry/feedback_loop.py +557 -0
  27. empathy_os/vscode_bridge 2.py +173 -0
  28. empathy_os/workflows/__init__.py +4 -4
  29. empathy_os/workflows/base.py +495 -43
  30. empathy_os/workflows/history.py +3 -5
  31. empathy_os/workflows/output.py +410 -0
  32. empathy_os/workflows/progress.py +324 -22
  33. empathy_os/workflows/progressive/README 2.md +454 -0
  34. empathy_os/workflows/progressive/__init__ 2.py +92 -0
  35. empathy_os/workflows/progressive/cli 2.py +242 -0
  36. empathy_os/workflows/progressive/core 2.py +488 -0
  37. empathy_os/workflows/progressive/orchestrator 2.py +701 -0
  38. empathy_os/workflows/progressive/reports 2.py +528 -0
  39. empathy_os/workflows/progressive/telemetry 2.py +280 -0
  40. empathy_os/workflows/progressive/test_gen 2.py +514 -0
  41. empathy_os/workflows/progressive/workflow 2.py +628 -0
  42. empathy_os/workflows/routing.py +5 -0
  43. empathy_os/workflows/security_audit.py +189 -0
  44. {empathy_framework-4.9.0.dist-info → empathy_framework-5.0.0.dist-info}/WHEEL +0 -0
  45. {empathy_framework-4.9.0.dist-info → empathy_framework-5.0.0.dist-info}/entry_points.txt +0 -0
  46. {empathy_framework-4.9.0.dist-info → empathy_framework-5.0.0.dist-info}/licenses/LICENSE +0 -0
  47. {empathy_framework-4.9.0.dist-info → empathy_framework-5.0.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,280 @@
1
+ """Telemetry integration for progressive workflows.
2
+
3
+ Tracks cost, tier usage, escalation patterns, and savings for progressive workflows.
4
+ """
5
+
6
+ import logging
7
+ from datetime import datetime
8
+ from typing import Any
9
+
10
+ from empathy_os.telemetry.usage_tracker import UsageTracker
11
+ from empathy_os.workflows.progressive.core import (
12
+ ProgressiveWorkflowResult,
13
+ Tier,
14
+ TierResult,
15
+ )
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+
20
+ class ProgressiveTelemetry:
21
+ """Telemetry tracker for progressive workflows."""
22
+
23
+ def __init__(self, workflow_name: str, user_id: str | None = None) -> None:
24
+ """Initialize telemetry tracker.
25
+
26
+ Args:
27
+ workflow_name: Name of the workflow (e.g., "test-gen")
28
+ user_id: Optional user identifier (will be hashed)
29
+ """
30
+ self.workflow_name = workflow_name
31
+ self.user_id = user_id
32
+ self.tracker = UsageTracker.get_instance()
33
+
34
+ def track_tier_execution(
35
+ self,
36
+ tier_result: TierResult,
37
+ attempt: int,
38
+ escalated: bool,
39
+ escalation_reason: str | None = None,
40
+ ) -> None:
41
+ """Track a single tier execution.
42
+
43
+ Args:
44
+ tier_result: Result from tier execution
45
+ attempt: Attempt number for this tier
46
+ escalated: Whether this tier escalated to next
47
+ escalation_reason: Why escalation occurred (if escalated)
48
+ """
49
+ # Extract token counts from tier result
50
+ tokens = {
51
+ "input_tokens": tier_result.tokens_used.get("input", 0),
52
+ "output_tokens": tier_result.tokens_used.get("output", 0),
53
+ "total_tokens": tier_result.tokens_used.get("total", 0),
54
+ }
55
+
56
+ # Track LLM call
57
+ try:
58
+ self.tracker.track_llm_call(
59
+ workflow=self.workflow_name,
60
+ stage=f"tier-{tier_result.tier.value}-attempt-{attempt}",
61
+ tier=tier_result.tier.value.upper(),
62
+ model=tier_result.model,
63
+ provider=self._get_provider(tier_result.model),
64
+ cost=tier_result.cost,
65
+ tokens=tokens,
66
+ cache_hit=False, # Progressive workflows don't use cache
67
+ cache_type=None,
68
+ duration_ms=int(tier_result.duration * 1000),
69
+ user_id=self.user_id,
70
+ )
71
+
72
+ logger.debug(
73
+ f"Tracked {tier_result.tier.value} tier execution: "
74
+ f"${tier_result.cost:.3f}, {tokens['total_tokens']} tokens"
75
+ )
76
+
77
+ except Exception as e:
78
+ # Telemetry failure should not break workflow
79
+ logger.warning(f"Failed to track tier execution: {e}")
80
+
81
+ def track_workflow_completion(
82
+ self,
83
+ result: ProgressiveWorkflowResult,
84
+ ) -> None:
85
+ """Track complete workflow execution with cost savings analysis.
86
+
87
+ Args:
88
+ result: Final workflow result
89
+ """
90
+ try:
91
+ # Calculate metrics
92
+ total_items = len(result.final_result.generated_items)
93
+ total_cost = result.total_cost
94
+ total_duration_ms = int(result.total_duration * 1000)
95
+
96
+ # Cost savings vs all-premium
97
+ all_premium_cost = result._calculate_all_premium_cost()
98
+ savings = all_premium_cost - total_cost
99
+ savings_percent = (savings / all_premium_cost * 100) if all_premium_cost > 0 else 0
100
+
101
+ # Tier distribution
102
+ tier_breakdown = {}
103
+ for tier_result in result.tier_results:
104
+ tier_name = tier_result.tier.value
105
+ if tier_name not in tier_breakdown:
106
+ tier_breakdown[tier_name] = {
107
+ "items": 0,
108
+ "cost": 0.0,
109
+ "attempts": 0,
110
+ }
111
+ tier_breakdown[tier_name]["items"] += len(tier_result.generated_items)
112
+ tier_breakdown[tier_name]["cost"] += tier_result.cost
113
+ tier_breakdown[tier_name]["attempts"] += 1
114
+
115
+ # Log summary
116
+ logger.info(
117
+ f"Progressive workflow '{self.workflow_name}' completed:\n"
118
+ f" Items: {total_items}\n"
119
+ f" Cost: ${total_cost:.3f} (saved ${savings:.3f}, {savings_percent:.0f}%)\n"
120
+ f" Duration: {result.total_duration:.1f}s\n"
121
+ f" Tiers: {list(tier_breakdown.keys())}"
122
+ )
123
+
124
+ # Custom telemetry event for workflow summary
125
+ self._track_custom_event(
126
+ event_type="progressive_workflow_completion",
127
+ data={
128
+ "workflow": self.workflow_name,
129
+ "task_id": result.task_id,
130
+ "total_items": total_items,
131
+ "total_cost": total_cost,
132
+ "total_duration_ms": total_duration_ms,
133
+ "cost_savings": savings,
134
+ "savings_percent": savings_percent,
135
+ "all_premium_cost": all_premium_cost,
136
+ "tier_breakdown": tier_breakdown,
137
+ "success": result.success,
138
+ "final_cqs": result.final_result.failure_analysis.calculate_quality_score()
139
+ if result.final_result.failure_analysis
140
+ else None,
141
+ },
142
+ )
143
+
144
+ except Exception as e:
145
+ logger.warning(f"Failed to track workflow completion: {e}")
146
+
147
+ def track_escalation(
148
+ self,
149
+ from_tier: Tier,
150
+ to_tier: Tier,
151
+ reason: str,
152
+ item_count: int,
153
+ current_cost: float,
154
+ ) -> None:
155
+ """Track tier escalation event.
156
+
157
+ Args:
158
+ from_tier: Source tier
159
+ to_tier: Destination tier
160
+ reason: Why escalation occurred
161
+ item_count: Number of items being escalated
162
+ current_cost: Cost accumulated so far
163
+ """
164
+ try:
165
+ self._track_custom_event(
166
+ event_type="progressive_escalation",
167
+ data={
168
+ "workflow": self.workflow_name,
169
+ "from_tier": from_tier.value,
170
+ "to_tier": to_tier.value,
171
+ "reason": reason,
172
+ "item_count": item_count,
173
+ "current_cost": current_cost,
174
+ },
175
+ )
176
+
177
+ logger.info(
178
+ f"Escalated {item_count} items from {from_tier.value} → {to_tier.value}: {reason}"
179
+ )
180
+
181
+ except Exception as e:
182
+ logger.warning(f"Failed to track escalation: {e}")
183
+
184
+ def track_budget_exceeded(
185
+ self,
186
+ current_cost: float,
187
+ max_budget: float,
188
+ action: str,
189
+ ) -> None:
190
+ """Track budget exceeded event.
191
+
192
+ Args:
193
+ current_cost: Current cost that exceeded budget
194
+ max_budget: Maximum budget allowed
195
+ action: Action taken ("abort" or "warn")
196
+ """
197
+ try:
198
+ self._track_custom_event(
199
+ event_type="progressive_budget_exceeded",
200
+ data={
201
+ "workflow": self.workflow_name,
202
+ "current_cost": current_cost,
203
+ "max_budget": max_budget,
204
+ "overage": current_cost - max_budget,
205
+ "action": action,
206
+ },
207
+ )
208
+
209
+ logger.warning(
210
+ f"Budget exceeded: ${current_cost:.3f} > ${max_budget:.3f} ({action})"
211
+ )
212
+
213
+ except Exception as e:
214
+ logger.warning(f"Failed to track budget exceeded: {e}")
215
+
216
+ def _track_custom_event(self, event_type: str, data: dict[str, Any]) -> None:
217
+ """Track custom telemetry event.
218
+
219
+ Args:
220
+ event_type: Type of event
221
+ data: Event data
222
+ """
223
+ # For now, just log to telemetry directory as JSONL
224
+ # Future: could send to analytics service
225
+ import json
226
+ from pathlib import Path
227
+
228
+ try:
229
+ telemetry_dir = Path.home() / ".empathy" / "telemetry"
230
+ telemetry_dir.mkdir(parents=True, exist_ok=True)
231
+
232
+ events_file = telemetry_dir / "progressive_events.jsonl"
233
+
234
+ event = {
235
+ "timestamp": datetime.now().isoformat(),
236
+ "event_type": event_type,
237
+ "user_id_hash": (
238
+ self._hash_user_id(self.user_id) if self.user_id else "anonymous"
239
+ ),
240
+ **data,
241
+ }
242
+
243
+ with events_file.open("a") as f:
244
+ f.write(json.dumps(event) + "\n")
245
+
246
+ except Exception as e:
247
+ logger.debug(f"Failed to write custom event: {e}")
248
+
249
+ @staticmethod
250
+ def _get_provider(model: str) -> str:
251
+ """Infer provider from model name.
252
+
253
+ Args:
254
+ model: Model name
255
+
256
+ Returns:
257
+ Provider name
258
+ """
259
+ if "gpt" in model.lower():
260
+ return "openai"
261
+ elif "claude" in model.lower():
262
+ return "anthropic"
263
+ elif "gemini" in model.lower():
264
+ return "google"
265
+ else:
266
+ return "unknown"
267
+
268
+ @staticmethod
269
+ def _hash_user_id(user_id: str) -> str:
270
+ """Hash user ID for privacy.
271
+
272
+ Args:
273
+ user_id: User identifier
274
+
275
+ Returns:
276
+ SHA256 hash of user_id
277
+ """
278
+ import hashlib
279
+
280
+ return hashlib.sha256(user_id.encode()).hexdigest()