attune-ai 2.1.5__py3-none-any.whl → 2.2.1__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 (125) hide show
  1. attune/cli/__init__.py +3 -59
  2. attune/cli/commands/batch.py +4 -12
  3. attune/cli/commands/cache.py +8 -16
  4. attune/cli/commands/provider.py +17 -0
  5. attune/cli/commands/routing.py +3 -1
  6. attune/cli/commands/setup.py +122 -0
  7. attune/cli/commands/tier.py +1 -3
  8. attune/cli/commands/workflow.py +31 -0
  9. attune/cli/parsers/cache.py +1 -0
  10. attune/cli/parsers/help.py +1 -3
  11. attune/cli/parsers/provider.py +7 -0
  12. attune/cli/parsers/routing.py +1 -3
  13. attune/cli/parsers/setup.py +7 -0
  14. attune/cli/parsers/status.py +1 -3
  15. attune/cli/parsers/tier.py +1 -3
  16. attune/cli_minimal.py +9 -3
  17. attune/cli_router.py +9 -7
  18. attune/cli_unified.py +3 -0
  19. attune/dashboard/app.py +3 -1
  20. attune/dashboard/simple_server.py +3 -1
  21. attune/dashboard/standalone_server.py +7 -3
  22. attune/mcp/server.py +54 -102
  23. attune/memory/long_term.py +0 -2
  24. attune/memory/short_term/__init__.py +84 -0
  25. attune/memory/short_term/base.py +465 -0
  26. attune/memory/short_term/batch.py +219 -0
  27. attune/memory/short_term/caching.py +227 -0
  28. attune/memory/short_term/conflicts.py +265 -0
  29. attune/memory/short_term/cross_session.py +122 -0
  30. attune/memory/short_term/facade.py +653 -0
  31. attune/memory/short_term/pagination.py +207 -0
  32. attune/memory/short_term/patterns.py +271 -0
  33. attune/memory/short_term/pubsub.py +286 -0
  34. attune/memory/short_term/queues.py +244 -0
  35. attune/memory/short_term/security.py +300 -0
  36. attune/memory/short_term/sessions.py +250 -0
  37. attune/memory/short_term/streams.py +242 -0
  38. attune/memory/short_term/timelines.py +234 -0
  39. attune/memory/short_term/transactions.py +184 -0
  40. attune/memory/short_term/working.py +252 -0
  41. attune/meta_workflows/cli_commands/__init__.py +3 -0
  42. attune/meta_workflows/cli_commands/agent_commands.py +0 -4
  43. attune/meta_workflows/cli_commands/analytics_commands.py +0 -6
  44. attune/meta_workflows/cli_commands/config_commands.py +0 -5
  45. attune/meta_workflows/cli_commands/memory_commands.py +0 -5
  46. attune/meta_workflows/cli_commands/template_commands.py +0 -5
  47. attune/meta_workflows/cli_commands/workflow_commands.py +0 -6
  48. attune/meta_workflows/plan_generator.py +2 -4
  49. attune/models/adaptive_routing.py +4 -8
  50. attune/models/auth_cli.py +3 -9
  51. attune/models/auth_strategy.py +2 -4
  52. attune/models/telemetry/analytics.py +0 -2
  53. attune/models/telemetry/backend.py +0 -3
  54. attune/models/telemetry/storage.py +0 -2
  55. attune/monitoring/alerts.py +6 -10
  56. attune/orchestration/_strategies/__init__.py +156 -0
  57. attune/orchestration/_strategies/base.py +227 -0
  58. attune/orchestration/_strategies/conditional_strategies.py +365 -0
  59. attune/orchestration/_strategies/conditions.py +369 -0
  60. attune/orchestration/_strategies/core_strategies.py +479 -0
  61. attune/orchestration/_strategies/data_classes.py +64 -0
  62. attune/orchestration/_strategies/nesting.py +233 -0
  63. attune/orchestration/execution_strategies.py +58 -1567
  64. attune/orchestration/meta_orchestrator.py +1 -3
  65. attune/project_index/scanner.py +1 -3
  66. attune/project_index/scanner_parallel.py +7 -5
  67. attune/socratic/storage.py +2 -4
  68. attune/socratic_router.py +1 -3
  69. attune/telemetry/agent_coordination.py +9 -3
  70. attune/telemetry/agent_tracking.py +16 -3
  71. attune/telemetry/approval_gates.py +22 -5
  72. attune/telemetry/cli.py +1 -3
  73. attune/telemetry/commands/dashboard_commands.py +24 -8
  74. attune/telemetry/event_streaming.py +8 -2
  75. attune/telemetry/feedback_loop.py +10 -2
  76. attune/tools.py +2 -1
  77. attune/workflow_commands.py +1 -3
  78. attune/workflow_patterns/structural.py +4 -8
  79. attune/workflows/__init__.py +54 -10
  80. attune/workflows/autonomous_test_gen.py +158 -102
  81. attune/workflows/base.py +48 -672
  82. attune/workflows/batch_processing.py +1 -3
  83. attune/workflows/compat.py +156 -0
  84. attune/workflows/cost_mixin.py +141 -0
  85. attune/workflows/data_classes.py +92 -0
  86. attune/workflows/document_gen/workflow.py +11 -14
  87. attune/workflows/history.py +16 -9
  88. attune/workflows/llm_base.py +1 -3
  89. attune/workflows/migration.py +432 -0
  90. attune/workflows/output.py +2 -7
  91. attune/workflows/parsing_mixin.py +427 -0
  92. attune/workflows/perf_audit.py +3 -1
  93. attune/workflows/progress.py +9 -11
  94. attune/workflows/release_prep.py +5 -1
  95. attune/workflows/routing.py +0 -2
  96. attune/workflows/secure_release.py +4 -1
  97. attune/workflows/security_audit.py +20 -14
  98. attune/workflows/security_audit_phase3.py +28 -22
  99. attune/workflows/seo_optimization.py +27 -27
  100. attune/workflows/test_gen/test_templates.py +1 -4
  101. attune/workflows/test_gen/workflow.py +0 -2
  102. attune/workflows/test_gen_behavioral.py +6 -19
  103. attune/workflows/test_gen_parallel.py +8 -6
  104. {attune_ai-2.1.5.dist-info → attune_ai-2.2.1.dist-info}/METADATA +4 -3
  105. {attune_ai-2.1.5.dist-info → attune_ai-2.2.1.dist-info}/RECORD +121 -96
  106. {attune_ai-2.1.5.dist-info → attune_ai-2.2.1.dist-info}/entry_points.txt +0 -2
  107. attune_healthcare/monitors/monitoring/__init__.py +9 -9
  108. attune_llm/agent_factory/__init__.py +6 -6
  109. attune_llm/agent_factory/adapters/haystack_adapter.py +1 -4
  110. attune_llm/commands/__init__.py +10 -10
  111. attune_llm/commands/models.py +3 -3
  112. attune_llm/config/__init__.py +8 -8
  113. attune_llm/learning/__init__.py +3 -3
  114. attune_llm/learning/extractor.py +5 -3
  115. attune_llm/learning/storage.py +5 -3
  116. attune_llm/security/__init__.py +17 -17
  117. attune_llm/utils/tokens.py +3 -1
  118. attune/cli_legacy.py +0 -3978
  119. attune/memory/short_term.py +0 -2192
  120. attune/workflows/manage_docs.py +0 -87
  121. attune/workflows/test5.py +0 -125
  122. {attune_ai-2.1.5.dist-info → attune_ai-2.2.1.dist-info}/WHEEL +0 -0
  123. {attune_ai-2.1.5.dist-info → attune_ai-2.2.1.dist-info}/licenses/LICENSE +0 -0
  124. {attune_ai-2.1.5.dist-info → attune_ai-2.2.1.dist-info}/licenses/LICENSE_CHANGE_ANNOUNCEMENT.md +0 -0
  125. {attune_ai-2.1.5.dist-info → attune_ai-2.2.1.dist-info}/top_level.txt +0 -0
