Skip to content

Commit 0c18bb5

Browse files
committed
Add Kiro CLI integration for v4 API
- Add api_core/kiro.py with CLI-based translation - Add v4 router with Kiro endpoints (/v4/GPU, /v4/NPU) - Update app.py to include v4 routers - Clean ANSI formatting from CLI output - Maintain same API structure as v3 Gemini integration
1 parent 2a97b2b commit 0c18bb5

6 files changed

Lines changed: 254 additions & 2 deletions

File tree

‎api_core/app.py‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,13 @@ async def global_exception_handler(request: Request, exc: Exception):
2626
)
2727

2828
# Dynamically load routers from versioned packages
29-
from .routers import v1, v2, v3
29+
from .routers import v1, v2, v3, v4
3030

3131
versions = {
3232
"v1": v1,
3333
"v2": v2,
34-
"v3": v3
34+
"v3": v3,
35+
"v4": v4
3536
}
3637

3738
for version_name, version_package in versions.items():

‎api_core/kiro.py‎

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import asyncio
2+
import logging
3+
import re
4+
5+
logger = logging.getLogger(__name__)
6+
7+
def clean_kiro_output(text: str) -> str:
8+
"""Clean ANSI escape codes and CLI formatting from Kiro output."""
9+
if not text:
10+
return text
11+
12+
# Remove ANSI escape codes
13+
ansi_escape = re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])')
14+
cleaned = ansi_escape.sub('', text)
15+
16+
# Remove CLI prompt markers
17+
cleaned = re.sub(r'^>\s*', '', cleaned, flags=re.MULTILINE)
18+
19+
return cleaned.strip()
20+
21+
async def call_kiro_cli(prompt: str):
22+
"""Calls Kiro using CLI interface."""
23+
try:
24+
# Run kiro-cli chat command with prompt as argument
25+
process = await asyncio.create_subprocess_exec(
26+
'kiro-cli', 'chat', '--no-interactive', prompt,
27+
stdout=asyncio.subprocess.PIPE,
28+
stderr=asyncio.subprocess.PIPE
29+
)
30+
31+
stdout, stderr = await process.communicate()
32+
33+
if process.returncode == 0:
34+
raw_output = stdout.decode().strip()
35+
return clean_kiro_output(raw_output)
36+
else:
37+
logger.error(f"Kiro CLI error: {stderr.decode()}")
38+
return None
39+
40+
except Exception as e:
41+
logger.error(f"Error calling Kiro CLI: {e}")
42+
return None
43+
44+
async def glosses_to_text_async(glosses, target_language="English"):
45+
"""Translate DGS glosses to text using Kiro CLI."""
46+
if target_language == "German":
47+
prompt = f"""Translate these German Sign Language glosses to German: {glosses}
48+
49+
Return only the German translation, no explanations."""
50+
else:
51+
prompt = f"""Translate these German Sign Language glosses to English: {glosses}
52+
53+
Return only the English translation, no explanations."""
54+
55+
response = await call_kiro_cli(prompt)
56+
return response
57+
58+
async def custom_prompt_async(prompt):
59+
"""Send custom prompt to Kiro CLI."""
60+
response = await call_kiro_cli(prompt)
61+
return response
62+
63+
async def process_video_translation(prediction_text: str, target_language: str = "English"):
64+
"""
65+
High-level function to process the translation flow using Kiro CLI:
66+
1. Translate glosses to text
67+
2. Generate summary (if English)
68+
3. Return result dict
69+
"""
70+
if not prediction_text:
71+
return None
72+
73+
kiro_response = await glosses_to_text_async(prediction_text, target_language)
74+
75+
if not kiro_response:
76+
return None
77+
78+
result = {"translation": kiro_response}
79+
80+
# Only generate summary for English
81+
if target_language == "English":
82+
summary_prompt = f"Summarize in 2-4 words: {kiro_response}"
83+
kiro_summary = await custom_prompt_async(summary_prompt)
84+
85+
if kiro_summary:
86+
result["summary"] = kiro_summary
87+
else:
88+
result["summary"] = "No summary generated"
89+
90+
return result

‎api_core/routers/__init__.py‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from . import v4

‎api_core/routers/v4/README.md‎

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# SignAI-SFS v4 API with Kiro CLI Integration
2+
3+
This version integrates Kiro AI via CLI for sign language translation.
4+
5+
## Setup
6+
7+
1. Install Kiro CLI:
8+
```bash
9+
# Download and install from https://kiro.dev
10+
curl -fsSL https://kiro.dev/install.sh | sh
11+
```
12+
13+
2. Authenticate with Kiro:
14+
```bash
15+
kiro-cli auth login
16+
# Choose: GitHub, Google, AWS Builder ID, or AWS IAM Identity Center
17+
```
18+
19+
3. Verify installation:
20+
```bash
21+
kiro-cli --version
22+
```
23+
24+
## Endpoints
25+
26+
### GPU Prediction with Kiro Translation
27+
```
28+
GET /v4/GPU?video_url=https://example.com/video.mp4
29+
```
30+
31+
### NPU Prediction with Kiro Translation
32+
```
33+
GET /v4/NPU?video_url=https://example.com/video.mp4
34+
```
35+
36+
### Raw Prediction (no translation)
37+
```
38+
GET /v4/GPU/predict?video_url=https://example.com/video.mp4
39+
```
40+
41+
## Response Format
42+
43+
Translation endpoints return:
44+
```json
45+
{
46+
"translation": "Translated text from Kiro",
47+
"summary": "Brief summary"
48+
}
49+
```
50+
51+
Raw prediction endpoints return:
52+
```json
53+
{
54+
"prediction": "Raw glosses from model"
55+
}
56+
```
57+
58+
## Requirements
59+
60+
- Kiro CLI must be installed and authenticated
61+
- Server must have access to run `kiro-cli` commands
62+
- Authenticated Kiro session (GitHub/Google/AWS Builder ID/IAM Identity Center)

