@voria/cli 0.0.2
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.
- package/README.md +439 -0
- package/bin/voria +730 -0
- package/docs/ARCHITECTURE.md +419 -0
- package/docs/CHANGELOG.md +189 -0
- package/docs/CONTRIBUTING.md +447 -0
- package/docs/DESIGN_DECISIONS.md +380 -0
- package/docs/DEVELOPMENT.md +535 -0
- package/docs/EXAMPLES.md +434 -0
- package/docs/INSTALL.md +335 -0
- package/docs/IPC_PROTOCOL.md +310 -0
- package/docs/LLM_INTEGRATION.md +416 -0
- package/docs/MODULES.md +470 -0
- package/docs/PERFORMANCE.md +346 -0
- package/docs/PLUGINS.md +432 -0
- package/docs/QUICKSTART.md +184 -0
- package/docs/README.md +133 -0
- package/docs/ROADMAP.md +346 -0
- package/docs/SECURITY.md +334 -0
- package/docs/TROUBLESHOOTING.md +565 -0
- package/docs/USER_GUIDE.md +700 -0
- package/package.json +63 -0
- package/python/voria/__init__.py +8 -0
- package/python/voria/__pycache__/__init__.cpython-312.pyc +0 -0
- package/python/voria/__pycache__/engine.cpython-312.pyc +0 -0
- package/python/voria/core/__init__.py +1 -0
- package/python/voria/core/__pycache__/__init__.cpython-312.pyc +0 -0
- package/python/voria/core/__pycache__/setup.cpython-312.pyc +0 -0
- package/python/voria/core/agent/__init__.py +9 -0
- package/python/voria/core/agent/__pycache__/__init__.cpython-312.pyc +0 -0
- package/python/voria/core/agent/__pycache__/loop.cpython-312.pyc +0 -0
- package/python/voria/core/agent/loop.py +343 -0
- package/python/voria/core/executor/__init__.py +19 -0
- package/python/voria/core/executor/__pycache__/__init__.cpython-312.pyc +0 -0
- package/python/voria/core/executor/__pycache__/executor.cpython-312.pyc +0 -0
- package/python/voria/core/executor/executor.py +431 -0
- package/python/voria/core/github/__init__.py +33 -0
- package/python/voria/core/github/__pycache__/__init__.cpython-312.pyc +0 -0
- package/python/voria/core/github/__pycache__/client.cpython-312.pyc +0 -0
- package/python/voria/core/github/client.py +438 -0
- package/python/voria/core/llm/__init__.py +55 -0
- package/python/voria/core/llm/__pycache__/__init__.cpython-312.pyc +0 -0
- package/python/voria/core/llm/__pycache__/base.cpython-312.pyc +0 -0
- package/python/voria/core/llm/__pycache__/claude_provider.cpython-312.pyc +0 -0
- package/python/voria/core/llm/__pycache__/gemini_provider.cpython-312.pyc +0 -0
- package/python/voria/core/llm/__pycache__/modal_provider.cpython-312.pyc +0 -0
- package/python/voria/core/llm/__pycache__/model_discovery.cpython-312.pyc +0 -0
- package/python/voria/core/llm/__pycache__/openai_provider.cpython-312.pyc +0 -0
- package/python/voria/core/llm/base.py +152 -0
- package/python/voria/core/llm/claude_provider.py +188 -0
- package/python/voria/core/llm/gemini_provider.py +148 -0
- package/python/voria/core/llm/modal_provider.py +228 -0
- package/python/voria/core/llm/model_discovery.py +289 -0
- package/python/voria/core/llm/openai_provider.py +146 -0
- package/python/voria/core/patcher/__init__.py +9 -0
- package/python/voria/core/patcher/__pycache__/__init__.cpython-312.pyc +0 -0
- package/python/voria/core/patcher/__pycache__/patcher.cpython-312.pyc +0 -0
- package/python/voria/core/patcher/patcher.py +375 -0
- package/python/voria/core/planner/__init__.py +1 -0
- package/python/voria/core/setup.py +201 -0
- package/python/voria/core/token_manager/__init__.py +29 -0
- package/python/voria/core/token_manager/__pycache__/__init__.cpython-312.pyc +0 -0
- package/python/voria/core/token_manager/__pycache__/manager.cpython-312.pyc +0 -0
- package/python/voria/core/token_manager/manager.py +241 -0
- package/python/voria/engine.py +1185 -0
- package/python/voria/plugins/__init__.py +1 -0
- package/python/voria/plugins/python/__init__.py +1 -0
- package/python/voria/plugins/typescript/__init__.py +1 -0
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
"""Token Usage Management Module
|
|
2
|
+
|
|
3
|
+
Tracks LLM token usage and costs across all providers.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import logging
|
|
7
|
+
from typing import Dict, Any
|
|
8
|
+
from dataclasses import dataclass, field
|
|
9
|
+
from datetime import datetime
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# Provider pricing (per 1M tokens)
|
|
15
|
+
PRICING = {
|
|
16
|
+
"modal": {
|
|
17
|
+
"zai-org/GLM-5.1-FP8": {"input": 0.0, "output": 0.0} # Free until April 30th
|
|
18
|
+
},
|
|
19
|
+
"openai": {
|
|
20
|
+
"gpt-4": {"input": 0.03, "output": 0.06},
|
|
21
|
+
"gpt-3.5-turbo": {"input": 0.0005, "output": 0.0015},
|
|
22
|
+
},
|
|
23
|
+
"gemini": {
|
|
24
|
+
"gemini-pro": {"input": 0.0005, "output": 0.0015},
|
|
25
|
+
},
|
|
26
|
+
"claude": {
|
|
27
|
+
"claude-3-opus-20240229": {"input": 0.015, "output": 0.075},
|
|
28
|
+
"claude-3-sonnet-20240229": {"input": 0.003, "output": 0.015},
|
|
29
|
+
"claude-3-haiku-20240307": {"input": 0.00025, "output": 0.00125},
|
|
30
|
+
},
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@dataclass
|
|
35
|
+
class TokenUsageRecord:
|
|
36
|
+
"""Record of a single token usage event"""
|
|
37
|
+
|
|
38
|
+
provider: str
|
|
39
|
+
model: str
|
|
40
|
+
input_tokens: int
|
|
41
|
+
output_tokens: int
|
|
42
|
+
total_tokens: int
|
|
43
|
+
timestamp: str = field(default_factory=lambda: datetime.now().isoformat())
|
|
44
|
+
cost: float = 0.0
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@dataclass
|
|
48
|
+
class TokenBudget:
|
|
49
|
+
"""Token budget configuration"""
|
|
50
|
+
|
|
51
|
+
max_tokens: int = 100000 # Total token budget
|
|
52
|
+
warning_threshold: float = 0.7 # Warn at 70% usage
|
|
53
|
+
stop_threshold: float = 0.9 # Stop at 90% usage
|
|
54
|
+
max_cost: float = 100.0 # Stop at $100
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class TokenManager:
|
|
58
|
+
"""Manages token usage and budget tracking"""
|
|
59
|
+
|
|
60
|
+
def __init__(self, budget: TokenBudget = None):
|
|
61
|
+
"""
|
|
62
|
+
Initialize token manager
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
budget: Token budget configuration
|
|
66
|
+
"""
|
|
67
|
+
self.budget = budget or TokenBudget()
|
|
68
|
+
self.records: list[TokenUsageRecord] = []
|
|
69
|
+
self.total_input_tokens = 0
|
|
70
|
+
self.total_output_tokens = 0
|
|
71
|
+
self.total_cost = 0.0
|
|
72
|
+
|
|
73
|
+
def record_usage(
|
|
74
|
+
self, provider: str, model: str, input_tokens: int, output_tokens: int
|
|
75
|
+
) -> TokenUsageRecord:
|
|
76
|
+
"""
|
|
77
|
+
Record token usage from an LLM call
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
provider: LLM provider name
|
|
81
|
+
model: Model identifier
|
|
82
|
+
input_tokens: Input token count
|
|
83
|
+
output_tokens: Output token count
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
TokenUsageRecord
|
|
87
|
+
"""
|
|
88
|
+
total_tokens = input_tokens + output_tokens
|
|
89
|
+
cost = self._calculate_cost(provider, model, input_tokens, output_tokens)
|
|
90
|
+
|
|
91
|
+
record = TokenUsageRecord(
|
|
92
|
+
provider=provider,
|
|
93
|
+
model=model,
|
|
94
|
+
input_tokens=input_tokens,
|
|
95
|
+
output_tokens=output_tokens,
|
|
96
|
+
total_tokens=total_tokens,
|
|
97
|
+
cost=cost,
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
self.records.append(record)
|
|
101
|
+
self.total_input_tokens += input_tokens
|
|
102
|
+
self.total_output_tokens += output_tokens
|
|
103
|
+
self.total_cost += cost
|
|
104
|
+
|
|
105
|
+
logger.info(
|
|
106
|
+
f"Recorded: {total_tokens} tokens ({provider}/{model}), "
|
|
107
|
+
f"Cost: ${cost:.4f}, Total cost: ${self.total_cost:.2f}"
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
self._check_limits()
|
|
111
|
+
|
|
112
|
+
return record
|
|
113
|
+
|
|
114
|
+
def _calculate_cost(
|
|
115
|
+
self, provider: str, model: str, input_tokens: int, output_tokens: int
|
|
116
|
+
) -> float:
|
|
117
|
+
"""Calculate cost for token usage"""
|
|
118
|
+
try:
|
|
119
|
+
pricing = PRICING.get(provider, {}).get(model)
|
|
120
|
+
if not pricing:
|
|
121
|
+
logger.warning(f"No pricing data for {provider}/{model}")
|
|
122
|
+
return 0.0
|
|
123
|
+
|
|
124
|
+
input_cost = (input_tokens / 1_000_000) * pricing.get("input", 0)
|
|
125
|
+
output_cost = (output_tokens / 1_000_000) * pricing.get("output", 0)
|
|
126
|
+
|
|
127
|
+
return input_cost + output_cost
|
|
128
|
+
|
|
129
|
+
except Exception as e:
|
|
130
|
+
logger.error(f"Error calculating cost: {e}")
|
|
131
|
+
return 0.0
|
|
132
|
+
|
|
133
|
+
def _check_limits(self) -> None:
|
|
134
|
+
"""Check if token usage exceeds limits"""
|
|
135
|
+
total_tokens = self.total_input_tokens + self.total_output_tokens
|
|
136
|
+
usage_percent = total_tokens / self.budget.max_tokens
|
|
137
|
+
|
|
138
|
+
if usage_percent >= self.budget.stop_threshold:
|
|
139
|
+
logger.warning(
|
|
140
|
+
f"Token usage at {usage_percent*100:.1f}% threshold "
|
|
141
|
+
f"({total_tokens}/{self.budget.max_tokens})"
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
if self.total_cost >= self.budget.max_cost:
|
|
145
|
+
logger.warning(
|
|
146
|
+
f"Cost limit reached: ${self.total_cost:.2f} "
|
|
147
|
+
f"(budget: ${self.budget.max_cost})"
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
def get_remaining_tokens(self) -> int:
|
|
151
|
+
"""Get remaining tokens in budget"""
|
|
152
|
+
total = self.total_input_tokens + self.total_output_tokens
|
|
153
|
+
return max(0, self.budget.max_tokens - total)
|
|
154
|
+
|
|
155
|
+
def get_usage_summary(self) -> Dict[str, Any]:
|
|
156
|
+
"""Get usage summary statistics"""
|
|
157
|
+
total_tokens = self.total_input_tokens + self.total_output_tokens
|
|
158
|
+
usage_percent = (total_tokens / self.budget.max_tokens) * 100
|
|
159
|
+
|
|
160
|
+
# Group by provider
|
|
161
|
+
by_provider = {}
|
|
162
|
+
for record in self.records:
|
|
163
|
+
if record.provider not in by_provider:
|
|
164
|
+
by_provider[record.provider] = {"calls": 0, "tokens": 0, "cost": 0.0}
|
|
165
|
+
by_provider[record.provider]["calls"] += 1
|
|
166
|
+
by_provider[record.provider]["tokens"] += record.total_tokens
|
|
167
|
+
by_provider[record.provider]["cost"] += record.cost
|
|
168
|
+
|
|
169
|
+
return {
|
|
170
|
+
"total_tokens": total_tokens,
|
|
171
|
+
"input_tokens": self.total_input_tokens,
|
|
172
|
+
"output_tokens": self.total_output_tokens,
|
|
173
|
+
"remaining_tokens": self.get_remaining_tokens(),
|
|
174
|
+
"usage_percent": usage_percent,
|
|
175
|
+
"total_cost": self.total_cost,
|
|
176
|
+
"budget": {
|
|
177
|
+
"max_tokens": self.budget.max_tokens,
|
|
178
|
+
"max_cost": self.budget.max_cost,
|
|
179
|
+
"warning_threshold": self.budget.warning_threshold,
|
|
180
|
+
"stop_threshold": self.budget.stop_threshold,
|
|
181
|
+
},
|
|
182
|
+
"by_provider": by_provider,
|
|
183
|
+
"num_calls": len(self.records),
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
def should_stop(self) -> bool:
|
|
187
|
+
"""Check if processing should stop due to limits"""
|
|
188
|
+
total = self.total_input_tokens + self.total_output_tokens
|
|
189
|
+
|
|
190
|
+
if total >= self.budget.max_tokens * self.budget.stop_threshold:
|
|
191
|
+
return True
|
|
192
|
+
|
|
193
|
+
if self.total_cost >= self.budget.max_cost:
|
|
194
|
+
return True
|
|
195
|
+
|
|
196
|
+
return False
|
|
197
|
+
|
|
198
|
+
def log_summary(self) -> None:
|
|
199
|
+
"""Log usage summary"""
|
|
200
|
+
summary = self.get_usage_summary()
|
|
201
|
+
|
|
202
|
+
logger.info("=" * 60)
|
|
203
|
+
logger.info("Token Usage Summary")
|
|
204
|
+
logger.info("=" * 60)
|
|
205
|
+
logger.info(f"Total tokens: {summary['total_tokens']:,}")
|
|
206
|
+
logger.info(f" Input: {summary['input_tokens']:,}")
|
|
207
|
+
logger.info(f" Output: {summary['output_tokens']:,}")
|
|
208
|
+
logger.info(f" Remaining: {summary['remaining_tokens']:,}")
|
|
209
|
+
logger.info(f"Usage: {summary['usage_percent']:.1f}% of budget")
|
|
210
|
+
logger.info(f"Total cost: ${summary['total_cost']:.2f}")
|
|
211
|
+
|
|
212
|
+
if summary["by_provider"]:
|
|
213
|
+
logger.info("\nBy Provider:")
|
|
214
|
+
for provider, stats in summary["by_provider"].items():
|
|
215
|
+
logger.info(
|
|
216
|
+
f" {provider}: "
|
|
217
|
+
f"{stats['calls']} calls, "
|
|
218
|
+
f"{stats['tokens']:,} tokens, "
|
|
219
|
+
f"${stats['cost']:.2f}"
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
logger.info("=" * 60)
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
# Global token manager instance
|
|
226
|
+
_token_manager: TokenManager = None
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def get_token_manager() -> TokenManager:
|
|
230
|
+
"""Get or create global token manager"""
|
|
231
|
+
global _token_manager
|
|
232
|
+
if _token_manager is None:
|
|
233
|
+
_token_manager = TokenManager()
|
|
234
|
+
return _token_manager
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def init_token_manager(budget: TokenBudget) -> TokenManager:
|
|
238
|
+
"""Initialize global token manager with custom budget"""
|
|
239
|
+
global _token_manager
|
|
240
|
+
_token_manager = TokenManager(budget)
|
|
241
|
+
return _token_manager
|