@@ -192,9 +192,7 @@ class BatchProcessingWorkflow:
192
192
  )
193
193
 
194
194
  elif result_type == "expired":
195
- results.append(
196
- BatchResult(task_id=task_id, success=False, error="Request expired")
197
- )
195
+ results.append(BatchResult(task_id=task_id, success=False, error="Request expired"))
198
196
 
199
197
  elif result_type == "canceled":
200
198
  results.append(
@@ -0,0 +1,156 @@
1
+ """Backward compatibility module for deprecated workflow enums.
2
+
3
+ This module contains deprecated enums that were originally in base.py:
4
+ - ModelTier: Use attune.models.ModelTier instead
5
+ - ModelProvider: Use attune.models.ModelProvider instead
6
+
7
+ These are maintained for backward compatibility only.
8
+ New code should use attune.models imports directly.
9
+
10
+ Migration guide:
11
+ # Old (deprecated):
12
+ from attune.workflows.base import ModelTier, ModelProvider
13
+
14
+ # New (recommended):
15
+ from attune.models import ModelTier, ModelProvider
16
+
17
+ Copyright 2025 Smart-AI-Memory
18
+ Licensed under Fair Source License 0.9
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ import warnings
24
+ from enum import Enum
25
+ from typing import TYPE_CHECKING
26
+
27
+ # Import unified types for conversion
28
+ from attune.models import ModelProvider as UnifiedModelProvider
29
+ from attune.models import ModelTier as UnifiedModelTier
30
+
31
+ if TYPE_CHECKING:
32
+ pass
33
+
34
+
35
+ class ModelTier(Enum):
36
+ """DEPRECATED: Model tier for cost optimization.
37
+
38
+ This enum is deprecated and will be removed in v5.0.
39
+ Use attune.models.ModelTier instead.
40
+
41
+ Migration:
42
+ # Old:
43
+ from attune.workflows.base import ModelTier
44
+
45
+ # New:
46
+ from attune.models import ModelTier
47
+
48
+ Why deprecated:
49
+ - Creates confusion with dual definitions
50
+ - attune.models.ModelTier is the canonical location
51
+ - Simplifies imports and reduces duplication
52
+ """
53
+
54
+ CHEAP = "cheap" # Haiku/GPT-4o-mini - $0.25-1.25/M tokens
55
+ CAPABLE = "capable" # Sonnet/GPT-4o - $3-15/M tokens
56
+ PREMIUM = "premium" # Opus/o1 - $15-75/M tokens
57
+
58
+ def __init__(self, value: str):
59
+ """Initialize with deprecation warning."""
60
+ # Only warn once per process, not per instance
61
+ if not hasattr(self.__class__, "_deprecation_warned"):
62
+ warnings.warn(
63
+ "workflows.base.ModelTier is deprecated and will be removed in v5.0. "
64
+ "Use attune.models.ModelTier instead. "
65
+ "Update imports: from attune.models import ModelTier",
66
+ DeprecationWarning,
67
+ stacklevel=4,
68
+ )
69
+ self.__class__._deprecation_warned = True
70
+
71
+ def to_unified(self) -> UnifiedModelTier:
72
+ """Convert to unified ModelTier from attune.models."""
73
+ return UnifiedModelTier(self.value)
74
+
75
+
76
+ class ModelProvider(Enum):
77
+ """DEPRECATED: Supported model providers.
78
+
79
+ This enum is deprecated and will be removed in v5.0.
80
+ Use attune.models.ModelProvider instead.
81
+
82
+ Migration:
83
+ # Old:
84
+ from attune.workflows.base import ModelProvider
85
+
86
+ # New:
87
+ from attune.models import ModelProvider
88
+ """
89
+
90
+ ANTHROPIC = "anthropic"
91
+ OPENAI = "openai"
92
+ GOOGLE = "google" # Google Gemini models
93
+ OLLAMA = "ollama"
94
+ HYBRID = "hybrid" # Mix of best models from different providers
95
+ CUSTOM = "custom" # User-defined custom models
96
+
97
+ def to_unified(self) -> UnifiedModelProvider:
98
+ """Convert to unified ModelProvider from attune.models.
99
+
100
+ As of v5.0.0, framework is Claude-native. All providers map to ANTHROPIC.
101
+ """
102
+ # v5.0.0: Framework is Claude-native, only ANTHROPIC supported
103
+ return UnifiedModelProvider.ANTHROPIC
104
+
105
+
106
+ def _build_provider_models() -> dict[ModelProvider, dict[ModelTier, str]]:
107
+ """Build PROVIDER_MODELS from MODEL_REGISTRY.
108
+
109
+ This ensures PROVIDER_MODELS stays in sync with the single source of truth.
110
+
111
+ Returns:
112
+ Dictionary mapping ModelProvider -> ModelTier -> model_id
113
+ """
114
+ # Lazy import to avoid circular dependencies
115
+ from attune.models import MODEL_REGISTRY
116
+
117
+ result: dict[ModelProvider, dict[ModelTier, str]] = {}
118
+
119
+ # Map string provider names to ModelProvider enum
120
+ provider_map = {
121
+ "anthropic": ModelProvider.ANTHROPIC,
122
+ "openai": ModelProvider.OPENAI,
123
+ "google": ModelProvider.GOOGLE,
124
+ "ollama": ModelProvider.OLLAMA,
125
+ "hybrid": ModelProvider.HYBRID,
126
+ }
127
+
128
+ # Map string tier names to ModelTier enum
129
+ tier_map = {
130
+ "cheap": ModelTier.CHEAP,
131
+ "capable": ModelTier.CAPABLE,
132
+ "premium": ModelTier.PREMIUM,
133
+ }
134
+
135
+ for provider_str, tiers in MODEL_REGISTRY.items():
136
+ if provider_str not in provider_map:
137
+ continue # Skip custom providers
138
+ provider_enum = provider_map[provider_str]
139
+ result[provider_enum] = {}
140
+ for tier_str, model_info in tiers.items():
141
+ if tier_str in tier_map:
142
+ result[provider_enum][tier_map[tier_str]] = model_info.id
143
+
144
+ return result
145
+
146
+
147
+ # Build PROVIDER_MODELS at module load time
148
+ PROVIDER_MODELS: dict[ModelProvider, dict[ModelTier, str]] = _build_provider_models()
149
+
150
+ # Expose all public symbols
151
+ __all__ = [
152
+ "ModelTier",
153
+ "ModelProvider",
154
+ "PROVIDER_MODELS",
155
+ "_build_provider_models",
156
+ ]
@@ -0,0 +1,141 @@
1
+ """Cost tracking mixin for workflow classes.
2
+
3
+ This module provides methods for calculating and reporting workflow costs,
4
+ including tier-based pricing, baseline comparisons, and cache savings.
5
+
6
+ Extracted from base.py for improved maintainability and import performance.
7
+
8
+ Copyright 2025 Smart-AI-Memory
9
+ Licensed under Fair Source License 0.9
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ from typing import TYPE_CHECKING, Any
15
+
16
+ if TYPE_CHECKING:
17
+ from .data_classes import CostReport
18
+
19
+
20
+ class CostTrackingMixin:
21
+ """Mixin providing cost tracking capabilities for workflows.
22
+
23
+ This mixin adds methods for calculating costs, baseline comparisons,
24
+ and generating cost reports with cache savings.
25
+
26
+ Methods:
27
+ _calculate_cost: Calculate cost for a stage based on tier and tokens
28
+ _calculate_baseline_cost: Calculate premium-tier baseline cost
29
+ _generate_cost_report: Generate comprehensive cost report
30
+
31
+ Note:
32
+ This mixin expects the class to have:
33
+ - _stages_run: list[WorkflowStage] - stages executed in workflow
34
+ - _get_cache_stats(): method returning cache statistics dict
35
+ - ModelTier enum available
36
+ """
37
+
38
+ # These will be provided by the main class
39
+ _stages_run: list[Any]
40
+
41
+ def _calculate_cost(self, tier: Any, input_tokens: int, output_tokens: int) -> float:
42
+ """Calculate cost for a stage.
43
+
44
+ Args:
45
+ tier: ModelTier enum value for the stage
46
+ input_tokens: Number of input tokens used
47
+ output_tokens: Number of output tokens generated
48
+
49
+ Returns:
50
+ Total cost in dollars for this stage
51
+ """
52
+ from attune.cost_tracker import MODEL_PRICING
53
+
54
+ tier_name = tier.value
55
+ pricing = MODEL_PRICING.get(tier_name, MODEL_PRICING["capable"])
56
+ input_cost = (input_tokens / 1_000_000) * pricing["input"]
57
+ output_cost = (output_tokens / 1_000_000) * pricing["output"]
58
+ return input_cost + output_cost
59
+
60
+ def _calculate_baseline_cost(self, input_tokens: int, output_tokens: int) -> float:
61
+ """Calculate what the cost would be using premium tier.
62
+
63
+ Args:
64
+ input_tokens: Number of input tokens used
65
+ output_tokens: Number of output tokens generated
66
+
67
+ Returns:
68
+ Cost in dollars if premium tier was used
69
+ """
70
+ from attune.cost_tracker import MODEL_PRICING
71
+
72
+ pricing = MODEL_PRICING["premium"]
73
+ input_cost = (input_tokens / 1_000_000) * pricing["input"]
74
+ output_cost = (output_tokens / 1_000_000) * pricing["output"]
75
+ return input_cost + output_cost
76
+
77
+ def _generate_cost_report(self) -> CostReport:
78
+ """Generate cost report from completed stages.
79
+
80
+ Calculates total costs, baseline comparisons, savings percentages,
81
+ and includes cache performance metrics.
82
+
83
+ Returns:
84
+ CostReport with comprehensive cost breakdown and savings analysis
85
+ """
86
+ from .data_classes import CostReport
87
+
88
+ total_cost = 0.0
89
+ baseline_cost = 0.0
90
+ by_stage: dict[str, float] = {}
91
+ by_tier: dict[str, float] = {}
92
+
93
+ for stage in self._stages_run:
94
+ if stage.skipped:
95
+ continue
96
+
97
+ total_cost += stage.cost
98
+ by_stage[stage.name] = stage.cost
99
+
100
+ tier_name = stage.tier.value
101
+ by_tier[tier_name] = by_tier.get(tier_name, 0.0) + stage.cost
102
+
103
+ # Calculate what this would cost at premium tier
104
+ baseline_cost += self._calculate_baseline_cost(stage.input_tokens, stage.output_tokens)
105
+
106
+ savings = baseline_cost - total_cost
107
+ savings_percent = (savings / baseline_cost * 100) if baseline_cost > 0 else 0.0
108
+
109
+ # Calculate cache metrics using CachingMixin
110
+ cache_stats = self._get_cache_stats()
111
+ cache_hits = cache_stats["hits"]
112
+ cache_misses = cache_stats["misses"]
113
+ cache_hit_rate = cache_stats["hit_rate"]
114
+ estimated_cost_without_cache = total_cost
115
+ savings_from_cache = 0.0
116
+
117
+ # Estimate cost without cache (assumes cache hits would have incurred full cost)
118
+ if cache_hits > 0:
119
+ avg_cost_per_call = total_cost / cache_misses if cache_misses > 0 else 0.0
120
+ estimated_additional_cost = cache_hits * avg_cost_per_call
121
+ estimated_cost_without_cache = total_cost + estimated_additional_cost
122
+ savings_from_cache = estimated_additional_cost
123
+
124
+ return CostReport(
125
+ total_cost=total_cost,
126
+ baseline_cost=baseline_cost,
127
+ savings=savings,
128
+ savings_percent=savings_percent,
129
+ by_stage=by_stage,
130
+ by_tier=by_tier,
131
+ cache_hits=cache_hits,
132
+ cache_misses=cache_misses,
133
+ cache_hit_rate=cache_hit_rate,
134
+ estimated_cost_without_cache=estimated_cost_without_cache,
135
+ savings_from_cache=savings_from_cache,
136
+ )
137
+
138
+ def _get_cache_stats(self) -> dict[str, Any]:
139
+ """Get cache statistics. Override in subclass or mixin."""
140
+ # Default implementation - CachingMixin provides the real one
141
+ return {"hits": 0, "misses": 0, "hit_rate": 0.0}
@@ -0,0 +1,92 @@
1
+ """Data classes for workflow execution.
2
+
3
+ This module contains the core data structures used across all workflows:
4
+ - WorkflowStage: Represents a single stage in a workflow
5
+ - CostReport: Cost breakdown for a workflow execution
6
+ - StageQualityMetrics: Quality metrics for stage output validation
7
+ - WorkflowResult: Result of a workflow execution
8
+
9
+ Copyright 2025 Smart-AI-Memory
10
+ Licensed under Fair Source License 0.9
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ from dataclasses import dataclass, field
16
+ from datetime import datetime
17
+ from typing import TYPE_CHECKING, Any
18
+
19
+ if TYPE_CHECKING:
20
+ # Use unified ModelTier for type hints (avoids circular imports)
21
+ from attune.models import ModelTier
22
+
23
+
24
+ @dataclass
25
+ class WorkflowStage:
26
+ """Represents a single stage in a workflow."""
27
+
28
+ name: str
29
+ tier: ModelTier
30
+ description: str
31
+ input_tokens: int = 0
32
+ output_tokens: int = 0
33
+ cost: float = 0.0
34
+ result: Any = None
35
+ duration_ms: int = 0
36
+ skipped: bool = False
37
+ skip_reason: str | None = None
38
+
39
+
40
+ @dataclass
41
+ class CostReport:
42
+ """Cost breakdown for a workflow execution."""
43
+
44
+ total_cost: float
45
+ baseline_cost: float # If all stages used premium
46
+ savings: float
47
+ savings_percent: float
48
+ by_stage: dict[str, float] = field(default_factory=dict)
49
+ by_tier: dict[str, float] = field(default_factory=dict)
50
+ # Cache metrics
51
+ cache_hits: int = 0
52
+ cache_misses: int = 0
53
+ cache_hit_rate: float = 0.0
54
+ estimated_cost_without_cache: float = 0.0
55
+ savings_from_cache: float = 0.0
56
+
57
+
58
+ @dataclass
59
+ class StageQualityMetrics:
60
+ """Quality metrics for stage output validation."""
61
+
62
+ execution_succeeded: bool
63
+ output_valid: bool
64
+ quality_improved: bool # Workflow-specific (e.g., health score improved)
65
+ error_type: str | None
66
+ validation_error: str | None
67
+
68
+
69
+ @dataclass
70
+ class WorkflowResult:
71
+ """Result of a workflow execution."""
72
+
73
+ success: bool
74
+ stages: list[WorkflowStage]
75
+ final_output: Any
76
+ cost_report: CostReport
77
+ started_at: datetime
78
+ completed_at: datetime
79
+ total_duration_ms: int
80
+ provider: str = "unknown"
81
+ error: str | None = None
82
+ # Structured error taxonomy for reliability
83
+ error_type: str | None = None # "config" | "runtime" | "provider" | "timeout" | "validation"
84
+ transient: bool = False # True if retry is reasonable (e.g., provider timeout)
85
+ # Optional metadata and summary for extended reporting
86
+ metadata: dict[str, Any] = field(default_factory=dict)
87
+ summary: str | None = None
88
+
89
+ @property
90
+ def duration_seconds(self) -> float:
91
+ """Get duration in seconds (computed from total_duration_ms)."""
92
+ return self.total_duration_ms / 1000.0
@@ -377,8 +377,7 @@ class DocumentGenerationWorkflow(BaseWorkflow):
377
377
  )
378
378
  else: # API
379
379
  logger.info(
380
- f"Cost: ~${cost_estimate['monetary_cost']:.4f} "
381
- f"(1M context window)"
380
+ f"Cost: ~${cost_estimate['monetary_cost']:.4f} " f"(1M context window)"
382
381
  )
383
382
 
384
383
  except Exception as e:
@@ -1257,13 +1256,15 @@ Make all code examples complete and executable."""
1257
1256
  # Extract docstring
1258
1257
  docstring = ast.get_docstring(node) or ""
1259
1258
 
1260
- functions.append({
1261
- "name": node.name,
1262
- "args": args_list,
1263
- "return_type": return_type,
1264
- "docstring": docstring,
1265
- "lineno": node.lineno,
1266
- })
1259
+ functions.append(
1260
+ {
1261
+ "name": node.name,
1262
+ "args": args_list,
1263
+ "return_type": return_type,
1264
+ "docstring": docstring,
1265
+ "lineno": node.lineno,
1266
+ }
1267
+ )
1267
1268
 
1268
1269
  return functions
1269
1270
 
@@ -1407,9 +1408,7 @@ None
1407
1408
  func_name = func_info["name"]
1408
1409
  logger.debug(f"Generating API reference for {func_name}()")
1409
1410
 
1410
- api_section = await self._generate_api_section_for_function(
1411
- func_info, tier
1412
- )
1411
+ api_section = await self._generate_api_section_for_function(func_info, tier)
1413
1412
  api_sections.append(api_section)
1414
1413
 
1415
1414
  # Append API reference section to narrative doc
@@ -1422,5 +1421,3 @@ None
1422
1421
  logger.info(f"Added {len(api_sections)} API reference sections")
1423
1422
 
1424
1423
  return full_doc
1425
-
1426
-
@@ -184,13 +184,18 @@ class WorkflowHistoryStore:
184
184
  result.error,
185
185
  result.error_type,
186
186
  1 if result.transient else 0,
187
- 1
188
- if isinstance(result.final_output, dict)
189
- and result.final_output.get("xml_parsed")
190
- else 0,
191
- result.final_output.get("summary")
192
- if isinstance(result.final_output, dict)
193
- else None,
187
+ (
188
+ 1
189
+ if isinstance(result.final_output, dict)
190
+ and result.final_output.get("xml_parsed")
191
+ else 0
192
+ ),
193
+ (
194
+ str(result.final_output.get("summary"))
195
+ if isinstance(result.final_output, dict)
196
+ and result.final_output.get("summary") is not None
197
+ else None
198
+ ),
194
199
  ),
