kubiya-control-plane-api 0.1.0__py3-none-any.whl → 0.3.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.
Potentially problematic release.
This version of kubiya-control-plane-api might be problematic. Click here for more details.
- control_plane_api/README.md +266 -0
- control_plane_api/__init__.py +0 -0
- control_plane_api/__version__.py +1 -0
- control_plane_api/alembic/README +1 -0
- control_plane_api/alembic/env.py +98 -0
- control_plane_api/alembic/script.py.mako +28 -0
- control_plane_api/alembic/versions/1382bec74309_initial_migration_with_all_models.py +251 -0
- control_plane_api/alembic/versions/1f54bc2a37e3_add_analytics_tables.py +162 -0
- control_plane_api/alembic/versions/2e4cb136dc10_rename_toolset_ids_to_skill_ids_in_teams.py +30 -0
- control_plane_api/alembic/versions/31cd69a644ce_add_skill_templates_table.py +28 -0
- control_plane_api/alembic/versions/89e127caa47d_add_jobs_and_job_executions_tables.py +161 -0
- control_plane_api/alembic/versions/add_llm_models_table.py +51 -0
- control_plane_api/alembic/versions/b0e10697f212_add_runtime_column_to_teams_simple.py +42 -0
- control_plane_api/alembic/versions/ce43b24b63bf_add_execution_trigger_source_and_fix_.py +155 -0
- control_plane_api/alembic/versions/d4eaf16e3f8d_rename_toolsets_to_skills.py +84 -0
- control_plane_api/alembic/versions/efa2dc427da1_rename_metadata_to_custom_metadata.py +32 -0
- control_plane_api/alembic/versions/f973b431d1ce_add_workflow_executor_to_skill_types.py +44 -0
- control_plane_api/alembic.ini +148 -0
- control_plane_api/api/index.py +12 -0
- control_plane_api/app/__init__.py +11 -0
- control_plane_api/app/activities/__init__.py +20 -0
- control_plane_api/app/activities/agent_activities.py +379 -0
- control_plane_api/app/activities/team_activities.py +410 -0
- control_plane_api/app/activities/temporal_cloud_activities.py +577 -0
- control_plane_api/app/config/__init__.py +35 -0
- control_plane_api/app/config/api_config.py +354 -0
- control_plane_api/app/config/model_pricing.py +318 -0
- control_plane_api/app/config.py +95 -0
- control_plane_api/app/database.py +135 -0
- control_plane_api/app/exceptions.py +408 -0
- control_plane_api/app/lib/__init__.py +11 -0
- control_plane_api/app/lib/job_executor.py +312 -0
- control_plane_api/app/lib/kubiya_client.py +235 -0
- control_plane_api/app/lib/litellm_pricing.py +166 -0
- control_plane_api/app/lib/planning_tools/__init__.py +22 -0
- control_plane_api/app/lib/planning_tools/agents.py +155 -0
- control_plane_api/app/lib/planning_tools/base.py +189 -0
- control_plane_api/app/lib/planning_tools/environments.py +214 -0
- control_plane_api/app/lib/planning_tools/resources.py +240 -0
- control_plane_api/app/lib/planning_tools/teams.py +198 -0
- control_plane_api/app/lib/policy_enforcer_client.py +939 -0
- control_plane_api/app/lib/redis_client.py +436 -0
- control_plane_api/app/lib/supabase.py +71 -0
- control_plane_api/app/lib/temporal_client.py +138 -0
- control_plane_api/app/lib/validation/__init__.py +20 -0
- control_plane_api/app/lib/validation/runtime_validation.py +287 -0
- control_plane_api/app/main.py +128 -0
- control_plane_api/app/middleware/__init__.py +8 -0
- control_plane_api/app/middleware/auth.py +513 -0
- control_plane_api/app/middleware/exception_handler.py +267 -0
- control_plane_api/app/middleware/rate_limiting.py +384 -0
- control_plane_api/app/middleware/request_id.py +202 -0
- control_plane_api/app/models/__init__.py +27 -0
- control_plane_api/app/models/agent.py +79 -0
- control_plane_api/app/models/analytics.py +206 -0
- control_plane_api/app/models/associations.py +81 -0
- control_plane_api/app/models/environment.py +63 -0
- control_plane_api/app/models/execution.py +93 -0
- control_plane_api/app/models/job.py +179 -0
- control_plane_api/app/models/llm_model.py +75 -0
- control_plane_api/app/models/presence.py +49 -0
- control_plane_api/app/models/project.py +47 -0
- control_plane_api/app/models/session.py +38 -0
- control_plane_api/app/models/team.py +66 -0
- control_plane_api/app/models/workflow.py +55 -0
- control_plane_api/app/policies/README.md +121 -0
- control_plane_api/app/policies/approved_users.rego +62 -0
- control_plane_api/app/policies/business_hours.rego +51 -0
- control_plane_api/app/policies/rate_limiting.rego +100 -0
- control_plane_api/app/policies/tool_restrictions.rego +86 -0
- control_plane_api/app/routers/__init__.py +4 -0
- control_plane_api/app/routers/agents.py +364 -0
- control_plane_api/app/routers/agents_v2.py +1260 -0
- control_plane_api/app/routers/analytics.py +1014 -0
- control_plane_api/app/routers/context_manager.py +562 -0
- control_plane_api/app/routers/environment_context.py +270 -0
- control_plane_api/app/routers/environments.py +715 -0
- control_plane_api/app/routers/execution_environment.py +517 -0
- control_plane_api/app/routers/executions.py +1911 -0
- control_plane_api/app/routers/health.py +92 -0
- control_plane_api/app/routers/health_v2.py +326 -0
- control_plane_api/app/routers/integrations.py +274 -0
- control_plane_api/app/routers/jobs.py +1344 -0
- control_plane_api/app/routers/models.py +82 -0
- control_plane_api/app/routers/models_v2.py +361 -0
- control_plane_api/app/routers/policies.py +639 -0
- control_plane_api/app/routers/presence.py +234 -0
- control_plane_api/app/routers/projects.py +902 -0
- control_plane_api/app/routers/runners.py +379 -0
- control_plane_api/app/routers/runtimes.py +172 -0
- control_plane_api/app/routers/secrets.py +155 -0
- control_plane_api/app/routers/skills.py +1001 -0
- control_plane_api/app/routers/skills_definitions.py +140 -0
- control_plane_api/app/routers/task_planning.py +1256 -0
- control_plane_api/app/routers/task_queues.py +654 -0
- control_plane_api/app/routers/team_context.py +270 -0
- control_plane_api/app/routers/teams.py +1400 -0
- control_plane_api/app/routers/worker_queues.py +1545 -0
- control_plane_api/app/routers/workers.py +935 -0
- control_plane_api/app/routers/workflows.py +204 -0
- control_plane_api/app/runtimes/__init__.py +6 -0
- control_plane_api/app/runtimes/validation.py +344 -0
- control_plane_api/app/schemas/job_schemas.py +295 -0
- control_plane_api/app/services/__init__.py +1 -0
- control_plane_api/app/services/agno_service.py +619 -0
- control_plane_api/app/services/litellm_service.py +190 -0
- control_plane_api/app/services/policy_service.py +525 -0
- control_plane_api/app/services/temporal_cloud_provisioning.py +150 -0
- control_plane_api/app/skills/__init__.py +44 -0
- control_plane_api/app/skills/base.py +229 -0
- control_plane_api/app/skills/business_intelligence.py +189 -0
- control_plane_api/app/skills/data_visualization.py +154 -0
- control_plane_api/app/skills/docker.py +104 -0
- control_plane_api/app/skills/file_generation.py +94 -0
- control_plane_api/app/skills/file_system.py +110 -0
- control_plane_api/app/skills/python.py +92 -0
- control_plane_api/app/skills/registry.py +65 -0
- control_plane_api/app/skills/shell.py +102 -0
- control_plane_api/app/skills/workflow_executor.py +469 -0
- control_plane_api/app/utils/workflow_executor.py +354 -0
- control_plane_api/app/workflows/__init__.py +11 -0
- control_plane_api/app/workflows/agent_execution.py +507 -0
- control_plane_api/app/workflows/agent_execution_with_skills.py +222 -0
- control_plane_api/app/workflows/namespace_provisioning.py +326 -0
- control_plane_api/app/workflows/team_execution.py +399 -0
- control_plane_api/scripts/seed_models.py +239 -0
- control_plane_api/worker/__init__.py +0 -0
- control_plane_api/worker/activities/__init__.py +0 -0
- control_plane_api/worker/activities/agent_activities.py +1241 -0
- control_plane_api/worker/activities/approval_activities.py +234 -0
- control_plane_api/worker/activities/runtime_activities.py +388 -0
- control_plane_api/worker/activities/skill_activities.py +267 -0
- control_plane_api/worker/activities/team_activities.py +1217 -0
- control_plane_api/worker/config/__init__.py +31 -0
- control_plane_api/worker/config/worker_config.py +275 -0
- control_plane_api/worker/control_plane_client.py +529 -0
- control_plane_api/worker/examples/analytics_integration_example.py +362 -0
- control_plane_api/worker/models/__init__.py +1 -0
- control_plane_api/worker/models/inputs.py +89 -0
- control_plane_api/worker/runtimes/__init__.py +31 -0
- control_plane_api/worker/runtimes/base.py +789 -0
- control_plane_api/worker/runtimes/claude_code_runtime.py +1443 -0
- control_plane_api/worker/runtimes/default_runtime.py +617 -0
- control_plane_api/worker/runtimes/factory.py +173 -0
- control_plane_api/worker/runtimes/validation.py +93 -0
- control_plane_api/worker/services/__init__.py +1 -0
- control_plane_api/worker/services/agent_executor.py +422 -0
- control_plane_api/worker/services/agent_executor_v2.py +383 -0
- control_plane_api/worker/services/analytics_collector.py +457 -0
- control_plane_api/worker/services/analytics_service.py +464 -0
- control_plane_api/worker/services/approval_tools.py +310 -0
- control_plane_api/worker/services/approval_tools_agno.py +207 -0
- control_plane_api/worker/services/cancellation_manager.py +177 -0
- control_plane_api/worker/services/data_visualization.py +827 -0
- control_plane_api/worker/services/jira_tools.py +257 -0
- control_plane_api/worker/services/runtime_analytics.py +328 -0
- control_plane_api/worker/services/session_service.py +194 -0
- control_plane_api/worker/services/skill_factory.py +175 -0
- control_plane_api/worker/services/team_executor.py +574 -0
- control_plane_api/worker/services/team_executor_v2.py +465 -0
- control_plane_api/worker/services/workflow_executor_tools.py +1418 -0
- control_plane_api/worker/tests/__init__.py +1 -0
- control_plane_api/worker/tests/e2e/__init__.py +0 -0
- control_plane_api/worker/tests/e2e/test_execution_flow.py +571 -0
- control_plane_api/worker/tests/integration/__init__.py +0 -0
- control_plane_api/worker/tests/integration/test_control_plane_integration.py +308 -0
- control_plane_api/worker/tests/unit/__init__.py +0 -0
- control_plane_api/worker/tests/unit/test_control_plane_client.py +401 -0
- control_plane_api/worker/utils/__init__.py +1 -0
- control_plane_api/worker/utils/chunk_batcher.py +305 -0
- control_plane_api/worker/utils/retry_utils.py +60 -0
- control_plane_api/worker/utils/streaming_utils.py +373 -0
- control_plane_api/worker/worker.py +753 -0
- control_plane_api/worker/workflows/__init__.py +0 -0
- control_plane_api/worker/workflows/agent_execution.py +589 -0
- control_plane_api/worker/workflows/team_execution.py +429 -0
- kubiya_control_plane_api-0.3.4.dist-info/METADATA +229 -0
- kubiya_control_plane_api-0.3.4.dist-info/RECORD +182 -0
- kubiya_control_plane_api-0.3.4.dist-info/entry_points.txt +2 -0
- kubiya_control_plane_api-0.3.4.dist-info/top_level.txt +1 -0
- kubiya_control_plane_api-0.1.0.dist-info/METADATA +0 -66
- kubiya_control_plane_api-0.1.0.dist-info/RECORD +0 -5
- kubiya_control_plane_api-0.1.0.dist-info/top_level.txt +0 -1
- {kubiya_control_plane_api-0.1.0.dist-info/licenses → control_plane_api}/LICENSE +0 -0
- {kubiya_control_plane_api-0.1.0.dist-info → kubiya_control_plane_api-0.3.4.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
"""
|
|
2
|
+
API-specific configuration.
|
|
3
|
+
|
|
4
|
+
This module contains settings specific to the Control Plane API server.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
8
|
+
from pydantic import Field, validator, model_validator, AliasChoices
|
|
9
|
+
from typing import List, Optional
|
|
10
|
+
import secrets
|
|
11
|
+
import os
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class APIConfig(BaseSettings):
|
|
15
|
+
"""Configuration for Control Plane API server."""
|
|
16
|
+
|
|
17
|
+
# ==================== API Server Settings ====================
|
|
18
|
+
|
|
19
|
+
api_host: str = Field(
|
|
20
|
+
default="0.0.0.0",
|
|
21
|
+
description="API server host",
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
api_port: int = Field(
|
|
25
|
+
default=8000,
|
|
26
|
+
description="API server port",
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
api_workers: int = Field(
|
|
30
|
+
default=4,
|
|
31
|
+
description="Number of API worker processes",
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
api_title: str = Field(
|
|
35
|
+
default="Agent Control Plane API",
|
|
36
|
+
description="API title for documentation",
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
api_version: str = Field(
|
|
40
|
+
default="0.3.0",
|
|
41
|
+
description="API version",
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
api_description: str = Field(
|
|
45
|
+
default="Multi-tenant agent orchestration with Temporal workflows",
|
|
46
|
+
description="API description for documentation",
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
# ==================== Environment ====================
|
|
50
|
+
|
|
51
|
+
environment: str = Field(
|
|
52
|
+
default="development",
|
|
53
|
+
description="Environment (development, staging, production)",
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
debug: bool = Field(
|
|
57
|
+
default=False,
|
|
58
|
+
description="Debug mode",
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
@validator("debug", pre=True)
|
|
62
|
+
def set_debug_from_env(cls, v, values):
|
|
63
|
+
"""Set debug based on environment if not explicitly set."""
|
|
64
|
+
if v is None:
|
|
65
|
+
env = values.get("environment", "development")
|
|
66
|
+
return env == "development"
|
|
67
|
+
return v
|
|
68
|
+
|
|
69
|
+
# ==================== Database Settings ====================
|
|
70
|
+
|
|
71
|
+
database_url: Optional[str] = Field(
|
|
72
|
+
default=None,
|
|
73
|
+
description="PostgreSQL database URL",
|
|
74
|
+
validation_alias=AliasChoices("DATABASE_URL", "database_url"),
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
supabase_url: Optional[str] = Field(
|
|
78
|
+
default=None,
|
|
79
|
+
description="Supabase project URL",
|
|
80
|
+
validation_alias=AliasChoices("SUPABASE_URL", "supabase_url"),
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
supabase_service_key: Optional[str] = Field(
|
|
84
|
+
default=None,
|
|
85
|
+
description="Supabase service role key",
|
|
86
|
+
validation_alias=AliasChoices("SUPABASE_SERVICE_KEY", "supabase_service_key"),
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
supabase_anon_key: Optional[str] = Field(
|
|
90
|
+
default=None,
|
|
91
|
+
description="Supabase anonymous key",
|
|
92
|
+
validation_alias=AliasChoices("SUPABASE_ANON_KEY", "supabase_anon_key"),
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
database_pool_size: int = Field(
|
|
96
|
+
default=20,
|
|
97
|
+
description="Database connection pool size",
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
database_max_overflow: int = Field(
|
|
101
|
+
default=40,
|
|
102
|
+
description="Maximum overflow for database pool",
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
database_pool_timeout: float = Field(
|
|
106
|
+
default=30.0,
|
|
107
|
+
description="Database pool timeout in seconds",
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
@model_validator(mode='after')
|
|
111
|
+
def validate_database_config(self):
|
|
112
|
+
"""Ensure we have either DATABASE_URL or Supabase configuration."""
|
|
113
|
+
# Always try to set database_url from Supabase env vars if not already set
|
|
114
|
+
if not self.database_url:
|
|
115
|
+
supabase_db_url = (
|
|
116
|
+
os.environ.get("SUPABASE_POSTGRES_URL") or
|
|
117
|
+
os.environ.get("SUPABASE_POSTGRES_PRISMA_URL") or
|
|
118
|
+
os.environ.get("SUPABASE_DB_URL")
|
|
119
|
+
)
|
|
120
|
+
if supabase_db_url:
|
|
121
|
+
# Fix URL format for SQLAlchemy 2.0+
|
|
122
|
+
if supabase_db_url.startswith("postgres://"):
|
|
123
|
+
supabase_db_url = supabase_db_url.replace("postgres://", "postgresql://", 1)
|
|
124
|
+
# Remove invalid Supabase pooler parameters that SQLAlchemy doesn't understand
|
|
125
|
+
supabase_db_url = supabase_db_url.replace("&supa=base-pooler.x", "")
|
|
126
|
+
self.database_url = supabase_db_url
|
|
127
|
+
elif not (self.supabase_url and self.supabase_service_key) and self.environment != "development":
|
|
128
|
+
raise ValueError(
|
|
129
|
+
"Either DATABASE_URL or Supabase configuration (SUPABASE_URL and SUPABASE_SERVICE_KEY) must be provided"
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
# Fix postgres:// to postgresql:// if needed and remove invalid params
|
|
133
|
+
if self.database_url:
|
|
134
|
+
if self.database_url.startswith("postgres://"):
|
|
135
|
+
self.database_url = self.database_url.replace("postgres://", "postgresql://", 1)
|
|
136
|
+
# Remove invalid Supabase pooler parameters from DATABASE_URL
|
|
137
|
+
self.database_url = self.database_url.replace("&supa=base-pooler.x", "")
|
|
138
|
+
|
|
139
|
+
return self
|
|
140
|
+
|
|
141
|
+
# ==================== Redis Settings ====================
|
|
142
|
+
|
|
143
|
+
redis_url: str = Field(
|
|
144
|
+
default="redis://localhost:6379/0",
|
|
145
|
+
description="Redis connection URL",
|
|
146
|
+
validation_alias=AliasChoices("REDIS_URL", "redis_url"),
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
redis_host: Optional[str] = Field(
|
|
150
|
+
default=None,
|
|
151
|
+
description="Redis host (overrides URL)",
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
redis_port: int = Field(
|
|
155
|
+
default=6379,
|
|
156
|
+
description="Redis port",
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
redis_password: Optional[str] = Field(
|
|
160
|
+
default=None,
|
|
161
|
+
description="Redis password",
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
redis_db: int = Field(
|
|
165
|
+
default=0,
|
|
166
|
+
description="Redis database number",
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
redis_pool_size: int = Field(
|
|
170
|
+
default=10,
|
|
171
|
+
description="Redis connection pool size",
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
# ==================== Temporal Settings ====================
|
|
175
|
+
|
|
176
|
+
temporal_host: str = Field(
|
|
177
|
+
default="localhost:7233",
|
|
178
|
+
description="Temporal server host:port",
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
temporal_namespace: str = Field(
|
|
182
|
+
default="default",
|
|
183
|
+
description="Temporal namespace",
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
temporal_client_cert_path: Optional[str] = Field(
|
|
187
|
+
default=None,
|
|
188
|
+
description="Path to Temporal client certificate",
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
temporal_client_key_path: Optional[str] = Field(
|
|
192
|
+
default=None,
|
|
193
|
+
description="Path to Temporal client key",
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
# ==================== Security Settings ====================
|
|
197
|
+
|
|
198
|
+
secret_key: str = Field(
|
|
199
|
+
default_factory=lambda: secrets.token_urlsafe(32),
|
|
200
|
+
description="Secret key for JWT signing",
|
|
201
|
+
validation_alias=AliasChoices("SECRET_KEY", "secret_key"),
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
algorithm: str = Field(
|
|
205
|
+
default="HS256",
|
|
206
|
+
description="JWT signing algorithm",
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
access_token_expire_minutes: int = Field(
|
|
210
|
+
default=30,
|
|
211
|
+
description="Access token expiration in minutes",
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
refresh_token_expire_days: int = Field(
|
|
215
|
+
default=30,
|
|
216
|
+
description="Refresh token expiration in days",
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
# ==================== CORS Settings ====================
|
|
220
|
+
|
|
221
|
+
cors_origins: List[str] = Field(
|
|
222
|
+
default=["*"],
|
|
223
|
+
description="Allowed CORS origins",
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
cors_allow_credentials: bool = Field(
|
|
227
|
+
default=True,
|
|
228
|
+
description="Allow credentials in CORS requests",
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
cors_allow_methods: List[str] = Field(
|
|
232
|
+
default=["*"],
|
|
233
|
+
description="Allowed CORS methods",
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
cors_allow_headers: List[str] = Field(
|
|
237
|
+
default=["*"],
|
|
238
|
+
description="Allowed CORS headers",
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
@validator("cors_origins", pre=True)
|
|
242
|
+
def validate_cors_origins(cls, v, values):
|
|
243
|
+
"""Validate CORS origins for production."""
|
|
244
|
+
# Allow environment variable to override default
|
|
245
|
+
if v is None or (isinstance(v, list) and len(v) == 1 and v[0] == "*"):
|
|
246
|
+
# Check if we're in production
|
|
247
|
+
env = values.get("environment", "development")
|
|
248
|
+
if env == "production":
|
|
249
|
+
# In production, use specific origins unless explicitly overridden
|
|
250
|
+
return [
|
|
251
|
+
"https://agent-control-plane.vercel.app",
|
|
252
|
+
"https://*.vercel.app",
|
|
253
|
+
"http://localhost:3000",
|
|
254
|
+
"http://localhost:8000",
|
|
255
|
+
]
|
|
256
|
+
return v
|
|
257
|
+
|
|
258
|
+
# ==================== External Services ====================
|
|
259
|
+
|
|
260
|
+
kubiya_api_base: str = Field(
|
|
261
|
+
default="https://api.kubiya.ai",
|
|
262
|
+
description="Kubiya API base URL",
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
kubiya_api_key: Optional[str] = Field(
|
|
266
|
+
default=None,
|
|
267
|
+
description="Kubiya API key",
|
|
268
|
+
validation_alias=AliasChoices("KUBIYA_API_KEY", "kubiya_api_key"),
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
litellm_api_base: str = Field(
|
|
272
|
+
default="https://llm-proxy.kubiya.ai",
|
|
273
|
+
description="LiteLLM proxy base URL",
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
litellm_api_key: Optional[str] = Field(
|
|
277
|
+
default=None,
|
|
278
|
+
description="LiteLLM API key",
|
|
279
|
+
validation_alias=AliasChoices("LITELLM_API_KEY", "litellm_api_key"),
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
litellm_default_model: str = Field(
|
|
283
|
+
default="kubiya/claude-sonnet-4",
|
|
284
|
+
description="Default LLM model",
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
litellm_timeout: int = Field(
|
|
288
|
+
default=300,
|
|
289
|
+
description="LiteLLM request timeout in seconds",
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
# ==================== Logging Settings ====================
|
|
293
|
+
|
|
294
|
+
log_level: str = Field(
|
|
295
|
+
default="INFO",
|
|
296
|
+
description="Logging level",
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
log_format: str = Field(
|
|
300
|
+
default="json",
|
|
301
|
+
description="Log format (json or text)",
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
# ==================== Monitoring Settings ====================
|
|
305
|
+
|
|
306
|
+
metrics_enabled: bool = Field(
|
|
307
|
+
default=False,
|
|
308
|
+
description="Enable Prometheus metrics",
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
metrics_port: int = Field(
|
|
312
|
+
default=9090,
|
|
313
|
+
description="Prometheus metrics port",
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
tracing_enabled: bool = Field(
|
|
317
|
+
default=False,
|
|
318
|
+
description="Enable OpenTelemetry tracing",
|
|
319
|
+
)
|
|
320
|
+
|
|
321
|
+
otlp_endpoint: Optional[str] = Field(
|
|
322
|
+
default=None,
|
|
323
|
+
description="OpenTelemetry collector endpoint",
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
sentry_dsn: Optional[str] = Field(
|
|
327
|
+
default=None,
|
|
328
|
+
description="Sentry DSN for error reporting",
|
|
329
|
+
validation_alias=AliasChoices("SENTRY_DSN", "sentry_dsn"),
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
# ==================== Rate Limiting ====================
|
|
333
|
+
|
|
334
|
+
rate_limit_enabled: bool = Field(
|
|
335
|
+
default=True,
|
|
336
|
+
description="Enable rate limiting",
|
|
337
|
+
)
|
|
338
|
+
|
|
339
|
+
rate_limit_requests_per_minute: int = Field(
|
|
340
|
+
default=60,
|
|
341
|
+
description="Default requests per minute limit",
|
|
342
|
+
)
|
|
343
|
+
|
|
344
|
+
rate_limit_burst_size: int = Field(
|
|
345
|
+
default=10,
|
|
346
|
+
description="Burst size for rate limiting",
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
model_config = SettingsConfigDict(
|
|
350
|
+
env_file=".env.local",
|
|
351
|
+
env_file_encoding="utf-8",
|
|
352
|
+
case_sensitive=False,
|
|
353
|
+
extra="ignore",
|
|
354
|
+
)
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Model Pricing and AEM Weight Configuration
|
|
3
|
+
|
|
4
|
+
This module defines pricing tiers and Agentic Engineering Minutes (AEM) weights
|
|
5
|
+
for different model families.
|
|
6
|
+
|
|
7
|
+
AEM Formula: Runtime (minutes) × Model Weight × Tool Calls Weight
|
|
8
|
+
|
|
9
|
+
Model Weights:
|
|
10
|
+
- Opus-class (most capable): 2.0x weight
|
|
11
|
+
- Sonnet-class (balanced): 1.0x weight
|
|
12
|
+
- Haiku-class (fast): 0.5x weight
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from typing import Dict, Any
|
|
16
|
+
from enum import Enum
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class ModelTier(str, Enum):
|
|
20
|
+
"""Model capability tiers"""
|
|
21
|
+
OPUS = "opus" # Most capable, highest cost
|
|
22
|
+
SONNET = "sonnet" # Balanced capability and cost
|
|
23
|
+
HAIKU = "haiku" # Fast and efficient
|
|
24
|
+
CUSTOM = "custom" # Custom/unknown models
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
# Model Weight Configuration for AEM Calculation
|
|
28
|
+
MODEL_WEIGHTS: Dict[str, float] = {
|
|
29
|
+
# Anthropic Claude Models
|
|
30
|
+
"claude-opus-4": 2.0,
|
|
31
|
+
"claude-4-opus": 2.0,
|
|
32
|
+
"claude-3-opus": 2.0,
|
|
33
|
+
"claude-3-opus-20240229": 2.0,
|
|
34
|
+
|
|
35
|
+
"claude-sonnet-4": 1.0,
|
|
36
|
+
"claude-4-sonnet": 1.0,
|
|
37
|
+
"claude-3.5-sonnet": 1.0,
|
|
38
|
+
"claude-3-5-sonnet-20241022": 1.0,
|
|
39
|
+
"claude-3-sonnet": 1.0,
|
|
40
|
+
"claude-3-sonnet-20240229": 1.0,
|
|
41
|
+
|
|
42
|
+
"claude-haiku-4": 0.5,
|
|
43
|
+
"claude-4-haiku": 0.5,
|
|
44
|
+
"claude-3.5-haiku": 0.5,
|
|
45
|
+
"claude-3-5-haiku-20241022": 0.5,
|
|
46
|
+
"claude-3-haiku": 0.5,
|
|
47
|
+
"claude-3-haiku-20240307": 0.5,
|
|
48
|
+
|
|
49
|
+
# OpenAI Models
|
|
50
|
+
"gpt-4": 2.0,
|
|
51
|
+
"gpt-4-turbo": 2.0,
|
|
52
|
+
"gpt-4-turbo-preview": 2.0,
|
|
53
|
+
"gpt-4-0125-preview": 2.0,
|
|
54
|
+
"gpt-4-1106-preview": 2.0,
|
|
55
|
+
"gpt-4o": 1.3, # As shown in the image
|
|
56
|
+
"gpt-4o-mini": 0.7,
|
|
57
|
+
|
|
58
|
+
"gpt-3.5-turbo": 0.5,
|
|
59
|
+
"gpt-3.5-turbo-16k": 0.5,
|
|
60
|
+
|
|
61
|
+
# Google Models
|
|
62
|
+
"gemini-1.5-pro": 1.5,
|
|
63
|
+
"gemini-1.5-flash": 0.7,
|
|
64
|
+
"gemini-pro": 1.0,
|
|
65
|
+
|
|
66
|
+
# Meta Models
|
|
67
|
+
"llama-3-70b": 1.2,
|
|
68
|
+
"llama-3-8b": 0.5,
|
|
69
|
+
|
|
70
|
+
# Mistral Models
|
|
71
|
+
"mistral-large": 1.5,
|
|
72
|
+
"mistral-medium": 1.0,
|
|
73
|
+
"mistral-small": 0.5,
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
# Token Pricing per 1M tokens (in USD)
|
|
78
|
+
TOKEN_PRICING: Dict[str, Dict[str, float]] = {
|
|
79
|
+
# Anthropic Claude
|
|
80
|
+
"claude-opus-4": {
|
|
81
|
+
"input": 15.00,
|
|
82
|
+
"output": 75.00,
|
|
83
|
+
"cache_read": 1.50,
|
|
84
|
+
"cache_creation": 18.75,
|
|
85
|
+
},
|
|
86
|
+
"claude-sonnet-4": {
|
|
87
|
+
"input": 3.00,
|
|
88
|
+
"output": 15.00,
|
|
89
|
+
"cache_read": 0.30,
|
|
90
|
+
"cache_creation": 3.75,
|
|
91
|
+
},
|
|
92
|
+
"claude-haiku-4": {
|
|
93
|
+
"input": 0.80,
|
|
94
|
+
"output": 4.00,
|
|
95
|
+
"cache_read": 0.08,
|
|
96
|
+
"cache_creation": 1.00,
|
|
97
|
+
},
|
|
98
|
+
|
|
99
|
+
# OpenAI
|
|
100
|
+
"gpt-4": {
|
|
101
|
+
"input": 30.00,
|
|
102
|
+
"output": 60.00,
|
|
103
|
+
"cache_read": 0.0,
|
|
104
|
+
"cache_creation": 0.0,
|
|
105
|
+
},
|
|
106
|
+
"gpt-4o": {
|
|
107
|
+
"input": 5.00,
|
|
108
|
+
"output": 15.00,
|
|
109
|
+
"cache_read": 0.0,
|
|
110
|
+
"cache_creation": 0.0,
|
|
111
|
+
},
|
|
112
|
+
"gpt-3.5-turbo": {
|
|
113
|
+
"input": 0.50,
|
|
114
|
+
"output": 1.50,
|
|
115
|
+
"cache_read": 0.0,
|
|
116
|
+
"cache_creation": 0.0,
|
|
117
|
+
},
|
|
118
|
+
|
|
119
|
+
# Google Gemini
|
|
120
|
+
"gemini-1.5-pro": {
|
|
121
|
+
"input": 3.50,
|
|
122
|
+
"output": 10.50,
|
|
123
|
+
"cache_read": 0.0,
|
|
124
|
+
"cache_creation": 0.0,
|
|
125
|
+
},
|
|
126
|
+
"gemini-1.5-flash": {
|
|
127
|
+
"input": 0.35,
|
|
128
|
+
"output": 1.05,
|
|
129
|
+
"cache_read": 0.0,
|
|
130
|
+
"cache_creation": 0.0,
|
|
131
|
+
},
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
# AEM Pricing Configuration
|
|
136
|
+
AEM_PRICING: Dict[str, float] = {
|
|
137
|
+
"saas_prepaid_per_minute": 0.15, # $0.15/min for SaaS/Hybrid
|
|
138
|
+
"on_prem_unlimited": 0.0, # Unlimited for on-prem
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def get_model_weight(model: str) -> float:
|
|
143
|
+
"""
|
|
144
|
+
Get the AEM weight for a model.
|
|
145
|
+
|
|
146
|
+
Args:
|
|
147
|
+
model: Model identifier
|
|
148
|
+
|
|
149
|
+
Returns:
|
|
150
|
+
Weight multiplier for AEM calculation (default 1.0)
|
|
151
|
+
"""
|
|
152
|
+
# Normalize model name
|
|
153
|
+
model_lower = model.lower().strip()
|
|
154
|
+
|
|
155
|
+
# Try exact match first
|
|
156
|
+
if model_lower in MODEL_WEIGHTS:
|
|
157
|
+
return MODEL_WEIGHTS[model_lower]
|
|
158
|
+
|
|
159
|
+
# Try fuzzy matching by keywords
|
|
160
|
+
if "opus" in model_lower:
|
|
161
|
+
return 2.0
|
|
162
|
+
elif "sonnet" in model_lower:
|
|
163
|
+
return 1.0
|
|
164
|
+
elif "haiku" in model_lower:
|
|
165
|
+
return 0.5
|
|
166
|
+
elif "gpt-4" in model_lower and "turbo" in model_lower:
|
|
167
|
+
return 2.0
|
|
168
|
+
elif "gpt-4o" in model_lower:
|
|
169
|
+
return 1.3
|
|
170
|
+
elif "gpt-3.5" in model_lower:
|
|
171
|
+
return 0.5
|
|
172
|
+
elif "gemini" in model_lower and "pro" in model_lower:
|
|
173
|
+
return 1.5
|
|
174
|
+
elif "gemini" in model_lower and "flash" in model_lower:
|
|
175
|
+
return 0.7
|
|
176
|
+
|
|
177
|
+
# Default weight
|
|
178
|
+
return 1.0
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def get_model_tier(model: str) -> ModelTier:
|
|
182
|
+
"""
|
|
183
|
+
Determine the tier/class of a model.
|
|
184
|
+
|
|
185
|
+
Args:
|
|
186
|
+
model: Model identifier
|
|
187
|
+
|
|
188
|
+
Returns:
|
|
189
|
+
ModelTier enum value
|
|
190
|
+
"""
|
|
191
|
+
weight = get_model_weight(model)
|
|
192
|
+
|
|
193
|
+
if weight >= 1.5:
|
|
194
|
+
return ModelTier.OPUS
|
|
195
|
+
elif weight >= 0.8:
|
|
196
|
+
return ModelTier.SONNET
|
|
197
|
+
elif weight >= 0.3:
|
|
198
|
+
return ModelTier.HAIKU
|
|
199
|
+
else:
|
|
200
|
+
return ModelTier.CUSTOM
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def calculate_token_cost(
|
|
204
|
+
model: str,
|
|
205
|
+
input_tokens: int,
|
|
206
|
+
output_tokens: int,
|
|
207
|
+
cache_read_tokens: int = 0,
|
|
208
|
+
cache_creation_tokens: int = 0,
|
|
209
|
+
) -> Dict[str, float]:
|
|
210
|
+
"""
|
|
211
|
+
Calculate token costs based on model pricing.
|
|
212
|
+
|
|
213
|
+
Args:
|
|
214
|
+
model: Model identifier
|
|
215
|
+
input_tokens: Number of input tokens
|
|
216
|
+
output_tokens: Number of output tokens
|
|
217
|
+
cache_read_tokens: Number of cached tokens read
|
|
218
|
+
cache_creation_tokens: Number of tokens used for cache creation
|
|
219
|
+
|
|
220
|
+
Returns:
|
|
221
|
+
Dict with cost breakdown
|
|
222
|
+
"""
|
|
223
|
+
# Normalize model name
|
|
224
|
+
model_lower = model.lower().strip()
|
|
225
|
+
|
|
226
|
+
# Get pricing (use closest match or default to sonnet pricing)
|
|
227
|
+
pricing = None
|
|
228
|
+
|
|
229
|
+
# Try exact match
|
|
230
|
+
for price_key in TOKEN_PRICING:
|
|
231
|
+
if price_key in model_lower or model_lower in price_key:
|
|
232
|
+
pricing = TOKEN_PRICING[price_key]
|
|
233
|
+
break
|
|
234
|
+
|
|
235
|
+
# Default to sonnet pricing if no match
|
|
236
|
+
if pricing is None:
|
|
237
|
+
pricing = TOKEN_PRICING["claude-sonnet-4"]
|
|
238
|
+
|
|
239
|
+
# Calculate costs (pricing is per 1M tokens)
|
|
240
|
+
input_cost = (input_tokens / 1_000_000) * pricing["input"]
|
|
241
|
+
output_cost = (output_tokens / 1_000_000) * pricing["output"]
|
|
242
|
+
cache_read_cost = (cache_read_tokens / 1_000_000) * pricing["cache_read"]
|
|
243
|
+
cache_creation_cost = (cache_creation_tokens / 1_000_000) * pricing["cache_creation"]
|
|
244
|
+
|
|
245
|
+
return {
|
|
246
|
+
"input_cost": round(input_cost, 6),
|
|
247
|
+
"output_cost": round(output_cost, 6),
|
|
248
|
+
"cache_read_cost": round(cache_read_cost, 6),
|
|
249
|
+
"cache_creation_cost": round(cache_creation_cost, 6),
|
|
250
|
+
"total_cost": round(input_cost + output_cost + cache_read_cost + cache_creation_cost, 6),
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def calculate_aem(
|
|
255
|
+
duration_ms: int,
|
|
256
|
+
model: str,
|
|
257
|
+
tool_calls_count: int,
|
|
258
|
+
tool_calls_weight: float = 1.0,
|
|
259
|
+
) -> Dict[str, float]:
|
|
260
|
+
"""
|
|
261
|
+
Calculate Agentic Engineering Minutes (AEM).
|
|
262
|
+
|
|
263
|
+
Formula: Runtime (minutes) × Model Weight × Tool Calls Weight
|
|
264
|
+
|
|
265
|
+
Args:
|
|
266
|
+
duration_ms: Turn duration in milliseconds
|
|
267
|
+
model: Model identifier
|
|
268
|
+
tool_calls_count: Number of tool calls in this turn
|
|
269
|
+
tool_calls_weight: Weight multiplier for tool complexity (default 1.0)
|
|
270
|
+
|
|
271
|
+
Returns:
|
|
272
|
+
Dict with AEM metrics
|
|
273
|
+
"""
|
|
274
|
+
# Convert duration to minutes
|
|
275
|
+
runtime_minutes = duration_ms / 60_000.0
|
|
276
|
+
|
|
277
|
+
# Get model weight
|
|
278
|
+
model_weight = get_model_weight(model)
|
|
279
|
+
|
|
280
|
+
# Calculate tool calls weight (simple linear for now, can be made more complex)
|
|
281
|
+
# Example: Each tool call adds to complexity
|
|
282
|
+
# From the image example: 200 tool calls = 3.9 weight
|
|
283
|
+
# This suggests approximately: tool_calls_weight = (tool_calls_count / 50) if tool_calls_count > 0 else 1.0
|
|
284
|
+
if tool_calls_count > 0:
|
|
285
|
+
calculated_tool_weight = max(1.0, tool_calls_count / 50.0) # Roughly matches 200 calls = 4.0
|
|
286
|
+
else:
|
|
287
|
+
calculated_tool_weight = 1.0
|
|
288
|
+
|
|
289
|
+
# Override with provided weight if specified
|
|
290
|
+
final_tool_weight = tool_calls_weight if tool_calls_weight != 1.0 else calculated_tool_weight
|
|
291
|
+
|
|
292
|
+
# Calculate AEM value
|
|
293
|
+
aem_value = runtime_minutes * model_weight * final_tool_weight
|
|
294
|
+
|
|
295
|
+
# Calculate AEM cost (using SaaS pricing)
|
|
296
|
+
aem_cost = aem_value * AEM_PRICING["saas_prepaid_per_minute"]
|
|
297
|
+
|
|
298
|
+
return {
|
|
299
|
+
"runtime_minutes": round(runtime_minutes, 4),
|
|
300
|
+
"model_weight": round(model_weight, 2),
|
|
301
|
+
"tool_calls_weight": round(final_tool_weight, 2),
|
|
302
|
+
"aem_value": round(aem_value, 4),
|
|
303
|
+
"aem_cost": round(aem_cost, 4),
|
|
304
|
+
"model_tier": get_model_tier(model).value,
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
# Export configuration for external use
|
|
309
|
+
__all__ = [
|
|
310
|
+
"ModelTier",
|
|
311
|
+
"MODEL_WEIGHTS",
|
|
312
|
+
"TOKEN_PRICING",
|
|
313
|
+
"AEM_PRICING",
|
|
314
|
+
"get_model_weight",
|
|
315
|
+
"get_model_tier",
|
|
316
|
+
"calculate_token_cost",
|
|
317
|
+
"calculate_aem",
|
|
318
|
+
]
|