kubiya-control-plane-api 0.9.15__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.
- control_plane_api/LICENSE +676 -0
- control_plane_api/README.md +350 -0
- control_plane_api/__init__.py +4 -0
- control_plane_api/__version__.py +8 -0
- control_plane_api/alembic/README +1 -0
- control_plane_api/alembic/env.py +121 -0
- control_plane_api/alembic/script.py.mako +28 -0
- control_plane_api/alembic/versions/2613c65c3dbe_initial_database_setup.py +32 -0
- control_plane_api/alembic/versions/2df520d4927d_merge_heads.py +28 -0
- control_plane_api/alembic/versions/43abf98d6a01_add_paused_status_to_executions.py +73 -0
- control_plane_api/alembic/versions/6289854264cb_merge_multiple_heads.py +28 -0
- control_plane_api/alembic/versions/6a4d4dc3d8dc_generate_execution_transitions.py +50 -0
- control_plane_api/alembic/versions/87d11cf0a783_add_disconnected_status_to_worker_.py +44 -0
- control_plane_api/alembic/versions/add_ephemeral_queue_support.py +85 -0
- control_plane_api/alembic/versions/add_model_type_to_llm_models.py +31 -0
- control_plane_api/alembic/versions/add_plan_executions_table.py +114 -0
- control_plane_api/alembic/versions/add_trace_span_tables.py +154 -0
- control_plane_api/alembic/versions/add_user_info_to_traces.py +36 -0
- control_plane_api/alembic/versions/adjusting_foreign_keys.py +32 -0
- control_plane_api/alembic/versions/b4983d976db2_initial_tables.py +1128 -0
- control_plane_api/alembic/versions/d181a3b40e71_rename_custom_metadata_to_metadata_in_.py +50 -0
- control_plane_api/alembic/versions/df9117888e82_add_missing_columns.py +82 -0
- control_plane_api/alembic/versions/f25de6ad895a_missing_migrations.py +34 -0
- control_plane_api/alembic/versions/f71305fb69b9_fix_ephemeral_queue_deletion_foreign_key.py +54 -0
- control_plane_api/alembic/versions/mark_local_exec_queues_as_ephemeral.py +68 -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 +384 -0
- control_plane_api/app/activities/plan_generation_activities.py +499 -0
- control_plane_api/app/activities/team_activities.py +424 -0
- control_plane_api/app/activities/temporal_cloud_activities.py +588 -0
- control_plane_api/app/config/__init__.py +35 -0
- control_plane_api/app/config/api_config.py +469 -0
- control_plane_api/app/config/config_loader.py +224 -0
- control_plane_api/app/config/model_pricing.py +323 -0
- control_plane_api/app/config/storage_config.py +159 -0
- control_plane_api/app/config.py +115 -0
- control_plane_api/app/controllers/__init__.py +0 -0
- control_plane_api/app/controllers/execution_environment_controller.py +1315 -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/environment.py +65 -0
- control_plane_api/app/lib/event_bus/__init__.py +17 -0
- control_plane_api/app/lib/event_bus/base.py +136 -0
- control_plane_api/app/lib/event_bus/manager.py +335 -0
- control_plane_api/app/lib/event_bus/providers/__init__.py +6 -0
- control_plane_api/app/lib/event_bus/providers/http_provider.py +166 -0
- control_plane_api/app/lib/event_bus/providers/nats_provider.py +324 -0
- control_plane_api/app/lib/event_bus/providers/redis_provider.py +233 -0
- control_plane_api/app/lib/event_bus/providers/websocket_provider.py +497 -0
- control_plane_api/app/lib/job_executor.py +330 -0
- control_plane_api/app/lib/kubiya_client.py +293 -0
- control_plane_api/app/lib/litellm_pricing.py +166 -0
- control_plane_api/app/lib/mcp_validation.py +163 -0
- control_plane_api/app/lib/nats/__init__.py +13 -0
- control_plane_api/app/lib/nats/credentials_manager.py +288 -0
- control_plane_api/app/lib/nats/listener.py +374 -0
- control_plane_api/app/lib/planning_prompt_builder.py +153 -0
- control_plane_api/app/lib/planning_tools/__init__.py +41 -0
- control_plane_api/app/lib/planning_tools/agents.py +409 -0
- control_plane_api/app/lib/planning_tools/agno_toolkit.py +836 -0
- control_plane_api/app/lib/planning_tools/base.py +119 -0
- control_plane_api/app/lib/planning_tools/cognitive_memory_tools.py +403 -0
- control_plane_api/app/lib/planning_tools/context_graph_tools.py +545 -0
- control_plane_api/app/lib/planning_tools/environments.py +218 -0
- control_plane_api/app/lib/planning_tools/knowledge.py +204 -0
- control_plane_api/app/lib/planning_tools/models.py +93 -0
- control_plane_api/app/lib/planning_tools/planning_service.py +646 -0
- control_plane_api/app/lib/planning_tools/resources.py +242 -0
- control_plane_api/app/lib/planning_tools/teams.py +334 -0
- control_plane_api/app/lib/policy_enforcer_client.py +1016 -0
- control_plane_api/app/lib/redis_client.py +803 -0
- control_plane_api/app/lib/sqlalchemy_utils.py +486 -0
- control_plane_api/app/lib/state_transition_tools/__init__.py +7 -0
- control_plane_api/app/lib/state_transition_tools/execution_context.py +388 -0
- control_plane_api/app/lib/storage/__init__.py +20 -0
- control_plane_api/app/lib/storage/base_provider.py +274 -0
- control_plane_api/app/lib/storage/provider_factory.py +157 -0
- control_plane_api/app/lib/storage/vercel_blob_provider.py +468 -0
- control_plane_api/app/lib/supabase.py +71 -0
- control_plane_api/app/lib/supabase_utils.py +138 -0
- control_plane_api/app/lib/task_planning/__init__.py +138 -0
- control_plane_api/app/lib/task_planning/agent_factory.py +308 -0
- control_plane_api/app/lib/task_planning/agents.py +389 -0
- control_plane_api/app/lib/task_planning/cache.py +218 -0
- control_plane_api/app/lib/task_planning/entity_resolver.py +273 -0
- control_plane_api/app/lib/task_planning/helpers.py +293 -0
- control_plane_api/app/lib/task_planning/hooks.py +474 -0
- control_plane_api/app/lib/task_planning/models.py +503 -0
- control_plane_api/app/lib/task_planning/plan_validator.py +166 -0
- control_plane_api/app/lib/task_planning/planning_workflow.py +2911 -0
- control_plane_api/app/lib/task_planning/runner.py +656 -0
- control_plane_api/app/lib/task_planning/streaming_hook.py +213 -0
- control_plane_api/app/lib/task_planning/workflow.py +424 -0
- control_plane_api/app/lib/templating/__init__.py +88 -0
- control_plane_api/app/lib/templating/compiler.py +278 -0
- control_plane_api/app/lib/templating/engine.py +178 -0
- control_plane_api/app/lib/templating/parsers/__init__.py +29 -0
- control_plane_api/app/lib/templating/parsers/base.py +96 -0
- control_plane_api/app/lib/templating/parsers/env.py +85 -0
- control_plane_api/app/lib/templating/parsers/graph.py +112 -0
- control_plane_api/app/lib/templating/parsers/secret.py +87 -0
- control_plane_api/app/lib/templating/parsers/simple.py +81 -0
- control_plane_api/app/lib/templating/resolver.py +366 -0
- control_plane_api/app/lib/templating/types.py +214 -0
- control_plane_api/app/lib/templating/validator.py +201 -0
- control_plane_api/app/lib/temporal_client.py +232 -0
- control_plane_api/app/lib/temporal_credentials_cache.py +178 -0
- control_plane_api/app/lib/temporal_credentials_service.py +203 -0
- control_plane_api/app/lib/validation/__init__.py +24 -0
- control_plane_api/app/lib/validation/runtime_validation.py +388 -0
- control_plane_api/app/main.py +531 -0
- control_plane_api/app/middleware/__init__.py +10 -0
- control_plane_api/app/middleware/auth.py +645 -0
- control_plane_api/app/middleware/exception_handler.py +267 -0
- control_plane_api/app/middleware/prometheus_middleware.py +173 -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 +40 -0
- control_plane_api/app/models/agent.py +90 -0
- control_plane_api/app/models/analytics.py +206 -0
- control_plane_api/app/models/associations.py +107 -0
- control_plane_api/app/models/auth_user.py +73 -0
- control_plane_api/app/models/context.py +161 -0
- control_plane_api/app/models/custom_integration.py +99 -0
- control_plane_api/app/models/environment.py +64 -0
- control_plane_api/app/models/execution.py +125 -0
- control_plane_api/app/models/execution_transition.py +50 -0
- control_plane_api/app/models/job.py +159 -0
- control_plane_api/app/models/llm_model.py +78 -0
- control_plane_api/app/models/orchestration.py +66 -0
- control_plane_api/app/models/plan_execution.py +102 -0
- control_plane_api/app/models/presence.py +49 -0
- control_plane_api/app/models/project.py +61 -0
- control_plane_api/app/models/project_management.py +85 -0
- control_plane_api/app/models/session.py +29 -0
- control_plane_api/app/models/skill.py +155 -0
- control_plane_api/app/models/system_tables.py +43 -0
- control_plane_api/app/models/task_planning.py +372 -0
- control_plane_api/app/models/team.py +86 -0
- control_plane_api/app/models/trace.py +257 -0
- control_plane_api/app/models/user_profile.py +54 -0
- control_plane_api/app/models/worker.py +221 -0
- control_plane_api/app/models/workflow.py +161 -0
- control_plane_api/app/models/workspace.py +50 -0
- control_plane_api/app/observability/__init__.py +177 -0
- control_plane_api/app/observability/context_logging.py +475 -0
- control_plane_api/app/observability/decorators.py +337 -0
- control_plane_api/app/observability/local_span_processor.py +702 -0
- control_plane_api/app/observability/metrics.py +303 -0
- control_plane_api/app/observability/middleware.py +246 -0
- control_plane_api/app/observability/optional.py +115 -0
- control_plane_api/app/observability/tracing.py +382 -0
- control_plane_api/app/policies/README.md +149 -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_enforcement/README.md +336 -0
- control_plane_api/app/policies/tool_enforcement/bash_command_validation.rego +71 -0
- control_plane_api/app/policies/tool_enforcement/business_hours_enforcement.rego +82 -0
- control_plane_api/app/policies/tool_enforcement/mcp_tool_allowlist.rego +58 -0
- control_plane_api/app/policies/tool_enforcement/production_safeguards.rego +80 -0
- control_plane_api/app/policies/tool_enforcement/role_based_tool_access.rego +44 -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 +382 -0
- control_plane_api/app/routers/agents_v2.py +1598 -0
- control_plane_api/app/routers/analytics.py +1310 -0
- control_plane_api/app/routers/auth.py +59 -0
- control_plane_api/app/routers/client_config.py +57 -0
- control_plane_api/app/routers/context_graph.py +561 -0
- control_plane_api/app/routers/context_manager.py +577 -0
- control_plane_api/app/routers/custom_integrations.py +490 -0
- control_plane_api/app/routers/enforcer.py +132 -0
- control_plane_api/app/routers/environment_context.py +252 -0
- control_plane_api/app/routers/environments.py +761 -0
- control_plane_api/app/routers/execution_environment.py +847 -0
- control_plane_api/app/routers/executions/__init__.py +28 -0
- control_plane_api/app/routers/executions/router.py +286 -0
- control_plane_api/app/routers/executions/services/__init__.py +22 -0
- control_plane_api/app/routers/executions/services/demo_worker_health.py +156 -0
- control_plane_api/app/routers/executions/services/status_service.py +420 -0
- control_plane_api/app/routers/executions/services/test_worker_health.py +480 -0
- control_plane_api/app/routers/executions/services/worker_health.py +514 -0
- control_plane_api/app/routers/executions/streaming/__init__.py +22 -0
- control_plane_api/app/routers/executions/streaming/deduplication.py +352 -0
- control_plane_api/app/routers/executions/streaming/event_buffer.py +353 -0
- control_plane_api/app/routers/executions/streaming/event_formatter.py +964 -0
- control_plane_api/app/routers/executions/streaming/history_loader.py +588 -0
- control_plane_api/app/routers/executions/streaming/live_source.py +693 -0
- control_plane_api/app/routers/executions/streaming/streamer.py +849 -0
- control_plane_api/app/routers/executions.py +4888 -0
- control_plane_api/app/routers/health.py +165 -0
- control_plane_api/app/routers/health_v2.py +394 -0
- control_plane_api/app/routers/integration_templates.py +496 -0
- control_plane_api/app/routers/integrations.py +287 -0
- control_plane_api/app/routers/jobs.py +1809 -0
- control_plane_api/app/routers/metrics.py +517 -0
- control_plane_api/app/routers/models.py +82 -0
- control_plane_api/app/routers/models_v2.py +628 -0
- control_plane_api/app/routers/plan_executions.py +1481 -0
- control_plane_api/app/routers/plan_generation_async.py +304 -0
- control_plane_api/app/routers/policies.py +669 -0
- control_plane_api/app/routers/presence.py +234 -0
- control_plane_api/app/routers/projects.py +987 -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 +171 -0
- control_plane_api/app/routers/skills.py +1010 -0
- control_plane_api/app/routers/skills_definitions.py +140 -0
- control_plane_api/app/routers/storage.py +456 -0
- control_plane_api/app/routers/task_planning.py +611 -0
- control_plane_api/app/routers/task_queues.py +650 -0
- control_plane_api/app/routers/team_context.py +274 -0
- control_plane_api/app/routers/teams.py +1747 -0
- control_plane_api/app/routers/templates.py +248 -0
- control_plane_api/app/routers/traces.py +571 -0
- control_plane_api/app/routers/websocket_client.py +479 -0
- control_plane_api/app/routers/websocket_executions_status.py +437 -0
- control_plane_api/app/routers/websocket_gateway.py +323 -0
- control_plane_api/app/routers/websocket_traces.py +576 -0
- control_plane_api/app/routers/worker_queues.py +2555 -0
- control_plane_api/app/routers/worker_websocket.py +419 -0
- control_plane_api/app/routers/workers.py +1004 -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/__init__.py +1 -0
- control_plane_api/app/schemas/job_schemas.py +302 -0
- control_plane_api/app/schemas/mcp_schemas.py +311 -0
- control_plane_api/app/schemas/template_schemas.py +133 -0
- control_plane_api/app/schemas/trace_schemas.py +168 -0
- control_plane_api/app/schemas/worker_queue_observability_schemas.py +165 -0
- control_plane_api/app/services/__init__.py +1 -0
- control_plane_api/app/services/agno_planning_strategy.py +233 -0
- control_plane_api/app/services/agno_service.py +838 -0
- control_plane_api/app/services/claude_code_planning_service.py +203 -0
- control_plane_api/app/services/context_graph_client.py +224 -0
- control_plane_api/app/services/custom_integration_service.py +415 -0
- control_plane_api/app/services/integration_resolution_service.py +345 -0
- control_plane_api/app/services/litellm_service.py +394 -0
- control_plane_api/app/services/plan_generator.py +79 -0
- control_plane_api/app/services/planning_strategy.py +66 -0
- control_plane_api/app/services/planning_strategy_factory.py +118 -0
- control_plane_api/app/services/policy_service.py +615 -0
- control_plane_api/app/services/state_transition_service.py +755 -0
- control_plane_api/app/services/storage_service.py +593 -0
- control_plane_api/app/services/temporal_cloud_provisioning.py +150 -0
- control_plane_api/app/services/toolsets/context_graph_skill.py +432 -0
- control_plane_api/app/services/trace_retention.py +354 -0
- control_plane_api/app/services/worker_queue_metrics_service.py +190 -0
- control_plane_api/app/services/workflow_cancellation_manager.py +135 -0
- control_plane_api/app/services/workflow_operations_service.py +611 -0
- control_plane_api/app/skills/__init__.py +100 -0
- control_plane_api/app/skills/base.py +239 -0
- control_plane_api/app/skills/builtin/__init__.py +37 -0
- control_plane_api/app/skills/builtin/agent_communication/__init__.py +8 -0
- control_plane_api/app/skills/builtin/agent_communication/skill.py +246 -0
- control_plane_api/app/skills/builtin/code_ingestion/__init__.py +4 -0
- control_plane_api/app/skills/builtin/code_ingestion/skill.py +267 -0
- control_plane_api/app/skills/builtin/cognitive_memory/__init__.py +4 -0
- control_plane_api/app/skills/builtin/cognitive_memory/skill.py +174 -0
- control_plane_api/app/skills/builtin/contextual_awareness/__init__.py +4 -0
- control_plane_api/app/skills/builtin/contextual_awareness/skill.py +387 -0
- control_plane_api/app/skills/builtin/data_visualization/__init__.py +4 -0
- control_plane_api/app/skills/builtin/data_visualization/skill.py +154 -0
- control_plane_api/app/skills/builtin/docker/__init__.py +4 -0
- control_plane_api/app/skills/builtin/docker/skill.py +104 -0
- control_plane_api/app/skills/builtin/file_generation/__init__.py +4 -0
- control_plane_api/app/skills/builtin/file_generation/skill.py +94 -0
- control_plane_api/app/skills/builtin/file_system/__init__.py +4 -0
- control_plane_api/app/skills/builtin/file_system/skill.py +110 -0
- control_plane_api/app/skills/builtin/knowledge_api/__init__.py +5 -0
- control_plane_api/app/skills/builtin/knowledge_api/skill.py +124 -0
- control_plane_api/app/skills/builtin/python/__init__.py +4 -0
- control_plane_api/app/skills/builtin/python/skill.py +92 -0
- control_plane_api/app/skills/builtin/remote_filesystem/__init__.py +5 -0
- control_plane_api/app/skills/builtin/remote_filesystem/skill.py +170 -0
- control_plane_api/app/skills/builtin/shell/__init__.py +4 -0
- control_plane_api/app/skills/builtin/shell/skill.py +161 -0
- control_plane_api/app/skills/builtin/slack/__init__.py +3 -0
- control_plane_api/app/skills/builtin/slack/skill.py +302 -0
- control_plane_api/app/skills/builtin/workflow_executor/__init__.py +4 -0
- control_plane_api/app/skills/builtin/workflow_executor/skill.py +469 -0
- control_plane_api/app/skills/business_intelligence.py +189 -0
- control_plane_api/app/skills/config.py +63 -0
- control_plane_api/app/skills/loaders/__init__.py +14 -0
- control_plane_api/app/skills/loaders/base.py +73 -0
- control_plane_api/app/skills/loaders/filesystem_loader.py +199 -0
- control_plane_api/app/skills/registry.py +125 -0
- control_plane_api/app/utils/helpers.py +12 -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 +520 -0
- control_plane_api/app/workflows/agent_execution_with_skills.py +223 -0
- control_plane_api/app/workflows/namespace_provisioning.py +326 -0
- control_plane_api/app/workflows/plan_generation.py +254 -0
- control_plane_api/app/workflows/team_execution.py +442 -0
- control_plane_api/scripts/seed_models.py +240 -0
- control_plane_api/scripts/validate_existing_tool_names.py +492 -0
- control_plane_api/shared/__init__.py +8 -0
- control_plane_api/shared/version.py +17 -0
- control_plane_api/test_deduplication.py +274 -0
- control_plane_api/test_executor_deduplication_e2e.py +309 -0
- control_plane_api/test_job_execution_e2e.py +283 -0
- control_plane_api/test_real_integration.py +193 -0
- control_plane_api/version.py +38 -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 +1585 -0
- control_plane_api/worker/activities/approval_activities.py +234 -0
- control_plane_api/worker/activities/job_activities.py +199 -0
- control_plane_api/worker/activities/runtime_activities.py +1167 -0
- control_plane_api/worker/activities/skill_activities.py +282 -0
- control_plane_api/worker/activities/team_activities.py +479 -0
- control_plane_api/worker/agent_runtime_server.py +370 -0
- control_plane_api/worker/binary_manager.py +333 -0
- control_plane_api/worker/config/__init__.py +31 -0
- control_plane_api/worker/config/worker_config.py +273 -0
- control_plane_api/worker/control_plane_client.py +1491 -0
- control_plane_api/worker/examples/analytics_integration_example.py +362 -0
- control_plane_api/worker/health_monitor.py +159 -0
- control_plane_api/worker/metrics.py +237 -0
- control_plane_api/worker/models/__init__.py +1 -0
- control_plane_api/worker/models/error_events.py +105 -0
- control_plane_api/worker/models/inputs.py +89 -0
- control_plane_api/worker/runtimes/__init__.py +35 -0
- control_plane_api/worker/runtimes/agent_runtime/runtime.py +485 -0
- control_plane_api/worker/runtimes/agno/__init__.py +34 -0
- control_plane_api/worker/runtimes/agno/config.py +248 -0
- control_plane_api/worker/runtimes/agno/hooks.py +385 -0
- control_plane_api/worker/runtimes/agno/mcp_builder.py +195 -0
- control_plane_api/worker/runtimes/agno/runtime.py +1063 -0
- control_plane_api/worker/runtimes/agno/utils.py +163 -0
- control_plane_api/worker/runtimes/base.py +979 -0
- control_plane_api/worker/runtimes/claude_code/__init__.py +38 -0
- control_plane_api/worker/runtimes/claude_code/cleanup.py +184 -0
- control_plane_api/worker/runtimes/claude_code/client_pool.py +529 -0
- control_plane_api/worker/runtimes/claude_code/config.py +829 -0
- control_plane_api/worker/runtimes/claude_code/hooks.py +482 -0
- control_plane_api/worker/runtimes/claude_code/litellm_proxy.py +1702 -0
- control_plane_api/worker/runtimes/claude_code/mcp_builder.py +467 -0
- control_plane_api/worker/runtimes/claude_code/mcp_discovery.py +558 -0
- control_plane_api/worker/runtimes/claude_code/runtime.py +1546 -0
- control_plane_api/worker/runtimes/claude_code/tool_mapper.py +403 -0
- control_plane_api/worker/runtimes/claude_code/utils.py +149 -0
- control_plane_api/worker/runtimes/factory.py +173 -0
- control_plane_api/worker/runtimes/model_utils.py +107 -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_communication_tools.py +908 -0
- control_plane_api/worker/services/agent_executor.py +485 -0
- control_plane_api/worker/services/agent_executor_v2.py +793 -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/code_ingestion_tools.py +465 -0
- control_plane_api/worker/services/contextual_awareness_tools.py +405 -0
- control_plane_api/worker/services/data_visualization.py +834 -0
- control_plane_api/worker/services/event_publisher.py +531 -0
- control_plane_api/worker/services/jira_tools.py +257 -0
- control_plane_api/worker/services/remote_filesystem_tools.py +498 -0
- control_plane_api/worker/services/runtime_analytics.py +328 -0
- control_plane_api/worker/services/session_service.py +365 -0
- control_plane_api/worker/services/skill_context_enhancement.py +181 -0
- control_plane_api/worker/services/skill_factory.py +471 -0
- control_plane_api/worker/services/system_prompt_enhancement.py +410 -0
- control_plane_api/worker/services/team_executor.py +715 -0
- control_plane_api/worker/services/team_executor_v2.py +1866 -0
- control_plane_api/worker/services/tool_enforcement.py +254 -0
- control_plane_api/worker/services/workflow_executor/__init__.py +52 -0
- control_plane_api/worker/services/workflow_executor/event_processor.py +287 -0
- control_plane_api/worker/services/workflow_executor/event_publisher.py +210 -0
- control_plane_api/worker/services/workflow_executor/executors/__init__.py +15 -0
- control_plane_api/worker/services/workflow_executor/executors/base.py +270 -0
- control_plane_api/worker/services/workflow_executor/executors/json_executor.py +50 -0
- control_plane_api/worker/services/workflow_executor/executors/python_executor.py +50 -0
- control_plane_api/worker/services/workflow_executor/models.py +142 -0
- control_plane_api/worker/services/workflow_executor_tools.py +1748 -0
- control_plane_api/worker/skills/__init__.py +12 -0
- control_plane_api/worker/skills/builtin/context_graph_search/README.md +213 -0
- control_plane_api/worker/skills/builtin/context_graph_search/__init__.py +5 -0
- control_plane_api/worker/skills/builtin/context_graph_search/agno_impl.py +808 -0
- control_plane_api/worker/skills/builtin/context_graph_search/skill.yaml +67 -0
- control_plane_api/worker/skills/builtin/contextual_awareness/__init__.py +4 -0
- control_plane_api/worker/skills/builtin/contextual_awareness/agno_impl.py +62 -0
- control_plane_api/worker/skills/builtin/data_visualization/agno_impl.py +18 -0
- control_plane_api/worker/skills/builtin/data_visualization/skill.yaml +84 -0
- control_plane_api/worker/skills/builtin/docker/agno_impl.py +65 -0
- control_plane_api/worker/skills/builtin/docker/skill.yaml +60 -0
- control_plane_api/worker/skills/builtin/file_generation/agno_impl.py +47 -0
- control_plane_api/worker/skills/builtin/file_generation/skill.yaml +64 -0
- control_plane_api/worker/skills/builtin/file_system/agno_impl.py +32 -0
- control_plane_api/worker/skills/builtin/file_system/skill.yaml +54 -0
- control_plane_api/worker/skills/builtin/knowledge_api/__init__.py +4 -0
- control_plane_api/worker/skills/builtin/knowledge_api/agno_impl.py +50 -0
- control_plane_api/worker/skills/builtin/knowledge_api/skill.yaml +66 -0
- control_plane_api/worker/skills/builtin/python/agno_impl.py +25 -0
- control_plane_api/worker/skills/builtin/python/skill.yaml +60 -0
- control_plane_api/worker/skills/builtin/schema_fix_mixin.py +260 -0
- control_plane_api/worker/skills/builtin/shell/agno_impl.py +31 -0
- control_plane_api/worker/skills/builtin/shell/skill.yaml +60 -0
- control_plane_api/worker/skills/builtin/slack/__init__.py +3 -0
- control_plane_api/worker/skills/builtin/slack/agno_impl.py +1282 -0
- control_plane_api/worker/skills/builtin/slack/skill.yaml +276 -0
- control_plane_api/worker/skills/builtin/workflow_executor/agno_impl.py +62 -0
- control_plane_api/worker/skills/builtin/workflow_executor/skill.yaml +79 -0
- control_plane_api/worker/skills/loaders/__init__.py +5 -0
- control_plane_api/worker/skills/loaders/base.py +23 -0
- control_plane_api/worker/skills/loaders/filesystem_loader.py +357 -0
- control_plane_api/worker/skills/registry.py +208 -0
- control_plane_api/worker/tests/__init__.py +1 -0
- control_plane_api/worker/tests/conftest.py +12 -0
- control_plane_api/worker/tests/e2e/__init__.py +0 -0
- control_plane_api/worker/tests/e2e/test_context_graph_real_api.py +338 -0
- control_plane_api/worker/tests/e2e/test_context_graph_templates_e2e.py +523 -0
- control_plane_api/worker/tests/e2e/test_enforcement_e2e.py +344 -0
- control_plane_api/worker/tests/e2e/test_execution_flow.py +571 -0
- control_plane_api/worker/tests/e2e/test_single_execution_mode.py +656 -0
- control_plane_api/worker/tests/integration/__init__.py +0 -0
- control_plane_api/worker/tests/integration/test_builtin_skills_fixes.py +245 -0
- control_plane_api/worker/tests/integration/test_context_graph_search_integration.py +365 -0
- control_plane_api/worker/tests/integration/test_control_plane_integration.py +308 -0
- control_plane_api/worker/tests/integration/test_hook_enforcement_integration.py +579 -0
- control_plane_api/worker/tests/integration/test_scheduled_job_workflow.py +237 -0
- control_plane_api/worker/tests/integration/test_system_prompt_enhancement_integration.py +343 -0
- control_plane_api/worker/tests/unit/__init__.py +0 -0
- control_plane_api/worker/tests/unit/test_builtin_skill_autoload.py +396 -0
- control_plane_api/worker/tests/unit/test_context_graph_search.py +450 -0
- control_plane_api/worker/tests/unit/test_context_graph_templates.py +403 -0
- control_plane_api/worker/tests/unit/test_control_plane_client.py +401 -0
- control_plane_api/worker/tests/unit/test_control_plane_client_jobs.py +345 -0
- control_plane_api/worker/tests/unit/test_job_activities.py +353 -0
- control_plane_api/worker/tests/unit/test_skill_context_enhancement.py +321 -0
- control_plane_api/worker/tests/unit/test_system_prompt_enhancement.py +415 -0
- control_plane_api/worker/tests/unit/test_tool_enforcement.py +324 -0
- control_plane_api/worker/utils/__init__.py +1 -0
- control_plane_api/worker/utils/chunk_batcher.py +330 -0
- control_plane_api/worker/utils/environment.py +65 -0
- control_plane_api/worker/utils/error_publisher.py +260 -0
- control_plane_api/worker/utils/event_batcher.py +256 -0
- control_plane_api/worker/utils/logging_config.py +335 -0
- control_plane_api/worker/utils/logging_helper.py +326 -0
- control_plane_api/worker/utils/parameter_validator.py +120 -0
- control_plane_api/worker/utils/retry_utils.py +60 -0
- control_plane_api/worker/utils/streaming_utils.py +665 -0
- control_plane_api/worker/utils/tool_validation.py +332 -0
- control_plane_api/worker/utils/workspace_manager.py +163 -0
- control_plane_api/worker/websocket_client.py +393 -0
- control_plane_api/worker/worker.py +1297 -0
- control_plane_api/worker/workflows/__init__.py +0 -0
- control_plane_api/worker/workflows/agent_execution.py +909 -0
- control_plane_api/worker/workflows/scheduled_job_wrapper.py +332 -0
- control_plane_api/worker/workflows/team_execution.py +611 -0
- kubiya_control_plane_api-0.9.15.dist-info/METADATA +354 -0
- kubiya_control_plane_api-0.9.15.dist-info/RECORD +479 -0
- kubiya_control_plane_api-0.9.15.dist-info/WHEEL +5 -0
- kubiya_control_plane_api-0.9.15.dist-info/entry_points.txt +5 -0
- kubiya_control_plane_api-0.9.15.dist-info/licenses/LICENSE +676 -0
- kubiya_control_plane_api-0.9.15.dist-info/top_level.txt +3 -0
- scripts/__init__.py +1 -0
- scripts/migrations.py +39 -0
- scripts/seed_worker_queues.py +128 -0
- scripts/setup_agent_runtime.py +142 -0
- worker_internal/__init__.py +1 -0
- worker_internal/planner/__init__.py +1 -0
- worker_internal/planner/activities.py +1499 -0
- worker_internal/planner/agent_tools.py +197 -0
- worker_internal/planner/event_models.py +148 -0
- worker_internal/planner/event_publisher.py +67 -0
- worker_internal/planner/models.py +199 -0
- worker_internal/planner/retry_logic.py +134 -0
- worker_internal/planner/worker.py +300 -0
- worker_internal/planner/workflows.py +970 -0
|
@@ -0,0 +1,628 @@
|
|
|
1
|
+
"""
|
|
2
|
+
LLM Models CRUD API with LiteLLM Integration
|
|
3
|
+
|
|
4
|
+
This router provides model management with native LiteLLM integration.
|
|
5
|
+
Models are fetched dynamically from the LiteLLM server with caching for performance.
|
|
6
|
+
"""
|
|
7
|
+
from fastapi import APIRouter, Depends, HTTPException, status, Request, Query, BackgroundTasks
|
|
8
|
+
from typing import List, Optional, Dict, Any
|
|
9
|
+
from pydantic import BaseModel, Field
|
|
10
|
+
from sqlalchemy.orm import Session
|
|
11
|
+
from datetime import datetime, timedelta
|
|
12
|
+
import structlog
|
|
13
|
+
|
|
14
|
+
from control_plane_api.app.middleware.auth import get_current_organization
|
|
15
|
+
from control_plane_api.app.database import get_db
|
|
16
|
+
from control_plane_api.app.models.llm_model import LLMModel as LLMModelDB
|
|
17
|
+
from control_plane_api.app.services.litellm_service import litellm_service
|
|
18
|
+
from control_plane_api.app.config import settings
|
|
19
|
+
|
|
20
|
+
logger = structlog.get_logger()
|
|
21
|
+
|
|
22
|
+
router = APIRouter()
|
|
23
|
+
|
|
24
|
+
# Cache for LiteLLM models (in-memory cache with TTL)
|
|
25
|
+
_models_cache: Optional[Dict[str, Any]] = None
|
|
26
|
+
_cache_timestamp: Optional[datetime] = None
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
# ==================== Pydantic Schemas ====================
|
|
30
|
+
|
|
31
|
+
class LLMModelCreate(BaseModel):
|
|
32
|
+
"""Schema for creating a new LLM model"""
|
|
33
|
+
value: str = Field(..., description="Model identifier (e.g., 'kubiya/claude-sonnet-4')")
|
|
34
|
+
label: str = Field(..., description="Display name (e.g., 'Claude Sonnet 4')")
|
|
35
|
+
provider: str = Field(..., description="Provider name (e.g., 'Anthropic', 'OpenAI')")
|
|
36
|
+
model_type: str = Field("text-generation", description="Model type: 'text-generation', 'embedding', 'multimodal'")
|
|
37
|
+
logo: Optional[str] = Field(None, description="Logo path or URL")
|
|
38
|
+
description: Optional[str] = Field(None, description="Model description")
|
|
39
|
+
enabled: bool = Field(True, description="Whether model is enabled")
|
|
40
|
+
recommended: bool = Field(False, description="Whether model is recommended by default")
|
|
41
|
+
compatible_runtimes: List[str] = Field(
|
|
42
|
+
default_factory=list,
|
|
43
|
+
description="List of compatible runtime IDs (e.g., ['default', 'claude_code'])"
|
|
44
|
+
)
|
|
45
|
+
capabilities: dict = Field(
|
|
46
|
+
default_factory=dict,
|
|
47
|
+
description="Model capabilities (e.g., {'vision': true, 'max_tokens': 4096})"
|
|
48
|
+
)
|
|
49
|
+
pricing: Optional[dict] = Field(None, description="Pricing information")
|
|
50
|
+
display_order: int = Field(1000, description="Display order (lower = shown first)")
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class LLMModelUpdate(BaseModel):
|
|
54
|
+
"""Schema for updating an existing LLM model"""
|
|
55
|
+
value: Optional[str] = None
|
|
56
|
+
label: Optional[str] = None
|
|
57
|
+
provider: Optional[str] = None
|
|
58
|
+
model_type: Optional[str] = None
|
|
59
|
+
logo: Optional[str] = None
|
|
60
|
+
description: Optional[str] = None
|
|
61
|
+
enabled: Optional[bool] = None
|
|
62
|
+
recommended: Optional[bool] = None
|
|
63
|
+
compatible_runtimes: Optional[List[str]] = None
|
|
64
|
+
capabilities: Optional[dict] = None
|
|
65
|
+
pricing: Optional[dict] = None
|
|
66
|
+
display_order: Optional[int] = None
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class LLMModelResponse(BaseModel):
|
|
70
|
+
"""Schema for LLM model responses"""
|
|
71
|
+
id: str
|
|
72
|
+
value: str
|
|
73
|
+
label: str
|
|
74
|
+
provider: str
|
|
75
|
+
model_type: str
|
|
76
|
+
logo: Optional[str]
|
|
77
|
+
description: Optional[str]
|
|
78
|
+
enabled: bool
|
|
79
|
+
recommended: bool
|
|
80
|
+
compatible_runtimes: List[str]
|
|
81
|
+
capabilities: dict
|
|
82
|
+
pricing: Optional[dict]
|
|
83
|
+
display_order: int
|
|
84
|
+
created_at: str
|
|
85
|
+
updated_at: str
|
|
86
|
+
|
|
87
|
+
class Config:
|
|
88
|
+
from_attributes = True
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
# ==================== Helper Functions ====================
|
|
92
|
+
|
|
93
|
+
async def fetch_models_from_litellm_cached(db: Optional[Session] = None) -> List[Dict[str, Any]]:
|
|
94
|
+
"""
|
|
95
|
+
Fetch models from LiteLLM with caching and database fallback.
|
|
96
|
+
|
|
97
|
+
Uses in-memory cache with configurable TTL to avoid hitting LiteLLM on every request.
|
|
98
|
+
Falls back to:
|
|
99
|
+
1. Stale cache if LiteLLM is temporarily unavailable
|
|
100
|
+
2. Database models if no cache available
|
|
101
|
+
3. Empty list if all sources fail
|
|
102
|
+
|
|
103
|
+
This makes the system work seamlessly in all scenarios:
|
|
104
|
+
- With LiteLLM gateway: Returns live models
|
|
105
|
+
- Without LiteLLM: Returns database models (manually configured)
|
|
106
|
+
- Production: Works with or without LiteLLM endpoint
|
|
107
|
+
"""
|
|
108
|
+
global _models_cache, _cache_timestamp
|
|
109
|
+
|
|
110
|
+
# Check if cache is valid
|
|
111
|
+
if _models_cache is not None and _cache_timestamp is not None:
|
|
112
|
+
cache_age = datetime.utcnow() - _cache_timestamp
|
|
113
|
+
if cache_age.total_seconds() < settings.litellm_models_cache_ttl:
|
|
114
|
+
logger.debug("returning_cached_models", count=len(_models_cache.get("models", [])), source=_models_cache.get("source", "unknown"))
|
|
115
|
+
return _models_cache.get("models", [])
|
|
116
|
+
|
|
117
|
+
# Cache miss or expired - try to fetch from LiteLLM
|
|
118
|
+
try:
|
|
119
|
+
logger.info("fetching_models_from_litellm", base_url=settings.litellm_api_base)
|
|
120
|
+
models = await litellm_service.fetch_available_models()
|
|
121
|
+
|
|
122
|
+
if models:
|
|
123
|
+
# Update cache with LiteLLM models
|
|
124
|
+
_models_cache = {"models": models, "source": "litellm"}
|
|
125
|
+
_cache_timestamp = datetime.utcnow()
|
|
126
|
+
|
|
127
|
+
logger.info("models_fetched_from_litellm", count=len(models), ttl=settings.litellm_models_cache_ttl)
|
|
128
|
+
return models
|
|
129
|
+
|
|
130
|
+
except Exception as e:
|
|
131
|
+
logger.warning("litellm_fetch_failed", error=str(e), msg="Trying fallbacks...")
|
|
132
|
+
|
|
133
|
+
# Fallback 1: Return stale cache if available
|
|
134
|
+
if _models_cache is not None and _models_cache.get("models"):
|
|
135
|
+
cache_age = (datetime.utcnow() - _cache_timestamp).total_seconds() if _cache_timestamp else float('inf')
|
|
136
|
+
logger.warning("returning_stale_cache", age_seconds=cache_age, source=_models_cache.get("source", "unknown"))
|
|
137
|
+
return _models_cache.get("models", [])
|
|
138
|
+
|
|
139
|
+
# Fallback 2: Fetch from database if available
|
|
140
|
+
if db is not None:
|
|
141
|
+
try:
|
|
142
|
+
logger.info("fetching_models_from_database")
|
|
143
|
+
db_models = db.query(LLMModelDB).filter(LLMModelDB.enabled == True).all()
|
|
144
|
+
|
|
145
|
+
if db_models:
|
|
146
|
+
# Convert database models to OpenAI format for consistency
|
|
147
|
+
models = [
|
|
148
|
+
{
|
|
149
|
+
"id": model.value,
|
|
150
|
+
"object": "model",
|
|
151
|
+
"created": int(model.created_at.timestamp()) if model.created_at else 0,
|
|
152
|
+
"owned_by": model.provider
|
|
153
|
+
}
|
|
154
|
+
for model in db_models
|
|
155
|
+
]
|
|
156
|
+
|
|
157
|
+
# Cache database models
|
|
158
|
+
_models_cache = {"models": models, "source": "database"}
|
|
159
|
+
_cache_timestamp = datetime.utcnow()
|
|
160
|
+
|
|
161
|
+
logger.info("models_fetched_from_database", count=len(models))
|
|
162
|
+
return models
|
|
163
|
+
|
|
164
|
+
except Exception as e:
|
|
165
|
+
logger.error("database_fetch_failed", error=str(e))
|
|
166
|
+
|
|
167
|
+
# No models available from any source
|
|
168
|
+
logger.error("no_models_available_from_any_source")
|
|
169
|
+
return []
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def convert_litellm_model_to_response(litellm_model: Dict[str, Any]) -> LLMModelResponse:
|
|
173
|
+
"""Convert a LiteLLM model dict to our response format"""
|
|
174
|
+
model_id = litellm_model.get("id", "")
|
|
175
|
+
|
|
176
|
+
# Provider mapping for logo resolution
|
|
177
|
+
provider_logo_map = {
|
|
178
|
+
"anthropic": "/thirdparty/logos/anthropic.svg",
|
|
179
|
+
"openai": "/thirdparty/logos/openai.svg",
|
|
180
|
+
"google": "/thirdparty/logos/google.svg",
|
|
181
|
+
"mistral": "/thirdparty/logos/mistral.svg",
|
|
182
|
+
"groq": "/thirdparty/logos/groq.svg",
|
|
183
|
+
"deepseek": "/thirdparty/logos/deepseek.svg",
|
|
184
|
+
"xai": "/thirdparty/logos/xai.svg",
|
|
185
|
+
"meta": "/logos/meta-logo.svg",
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
# Extract provider - handle both "provider/model" and model names
|
|
189
|
+
if "/" in model_id:
|
|
190
|
+
# Format: kubiya/claude-sonnet-4 or openai/gpt-4
|
|
191
|
+
prefix = model_id.split("/")[0].lower()
|
|
192
|
+
# Check if it's a known provider prefix
|
|
193
|
+
if prefix in provider_logo_map:
|
|
194
|
+
provider = prefix.capitalize()
|
|
195
|
+
else:
|
|
196
|
+
# It's a custom prefix (like "kubiya"), detect actual provider from model name
|
|
197
|
+
model_name = model_id.split("/", 1)[1].lower()
|
|
198
|
+
if "claude" in model_name:
|
|
199
|
+
provider = "Anthropic"
|
|
200
|
+
elif "gpt" in model_name or "o1" in model_name or "o3" in model_name:
|
|
201
|
+
provider = "OpenAI"
|
|
202
|
+
elif "gemini" in model_name:
|
|
203
|
+
provider = "Google"
|
|
204
|
+
elif "llama" in model_name:
|
|
205
|
+
provider = "Meta"
|
|
206
|
+
elif "mistral" in model_name:
|
|
207
|
+
provider = "Mistral"
|
|
208
|
+
elif "deepseek" in model_name:
|
|
209
|
+
provider = "DeepSeek"
|
|
210
|
+
elif "grok" in model_name:
|
|
211
|
+
provider = "xAI"
|
|
212
|
+
else:
|
|
213
|
+
provider = prefix.capitalize()
|
|
214
|
+
else:
|
|
215
|
+
# No slash - try to detect from model name
|
|
216
|
+
model_lower = model_id.lower()
|
|
217
|
+
if "claude" in model_lower:
|
|
218
|
+
provider = "Anthropic"
|
|
219
|
+
elif "gpt" in model_lower or "o1" in model_lower or "o3" in model_lower:
|
|
220
|
+
provider = "OpenAI"
|
|
221
|
+
elif "gemini" in model_lower:
|
|
222
|
+
provider = "Google"
|
|
223
|
+
elif "llama" in model_lower:
|
|
224
|
+
provider = "Meta"
|
|
225
|
+
elif "mistral" in model_lower:
|
|
226
|
+
provider = "Mistral"
|
|
227
|
+
elif "deepseek" in model_lower:
|
|
228
|
+
provider = "DeepSeek"
|
|
229
|
+
elif "grok" in model_lower:
|
|
230
|
+
provider = "xAI"
|
|
231
|
+
else:
|
|
232
|
+
provider = litellm_model.get("owned_by", "Unknown")
|
|
233
|
+
|
|
234
|
+
# Get logo based on provider
|
|
235
|
+
provider_key = provider.lower()
|
|
236
|
+
logo = provider_logo_map.get(provider_key)
|
|
237
|
+
|
|
238
|
+
# Generate label - remove provider prefix and clean up
|
|
239
|
+
if "/" in model_id:
|
|
240
|
+
label_base = model_id.split("/", 1)[1]
|
|
241
|
+
else:
|
|
242
|
+
label_base = model_id
|
|
243
|
+
|
|
244
|
+
# Better label formatting
|
|
245
|
+
label = label_base.replace("-", " ").replace("_", " ").title()
|
|
246
|
+
# Fix common capitalization issues
|
|
247
|
+
label = label.replace("Gpt", "GPT").replace("Llm", "LLM").replace("Api", "API")
|
|
248
|
+
|
|
249
|
+
# Get mode from LiteLLM and map to our model_type
|
|
250
|
+
litellm_mode = litellm_model.get("mode", "completion")
|
|
251
|
+
|
|
252
|
+
# Map LiteLLM mode to our model_type
|
|
253
|
+
model_type_map = {
|
|
254
|
+
"embedding": "embedding",
|
|
255
|
+
"chat": "text-generation",
|
|
256
|
+
"completion": "text-generation",
|
|
257
|
+
"image_generation": "multimodal",
|
|
258
|
+
"audio_transcription": "audio",
|
|
259
|
+
"moderation": "moderation"
|
|
260
|
+
}
|
|
261
|
+
model_type = model_type_map.get(litellm_mode, "text-generation")
|
|
262
|
+
|
|
263
|
+
# Determine compatible runtimes based on model ID
|
|
264
|
+
compatible_runtimes = ["default"]
|
|
265
|
+
if "claude" in model_id.lower():
|
|
266
|
+
compatible_runtimes.append("claude_code")
|
|
267
|
+
|
|
268
|
+
# Determine if model should be recommended
|
|
269
|
+
# Prioritize Claude Sonnet 4 variants
|
|
270
|
+
model_lower = model_id.lower()
|
|
271
|
+
is_recommended = (
|
|
272
|
+
"claude-sonnet-4" in model_lower or
|
|
273
|
+
"claude-4-sonnet" in model_lower or
|
|
274
|
+
(model_lower.endswith("claude-sonnet-4") or "claude-sonnet-4-" in model_lower)
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
# Set display order (lower = shown first)
|
|
278
|
+
# Recommended models get priority
|
|
279
|
+
if is_recommended:
|
|
280
|
+
display_order = 1
|
|
281
|
+
elif "claude" in model_lower and "sonnet" in model_lower:
|
|
282
|
+
display_order = 10
|
|
283
|
+
elif "claude" in model_lower:
|
|
284
|
+
display_order = 50
|
|
285
|
+
elif "gpt-4" in model_lower:
|
|
286
|
+
display_order = 100
|
|
287
|
+
else:
|
|
288
|
+
display_order = 1000
|
|
289
|
+
|
|
290
|
+
return LLMModelResponse(
|
|
291
|
+
id=model_id, # Use model ID as the ID for LiteLLM models
|
|
292
|
+
value=model_id,
|
|
293
|
+
label=label,
|
|
294
|
+
provider=provider,
|
|
295
|
+
model_type=model_type,
|
|
296
|
+
logo=logo,
|
|
297
|
+
description=f"{provider} model: {label}",
|
|
298
|
+
enabled=True,
|
|
299
|
+
recommended=is_recommended,
|
|
300
|
+
compatible_runtimes=compatible_runtimes,
|
|
301
|
+
capabilities={},
|
|
302
|
+
pricing=None,
|
|
303
|
+
display_order=display_order,
|
|
304
|
+
created_at=datetime.utcnow().isoformat(),
|
|
305
|
+
updated_at=datetime.utcnow().isoformat(),
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
def check_runtime_compatibility(model: LLMModelDB, runtime_id: Optional[str]) -> bool:
|
|
310
|
+
"""Check if a model is compatible with a specific runtime"""
|
|
311
|
+
if not runtime_id:
|
|
312
|
+
return True # No filter specified
|
|
313
|
+
if not model.compatible_runtimes:
|
|
314
|
+
return True # Model doesn't specify compatibility, allow all
|
|
315
|
+
return runtime_id in model.compatible_runtimes
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
def check_runtime_compatibility_dict(compatible_runtimes: List[str], runtime_id: Optional[str]) -> bool:
|
|
319
|
+
"""Check if a model is compatible with a specific runtime (dict version)"""
|
|
320
|
+
if not runtime_id:
|
|
321
|
+
return True # No filter specified
|
|
322
|
+
if not compatible_runtimes:
|
|
323
|
+
return True # Model doesn't specify compatibility, allow all
|
|
324
|
+
return runtime_id in compatible_runtimes
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
# ==================== CRUD Endpoints ====================
|
|
328
|
+
|
|
329
|
+
@router.post("", response_model=LLMModelResponse, status_code=status.HTTP_201_CREATED)
|
|
330
|
+
def create_model(
|
|
331
|
+
model_data: LLMModelCreate,
|
|
332
|
+
request: Request,
|
|
333
|
+
db: Session = Depends(get_db),
|
|
334
|
+
organization: dict = Depends(get_current_organization),
|
|
335
|
+
):
|
|
336
|
+
"""
|
|
337
|
+
Create a new LLM model.
|
|
338
|
+
|
|
339
|
+
Only accessible by authenticated users (org admins recommended).
|
|
340
|
+
"""
|
|
341
|
+
# Check if model with this value already exists
|
|
342
|
+
existing = db.query(LLMModelDB).filter(LLMModelDB.value == model_data.value).first()
|
|
343
|
+
if existing:
|
|
344
|
+
raise HTTPException(
|
|
345
|
+
status_code=status.HTTP_409_CONFLICT,
|
|
346
|
+
detail=f"Model with value '{model_data.value}' already exists"
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
# Create new model
|
|
350
|
+
new_model = LLMModelDB(
|
|
351
|
+
value=model_data.value,
|
|
352
|
+
label=model_data.label,
|
|
353
|
+
provider=model_data.provider,
|
|
354
|
+
model_type=model_data.model_type,
|
|
355
|
+
logo=model_data.logo,
|
|
356
|
+
description=model_data.description,
|
|
357
|
+
enabled=model_data.enabled,
|
|
358
|
+
recommended=model_data.recommended,
|
|
359
|
+
compatible_runtimes=model_data.compatible_runtimes,
|
|
360
|
+
capabilities=model_data.capabilities,
|
|
361
|
+
pricing=model_data.pricing,
|
|
362
|
+
display_order=model_data.display_order,
|
|
363
|
+
created_by=organization.get("user_id"),
|
|
364
|
+
)
|
|
365
|
+
|
|
366
|
+
db.add(new_model)
|
|
367
|
+
db.commit()
|
|
368
|
+
db.refresh(new_model)
|
|
369
|
+
|
|
370
|
+
logger.info(
|
|
371
|
+
"llm_model_created",
|
|
372
|
+
model_id=new_model.id,
|
|
373
|
+
model_value=new_model.value,
|
|
374
|
+
provider=new_model.provider,
|
|
375
|
+
org_id=organization["id"]
|
|
376
|
+
)
|
|
377
|
+
|
|
378
|
+
return model_to_response(new_model)
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
def get_db_optional():
|
|
382
|
+
"""Get database session, returns None if database not configured"""
|
|
383
|
+
try:
|
|
384
|
+
db = next(get_db())
|
|
385
|
+
try:
|
|
386
|
+
yield db
|
|
387
|
+
finally:
|
|
388
|
+
db.close()
|
|
389
|
+
except RuntimeError:
|
|
390
|
+
# Database not configured
|
|
391
|
+
yield None
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
@router.get("", response_model=List[LLMModelResponse])
|
|
395
|
+
async def list_models(
|
|
396
|
+
db: Optional[Session] = Depends(get_db_optional),
|
|
397
|
+
provider: Optional[str] = Query(None, description="Filter by provider (e.g., 'xai', 'anthropic')"),
|
|
398
|
+
runtime: Optional[str] = Query(None, description="Filter by compatible runtime (e.g., 'claude_code')"),
|
|
399
|
+
type: Optional[str] = Query(None, description="Filter by model type (e.g., 'embedding', 'text-generation')"),
|
|
400
|
+
skip: int = Query(0, ge=0),
|
|
401
|
+
limit: int = Query(100, ge=1, le=1000),
|
|
402
|
+
):
|
|
403
|
+
"""
|
|
404
|
+
List all LLM models from LiteLLM server with database fallback.
|
|
405
|
+
|
|
406
|
+
Models are fetched dynamically from the configured LiteLLM server with caching for performance.
|
|
407
|
+
Falls back to database models if LiteLLM is unavailable.
|
|
408
|
+
|
|
409
|
+
Query Parameters:
|
|
410
|
+
- provider: Filter by provider name (e.g., 'xai', 'anthropic', 'openai')
|
|
411
|
+
- runtime: Filter by compatible runtime (e.g., 'claude_code', 'default')
|
|
412
|
+
- type: Filter by model type (e.g., 'embedding', 'text-generation', 'multimodal')
|
|
413
|
+
- skip/limit: Pagination
|
|
414
|
+
|
|
415
|
+
**Example:**
|
|
416
|
+
```
|
|
417
|
+
GET /api/v1/models
|
|
418
|
+
GET /api/v1/models?provider=xai
|
|
419
|
+
GET /api/v1/models?runtime=claude_code
|
|
420
|
+
GET /api/v1/models?type=embedding
|
|
421
|
+
GET /api/v1/models?type=text-generation&provider=anthropic
|
|
422
|
+
```
|
|
423
|
+
"""
|
|
424
|
+
# Fetch models from LiteLLM (with caching and database fallback)
|
|
425
|
+
litellm_models = await fetch_models_from_litellm_cached(db=db)
|
|
426
|
+
|
|
427
|
+
# Convert to response format
|
|
428
|
+
models = [convert_litellm_model_to_response(m) for m in litellm_models]
|
|
429
|
+
|
|
430
|
+
# Apply filters
|
|
431
|
+
if provider:
|
|
432
|
+
models = [m for m in models if m.provider.lower() == provider.lower()]
|
|
433
|
+
|
|
434
|
+
if runtime:
|
|
435
|
+
models = [m for m in models if check_runtime_compatibility_dict(m.compatible_runtimes, runtime)]
|
|
436
|
+
|
|
437
|
+
if type:
|
|
438
|
+
models = [m for m in models if m.model_type.lower() == type.lower()]
|
|
439
|
+
|
|
440
|
+
# Apply pagination
|
|
441
|
+
total = len(models)
|
|
442
|
+
models = models[skip : skip + limit]
|
|
443
|
+
|
|
444
|
+
logger.info("listed_models", total=total, returned=len(models), provider=provider, runtime=runtime, type=type)
|
|
445
|
+
|
|
446
|
+
return models
|
|
447
|
+
|
|
448
|
+
|
|
449
|
+
@router.get("/default", response_model=LLMModelResponse)
|
|
450
|
+
async def get_default_model(db: Optional[Session] = Depends(get_db_optional)):
|
|
451
|
+
"""
|
|
452
|
+
Get the default recommended LLM model.
|
|
453
|
+
|
|
454
|
+
Returns a recommended model from LiteLLM or falls back to database/first available model.
|
|
455
|
+
Uses LITELLM_DEFAULT_MODEL from environment if set.
|
|
456
|
+
"""
|
|
457
|
+
# Fetch models from LiteLLM (with database fallback)
|
|
458
|
+
litellm_models = await fetch_models_from_litellm_cached(db=db)
|
|
459
|
+
|
|
460
|
+
if not litellm_models:
|
|
461
|
+
raise HTTPException(
|
|
462
|
+
status_code=status.HTTP_404_NOT_FOUND,
|
|
463
|
+
detail="No models available"
|
|
464
|
+
)
|
|
465
|
+
|
|
466
|
+
# Try to find the default model from config
|
|
467
|
+
if settings.litellm_default_model:
|
|
468
|
+
for model in litellm_models:
|
|
469
|
+
if model.get("id") == settings.litellm_default_model:
|
|
470
|
+
return convert_litellm_model_to_response(model)
|
|
471
|
+
|
|
472
|
+
# Fallback to first available model
|
|
473
|
+
return convert_litellm_model_to_response(litellm_models[0])
|
|
474
|
+
|
|
475
|
+
|
|
476
|
+
@router.get("/providers", response_model=List[str])
|
|
477
|
+
async def list_providers(db: Optional[Session] = Depends(get_db_optional)):
|
|
478
|
+
"""
|
|
479
|
+
Get list of unique model providers.
|
|
480
|
+
|
|
481
|
+
Returns a list of all unique provider names from available models.
|
|
482
|
+
"""
|
|
483
|
+
# Fetch models (with database fallback)
|
|
484
|
+
litellm_models = await fetch_models_from_litellm_cached(db=db)
|
|
485
|
+
|
|
486
|
+
# Extract unique providers
|
|
487
|
+
providers = set()
|
|
488
|
+
for model in litellm_models:
|
|
489
|
+
model_id = model.get("id", "")
|
|
490
|
+
if "/" in model_id:
|
|
491
|
+
provider = model_id.split("/")[0]
|
|
492
|
+
providers.add(provider)
|
|
493
|
+
|
|
494
|
+
return sorted(list(providers))
|
|
495
|
+
|
|
496
|
+
|
|
497
|
+
@router.get("/{model_id:path}", response_model=LLMModelResponse)
|
|
498
|
+
async def get_model(model_id: str, db: Optional[Session] = Depends(get_db_optional)):
|
|
499
|
+
"""
|
|
500
|
+
Get a specific LLM model by ID.
|
|
501
|
+
|
|
502
|
+
Accepts model ID in the format: provider/model (e.g., 'xai/grok-2-1212')
|
|
503
|
+
|
|
504
|
+
**Example:**
|
|
505
|
+
```
|
|
506
|
+
GET /api/v1/models/xai/grok-2-1212
|
|
507
|
+
GET /api/v1/models/kubiya/claude-sonnet-4
|
|
508
|
+
```
|
|
509
|
+
"""
|
|
510
|
+
# Fetch models (with database fallback)
|
|
511
|
+
litellm_models = await fetch_models_from_litellm_cached(db=db)
|
|
512
|
+
|
|
513
|
+
# Find the model
|
|
514
|
+
for model in litellm_models:
|
|
515
|
+
if model.get("id") == model_id:
|
|
516
|
+
return convert_litellm_model_to_response(model)
|
|
517
|
+
|
|
518
|
+
# Model not found
|
|
519
|
+
raise HTTPException(
|
|
520
|
+
status_code=status.HTTP_404_NOT_FOUND,
|
|
521
|
+
detail=f"Model '{model_id}' not found"
|
|
522
|
+
)
|
|
523
|
+
|
|
524
|
+
|
|
525
|
+
@router.patch("/{model_id}", response_model=LLMModelResponse)
|
|
526
|
+
def update_model(
|
|
527
|
+
model_id: str,
|
|
528
|
+
model_data: LLMModelUpdate,
|
|
529
|
+
request: Request,
|
|
530
|
+
db: Session = Depends(get_db),
|
|
531
|
+
organization: dict = Depends(get_current_organization),
|
|
532
|
+
):
|
|
533
|
+
"""
|
|
534
|
+
Update an existing LLM model.
|
|
535
|
+
|
|
536
|
+
Only accessible by authenticated users (org admins recommended).
|
|
537
|
+
"""
|
|
538
|
+
# Find model
|
|
539
|
+
model = db.query(LLMModelDB).filter(LLMModelDB.id == model_id).first()
|
|
540
|
+
if not model:
|
|
541
|
+
raise HTTPException(
|
|
542
|
+
status_code=status.HTTP_404_NOT_FOUND,
|
|
543
|
+
detail=f"Model '{model_id}' not found"
|
|
544
|
+
)
|
|
545
|
+
|
|
546
|
+
# Check if value is being updated and conflicts with existing
|
|
547
|
+
if model_data.value and model_data.value != model.value:
|
|
548
|
+
existing = db.query(LLMModelDB).filter(LLMModelDB.value == model_data.value).first()
|
|
549
|
+
if existing:
|
|
550
|
+
raise HTTPException(
|
|
551
|
+
status_code=status.HTTP_409_CONFLICT,
|
|
552
|
+
detail=f"Model with value '{model_data.value}' already exists"
|
|
553
|
+
)
|
|
554
|
+
|
|
555
|
+
# Update fields
|
|
556
|
+
update_dict = model_data.model_dump(exclude_unset=True)
|
|
557
|
+
for field, value in update_dict.items():
|
|
558
|
+
setattr(model, field, value)
|
|
559
|
+
|
|
560
|
+
model.updated_at = datetime.utcnow()
|
|
561
|
+
|
|
562
|
+
db.commit()
|
|
563
|
+
db.refresh(model)
|
|
564
|
+
|
|
565
|
+
logger.info(
|
|
566
|
+
"llm_model_updated",
|
|
567
|
+
model_id=model.id,
|
|
568
|
+
model_value=model.value,
|
|
569
|
+
updated_fields=list(update_dict.keys()),
|
|
570
|
+
org_id=organization["id"]
|
|
571
|
+
)
|
|
572
|
+
|
|
573
|
+
return model_to_response(model)
|
|
574
|
+
|
|
575
|
+
|
|
576
|
+
@router.delete("/{model_id}", status_code=status.HTTP_204_NO_CONTENT)
|
|
577
|
+
def delete_model(
|
|
578
|
+
model_id: str,
|
|
579
|
+
request: Request,
|
|
580
|
+
db: Session = Depends(get_db),
|
|
581
|
+
organization: dict = Depends(get_current_organization),
|
|
582
|
+
):
|
|
583
|
+
"""
|
|
584
|
+
Delete an LLM model.
|
|
585
|
+
|
|
586
|
+
Only accessible by authenticated users (org admins recommended).
|
|
587
|
+
"""
|
|
588
|
+
model = db.query(LLMModelDB).filter(LLMModelDB.id == model_id).first()
|
|
589
|
+
if not model:
|
|
590
|
+
raise HTTPException(
|
|
591
|
+
status_code=status.HTTP_404_NOT_FOUND,
|
|
592
|
+
detail=f"Model '{model_id}' not found"
|
|
593
|
+
)
|
|
594
|
+
|
|
595
|
+
db.delete(model)
|
|
596
|
+
db.commit()
|
|
597
|
+
|
|
598
|
+
logger.info(
|
|
599
|
+
"llm_model_deleted",
|
|
600
|
+
model_id=model.id,
|
|
601
|
+
model_value=model.value,
|
|
602
|
+
org_id=organization["id"]
|
|
603
|
+
)
|
|
604
|
+
|
|
605
|
+
return None
|
|
606
|
+
|
|
607
|
+
|
|
608
|
+
# ==================== Helper Functions ====================
|
|
609
|
+
|
|
610
|
+
def model_to_response(model: LLMModelDB) -> LLMModelResponse:
|
|
611
|
+
"""Convert database model to response schema"""
|
|
612
|
+
return LLMModelResponse(
|
|
613
|
+
id=model.id,
|
|
614
|
+
value=model.value,
|
|
615
|
+
label=model.label,
|
|
616
|
+
provider=model.provider,
|
|
617
|
+
model_type=model.model_type,
|
|
618
|
+
logo=model.logo,
|
|
619
|
+
description=model.description,
|
|
620
|
+
enabled=model.enabled,
|
|
621
|
+
recommended=model.recommended,
|
|
622
|
+
compatible_runtimes=model.compatible_runtimes or [],
|
|
623
|
+
capabilities=model.capabilities or {},
|
|
624
|
+
pricing=model.pricing,
|
|
625
|
+
display_order=model.display_order,
|
|
626
|
+
created_at=model.created_at.isoformat() if model.created_at else "",
|
|
627
|
+
updated_at=model.updated_at.isoformat() if model.updated_at else "",
|
|
628
|
+
)
|