isa-model 0.3.91__py3-none-any.whl → 0.4.3__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.
Files changed (228) hide show
  1. isa_model/client.py +1166 -584
  2. isa_model/core/cache/redis_cache.py +410 -0
  3. isa_model/core/config/config_manager.py +282 -12
  4. isa_model/core/config.py +91 -1
  5. isa_model/core/database/__init__.py +1 -0
  6. isa_model/core/database/direct_db_client.py +114 -0
  7. isa_model/core/database/migration_manager.py +563 -0
  8. isa_model/core/database/migrations.py +297 -0
  9. isa_model/core/database/supabase_client.py +258 -0
  10. isa_model/core/dependencies.py +316 -0
  11. isa_model/core/discovery/__init__.py +19 -0
  12. isa_model/core/discovery/consul_discovery.py +190 -0
  13. isa_model/core/logging/__init__.py +54 -0
  14. isa_model/core/logging/influx_logger.py +523 -0
  15. isa_model/core/logging/loki_logger.py +160 -0
  16. isa_model/core/models/__init__.py +46 -0
  17. isa_model/core/models/config_models.py +625 -0
  18. isa_model/core/models/deployment_billing_tracker.py +430 -0
  19. isa_model/core/models/model_billing_tracker.py +60 -88
  20. isa_model/core/models/model_manager.py +66 -25
  21. isa_model/core/models/model_metadata.py +690 -0
  22. isa_model/core/models/model_repo.py +217 -55
  23. isa_model/core/models/model_statistics_tracker.py +234 -0
  24. isa_model/core/models/model_storage.py +0 -1
  25. isa_model/core/models/model_version_manager.py +959 -0
  26. isa_model/core/models/system_models.py +857 -0
  27. isa_model/core/pricing_manager.py +2 -249
  28. isa_model/core/repositories/__init__.py +9 -0
  29. isa_model/core/repositories/config_repository.py +912 -0
  30. isa_model/core/resilience/circuit_breaker.py +366 -0
  31. isa_model/core/security/secrets.py +358 -0
  32. isa_model/core/services/__init__.py +2 -4
  33. isa_model/core/services/intelligent_model_selector.py +479 -370
  34. isa_model/core/storage/hf_storage.py +2 -2
  35. isa_model/core/types.py +8 -0
  36. isa_model/deployment/__init__.py +5 -48
  37. isa_model/deployment/core/__init__.py +2 -31
  38. isa_model/deployment/core/deployment_manager.py +1278 -368
  39. isa_model/deployment/local/__init__.py +31 -0
  40. isa_model/deployment/local/config.py +248 -0
  41. isa_model/deployment/local/gpu_gateway.py +607 -0
  42. isa_model/deployment/local/health_checker.py +428 -0
  43. isa_model/deployment/local/provider.py +586 -0
  44. isa_model/deployment/local/tensorrt_service.py +621 -0
  45. isa_model/deployment/local/transformers_service.py +644 -0
  46. isa_model/deployment/local/vllm_service.py +527 -0
  47. isa_model/deployment/modal/__init__.py +8 -0
  48. isa_model/deployment/modal/config.py +136 -0
  49. isa_model/deployment/modal/deployer.py +894 -0
  50. isa_model/deployment/modal/services/__init__.py +3 -0
  51. isa_model/deployment/modal/services/audio/__init__.py +1 -0
  52. isa_model/deployment/modal/services/audio/isa_audio_chatTTS_service.py +520 -0
  53. isa_model/deployment/modal/services/audio/isa_audio_openvoice_service.py +758 -0
  54. isa_model/deployment/modal/services/audio/isa_audio_service_v2.py +1044 -0
  55. isa_model/deployment/modal/services/embedding/__init__.py +1 -0
  56. isa_model/deployment/modal/services/embedding/isa_embed_rerank_service.py +296 -0
  57. isa_model/deployment/modal/services/llm/__init__.py +1 -0
  58. isa_model/deployment/modal/services/llm/isa_llm_service.py +424 -0
  59. isa_model/deployment/modal/services/video/__init__.py +1 -0
  60. isa_model/deployment/modal/services/video/isa_video_hunyuan_service.py +423 -0
  61. isa_model/deployment/modal/services/vision/__init__.py +1 -0
  62. isa_model/deployment/modal/services/vision/isa_vision_ocr_service.py +519 -0
  63. isa_model/deployment/modal/services/vision/isa_vision_qwen25_service.py +709 -0
  64. isa_model/deployment/modal/services/vision/isa_vision_table_service.py +676 -0
  65. isa_model/deployment/modal/services/vision/isa_vision_ui_service.py +833 -0
  66. isa_model/deployment/modal/services/vision/isa_vision_ui_service_optimized.py +660 -0
  67. isa_model/deployment/models/org-org-acme-corp-tenant-a-service-llm-20250825-225822/tenant-a-service_modal_service.py +48 -0
  68. isa_model/deployment/models/org-test-org-123-prefix-test-service-llm-20250825-225822/prefix-test-service_modal_service.py +48 -0
  69. isa_model/deployment/models/test-llm-service-llm-20250825-204442/test-llm-service_modal_service.py +48 -0
  70. isa_model/deployment/models/test-monitoring-gpt2-llm-20250825-212906/test-monitoring-gpt2_modal_service.py +48 -0
  71. isa_model/deployment/models/test-monitoring-gpt2-llm-20250825-213009/test-monitoring-gpt2_modal_service.py +48 -0
  72. isa_model/deployment/storage/__init__.py +5 -0
  73. isa_model/deployment/storage/deployment_repository.py +824 -0
  74. isa_model/deployment/triton/__init__.py +10 -0
  75. isa_model/deployment/triton/config.py +196 -0
  76. isa_model/deployment/triton/configs/__init__.py +1 -0
  77. isa_model/deployment/triton/provider.py +512 -0
  78. isa_model/deployment/triton/scripts/__init__.py +1 -0
  79. isa_model/deployment/triton/templates/__init__.py +1 -0
  80. isa_model/inference/__init__.py +47 -1
  81. isa_model/inference/ai_factory.py +179 -16
  82. isa_model/inference/legacy_services/__init__.py +21 -0
  83. isa_model/inference/legacy_services/model_evaluation.py +637 -0
  84. isa_model/inference/legacy_services/model_service.py +573 -0
  85. isa_model/inference/legacy_services/model_serving.py +717 -0
  86. isa_model/inference/legacy_services/model_training.py +561 -0
  87. isa_model/inference/models/__init__.py +21 -0
  88. isa_model/inference/models/inference_config.py +551 -0
  89. isa_model/inference/models/inference_record.py +675 -0
  90. isa_model/inference/models/performance_models.py +714 -0
  91. isa_model/inference/repositories/__init__.py +9 -0
  92. isa_model/inference/repositories/inference_repository.py +828 -0
  93. isa_model/inference/services/audio/__init__.py +21 -0
  94. isa_model/inference/services/audio/base_realtime_service.py +225 -0
  95. isa_model/inference/services/audio/base_stt_service.py +184 -11
  96. isa_model/inference/services/audio/isa_tts_service.py +0 -0
  97. isa_model/inference/services/audio/openai_realtime_service.py +320 -124
  98. isa_model/inference/services/audio/openai_stt_service.py +53 -11
  99. isa_model/inference/services/base_service.py +17 -1
  100. isa_model/inference/services/custom_model_manager.py +277 -0
  101. isa_model/inference/services/embedding/__init__.py +13 -0
  102. isa_model/inference/services/embedding/base_embed_service.py +111 -8
  103. isa_model/inference/services/embedding/isa_embed_service.py +305 -0
  104. isa_model/inference/services/embedding/ollama_embed_service.py +15 -3
  105. isa_model/inference/services/embedding/openai_embed_service.py +2 -4
  106. isa_model/inference/services/embedding/resilient_embed_service.py +285 -0
  107. isa_model/inference/services/embedding/tests/test_embedding.py +222 -0
  108. isa_model/inference/services/img/__init__.py +2 -2
  109. isa_model/inference/services/img/base_image_gen_service.py +24 -7
  110. isa_model/inference/services/img/replicate_image_gen_service.py +84 -422
  111. isa_model/inference/services/img/services/replicate_face_swap.py +193 -0
  112. isa_model/inference/services/img/services/replicate_flux.py +226 -0
  113. isa_model/inference/services/img/services/replicate_flux_kontext.py +219 -0
  114. isa_model/inference/services/img/services/replicate_sticker_maker.py +249 -0
  115. isa_model/inference/services/img/tests/test_img_client.py +297 -0
  116. isa_model/inference/services/llm/__init__.py +10 -2
  117. isa_model/inference/services/llm/base_llm_service.py +361 -26
  118. isa_model/inference/services/llm/cerebras_llm_service.py +628 -0
  119. isa_model/inference/services/llm/helpers/llm_adapter.py +71 -12
  120. isa_model/inference/services/llm/helpers/llm_prompts.py +342 -0
  121. isa_model/inference/services/llm/helpers/llm_utils.py +321 -23
  122. isa_model/inference/services/llm/huggingface_llm_service.py +581 -0
  123. isa_model/inference/services/llm/local_llm_service.py +747 -0
  124. isa_model/inference/services/llm/ollama_llm_service.py +11 -3
  125. isa_model/inference/services/llm/openai_llm_service.py +670 -56
  126. isa_model/inference/services/llm/yyds_llm_service.py +10 -3
  127. isa_model/inference/services/vision/__init__.py +27 -6
  128. isa_model/inference/services/vision/base_vision_service.py +118 -185
  129. isa_model/inference/services/vision/blip_vision_service.py +359 -0
  130. isa_model/inference/services/vision/helpers/image_utils.py +19 -10
  131. isa_model/inference/services/vision/isa_vision_service.py +634 -0
  132. isa_model/inference/services/vision/openai_vision_service.py +19 -10
  133. isa_model/inference/services/vision/tests/test_ocr_client.py +284 -0
  134. isa_model/inference/services/vision/vgg16_vision_service.py +257 -0
  135. isa_model/serving/api/cache_manager.py +245 -0
  136. isa_model/serving/api/dependencies/__init__.py +1 -0
  137. isa_model/serving/api/dependencies/auth.py +194 -0
  138. isa_model/serving/api/dependencies/database.py +139 -0
  139. isa_model/serving/api/error_handlers.py +284 -0
  140. isa_model/serving/api/fastapi_server.py +240 -18
  141. isa_model/serving/api/middleware/auth.py +317 -0
  142. isa_model/serving/api/middleware/security.py +268 -0
  143. isa_model/serving/api/middleware/tenant_context.py +414 -0
  144. isa_model/serving/api/routes/analytics.py +489 -0
  145. isa_model/serving/api/routes/config.py +645 -0
  146. isa_model/serving/api/routes/deployment_billing.py +315 -0
  147. isa_model/serving/api/routes/deployments.py +475 -0
  148. isa_model/serving/api/routes/gpu_gateway.py +440 -0
  149. isa_model/serving/api/routes/health.py +32 -12
  150. isa_model/serving/api/routes/inference_monitoring.py +486 -0
  151. isa_model/serving/api/routes/local_deployments.py +448 -0
  152. isa_model/serving/api/routes/logs.py +430 -0
  153. isa_model/serving/api/routes/settings.py +582 -0
  154. isa_model/serving/api/routes/tenants.py +575 -0
  155. isa_model/serving/api/routes/unified.py +992 -171
  156. isa_model/serving/api/routes/webhooks.py +479 -0
  157. isa_model/serving/api/startup.py +318 -0
  158. isa_model/serving/modal_proxy_server.py +249 -0
  159. isa_model/utils/gpu_utils.py +311 -0
  160. {isa_model-0.3.91.dist-info → isa_model-0.4.3.dist-info}/METADATA +76 -22
  161. isa_model-0.4.3.dist-info/RECORD +193 -0
  162. isa_model/deployment/cloud/__init__.py +0 -9
  163. isa_model/deployment/cloud/modal/__init__.py +0 -10
  164. isa_model/deployment/cloud/modal/isa_vision_doc_service.py +0 -766
  165. isa_model/deployment/cloud/modal/isa_vision_table_service.py +0 -532
  166. isa_model/deployment/cloud/modal/isa_vision_ui_service.py +0 -406
  167. isa_model/deployment/cloud/modal/register_models.py +0 -321
  168. isa_model/deployment/core/deployment_config.py +0 -356
  169. isa_model/deployment/core/isa_deployment_service.py +0 -401
  170. isa_model/deployment/gpu_int8_ds8/app/server.py +0 -66
  171. isa_model/deployment/gpu_int8_ds8/scripts/test_client.py +0 -43
  172. isa_model/deployment/gpu_int8_ds8/scripts/test_client_os.py +0 -35
  173. isa_model/deployment/runtime/deployed_service.py +0 -338
  174. isa_model/deployment/services/__init__.py +0 -9
  175. isa_model/deployment/services/auto_deploy_vision_service.py +0 -538
  176. isa_model/deployment/services/model_service.py +0 -332
  177. isa_model/deployment/services/service_monitor.py +0 -356
  178. isa_model/deployment/services/service_registry.py +0 -527
  179. isa_model/eval/__init__.py +0 -92
  180. isa_model/eval/benchmarks.py +0 -469
  181. isa_model/eval/config/__init__.py +0 -10
  182. isa_model/eval/config/evaluation_config.py +0 -108
  183. isa_model/eval/evaluators/__init__.py +0 -18
  184. isa_model/eval/evaluators/base_evaluator.py +0 -503
  185. isa_model/eval/evaluators/llm_evaluator.py +0 -472
  186. isa_model/eval/factory.py +0 -531
  187. isa_model/eval/infrastructure/__init__.py +0 -24
  188. isa_model/eval/infrastructure/experiment_tracker.py +0 -466
  189. isa_model/eval/metrics.py +0 -798
  190. isa_model/inference/adapter/unified_api.py +0 -248
  191. isa_model/inference/services/helpers/stacked_config.py +0 -148
  192. isa_model/inference/services/img/flux_professional_service.py +0 -603
  193. isa_model/inference/services/img/helpers/base_stacked_service.py +0 -274
  194. isa_model/inference/services/others/table_transformer_service.py +0 -61
  195. isa_model/inference/services/vision/doc_analysis_service.py +0 -640
  196. isa_model/inference/services/vision/helpers/base_stacked_service.py +0 -274
  197. isa_model/inference/services/vision/ui_analysis_service.py +0 -823
  198. isa_model/scripts/inference_tracker.py +0 -283
  199. isa_model/scripts/mlflow_manager.py +0 -379
  200. isa_model/scripts/model_registry.py +0 -465
  201. isa_model/scripts/register_models.py +0 -370
  202. isa_model/scripts/register_models_with_embeddings.py +0 -510
  203. isa_model/scripts/start_mlflow.py +0 -95
  204. isa_model/scripts/training_tracker.py +0 -257
  205. isa_model/training/__init__.py +0 -74
  206. isa_model/training/annotation/annotation_schema.py +0 -47
  207. isa_model/training/annotation/processors/annotation_processor.py +0 -126
  208. isa_model/training/annotation/storage/dataset_manager.py +0 -131
  209. isa_model/training/annotation/storage/dataset_schema.py +0 -44
  210. isa_model/training/annotation/tests/test_annotation_flow.py +0 -109
  211. isa_model/training/annotation/tests/test_minio copy.py +0 -113
  212. isa_model/training/annotation/tests/test_minio_upload.py +0 -43
  213. isa_model/training/annotation/views/annotation_controller.py +0 -158
  214. isa_model/training/cloud/__init__.py +0 -22
  215. isa_model/training/cloud/job_orchestrator.py +0 -402
  216. isa_model/training/cloud/runpod_trainer.py +0 -454
  217. isa_model/training/cloud/storage_manager.py +0 -482
  218. isa_model/training/core/__init__.py +0 -23
  219. isa_model/training/core/config.py +0 -181
  220. isa_model/training/core/dataset.py +0 -222
  221. isa_model/training/core/trainer.py +0 -720
  222. isa_model/training/core/utils.py +0 -213
  223. isa_model/training/factory.py +0 -424
  224. isa_model-0.3.91.dist-info/RECORD +0 -138
  225. /isa_model/{core/storage/minio_storage.py → deployment/modal/services/audio/isa_audio_fish_service.py} +0 -0
  226. /isa_model/deployment/{services → modal/services/vision}/simple_auto_deploy_vision_service.py +0 -0
  227. {isa_model-0.3.91.dist-info → isa_model-0.4.3.dist-info}/WHEEL +0 -0
  228. {isa_model-0.3.91.dist-info → isa_model-0.4.3.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,582 @@
1
+ """
2
+ Settings API Routes
3
+
4
+ Provides API key management and platform configuration endpoints.
5
+ This module handles sensitive operations safely without affecting running services.
6
+ """
7
+
8
+ from fastapi import APIRouter, HTTPException, Depends
9
+ from pydantic import BaseModel
10
+ from typing import Optional, List, Dict, Any
11
+ import logging
12
+ import os
13
+ import json
14
+ import hashlib
15
+ from pathlib import Path
16
+ from ..middleware.auth import api_key_manager, require_admin_access
17
+
18
+ logger = logging.getLogger(__name__)
19
+
20
+ router = APIRouter()
21
+
22
+ # Configuration file path
23
+ CONFIG_DIR = Path(os.path.dirname(__file__)).parent.parent.parent / "deployment" / "dev"
24
+ ENV_FILE = CONFIG_DIR / ".env"
25
+ CONFIG_BACKUP_FILE = CONFIG_DIR / ".env.backup"
26
+
27
+ class APIKeyEntry(BaseModel):
28
+ provider: str
29
+ key_name: str
30
+ masked_value: str
31
+ is_set: bool
32
+ last_updated: Optional[str] = None
33
+
34
+ class APIKeyUpdate(BaseModel):
35
+ provider: str
36
+ key_name: str
37
+ key_value: str
38
+
39
+ class GeneralSettings(BaseModel):
40
+ platform_name: Optional[str] = "ISA Model Platform"
41
+ default_provider: Optional[str] = "auto"
42
+ log_level: Optional[str] = "INFO"
43
+ max_workers: Optional[int] = 1
44
+ request_timeout: Optional[int] = 300
45
+
46
+ class PlatformAPIKey(BaseModel):
47
+ name: str
48
+ scopes: List[str] = ["read"]
49
+
50
+ class AuthSettings(BaseModel):
51
+ auth_enabled: bool
52
+ total_keys: int
53
+ active_keys: int
54
+
55
+ # Known API key mappings
56
+ API_KEY_PROVIDERS = {
57
+ "openai": {
58
+ "OPENAI_API_KEY": "OpenAI API Key",
59
+ "OPENAI_API_BASE": "OpenAI API Base URL"
60
+ },
61
+ "replicate": {
62
+ "REPLICATE_API_TOKEN": "Replicate API Token"
63
+ },
64
+ "yyds": {
65
+ "YYDS_API_KEY": "YYDS API Key",
66
+ "YYDS_API_BASE": "YYDS API Base URL"
67
+ },
68
+ "huggingface": {
69
+ "HF_TOKEN": "Hugging Face Token"
70
+ },
71
+ "runpod": {
72
+ "RUNPOD_API_KEY": "RunPod API Key"
73
+ },
74
+ "pypi": {
75
+ "PYPI_API_TOKEN": "PyPI API Token"
76
+ }
77
+ }
78
+
79
+ def mask_api_key(api_key: str) -> str:
80
+ """Mask API key for display, showing only first 4 and last 4 characters"""
81
+ if not api_key or len(api_key) < 8:
82
+ return "••••••••"
83
+ return f"{api_key[:4]}{'•' * (len(api_key) - 8)}{api_key[-4:]}"
84
+
85
+ def read_env_file() -> Dict[str, str]:
86
+ """Read environment variables from .env file"""
87
+ env_vars = {}
88
+
89
+ if not ENV_FILE.exists():
90
+ logger.warning(f"Environment file not found: {ENV_FILE}")
91
+ return env_vars
92
+
93
+ try:
94
+ with open(ENV_FILE, 'r') as f:
95
+ for line in f:
96
+ line = line.strip()
97
+ if line and not line.startswith('#') and '=' in line:
98
+ key, value = line.split('=', 1)
99
+ # Remove quotes if present
100
+ value = value.strip('"\'')
101
+ env_vars[key] = value
102
+ except Exception as e:
103
+ logger.error(f"Error reading environment file: {e}")
104
+
105
+ return env_vars
106
+
107
+ def write_env_file(env_vars: Dict[str, str]) -> bool:
108
+ """Write environment variables to .env file with backup"""
109
+ try:
110
+ # Create backup
111
+ if ENV_FILE.exists():
112
+ import shutil
113
+ shutil.copy2(ENV_FILE, CONFIG_BACKUP_FILE)
114
+ logger.info("Created backup of environment file")
115
+
116
+ # Write new file
117
+ with open(ENV_FILE, 'w') as f:
118
+ f.write("# ISA Model Local Development Environment\n")
119
+ f.write("# Copy this to .env for local development\n\n")
120
+
121
+ # Organize by sections
122
+ sections = {
123
+ "Environment": ["ENVIRONMENT", "DEBUG", "LOG_LEVEL", "VERBOSE_LOGGING"],
124
+ "Server Configuration": ["PORT", "MAX_WORKERS", "REQUEST_TIMEOUT", "MAX_REQUEST_SIZE"],
125
+ "API Keys": [k for provider in API_KEY_PROVIDERS.values() for k in provider.keys()],
126
+ "Database Configuration": ["SUPABASE_LOCAL_URL", "SUPABASE_LOCAL_ANON_KEY", "SUPABASE_LOCAL_SERVICE_ROLE_KEY", "SUPABASE_PWD", "DATABASE_URL"],
127
+ "Local Services": ["OLLAMA_BASE_URL"],
128
+ "Model Defaults": [k for k in env_vars.keys() if k.startswith("DEFAULT_")],
129
+ "Development Configuration": ["RATE_LIMIT_REQUESTS_PER_MINUTE", "CORS_ORIGINS", "ENABLE_METRICS", "METRICS_PORT"],
130
+ "Storage Configuration": ["MODEL_STORAGE_PATH"]
131
+ }
132
+
133
+ for section, keys in sections.items():
134
+ section_vars = {k: v for k, v in env_vars.items() if k in keys}
135
+ if section_vars:
136
+ f.write(f"\n# ============= {section} =============\n")
137
+ for key, value in section_vars.items():
138
+ f.write(f"{key}={value}\n")
139
+
140
+ # Add any remaining variables
141
+ written_keys = set()
142
+ for keys in sections.values():
143
+ written_keys.update(keys)
144
+
145
+ remaining = {k: v for k, v in env_vars.items() if k not in written_keys}
146
+ if remaining:
147
+ f.write(f"\n# ============= Other =============\n")
148
+ for key, value in remaining.items():
149
+ f.write(f"{key}={value}\n")
150
+
151
+ logger.info("Successfully updated environment file")
152
+ return True
153
+
154
+ except Exception as e:
155
+ logger.error(f"Error writing environment file: {e}")
156
+ # Restore backup if write failed
157
+ if CONFIG_BACKUP_FILE.exists():
158
+ import shutil
159
+ shutil.copy2(CONFIG_BACKUP_FILE, ENV_FILE)
160
+ logger.info("Restored backup due to write failure")
161
+ return False
162
+
163
+ @router.get("/api-keys")
164
+ async def get_api_keys():
165
+ """Get current API key configuration (masked for security)"""
166
+ try:
167
+ env_vars = read_env_file()
168
+ api_keys = []
169
+
170
+ for provider, keys in API_KEY_PROVIDERS.items():
171
+ for env_key, display_name in keys.items():
172
+ current_value = env_vars.get(env_key, "")
173
+ api_keys.append(APIKeyEntry(
174
+ provider=provider,
175
+ key_name=env_key,
176
+ masked_value=mask_api_key(current_value) if current_value else "",
177
+ is_set=bool(current_value)
178
+ ))
179
+
180
+ return {
181
+ "api_keys": api_keys,
182
+ "total_keys": len(api_keys),
183
+ "configured_keys": sum(1 for key in api_keys if key.is_set)
184
+ }
185
+
186
+ except Exception as e:
187
+ logger.error(f"Error getting API keys: {e}")
188
+ raise HTTPException(status_code=500, detail="Failed to retrieve API key configuration")
189
+
190
+ @router.put("/api-keys")
191
+ async def update_api_key(api_key_update: APIKeyUpdate):
192
+ """Update or add an API key"""
193
+ try:
194
+ # Validate provider and key name
195
+ if api_key_update.provider not in API_KEY_PROVIDERS:
196
+ raise HTTPException(status_code=400, detail="Invalid provider")
197
+
198
+ provider_keys = API_KEY_PROVIDERS[api_key_update.provider]
199
+ if api_key_update.key_name not in provider_keys:
200
+ raise HTTPException(status_code=400, detail="Invalid key name for provider")
201
+
202
+ # Read current environment
203
+ env_vars = read_env_file()
204
+
205
+ # Update the specific key
206
+ env_vars[api_key_update.key_name] = api_key_update.key_value
207
+
208
+ # Write back to file
209
+ if not write_env_file(env_vars):
210
+ raise HTTPException(status_code=500, detail="Failed to update configuration file")
211
+
212
+ return {
213
+ "success": True,
214
+ "message": f"Successfully updated {api_key_update.key_name}",
215
+ "restart_required": True # Note: Changes require restart to take effect
216
+ }
217
+
218
+ except HTTPException:
219
+ raise
220
+ except Exception as e:
221
+ logger.error(f"Error updating API key: {e}")
222
+ raise HTTPException(status_code=500, detail="Failed to update API key")
223
+
224
+ @router.delete("/api-keys/{provider}/{key_name}")
225
+ async def delete_api_key(provider: str, key_name: str):
226
+ """Remove an API key"""
227
+ try:
228
+ # Validate provider and key name
229
+ if provider not in API_KEY_PROVIDERS:
230
+ raise HTTPException(status_code=400, detail="Invalid provider")
231
+
232
+ provider_keys = API_KEY_PROVIDERS[provider]
233
+ if key_name not in provider_keys:
234
+ raise HTTPException(status_code=400, detail="Invalid key name for provider")
235
+
236
+ # Read current environment
237
+ env_vars = read_env_file()
238
+
239
+ # Remove the key
240
+ if key_name in env_vars:
241
+ del env_vars[key_name]
242
+
243
+ # Write back to file
244
+ if not write_env_file(env_vars):
245
+ raise HTTPException(status_code=500, detail="Failed to update configuration file")
246
+
247
+ return {
248
+ "success": True,
249
+ "message": f"Successfully removed {key_name}",
250
+ "restart_required": True
251
+ }
252
+
253
+ except HTTPException:
254
+ raise
255
+ except Exception as e:
256
+ logger.error(f"Error deleting API key: {e}")
257
+ raise HTTPException(status_code=500, detail="Failed to delete API key")
258
+
259
+ @router.get("/general")
260
+ async def get_general_settings():
261
+ """Get general platform settings"""
262
+ try:
263
+ env_vars = read_env_file()
264
+
265
+ settings = GeneralSettings(
266
+ platform_name=env_vars.get("PLATFORM_NAME", "ISA Model Platform"),
267
+ default_provider=env_vars.get("DEFAULT_LLM_PROVIDER", "auto"),
268
+ log_level=env_vars.get("LOG_LEVEL", "INFO"),
269
+ max_workers=int(env_vars.get("MAX_WORKERS", "1")),
270
+ request_timeout=int(env_vars.get("REQUEST_TIMEOUT", "300"))
271
+ )
272
+
273
+ return settings
274
+
275
+ except Exception as e:
276
+ logger.error(f"Error getting general settings: {e}")
277
+ raise HTTPException(status_code=500, detail="Failed to retrieve general settings")
278
+
279
+ @router.put("/general")
280
+ async def update_general_settings(settings: GeneralSettings):
281
+ """Update general platform settings"""
282
+ try:
283
+ env_vars = read_env_file()
284
+
285
+ # Update settings
286
+ if settings.platform_name:
287
+ env_vars["PLATFORM_NAME"] = settings.platform_name
288
+ if settings.default_provider:
289
+ env_vars["DEFAULT_LLM_PROVIDER"] = settings.default_provider
290
+ if settings.log_level:
291
+ env_vars["LOG_LEVEL"] = settings.log_level
292
+ if settings.max_workers:
293
+ env_vars["MAX_WORKERS"] = str(settings.max_workers)
294
+ if settings.request_timeout:
295
+ env_vars["REQUEST_TIMEOUT"] = str(settings.request_timeout)
296
+
297
+ # Write back to file
298
+ if not write_env_file(env_vars):
299
+ raise HTTPException(status_code=500, detail="Failed to update configuration file")
300
+
301
+ return {
302
+ "success": True,
303
+ "message": "Successfully updated general settings",
304
+ "restart_required": True
305
+ }
306
+
307
+ except HTTPException:
308
+ raise
309
+ except Exception as e:
310
+ logger.error(f"Error updating general settings: {e}")
311
+ raise HTTPException(status_code=500, detail="Failed to update general settings")
312
+
313
+ @router.get("/backup")
314
+ async def get_config_backup():
315
+ """Get information about configuration backups"""
316
+ try:
317
+ backups = []
318
+
319
+ if CONFIG_BACKUP_FILE.exists():
320
+ stat = CONFIG_BACKUP_FILE.stat()
321
+ backups.append({
322
+ "filename": CONFIG_BACKUP_FILE.name,
323
+ "size": stat.st_size,
324
+ "created": stat.st_mtime,
325
+ "type": "automatic"
326
+ })
327
+
328
+ return {
329
+ "backups": backups,
330
+ "backup_location": str(CONFIG_DIR)
331
+ }
332
+
333
+ except Exception as e:
334
+ logger.error(f"Error getting backup info: {e}")
335
+ raise HTTPException(status_code=500, detail="Failed to retrieve backup information")
336
+
337
+ @router.post("/backup/restore")
338
+ async def restore_config_backup():
339
+ """Restore configuration from backup"""
340
+ try:
341
+ if not CONFIG_BACKUP_FILE.exists():
342
+ raise HTTPException(status_code=404, detail="No backup file found")
343
+
344
+ import shutil
345
+ shutil.copy2(CONFIG_BACKUP_FILE, ENV_FILE)
346
+
347
+ return {
348
+ "success": True,
349
+ "message": "Configuration restored from backup",
350
+ "restart_required": True
351
+ }
352
+
353
+ except HTTPException:
354
+ raise
355
+ except Exception as e:
356
+ logger.error(f"Error restoring backup: {e}")
357
+ raise HTTPException(status_code=500, detail="Failed to restore backup")
358
+
359
+ @router.get("/health")
360
+ async def settings_health():
361
+ """Health check for settings service"""
362
+ try:
363
+ env_exists = ENV_FILE.exists()
364
+ backup_exists = CONFIG_BACKUP_FILE.exists()
365
+
366
+ # Test read access
367
+ env_vars = read_env_file() if env_exists else {}
368
+
369
+ return {
370
+ "status": "healthy",
371
+ "service": "settings",
372
+ "config_file_exists": env_exists,
373
+ "backup_exists": backup_exists,
374
+ "config_vars_count": len(env_vars),
375
+ "writable": os.access(CONFIG_DIR, os.W_OK) if CONFIG_DIR.exists() else False
376
+ }
377
+
378
+ except Exception as e:
379
+ logger.error(f"Settings health check failed: {e}")
380
+ return {
381
+ "status": "unhealthy",
382
+ "service": "settings",
383
+ "error": str(e)
384
+ }
385
+
386
+ # =================== PLATFORM API KEY MANAGEMENT ===================
387
+
388
+ @router.get("/auth/status")
389
+ async def get_auth_status():
390
+ """Get current authentication status"""
391
+ try:
392
+ platform_keys = api_key_manager.list_api_keys()
393
+
394
+ return AuthSettings(
395
+ auth_enabled=api_key_manager.auth_enabled,
396
+ total_keys=len(platform_keys),
397
+ active_keys=sum(1 for key in platform_keys if key.get("active", True))
398
+ )
399
+
400
+ except Exception as e:
401
+ logger.error(f"Error getting auth status: {e}")
402
+ raise HTTPException(status_code=500, detail="Failed to get authentication status")
403
+
404
+ @router.post("/auth/bootstrap")
405
+ async def bootstrap_authentication():
406
+ """Bootstrap authentication by creating initial admin key (only works when auth is disabled)"""
407
+ try:
408
+ if api_key_manager.auth_enabled:
409
+ raise HTTPException(status_code=400, detail="Authentication is already enabled")
410
+
411
+ # Enable auth and create default keys
412
+ default_keys = api_key_manager.enable_auth()
413
+
414
+ return {
415
+ "success": True,
416
+ "message": "Authentication bootstrapped successfully",
417
+ "keys_generated": default_keys,
418
+ "restart_required": False, # This takes effect immediately
419
+ "warning": "Save the generated API keys securely. They will not be shown again."
420
+ }
421
+
422
+ except Exception as e:
423
+ logger.error(f"Error bootstrapping authentication: {e}")
424
+ raise HTTPException(status_code=500, detail="Failed to bootstrap authentication")
425
+
426
+ @router.post("/auth/enable")
427
+ async def enable_authentication(current_user: Dict = Depends(require_admin_access)):
428
+ """Enable API key authentication for the platform"""
429
+ try:
430
+ if api_key_manager.auth_enabled:
431
+ return {
432
+ "success": True,
433
+ "message": "Authentication is already enabled",
434
+ "keys_generated": None
435
+ }
436
+
437
+ # Enable auth and create default keys if needed
438
+ default_keys = api_key_manager.enable_auth()
439
+
440
+ return {
441
+ "success": True,
442
+ "message": "Authentication enabled successfully",
443
+ "keys_generated": default_keys,
444
+ "restart_required": True,
445
+ "warning": "Save the generated API keys securely. They will not be shown again."
446
+ }
447
+
448
+ except Exception as e:
449
+ logger.error(f"Error enabling authentication: {e}")
450
+ raise HTTPException(status_code=500, detail="Failed to enable authentication")
451
+
452
+ @router.post("/auth/disable")
453
+ async def disable_authentication(current_user: Dict = Depends(require_admin_access)):
454
+ """Disable API key authentication for the platform"""
455
+ try:
456
+ if not api_key_manager.auth_enabled:
457
+ return {
458
+ "success": True,
459
+ "message": "Authentication is already disabled"
460
+ }
461
+
462
+ api_key_manager.disable_auth()
463
+
464
+ return {
465
+ "success": True,
466
+ "message": "Authentication disabled successfully",
467
+ "restart_required": True,
468
+ "warning": "All endpoints are now publicly accessible"
469
+ }
470
+
471
+ except Exception as e:
472
+ logger.error(f"Error disabling authentication: {e}")
473
+ raise HTTPException(status_code=500, detail="Failed to disable authentication")
474
+
475
+ @router.get("/auth/platform-keys")
476
+ async def get_platform_api_keys(current_user: Dict = Depends(require_admin_access)):
477
+ """Get list of platform API keys"""
478
+ try:
479
+ if not api_key_manager.auth_enabled:
480
+ return {
481
+ "auth_enabled": False,
482
+ "api_keys": [],
483
+ "message": "Authentication is disabled"
484
+ }
485
+
486
+ keys = api_key_manager.list_api_keys()
487
+
488
+ return {
489
+ "auth_enabled": True,
490
+ "api_keys": keys,
491
+ "total_keys": len(keys)
492
+ }
493
+
494
+ except Exception as e:
495
+ logger.error(f"Error getting platform API keys: {e}")
496
+ raise HTTPException(status_code=500, detail="Failed to get platform API keys")
497
+
498
+ @router.post("/auth/platform-keys")
499
+ async def create_platform_api_key(
500
+ key_request: PlatformAPIKey,
501
+ current_user: Dict = Depends(require_admin_access)
502
+ ):
503
+ """Create a new platform API key"""
504
+ try:
505
+ if not api_key_manager.auth_enabled:
506
+ raise HTTPException(status_code=400, detail="Authentication is disabled")
507
+
508
+ # Validate scopes
509
+ valid_scopes = ["read", "write", "admin"]
510
+ invalid_scopes = [scope for scope in key_request.scopes if scope not in valid_scopes]
511
+ if invalid_scopes:
512
+ raise HTTPException(
513
+ status_code=400,
514
+ detail=f"Invalid scopes: {invalid_scopes}. Valid scopes: {valid_scopes}"
515
+ )
516
+
517
+ # Generate new API key
518
+ new_key = api_key_manager.generate_api_key(key_request.name, key_request.scopes)
519
+
520
+ return {
521
+ "success": True,
522
+ "message": f"API key '{key_request.name}' created successfully",
523
+ "api_key": new_key,
524
+ "scopes": key_request.scopes,
525
+ "warning": "Save this API key securely. It will not be shown again."
526
+ }
527
+
528
+ except HTTPException:
529
+ raise
530
+ except Exception as e:
531
+ logger.error(f"Error creating platform API key: {e}")
532
+ raise HTTPException(status_code=500, detail="Failed to create platform API key")
533
+
534
+ @router.delete("/auth/platform-keys/{key_hash}")
535
+ async def revoke_platform_api_key(
536
+ key_hash: str,
537
+ current_user: Dict = Depends(require_admin_access)
538
+ ):
539
+ """Revoke a platform API key"""
540
+ try:
541
+ if not api_key_manager.auth_enabled:
542
+ raise HTTPException(status_code=400, detail="Authentication is disabled")
543
+
544
+ # Find the key by hash prefix
545
+ keys = api_key_manager.list_api_keys()
546
+ target_key = None
547
+
548
+ for key in keys:
549
+ if key["key_hash"].startswith(key_hash):
550
+ target_key = key
551
+ break
552
+
553
+ if not target_key:
554
+ raise HTTPException(status_code=404, detail="API key not found")
555
+
556
+ # Note: We need the actual key to revoke, but we don't store it.
557
+ # In a real implementation, you'd store key hashes and mark them as revoked.
558
+ # For now, we'll mark it as revoked in the key data directly.
559
+
560
+ # This is a simplified revocation - in production you'd want a proper key management system
561
+ full_hash = None
562
+ for hash_key, data in api_key_manager.api_keys.items():
563
+ if hash_key.startswith(key_hash):
564
+ full_hash = hash_key
565
+ break
566
+
567
+ if full_hash:
568
+ api_key_manager.api_keys[full_hash]["active"] = False
569
+ api_key_manager.save_api_keys()
570
+
571
+ return {
572
+ "success": True,
573
+ "message": f"API key '{target_key['name']}' revoked successfully"
574
+ }
575
+ else:
576
+ raise HTTPException(status_code=404, detail="API key not found")
577
+
578
+ except HTTPException:
579
+ raise
580
+ except Exception as e:
581
+ logger.error(f"Error revoking platform API key: {e}")
582
+ raise HTTPException(status_code=500, detail="Failed to revoke platform API key")