dtSpark 1.0.4__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.
- dtSpark/__init__.py +0 -0
- dtSpark/_description.txt +1 -0
- dtSpark/_full_name.txt +1 -0
- dtSpark/_licence.txt +21 -0
- dtSpark/_metadata.yaml +6 -0
- dtSpark/_name.txt +1 -0
- dtSpark/_version.txt +1 -0
- dtSpark/aws/__init__.py +7 -0
- dtSpark/aws/authentication.py +296 -0
- dtSpark/aws/bedrock.py +578 -0
- dtSpark/aws/costs.py +318 -0
- dtSpark/aws/pricing.py +580 -0
- dtSpark/cli_interface.py +2645 -0
- dtSpark/conversation_manager.py +3050 -0
- dtSpark/core/__init__.py +12 -0
- dtSpark/core/application.py +3355 -0
- dtSpark/core/context_compaction.py +735 -0
- dtSpark/daemon/__init__.py +104 -0
- dtSpark/daemon/__main__.py +10 -0
- dtSpark/daemon/action_monitor.py +213 -0
- dtSpark/daemon/daemon_app.py +730 -0
- dtSpark/daemon/daemon_manager.py +289 -0
- dtSpark/daemon/execution_coordinator.py +194 -0
- dtSpark/daemon/pid_file.py +169 -0
- dtSpark/database/__init__.py +482 -0
- dtSpark/database/autonomous_actions.py +1191 -0
- dtSpark/database/backends.py +329 -0
- dtSpark/database/connection.py +122 -0
- dtSpark/database/conversations.py +520 -0
- dtSpark/database/credential_prompt.py +218 -0
- dtSpark/database/files.py +205 -0
- dtSpark/database/mcp_ops.py +355 -0
- dtSpark/database/messages.py +161 -0
- dtSpark/database/schema.py +673 -0
- dtSpark/database/tool_permissions.py +186 -0
- dtSpark/database/usage.py +167 -0
- dtSpark/files/__init__.py +4 -0
- dtSpark/files/manager.py +322 -0
- dtSpark/launch.py +39 -0
- dtSpark/limits/__init__.py +10 -0
- dtSpark/limits/costs.py +296 -0
- dtSpark/limits/tokens.py +342 -0
- dtSpark/llm/__init__.py +17 -0
- dtSpark/llm/anthropic_direct.py +446 -0
- dtSpark/llm/base.py +146 -0
- dtSpark/llm/context_limits.py +438 -0
- dtSpark/llm/manager.py +177 -0
- dtSpark/llm/ollama.py +578 -0
- dtSpark/mcp_integration/__init__.py +5 -0
- dtSpark/mcp_integration/manager.py +653 -0
- dtSpark/mcp_integration/tool_selector.py +225 -0
- dtSpark/resources/config.yaml.template +631 -0
- dtSpark/safety/__init__.py +22 -0
- dtSpark/safety/llm_service.py +111 -0
- dtSpark/safety/patterns.py +229 -0
- dtSpark/safety/prompt_inspector.py +442 -0
- dtSpark/safety/violation_logger.py +346 -0
- dtSpark/scheduler/__init__.py +20 -0
- dtSpark/scheduler/creation_tools.py +599 -0
- dtSpark/scheduler/execution_queue.py +159 -0
- dtSpark/scheduler/executor.py +1152 -0
- dtSpark/scheduler/manager.py +395 -0
- dtSpark/tools/__init__.py +4 -0
- dtSpark/tools/builtin.py +833 -0
- dtSpark/web/__init__.py +20 -0
- dtSpark/web/auth.py +152 -0
- dtSpark/web/dependencies.py +37 -0
- dtSpark/web/endpoints/__init__.py +17 -0
- dtSpark/web/endpoints/autonomous_actions.py +1125 -0
- dtSpark/web/endpoints/chat.py +621 -0
- dtSpark/web/endpoints/conversations.py +353 -0
- dtSpark/web/endpoints/main_menu.py +547 -0
- dtSpark/web/endpoints/streaming.py +421 -0
- dtSpark/web/server.py +578 -0
- dtSpark/web/session.py +167 -0
- dtSpark/web/ssl_utils.py +195 -0
- dtSpark/web/static/css/dark-theme.css +427 -0
- dtSpark/web/static/js/actions.js +1101 -0
- dtSpark/web/static/js/chat.js +614 -0
- dtSpark/web/static/js/main.js +496 -0
- dtSpark/web/static/js/sse-client.js +242 -0
- dtSpark/web/templates/actions.html +408 -0
- dtSpark/web/templates/base.html +93 -0
- dtSpark/web/templates/chat.html +814 -0
- dtSpark/web/templates/conversations.html +350 -0
- dtSpark/web/templates/goodbye.html +81 -0
- dtSpark/web/templates/login.html +90 -0
- dtSpark/web/templates/main_menu.html +983 -0
- dtSpark/web/templates/new_conversation.html +191 -0
- dtSpark/web/web_interface.py +137 -0
- dtspark-1.0.4.dist-info/METADATA +187 -0
- dtspark-1.0.4.dist-info/RECORD +96 -0
- dtspark-1.0.4.dist-info/WHEEL +5 -0
- dtspark-1.0.4.dist-info/entry_points.txt +3 -0
- dtspark-1.0.4.dist-info/licenses/LICENSE +21 -0
- dtspark-1.0.4.dist-info/top_level.txt +1 -0
dtSpark/aws/costs.py
ADDED
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Cost tracking module for AWS Bedrock usage.
|
|
3
|
+
|
|
4
|
+
This module provides functionality for:
|
|
5
|
+
- Retrieving Bedrock usage costs from AWS Cost Explorer
|
|
6
|
+
- Breaking down costs by model/usage type
|
|
7
|
+
- Reporting on monthly and daily costs
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import logging
|
|
11
|
+
from datetime import datetime, timedelta
|
|
12
|
+
from typing import Dict, Optional, List
|
|
13
|
+
from botocore.exceptions import ClientError
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class CostTracker:
|
|
17
|
+
"""Tracks AWS Bedrock costs using Cost Explorer API."""
|
|
18
|
+
|
|
19
|
+
def __init__(self, cost_explorer_client):
|
|
20
|
+
"""
|
|
21
|
+
Initialise the cost tracker.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
cost_explorer_client: Boto3 Cost Explorer client
|
|
25
|
+
"""
|
|
26
|
+
self.ce_client = cost_explorer_client
|
|
27
|
+
self.has_permissions = None # Cache permission check
|
|
28
|
+
|
|
29
|
+
def check_permissions(self) -> bool:
|
|
30
|
+
"""
|
|
31
|
+
Check if the authenticated user has Cost Explorer permissions.
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
True if user has permissions, False otherwise
|
|
35
|
+
"""
|
|
36
|
+
if self.has_permissions is not None:
|
|
37
|
+
return self.has_permissions
|
|
38
|
+
|
|
39
|
+
try:
|
|
40
|
+
# Try a minimal query to check permissions
|
|
41
|
+
end_date = datetime.now().date()
|
|
42
|
+
start_date = end_date - timedelta(days=1)
|
|
43
|
+
|
|
44
|
+
self.ce_client.get_cost_and_usage(
|
|
45
|
+
TimePeriod={
|
|
46
|
+
'Start': start_date.strftime('%Y-%m-%d'),
|
|
47
|
+
'End': end_date.strftime('%Y-%m-%d')
|
|
48
|
+
},
|
|
49
|
+
Granularity='DAILY',
|
|
50
|
+
Metrics=['UnblendedCost']
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
self.has_permissions = True
|
|
54
|
+
logging.info("Cost Explorer permissions verified")
|
|
55
|
+
return True
|
|
56
|
+
|
|
57
|
+
except ClientError as e:
|
|
58
|
+
error_code = e.response.get('Error', {}).get('Code', '')
|
|
59
|
+
if error_code in ['AccessDeniedException', 'UnauthorizedException']:
|
|
60
|
+
logging.info("User does not have Cost Explorer permissions")
|
|
61
|
+
self.has_permissions = False
|
|
62
|
+
return False
|
|
63
|
+
else:
|
|
64
|
+
logging.warning(f"Error checking Cost Explorer permissions: {e}")
|
|
65
|
+
self.has_permissions = False
|
|
66
|
+
return False
|
|
67
|
+
except Exception as e:
|
|
68
|
+
logging.warning(f"Unexpected error checking permissions: {e}")
|
|
69
|
+
self.has_permissions = False
|
|
70
|
+
return False
|
|
71
|
+
|
|
72
|
+
def get_bedrock_costs(self) -> Optional[Dict]:
|
|
73
|
+
"""
|
|
74
|
+
Retrieve Bedrock costs for current month, last month and last 24 hours.
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
Dictionary with cost information, or None if unavailable
|
|
78
|
+
"""
|
|
79
|
+
if not self.check_permissions():
|
|
80
|
+
return None
|
|
81
|
+
|
|
82
|
+
try:
|
|
83
|
+
# Calculate date ranges
|
|
84
|
+
today = datetime.now().date()
|
|
85
|
+
yesterday = today - timedelta(days=1)
|
|
86
|
+
|
|
87
|
+
# Current month (from 1st of this month to today)
|
|
88
|
+
first_day_this_month = today.replace(day=1)
|
|
89
|
+
|
|
90
|
+
# Last month (from 1st to last day of previous month)
|
|
91
|
+
last_day_last_month = first_day_this_month - timedelta(days=1)
|
|
92
|
+
first_day_last_month = last_day_last_month.replace(day=1)
|
|
93
|
+
|
|
94
|
+
# Get current month's costs (month-to-date)
|
|
95
|
+
current_month_costs = self._get_costs_for_period(
|
|
96
|
+
first_day_this_month,
|
|
97
|
+
today + timedelta(days=1), # End date is exclusive, so add 1 day to include today
|
|
98
|
+
'Current Month'
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
# Get last month's costs
|
|
102
|
+
last_month_costs = self._get_costs_for_period(
|
|
103
|
+
first_day_last_month,
|
|
104
|
+
first_day_this_month, # End date is exclusive
|
|
105
|
+
'Last Month'
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
# Get last 24 hours costs
|
|
109
|
+
last_24h_costs = self._get_costs_for_period(
|
|
110
|
+
yesterday,
|
|
111
|
+
today,
|
|
112
|
+
'Last 24 Hours'
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
return {
|
|
116
|
+
'current_month': current_month_costs,
|
|
117
|
+
'last_month': last_month_costs,
|
|
118
|
+
'last_24h': last_24h_costs,
|
|
119
|
+
'currency': 'USD' # Cost Explorer returns USD by default
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
except Exception as e:
|
|
123
|
+
logging.error(f"Error retrieving Bedrock costs: {e}")
|
|
124
|
+
return None
|
|
125
|
+
|
|
126
|
+
def _get_costs_for_period(self, start_date, end_date, period_name: str) -> Dict:
|
|
127
|
+
"""
|
|
128
|
+
Get Bedrock costs for a specific time period with model breakdown.
|
|
129
|
+
|
|
130
|
+
Args:
|
|
131
|
+
start_date: Start date (inclusive)
|
|
132
|
+
end_date: End date (exclusive)
|
|
133
|
+
period_name: Human-readable period name for logging
|
|
134
|
+
|
|
135
|
+
Returns:
|
|
136
|
+
Dictionary with total cost and breakdown by usage type
|
|
137
|
+
"""
|
|
138
|
+
try:
|
|
139
|
+
# Query 1: Get total Bedrock costs
|
|
140
|
+
# Note: AWS Cost Explorer uses model-specific service names for Bedrock
|
|
141
|
+
# We search for services containing "Amazon Bedrock Edition"
|
|
142
|
+
response = self.ce_client.get_cost_and_usage(
|
|
143
|
+
TimePeriod={
|
|
144
|
+
'Start': start_date.strftime('%Y-%m-%d'),
|
|
145
|
+
'End': end_date.strftime('%Y-%m-%d')
|
|
146
|
+
},
|
|
147
|
+
Granularity='MONTHLY' if period_name in ['Last Month', 'Current Month'] else 'DAILY',
|
|
148
|
+
Metrics=['UnblendedCost'],
|
|
149
|
+
GroupBy=[{
|
|
150
|
+
'Type': 'DIMENSION',
|
|
151
|
+
'Key': 'SERVICE'
|
|
152
|
+
}]
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
# Extract total cost and breakdown by filtering for Bedrock services
|
|
156
|
+
# AWS uses service names like "Claude 3.5 Sonnet (Amazon Bedrock Edition)"
|
|
157
|
+
total_cost = 0.0
|
|
158
|
+
breakdown = {}
|
|
159
|
+
|
|
160
|
+
if response.get('ResultsByTime'):
|
|
161
|
+
for result in response['ResultsByTime']:
|
|
162
|
+
for group in result.get('Groups', []):
|
|
163
|
+
service_name = group.get('Keys', [''])[0]
|
|
164
|
+
|
|
165
|
+
# Filter for Bedrock services (contain "Amazon Bedrock Edition")
|
|
166
|
+
if 'Amazon Bedrock Edition' in service_name or 'Bedrock' in service_name:
|
|
167
|
+
amount = float(group.get('Metrics', {}).get('UnblendedCost', {}).get('Amount', '0'))
|
|
168
|
+
|
|
169
|
+
if amount > 0:
|
|
170
|
+
total_cost += amount
|
|
171
|
+
|
|
172
|
+
# Extract model name from service name
|
|
173
|
+
# Example: "Claude 3.5 Sonnet (Amazon Bedrock Edition)" -> "Claude 3.5 Sonnet"
|
|
174
|
+
model_name = service_name.replace(' (Amazon Bedrock Edition)', '').strip()
|
|
175
|
+
|
|
176
|
+
if model_name in breakdown:
|
|
177
|
+
breakdown[model_name] += amount
|
|
178
|
+
else:
|
|
179
|
+
breakdown[model_name] = amount
|
|
180
|
+
|
|
181
|
+
logging.info(f"{period_name} Bedrock costs: ${total_cost:.4f}")
|
|
182
|
+
if breakdown:
|
|
183
|
+
logging.debug(f"{period_name} breakdown: {breakdown}")
|
|
184
|
+
|
|
185
|
+
return {
|
|
186
|
+
'total': total_cost,
|
|
187
|
+
'breakdown': breakdown,
|
|
188
|
+
'period': period_name,
|
|
189
|
+
'start_date': start_date.strftime('%Y-%m-%d'),
|
|
190
|
+
'end_date': end_date.strftime('%Y-%m-%d')
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
except Exception as e:
|
|
194
|
+
logging.error(f"Error getting costs for {period_name}: {e}")
|
|
195
|
+
return {
|
|
196
|
+
'total': 0.0,
|
|
197
|
+
'breakdown': {},
|
|
198
|
+
'period': period_name,
|
|
199
|
+
'error': str(e)
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
def _parse_model_from_usage_type(self, usage_type: str) -> str:
|
|
203
|
+
"""
|
|
204
|
+
Parse model name from AWS usage type string.
|
|
205
|
+
|
|
206
|
+
Usage types typically look like:
|
|
207
|
+
- "APS2-ModelInference-Claude-3-5-Sonnet-v2"
|
|
208
|
+
- "USE1-ModelInference-Titan-Text-Express"
|
|
209
|
+
- "APS2-OnDemand-Throughput"
|
|
210
|
+
|
|
211
|
+
Args:
|
|
212
|
+
usage_type: AWS usage type string
|
|
213
|
+
|
|
214
|
+
Returns:
|
|
215
|
+
Human-readable model name
|
|
216
|
+
"""
|
|
217
|
+
# Remove region prefix (e.g., "APS2-", "USE1-")
|
|
218
|
+
parts = usage_type.split('-')
|
|
219
|
+
|
|
220
|
+
if 'ModelInference' in usage_type:
|
|
221
|
+
# Find the index of ModelInference and take everything after it
|
|
222
|
+
try:
|
|
223
|
+
idx = parts.index('ModelInference')
|
|
224
|
+
model_parts = parts[idx + 1:]
|
|
225
|
+
|
|
226
|
+
# Join and clean up
|
|
227
|
+
model_name = ' '.join(model_parts)
|
|
228
|
+
|
|
229
|
+
# Common model name mappings for readability
|
|
230
|
+
replacements = {
|
|
231
|
+
'Claude 3 5 Sonnet v2': 'Claude 3.5 Sonnet v2',
|
|
232
|
+
'Claude 3 5 Sonnet': 'Claude 3.5 Sonnet',
|
|
233
|
+
'Claude 3 Opus': 'Claude 3 Opus',
|
|
234
|
+
'Claude 3 Haiku': 'Claude 3 Haiku',
|
|
235
|
+
'Titan Text Express': 'Titan Text Express',
|
|
236
|
+
'Titan Text Lite': 'Titan Text Lite',
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
for old, new in replacements.items():
|
|
240
|
+
if old in model_name:
|
|
241
|
+
model_name = new
|
|
242
|
+
break
|
|
243
|
+
|
|
244
|
+
return model_name
|
|
245
|
+
except (ValueError, IndexError):
|
|
246
|
+
pass
|
|
247
|
+
|
|
248
|
+
# If we can't parse it, return the usage type
|
|
249
|
+
return usage_type
|
|
250
|
+
|
|
251
|
+
def format_cost_report(self, costs: Optional[Dict]) -> List[str]:
|
|
252
|
+
"""
|
|
253
|
+
Format cost information into human-readable lines.
|
|
254
|
+
|
|
255
|
+
Args:
|
|
256
|
+
costs: Cost dictionary from get_bedrock_costs()
|
|
257
|
+
|
|
258
|
+
Returns:
|
|
259
|
+
List of formatted strings for display
|
|
260
|
+
"""
|
|
261
|
+
if not costs:
|
|
262
|
+
return ["Financial tracking not available with current user permissions"]
|
|
263
|
+
|
|
264
|
+
lines = []
|
|
265
|
+
currency = costs.get('currency', 'USD')
|
|
266
|
+
|
|
267
|
+
# Current Month
|
|
268
|
+
current_month = costs.get('current_month', {})
|
|
269
|
+
if current_month:
|
|
270
|
+
total = current_month.get('total', 0.0)
|
|
271
|
+
breakdown = current_month.get('breakdown', {})
|
|
272
|
+
period = current_month.get('period', 'Current Month')
|
|
273
|
+
|
|
274
|
+
lines.append(f"{period}: ${total:.2f} {currency}")
|
|
275
|
+
|
|
276
|
+
if breakdown and total > 0:
|
|
277
|
+
# Sort by cost (descending)
|
|
278
|
+
sorted_breakdown = sorted(breakdown.items(), key=lambda x: x[1], reverse=True)
|
|
279
|
+
for model, cost in sorted_breakdown:
|
|
280
|
+
percentage = (cost / total * 100) if total > 0 else 0
|
|
281
|
+
lines.append(f" • {model}: ${cost:.2f} ({percentage:.1f}%)")
|
|
282
|
+
|
|
283
|
+
# Last Month
|
|
284
|
+
last_month = costs.get('last_month', {})
|
|
285
|
+
if last_month:
|
|
286
|
+
total = last_month.get('total', 0.0)
|
|
287
|
+
breakdown = last_month.get('breakdown', {})
|
|
288
|
+
period = last_month.get('period', 'Last Month')
|
|
289
|
+
|
|
290
|
+
lines.append(f"{period}: ${total:.2f} {currency}")
|
|
291
|
+
|
|
292
|
+
if breakdown and total > 0:
|
|
293
|
+
# Sort by cost (descending)
|
|
294
|
+
sorted_breakdown = sorted(breakdown.items(), key=lambda x: x[1], reverse=True)
|
|
295
|
+
for model, cost in sorted_breakdown:
|
|
296
|
+
percentage = (cost / total * 100) if total > 0 else 0
|
|
297
|
+
lines.append(f" • {model}: ${cost:.2f} ({percentage:.1f}%)")
|
|
298
|
+
|
|
299
|
+
# Last 24 Hours
|
|
300
|
+
last_24h = costs.get('last_24h', {})
|
|
301
|
+
if last_24h:
|
|
302
|
+
total = last_24h.get('total', 0.0)
|
|
303
|
+
breakdown = last_24h.get('breakdown', {})
|
|
304
|
+
period = last_24h.get('period', 'Last 24 Hours')
|
|
305
|
+
|
|
306
|
+
lines.append(f"{period}: ${total:.4f} {currency}")
|
|
307
|
+
|
|
308
|
+
if breakdown and total > 0:
|
|
309
|
+
# Sort by cost (descending)
|
|
310
|
+
sorted_breakdown = sorted(breakdown.items(), key=lambda x: x[1], reverse=True)
|
|
311
|
+
for model, cost in sorted_breakdown:
|
|
312
|
+
percentage = (cost / total * 100) if total > 0 else 0
|
|
313
|
+
lines.append(f" • {model}: ${cost:.4f} ({percentage:.1f}%)")
|
|
314
|
+
|
|
315
|
+
if not lines:
|
|
316
|
+
lines.append("No Bedrock usage costs found for the specified periods")
|
|
317
|
+
|
|
318
|
+
return lines
|