isa-model 0.3.4__py3-none-any.whl → 0.3.6__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.
- isa_model/__init__.py +30 -1
- isa_model/client.py +770 -0
- isa_model/core/config/__init__.py +16 -0
- isa_model/core/config/config_manager.py +514 -0
- isa_model/core/config.py +426 -0
- isa_model/core/models/model_billing_tracker.py +476 -0
- isa_model/core/models/model_manager.py +399 -0
- isa_model/core/models/model_repo.py +343 -0
- isa_model/core/pricing_manager.py +426 -0
- isa_model/core/services/__init__.py +19 -0
- isa_model/core/services/intelligent_model_selector.py +547 -0
- isa_model/core/types.py +291 -0
- isa_model/deployment/__init__.py +2 -0
- isa_model/deployment/cloud/__init__.py +9 -0
- isa_model/deployment/cloud/modal/__init__.py +10 -0
- isa_model/deployment/cloud/modal/isa_vision_doc_service.py +766 -0
- isa_model/deployment/cloud/modal/isa_vision_table_service.py +532 -0
- isa_model/deployment/cloud/modal/isa_vision_ui_service.py +406 -0
- isa_model/deployment/cloud/modal/register_models.py +321 -0
- isa_model/deployment/runtime/deployed_service.py +338 -0
- isa_model/deployment/services/__init__.py +9 -0
- isa_model/deployment/services/auto_deploy_vision_service.py +537 -0
- isa_model/deployment/services/model_service.py +332 -0
- isa_model/deployment/services/service_monitor.py +356 -0
- isa_model/deployment/services/service_registry.py +527 -0
- isa_model/eval/__init__.py +80 -44
- isa_model/eval/config/__init__.py +10 -0
- isa_model/eval/config/evaluation_config.py +108 -0
- isa_model/eval/evaluators/__init__.py +18 -0
- isa_model/eval/evaluators/base_evaluator.py +503 -0
- isa_model/eval/evaluators/llm_evaluator.py +472 -0
- isa_model/eval/factory.py +417 -709
- isa_model/eval/infrastructure/__init__.py +24 -0
- isa_model/eval/infrastructure/experiment_tracker.py +466 -0
- isa_model/eval/metrics.py +191 -21
- isa_model/inference/ai_factory.py +187 -387
- isa_model/inference/providers/modal_provider.py +109 -0
- isa_model/inference/providers/yyds_provider.py +108 -0
- isa_model/inference/services/__init__.py +2 -1
- isa_model/inference/services/audio/base_stt_service.py +65 -1
- isa_model/inference/services/audio/base_tts_service.py +75 -1
- isa_model/inference/services/audio/openai_stt_service.py +189 -151
- isa_model/inference/services/audio/openai_tts_service.py +12 -10
- isa_model/inference/services/audio/replicate_tts_service.py +61 -56
- isa_model/inference/services/base_service.py +55 -55
- isa_model/inference/services/embedding/base_embed_service.py +65 -1
- isa_model/inference/services/embedding/ollama_embed_service.py +103 -43
- isa_model/inference/services/embedding/openai_embed_service.py +8 -10
- isa_model/inference/services/helpers/stacked_config.py +148 -0
- isa_model/inference/services/img/__init__.py +18 -0
- isa_model/inference/services/{vision → img}/base_image_gen_service.py +80 -35
- isa_model/inference/services/img/flux_professional_service.py +603 -0
- isa_model/inference/services/img/helpers/base_stacked_service.py +274 -0
- isa_model/inference/services/{vision → img}/replicate_image_gen_service.py +210 -69
- isa_model/inference/services/llm/__init__.py +3 -3
- isa_model/inference/services/llm/base_llm_service.py +519 -35
- isa_model/inference/services/llm/{llm_adapter.py → helpers/llm_adapter.py} +40 -0
- isa_model/inference/services/llm/helpers/llm_prompts.py +258 -0
- isa_model/inference/services/llm/helpers/llm_utils.py +280 -0
- isa_model/inference/services/llm/ollama_llm_service.py +150 -15
- isa_model/inference/services/llm/openai_llm_service.py +134 -31
- isa_model/inference/services/llm/yyds_llm_service.py +255 -0
- isa_model/inference/services/vision/__init__.py +38 -4
- isa_model/inference/services/vision/base_vision_service.py +241 -96
- isa_model/inference/services/vision/disabled/isA_vision_service.py +500 -0
- isa_model/inference/services/vision/doc_analysis_service.py +640 -0
- isa_model/inference/services/vision/helpers/base_stacked_service.py +274 -0
- isa_model/inference/services/vision/helpers/image_utils.py +272 -3
- isa_model/inference/services/vision/helpers/vision_prompts.py +297 -0
- isa_model/inference/services/vision/openai_vision_service.py +109 -170
- isa_model/inference/services/vision/replicate_vision_service.py +508 -0
- isa_model/inference/services/vision/ui_analysis_service.py +823 -0
- isa_model/scripts/register_models.py +370 -0
- isa_model/scripts/register_models_with_embeddings.py +510 -0
- isa_model/serving/__init__.py +19 -0
- isa_model/serving/api/__init__.py +10 -0
- isa_model/serving/api/fastapi_server.py +89 -0
- isa_model/serving/api/middleware/__init__.py +9 -0
- isa_model/serving/api/middleware/request_logger.py +88 -0
- isa_model/serving/api/routes/__init__.py +5 -0
- isa_model/serving/api/routes/health.py +82 -0
- isa_model/serving/api/routes/llm.py +19 -0
- isa_model/serving/api/routes/ui_analysis.py +223 -0
- isa_model/serving/api/routes/unified.py +202 -0
- isa_model/serving/api/routes/vision.py +19 -0
- isa_model/serving/api/schemas/__init__.py +17 -0
- isa_model/serving/api/schemas/common.py +33 -0
- isa_model/serving/api/schemas/ui_analysis.py +78 -0
- {isa_model-0.3.4.dist-info → isa_model-0.3.6.dist-info}/METADATA +4 -1
- isa_model-0.3.6.dist-info/RECORD +147 -0
- isa_model/core/model_manager.py +0 -208
- isa_model/core/model_registry.py +0 -342
- isa_model/inference/billing_tracker.py +0 -406
- isa_model/inference/services/llm/triton_llm_service.py +0 -481
- isa_model/inference/services/vision/ollama_vision_service.py +0 -194
- isa_model-0.3.4.dist-info/RECORD +0 -91
- /isa_model/core/{model_storage.py → models/model_storage.py} +0 -0
- /isa_model/inference/services/{vision → embedding}/helpers/text_splitter.py +0 -0
- {isa_model-0.3.4.dist-info → isa_model-0.3.6.dist-info}/WHEEL +0 -0
- {isa_model-0.3.4.dist-info → isa_model-0.3.6.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,88 @@
|
|
1
|
+
"""
|
2
|
+
Request Logger Middleware
|
3
|
+
|
4
|
+
Logs all incoming requests and responses for monitoring
|
5
|
+
"""
|
6
|
+
|
7
|
+
from fastapi import Request, Response
|
8
|
+
from starlette.middleware.base import BaseHTTPMiddleware
|
9
|
+
import time
|
10
|
+
import logging
|
11
|
+
import json
|
12
|
+
from typing import Callable
|
13
|
+
|
14
|
+
logger = logging.getLogger(__name__)
|
15
|
+
|
16
|
+
class RequestLoggerMiddleware(BaseHTTPMiddleware):
|
17
|
+
"""
|
18
|
+
Middleware to log HTTP requests and responses
|
19
|
+
"""
|
20
|
+
|
21
|
+
def __init__(self, app, log_body: bool = False):
|
22
|
+
super().__init__(app)
|
23
|
+
self.log_body = log_body
|
24
|
+
|
25
|
+
async def dispatch(self, request: Request, call_next: Callable) -> Response:
|
26
|
+
"""
|
27
|
+
Process request and log details
|
28
|
+
"""
|
29
|
+
start_time = time.time()
|
30
|
+
|
31
|
+
# Log request
|
32
|
+
request_info = {
|
33
|
+
"method": request.method,
|
34
|
+
"url": str(request.url),
|
35
|
+
"headers": dict(request.headers),
|
36
|
+
"client": request.client.host if request.client else None,
|
37
|
+
"timestamp": start_time
|
38
|
+
}
|
39
|
+
|
40
|
+
# Optionally log request body (be careful with large images)
|
41
|
+
if self.log_body and request.method in ["POST", "PUT", "PATCH"]:
|
42
|
+
try:
|
43
|
+
body = await request.body()
|
44
|
+
if len(body) < 1024: # Only log small bodies
|
45
|
+
request_info["body_size"] = len(body)
|
46
|
+
else:
|
47
|
+
request_info["body_size"] = len(body)
|
48
|
+
request_info["body_preview"] = "Large body truncated"
|
49
|
+
except Exception as e:
|
50
|
+
request_info["body_error"] = str(e)
|
51
|
+
|
52
|
+
logger.info(f"Request: {json.dumps(request_info, default=str)}")
|
53
|
+
|
54
|
+
# Process request
|
55
|
+
try:
|
56
|
+
response = await call_next(request)
|
57
|
+
|
58
|
+
# Calculate processing time
|
59
|
+
process_time = time.time() - start_time
|
60
|
+
|
61
|
+
# Log response
|
62
|
+
response_info = {
|
63
|
+
"status_code": response.status_code,
|
64
|
+
"processing_time": process_time,
|
65
|
+
"url": str(request.url),
|
66
|
+
"method": request.method
|
67
|
+
}
|
68
|
+
|
69
|
+
# Add processing time header
|
70
|
+
response.headers["X-Process-Time"] = str(process_time)
|
71
|
+
|
72
|
+
if response.status_code >= 400:
|
73
|
+
logger.warning(f"Response: {json.dumps(response_info, default=str)}")
|
74
|
+
else:
|
75
|
+
logger.info(f"Response: {json.dumps(response_info, default=str)}")
|
76
|
+
|
77
|
+
return response
|
78
|
+
|
79
|
+
except Exception as e:
|
80
|
+
process_time = time.time() - start_time
|
81
|
+
error_info = {
|
82
|
+
"error": str(e),
|
83
|
+
"processing_time": process_time,
|
84
|
+
"url": str(request.url),
|
85
|
+
"method": request.method
|
86
|
+
}
|
87
|
+
logger.error(f"Request error: {json.dumps(error_info, default=str)}")
|
88
|
+
raise
|
@@ -0,0 +1,82 @@
|
|
1
|
+
"""
|
2
|
+
Health Check Routes
|
3
|
+
|
4
|
+
System health and status endpoints
|
5
|
+
"""
|
6
|
+
|
7
|
+
from fastapi import APIRouter, HTTPException
|
8
|
+
from pydantic import BaseModel
|
9
|
+
import time
|
10
|
+
import psutil
|
11
|
+
import torch
|
12
|
+
from typing import Dict, Any
|
13
|
+
|
14
|
+
router = APIRouter()
|
15
|
+
|
16
|
+
class HealthResponse(BaseModel):
|
17
|
+
status: str
|
18
|
+
timestamp: float
|
19
|
+
version: str
|
20
|
+
uptime: float
|
21
|
+
system: Dict[str, Any]
|
22
|
+
|
23
|
+
@router.get("/", response_model=HealthResponse)
|
24
|
+
async def health_check():
|
25
|
+
"""
|
26
|
+
Basic health check endpoint
|
27
|
+
"""
|
28
|
+
return HealthResponse(
|
29
|
+
status="healthy",
|
30
|
+
timestamp=time.time(),
|
31
|
+
version="1.0.0",
|
32
|
+
uptime=time.time(), # Simplified uptime
|
33
|
+
system={
|
34
|
+
"cpu_percent": psutil.cpu_percent(),
|
35
|
+
"memory_percent": psutil.virtual_memory().percent,
|
36
|
+
"gpu_available": torch.cuda.is_available(),
|
37
|
+
"gpu_count": torch.cuda.device_count() if torch.cuda.is_available() else 0
|
38
|
+
}
|
39
|
+
)
|
40
|
+
|
41
|
+
@router.get("/detailed")
|
42
|
+
async def detailed_health():
|
43
|
+
"""
|
44
|
+
Detailed health check with system information
|
45
|
+
"""
|
46
|
+
gpu_info = []
|
47
|
+
if torch.cuda.is_available():
|
48
|
+
for i in range(torch.cuda.device_count()):
|
49
|
+
gpu_info.append({
|
50
|
+
"device": i,
|
51
|
+
"name": torch.cuda.get_device_name(i),
|
52
|
+
"memory_allocated": torch.cuda.memory_allocated(i),
|
53
|
+
"memory_cached": torch.cuda.memory_reserved(i)
|
54
|
+
})
|
55
|
+
|
56
|
+
return {
|
57
|
+
"status": "healthy",
|
58
|
+
"timestamp": time.time(),
|
59
|
+
"system": {
|
60
|
+
"cpu": {
|
61
|
+
"percent": psutil.cpu_percent(),
|
62
|
+
"count": psutil.cpu_count()
|
63
|
+
},
|
64
|
+
"memory": {
|
65
|
+
"percent": psutil.virtual_memory().percent,
|
66
|
+
"available": psutil.virtual_memory().available,
|
67
|
+
"total": psutil.virtual_memory().total
|
68
|
+
},
|
69
|
+
"gpu": {
|
70
|
+
"available": torch.cuda.is_available(),
|
71
|
+
"devices": gpu_info
|
72
|
+
}
|
73
|
+
}
|
74
|
+
}
|
75
|
+
|
76
|
+
@router.get("/ready")
|
77
|
+
async def readiness_probe():
|
78
|
+
"""
|
79
|
+
Kubernetes readiness probe endpoint
|
80
|
+
"""
|
81
|
+
# Add model loading checks here
|
82
|
+
return {"status": "ready", "timestamp": time.time()}
|
@@ -0,0 +1,19 @@
|
|
1
|
+
"""
|
2
|
+
LLM API Routes
|
3
|
+
|
4
|
+
Endpoints for language model tasks (placeholder)
|
5
|
+
"""
|
6
|
+
|
7
|
+
from fastapi import APIRouter, HTTPException
|
8
|
+
from pydantic import BaseModel
|
9
|
+
|
10
|
+
router = APIRouter()
|
11
|
+
|
12
|
+
@router.get("/")
|
13
|
+
async def llm_info():
|
14
|
+
"""LLM service information"""
|
15
|
+
return {
|
16
|
+
"service": "llm",
|
17
|
+
"status": "placeholder",
|
18
|
+
"description": "Language model processing endpoints"
|
19
|
+
}
|
@@ -0,0 +1,223 @@
|
|
1
|
+
"""
|
2
|
+
UI Analysis API Routes
|
3
|
+
|
4
|
+
Endpoints for UI element detection and analysis
|
5
|
+
"""
|
6
|
+
|
7
|
+
from fastapi import APIRouter, HTTPException, UploadFile, File, Form
|
8
|
+
from pydantic import BaseModel
|
9
|
+
from typing import List, Dict, Any, Optional
|
10
|
+
import base64
|
11
|
+
import time
|
12
|
+
import logging
|
13
|
+
|
14
|
+
from ..schemas.ui_analysis import (
|
15
|
+
UIAnalysisRequest,
|
16
|
+
UIAnalysisResponse,
|
17
|
+
UIElement,
|
18
|
+
ActionPlan
|
19
|
+
)
|
20
|
+
|
21
|
+
router = APIRouter()
|
22
|
+
logger = logging.getLogger(__name__)
|
23
|
+
|
24
|
+
class UIAnalysisService:
|
25
|
+
"""
|
26
|
+
Placeholder for UI Analysis Service
|
27
|
+
Will be replaced with actual Modal deployment integration
|
28
|
+
"""
|
29
|
+
|
30
|
+
@staticmethod
|
31
|
+
async def analyze_ui(image_b64: str, task_type: str = "search") -> Dict[str, Any]:
|
32
|
+
"""
|
33
|
+
Placeholder method for UI analysis
|
34
|
+
"""
|
35
|
+
# TODO: Replace with actual Modal service call
|
36
|
+
return {
|
37
|
+
"success": True,
|
38
|
+
"service": "ui_analysis",
|
39
|
+
"total_execution_time": 2.5,
|
40
|
+
"final_output": {
|
41
|
+
"ui_elements": {
|
42
|
+
"interactive_elements": [
|
43
|
+
{
|
44
|
+
"id": "ui_0",
|
45
|
+
"type": "textbox",
|
46
|
+
"content": "Search",
|
47
|
+
"center": [400, 200],
|
48
|
+
"bbox": [300, 180, 500, 220],
|
49
|
+
"confidence": 0.95,
|
50
|
+
"interactable": True
|
51
|
+
}
|
52
|
+
],
|
53
|
+
"summary": {
|
54
|
+
"interactive_count": 1,
|
55
|
+
"detection_confidence": 0.95
|
56
|
+
}
|
57
|
+
},
|
58
|
+
"action_plan": {
|
59
|
+
"action_plan": [
|
60
|
+
{
|
61
|
+
"step": 1,
|
62
|
+
"action": "click",
|
63
|
+
"target_coordinates": [400, 200],
|
64
|
+
"actual_coordinates": [400, 200],
|
65
|
+
"description": "Click search box",
|
66
|
+
"confidence": 0.95
|
67
|
+
}
|
68
|
+
]
|
69
|
+
},
|
70
|
+
"automation_ready": {
|
71
|
+
"ready": True,
|
72
|
+
"confidence": 0.95,
|
73
|
+
"page_type": task_type,
|
74
|
+
"steps_count": 1
|
75
|
+
}
|
76
|
+
}
|
77
|
+
}
|
78
|
+
|
79
|
+
@router.post("/analyze", response_model=UIAnalysisResponse)
|
80
|
+
async def analyze_ui_elements(request: UIAnalysisRequest):
|
81
|
+
"""
|
82
|
+
Analyze UI elements in an image
|
83
|
+
|
84
|
+
Args:
|
85
|
+
request: UI analysis request with image and task type
|
86
|
+
|
87
|
+
Returns:
|
88
|
+
UI analysis results with detected elements and action plan
|
89
|
+
"""
|
90
|
+
try:
|
91
|
+
start_time = time.time()
|
92
|
+
|
93
|
+
# Validate task type
|
94
|
+
valid_task_types = ["login", "search", "content", "navigation"]
|
95
|
+
if request.task_type not in valid_task_types:
|
96
|
+
raise HTTPException(
|
97
|
+
status_code=400,
|
98
|
+
detail=f"Invalid task_type. Must be one of: {valid_task_types}"
|
99
|
+
)
|
100
|
+
|
101
|
+
# Call UI analysis service
|
102
|
+
result = await UIAnalysisService.analyze_ui(
|
103
|
+
request.image_b64,
|
104
|
+
request.task_type
|
105
|
+
)
|
106
|
+
|
107
|
+
if not result.get("success"):
|
108
|
+
raise HTTPException(
|
109
|
+
status_code=500,
|
110
|
+
detail=f"UI analysis failed: {result.get('error', 'Unknown error')}"
|
111
|
+
)
|
112
|
+
|
113
|
+
# Convert to response model
|
114
|
+
final_output = result["final_output"]
|
115
|
+
|
116
|
+
return UIAnalysisResponse(
|
117
|
+
success=True,
|
118
|
+
service="ui_analysis",
|
119
|
+
total_execution_time=result["total_execution_time"],
|
120
|
+
ui_elements=[
|
121
|
+
UIElement(**elem)
|
122
|
+
for elem in final_output["ui_elements"]["interactive_elements"]
|
123
|
+
],
|
124
|
+
action_plan=ActionPlan(
|
125
|
+
steps=final_output["action_plan"]["action_plan"]
|
126
|
+
),
|
127
|
+
automation_ready=final_output["automation_ready"],
|
128
|
+
metadata={
|
129
|
+
"detection_method": "modal_omniparser",
|
130
|
+
"request_time": start_time,
|
131
|
+
"task_type": request.task_type
|
132
|
+
}
|
133
|
+
)
|
134
|
+
|
135
|
+
except HTTPException:
|
136
|
+
raise
|
137
|
+
except Exception as e:
|
138
|
+
logger.error(f"UI analysis error: {e}", exc_info=True)
|
139
|
+
raise HTTPException(status_code=500, detail=str(e))
|
140
|
+
|
141
|
+
@router.post("/upload")
|
142
|
+
async def upload_and_analyze(
|
143
|
+
file: UploadFile = File(...),
|
144
|
+
task_type: str = Form("search")
|
145
|
+
):
|
146
|
+
"""
|
147
|
+
Upload image file and analyze UI elements
|
148
|
+
|
149
|
+
Args:
|
150
|
+
file: Image file upload
|
151
|
+
task_type: Type of UI analysis task
|
152
|
+
|
153
|
+
Returns:
|
154
|
+
UI analysis results
|
155
|
+
"""
|
156
|
+
try:
|
157
|
+
# Validate file type
|
158
|
+
if not file.content_type.startswith('image/'):
|
159
|
+
raise HTTPException(
|
160
|
+
status_code=400,
|
161
|
+
detail="File must be an image"
|
162
|
+
)
|
163
|
+
|
164
|
+
# Read and encode image
|
165
|
+
image_data = await file.read()
|
166
|
+
image_b64 = base64.b64encode(image_data).decode()
|
167
|
+
|
168
|
+
# Create request
|
169
|
+
request = UIAnalysisRequest(
|
170
|
+
image_b64=image_b64,
|
171
|
+
task_type=task_type
|
172
|
+
)
|
173
|
+
|
174
|
+
# Analyze
|
175
|
+
return await analyze_ui_elements(request)
|
176
|
+
|
177
|
+
except HTTPException:
|
178
|
+
raise
|
179
|
+
except Exception as e:
|
180
|
+
logger.error(f"Upload and analyze error: {e}", exc_info=True)
|
181
|
+
raise HTTPException(status_code=500, detail=str(e))
|
182
|
+
|
183
|
+
@router.post("/detect")
|
184
|
+
async def detect_elements_only(request: UIAnalysisRequest):
|
185
|
+
"""
|
186
|
+
Detect UI elements only (without action planning)
|
187
|
+
|
188
|
+
Args:
|
189
|
+
request: UI analysis request
|
190
|
+
|
191
|
+
Returns:
|
192
|
+
UI elements detection results
|
193
|
+
"""
|
194
|
+
try:
|
195
|
+
# Call UI analysis service for detection only
|
196
|
+
result = await UIAnalysisService.analyze_ui(
|
197
|
+
request.image_b64,
|
198
|
+
request.task_type
|
199
|
+
)
|
200
|
+
|
201
|
+
if not result.get("success"):
|
202
|
+
raise HTTPException(
|
203
|
+
status_code=500,
|
204
|
+
detail=f"UI detection failed: {result.get('error', 'Unknown error')}"
|
205
|
+
)
|
206
|
+
|
207
|
+
# Return only UI elements
|
208
|
+
final_output = result["final_output"]
|
209
|
+
ui_elements = final_output["ui_elements"]["interactive_elements"]
|
210
|
+
|
211
|
+
return {
|
212
|
+
"success": True,
|
213
|
+
"processing_time": result["total_execution_time"],
|
214
|
+
"ui_elements": ui_elements,
|
215
|
+
"element_count": len(ui_elements),
|
216
|
+
"task_type": request.task_type
|
217
|
+
}
|
218
|
+
|
219
|
+
except HTTPException:
|
220
|
+
raise
|
221
|
+
except Exception as e:
|
222
|
+
logger.error(f"UI detection error: {e}", exc_info=True)
|
223
|
+
raise HTTPException(status_code=500, detail=str(e))
|
@@ -0,0 +1,202 @@
|
|
1
|
+
"""
|
2
|
+
Unified API Route - Single endpoint for all AI services
|
3
|
+
|
4
|
+
This is the main API that handles all types of AI requests:
|
5
|
+
- Vision tasks (image analysis, OCR, UI detection)
|
6
|
+
- Text tasks (chat, generation, translation)
|
7
|
+
- Audio tasks (TTS, STT)
|
8
|
+
- Image generation tasks
|
9
|
+
- Embedding tasks
|
10
|
+
"""
|
11
|
+
|
12
|
+
from fastapi import APIRouter, HTTPException, UploadFile, File, Form
|
13
|
+
from pydantic import BaseModel, Field
|
14
|
+
from typing import Optional, Dict, Any, Union, List
|
15
|
+
import logging
|
16
|
+
import asyncio
|
17
|
+
from pathlib import Path
|
18
|
+
|
19
|
+
from isa_model.client import ISAModelClient
|
20
|
+
|
21
|
+
logger = logging.getLogger(__name__)
|
22
|
+
router = APIRouter()
|
23
|
+
|
24
|
+
class UnifiedRequest(BaseModel):
|
25
|
+
"""Unified request model for all AI services"""
|
26
|
+
input_data: Union[str, Dict[str, Any]] = Field(..., description="Input data (text, image URL, etc.)")
|
27
|
+
task: str = Field(..., description="Task to perform (chat, analyze_image, generate_speech, etc.)")
|
28
|
+
service_type: str = Field(..., description="Service type (text, vision, audio, image, embedding)")
|
29
|
+
model_hint: Optional[str] = Field(None, description="Optional model preference")
|
30
|
+
provider_hint: Optional[str] = Field(None, description="Optional provider preference")
|
31
|
+
parameters: Optional[Dict[str, Any]] = Field(default_factory=dict, description="Additional task parameters")
|
32
|
+
|
33
|
+
class UnifiedResponse(BaseModel):
|
34
|
+
"""Unified response model for all AI services"""
|
35
|
+
success: bool
|
36
|
+
result: Optional[Any] = None
|
37
|
+
error: Optional[str] = None
|
38
|
+
metadata: Dict[str, Any]
|
39
|
+
|
40
|
+
# Global ISA client instance for server-side processing
|
41
|
+
_isa_client = None
|
42
|
+
|
43
|
+
def get_isa_client():
|
44
|
+
"""Get or create ISA client for local processing"""
|
45
|
+
global _isa_client
|
46
|
+
if _isa_client is None:
|
47
|
+
_isa_client = ISAModelClient(mode="local") # Use local mode
|
48
|
+
return _isa_client
|
49
|
+
|
50
|
+
@router.get("/")
|
51
|
+
async def unified_info():
|
52
|
+
"""API information"""
|
53
|
+
return {
|
54
|
+
"service": "unified_api",
|
55
|
+
"status": "active",
|
56
|
+
"description": "Single endpoint for all AI services",
|
57
|
+
"supported_service_types": ["vision", "text", "audio", "image", "embedding"],
|
58
|
+
"version": "1.0.0"
|
59
|
+
}
|
60
|
+
|
61
|
+
@router.post("/invoke", response_model=UnifiedResponse)
|
62
|
+
async def unified_invoke(request: UnifiedRequest) -> UnifiedResponse:
|
63
|
+
"""
|
64
|
+
**Unified API endpoint for all AI services**
|
65
|
+
|
66
|
+
This single endpoint handles:
|
67
|
+
- Vision: image analysis, OCR, UI detection
|
68
|
+
- Text: chat, generation, translation
|
69
|
+
- Audio: TTS, STT, transcription
|
70
|
+
- Image: generation, img2img
|
71
|
+
- Embedding: text embedding, similarity
|
72
|
+
|
73
|
+
**Uses ISAModelClient in local mode - all the complex logic is in client.py**
|
74
|
+
"""
|
75
|
+
try:
|
76
|
+
# Get ISA client instance (local mode)
|
77
|
+
client = get_isa_client()
|
78
|
+
|
79
|
+
# Use client's local invoke method directly
|
80
|
+
# This handles all the complexity: model selection, service routing, execution
|
81
|
+
result = await client._invoke_local(
|
82
|
+
input_data=request.input_data,
|
83
|
+
task=request.task,
|
84
|
+
service_type=request.service_type,
|
85
|
+
model_hint=request.model_hint,
|
86
|
+
provider_hint=request.provider_hint,
|
87
|
+
**request.parameters
|
88
|
+
)
|
89
|
+
|
90
|
+
# Return the result in our API format
|
91
|
+
return UnifiedResponse(
|
92
|
+
success=result["success"],
|
93
|
+
result=result.get("result"),
|
94
|
+
error=result.get("error"),
|
95
|
+
metadata=result["metadata"]
|
96
|
+
)
|
97
|
+
|
98
|
+
except Exception as e:
|
99
|
+
logger.error(f"Unified invoke failed: {e}")
|
100
|
+
return UnifiedResponse(
|
101
|
+
success=False,
|
102
|
+
error=str(e),
|
103
|
+
metadata={
|
104
|
+
"task": request.task,
|
105
|
+
"service_type": request.service_type,
|
106
|
+
"model_hint": request.model_hint,
|
107
|
+
"provider_hint": request.provider_hint
|
108
|
+
}
|
109
|
+
)
|
110
|
+
|
111
|
+
@router.post("/invoke-file", response_model=UnifiedResponse)
|
112
|
+
async def unified_invoke_file(
|
113
|
+
task: str = Form(...),
|
114
|
+
service_type: str = Form(...),
|
115
|
+
model_hint: Optional[str] = Form(None),
|
116
|
+
provider_hint: Optional[str] = Form(None),
|
117
|
+
file: UploadFile = File(...)
|
118
|
+
) -> UnifiedResponse:
|
119
|
+
"""
|
120
|
+
Unified file upload endpoint
|
121
|
+
|
122
|
+
For tasks that require file input (images, audio, documents)
|
123
|
+
"""
|
124
|
+
try:
|
125
|
+
# Read file data
|
126
|
+
file_data = await file.read()
|
127
|
+
|
128
|
+
# Get ISA client instance (local mode)
|
129
|
+
client = get_isa_client()
|
130
|
+
|
131
|
+
# Use client's local invoke method with binary data
|
132
|
+
result = await client._invoke_local(
|
133
|
+
input_data=file_data, # Binary data
|
134
|
+
task=task,
|
135
|
+
service_type=service_type,
|
136
|
+
model_hint=model_hint,
|
137
|
+
provider_hint=provider_hint,
|
138
|
+
filename=file.filename,
|
139
|
+
content_type=file.content_type,
|
140
|
+
file_size=len(file_data)
|
141
|
+
)
|
142
|
+
|
143
|
+
# Return the result in our API format
|
144
|
+
return UnifiedResponse(
|
145
|
+
success=result["success"],
|
146
|
+
result=result.get("result"),
|
147
|
+
error=result.get("error"),
|
148
|
+
metadata={
|
149
|
+
**result["metadata"],
|
150
|
+
"filename": file.filename,
|
151
|
+
"content_type": file.content_type,
|
152
|
+
"file_size": len(file_data)
|
153
|
+
}
|
154
|
+
)
|
155
|
+
|
156
|
+
except Exception as e:
|
157
|
+
logger.error(f"File invoke failed: {e}")
|
158
|
+
return UnifiedResponse(
|
159
|
+
success=False,
|
160
|
+
error=str(e),
|
161
|
+
metadata={
|
162
|
+
"task": task,
|
163
|
+
"service_type": service_type,
|
164
|
+
"filename": file.filename if file else None
|
165
|
+
}
|
166
|
+
)
|
167
|
+
|
168
|
+
@router.get("/models")
|
169
|
+
async def get_available_models(service_type: Optional[str] = None):
|
170
|
+
"""Get available models (optional filter by service type)"""
|
171
|
+
try:
|
172
|
+
client = get_isa_client()
|
173
|
+
return await client.get_available_models(service_type)
|
174
|
+
except Exception as e:
|
175
|
+
logger.error(f"Failed to get available models: {e}")
|
176
|
+
# Fallback static model list
|
177
|
+
return {
|
178
|
+
"models": [
|
179
|
+
{"service_type": "vision", "provider": "openai", "model_id": "gpt-4.1-mini"},
|
180
|
+
{"service_type": "text", "provider": "openai", "model_id": "gpt-4.1-mini"},
|
181
|
+
{"service_type": "audio", "provider": "openai", "model_id": "whisper-1"},
|
182
|
+
{"service_type": "audio", "provider": "openai", "model_id": "tts-1"},
|
183
|
+
{"service_type": "embedding", "provider": "openai", "model_id": "text-embedding-3-small"},
|
184
|
+
{"service_type": "image", "provider": "replicate", "model_id": "black-forest-labs/flux-schnell"}
|
185
|
+
]
|
186
|
+
}
|
187
|
+
|
188
|
+
@router.get("/health")
|
189
|
+
async def health_check():
|
190
|
+
"""Health check for unified API"""
|
191
|
+
try:
|
192
|
+
client = get_isa_client()
|
193
|
+
health_result = await client.health_check()
|
194
|
+
return {
|
195
|
+
"api": "healthy",
|
196
|
+
"client_health": health_result
|
197
|
+
}
|
198
|
+
except Exception as e:
|
199
|
+
return {
|
200
|
+
"api": "error",
|
201
|
+
"error": str(e)
|
202
|
+
}
|
@@ -0,0 +1,19 @@
|
|
1
|
+
"""
|
2
|
+
Vision API Routes
|
3
|
+
|
4
|
+
Endpoints for general vision tasks (placeholder)
|
5
|
+
"""
|
6
|
+
|
7
|
+
from fastapi import APIRouter, HTTPException
|
8
|
+
from pydantic import BaseModel
|
9
|
+
|
10
|
+
router = APIRouter()
|
11
|
+
|
12
|
+
@router.get("/")
|
13
|
+
async def vision_info():
|
14
|
+
"""Vision service information"""
|
15
|
+
return {
|
16
|
+
"service": "vision",
|
17
|
+
"status": "placeholder",
|
18
|
+
"description": "General vision processing endpoints"
|
19
|
+
}
|
@@ -0,0 +1,17 @@
|
|
1
|
+
"""
|
2
|
+
API Schemas Module
|
3
|
+
|
4
|
+
Pydantic models for API request and response validation
|
5
|
+
"""
|
6
|
+
|
7
|
+
from .ui_analysis import *
|
8
|
+
from .common import *
|
9
|
+
|
10
|
+
__all__ = [
|
11
|
+
"UIAnalysisRequest",
|
12
|
+
"UIAnalysisResponse",
|
13
|
+
"UIElement",
|
14
|
+
"ActionPlan",
|
15
|
+
"BaseResponse",
|
16
|
+
"ErrorResponse"
|
17
|
+
]
|
@@ -0,0 +1,33 @@
|
|
1
|
+
"""
|
2
|
+
Common API Schemas
|
3
|
+
|
4
|
+
Base schemas used across different endpoints
|
5
|
+
"""
|
6
|
+
|
7
|
+
from pydantic import BaseModel
|
8
|
+
from typing import Dict, Any, Optional
|
9
|
+
import time
|
10
|
+
|
11
|
+
class BaseResponse(BaseModel):
|
12
|
+
"""Base response model"""
|
13
|
+
success: bool
|
14
|
+
timestamp: float = time.time()
|
15
|
+
|
16
|
+
class ErrorResponse(BaseResponse):
|
17
|
+
"""Error response model"""
|
18
|
+
success: bool = False
|
19
|
+
error: str
|
20
|
+
detail: Optional[str] = None
|
21
|
+
|
22
|
+
class HealthStatus(BaseModel):
|
23
|
+
"""Health status model"""
|
24
|
+
status: str
|
25
|
+
timestamp: float
|
26
|
+
version: str
|
27
|
+
|
28
|
+
class SystemInfo(BaseModel):
|
29
|
+
"""System information model"""
|
30
|
+
cpu_percent: float
|
31
|
+
memory_percent: float
|
32
|
+
gpu_available: bool
|
33
|
+
gpu_count: int
|