empathy-framework 5.0.1__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.
Files changed (61) hide show
  1. {empathy_framework-5.0.1.dist-info → empathy_framework-5.1.0.dist-info}/METADATA +311 -150
  2. {empathy_framework-5.0.1.dist-info → empathy_framework-5.1.0.dist-info}/RECORD +60 -33
  3. empathy_framework-5.1.0.dist-info/licenses/LICENSE +201 -0
  4. empathy_framework-5.1.0.dist-info/licenses/LICENSE_CHANGE_ANNOUNCEMENT.md +101 -0
  5. empathy_llm_toolkit/providers.py +175 -35
  6. empathy_llm_toolkit/utils/tokens.py +150 -30
  7. empathy_os/__init__.py +1 -1
  8. empathy_os/cli/commands/batch.py +256 -0
  9. empathy_os/cli/commands/cache.py +248 -0
  10. empathy_os/cli/commands/inspect.py +1 -2
  11. empathy_os/cli/commands/metrics.py +1 -1
  12. empathy_os/cli/commands/routing.py +285 -0
  13. empathy_os/cli/commands/workflow.py +2 -1
  14. empathy_os/cli/parsers/__init__.py +6 -0
  15. empathy_os/cli/parsers/batch.py +118 -0
  16. empathy_os/cli/parsers/cache 2.py +65 -0
  17. empathy_os/cli/parsers/cache.py +65 -0
  18. empathy_os/cli/parsers/routing.py +110 -0
  19. empathy_os/cli_minimal.py +3 -3
  20. empathy_os/cli_router 2.py +416 -0
  21. empathy_os/dashboard/__init__.py +1 -2
  22. empathy_os/dashboard/app 2.py +512 -0
  23. empathy_os/dashboard/app.py +1 -1
  24. empathy_os/dashboard/simple_server 2.py +403 -0
  25. empathy_os/dashboard/standalone_server 2.py +536 -0
  26. empathy_os/dashboard/standalone_server.py +22 -11
  27. empathy_os/memory/types 2.py +441 -0
  28. empathy_os/metrics/collector.py +31 -0
  29. empathy_os/models/__init__.py +19 -0
  30. empathy_os/models/adaptive_routing 2.py +437 -0
  31. empathy_os/models/auth_cli.py +444 -0
  32. empathy_os/models/auth_strategy.py +450 -0
  33. empathy_os/models/token_estimator.py +21 -13
  34. empathy_os/project_index/scanner_parallel 2.py +291 -0
  35. empathy_os/telemetry/agent_coordination 2.py +478 -0
  36. empathy_os/telemetry/agent_coordination.py +14 -16
  37. empathy_os/telemetry/agent_tracking 2.py +350 -0
  38. empathy_os/telemetry/agent_tracking.py +18 -20
  39. empathy_os/telemetry/approval_gates 2.py +563 -0
  40. empathy_os/telemetry/approval_gates.py +27 -39
  41. empathy_os/telemetry/event_streaming 2.py +405 -0
  42. empathy_os/telemetry/event_streaming.py +22 -22
  43. empathy_os/telemetry/feedback_loop 2.py +557 -0
  44. empathy_os/telemetry/feedback_loop.py +14 -17
  45. empathy_os/workflows/__init__.py +8 -0
  46. empathy_os/workflows/autonomous_test_gen.py +569 -0
  47. empathy_os/workflows/batch_processing.py +56 -10
  48. empathy_os/workflows/bug_predict.py +45 -0
  49. empathy_os/workflows/code_review.py +92 -22
  50. empathy_os/workflows/document_gen.py +594 -62
  51. empathy_os/workflows/llm_base.py +363 -0
  52. empathy_os/workflows/perf_audit.py +69 -0
  53. empathy_os/workflows/release_prep.py +54 -0
  54. empathy_os/workflows/security_audit.py +154 -79
  55. empathy_os/workflows/test_gen.py +60 -0
  56. empathy_os/workflows/test_gen_behavioral.py +477 -0
  57. empathy_os/workflows/test_gen_parallel.py +341 -0
  58. empathy_framework-5.0.1.dist-info/licenses/LICENSE +0 -139
  59. {empathy_framework-5.0.1.dist-info → empathy_framework-5.1.0.dist-info}/WHEEL +0 -0
  60. {empathy_framework-5.0.1.dist-info → empathy_framework-5.1.0.dist-info}/entry_points.txt +0 -0
  61. {empathy_framework-5.0.1.dist-info → empathy_framework-5.1.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,363 @@
1
+ """Base class for LLM-enhanced workflow generation.
2
+
3
+ Provides reusable patterns for hybrid LLM + template generation with:
4
+ - Smart caching for expensive operations
5
+ - Fallback to templates when LLM fails
6
+ - Quality validation
7
+ - Dashboard integration
8
+ - Cost tracking
9
+
10
+ Copyright 2026 Smart-AI-Memory
11
+ Licensed under Apache 2.0
12
+ """
13
+
14
+ import hashlib
15
+ import json
16
+ import logging
17
+ import os
18
+ from abc import ABC, abstractmethod
19
+ from datetime import datetime, timedelta
20
+ from typing import Any
21
+
22
+ logger = logging.getLogger(__name__)
23
+
24
+
25
+ class LLMWorkflowGenerator(ABC):
26
+ """Base class for LLM-enhanced workflow generation.
27
+
28
+ Provides hybrid approach: intelligent LLM generation with fallback templates.
29
+
30
+ Usage:
31
+ class TestGeneratorLLM(LLMWorkflowGenerator):
32
+ def _generate_with_template(self, context: dict) -> str:
33
+ return create_template(context)
34
+
35
+ def _validate(self, result: str) -> bool:
36
+ return validate_python_syntax(result)
37
+
38
+ generator = TestGeneratorLLM(model_tier="capable")
39
+ output = generator.generate(context, prompt)
40
+ """
41
+
42
+ def __init__(
43
+ self,
44
+ model_tier: str = "capable",
45
+ enable_cache: bool = True,
46
+ cache_ttl_hours: int = 24,
47
+ ):
48
+ """Initialize LLM workflow generator.
49
+
50
+ Args:
51
+ model_tier: Model tier to use (cheap, capable, premium)
52
+ enable_cache: Whether to cache LLM responses
53
+ cache_ttl_hours: Cache time-to-live in hours
54
+ """
55
+ self.model_tier = model_tier
56
+ self.enable_cache = enable_cache
57
+ self.cache_ttl = timedelta(hours=cache_ttl_hours)
58
+ self._cache: dict[str, tuple[str, datetime]] = {}
59
+ self._stats = {
60
+ "llm_requests": 0,
61
+ "llm_failures": 0,
62
+ "template_fallbacks": 0,
63
+ "cache_hits": 0,
64
+ "cache_misses": 0,
65
+ "total_tokens": 0,
66
+ "total_cost_usd": 0.0,
67
+ }
68
+
69
+ def generate(self, context: dict[str, Any], prompt: str) -> str:
70
+ """Generate output with LLM, fallback to template.
71
+
72
+ Args:
73
+ context: Context dict for generation
74
+ prompt: LLM prompt
75
+
76
+ Returns:
77
+ Generated output (from LLM or template)
78
+ """
79
+ # Check cache first
80
+ if self.enable_cache:
81
+ cache_key = self._make_cache_key(context, prompt)
82
+ cached = self._get_from_cache(cache_key)
83
+ if cached:
84
+ self._stats["cache_hits"] += 1
85
+ logger.debug(f"Cache hit for {cache_key[:16]}...")
86
+ return cached
87
+ self._stats["cache_misses"] += 1
88
+
89
+ # Try LLM generation
90
+ try:
91
+ self._stats["llm_requests"] += 1
92
+ result = self._generate_with_llm(prompt)
93
+
94
+ # Validate result
95
+ if self._validate(result):
96
+ # Cache successful result
97
+ if self.enable_cache:
98
+ self._put_in_cache(cache_key, result)
99
+
100
+ # Track tokens and cost
101
+ self._update_usage_stats(result)
102
+
103
+ logger.info("LLM generation successful")
104
+ return result
105
+ else:
106
+ logger.warning("LLM result failed validation")
107
+
108
+ except Exception as e:
109
+ self._stats["llm_failures"] += 1
110
+ logger.warning(f"LLM generation failed: {e}")
111
+
112
+ # Fallback to template
113
+ self._stats["template_fallbacks"] += 1
114
+ logger.info("Falling back to template generation")
115
+ return self._generate_with_template(context)
116
+
117
+ def _generate_with_llm(self, prompt: str) -> str:
118
+ """Generate using LLM API.
119
+
120
+ Args:
121
+ prompt: LLM prompt
122
+
123
+ Returns:
124
+ Generated content
125
+
126
+ Raises:
127
+ Exception: If LLM generation fails
128
+ """
129
+ try:
130
+ import anthropic
131
+ except ImportError:
132
+ raise ImportError("anthropic package not installed")
133
+
134
+ # Get API key
135
+ api_key = os.getenv("ANTHROPIC_API_KEY")
136
+ if not api_key:
137
+ raise ValueError("ANTHROPIC_API_KEY not set")
138
+
139
+ # Get model ID for tier
140
+ model_id = self._get_model_id(self.model_tier)
141
+
142
+ # Call Anthropic API
143
+ logger.debug(f"Calling LLM with {self.model_tier} tier (model: {model_id})")
144
+ client = anthropic.Anthropic(api_key=api_key)
145
+ response = client.messages.create(
146
+ model=model_id,
147
+ max_tokens=4000,
148
+ messages=[{"role": "user", "content": prompt}],
149
+ )
150
+
151
+ if not response.content:
152
+ raise ValueError("Empty LLM response")
153
+
154
+ result = response.content[0].text.strip()
155
+
156
+ # Clean up markdown fences if present
157
+ if result.startswith("```python"):
158
+ result = result[len("```python") :].strip()
159
+ elif result.startswith("```"):
160
+ result = result[3:].strip()
161
+ if result.endswith("```"):
162
+ result = result[:-3].strip()
163
+
164
+ return result
165
+
166
+ def _get_model_id(self, tier: str) -> str:
167
+ """Get model ID for tier.
168
+
169
+ Args:
170
+ tier: Model tier (cheap, capable, premium)
171
+
172
+ Returns:
173
+ Model ID string
174
+ """
175
+ from empathy_os.models.registry import get_model
176
+
177
+ model_info = get_model("anthropic", tier)
178
+ if not model_info:
179
+ raise ValueError(f"No model found for tier: {tier}")
180
+
181
+ return model_info.model_id
182
+
183
+ def _make_cache_key(self, context: dict[str, Any], prompt: str) -> str:
184
+ """Create cache key from context and prompt.
185
+
186
+ Args:
187
+ context: Context dict
188
+ prompt: Prompt string
189
+
190
+ Returns:
191
+ Cache key (hex hash)
192
+ """
193
+ # Combine context and prompt for cache key
194
+ cache_data = {
195
+ "context": context,
196
+ "prompt": prompt,
197
+ "model_tier": self.model_tier,
198
+ }
199
+ cache_json = json.dumps(cache_data, sort_keys=True)
200
+ return hashlib.sha256(cache_json.encode()).hexdigest()
201
+
202
+ def _get_from_cache(self, cache_key: str) -> str | None:
203
+ """Get item from cache if not expired.
204
+
205
+ Args:
206
+ cache_key: Cache key
207
+
208
+ Returns:
209
+ Cached value or None if not found/expired
210
+ """
211
+ if cache_key not in self._cache:
212
+ return None
213
+
214
+ value, timestamp = self._cache[cache_key]
215
+
216
+ # Check if expired
217
+ if datetime.now() - timestamp > self.cache_ttl:
218
+ del self._cache[cache_key]
219
+ return None
220
+
221
+ return value
222
+
223
+ def _put_in_cache(self, cache_key: str, value: str):
224
+ """Put item in cache with current timestamp.
225
+
226
+ Args:
227
+ cache_key: Cache key
228
+ value: Value to cache
229
+ """
230
+ self._cache[cache_key] = (value, datetime.now())
231
+
232
+ def _update_usage_stats(self, result: str):
233
+ """Update token and cost statistics.
234
+
235
+ Args:
236
+ result: Generated result
237
+ """
238
+ # Rough token estimate (4 chars per token)
239
+ estimated_tokens = len(result) // 4
240
+ self._stats["total_tokens"] += estimated_tokens
241
+
242
+ # Cost estimation (based on capable tier: $3/M input, $15/M output)
243
+ if self.model_tier == "cheap":
244
+ cost_per_token = 1.0 / 1_000_000 # $1/M tokens
245
+ elif self.model_tier == "capable":
246
+ cost_per_token = 15.0 / 1_000_000 # $15/M output tokens
247
+ elif self.model_tier == "premium":
248
+ cost_per_token = 75.0 / 1_000_000 # $75/M output tokens
249
+ else:
250
+ cost_per_token = 15.0 / 1_000_000
251
+
252
+ self._stats["total_cost_usd"] += estimated_tokens * cost_per_token
253
+
254
+ def get_stats(self) -> dict[str, Any]:
255
+ """Get generation statistics.
256
+
257
+ Returns:
258
+ Dict with usage stats
259
+ """
260
+ stats = self._stats.copy()
261
+
262
+ # Calculate rates
263
+ total_requests = stats["llm_requests"]
264
+ if total_requests > 0:
265
+ stats["llm_success_rate"] = (
266
+ total_requests - stats["llm_failures"]
267
+ ) / total_requests
268
+ stats["template_fallback_rate"] = stats["template_fallbacks"] / total_requests
269
+ else:
270
+ stats["llm_success_rate"] = 0.0
271
+ stats["template_fallback_rate"] = 0.0
272
+
273
+ # Cache performance
274
+ total_cache_ops = stats["cache_hits"] + stats["cache_misses"]
275
+ if total_cache_ops > 0:
276
+ stats["cache_hit_rate"] = stats["cache_hits"] / total_cache_ops
277
+ else:
278
+ stats["cache_hit_rate"] = 0.0
279
+
280
+ return stats
281
+
282
+ def clear_cache(self):
283
+ """Clear the cache."""
284
+ self._cache.clear()
285
+ logger.info("Cache cleared")
286
+
287
+ @abstractmethod
288
+ def _generate_with_template(self, context: dict[str, Any]) -> str:
289
+ """Generate using template fallback.
290
+
291
+ Args:
292
+ context: Context dict with generation data
293
+
294
+ Returns:
295
+ Generated output from template
296
+
297
+ Note:
298
+ Subclasses must implement this method.
299
+ """
300
+ raise NotImplementedError("Subclass must implement _generate_with_template")
301
+
302
+ @abstractmethod
303
+ def _validate(self, result: str) -> bool:
304
+ """Validate generated output.
305
+
306
+ Args:
307
+ result: Generated output to validate
308
+
309
+ Returns:
310
+ True if valid, False otherwise
311
+
312
+ Note:
313
+ Subclasses must implement this method.
314
+ """
315
+ raise NotImplementedError("Subclass must implement _validate")
316
+
317
+
318
+ class TestGeneratorLLM(LLMWorkflowGenerator):
319
+ """Example LLM-enhanced test generator.
320
+
321
+ Shows how to use the base class for test generation.
322
+ """
323
+
324
+ def _generate_with_template(self, context: dict[str, Any]) -> str:
325
+ """Fallback template generation.
326
+
327
+ Args:
328
+ context: Must contain 'module_name', 'module_path'
329
+
330
+ Returns:
331
+ Template test file
332
+ """
333
+ module_name = context.get("module_name", "unknown")
334
+ module_path = context.get("module_path", "unknown")
335
+
336
+ return f'''"""Behavioral tests for {module_name}.
337
+
338
+ Generated by template fallback.
339
+
340
+ Copyright 2026 Smart-AI-Memory
341
+ Licensed under Apache 2.0
342
+ """
343
+
344
+ import pytest
345
+
346
+ def test_{module_name}_placeholder():
347
+ """Placeholder test - implement actual tests."""
348
+ # TODO: Implement comprehensive tests
349
+ pass
350
+ '''
351
+
352
+ def _validate(self, result: str) -> bool:
353
+ """Validate test file has basic structure.
354
+
355
+ Args:
356
+ result: Generated test file content
357
+
358
+ Returns:
359
+ True if valid test file structure
360
+ """
361
+ # Check for basic test file structure
362
+ required = ["import pytest", "def test_", '"""']
363
+ return all(req in result for req in required) and len(result) > 100
@@ -140,18 +140,22 @@ class PerformanceAuditWorkflow(BaseWorkflow):
140
140
  def __init__(
141
141
  self,
142
142
  min_hotspots_for_premium: int = 3,
143
+ enable_auth_strategy: bool = True,
143
144
  **kwargs: Any,
144
145
  ):
145
146
  """Initialize performance audit workflow.
146
147
 
147
148
  Args:
148
149
  min_hotspots_for_premium: Minimum hotspots to trigger premium optimization
150
+ enable_auth_strategy: Enable intelligent auth routing (default: True)
149
151
  **kwargs: Additional arguments passed to BaseWorkflow
150
152
 
151
153
  """
152
154
  super().__init__(**kwargs)
153
155
  self.min_hotspots_for_premium = min_hotspots_for_premium
156
+ self.enable_auth_strategy = enable_auth_strategy
154
157
  self._hotspot_count: int = 0
158
+ self._auth_mode_used: str | None = None
155
159
 
156
160
  def should_skip_stage(self, stage_name: str, input_data: Any) -> tuple[bool, str | None]:
157
161
  """Downgrade optimize stage if few hotspots.
@@ -199,6 +203,70 @@ class PerformanceAuditWorkflow(BaseWorkflow):
199
203
  files_scanned = 0
200
204
 
201
205
  target = Path(target_path)
206
+
207
+ # === AUTH STRATEGY INTEGRATION ===
208
+ if self.enable_auth_strategy:
209
+ try:
210
+ import logging
211
+
212
+ from empathy_os.models import (
213
+ count_lines_of_code,
214
+ get_auth_strategy,
215
+ get_module_size_category,
216
+ )
217
+
218
+ logger = logging.getLogger(__name__)
219
+
220
+ # Calculate total LOC for the project/path
221
+ total_lines = 0
222
+ if target.is_file():
223
+ total_lines = count_lines_of_code(target)
224
+ elif target.is_dir():
225
+ # Estimate total lines for directory
226
+ for ext in file_types:
227
+ for file_path in target.rglob(f"*{ext}"):
228
+ if any(
229
+ skip in str(file_path)
230
+ for skip in [".git", "node_modules", "__pycache__", "venv", "test"]
231
+ ):
232
+ continue
233
+ try:
234
+ total_lines += count_lines_of_code(file_path)
235
+ except Exception:
236
+ pass
237
+
238
+ if total_lines > 0:
239
+ strategy = get_auth_strategy()
240
+ recommended_mode = strategy.get_recommended_mode(total_lines)
241
+ self._auth_mode_used = recommended_mode.value
242
+
243
+ size_category = get_module_size_category(total_lines)
244
+ logger.info(
245
+ f"Performance audit target: {target_path} "
246
+ f"({total_lines:,} LOC, {size_category})"
247
+ )
248
+ logger.info(f"Recommended auth mode: {recommended_mode.value}")
249
+
250
+ cost_estimate = strategy.estimate_cost(total_lines, recommended_mode)
251
+ if recommended_mode.value == "subscription":
252
+ logger.info(
253
+ f"Cost estimate: ~${cost_estimate:.4f} "
254
+ "(significantly cheaper with subscription)"
255
+ )
256
+ else:
257
+ logger.info(f"Cost estimate: ~${cost_estimate:.4f} (API-based)")
258
+
259
+ except ImportError as e:
260
+ import logging
261
+
262
+ logger = logging.getLogger(__name__)
263
+ logger.debug(f"Auth strategy not available: {e}")
264
+ except Exception as e:
265
+ import logging
266
+
267
+ logger = logging.getLogger(__name__)
268
+ logger.warning(f"Auth strategy detection failed: {e}")
269
+ # === END AUTH STRATEGY INTEGRATION ===
202
270
  if target.exists():
203
271
  for ext in file_types:
204
272
  for file_path in target.rglob(f"*{ext}"):
@@ -468,6 +536,7 @@ Provide detailed optimization strategies."""
468
536
  "perf_score": hotspot_result.get("perf_score", 0),
469
537
  "perf_level": hotspot_result.get("perf_level", "unknown"),
470
538
  "model_tier_used": tier.value,
539
+ "auth_mode_used": self._auth_mode_used,
471
540
  }