195
200
  )
196
201
 
@@ -480,12 +485,14 @@ class WorkflowHistoryStore:
480
485
  # Actual data (run_ids) passed as parameters - SQL injection safe
481
486
  placeholders = ",".join("?" * len(run_ids))
482
487
  cursor.execute(
483
- f"DELETE FROM workflow_stages WHERE run_id IN ({placeholders})", run_ids
488
+ f"DELETE FROM workflow_stages WHERE run_id IN ({placeholders})", # nosec B608
489
+ run_ids,
484
490
  )
485
491
 
486
492
  # Delete runs (same safe parameterization pattern)
487
493
  cursor.execute(
488
- f"DELETE FROM workflow_runs WHERE run_id IN ({placeholders})", run_ids
494
+ f"DELETE FROM workflow_runs WHERE run_id IN ({placeholders})", # nosec B608
495
+ run_ids,
489
496
  )
490
497
 
491
498
  self.conn.commit()
@@ -262,9 +262,7 @@ class LLMWorkflowGenerator(ABC):
262
262
  # Calculate rates
263
263
  total_requests = stats["llm_requests"]
264
264
  if total_requests > 0:
265
- stats["llm_success_rate"] = (
266
- total_requests - stats["llm_failures"]
267
- ) / total_requests
265
+ stats["llm_success_rate"] = (total_requests - stats["llm_failures"]) / total_requests
268
266
  stats["template_fallback_rate"] = stats["template_fallbacks"] / total_requests
269
267
  else:
270
268
  stats["llm_success_rate"] = 0.0