Compare commits

...

3 commits

4 changed files with 88 additions and 59 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

80
PROMPT.md Normal file
View 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.

View file

@ -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

View file

@ -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",
}, },
} }
) )