472
541
 
473
542
  # Merge parsed XML data if available
@@ -60,6 +60,7 @@ class ReleasePreparationWorkflow(BaseWorkflow):
60
60
  skip_approve_if_clean: bool = True,
61
61
  use_security_crew: bool = False,
62
62
  crew_config: dict | None = None,
63
+ enable_auth_strategy: bool = True,
63
64
  **kwargs: Any,
64
65
  ):
65
66
  """Initialize release preparation workflow.
@@ -68,6 +69,7 @@ class ReleasePreparationWorkflow(BaseWorkflow):
68
69
  skip_approve_if_clean: Skip premium approval if all checks pass
69
70
  use_security_crew: Enable SecurityAuditCrew for comprehensive security audit
70
71
  crew_config: Configuration dict for SecurityAuditCrew
72
+ enable_auth_strategy: Enable intelligent auth routing (default: True)
71
73
  **kwargs: Additional arguments passed to BaseWorkflow
72
74
 
73
75
  """
@@ -75,7 +77,9 @@ class ReleasePreparationWorkflow(BaseWorkflow):
75
77
  self.skip_approve_if_clean = skip_approve_if_clean
76
78
  self.use_security_crew = use_security_crew
77
79
  self.crew_config = crew_config or {}
80
+ self.enable_auth_strategy = enable_auth_strategy
78
81
  self._has_blockers: bool = False
