PraisonAI 3.0.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.
- praisonai/__init__.py +54 -0
- praisonai/__main__.py +15 -0
- praisonai/acp/__init__.py +54 -0
- praisonai/acp/config.py +159 -0
- praisonai/acp/server.py +587 -0
- praisonai/acp/session.py +219 -0
- praisonai/adapters/__init__.py +50 -0
- praisonai/adapters/readers.py +395 -0
- praisonai/adapters/rerankers.py +315 -0
- praisonai/adapters/retrievers.py +394 -0
- praisonai/adapters/vector_stores.py +409 -0
- praisonai/agent_scheduler.py +337 -0
- praisonai/agents_generator.py +903 -0
- praisonai/api/call.py +292 -0
- praisonai/auto.py +1197 -0
- praisonai/capabilities/__init__.py +275 -0
- praisonai/capabilities/a2a.py +140 -0
- praisonai/capabilities/assistants.py +283 -0
- praisonai/capabilities/audio.py +320 -0
- praisonai/capabilities/batches.py +469 -0
- praisonai/capabilities/completions.py +336 -0
- praisonai/capabilities/container_files.py +155 -0
- praisonai/capabilities/containers.py +93 -0
- praisonai/capabilities/embeddings.py +158 -0
- praisonai/capabilities/files.py +467 -0
- praisonai/capabilities/fine_tuning.py +293 -0
- praisonai/capabilities/guardrails.py +182 -0
- praisonai/capabilities/images.py +330 -0
- praisonai/capabilities/mcp.py +190 -0
- praisonai/capabilities/messages.py +270 -0
- praisonai/capabilities/moderations.py +154 -0
- praisonai/capabilities/ocr.py +217 -0
- praisonai/capabilities/passthrough.py +204 -0
- praisonai/capabilities/rag.py +207 -0
- praisonai/capabilities/realtime.py +160 -0
- praisonai/capabilities/rerank.py +165 -0
- praisonai/capabilities/responses.py +266 -0
- praisonai/capabilities/search.py +109 -0
- praisonai/capabilities/skills.py +133 -0
- praisonai/capabilities/vector_store_files.py +334 -0
- praisonai/capabilities/vector_stores.py +304 -0
- praisonai/capabilities/videos.py +141 -0
- praisonai/chainlit_ui.py +304 -0
- praisonai/chat/__init__.py +106 -0
- praisonai/chat/app.py +125 -0
- praisonai/cli/__init__.py +26 -0
- praisonai/cli/app.py +213 -0
- praisonai/cli/commands/__init__.py +75 -0
- praisonai/cli/commands/acp.py +70 -0
- praisonai/cli/commands/completion.py +333 -0
- praisonai/cli/commands/config.py +166 -0
- praisonai/cli/commands/debug.py +142 -0
- praisonai/cli/commands/diag.py +55 -0
- praisonai/cli/commands/doctor.py +166 -0
- praisonai/cli/commands/environment.py +179 -0
- praisonai/cli/commands/lsp.py +112 -0
- praisonai/cli/commands/mcp.py +210 -0
- praisonai/cli/commands/profile.py +457 -0
- praisonai/cli/commands/run.py +228 -0
- praisonai/cli/commands/schedule.py +150 -0
- praisonai/cli/commands/serve.py +97 -0
- praisonai/cli/commands/session.py +212 -0
- praisonai/cli/commands/traces.py +145 -0
- praisonai/cli/commands/version.py +101 -0
- praisonai/cli/configuration/__init__.py +18 -0
- praisonai/cli/configuration/loader.py +353 -0
- praisonai/cli/configuration/paths.py +114 -0
- praisonai/cli/configuration/schema.py +164 -0
- praisonai/cli/features/__init__.py +268 -0
- praisonai/cli/features/acp.py +236 -0
- praisonai/cli/features/action_orchestrator.py +546 -0
- praisonai/cli/features/agent_scheduler.py +773 -0
- praisonai/cli/features/agent_tools.py +474 -0
- praisonai/cli/features/agents.py +375 -0
- praisonai/cli/features/at_mentions.py +471 -0
- praisonai/cli/features/auto_memory.py +182 -0
- praisonai/cli/features/autonomy_mode.py +490 -0
- praisonai/cli/features/background.py +356 -0
- praisonai/cli/features/base.py +168 -0
- praisonai/cli/features/capabilities.py +1326 -0
- praisonai/cli/features/checkpoints.py +338 -0
- praisonai/cli/features/code_intelligence.py +652 -0
- praisonai/cli/features/compaction.py +294 -0
- praisonai/cli/features/compare.py +534 -0
- praisonai/cli/features/cost_tracker.py +514 -0
- praisonai/cli/features/debug.py +810 -0
- praisonai/cli/features/deploy.py +517 -0
- praisonai/cli/features/diag.py +289 -0
- praisonai/cli/features/doctor/__init__.py +63 -0
- praisonai/cli/features/doctor/checks/__init__.py +24 -0
- praisonai/cli/features/doctor/checks/acp_checks.py +240 -0
- praisonai/cli/features/doctor/checks/config_checks.py +366 -0
- praisonai/cli/features/doctor/checks/db_checks.py +366 -0
- praisonai/cli/features/doctor/checks/env_checks.py +543 -0
- praisonai/cli/features/doctor/checks/lsp_checks.py +199 -0
- praisonai/cli/features/doctor/checks/mcp_checks.py +349 -0
- praisonai/cli/features/doctor/checks/memory_checks.py +268 -0
- praisonai/cli/features/doctor/checks/network_checks.py +251 -0
- praisonai/cli/features/doctor/checks/obs_checks.py +328 -0
- praisonai/cli/features/doctor/checks/performance_checks.py +235 -0
- praisonai/cli/features/doctor/checks/permissions_checks.py +259 -0
- praisonai/cli/features/doctor/checks/selftest_checks.py +322 -0
- praisonai/cli/features/doctor/checks/serve_checks.py +426 -0
- praisonai/cli/features/doctor/checks/skills_checks.py +231 -0
- praisonai/cli/features/doctor/checks/tools_checks.py +371 -0
- praisonai/cli/features/doctor/engine.py +266 -0
- praisonai/cli/features/doctor/formatters.py +310 -0
- praisonai/cli/features/doctor/handler.py +397 -0
- praisonai/cli/features/doctor/models.py +264 -0
- praisonai/cli/features/doctor/registry.py +239 -0
- praisonai/cli/features/endpoints.py +1019 -0
- praisonai/cli/features/eval.py +560 -0
- praisonai/cli/features/external_agents.py +231 -0
- praisonai/cli/features/fast_context.py +410 -0
- praisonai/cli/features/flow_display.py +566 -0
- praisonai/cli/features/git_integration.py +651 -0
- praisonai/cli/features/guardrail.py +171 -0
- praisonai/cli/features/handoff.py +185 -0
- praisonai/cli/features/hooks.py +583 -0
- praisonai/cli/features/image.py +384 -0
- praisonai/cli/features/interactive_runtime.py +585 -0
- praisonai/cli/features/interactive_tools.py +380 -0
- praisonai/cli/features/interactive_tui.py +603 -0
- praisonai/cli/features/jobs.py +632 -0
- praisonai/cli/features/knowledge.py +531 -0
- praisonai/cli/features/lite.py +244 -0
- praisonai/cli/features/lsp_cli.py +225 -0
- praisonai/cli/features/mcp.py +169 -0
- praisonai/cli/features/message_queue.py +587 -0
- praisonai/cli/features/metrics.py +211 -0
- praisonai/cli/features/n8n.py +673 -0
- praisonai/cli/features/observability.py +293 -0
- praisonai/cli/features/ollama.py +361 -0
- praisonai/cli/features/output_style.py +273 -0
- praisonai/cli/features/package.py +631 -0
- praisonai/cli/features/performance.py +308 -0
- praisonai/cli/features/persistence.py +636 -0
- praisonai/cli/features/profile.py +226 -0
- praisonai/cli/features/profiler/__init__.py +81 -0
- praisonai/cli/features/profiler/core.py +558 -0
- praisonai/cli/features/profiler/optimizations.py +652 -0
- praisonai/cli/features/profiler/suite.py +386 -0
- praisonai/cli/features/profiling.py +350 -0
- praisonai/cli/features/queue/__init__.py +73 -0
- praisonai/cli/features/queue/manager.py +395 -0
- praisonai/cli/features/queue/models.py +286 -0
- praisonai/cli/features/queue/persistence.py +564 -0
- praisonai/cli/features/queue/scheduler.py +484 -0
- praisonai/cli/features/queue/worker.py +372 -0
- praisonai/cli/features/recipe.py +1723 -0
- praisonai/cli/features/recipes.py +449 -0
- praisonai/cli/features/registry.py +229 -0
- praisonai/cli/features/repo_map.py +860 -0
- praisonai/cli/features/router.py +466 -0
- praisonai/cli/features/sandbox_executor.py +515 -0
- praisonai/cli/features/serve.py +829 -0
- praisonai/cli/features/session.py +222 -0
- praisonai/cli/features/skills.py +856 -0
- praisonai/cli/features/slash_commands.py +650 -0
- praisonai/cli/features/telemetry.py +179 -0
- praisonai/cli/features/templates.py +1384 -0
- praisonai/cli/features/thinking.py +305 -0
- praisonai/cli/features/todo.py +334 -0
- praisonai/cli/features/tools.py +680 -0
- praisonai/cli/features/tui/__init__.py +83 -0
- praisonai/cli/features/tui/app.py +580 -0
- praisonai/cli/features/tui/cli.py +566 -0
- praisonai/cli/features/tui/debug.py +511 -0
- praisonai/cli/features/tui/events.py +99 -0
- praisonai/cli/features/tui/mock_provider.py +328 -0
- praisonai/cli/features/tui/orchestrator.py +652 -0
- praisonai/cli/features/tui/screens/__init__.py +50 -0
- praisonai/cli/features/tui/screens/main.py +245 -0
- praisonai/cli/features/tui/screens/queue.py +174 -0
- praisonai/cli/features/tui/screens/session.py +124 -0
- praisonai/cli/features/tui/screens/settings.py +148 -0
- praisonai/cli/features/tui/widgets/__init__.py +56 -0
- praisonai/cli/features/tui/widgets/chat.py +261 -0
- praisonai/cli/features/tui/widgets/composer.py +224 -0
- praisonai/cli/features/tui/widgets/queue_panel.py +200 -0
- praisonai/cli/features/tui/widgets/status.py +167 -0
- praisonai/cli/features/tui/widgets/tool_panel.py +248 -0
- praisonai/cli/features/workflow.py +720 -0
- praisonai/cli/legacy.py +236 -0
- praisonai/cli/main.py +5559 -0
- praisonai/cli/schedule_cli.py +54 -0
- praisonai/cli/state/__init__.py +31 -0
- praisonai/cli/state/identifiers.py +161 -0
- praisonai/cli/state/sessions.py +313 -0
- praisonai/code/__init__.py +93 -0
- praisonai/code/agent_tools.py +344 -0
- praisonai/code/diff/__init__.py +21 -0
- praisonai/code/diff/diff_strategy.py +432 -0
- praisonai/code/tools/__init__.py +27 -0
- praisonai/code/tools/apply_diff.py +221 -0
- praisonai/code/tools/execute_command.py +275 -0
- praisonai/code/tools/list_files.py +274 -0
- praisonai/code/tools/read_file.py +206 -0
- praisonai/code/tools/search_replace.py +248 -0
- praisonai/code/tools/write_file.py +217 -0
- praisonai/code/utils/__init__.py +46 -0
- praisonai/code/utils/file_utils.py +307 -0
- praisonai/code/utils/ignore_utils.py +308 -0
- praisonai/code/utils/text_utils.py +276 -0
- praisonai/db/__init__.py +64 -0
- praisonai/db/adapter.py +531 -0
- praisonai/deploy/__init__.py +62 -0
- praisonai/deploy/api.py +231 -0
- praisonai/deploy/docker.py +454 -0
- praisonai/deploy/doctor.py +367 -0
- praisonai/deploy/main.py +327 -0
- praisonai/deploy/models.py +179 -0
- praisonai/deploy/providers/__init__.py +33 -0
- praisonai/deploy/providers/aws.py +331 -0
- praisonai/deploy/providers/azure.py +358 -0
- praisonai/deploy/providers/base.py +101 -0
- praisonai/deploy/providers/gcp.py +314 -0
- praisonai/deploy/schema.py +208 -0
- praisonai/deploy.py +185 -0
- praisonai/endpoints/__init__.py +53 -0
- praisonai/endpoints/a2u_server.py +410 -0
- praisonai/endpoints/discovery.py +165 -0
- praisonai/endpoints/providers/__init__.py +28 -0
- praisonai/endpoints/providers/a2a.py +253 -0
- praisonai/endpoints/providers/a2u.py +208 -0
- praisonai/endpoints/providers/agents_api.py +171 -0
- praisonai/endpoints/providers/base.py +231 -0
- praisonai/endpoints/providers/mcp.py +263 -0
- praisonai/endpoints/providers/recipe.py +206 -0
- praisonai/endpoints/providers/tools_mcp.py +150 -0
- praisonai/endpoints/registry.py +131 -0
- praisonai/endpoints/server.py +161 -0
- praisonai/inbuilt_tools/__init__.py +24 -0
- praisonai/inbuilt_tools/autogen_tools.py +117 -0
- praisonai/inc/__init__.py +2 -0
- praisonai/inc/config.py +96 -0
- praisonai/inc/models.py +155 -0
- praisonai/integrations/__init__.py +56 -0
- praisonai/integrations/base.py +303 -0
- praisonai/integrations/claude_code.py +270 -0
- praisonai/integrations/codex_cli.py +255 -0
- praisonai/integrations/cursor_cli.py +195 -0
- praisonai/integrations/gemini_cli.py +222 -0
- praisonai/jobs/__init__.py +67 -0
- praisonai/jobs/executor.py +425 -0
- praisonai/jobs/models.py +230 -0
- praisonai/jobs/router.py +314 -0
- praisonai/jobs/server.py +186 -0
- praisonai/jobs/store.py +203 -0
- praisonai/llm/__init__.py +66 -0
- praisonai/llm/registry.py +382 -0
- praisonai/mcp_server/__init__.py +152 -0
- praisonai/mcp_server/adapters/__init__.py +74 -0
- praisonai/mcp_server/adapters/agents.py +128 -0
- praisonai/mcp_server/adapters/capabilities.py +168 -0
- praisonai/mcp_server/adapters/cli_tools.py +568 -0
- praisonai/mcp_server/adapters/extended_capabilities.py +462 -0
- praisonai/mcp_server/adapters/knowledge.py +93 -0
- praisonai/mcp_server/adapters/memory.py +104 -0
- praisonai/mcp_server/adapters/prompts.py +306 -0
- praisonai/mcp_server/adapters/resources.py +124 -0
- praisonai/mcp_server/adapters/tools_bridge.py +280 -0
- praisonai/mcp_server/auth/__init__.py +48 -0
- praisonai/mcp_server/auth/api_key.py +291 -0
- praisonai/mcp_server/auth/oauth.py +460 -0
- praisonai/mcp_server/auth/oidc.py +289 -0
- praisonai/mcp_server/auth/scopes.py +260 -0
- praisonai/mcp_server/cli.py +852 -0
- praisonai/mcp_server/elicitation.py +445 -0
- praisonai/mcp_server/icons.py +302 -0
- praisonai/mcp_server/recipe_adapter.py +573 -0
- praisonai/mcp_server/recipe_cli.py +824 -0
- praisonai/mcp_server/registry.py +703 -0
- praisonai/mcp_server/sampling.py +422 -0
- praisonai/mcp_server/server.py +490 -0
- praisonai/mcp_server/tasks.py +443 -0
- praisonai/mcp_server/transports/__init__.py +18 -0
- praisonai/mcp_server/transports/http_stream.py +376 -0
- praisonai/mcp_server/transports/stdio.py +132 -0
- praisonai/persistence/__init__.py +84 -0
- praisonai/persistence/config.py +238 -0
- praisonai/persistence/conversation/__init__.py +25 -0
- praisonai/persistence/conversation/async_mysql.py +427 -0
- praisonai/persistence/conversation/async_postgres.py +410 -0
- praisonai/persistence/conversation/async_sqlite.py +371 -0
- praisonai/persistence/conversation/base.py +151 -0
- praisonai/persistence/conversation/json_store.py +250 -0
- praisonai/persistence/conversation/mysql.py +387 -0
- praisonai/persistence/conversation/postgres.py +401 -0
- praisonai/persistence/conversation/singlestore.py +240 -0
- praisonai/persistence/conversation/sqlite.py +341 -0
- praisonai/persistence/conversation/supabase.py +203 -0
- praisonai/persistence/conversation/surrealdb.py +287 -0
- praisonai/persistence/factory.py +301 -0
- praisonai/persistence/hooks/__init__.py +18 -0
- praisonai/persistence/hooks/agent_hooks.py +297 -0
- praisonai/persistence/knowledge/__init__.py +26 -0
- praisonai/persistence/knowledge/base.py +144 -0
- praisonai/persistence/knowledge/cassandra.py +232 -0
- praisonai/persistence/knowledge/chroma.py +295 -0
- praisonai/persistence/knowledge/clickhouse.py +242 -0
- praisonai/persistence/knowledge/cosmosdb_vector.py +438 -0
- praisonai/persistence/knowledge/couchbase.py +286 -0
- praisonai/persistence/knowledge/lancedb.py +216 -0
- praisonai/persistence/knowledge/langchain_adapter.py +291 -0
- praisonai/persistence/knowledge/lightrag_adapter.py +212 -0
- praisonai/persistence/knowledge/llamaindex_adapter.py +256 -0
- praisonai/persistence/knowledge/milvus.py +277 -0
- praisonai/persistence/knowledge/mongodb_vector.py +306 -0
- praisonai/persistence/knowledge/pgvector.py +335 -0
- praisonai/persistence/knowledge/pinecone.py +253 -0
- praisonai/persistence/knowledge/qdrant.py +301 -0
- praisonai/persistence/knowledge/redis_vector.py +291 -0
- praisonai/persistence/knowledge/singlestore_vector.py +299 -0
- praisonai/persistence/knowledge/surrealdb_vector.py +309 -0
- praisonai/persistence/knowledge/upstash_vector.py +266 -0
- praisonai/persistence/knowledge/weaviate.py +223 -0
- praisonai/persistence/migrations/__init__.py +10 -0
- praisonai/persistence/migrations/manager.py +251 -0
- praisonai/persistence/orchestrator.py +406 -0
- praisonai/persistence/state/__init__.py +21 -0
- praisonai/persistence/state/async_mongodb.py +200 -0
- praisonai/persistence/state/base.py +107 -0
- praisonai/persistence/state/dynamodb.py +226 -0
- praisonai/persistence/state/firestore.py +175 -0
- praisonai/persistence/state/gcs.py +155 -0
- praisonai/persistence/state/memory.py +245 -0
- praisonai/persistence/state/mongodb.py +158 -0
- praisonai/persistence/state/redis.py +190 -0
- praisonai/persistence/state/upstash.py +144 -0
- praisonai/persistence/tests/__init__.py +3 -0
- praisonai/persistence/tests/test_all_backends.py +633 -0
- praisonai/profiler.py +1214 -0
- praisonai/recipe/__init__.py +134 -0
- praisonai/recipe/bridge.py +278 -0
- praisonai/recipe/core.py +893 -0
- praisonai/recipe/exceptions.py +54 -0
- praisonai/recipe/history.py +402 -0
- praisonai/recipe/models.py +266 -0
- praisonai/recipe/operations.py +440 -0
- praisonai/recipe/policy.py +422 -0
- praisonai/recipe/registry.py +849 -0
- praisonai/recipe/runtime.py +214 -0
- praisonai/recipe/security.py +711 -0
- praisonai/recipe/serve.py +859 -0
- praisonai/recipe/server.py +613 -0
- praisonai/scheduler/__init__.py +45 -0
- praisonai/scheduler/agent_scheduler.py +552 -0
- praisonai/scheduler/base.py +124 -0
- praisonai/scheduler/daemon_manager.py +225 -0
- praisonai/scheduler/state_manager.py +155 -0
- praisonai/scheduler/yaml_loader.py +193 -0
- praisonai/scheduler.py +194 -0
- praisonai/setup/__init__.py +1 -0
- praisonai/setup/build.py +21 -0
- praisonai/setup/post_install.py +23 -0
- praisonai/setup/setup_conda_env.py +25 -0
- praisonai/setup.py +16 -0
- praisonai/templates/__init__.py +116 -0
- praisonai/templates/cache.py +364 -0
- praisonai/templates/dependency_checker.py +358 -0
- praisonai/templates/discovery.py +391 -0
- praisonai/templates/loader.py +564 -0
- praisonai/templates/registry.py +511 -0
- praisonai/templates/resolver.py +206 -0
- praisonai/templates/security.py +327 -0
- praisonai/templates/tool_override.py +498 -0
- praisonai/templates/tools_doctor.py +256 -0
- praisonai/test.py +105 -0
- praisonai/train.py +562 -0
- praisonai/train_vision.py +306 -0
- praisonai/ui/agents.py +824 -0
- praisonai/ui/callbacks.py +57 -0
- praisonai/ui/chainlit_compat.py +246 -0
- praisonai/ui/chat.py +532 -0
- praisonai/ui/code.py +717 -0
- praisonai/ui/colab.py +474 -0
- praisonai/ui/colab_chainlit.py +81 -0
- praisonai/ui/components/aicoder.py +284 -0
- praisonai/ui/context.py +283 -0
- praisonai/ui/database_config.py +56 -0
- praisonai/ui/db.py +294 -0
- praisonai/ui/realtime.py +488 -0
- praisonai/ui/realtimeclient/__init__.py +756 -0
- praisonai/ui/realtimeclient/tools.py +242 -0
- praisonai/ui/sql_alchemy.py +710 -0
- praisonai/upload_vision.py +140 -0
- praisonai/version.py +1 -0
- praisonai-3.0.0.dist-info/METADATA +3493 -0
- praisonai-3.0.0.dist-info/RECORD +393 -0
- praisonai-3.0.0.dist-info/WHEEL +5 -0
- praisonai-3.0.0.dist-info/entry_points.txt +4 -0
- praisonai-3.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,514 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Cost Tracking System for PraisonAI CLI.
|
|
3
|
+
|
|
4
|
+
Inspired by Aider's cost tracking and Gemini CLI's stats command.
|
|
5
|
+
Provides real-time cost and token usage tracking.
|
|
6
|
+
|
|
7
|
+
Architecture:
|
|
8
|
+
- CostTracker: Tracks tokens and costs per session
|
|
9
|
+
- ModelPricing: Pricing data for different models
|
|
10
|
+
- UsageStats: Aggregated usage statistics
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from dataclasses import dataclass, field
|
|
14
|
+
from typing import Any, Dict, List, Optional
|
|
15
|
+
from datetime import datetime
|
|
16
|
+
import logging
|
|
17
|
+
import json
|
|
18
|
+
|
|
19
|
+
logger = logging.getLogger(__name__)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# ============================================================================
|
|
23
|
+
# Model Pricing Data
|
|
24
|
+
# ============================================================================
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class ModelPricing:
|
|
28
|
+
"""
|
|
29
|
+
Pricing information for a model.
|
|
30
|
+
|
|
31
|
+
Prices are per 1M tokens (as commonly quoted).
|
|
32
|
+
"""
|
|
33
|
+
model_name: str
|
|
34
|
+
input_price_per_1m: float # Price per 1M input tokens
|
|
35
|
+
output_price_per_1m: float # Price per 1M output tokens
|
|
36
|
+
context_window: int = 128000
|
|
37
|
+
|
|
38
|
+
def calculate_cost(self, input_tokens: int, output_tokens: int) -> float:
|
|
39
|
+
"""Calculate cost for given token counts."""
|
|
40
|
+
input_cost = (input_tokens / 1_000_000) * self.input_price_per_1m
|
|
41
|
+
output_cost = (output_tokens / 1_000_000) * self.output_price_per_1m
|
|
42
|
+
return input_cost + output_cost
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
# Default pricing for common models (as of late 2024)
|
|
46
|
+
DEFAULT_PRICING: Dict[str, ModelPricing] = {
|
|
47
|
+
# OpenAI models
|
|
48
|
+
"gpt-4o": ModelPricing("gpt-4o", 2.50, 10.00, 128000),
|
|
49
|
+
"gpt-4o-mini": ModelPricing("gpt-4o-mini", 0.15, 0.60, 128000),
|
|
50
|
+
"gpt-4-turbo": ModelPricing("gpt-4-turbo", 10.00, 30.00, 128000),
|
|
51
|
+
"gpt-4": ModelPricing("gpt-4", 30.00, 60.00, 8192),
|
|
52
|
+
"gpt-3.5-turbo": ModelPricing("gpt-3.5-turbo", 0.50, 1.50, 16385),
|
|
53
|
+
"o1": ModelPricing("o1", 15.00, 60.00, 200000),
|
|
54
|
+
"o1-mini": ModelPricing("o1-mini", 3.00, 12.00, 128000),
|
|
55
|
+
"o1-preview": ModelPricing("o1-preview", 15.00, 60.00, 128000),
|
|
56
|
+
"o3-mini": ModelPricing("o3-mini", 1.10, 4.40, 200000),
|
|
57
|
+
|
|
58
|
+
# Anthropic models
|
|
59
|
+
"claude-3-5-sonnet-20241022": ModelPricing("claude-3-5-sonnet", 3.00, 15.00, 200000),
|
|
60
|
+
"claude-3-5-sonnet": ModelPricing("claude-3-5-sonnet", 3.00, 15.00, 200000),
|
|
61
|
+
"claude-3-opus": ModelPricing("claude-3-opus", 15.00, 75.00, 200000),
|
|
62
|
+
"claude-3-sonnet": ModelPricing("claude-3-sonnet", 3.00, 15.00, 200000),
|
|
63
|
+
"claude-3-haiku": ModelPricing("claude-3-haiku", 0.25, 1.25, 200000),
|
|
64
|
+
|
|
65
|
+
# Google models
|
|
66
|
+
"gemini-2.0-flash": ModelPricing("gemini-2.0-flash", 0.10, 0.40, 1000000),
|
|
67
|
+
"gemini-1.5-pro": ModelPricing("gemini-1.5-pro", 1.25, 5.00, 2000000),
|
|
68
|
+
"gemini-1.5-flash": ModelPricing("gemini-1.5-flash", 0.075, 0.30, 1000000),
|
|
69
|
+
|
|
70
|
+
# Default fallback
|
|
71
|
+
"default": ModelPricing("default", 1.00, 3.00, 128000),
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def get_pricing(model_name: str) -> ModelPricing:
|
|
76
|
+
"""Get pricing for a model, with fallback to default."""
|
|
77
|
+
# Try exact match
|
|
78
|
+
if model_name in DEFAULT_PRICING:
|
|
79
|
+
return DEFAULT_PRICING[model_name]
|
|
80
|
+
|
|
81
|
+
# Try partial match
|
|
82
|
+
model_lower = model_name.lower()
|
|
83
|
+
for key, pricing in DEFAULT_PRICING.items():
|
|
84
|
+
if key in model_lower or model_lower in key:
|
|
85
|
+
return pricing
|
|
86
|
+
|
|
87
|
+
# Return default
|
|
88
|
+
logger.debug(f"No pricing found for model '{model_name}', using default")
|
|
89
|
+
return DEFAULT_PRICING["default"]
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
# ============================================================================
|
|
93
|
+
# Usage Statistics
|
|
94
|
+
# ============================================================================
|
|
95
|
+
|
|
96
|
+
@dataclass
|
|
97
|
+
class TokenUsage:
|
|
98
|
+
"""Token usage for a single request."""
|
|
99
|
+
input_tokens: int = 0
|
|
100
|
+
output_tokens: int = 0
|
|
101
|
+
total_tokens: int = 0
|
|
102
|
+
cached_tokens: int = 0
|
|
103
|
+
|
|
104
|
+
def __post_init__(self):
|
|
105
|
+
if self.total_tokens == 0:
|
|
106
|
+
self.total_tokens = self.input_tokens + self.output_tokens
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
@dataclass
|
|
110
|
+
class RequestStats:
|
|
111
|
+
"""Statistics for a single request."""
|
|
112
|
+
timestamp: datetime
|
|
113
|
+
model: str
|
|
114
|
+
usage: TokenUsage
|
|
115
|
+
cost: float
|
|
116
|
+
duration_ms: float = 0.0
|
|
117
|
+
prompt_preview: str = ""
|
|
118
|
+
|
|
119
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
120
|
+
"""Convert to dictionary for serialization."""
|
|
121
|
+
return {
|
|
122
|
+
"timestamp": self.timestamp.isoformat(),
|
|
123
|
+
"model": self.model,
|
|
124
|
+
"input_tokens": self.usage.input_tokens,
|
|
125
|
+
"output_tokens": self.usage.output_tokens,
|
|
126
|
+
"total_tokens": self.usage.total_tokens,
|
|
127
|
+
"cached_tokens": self.usage.cached_tokens,
|
|
128
|
+
"cost": self.cost,
|
|
129
|
+
"duration_ms": self.duration_ms,
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
@dataclass
|
|
134
|
+
class SessionStats:
|
|
135
|
+
"""Aggregated statistics for a session."""
|
|
136
|
+
session_id: str
|
|
137
|
+
start_time: datetime
|
|
138
|
+
end_time: Optional[datetime] = None
|
|
139
|
+
|
|
140
|
+
total_requests: int = 0
|
|
141
|
+
total_input_tokens: int = 0
|
|
142
|
+
total_output_tokens: int = 0
|
|
143
|
+
total_tokens: int = 0
|
|
144
|
+
total_cached_tokens: int = 0
|
|
145
|
+
total_cost: float = 0.0
|
|
146
|
+
total_duration_ms: float = 0.0
|
|
147
|
+
|
|
148
|
+
models_used: Dict[str, int] = field(default_factory=dict)
|
|
149
|
+
|
|
150
|
+
def add_request(self, stats: RequestStats) -> None:
|
|
151
|
+
"""Add request statistics to session totals."""
|
|
152
|
+
self.total_requests += 1
|
|
153
|
+
self.total_input_tokens += stats.usage.input_tokens
|
|
154
|
+
self.total_output_tokens += stats.usage.output_tokens
|
|
155
|
+
self.total_tokens += stats.usage.total_tokens
|
|
156
|
+
self.total_cached_tokens += stats.usage.cached_tokens
|
|
157
|
+
self.total_cost += stats.cost
|
|
158
|
+
self.total_duration_ms += stats.duration_ms
|
|
159
|
+
|
|
160
|
+
# Track model usage
|
|
161
|
+
if stats.model not in self.models_used:
|
|
162
|
+
self.models_used[stats.model] = 0
|
|
163
|
+
self.models_used[stats.model] += 1
|
|
164
|
+
|
|
165
|
+
@property
|
|
166
|
+
def duration_seconds(self) -> float:
|
|
167
|
+
"""Get session duration in seconds."""
|
|
168
|
+
end = self.end_time or datetime.now()
|
|
169
|
+
return (end - self.start_time).total_seconds()
|
|
170
|
+
|
|
171
|
+
@property
|
|
172
|
+
def avg_tokens_per_request(self) -> float:
|
|
173
|
+
"""Get average tokens per request."""
|
|
174
|
+
if self.total_requests == 0:
|
|
175
|
+
return 0.0
|
|
176
|
+
return self.total_tokens / self.total_requests
|
|
177
|
+
|
|
178
|
+
@property
|
|
179
|
+
def avg_cost_per_request(self) -> float:
|
|
180
|
+
"""Get average cost per request."""
|
|
181
|
+
if self.total_requests == 0:
|
|
182
|
+
return 0.0
|
|
183
|
+
return self.total_cost / self.total_requests
|
|
184
|
+
|
|
185
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
186
|
+
"""Convert to dictionary for serialization."""
|
|
187
|
+
return {
|
|
188
|
+
"session_id": self.session_id,
|
|
189
|
+
"start_time": self.start_time.isoformat(),
|
|
190
|
+
"end_time": self.end_time.isoformat() if self.end_time else None,
|
|
191
|
+
"duration_seconds": self.duration_seconds,
|
|
192
|
+
"total_requests": self.total_requests,
|
|
193
|
+
"total_input_tokens": self.total_input_tokens,
|
|
194
|
+
"total_output_tokens": self.total_output_tokens,
|
|
195
|
+
"total_tokens": self.total_tokens,
|
|
196
|
+
"total_cached_tokens": self.total_cached_tokens,
|
|
197
|
+
"total_cost": self.total_cost,
|
|
198
|
+
"avg_tokens_per_request": self.avg_tokens_per_request,
|
|
199
|
+
"avg_cost_per_request": self.avg_cost_per_request,
|
|
200
|
+
"models_used": self.models_used,
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
# ============================================================================
|
|
205
|
+
# Cost Tracker
|
|
206
|
+
# ============================================================================
|
|
207
|
+
|
|
208
|
+
class CostTracker:
|
|
209
|
+
"""
|
|
210
|
+
Tracks costs and token usage for a session.
|
|
211
|
+
|
|
212
|
+
Features:
|
|
213
|
+
- Real-time cost tracking
|
|
214
|
+
- Per-model statistics
|
|
215
|
+
- Session history
|
|
216
|
+
- Export to JSON
|
|
217
|
+
"""
|
|
218
|
+
|
|
219
|
+
def __init__(
|
|
220
|
+
self,
|
|
221
|
+
session_id: Optional[str] = None,
|
|
222
|
+
pricing: Optional[Dict[str, ModelPricing]] = None,
|
|
223
|
+
verbose: bool = False
|
|
224
|
+
):
|
|
225
|
+
self.session_id = session_id or self._generate_session_id()
|
|
226
|
+
self.pricing = pricing or DEFAULT_PRICING
|
|
227
|
+
self.verbose = verbose
|
|
228
|
+
|
|
229
|
+
self.session_stats = SessionStats(
|
|
230
|
+
session_id=self.session_id,
|
|
231
|
+
start_time=datetime.now()
|
|
232
|
+
)
|
|
233
|
+
self.request_history: List[RequestStats] = []
|
|
234
|
+
|
|
235
|
+
def _generate_session_id(self) -> str:
|
|
236
|
+
"""Generate a unique session ID."""
|
|
237
|
+
import uuid
|
|
238
|
+
return str(uuid.uuid4())[:8]
|
|
239
|
+
|
|
240
|
+
def track_request(
|
|
241
|
+
self,
|
|
242
|
+
model: str,
|
|
243
|
+
input_tokens: int,
|
|
244
|
+
output_tokens: int,
|
|
245
|
+
cached_tokens: int = 0,
|
|
246
|
+
duration_ms: float = 0.0,
|
|
247
|
+
prompt_preview: str = ""
|
|
248
|
+
) -> RequestStats:
|
|
249
|
+
"""
|
|
250
|
+
Track a single request.
|
|
251
|
+
|
|
252
|
+
Returns:
|
|
253
|
+
RequestStats for the tracked request
|
|
254
|
+
"""
|
|
255
|
+
usage = TokenUsage(
|
|
256
|
+
input_tokens=input_tokens,
|
|
257
|
+
output_tokens=output_tokens,
|
|
258
|
+
cached_tokens=cached_tokens
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
# Calculate cost
|
|
262
|
+
pricing = get_pricing(model)
|
|
263
|
+
cost = pricing.calculate_cost(input_tokens, output_tokens)
|
|
264
|
+
|
|
265
|
+
# Create request stats
|
|
266
|
+
stats = RequestStats(
|
|
267
|
+
timestamp=datetime.now(),
|
|
268
|
+
model=model,
|
|
269
|
+
usage=usage,
|
|
270
|
+
cost=cost,
|
|
271
|
+
duration_ms=duration_ms,
|
|
272
|
+
prompt_preview=prompt_preview[:100] if prompt_preview else ""
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
# Update session stats
|
|
276
|
+
self.session_stats.add_request(stats)
|
|
277
|
+
self.request_history.append(stats)
|
|
278
|
+
|
|
279
|
+
if self.verbose:
|
|
280
|
+
logger.info(
|
|
281
|
+
f"Request tracked: {model} - "
|
|
282
|
+
f"{usage.total_tokens} tokens, ${cost:.4f}"
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
return stats
|
|
286
|
+
|
|
287
|
+
def track_from_response(
|
|
288
|
+
self,
|
|
289
|
+
model: str,
|
|
290
|
+
response: Any,
|
|
291
|
+
duration_ms: float = 0.0
|
|
292
|
+
) -> Optional[RequestStats]:
|
|
293
|
+
"""
|
|
294
|
+
Track request from LLM response object.
|
|
295
|
+
|
|
296
|
+
Handles various response formats from different providers.
|
|
297
|
+
"""
|
|
298
|
+
try:
|
|
299
|
+
# Try to extract usage from response
|
|
300
|
+
usage = None
|
|
301
|
+
|
|
302
|
+
# OpenAI format
|
|
303
|
+
if hasattr(response, 'usage'):
|
|
304
|
+
usage = response.usage
|
|
305
|
+
input_tokens = getattr(usage, 'prompt_tokens', 0)
|
|
306
|
+
output_tokens = getattr(usage, 'completion_tokens', 0)
|
|
307
|
+
cached_tokens = getattr(usage, 'cached_tokens', 0)
|
|
308
|
+
# Dict format
|
|
309
|
+
elif isinstance(response, dict) and 'usage' in response:
|
|
310
|
+
usage = response['usage']
|
|
311
|
+
input_tokens = usage.get('prompt_tokens', 0)
|
|
312
|
+
output_tokens = usage.get('completion_tokens', 0)
|
|
313
|
+
cached_tokens = usage.get('cached_tokens', 0)
|
|
314
|
+
else:
|
|
315
|
+
logger.debug("Could not extract usage from response")
|
|
316
|
+
return None
|
|
317
|
+
|
|
318
|
+
return self.track_request(
|
|
319
|
+
model=model,
|
|
320
|
+
input_tokens=input_tokens,
|
|
321
|
+
output_tokens=output_tokens,
|
|
322
|
+
cached_tokens=cached_tokens,
|
|
323
|
+
duration_ms=duration_ms
|
|
324
|
+
)
|
|
325
|
+
except Exception as e:
|
|
326
|
+
logger.error(f"Error tracking response: {e}")
|
|
327
|
+
return None
|
|
328
|
+
|
|
329
|
+
def get_current_stats(self) -> SessionStats:
|
|
330
|
+
"""Get current session statistics."""
|
|
331
|
+
return self.session_stats
|
|
332
|
+
|
|
333
|
+
def get_total_cost(self) -> float:
|
|
334
|
+
"""Get total cost for the session."""
|
|
335
|
+
return self.session_stats.total_cost
|
|
336
|
+
|
|
337
|
+
def get_total_tokens(self) -> int:
|
|
338
|
+
"""Get total tokens for the session."""
|
|
339
|
+
return self.session_stats.total_tokens
|
|
340
|
+
|
|
341
|
+
def get_request_count(self) -> int:
|
|
342
|
+
"""Get total request count."""
|
|
343
|
+
return self.session_stats.total_requests
|
|
344
|
+
|
|
345
|
+
def format_summary(self) -> str:
|
|
346
|
+
"""Format a summary of the session statistics."""
|
|
347
|
+
stats = self.session_stats
|
|
348
|
+
|
|
349
|
+
lines = [
|
|
350
|
+
f"Session: {stats.session_id}",
|
|
351
|
+
f"Duration: {stats.duration_seconds:.1f}s",
|
|
352
|
+
f"Requests: {stats.total_requests}",
|
|
353
|
+
"",
|
|
354
|
+
"Tokens:",
|
|
355
|
+
f" Input: {stats.total_input_tokens:,}",
|
|
356
|
+
f" Output: {stats.total_output_tokens:,}",
|
|
357
|
+
f" Total: {stats.total_tokens:,}",
|
|
358
|
+
f" Cached: {stats.total_cached_tokens:,}",
|
|
359
|
+
"",
|
|
360
|
+
f"Cost: ${stats.total_cost:.4f}",
|
|
361
|
+
f"Avg per request: ${stats.avg_cost_per_request:.4f}",
|
|
362
|
+
]
|
|
363
|
+
|
|
364
|
+
if stats.models_used:
|
|
365
|
+
lines.append("")
|
|
366
|
+
lines.append("Models used:")
|
|
367
|
+
for model, count in stats.models_used.items():
|
|
368
|
+
lines.append(f" {model}: {count} requests")
|
|
369
|
+
|
|
370
|
+
return "\n".join(lines)
|
|
371
|
+
|
|
372
|
+
def print_summary(self) -> None:
|
|
373
|
+
"""Print a formatted summary to console."""
|
|
374
|
+
from rich.console import Console
|
|
375
|
+
from rich.panel import Panel
|
|
376
|
+
from rich.table import Table
|
|
377
|
+
|
|
378
|
+
console = Console()
|
|
379
|
+
stats = self.session_stats
|
|
380
|
+
|
|
381
|
+
# Create summary table
|
|
382
|
+
table = Table(show_header=True, header_style="bold cyan")
|
|
383
|
+
table.add_column("Metric")
|
|
384
|
+
table.add_column("Value", justify="right")
|
|
385
|
+
|
|
386
|
+
table.add_row("Session ID", stats.session_id)
|
|
387
|
+
table.add_row("Duration", f"{stats.duration_seconds:.1f}s")
|
|
388
|
+
table.add_row("Requests", str(stats.total_requests))
|
|
389
|
+
table.add_row("", "")
|
|
390
|
+
table.add_row("Input Tokens", f"{stats.total_input_tokens:,}")
|
|
391
|
+
table.add_row("Output Tokens", f"{stats.total_output_tokens:,}")
|
|
392
|
+
table.add_row("Total Tokens", f"{stats.total_tokens:,}")
|
|
393
|
+
table.add_row("Cached Tokens", f"{stats.total_cached_tokens:,}")
|
|
394
|
+
table.add_row("", "")
|
|
395
|
+
table.add_row("Total Cost", f"${stats.total_cost:.4f}")
|
|
396
|
+
table.add_row("Avg Cost/Request", f"${stats.avg_cost_per_request:.4f}")
|
|
397
|
+
|
|
398
|
+
console.print(Panel(table, title="💰 Session Statistics", border_style="green"))
|
|
399
|
+
|
|
400
|
+
# Print model breakdown if multiple models used
|
|
401
|
+
if len(stats.models_used) > 1:
|
|
402
|
+
model_table = Table(show_header=True, header_style="bold cyan")
|
|
403
|
+
model_table.add_column("Model")
|
|
404
|
+
model_table.add_column("Requests", justify="right")
|
|
405
|
+
|
|
406
|
+
for model, count in stats.models_used.items():
|
|
407
|
+
model_table.add_row(model, str(count))
|
|
408
|
+
|
|
409
|
+
console.print(Panel(model_table, title="Models Used", border_style="blue"))
|
|
410
|
+
|
|
411
|
+
def export_json(self, filepath: Optional[str] = None) -> str:
|
|
412
|
+
"""
|
|
413
|
+
Export session data to JSON.
|
|
414
|
+
|
|
415
|
+
Args:
|
|
416
|
+
filepath: Optional file path to write to
|
|
417
|
+
|
|
418
|
+
Returns:
|
|
419
|
+
JSON string
|
|
420
|
+
"""
|
|
421
|
+
data = {
|
|
422
|
+
"session": self.session_stats.to_dict(),
|
|
423
|
+
"requests": [r.to_dict() for r in self.request_history]
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
json_str = json.dumps(data, indent=2)
|
|
427
|
+
|
|
428
|
+
if filepath:
|
|
429
|
+
with open(filepath, 'w') as f:
|
|
430
|
+
f.write(json_str)
|
|
431
|
+
logger.info(f"Exported session data to {filepath}")
|
|
432
|
+
|
|
433
|
+
return json_str
|
|
434
|
+
|
|
435
|
+
def end_session(self) -> SessionStats:
|
|
436
|
+
"""Mark the session as ended."""
|
|
437
|
+
self.session_stats.end_time = datetime.now()
|
|
438
|
+
return self.session_stats
|
|
439
|
+
|
|
440
|
+
|
|
441
|
+
# ============================================================================
|
|
442
|
+
# CLI Integration Handler
|
|
443
|
+
# ============================================================================
|
|
444
|
+
|
|
445
|
+
class CostTrackerHandler:
|
|
446
|
+
"""
|
|
447
|
+
Handler for integrating cost tracking with PraisonAI CLI.
|
|
448
|
+
"""
|
|
449
|
+
|
|
450
|
+
def __init__(self, verbose: bool = False):
|
|
451
|
+
self.verbose = verbose
|
|
452
|
+
self._tracker: Optional[CostTracker] = None
|
|
453
|
+
|
|
454
|
+
@property
|
|
455
|
+
def feature_name(self) -> str:
|
|
456
|
+
return "cost_tracker"
|
|
457
|
+
|
|
458
|
+
def initialize(self, session_id: Optional[str] = None) -> CostTracker:
|
|
459
|
+
"""Initialize the cost tracker."""
|
|
460
|
+
self._tracker = CostTracker(
|
|
461
|
+
session_id=session_id,
|
|
462
|
+
verbose=self.verbose
|
|
463
|
+
)
|
|
464
|
+
|
|
465
|
+
if self.verbose:
|
|
466
|
+
from rich import print as rprint
|
|
467
|
+
rprint(f"[cyan]Cost tracking enabled for session: {self._tracker.session_id}[/cyan]")
|
|
468
|
+
|
|
469
|
+
return self._tracker
|
|
470
|
+
|
|
471
|
+
def get_tracker(self) -> Optional[CostTracker]:
|
|
472
|
+
"""Get the current cost tracker."""
|
|
473
|
+
return self._tracker
|
|
474
|
+
|
|
475
|
+
def track_request(
|
|
476
|
+
self,
|
|
477
|
+
model: str,
|
|
478
|
+
input_tokens: int,
|
|
479
|
+
output_tokens: int,
|
|
480
|
+
**kwargs
|
|
481
|
+
) -> Optional[RequestStats]:
|
|
482
|
+
"""Track a request."""
|
|
483
|
+
if not self._tracker:
|
|
484
|
+
self._tracker = self.initialize()
|
|
485
|
+
|
|
486
|
+
return self._tracker.track_request(
|
|
487
|
+
model=model,
|
|
488
|
+
input_tokens=input_tokens,
|
|
489
|
+
output_tokens=output_tokens,
|
|
490
|
+
**kwargs
|
|
491
|
+
)
|
|
492
|
+
|
|
493
|
+
def get_summary(self) -> Dict[str, Any]:
|
|
494
|
+
"""Get session summary as dict."""
|
|
495
|
+
if not self._tracker:
|
|
496
|
+
return {}
|
|
497
|
+
return self._tracker.session_stats.to_dict()
|
|
498
|
+
|
|
499
|
+
def print_summary(self) -> None:
|
|
500
|
+
"""Print session summary."""
|
|
501
|
+
if self._tracker:
|
|
502
|
+
self._tracker.print_summary()
|
|
503
|
+
|
|
504
|
+
def get_cost(self) -> float:
|
|
505
|
+
"""Get total cost."""
|
|
506
|
+
if self._tracker:
|
|
507
|
+
return self._tracker.get_total_cost()
|
|
508
|
+
return 0.0
|
|
509
|
+
|
|
510
|
+
def get_tokens(self) -> int:
|
|
511
|
+
"""Get total tokens."""
|
|
512
|
+
if self._tracker:
|
|
513
|
+
return self._tracker.get_total_tokens()
|
|
514
|
+
return 0
|