htmlgraph 0.20.1__py3-none-any.whl → 0.27.5__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.
- htmlgraph/.htmlgraph/.session-warning-state.json +6 -0
- htmlgraph/.htmlgraph/agents.json +72 -0
- htmlgraph/.htmlgraph/htmlgraph.db +0 -0
- htmlgraph/__init__.py +51 -1
- htmlgraph/__init__.pyi +123 -0
- htmlgraph/agent_detection.py +26 -10
- htmlgraph/agent_registry.py +2 -1
- htmlgraph/analytics/__init__.py +8 -1
- htmlgraph/analytics/cli.py +86 -20
- htmlgraph/analytics/cost_analyzer.py +391 -0
- htmlgraph/analytics/cost_monitor.py +664 -0
- htmlgraph/analytics/cost_reporter.py +675 -0
- htmlgraph/analytics/cross_session.py +617 -0
- htmlgraph/analytics/dependency.py +10 -6
- htmlgraph/analytics/pattern_learning.py +771 -0
- htmlgraph/analytics/session_graph.py +707 -0
- htmlgraph/analytics/strategic/__init__.py +80 -0
- htmlgraph/analytics/strategic/cost_optimizer.py +611 -0
- htmlgraph/analytics/strategic/pattern_detector.py +876 -0
- htmlgraph/analytics/strategic/preference_manager.py +709 -0
- htmlgraph/analytics/strategic/suggestion_engine.py +747 -0
- htmlgraph/analytics/work_type.py +67 -27
- htmlgraph/analytics_index.py +53 -20
- htmlgraph/api/__init__.py +3 -0
- htmlgraph/api/cost_alerts_websocket.py +416 -0
- htmlgraph/api/main.py +2498 -0
- htmlgraph/api/static/htmx.min.js +1 -0
- htmlgraph/api/static/style-redesign.css +1344 -0
- htmlgraph/api/static/style.css +1079 -0
- htmlgraph/api/templates/dashboard-redesign.html +1366 -0
- htmlgraph/api/templates/dashboard.html +794 -0
- htmlgraph/api/templates/partials/activity-feed-hierarchical.html +326 -0
- htmlgraph/api/templates/partials/activity-feed.html +1100 -0
- htmlgraph/api/templates/partials/agents-redesign.html +317 -0
- htmlgraph/api/templates/partials/agents.html +317 -0
- htmlgraph/api/templates/partials/event-traces.html +373 -0
- htmlgraph/api/templates/partials/features-kanban-redesign.html +509 -0
- htmlgraph/api/templates/partials/features.html +578 -0
- htmlgraph/api/templates/partials/metrics-redesign.html +346 -0
- htmlgraph/api/templates/partials/metrics.html +346 -0
- htmlgraph/api/templates/partials/orchestration-redesign.html +443 -0
- htmlgraph/api/templates/partials/orchestration.html +198 -0
- htmlgraph/api/templates/partials/spawners.html +375 -0
- htmlgraph/api/templates/partials/work-items.html +613 -0
- htmlgraph/api/websocket.py +538 -0
- htmlgraph/archive/__init__.py +24 -0
- htmlgraph/archive/bloom.py +234 -0
- htmlgraph/archive/fts.py +297 -0
- htmlgraph/archive/manager.py +583 -0
- htmlgraph/archive/search.py +244 -0
- htmlgraph/atomic_ops.py +560 -0
- htmlgraph/attribute_index.py +2 -1
- htmlgraph/bounded_paths.py +539 -0
- htmlgraph/builders/base.py +57 -2
- htmlgraph/builders/bug.py +19 -3
- htmlgraph/builders/chore.py +19 -3
- htmlgraph/builders/epic.py +19 -3
- htmlgraph/builders/feature.py +27 -3
- htmlgraph/builders/insight.py +2 -1
- htmlgraph/builders/metric.py +2 -1
- htmlgraph/builders/pattern.py +2 -1
- htmlgraph/builders/phase.py +19 -3
- htmlgraph/builders/spike.py +29 -3
- htmlgraph/builders/track.py +42 -1
- htmlgraph/cigs/__init__.py +81 -0
- htmlgraph/cigs/autonomy.py +385 -0
- htmlgraph/cigs/cost.py +475 -0
- htmlgraph/cigs/messages_basic.py +472 -0
- htmlgraph/cigs/messaging.py +365 -0
- htmlgraph/cigs/models.py +771 -0
- htmlgraph/cigs/pattern_storage.py +427 -0
- htmlgraph/cigs/patterns.py +503 -0
- htmlgraph/cigs/posttool_analyzer.py +234 -0
- htmlgraph/cigs/reporter.py +818 -0
- htmlgraph/cigs/tracker.py +317 -0
- htmlgraph/cli/.htmlgraph/.session-warning-state.json +6 -0
- htmlgraph/cli/.htmlgraph/agents.json +72 -0
- htmlgraph/cli/.htmlgraph/htmlgraph.db +0 -0
- htmlgraph/cli/__init__.py +42 -0
- htmlgraph/cli/__main__.py +6 -0
- htmlgraph/cli/analytics.py +1424 -0
- htmlgraph/cli/base.py +685 -0
- htmlgraph/cli/constants.py +206 -0
- htmlgraph/cli/core.py +954 -0
- htmlgraph/cli/main.py +147 -0
- htmlgraph/cli/models.py +475 -0
- htmlgraph/cli/templates/__init__.py +1 -0
- htmlgraph/cli/templates/cost_dashboard.py +399 -0
- htmlgraph/cli/work/__init__.py +239 -0
- htmlgraph/cli/work/browse.py +115 -0
- htmlgraph/cli/work/features.py +568 -0
- htmlgraph/cli/work/orchestration.py +676 -0
- htmlgraph/cli/work/report.py +728 -0
- htmlgraph/cli/work/sessions.py +466 -0
- htmlgraph/cli/work/snapshot.py +559 -0
- htmlgraph/cli/work/tracks.py +486 -0
- htmlgraph/cli_commands/__init__.py +1 -0
- htmlgraph/cli_commands/feature.py +195 -0
- htmlgraph/cli_framework.py +115 -0
- htmlgraph/collections/__init__.py +2 -0
- htmlgraph/collections/base.py +197 -14
- htmlgraph/collections/bug.py +2 -1
- htmlgraph/collections/chore.py +2 -1
- htmlgraph/collections/epic.py +2 -1
- htmlgraph/collections/feature.py +2 -1
- htmlgraph/collections/insight.py +2 -1
- htmlgraph/collections/metric.py +2 -1
- htmlgraph/collections/pattern.py +2 -1
- htmlgraph/collections/phase.py +2 -1
- htmlgraph/collections/session.py +194 -0
- htmlgraph/collections/spike.py +13 -2
- htmlgraph/collections/task_delegation.py +241 -0
- htmlgraph/collections/todo.py +14 -1
- htmlgraph/collections/traces.py +487 -0
- htmlgraph/config/cost_models.json +56 -0
- htmlgraph/config.py +190 -0
- htmlgraph/context_analytics.py +2 -1
- htmlgraph/converter.py +116 -7
- htmlgraph/cost_analysis/__init__.py +5 -0
- htmlgraph/cost_analysis/analyzer.py +438 -0
- htmlgraph/dashboard.html +2246 -248
- htmlgraph/dashboard.html.backup +6592 -0
- htmlgraph/dashboard.html.bak +7181 -0
- htmlgraph/dashboard.html.bak2 +7231 -0
- htmlgraph/dashboard.html.bak3 +7232 -0
- htmlgraph/db/__init__.py +38 -0
- htmlgraph/db/queries.py +790 -0
- htmlgraph/db/schema.py +1788 -0
- htmlgraph/decorators.py +317 -0
- htmlgraph/dependency_models.py +2 -1
- htmlgraph/deploy.py +26 -27
- htmlgraph/docs/API_REFERENCE.md +841 -0
- htmlgraph/docs/HTTP_API.md +750 -0
- htmlgraph/docs/INTEGRATION_GUIDE.md +752 -0
- htmlgraph/docs/ORCHESTRATION_PATTERNS.md +717 -0
- htmlgraph/docs/README.md +532 -0
- htmlgraph/docs/__init__.py +77 -0
- htmlgraph/docs/docs_version.py +55 -0
- htmlgraph/docs/metadata.py +93 -0
- htmlgraph/docs/migrations.py +232 -0
- htmlgraph/docs/template_engine.py +143 -0
- htmlgraph/docs/templates/_sections/cli_reference.md.j2 +52 -0
- htmlgraph/docs/templates/_sections/core_concepts.md.j2 +29 -0
- htmlgraph/docs/templates/_sections/sdk_basics.md.j2 +69 -0
- htmlgraph/docs/templates/base_agents.md.j2 +78 -0
- htmlgraph/docs/templates/example_user_override.md.j2 +47 -0
- htmlgraph/docs/version_check.py +163 -0
- htmlgraph/edge_index.py +2 -1
- htmlgraph/error_handler.py +544 -0
- htmlgraph/event_log.py +86 -37
- htmlgraph/event_migration.py +2 -1
- htmlgraph/file_watcher.py +12 -8
- htmlgraph/find_api.py +2 -1
- htmlgraph/git_events.py +67 -9
- htmlgraph/hooks/.htmlgraph/.session-warning-state.json +6 -0
- htmlgraph/hooks/.htmlgraph/agents.json +72 -0
- htmlgraph/hooks/.htmlgraph/index.sqlite +0 -0
- htmlgraph/hooks/__init__.py +8 -0
- htmlgraph/hooks/bootstrap.py +169 -0
- htmlgraph/hooks/cigs_pretool_enforcer.py +354 -0
- htmlgraph/hooks/concurrent_sessions.py +208 -0
- htmlgraph/hooks/context.py +350 -0
- htmlgraph/hooks/drift_handler.py +525 -0
- htmlgraph/hooks/event_tracker.py +790 -99
- htmlgraph/hooks/git_commands.py +175 -0
- htmlgraph/hooks/installer.py +5 -1
- htmlgraph/hooks/orchestrator.py +327 -76
- htmlgraph/hooks/orchestrator_reflector.py +31 -4
- htmlgraph/hooks/post_tool_use_failure.py +32 -7
- htmlgraph/hooks/post_tool_use_handler.py +257 -0
- htmlgraph/hooks/posttooluse.py +92 -19
- htmlgraph/hooks/pretooluse.py +527 -7
- htmlgraph/hooks/prompt_analyzer.py +637 -0
- htmlgraph/hooks/session_handler.py +668 -0
- htmlgraph/hooks/session_summary.py +395 -0
- htmlgraph/hooks/state_manager.py +504 -0
- htmlgraph/hooks/subagent_detection.py +202 -0
- htmlgraph/hooks/subagent_stop.py +369 -0
- htmlgraph/hooks/task_enforcer.py +99 -4
- htmlgraph/hooks/validator.py +212 -91
- htmlgraph/ids.py +2 -1
- htmlgraph/learning.py +125 -100
- htmlgraph/mcp_server.py +2 -1
- htmlgraph/models.py +217 -18
- htmlgraph/operations/README.md +62 -0
- htmlgraph/operations/__init__.py +79 -0
- htmlgraph/operations/analytics.py +339 -0
- htmlgraph/operations/bootstrap.py +289 -0
- htmlgraph/operations/events.py +244 -0
- htmlgraph/operations/fastapi_server.py +231 -0
- htmlgraph/operations/hooks.py +350 -0
- htmlgraph/operations/initialization.py +597 -0
- htmlgraph/operations/initialization.py.backup +228 -0
- htmlgraph/operations/server.py +303 -0
- htmlgraph/orchestration/__init__.py +58 -0
- htmlgraph/orchestration/claude_launcher.py +179 -0
- htmlgraph/orchestration/command_builder.py +72 -0
- htmlgraph/orchestration/headless_spawner.py +281 -0
- htmlgraph/orchestration/live_events.py +377 -0
- htmlgraph/orchestration/model_selection.py +327 -0
- htmlgraph/orchestration/plugin_manager.py +140 -0
- htmlgraph/orchestration/prompts.py +137 -0
- htmlgraph/orchestration/spawner_event_tracker.py +383 -0
- htmlgraph/orchestration/spawners/__init__.py +16 -0
- htmlgraph/orchestration/spawners/base.py +194 -0
- htmlgraph/orchestration/spawners/claude.py +173 -0
- htmlgraph/orchestration/spawners/codex.py +435 -0
- htmlgraph/orchestration/spawners/copilot.py +294 -0
- htmlgraph/orchestration/spawners/gemini.py +471 -0
- htmlgraph/orchestration/subprocess_runner.py +36 -0
- htmlgraph/{orchestration.py → orchestration/task_coordination.py} +16 -8
- htmlgraph/orchestration.md +563 -0
- htmlgraph/orchestrator-system-prompt-optimized.txt +863 -0
- htmlgraph/orchestrator.py +2 -1
- htmlgraph/orchestrator_config.py +357 -0
- htmlgraph/orchestrator_mode.py +115 -4
- htmlgraph/parallel.py +2 -1
- htmlgraph/parser.py +86 -6
- htmlgraph/path_query.py +608 -0
- htmlgraph/pattern_matcher.py +636 -0
- htmlgraph/pydantic_models.py +476 -0
- htmlgraph/quality_gates.py +350 -0
- htmlgraph/query_builder.py +2 -1
- htmlgraph/query_composer.py +509 -0
- htmlgraph/reflection.py +443 -0
- htmlgraph/refs.py +344 -0
- htmlgraph/repo_hash.py +512 -0
- htmlgraph/repositories/__init__.py +292 -0
- htmlgraph/repositories/analytics_repository.py +455 -0
- htmlgraph/repositories/analytics_repository_standard.py +628 -0
- htmlgraph/repositories/feature_repository.py +581 -0
- htmlgraph/repositories/feature_repository_htmlfile.py +668 -0
- htmlgraph/repositories/feature_repository_memory.py +607 -0
- htmlgraph/repositories/feature_repository_sqlite.py +858 -0
- htmlgraph/repositories/filter_service.py +620 -0
- htmlgraph/repositories/filter_service_standard.py +445 -0
- htmlgraph/repositories/shared_cache.py +621 -0
- htmlgraph/repositories/shared_cache_memory.py +395 -0
- htmlgraph/repositories/track_repository.py +552 -0
- htmlgraph/repositories/track_repository_htmlfile.py +619 -0
- htmlgraph/repositories/track_repository_memory.py +508 -0
- htmlgraph/repositories/track_repository_sqlite.py +711 -0
- htmlgraph/sdk/__init__.py +398 -0
- htmlgraph/sdk/__init__.pyi +14 -0
- htmlgraph/sdk/analytics/__init__.py +19 -0
- htmlgraph/sdk/analytics/engine.py +155 -0
- htmlgraph/sdk/analytics/helpers.py +178 -0
- htmlgraph/sdk/analytics/registry.py +109 -0
- htmlgraph/sdk/base.py +484 -0
- htmlgraph/sdk/constants.py +216 -0
- htmlgraph/sdk/core.pyi +308 -0
- htmlgraph/sdk/discovery.py +120 -0
- htmlgraph/sdk/help/__init__.py +12 -0
- htmlgraph/sdk/help/mixin.py +699 -0
- htmlgraph/sdk/mixins/__init__.py +15 -0
- htmlgraph/sdk/mixins/attribution.py +113 -0
- htmlgraph/sdk/mixins/mixin.py +410 -0
- htmlgraph/sdk/operations/__init__.py +12 -0
- htmlgraph/sdk/operations/mixin.py +427 -0
- htmlgraph/sdk/orchestration/__init__.py +17 -0
- htmlgraph/sdk/orchestration/coordinator.py +203 -0
- htmlgraph/sdk/orchestration/spawner.py +204 -0
- htmlgraph/sdk/planning/__init__.py +19 -0
- htmlgraph/sdk/planning/bottlenecks.py +93 -0
- htmlgraph/sdk/planning/mixin.py +211 -0
- htmlgraph/sdk/planning/parallel.py +186 -0
- htmlgraph/sdk/planning/queue.py +210 -0
- htmlgraph/sdk/planning/recommendations.py +87 -0
- htmlgraph/sdk/planning/smart_planning.py +319 -0
- htmlgraph/sdk/session/__init__.py +19 -0
- htmlgraph/sdk/session/continuity.py +57 -0
- htmlgraph/sdk/session/handoff.py +110 -0
- htmlgraph/sdk/session/info.py +309 -0
- htmlgraph/sdk/session/manager.py +103 -0
- htmlgraph/sdk/strategic/__init__.py +26 -0
- htmlgraph/sdk/strategic/mixin.py +563 -0
- htmlgraph/server.py +295 -107
- htmlgraph/session_hooks.py +300 -0
- htmlgraph/session_manager.py +285 -3
- htmlgraph/session_registry.py +587 -0
- htmlgraph/session_state.py +436 -0
- htmlgraph/session_warning.py +2 -1
- htmlgraph/sessions/__init__.py +23 -0
- htmlgraph/sessions/handoff.py +756 -0
- htmlgraph/system_prompts.py +450 -0
- htmlgraph/templates/orchestration-view.html +350 -0
- htmlgraph/track_builder.py +33 -1
- htmlgraph/track_manager.py +38 -0
- htmlgraph/transcript.py +18 -5
- htmlgraph/validation.py +115 -0
- htmlgraph/watch.py +2 -1
- htmlgraph/work_type_utils.py +2 -1
- {htmlgraph-0.20.1.data → htmlgraph-0.27.5.data}/data/htmlgraph/dashboard.html +2246 -248
- {htmlgraph-0.20.1.dist-info → htmlgraph-0.27.5.dist-info}/METADATA +95 -64
- htmlgraph-0.27.5.dist-info/RECORD +337 -0
- {htmlgraph-0.20.1.dist-info → htmlgraph-0.27.5.dist-info}/entry_points.txt +1 -1
- htmlgraph/cli.py +0 -4839
- htmlgraph/sdk.py +0 -2359
- htmlgraph-0.20.1.dist-info/RECORD +0 -118
- {htmlgraph-0.20.1.data → htmlgraph-0.27.5.data}/data/htmlgraph/styles.css +0 -0
- {htmlgraph-0.20.1.data → htmlgraph-0.27.5.data}/data/htmlgraph/templates/AGENTS.md.template +0 -0
- {htmlgraph-0.20.1.data → htmlgraph-0.27.5.data}/data/htmlgraph/templates/CLAUDE.md.template +0 -0
- {htmlgraph-0.20.1.data → htmlgraph-0.27.5.data}/data/htmlgraph/templates/GEMINI.md.template +0 -0
- {htmlgraph-0.20.1.dist-info → htmlgraph-0.27.5.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Strategic Analytics - Phase 3: Pattern Detection & Smart Suggestions
|
|
3
|
+
|
|
4
|
+
This module provides intelligent pattern detection and suggestion systems
|
|
5
|
+
that learn from user delegation patterns and make smart suggestions.
|
|
6
|
+
|
|
7
|
+
Key Components:
|
|
8
|
+
1. PatternDetector - Detects tool sequences, delegation chains, error patterns
|
|
9
|
+
2. SuggestionEngine - Generates context-aware suggestions with ranking
|
|
10
|
+
3. PreferenceManager - Learns user preferences from feedback
|
|
11
|
+
4. CostOptimizer - Suggests token budgets, parallelization, model selection
|
|
12
|
+
|
|
13
|
+
Usage:
|
|
14
|
+
from htmlgraph.analytics.strategic import (
|
|
15
|
+
PatternDetector,
|
|
16
|
+
SuggestionEngine,
|
|
17
|
+
PreferenceManager,
|
|
18
|
+
CostOptimizer,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
# Detect patterns from event history
|
|
22
|
+
detector = PatternDetector(db_path)
|
|
23
|
+
patterns = detector.detect_all_patterns()
|
|
24
|
+
|
|
25
|
+
# Get suggestions for current context
|
|
26
|
+
engine = SuggestionEngine(db_path)
|
|
27
|
+
suggestions = engine.suggest(context)
|
|
28
|
+
|
|
29
|
+
# Learn from feedback
|
|
30
|
+
manager = PreferenceManager(db_path)
|
|
31
|
+
manager.record_feedback(suggestion_id, accepted=True)
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
from htmlgraph.analytics.strategic.cost_optimizer import (
|
|
35
|
+
CostOptimizer,
|
|
36
|
+
ModelRecommendation,
|
|
37
|
+
ParallelizationStrategy,
|
|
38
|
+
TokenBudget,
|
|
39
|
+
)
|
|
40
|
+
from htmlgraph.analytics.strategic.pattern_detector import (
|
|
41
|
+
DelegationChain,
|
|
42
|
+
ErrorPattern,
|
|
43
|
+
Pattern,
|
|
44
|
+
PatternDetector,
|
|
45
|
+
PatternType,
|
|
46
|
+
ToolSequencePattern,
|
|
47
|
+
)
|
|
48
|
+
from htmlgraph.analytics.strategic.preference_manager import (
|
|
49
|
+
Feedback,
|
|
50
|
+
PreferenceManager,
|
|
51
|
+
UserPreferences,
|
|
52
|
+
)
|
|
53
|
+
from htmlgraph.analytics.strategic.suggestion_engine import (
|
|
54
|
+
Suggestion,
|
|
55
|
+
SuggestionEngine,
|
|
56
|
+
SuggestionType,
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
__all__ = [
|
|
60
|
+
# Pattern Detection
|
|
61
|
+
"PatternDetector",
|
|
62
|
+
"Pattern",
|
|
63
|
+
"PatternType",
|
|
64
|
+
"ToolSequencePattern",
|
|
65
|
+
"DelegationChain",
|
|
66
|
+
"ErrorPattern",
|
|
67
|
+
# Suggestion Engine
|
|
68
|
+
"SuggestionEngine",
|
|
69
|
+
"Suggestion",
|
|
70
|
+
"SuggestionType",
|
|
71
|
+
# Preference Manager
|
|
72
|
+
"PreferenceManager",
|
|
73
|
+
"UserPreferences",
|
|
74
|
+
"Feedback",
|
|
75
|
+
# Cost Optimizer
|
|
76
|
+
"CostOptimizer",
|
|
77
|
+
"TokenBudget",
|
|
78
|
+
"ParallelizationStrategy",
|
|
79
|
+
"ModelRecommendation",
|
|
80
|
+
]
|
|
@@ -0,0 +1,611 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CostOptimizer - Suggests token budgets, parallelization, and model selection.
|
|
3
|
+
|
|
4
|
+
This module provides cost optimization recommendations:
|
|
5
|
+
1. Token budgeting - Automatically suggest token budgets based on task scope
|
|
6
|
+
2. Parallelization - Calculate optimal parallelization strategies
|
|
7
|
+
3. Model selection - Choose cheapest model that can handle task
|
|
8
|
+
4. Caching - Identify caching opportunities to save tokens
|
|
9
|
+
|
|
10
|
+
Usage:
|
|
11
|
+
from htmlgraph.analytics.strategic import CostOptimizer
|
|
12
|
+
|
|
13
|
+
optimizer = CostOptimizer(db_path)
|
|
14
|
+
|
|
15
|
+
# Get token budget suggestion
|
|
16
|
+
budget = optimizer.suggest_token_budget(task_description)
|
|
17
|
+
|
|
18
|
+
# Get parallelization strategy
|
|
19
|
+
strategy = optimizer.suggest_parallelization(tasks)
|
|
20
|
+
|
|
21
|
+
# Get model recommendation
|
|
22
|
+
model = optimizer.choose_model(task, preferences)
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
import logging
|
|
26
|
+
import sqlite3
|
|
27
|
+
from dataclasses import dataclass, field
|
|
28
|
+
from enum import Enum
|
|
29
|
+
from pathlib import Path
|
|
30
|
+
from typing import Any
|
|
31
|
+
|
|
32
|
+
logger = logging.getLogger(__name__)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class ModelTier(Enum):
|
|
36
|
+
"""Model tiers by capability and cost."""
|
|
37
|
+
|
|
38
|
+
HAIKU = "haiku" # Fast, cheap, simple tasks
|
|
39
|
+
SONNET = "sonnet" # Balanced, default choice
|
|
40
|
+
OPUS = "opus" # Complex, expensive, high-quality
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@dataclass
|
|
44
|
+
class TokenBudget:
|
|
45
|
+
"""
|
|
46
|
+
Token budget recommendation for a task.
|
|
47
|
+
|
|
48
|
+
Attributes:
|
|
49
|
+
recommended: Recommended token budget
|
|
50
|
+
minimum: Minimum viable budget
|
|
51
|
+
maximum: Maximum reasonable budget
|
|
52
|
+
confidence: Confidence in the recommendation
|
|
53
|
+
reasoning: Explanation for the recommendation
|
|
54
|
+
based_on_history: Whether based on historical data
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
recommended: int
|
|
58
|
+
minimum: int
|
|
59
|
+
maximum: int
|
|
60
|
+
confidence: float = 0.7
|
|
61
|
+
reasoning: str = ""
|
|
62
|
+
based_on_history: bool = False
|
|
63
|
+
|
|
64
|
+
def to_dict(self) -> dict[str, Any]:
|
|
65
|
+
"""Convert to dictionary."""
|
|
66
|
+
return {
|
|
67
|
+
"recommended": self.recommended,
|
|
68
|
+
"minimum": self.minimum,
|
|
69
|
+
"maximum": self.maximum,
|
|
70
|
+
"confidence": self.confidence,
|
|
71
|
+
"reasoning": self.reasoning,
|
|
72
|
+
"based_on_history": self.based_on_history,
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@dataclass
|
|
77
|
+
class ParallelizationStrategy:
|
|
78
|
+
"""
|
|
79
|
+
Parallelization strategy for a set of tasks.
|
|
80
|
+
|
|
81
|
+
Attributes:
|
|
82
|
+
groups: List of task groups that can run in parallel
|
|
83
|
+
estimated_speedup: Expected speedup factor
|
|
84
|
+
estimated_cost: Estimated total token cost
|
|
85
|
+
reasoning: Explanation for the strategy
|
|
86
|
+
"""
|
|
87
|
+
|
|
88
|
+
groups: list[list[str]] = field(default_factory=list)
|
|
89
|
+
estimated_speedup: float = 1.0
|
|
90
|
+
estimated_cost: int = 0
|
|
91
|
+
reasoning: str = ""
|
|
92
|
+
|
|
93
|
+
def to_dict(self) -> dict[str, Any]:
|
|
94
|
+
"""Convert to dictionary."""
|
|
95
|
+
return {
|
|
96
|
+
"groups": self.groups,
|
|
97
|
+
"estimated_speedup": self.estimated_speedup,
|
|
98
|
+
"estimated_cost": self.estimated_cost,
|
|
99
|
+
"reasoning": self.reasoning,
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
@dataclass
|
|
104
|
+
class ModelRecommendation:
|
|
105
|
+
"""
|
|
106
|
+
Model selection recommendation.
|
|
107
|
+
|
|
108
|
+
Attributes:
|
|
109
|
+
recommended_model: Recommended model tier
|
|
110
|
+
alternative_model: Alternative if budget constrained
|
|
111
|
+
confidence: Confidence in recommendation
|
|
112
|
+
reasoning: Explanation for the choice
|
|
113
|
+
estimated_cost: Estimated token cost with this model
|
|
114
|
+
estimated_quality: Expected quality score (0-1)
|
|
115
|
+
"""
|
|
116
|
+
|
|
117
|
+
recommended_model: ModelTier
|
|
118
|
+
alternative_model: ModelTier | None = None
|
|
119
|
+
confidence: float = 0.7
|
|
120
|
+
reasoning: str = ""
|
|
121
|
+
estimated_cost: int = 0
|
|
122
|
+
estimated_quality: float = 0.8
|
|
123
|
+
|
|
124
|
+
def to_dict(self) -> dict[str, Any]:
|
|
125
|
+
"""Convert to dictionary."""
|
|
126
|
+
return {
|
|
127
|
+
"recommended_model": self.recommended_model.value,
|
|
128
|
+
"alternative_model": self.alternative_model.value
|
|
129
|
+
if self.alternative_model
|
|
130
|
+
else None,
|
|
131
|
+
"confidence": self.confidence,
|
|
132
|
+
"reasoning": self.reasoning,
|
|
133
|
+
"estimated_cost": self.estimated_cost,
|
|
134
|
+
"estimated_quality": self.estimated_quality,
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
class CostOptimizer:
|
|
139
|
+
"""
|
|
140
|
+
Optimizes cost through token budgeting, parallelization, and model selection.
|
|
141
|
+
|
|
142
|
+
Uses historical data and heuristics to make intelligent cost decisions.
|
|
143
|
+
"""
|
|
144
|
+
|
|
145
|
+
# Token cost multipliers (relative to Haiku = 1.0)
|
|
146
|
+
MODEL_COST_MULTIPLIERS = {
|
|
147
|
+
ModelTier.HAIKU: 1.0,
|
|
148
|
+
ModelTier.SONNET: 3.0,
|
|
149
|
+
ModelTier.OPUS: 15.0,
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
# Default token budgets by task complexity
|
|
153
|
+
DEFAULT_BUDGETS = {
|
|
154
|
+
"simple": 2000,
|
|
155
|
+
"medium": 5000,
|
|
156
|
+
"complex": 10000,
|
|
157
|
+
"very_complex": 20000,
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
def __init__(self, db_path: Path | str | None = None):
|
|
161
|
+
"""
|
|
162
|
+
Initialize cost optimizer.
|
|
163
|
+
|
|
164
|
+
Args:
|
|
165
|
+
db_path: Path to HtmlGraph database. If None, uses default location.
|
|
166
|
+
"""
|
|
167
|
+
if db_path is None:
|
|
168
|
+
from htmlgraph.config import get_database_path
|
|
169
|
+
|
|
170
|
+
db_path = get_database_path()
|
|
171
|
+
|
|
172
|
+
self.db_path = Path(db_path)
|
|
173
|
+
self._conn: sqlite3.Connection | None = None
|
|
174
|
+
|
|
175
|
+
def _get_connection(self) -> sqlite3.Connection:
|
|
176
|
+
"""Get database connection with row factory."""
|
|
177
|
+
if self._conn is None:
|
|
178
|
+
self._conn = sqlite3.connect(str(self.db_path))
|
|
179
|
+
self._conn.row_factory = sqlite3.Row
|
|
180
|
+
return self._conn
|
|
181
|
+
|
|
182
|
+
def close(self) -> None:
|
|
183
|
+
"""Close database connection."""
|
|
184
|
+
if self._conn:
|
|
185
|
+
self._conn.close()
|
|
186
|
+
self._conn = None
|
|
187
|
+
|
|
188
|
+
def suggest_token_budget(
|
|
189
|
+
self,
|
|
190
|
+
task_description: str,
|
|
191
|
+
tool_name: str | None = None,
|
|
192
|
+
) -> TokenBudget:
|
|
193
|
+
"""
|
|
194
|
+
Suggest token budget based on task description and history.
|
|
195
|
+
|
|
196
|
+
Args:
|
|
197
|
+
task_description: Description of the task
|
|
198
|
+
tool_name: Specific tool being used (optional)
|
|
199
|
+
|
|
200
|
+
Returns:
|
|
201
|
+
TokenBudget recommendation
|
|
202
|
+
"""
|
|
203
|
+
# First, try to get historical data for similar tasks
|
|
204
|
+
historical = self._get_historical_token_usage(task_description, tool_name)
|
|
205
|
+
|
|
206
|
+
if historical:
|
|
207
|
+
return TokenBudget(
|
|
208
|
+
recommended=int(historical["avg_tokens"] * 1.2), # 20% buffer
|
|
209
|
+
minimum=int(historical["min_tokens"]),
|
|
210
|
+
maximum=int(historical["max_tokens"] * 1.1),
|
|
211
|
+
confidence=0.8,
|
|
212
|
+
reasoning=f"Based on {historical['count']} similar tasks",
|
|
213
|
+
based_on_history=True,
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
# Fall back to heuristics
|
|
217
|
+
complexity = self._estimate_complexity(task_description)
|
|
218
|
+
base_budget = self.DEFAULT_BUDGETS.get(complexity, 5000)
|
|
219
|
+
|
|
220
|
+
# Adjust for specific tools
|
|
221
|
+
if tool_name:
|
|
222
|
+
tool_multipliers = {
|
|
223
|
+
"Edit": 1.2, # Edits often need more context
|
|
224
|
+
"Write": 1.3, # Writes can be lengthy
|
|
225
|
+
"Bash": 0.8, # Bash commands typically shorter
|
|
226
|
+
"Read": 0.5, # Reads are cheap
|
|
227
|
+
"Grep": 0.3, # Grep is very cheap
|
|
228
|
+
"Task": 2.0, # Task delegations need more budget
|
|
229
|
+
}
|
|
230
|
+
multiplier = tool_multipliers.get(tool_name, 1.0)
|
|
231
|
+
base_budget = int(base_budget * multiplier)
|
|
232
|
+
|
|
233
|
+
return TokenBudget(
|
|
234
|
+
recommended=base_budget,
|
|
235
|
+
minimum=int(base_budget * 0.5),
|
|
236
|
+
maximum=int(base_budget * 2.0),
|
|
237
|
+
confidence=0.6,
|
|
238
|
+
reasoning=f"Estimated {complexity} complexity task",
|
|
239
|
+
based_on_history=False,
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
def _get_historical_token_usage(
|
|
243
|
+
self,
|
|
244
|
+
task_description: str,
|
|
245
|
+
tool_name: str | None = None,
|
|
246
|
+
) -> dict[str, Any] | None:
|
|
247
|
+
"""
|
|
248
|
+
Get historical token usage for similar tasks.
|
|
249
|
+
|
|
250
|
+
Args:
|
|
251
|
+
task_description: Task description to match
|
|
252
|
+
tool_name: Optional tool filter
|
|
253
|
+
|
|
254
|
+
Returns:
|
|
255
|
+
Dict with avg, min, max tokens and count, or None if insufficient data
|
|
256
|
+
"""
|
|
257
|
+
conn = self._get_connection()
|
|
258
|
+
cursor = conn.cursor()
|
|
259
|
+
|
|
260
|
+
try:
|
|
261
|
+
# Build query based on available filters
|
|
262
|
+
if tool_name:
|
|
263
|
+
cursor.execute(
|
|
264
|
+
"""
|
|
265
|
+
SELECT
|
|
266
|
+
AVG(cost_tokens) as avg_tokens,
|
|
267
|
+
MIN(cost_tokens) as min_tokens,
|
|
268
|
+
MAX(cost_tokens) as max_tokens,
|
|
269
|
+
COUNT(*) as count
|
|
270
|
+
FROM agent_events
|
|
271
|
+
WHERE tool_name = ?
|
|
272
|
+
AND cost_tokens > 0
|
|
273
|
+
AND timestamp > datetime('now', '-30 days')
|
|
274
|
+
""",
|
|
275
|
+
(tool_name,),
|
|
276
|
+
)
|
|
277
|
+
else:
|
|
278
|
+
cursor.execute(
|
|
279
|
+
"""
|
|
280
|
+
SELECT
|
|
281
|
+
AVG(cost_tokens) as avg_tokens,
|
|
282
|
+
MIN(cost_tokens) as min_tokens,
|
|
283
|
+
MAX(cost_tokens) as max_tokens,
|
|
284
|
+
COUNT(*) as count
|
|
285
|
+
FROM agent_events
|
|
286
|
+
WHERE cost_tokens > 0
|
|
287
|
+
AND timestamp > datetime('now', '-30 days')
|
|
288
|
+
"""
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
row = cursor.fetchone()
|
|
292
|
+
if row and row["count"] >= 5: # Need at least 5 samples
|
|
293
|
+
return {
|
|
294
|
+
"avg_tokens": row["avg_tokens"],
|
|
295
|
+
"min_tokens": row["min_tokens"],
|
|
296
|
+
"max_tokens": row["max_tokens"],
|
|
297
|
+
"count": row["count"],
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
return None
|
|
301
|
+
|
|
302
|
+
except sqlite3.Error as e:
|
|
303
|
+
logger.warning(f"Error getting historical token usage: {e}")
|
|
304
|
+
return None
|
|
305
|
+
|
|
306
|
+
def _estimate_complexity(self, task_description: str) -> str:
|
|
307
|
+
"""
|
|
308
|
+
Estimate task complexity from description.
|
|
309
|
+
|
|
310
|
+
Args:
|
|
311
|
+
task_description: Task description
|
|
312
|
+
|
|
313
|
+
Returns:
|
|
314
|
+
Complexity level: simple, medium, complex, very_complex
|
|
315
|
+
"""
|
|
316
|
+
task_lower = task_description.lower()
|
|
317
|
+
|
|
318
|
+
# Very complex indicators
|
|
319
|
+
very_complex_indicators = [
|
|
320
|
+
"refactor",
|
|
321
|
+
"architecture",
|
|
322
|
+
"redesign",
|
|
323
|
+
"migrate",
|
|
324
|
+
"rewrite",
|
|
325
|
+
"security audit",
|
|
326
|
+
"performance optimize",
|
|
327
|
+
]
|
|
328
|
+
if any(ind in task_lower for ind in very_complex_indicators):
|
|
329
|
+
return "very_complex"
|
|
330
|
+
|
|
331
|
+
# Complex indicators
|
|
332
|
+
complex_indicators = [
|
|
333
|
+
"implement",
|
|
334
|
+
"create",
|
|
335
|
+
"build",
|
|
336
|
+
"design",
|
|
337
|
+
"integrate",
|
|
338
|
+
"debug",
|
|
339
|
+
"analyze",
|
|
340
|
+
"complex",
|
|
341
|
+
]
|
|
342
|
+
if any(ind in task_lower for ind in complex_indicators):
|
|
343
|
+
return "complex"
|
|
344
|
+
|
|
345
|
+
# Simple indicators
|
|
346
|
+
simple_indicators = [
|
|
347
|
+
"fix typo",
|
|
348
|
+
"rename",
|
|
349
|
+
"format",
|
|
350
|
+
"lint",
|
|
351
|
+
"move",
|
|
352
|
+
"copy",
|
|
353
|
+
"delete",
|
|
354
|
+
"list",
|
|
355
|
+
"show",
|
|
356
|
+
"status",
|
|
357
|
+
]
|
|
358
|
+
if any(ind in task_lower for ind in simple_indicators):
|
|
359
|
+
return "simple"
|
|
360
|
+
|
|
361
|
+
# Default to medium
|
|
362
|
+
return "medium"
|
|
363
|
+
|
|
364
|
+
def suggest_parallelization(
|
|
365
|
+
self,
|
|
366
|
+
tasks: list[dict[str, Any]],
|
|
367
|
+
) -> ParallelizationStrategy:
|
|
368
|
+
"""
|
|
369
|
+
Calculate optimal parallelization strategy for a set of tasks.
|
|
370
|
+
|
|
371
|
+
Analyzes task dependencies and groups independent tasks for parallel execution.
|
|
372
|
+
|
|
373
|
+
Args:
|
|
374
|
+
tasks: List of task dictionaries with 'id', 'description', 'dependencies'
|
|
375
|
+
|
|
376
|
+
Returns:
|
|
377
|
+
ParallelizationStrategy with grouped tasks
|
|
378
|
+
"""
|
|
379
|
+
if not tasks:
|
|
380
|
+
return ParallelizationStrategy(
|
|
381
|
+
groups=[],
|
|
382
|
+
estimated_speedup=1.0,
|
|
383
|
+
reasoning="No tasks provided",
|
|
384
|
+
)
|
|
385
|
+
|
|
386
|
+
# Build dependency graph
|
|
387
|
+
task_ids = {t.get("id", str(i)): t for i, t in enumerate(tasks)}
|
|
388
|
+
dependencies: dict[str, set[str]] = {}
|
|
389
|
+
|
|
390
|
+
for task in tasks:
|
|
391
|
+
task_id = task.get("id", str(tasks.index(task)))
|
|
392
|
+
deps = set(task.get("dependencies", []))
|
|
393
|
+
dependencies[task_id] = deps
|
|
394
|
+
|
|
395
|
+
# Topological sort into parallel groups
|
|
396
|
+
groups: list[list[str]] = []
|
|
397
|
+
remaining = set(task_ids.keys())
|
|
398
|
+
completed: set[str] = set()
|
|
399
|
+
|
|
400
|
+
while remaining:
|
|
401
|
+
# Find tasks with all dependencies satisfied
|
|
402
|
+
ready = []
|
|
403
|
+
for task_id in remaining:
|
|
404
|
+
if dependencies[task_id].issubset(completed):
|
|
405
|
+
ready.append(task_id)
|
|
406
|
+
|
|
407
|
+
if not ready:
|
|
408
|
+
# Circular dependency - just add remaining tasks
|
|
409
|
+
groups.append(list(remaining))
|
|
410
|
+
break
|
|
411
|
+
|
|
412
|
+
groups.append(ready)
|
|
413
|
+
completed.update(ready)
|
|
414
|
+
remaining -= set(ready)
|
|
415
|
+
|
|
416
|
+
# Calculate estimated speedup
|
|
417
|
+
sequential_time = len(tasks)
|
|
418
|
+
parallel_time = len(groups)
|
|
419
|
+
speedup = sequential_time / parallel_time if parallel_time > 0 else 1.0
|
|
420
|
+
|
|
421
|
+
# Estimate cost (sum of individual task budgets)
|
|
422
|
+
total_cost = 0
|
|
423
|
+
for task in tasks:
|
|
424
|
+
desc = task.get("description", "")
|
|
425
|
+
budget = self.suggest_token_budget(desc)
|
|
426
|
+
total_cost += budget.recommended
|
|
427
|
+
|
|
428
|
+
return ParallelizationStrategy(
|
|
429
|
+
groups=groups,
|
|
430
|
+
estimated_speedup=speedup,
|
|
431
|
+
estimated_cost=total_cost,
|
|
432
|
+
reasoning=f"Grouped {len(tasks)} tasks into {len(groups)} parallel batches",
|
|
433
|
+
)
|
|
434
|
+
|
|
435
|
+
def choose_model(
|
|
436
|
+
self,
|
|
437
|
+
task_description: str,
|
|
438
|
+
budget_constraint: int | None = None,
|
|
439
|
+
quality_threshold: float = 0.7,
|
|
440
|
+
) -> ModelRecommendation:
|
|
441
|
+
"""
|
|
442
|
+
Choose the most cost-effective model for a task.
|
|
443
|
+
|
|
444
|
+
Balances cost, quality, and task requirements.
|
|
445
|
+
|
|
446
|
+
Args:
|
|
447
|
+
task_description: Description of the task
|
|
448
|
+
budget_constraint: Maximum token budget (optional)
|
|
449
|
+
quality_threshold: Minimum acceptable quality (0-1)
|
|
450
|
+
|
|
451
|
+
Returns:
|
|
452
|
+
ModelRecommendation with suggested model
|
|
453
|
+
"""
|
|
454
|
+
complexity = self._estimate_complexity(task_description)
|
|
455
|
+
|
|
456
|
+
# Map complexity to model tier
|
|
457
|
+
complexity_model_map = {
|
|
458
|
+
"simple": (ModelTier.HAIKU, 0.7),
|
|
459
|
+
"medium": (ModelTier.SONNET, 0.85),
|
|
460
|
+
"complex": (ModelTier.SONNET, 0.9),
|
|
461
|
+
"very_complex": (ModelTier.OPUS, 0.95),
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
recommended, expected_quality = complexity_model_map.get(
|
|
465
|
+
complexity, (ModelTier.SONNET, 0.85)
|
|
466
|
+
)
|
|
467
|
+
|
|
468
|
+
# Check if we can use a cheaper model
|
|
469
|
+
alternative = None
|
|
470
|
+
if recommended == ModelTier.OPUS and quality_threshold <= 0.9:
|
|
471
|
+
alternative = ModelTier.SONNET
|
|
472
|
+
elif recommended == ModelTier.SONNET and quality_threshold <= 0.7:
|
|
473
|
+
alternative = ModelTier.HAIKU
|
|
474
|
+
|
|
475
|
+
# Calculate estimated cost
|
|
476
|
+
base_budget = self.DEFAULT_BUDGETS.get(complexity, 5000)
|
|
477
|
+
cost_multiplier = self.MODEL_COST_MULTIPLIERS[recommended]
|
|
478
|
+
estimated_cost = int(base_budget * cost_multiplier)
|
|
479
|
+
|
|
480
|
+
# Check budget constraint
|
|
481
|
+
if budget_constraint and estimated_cost > budget_constraint:
|
|
482
|
+
# Try to fit within budget with cheaper model
|
|
483
|
+
if alternative:
|
|
484
|
+
alt_cost = int(base_budget * self.MODEL_COST_MULTIPLIERS[alternative])
|
|
485
|
+
if alt_cost <= budget_constraint:
|
|
486
|
+
recommended = alternative
|
|
487
|
+
estimated_cost = alt_cost
|
|
488
|
+
expected_quality = expected_quality * 0.85
|
|
489
|
+
|
|
490
|
+
reasoning = (
|
|
491
|
+
f"{complexity.replace('_', ' ').title()} task - "
|
|
492
|
+
f"{recommended.value} recommended for balance of cost and quality"
|
|
493
|
+
)
|
|
494
|
+
|
|
495
|
+
return ModelRecommendation(
|
|
496
|
+
recommended_model=recommended,
|
|
497
|
+
alternative_model=alternative,
|
|
498
|
+
confidence=0.75,
|
|
499
|
+
reasoning=reasoning,
|
|
500
|
+
estimated_cost=estimated_cost,
|
|
501
|
+
estimated_quality=expected_quality,
|
|
502
|
+
)
|
|
503
|
+
|
|
504
|
+
def identify_cache_opportunities(
|
|
505
|
+
self,
|
|
506
|
+
recent_tools: list[dict[str, Any]],
|
|
507
|
+
) -> list[dict[str, Any]]:
|
|
508
|
+
"""
|
|
509
|
+
Identify caching opportunities to save tokens.
|
|
510
|
+
|
|
511
|
+
Looks for repeated patterns that could benefit from caching.
|
|
512
|
+
|
|
513
|
+
Args:
|
|
514
|
+
recent_tools: List of recent tool calls with input/output
|
|
515
|
+
|
|
516
|
+
Returns:
|
|
517
|
+
List of cache opportunity recommendations
|
|
518
|
+
"""
|
|
519
|
+
opportunities: list[dict[str, Any]] = []
|
|
520
|
+
|
|
521
|
+
if len(recent_tools) < 3:
|
|
522
|
+
return opportunities
|
|
523
|
+
|
|
524
|
+
# Track repeated tool+input combinations
|
|
525
|
+
seen: dict[str, list[int]] = {}
|
|
526
|
+
|
|
527
|
+
for i, tool in enumerate(recent_tools):
|
|
528
|
+
tool_name = tool.get("tool_name", "")
|
|
529
|
+
# Create a simple key from tool name and input hash
|
|
530
|
+
input_key = str(tool.get("input", {}))[:100]
|
|
531
|
+
key = f"{tool_name}:{hash(input_key)}"
|
|
532
|
+
|
|
533
|
+
if key not in seen:
|
|
534
|
+
seen[key] = []
|
|
535
|
+
seen[key].append(i)
|
|
536
|
+
|
|
537
|
+
# Find repeated calls
|
|
538
|
+
for key, indices in seen.items():
|
|
539
|
+
if len(indices) >= 2:
|
|
540
|
+
tool_name = key.split(":")[0]
|
|
541
|
+
opportunities.append(
|
|
542
|
+
{
|
|
543
|
+
"tool_name": tool_name,
|
|
544
|
+
"occurrences": len(indices),
|
|
545
|
+
"suggestion": f"Cache {tool_name} results - called {len(indices)} times with same input",
|
|
546
|
+
"estimated_savings": len(indices) - 1, # Could save N-1 calls
|
|
547
|
+
}
|
|
548
|
+
)
|
|
549
|
+
|
|
550
|
+
return opportunities
|
|
551
|
+
|
|
552
|
+
def get_cost_summary(
|
|
553
|
+
self,
|
|
554
|
+
session_id: str,
|
|
555
|
+
) -> dict[str, Any]:
|
|
556
|
+
"""
|
|
557
|
+
Get cost summary for a session.
|
|
558
|
+
|
|
559
|
+
Args:
|
|
560
|
+
session_id: Session to summarize
|
|
561
|
+
|
|
562
|
+
Returns:
|
|
563
|
+
Dictionary with cost breakdown
|
|
564
|
+
"""
|
|
565
|
+
conn = self._get_connection()
|
|
566
|
+
cursor = conn.cursor()
|
|
567
|
+
|
|
568
|
+
try:
|
|
569
|
+
cursor.execute(
|
|
570
|
+
"""
|
|
571
|
+
SELECT
|
|
572
|
+
tool_name,
|
|
573
|
+
COUNT(*) as call_count,
|
|
574
|
+
SUM(cost_tokens) as total_tokens,
|
|
575
|
+
AVG(cost_tokens) as avg_tokens,
|
|
576
|
+
model
|
|
577
|
+
FROM agent_events
|
|
578
|
+
WHERE session_id = ?
|
|
579
|
+
AND event_type = 'tool_call'
|
|
580
|
+
GROUP BY tool_name, model
|
|
581
|
+
ORDER BY total_tokens DESC
|
|
582
|
+
""",
|
|
583
|
+
(session_id,),
|
|
584
|
+
)
|
|
585
|
+
|
|
586
|
+
breakdown = []
|
|
587
|
+
total_tokens = 0
|
|
588
|
+
|
|
589
|
+
for row in cursor.fetchall():
|
|
590
|
+
tokens = row["total_tokens"] or 0
|
|
591
|
+
total_tokens += tokens
|
|
592
|
+
breakdown.append(
|
|
593
|
+
{
|
|
594
|
+
"tool_name": row["tool_name"],
|
|
595
|
+
"model": row["model"] or "unknown",
|
|
596
|
+
"call_count": row["call_count"],
|
|
597
|
+
"total_tokens": tokens,
|
|
598
|
+
"avg_tokens": row["avg_tokens"] or 0,
|
|
599
|
+
}
|
|
600
|
+
)
|
|
601
|
+
|
|
602
|
+
return {
|
|
603
|
+
"session_id": session_id,
|
|
604
|
+
"total_tokens": total_tokens,
|
|
605
|
+
"breakdown": breakdown,
|
|
606
|
+
"most_expensive_tool": breakdown[0]["tool_name"] if breakdown else None,
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
except sqlite3.Error as e:
|
|
610
|
+
logger.error(f"Error getting cost summary: {e}")
|
|
611
|
+
return {"session_id": session_id, "total_tokens": 0, "breakdown": []}
|