82
+ self._auth_mode_used: str | None = None
79
83
 
80
84
  # Dynamically configure stages based on security crew setting
81
85
  if use_security_crew:
@@ -137,6 +141,52 @@ class ReleasePreparationWorkflow(BaseWorkflow):
137
141
  Executes lint, type checking, and tests.
138
142
  """
139
143
  target_path = input_data.get("path", ".")
144
+
145
+ # === AUTH STRATEGY INTEGRATION ===
146
+ if self.enable_auth_strategy:
147
+ try:
148
+ import logging
149
+ from pathlib import Path
150
+
151
+ from empathy_os.models import (
152
+ count_lines_of_code,
153
+ get_auth_strategy,
154
+ get_module_size_category,
155
+ )
156
+ logger = logging.getLogger(__name__)
157
+
158
+ # Calculate total LOC for project/directory
159
+ target = Path(target_path)
160
+ total_lines = 0
161
+ if target.is_file():
162
+ total_lines = count_lines_of_code(target)
163
+ elif target.is_dir():
164
+ for py_file in target.rglob("*.py"):
165
+ try:
166
+ total_lines += count_lines_of_code(py_file)
167
+ except Exception:
168
+ pass
169
+
170
+ if total_lines > 0:
171
+ strategy = get_auth_strategy()
172
+ recommended_mode = strategy.get_recommended_mode(total_lines)
173
+ self._auth_mode_used = recommended_mode.value
174
+
175
+ size_category = get_module_size_category(total_lines)
176
+ logger.info(f"Release prep target: {target_path} ({total_lines:,} LOC, {size_category})")
177
+ logger.info(f"Recommended auth mode: {recommended_mode.value}")
178
+
179
+ cost_estimate = strategy.estimate_cost(total_lines, recommended_mode)
180
+ if recommended_mode.value == "subscription":
181
+ logger.info(f"Cost: {cost_estimate['quota_cost']}")
182
+ else:
183
+ logger.info(f"Cost: ~${cost_estimate['monetary_cost']:.4f}")
184
+
185
+ except Exception as e:
186
+ import logging
187
+ logger = logging.getLogger(__name__)
188
+ logger.warning(f"Auth strategy detection failed: {e}")
189
+
140
190
  checks: dict[str, dict] = {}
141
191
 
142
192
  # Lint check (ruff)
@@ -640,6 +690,10 @@ Provide a comprehensive release readiness assessment."""
640
690
  "model_tier_used": tier.value,
641
691
  }
642
692
 
693
+ # Include auth mode used for telemetry
694
+ if self._auth_mode_used:
695
+ result["auth_mode_used"] = self._auth_mode_used
696
+
643
697
  # Merge parsed XML data if available
644
698
  if parsed_data.get("xml_parsed"):
645
699
  result.update(