‎api_core/routers/v4/__init__.py‎

Whitespace-only changes.

‎api_core/routers/v4/remote.py‎

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
from fastapi import APIRouter, Query, HTTPException
2+
from fastapi.responses import JSONResponse
3+
import aiohttp
4+
import logging
5+
from api_core.kiro import process_video_translation
6+
from api_core.schemas import PredictionResponse, TranslationResponse, ErrorResponse
7+
8+
logger = logging.getLogger(__name__)
9+
10+
router = APIRouter()
11+
12+
# Remote prediction endpoints
13+
REMOTE_API_URL = "http://100.71.0.60:8082"
14+
REMOTE_2_API_URL = "http://100.102.136.67:8082"
15+
16+
@router.get("/GPU/predict", response_model=PredictionResponse, responses={500: {"model": ErrorResponse}})
17+
async def remote_predict(video_url: str = Query(..., description="URL of the video to predict")):
18+
try:
19+
async with aiohttp.ClientSession() as session:
20+
async with session.get(f"{REMOTE_API_URL}/predict", params={"video_url": video_url}) as response:
21+
if response.status == 200:
22+
data = await response.json()
23+
return PredictionResponse(**data)
24+
else:
25+
error_text = await response.text()
26+
logger.error(f"Remote API error: {response.status} - {error_text}")
27+
raise HTTPException(status_code=response.status, detail=f"Remote API error: {response.status}")
28+
except HTTPException:
29+
raise
30+
except Exception as e:
31+
logger.error(f"Remote prediction error: {e}")
32+
raise HTTPException(status_code=500, detail=str(e))
33+
34+
@router.get("/GPU", response_model=TranslationResponse, responses={400: {"model": ErrorResponse}, 500: {"model": ErrorResponse}})
35+
async def remote_predict_kiro(video_url: str = Query(..., description="URL of the video to predict")):
36+
try:
37+
# 1. Get prediction from remote API
38+
prediction_text = ""
39+
async with aiohttp.ClientSession() as session:
40+
async with session.get(f"{REMOTE_API_URL}/predict", params={"video_url": video_url}) as response:
41+
if response.status == 200:
42+
data = await response.json()
43+
prediction_text = data.get("prediction", "")
44+
else:
45+
error_text = await response.text()
46+
logger.error(f"Remote API error: {response.status} - {error_text}")
47+
raise HTTPException(status_code=response.status, detail=f"Remote API error: {response.status}")
48+
49+
if not prediction_text:
50+
raise HTTPException(status_code=400, detail="No prediction received from remote API")
51+
52+
logger.info(f"Remote model made prediction: {prediction_text}. This will be sent to Kiro API for translation.")
53+
54+
result = await process_video_translation(prediction_text)
55+
56+
if result:
57+
return TranslationResponse(**result)
58+
else:
59+
raise HTTPException(status_code=500, detail="Failed to get translation from Kiro API")
60+
61+
except HTTPException:
62+
raise
63+
except Exception as e:
64+
logger.error(f"Remote prediction error: {e}")
65+
raise HTTPException(status_code=500, detail=str(e))
66+
67+
@router.get("/NPU", response_model=TranslationResponse, responses={400: {"model": ErrorResponse}, 500: {"model": ErrorResponse}})
68+
async def remote_predict_kiro2(video_url: str = Query(..., description="URL of the video to predict")):
69+
try:
70+
# 1. Get prediction from remote API
71+
prediction_text = ""
72+
async with aiohttp.ClientSession() as session:
73+
async with session.get(f"{REMOTE_2_API_URL}/predict", params={"video_url": video_url}) as response:
74+
if response.status == 200:
75+
data = await response.json()
76+
prediction_text = data.get("prediction", "")
77+
else:
78+
error_text = await response.text()
79+
logger.error(f"Remote API error: {response.status} - {error_text}")
80+
raise HTTPException(status_code=response.status, detail=f"Remote API error: {response.status}")
81+
82+
if not prediction_text:
83+
raise HTTPException(status_code=400, detail="No prediction received from remote API")
84+
85+
logger.info(f"Remote model made prediction: {prediction_text}. This will be sent to Kiro API for translation.")
86+
87+
result = await process_video_translation(prediction_text)
88+
89+
if result:
90+
return TranslationResponse(**result)
91+
else:
92+
raise HTTPException(status_code=500, detail="Failed to get translation from Kiro API")
93+
94+
except HTTPException:
95+
raise
96+
except Exception as e:
97+
logger.error(f"Remote prediction error: {e}")
98+
raise HTTPException(status_code=500, detail=str(e))

0 commit comments

Comments
 (0)