empathy-framework 5.0.3__py3-none-any.whl → 5.1.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.
- {empathy_framework-5.0.3.dist-info → empathy_framework-5.1.0.dist-info}/METADATA +259 -142
- {empathy_framework-5.0.3.dist-info → empathy_framework-5.1.0.dist-info}/RECORD +56 -26
- empathy_framework-5.1.0.dist-info/licenses/LICENSE +201 -0
- empathy_framework-5.1.0.dist-info/licenses/LICENSE_CHANGE_ANNOUNCEMENT.md +101 -0
- empathy_os/__init__.py +1 -1
- empathy_os/cli/commands/batch.py +5 -5
- empathy_os/cli/commands/routing.py +1 -1
- empathy_os/cli/commands/workflow.py +2 -1
- empathy_os/cli/parsers/cache 2.py +65 -0
- empathy_os/cli_minimal.py +3 -3
- empathy_os/cli_router 2.py +416 -0
- empathy_os/dashboard/__init__.py +1 -2
- empathy_os/dashboard/app 2.py +512 -0
- empathy_os/dashboard/app.py +1 -1
- empathy_os/dashboard/simple_server 2.py +403 -0
- empathy_os/dashboard/standalone_server 2.py +536 -0
- empathy_os/memory/types 2.py +441 -0
- empathy_os/models/__init__.py +19 -0
- empathy_os/models/adaptive_routing 2.py +437 -0
- empathy_os/models/auth_cli.py +444 -0
- empathy_os/models/auth_strategy.py +450 -0
- empathy_os/project_index/scanner_parallel 2.py +291 -0
- empathy_os/telemetry/agent_coordination 2.py +478 -0
- empathy_os/telemetry/agent_coordination.py +3 -3
- empathy_os/telemetry/agent_tracking 2.py +350 -0
- empathy_os/telemetry/agent_tracking.py +1 -2
- empathy_os/telemetry/approval_gates 2.py +563 -0
- empathy_os/telemetry/event_streaming 2.py +405 -0
- empathy_os/telemetry/event_streaming.py +3 -3
- empathy_os/telemetry/feedback_loop 2.py +557 -0
- empathy_os/telemetry/feedback_loop.py +1 -1
- empathy_os/vscode_bridge 2.py +173 -0
- empathy_os/workflows/__init__.py +8 -0
- empathy_os/workflows/autonomous_test_gen.py +569 -0
- empathy_os/workflows/bug_predict.py +45 -0
- empathy_os/workflows/code_review.py +92 -22
- empathy_os/workflows/document_gen.py +594 -62
- empathy_os/workflows/llm_base.py +363 -0
- empathy_os/workflows/perf_audit.py +69 -0
- empathy_os/workflows/progressive/README 2.md +454 -0
- empathy_os/workflows/progressive/__init__ 2.py +92 -0
- empathy_os/workflows/progressive/cli 2.py +242 -0
- empathy_os/workflows/progressive/core 2.py +488 -0
- empathy_os/workflows/progressive/orchestrator 2.py +701 -0
- empathy_os/workflows/progressive/reports 2.py +528 -0
- empathy_os/workflows/progressive/telemetry 2.py +280 -0
- empathy_os/workflows/progressive/test_gen 2.py +514 -0
- empathy_os/workflows/progressive/workflow 2.py +628 -0
- empathy_os/workflows/release_prep.py +54 -0
- empathy_os/workflows/security_audit.py +154 -79
- empathy_os/workflows/test_gen.py +60 -0
- empathy_os/workflows/test_gen_behavioral.py +477 -0
- empathy_os/workflows/test_gen_parallel.py +341 -0
- empathy_framework-5.0.3.dist-info/licenses/LICENSE +0 -139
- {empathy_framework-5.0.3.dist-info → empathy_framework-5.1.0.dist-info}/WHEEL +0 -0
- {empathy_framework-5.0.3.dist-info → empathy_framework-5.1.0.dist-info}/entry_points.txt +0 -0
- {empathy_framework-5.0.3.dist-info → empathy_framework-5.1.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()
|