Compare commits
3 commits
178a088902
...
462a6dfa51
| Author | SHA1 | Date | |
|---|---|---|---|
| 462a6dfa51 | |||
| 216e34ec13 | |||
| f7690e1d81 |
4 changed files with 88 additions and 59 deletions
BIN
BotModels.png
BIN
BotModels.png
Binary file not shown.
|
Before Width: | Height: | Size: 58 KiB |
80
PROMPT.md
Normal file
80
PROMPT.md
Normal file
|
|
@ -0,0 +1,80 @@
|
||||||
|
# General Bots Models (Python) - Project Guidelines
|
||||||
|
|
||||||
|
**Version:** 1.0.0
|
||||||
|
**Role:** AI Inference Service for BotServer
|
||||||
|
**Primary Directive:** Provide access to the latest open-source AI models (Python ecosystem) that are impractical to implement in Rust.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🐍 PHILOSOPHY & SCOPE
|
||||||
|
|
||||||
|
### Why Python?
|
||||||
|
While `botserver` (Rust) handles the heavy lifting, networking, and business logic, `botmodels` exists solely to leverage the extensive **Python AI/ML ecosystem**.
|
||||||
|
|
||||||
|
- **Rust vs. Python Rule**:
|
||||||
|
- If logic is deterministic, systems-level, or performance-critical logic: **Do it in Rust (botserver)**.
|
||||||
|
- If logic requires cutting-edge ML models, rapid experimentation with HuggingFace, or specific Python-only libraries: **Do it here**.
|
||||||
|
|
||||||
|
### Architecture
|
||||||
|
- **Inference Only**: This service should NOT hold business state. It accepts inputs, runs inference, and returns predictions.
|
||||||
|
- **Stateless**: Treated as a sidecar to `botserver`.
|
||||||
|
- **API First**: Exposes strict HTTP/REST endpoints (or gRPC) consumed by `botserver`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🛠 TECHNOLOGY STACK
|
||||||
|
|
||||||
|
- **Runtime**: Python 3.10+
|
||||||
|
- **Web Framework**: FastAPI (preferred over Flask for async/performance) or Flask (legacy support).
|
||||||
|
- **ML Frameworks**: PyTorch, HuggingFace Transformers, raw ONNX (if speed needed).
|
||||||
|
- **Quality**: `ruff` (linting), `black` (formatting), `mypy` (typing).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⚡️ IMPERATIVES
|
||||||
|
|
||||||
|
### 1. Modern Model Usage
|
||||||
|
- **Deprecate Legacy**: Move away from outdated libs (e.g., old `allennlp` if superseded) in favor of **HuggingFace Transformers** and **Diffusers**.
|
||||||
|
- **Quantization**: Always consider quantized models (bitsandbytes, GGUF) to reduce VRAM usage given the "consumer/prosumer" target of General Bots.
|
||||||
|
|
||||||
|
### 2. Performance & Loading
|
||||||
|
- **Lazy Loading**: Do NOT load 10GB models at module import time. Load on startup lifecycle or first request with locking.
|
||||||
|
- **GPU Handling**: robustly detect CUDA/MPS (Mac) and fallback to CPU gracefully.
|
||||||
|
|
||||||
|
### 3. Code Quality
|
||||||
|
- **Type Hints**: All functions MUST have type hints.
|
||||||
|
- **Error Handling**: No bare check `except:`. Catch precise exceptions and return structured JSON errors to `botserver`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📝 DEVELOPMENT WORKFLOW
|
||||||
|
|
||||||
|
1. **Environment**: Always use a `venv`.
|
||||||
|
```bash
|
||||||
|
python3 -m venv venv
|
||||||
|
source venv/bin/activate
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
2. **Running**:
|
||||||
|
```bash
|
||||||
|
python app.py
|
||||||
|
# OR if migrated to FastAPI
|
||||||
|
uvicorn src.main:app --port 8089 --reload
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔗 INTEGRATION WITH BOTSERVER
|
||||||
|
|
||||||
|
- **Port**: Defaults to `8089` (internal).
|
||||||
|
- **Security**: Must implement the shared secret handshake (HMAC/API Key) validated against `botserver`.
|
||||||
|
- **Keep-Alive**: `botserver` manages the lifecycle of this process.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ✅ CONTINUATION PROMPT
|
||||||
|
|
||||||
|
When working in `botmodels`:
|
||||||
|
1. **Prioritize Ecosystem**: If a new SOTA model drops (e.g., Llama 3, Mistral), enable it here immediately.
|
||||||
|
2. **Optimize**: Ensure dependencies are minimized. Don't install `tensorflow` if `torch` suffices.
|
||||||
|
3. **Strict Typing**: Ensure all input/outputs match the `botserver` expectations perfectly.
|
||||||
|
|
@ -27,11 +27,6 @@ logger = get_logger("scoring")
|
||||||
router = APIRouter(prefix="/scoring", tags=["Lead Scoring"])
|
router = APIRouter(prefix="/scoring", tags=["Lead Scoring"])
|
||||||
|
|
||||||
|
|
||||||
# ============================================================================
|
|
||||||
# Request/Response Models
|
|
||||||
# ============================================================================
|
|
||||||
|
|
||||||
|
|
||||||
class LeadProfile(BaseModel):
|
class LeadProfile(BaseModel):
|
||||||
"""Lead profile information for scoring"""
|
"""Lead profile information for scoring"""
|
||||||
|
|
||||||
|
|
@ -118,42 +113,31 @@ class ModelInfoResponse(BaseModel):
|
||||||
accuracy_metrics: Dict[str, float]
|
accuracy_metrics: Dict[str, float]
|
||||||
|
|
||||||
|
|
||||||
# ============================================================================
|
|
||||||
# Scoring Configuration
|
|
||||||
# ============================================================================
|
|
||||||
|
|
||||||
|
|
||||||
class ScoringWeights:
|
class ScoringWeights:
|
||||||
"""Default weights for scoring components"""
|
"""Default weights for scoring components"""
|
||||||
|
|
||||||
# Demographic factors
|
|
||||||
COMPANY_SIZE_WEIGHT = 10.0
|
COMPANY_SIZE_WEIGHT = 10.0
|
||||||
INDUSTRY_MATCH_WEIGHT = 15.0
|
INDUSTRY_MATCH_WEIGHT = 15.0
|
||||||
LOCATION_MATCH_WEIGHT = 5.0
|
LOCATION_MATCH_WEIGHT = 5.0
|
||||||
JOB_TITLE_WEIGHT = 15.0
|
JOB_TITLE_WEIGHT = 15.0
|
||||||
|
|
||||||
# Behavioral factors
|
|
||||||
EMAIL_OPENS_WEIGHT = 5.0
|
EMAIL_OPENS_WEIGHT = 5.0
|
||||||
EMAIL_CLICKS_WEIGHT = 10.0
|
EMAIL_CLICKS_WEIGHT = 10.0
|
||||||
PAGE_VISITS_WEIGHT = 5.0
|
PAGE_VISITS_WEIGHT = 5.0
|
||||||
FORM_SUBMISSIONS_WEIGHT = 15.0
|
FORM_SUBMISSIONS_WEIGHT = 15.0
|
||||||
CONTENT_DOWNLOADS_WEIGHT = 10.0
|
CONTENT_DOWNLOADS_WEIGHT = 10.0
|
||||||
|
|
||||||
# Engagement factors
|
|
||||||
RESPONSE_TIME_WEIGHT = 10.0
|
RESPONSE_TIME_WEIGHT = 10.0
|
||||||
INTERACTION_FREQUENCY_WEIGHT = 10.0
|
INTERACTION_FREQUENCY_WEIGHT = 10.0
|
||||||
SESSION_DURATION_WEIGHT = 5.0
|
SESSION_DURATION_WEIGHT = 5.0
|
||||||
|
|
||||||
# Intent signals
|
|
||||||
PRICING_PAGE_WEIGHT = 20.0
|
PRICING_PAGE_WEIGHT = 20.0
|
||||||
DEMO_REQUEST_WEIGHT = 25.0
|
DEMO_REQUEST_WEIGHT = 25.0
|
||||||
TRIAL_SIGNUP_WEIGHT = 30.0
|
TRIAL_SIGNUP_WEIGHT = 30.0
|
||||||
|
|
||||||
# Penalties
|
|
||||||
INACTIVITY_PENALTY = -15.0
|
INACTIVITY_PENALTY = -15.0
|
||||||
|
|
||||||
|
|
||||||
# Target industries and titles for scoring
|
|
||||||
TARGET_INDUSTRIES = {
|
TARGET_INDUSTRIES = {
|
||||||
"technology": 1.0,
|
"technology": 1.0,
|
||||||
"software": 1.0,
|
"software": 1.0,
|
||||||
|
|
@ -202,17 +186,11 @@ COMPANY_SIZE_SCORES = {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
# ============================================================================
|
|
||||||
# Scoring Logic
|
|
||||||
# ============================================================================
|
|
||||||
|
|
||||||
|
|
||||||
def calculate_demographic_score(profile: LeadProfile) -> float:
|
def calculate_demographic_score(profile: LeadProfile) -> float:
|
||||||
"""Calculate demographic component of lead score"""
|
"""Calculate demographic component of lead score"""
|
||||||
score = 0.0
|
score = 0.0
|
||||||
weights = ScoringWeights()
|
weights = ScoringWeights()
|
||||||
|
|
||||||
# Company size scoring
|
|
||||||
if profile.company_size:
|
if profile.company_size:
|
||||||
size_lower = profile.company_size.lower()
|
size_lower = profile.company_size.lower()
|
||||||
for key, value in COMPANY_SIZE_SCORES.items():
|
for key, value in COMPANY_SIZE_SCORES.items():
|
||||||
|
|
@ -222,7 +200,6 @@ def calculate_demographic_score(profile: LeadProfile) -> float:
|
||||||
else:
|
else:
|
||||||
score += 0.3 * weights.COMPANY_SIZE_WEIGHT
|
score += 0.3 * weights.COMPANY_SIZE_WEIGHT
|
||||||
|
|
||||||
# Industry scoring
|
|
||||||
if profile.industry:
|
if profile.industry:
|
||||||
industry_lower = profile.industry.lower()
|
industry_lower = profile.industry.lower()
|
||||||
for key, value in TARGET_INDUSTRIES.items():
|
for key, value in TARGET_INDUSTRIES.items():
|
||||||
|
|
@ -232,16 +209,14 @@ def calculate_demographic_score(profile: LeadProfile) -> float:
|
||||||
else:
|
else:
|
||||||
score += 0.4 * weights.INDUSTRY_MATCH_WEIGHT
|
score += 0.4 * weights.INDUSTRY_MATCH_WEIGHT
|
||||||
|
|
||||||
# Job title scoring
|
|
||||||
if profile.job_title:
|
if profile.job_title:
|
||||||
title_lower = profile.job_title.lower()
|
title_lower = profile.job_title.lower()
|
||||||
title_score = 0.3 # default
|
title_score = 0.3
|
||||||
for key, value in TITLE_SCORES.items():
|
for key, value in TITLE_SCORES.items():
|
||||||
if key in title_lower:
|
if key in title_lower:
|
||||||
title_score = max(title_score, value)
|
title_score = max(title_score, value)
|
||||||
score += title_score * weights.JOB_TITLE_WEIGHT
|
score += title_score * weights.JOB_TITLE_WEIGHT
|
||||||
|
|
||||||
# Location scoring (simplified)
|
|
||||||
if profile.location:
|
if profile.location:
|
||||||
score += 0.5 * weights.LOCATION_MATCH_WEIGHT
|
score += 0.5 * weights.LOCATION_MATCH_WEIGHT
|
||||||
|
|
||||||
|
|
@ -253,22 +228,18 @@ def calculate_behavioral_score(behavior: LeadBehavior) -> float:
|
||||||
score = 0.0
|
score = 0.0
|
||||||
weights = ScoringWeights()
|
weights = ScoringWeights()
|
||||||
|
|
||||||
# Email engagement
|
|
||||||
email_open_score = min(behavior.email_opens / 10.0, 1.0)
|
email_open_score = min(behavior.email_opens / 10.0, 1.0)
|
||||||
score += email_open_score * weights.EMAIL_OPENS_WEIGHT
|
score += email_open_score * weights.EMAIL_OPENS_WEIGHT
|
||||||
|
|
||||||
email_click_score = min(behavior.email_clicks / 5.0, 1.0)
|
email_click_score = min(behavior.email_clicks / 5.0, 1.0)
|
||||||
score += email_click_score * weights.EMAIL_CLICKS_WEIGHT
|
score += email_click_score * weights.EMAIL_CLICKS_WEIGHT
|
||||||
|
|
||||||
# Page visits
|
|
||||||
visit_score = min(behavior.page_visits / 20.0, 1.0)
|
visit_score = min(behavior.page_visits / 20.0, 1.0)
|
||||||
score += visit_score * weights.PAGE_VISITS_WEIGHT
|
score += visit_score * weights.PAGE_VISITS_WEIGHT
|
||||||
|
|
||||||
# Form submissions
|
|
||||||
form_score = min(behavior.form_submissions / 3.0, 1.0)
|
form_score = min(behavior.form_submissions / 3.0, 1.0)
|
||||||
score += form_score * weights.FORM_SUBMISSIONS_WEIGHT
|
score += form_score * weights.FORM_SUBMISSIONS_WEIGHT
|
||||||
|
|
||||||
# Content downloads
|
|
||||||
download_score = min(behavior.content_downloads / 5.0, 1.0)
|
download_score = min(behavior.content_downloads / 5.0, 1.0)
|
||||||
score += download_score * weights.CONTENT_DOWNLOADS_WEIGHT
|
score += download_score * weights.CONTENT_DOWNLOADS_WEIGHT
|
||||||
|
|
||||||
|
|
@ -280,15 +251,12 @@ def calculate_engagement_score(behavior: LeadBehavior) -> float:
|
||||||
score = 0.0
|
score = 0.0
|
||||||
weights = ScoringWeights()
|
weights = ScoringWeights()
|
||||||
|
|
||||||
# Interaction frequency
|
|
||||||
frequency_score = min(behavior.total_sessions / 10.0, 1.0)
|
frequency_score = min(behavior.total_sessions / 10.0, 1.0)
|
||||||
score += frequency_score * weights.INTERACTION_FREQUENCY_WEIGHT
|
score += frequency_score * weights.INTERACTION_FREQUENCY_WEIGHT
|
||||||
|
|
||||||
# Session duration (5 min = max score)
|
|
||||||
duration_score = min(behavior.avg_session_duration / 300.0, 1.0)
|
duration_score = min(behavior.avg_session_duration / 300.0, 1.0)
|
||||||
score += duration_score * weights.SESSION_DURATION_WEIGHT
|
score += duration_score * weights.SESSION_DURATION_WEIGHT
|
||||||
|
|
||||||
# Recency scoring
|
|
||||||
if behavior.days_since_last_activity is not None:
|
if behavior.days_since_last_activity is not None:
|
||||||
days = behavior.days_since_last_activity
|
days = behavior.days_since_last_activity
|
||||||
if days <= 1:
|
if days <= 1:
|
||||||
|
|
@ -313,16 +281,13 @@ def calculate_intent_score(behavior: LeadBehavior) -> float:
|
||||||
score = 0.0
|
score = 0.0
|
||||||
weights = ScoringWeights()
|
weights = ScoringWeights()
|
||||||
|
|
||||||
# Pricing page visits
|
|
||||||
if behavior.pricing_page_visits > 0:
|
if behavior.pricing_page_visits > 0:
|
||||||
pricing_score = min(behavior.pricing_page_visits / 3.0, 1.0)
|
pricing_score = min(behavior.pricing_page_visits / 3.0, 1.0)
|
||||||
score += pricing_score * weights.PRICING_PAGE_WEIGHT
|
score += pricing_score * weights.PRICING_PAGE_WEIGHT
|
||||||
|
|
||||||
# Demo requests
|
|
||||||
if behavior.demo_requests > 0:
|
if behavior.demo_requests > 0:
|
||||||
score += weights.DEMO_REQUEST_WEIGHT
|
score += weights.DEMO_REQUEST_WEIGHT
|
||||||
|
|
||||||
# Trial signups
|
|
||||||
if behavior.trial_signups > 0:
|
if behavior.trial_signups > 0:
|
||||||
score += weights.TRIAL_SIGNUP_WEIGHT
|
score += weights.TRIAL_SIGNUP_WEIGHT
|
||||||
|
|
||||||
|
|
@ -334,7 +299,6 @@ def calculate_penalty_score(behavior: LeadBehavior) -> float:
|
||||||
penalty = 0.0
|
penalty = 0.0
|
||||||
weights = ScoringWeights()
|
weights = ScoringWeights()
|
||||||
|
|
||||||
# Inactivity penalty
|
|
||||||
if behavior.days_since_last_activity is not None:
|
if behavior.days_since_last_activity is not None:
|
||||||
if behavior.days_since_last_activity > 60:
|
if behavior.days_since_last_activity > 60:
|
||||||
penalty += weights.INACTIVITY_PENALTY
|
penalty += weights.INACTIVITY_PENALTY
|
||||||
|
|
@ -365,9 +329,9 @@ def get_qualification_status(
|
||||||
) -> str:
|
) -> str:
|
||||||
"""Determine qualification status"""
|
"""Determine qualification status"""
|
||||||
if has_trial or score >= 90:
|
if has_trial or score >= 90:
|
||||||
return "sql" # Sales Qualified Lead
|
return "sql"
|
||||||
elif has_demo or score >= 70:
|
elif has_demo or score >= 70:
|
||||||
return "mql" # Marketing Qualified Lead
|
return "mql"
|
||||||
else:
|
else:
|
||||||
return "unqualified"
|
return "unqualified"
|
||||||
|
|
||||||
|
|
@ -378,7 +342,6 @@ def generate_recommendations(
|
||||||
"""Generate actionable recommendations for the lead"""
|
"""Generate actionable recommendations for the lead"""
|
||||||
recommendations = []
|
recommendations = []
|
||||||
|
|
||||||
# Score-based recommendations
|
|
||||||
if score >= 80:
|
if score >= 80:
|
||||||
recommendations.append("Hot lead! Prioritize immediate sales outreach.")
|
recommendations.append("Hot lead! Prioritize immediate sales outreach.")
|
||||||
elif score >= 60:
|
elif score >= 60:
|
||||||
|
|
@ -388,7 +351,6 @@ def generate_recommendations(
|
||||||
else:
|
else:
|
||||||
recommendations.append("Low priority - add to nurturing campaign.")
|
recommendations.append("Low priority - add to nurturing campaign.")
|
||||||
|
|
||||||
# Behavior-based recommendations
|
|
||||||
if behavior.pricing_page_visits > 0 and behavior.demo_requests == 0:
|
if behavior.pricing_page_visits > 0 and behavior.demo_requests == 0:
|
||||||
recommendations.append("Visited pricing page - send personalized demo invite.")
|
recommendations.append("Visited pricing page - send personalized demo invite.")
|
||||||
|
|
||||||
|
|
@ -400,14 +362,12 @@ def generate_recommendations(
|
||||||
if behavior.email_opens > 5 and behavior.email_clicks < 2:
|
if behavior.email_opens > 5 and behavior.email_clicks < 2:
|
||||||
recommendations.append("Opens emails but doesn't click - try different CTAs.")
|
recommendations.append("Opens emails but doesn't click - try different CTAs.")
|
||||||
|
|
||||||
# Profile-based recommendations
|
|
||||||
if not profile.company:
|
if not profile.company:
|
||||||
recommendations.append("Missing company info - enrich profile data.")
|
recommendations.append("Missing company info - enrich profile data.")
|
||||||
|
|
||||||
if not profile.job_title:
|
if not profile.job_title:
|
||||||
recommendations.append("Unknown job title - request more information.")
|
recommendations.append("Unknown job title - request more information.")
|
||||||
|
|
||||||
# Engagement recommendations
|
|
||||||
if behavior.days_since_last_activity and behavior.days_since_last_activity > 14:
|
if behavior.days_since_last_activity and behavior.days_since_last_activity > 14:
|
||||||
recommendations.append("Inactive for 2+ weeks - send re-engagement email.")
|
recommendations.append("Inactive for 2+ weeks - send re-engagement email.")
|
||||||
|
|
||||||
|
|
@ -419,14 +379,12 @@ def score_lead(request: ScoreLeadRequest) -> LeadScoreResponse:
|
||||||
profile = request.profile
|
profile = request.profile
|
||||||
behavior = request.behavior or LeadBehavior()
|
behavior = request.behavior or LeadBehavior()
|
||||||
|
|
||||||
# Calculate component scores
|
|
||||||
demographic_score = calculate_demographic_score(profile)
|
demographic_score = calculate_demographic_score(profile)
|
||||||
behavioral_score = calculate_behavioral_score(behavior)
|
behavioral_score = calculate_behavioral_score(behavior)
|
||||||
engagement_score = calculate_engagement_score(behavior)
|
engagement_score = calculate_engagement_score(behavior)
|
||||||
intent_score = calculate_intent_score(behavior)
|
intent_score = calculate_intent_score(behavior)
|
||||||
penalty_score = calculate_penalty_score(behavior)
|
penalty_score = calculate_penalty_score(behavior)
|
||||||
|
|
||||||
# Calculate total score
|
|
||||||
raw_score = (
|
raw_score = (
|
||||||
demographic_score
|
demographic_score
|
||||||
+ behavioral_score
|
+ behavioral_score
|
||||||
|
|
@ -436,7 +394,6 @@ def score_lead(request: ScoreLeadRequest) -> LeadScoreResponse:
|
||||||
)
|
)
|
||||||
total_score = max(0, min(100, raw_score))
|
total_score = max(0, min(100, raw_score))
|
||||||
|
|
||||||
# Determine grade and status
|
|
||||||
grade = get_grade(total_score)
|
grade = get_grade(total_score)
|
||||||
qualification_status = get_qualification_status(
|
qualification_status = get_qualification_status(
|
||||||
total_score,
|
total_score,
|
||||||
|
|
@ -444,12 +401,10 @@ def score_lead(request: ScoreLeadRequest) -> LeadScoreResponse:
|
||||||
has_trial=behavior.trial_signups > 0,
|
has_trial=behavior.trial_signups > 0,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Generate recommendations
|
|
||||||
recommendations = []
|
recommendations = []
|
||||||
if request.include_recommendations:
|
if request.include_recommendations:
|
||||||
recommendations = generate_recommendations(profile, behavior, total_score)
|
recommendations = generate_recommendations(profile, behavior, total_score)
|
||||||
|
|
||||||
# Calculate confidence based on data completeness
|
|
||||||
data_points = sum(
|
data_points = sum(
|
||||||
[
|
[
|
||||||
1 if profile.email else 0,
|
1 if profile.email else 0,
|
||||||
|
|
@ -482,11 +437,6 @@ def score_lead(request: ScoreLeadRequest) -> LeadScoreResponse:
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
# ============================================================================
|
|
||||||
# API Endpoints
|
|
||||||
# ============================================================================
|
|
||||||
|
|
||||||
|
|
||||||
@router.post("/score", response_model=LeadScoreResponse)
|
@router.post("/score", response_model=LeadScoreResponse)
|
||||||
async def calculate_lead_score(
|
async def calculate_lead_score(
|
||||||
request: ScoreLeadRequest,
|
request: ScoreLeadRequest,
|
||||||
|
|
@ -551,7 +501,6 @@ async def batch_score_leads(
|
||||||
|
|
||||||
scores = [score_lead(lead_request) for lead_request in request.leads]
|
scores = [score_lead(lead_request) for lead_request in request.leads]
|
||||||
|
|
||||||
# Calculate statistics
|
|
||||||
total_score = sum(s.total_score for s in scores)
|
total_score = sum(s.total_score for s in scores)
|
||||||
avg_score = total_score / len(scores) if scores else 0
|
avg_score = total_score / len(scores) if scores else 0
|
||||||
|
|
||||||
|
|
|
||||||
10
src/main.py
10
src/main.py
|
|
@ -65,11 +65,11 @@ async def root():
|
||||||
"status": "running",
|
"status": "running",
|
||||||
"docs": "/api/docs",
|
"docs": "/api/docs",
|
||||||
"endpoints": {
|
"endpoints": {
|
||||||
"image": "/api/v1/image",
|
"image": "/api/image",
|
||||||
"video": "/api/v1/video",
|
"video": "/api/video",
|
||||||
"speech": "/api/v1/speech",
|
"speech": "/api/speech",
|
||||||
"vision": "/api/v1/vision",
|
"vision": "/api/vision",
|
||||||
"scoring": "/api/v1/scoring",
|
"scoring": "/api/scoring",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue