Initial commit — Mek-Tech AI Consultancy Framework v2.0
This commit is contained in:
@@ -0,0 +1,248 @@
|
||||
"""Flask API server for Lead Generation & Tech Scoping Engine."""
|
||||
from __future__ import annotations
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import httpx
|
||||
from flask import Flask, request, jsonify
|
||||
|
||||
from models import CompanyTechProfile, LeadGenRequest, LeadGenResponse
|
||||
from engine import LeadScopingEngine
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Logging
|
||||
# ---------------------------------------------------------------------------
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
|
||||
stream=sys.stdout,
|
||||
)
|
||||
logger = logging.getLogger("leadgen")
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Config file
|
||||
# ---------------------------------------------------------------------------
|
||||
CONFIG_PATH = Path(os.getenv("LEADGEN_CONFIG", "/app/data/leadgen_config.json"))
|
||||
CONFIG_PATH.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
DEFAULT_CONFIG = {
|
||||
"api_key": os.getenv("DEEPSEEK_API_KEY", ""),
|
||||
"base_url": "https://openrouter.ai/api/v1",
|
||||
"model": "qwen/qwen3-coder:free",
|
||||
"fallback_models": [
|
||||
"meta-llama/llama-3.3-70b-instruct:free",
|
||||
"google/gemma-4-26b-a4b-it:free",
|
||||
"nvidia/nemotron-3-super-120b-a12b:free",
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def load_config() -> dict:
|
||||
if CONFIG_PATH.exists():
|
||||
try:
|
||||
return json.loads(CONFIG_PATH.read_text())
|
||||
except Exception:
|
||||
pass
|
||||
cfg = dict(DEFAULT_CONFIG)
|
||||
save_config(cfg)
|
||||
return cfg
|
||||
|
||||
|
||||
def save_config(cfg: dict) -> None:
|
||||
CONFIG_PATH.write_text(json.dumps(cfg, indent=2))
|
||||
|
||||
|
||||
config = load_config()
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# App
|
||||
# ---------------------------------------------------------------------------
|
||||
app = Flask(__name__)
|
||||
|
||||
|
||||
def get_engine() -> LeadScopingEngine:
|
||||
cfg = load_config()
|
||||
return LeadScopingEngine(
|
||||
api_key=cfg.get("api_key"),
|
||||
api_base=cfg.get("base_url"),
|
||||
)
|
||||
|
||||
|
||||
@app.route("/python-leadgen/", methods=["GET"])
|
||||
def index():
|
||||
return jsonify({
|
||||
"service": "Mek-Tech Lead Generation & Tech Scoping Engine",
|
||||
"version": "2.0",
|
||||
"endpoints": {
|
||||
"GET /python-leadgen/config": "Get current config (model, base_url, key masked)",
|
||||
"POST /python-leadgen/config": "Update config",
|
||||
"GET /python-leadgen/models": "List available free models from configured API",
|
||||
"POST /python-leadgen/analyze": "Analyze a single URL",
|
||||
"POST /python-leadgen/batch": "Analyze multiple URLs",
|
||||
"GET /python-leadgen/health": "Health check",
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
@app.route("/python-leadgen/health", methods=["GET"])
|
||||
def health():
|
||||
cfg = load_config()
|
||||
return jsonify({
|
||||
"status": "healthy",
|
||||
"api_configured": bool(cfg.get("api_key")),
|
||||
"model": cfg.get("model"),
|
||||
"base_url": cfg.get("base_url"),
|
||||
})
|
||||
|
||||
|
||||
@app.route("/python-leadgen/config", methods=["GET"])
|
||||
def get_config():
|
||||
cfg = load_config()
|
||||
key = cfg.get("api_key", "")
|
||||
return jsonify({
|
||||
"base_url": cfg.get("base_url"),
|
||||
"model": cfg.get("model"),
|
||||
"fallback_models": cfg.get("fallback_models", []),
|
||||
"api_key_set": bool(key),
|
||||
"api_key_preview": (key[:12] + "..." + key[-4:]) if len(key) > 16 else ("***" if key else ""),
|
||||
})
|
||||
|
||||
|
||||
@app.route("/python-leadgen/config", methods=["POST"])
|
||||
def set_config():
|
||||
try:
|
||||
body = request.get_json(silent=True) or {}
|
||||
except Exception:
|
||||
return jsonify({"success": False, "error": "Invalid JSON"}), 400
|
||||
|
||||
cfg = load_config()
|
||||
if "api_key" in body and body["api_key"]:
|
||||
cfg["api_key"] = body["api_key"]
|
||||
if "base_url" in body:
|
||||
cfg["base_url"] = body["base_url"]
|
||||
if "model" in body:
|
||||
cfg["model"] = body["model"]
|
||||
if "fallback_models" in body:
|
||||
cfg["fallback_models"] = body["fallback_models"]
|
||||
|
||||
save_config(cfg)
|
||||
logger.info("Config updated: model=%s base_url=%s", cfg.get("model"), cfg.get("base_url"))
|
||||
return jsonify({"success": True, "model": cfg["model"]})
|
||||
|
||||
|
||||
@app.route("/python-leadgen/models", methods=["GET"])
|
||||
async def list_models():
|
||||
"""Fetch available free models from the configured API endpoint."""
|
||||
cfg = load_config()
|
||||
base = cfg.get("base_url", "https://openrouter.ai/api/v1")
|
||||
key = cfg.get("api_key", "")
|
||||
|
||||
models_url = base.rstrip("/") + "/models"
|
||||
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=15) as client:
|
||||
resp = await client.get(
|
||||
models_url,
|
||||
headers={"Authorization": f"Bearer {key}"} if key else {},
|
||||
)
|
||||
if resp.status_code != 200:
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": f"API returned {resp.status_code}",
|
||||
"models": _fallback_model_list(),
|
||||
})
|
||||
|
||||
data = resp.json()
|
||||
all_models = data.get("data", [])
|
||||
|
||||
free_models = [
|
||||
{
|
||||
"id": m["id"],
|
||||
"name": m.get("name", m["id"]),
|
||||
"context_length": m.get("context_length", 0),
|
||||
"pricing": str(m.get("pricing", {})),
|
||||
}
|
||||
for m in all_models
|
||||
if ":free" in m.get("id", "")
|
||||
]
|
||||
free_models.sort(key=lambda m: -m["context_length"])
|
||||
|
||||
return jsonify({"success": True, "total": len(free_models), "models": free_models})
|
||||
|
||||
except Exception as e:
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
"models": _fallback_model_list(),
|
||||
})
|
||||
|
||||
|
||||
def _fallback_model_list():
|
||||
return [
|
||||
{"id": "qwen/qwen3-coder:free", "name": "Qwen 3 Coder (Free)", "context_length": 1048576},
|
||||
{"id": "google/gemma-4-31b-it:free", "name": "Gemma 4 31B (Free)", "context_length": 262144},
|
||||
{"id": "google/gemma-4-26b-a4b-it:free", "name": "Gemma 4 26B (Free)", "context_length": 262144},
|
||||
{"id": "meta-llama/llama-3.3-70b-instruct:free", "name": "Llama 3.3 70B (Free)", "context_length": 131072},
|
||||
{"id": "nvidia/nemotron-3-super-120b-a12b:free", "name": "Nemotron Super 120B (Free)", "context_length": 1000000},
|
||||
]
|
||||
|
||||
|
||||
@app.route("/python-leadgen/analyze", methods=["POST"])
|
||||
async def analyze():
|
||||
try:
|
||||
body = request.get_json(silent=True) or {}
|
||||
except Exception:
|
||||
return jsonify({"success": False, "error": "Invalid JSON body"}), 400
|
||||
|
||||
url = body.get("url", "").strip()
|
||||
if not url:
|
||||
return jsonify({"success": False, "error": "Missing required field: url"}), 400
|
||||
|
||||
engine = get_engine()
|
||||
try:
|
||||
result: LeadGenResponse = await engine.analyze_url(url)
|
||||
except Exception as exc:
|
||||
logger.exception("Unexpected error analyzing %s", url)
|
||||
return jsonify({"success": False, "error": str(exc)}), 500
|
||||
|
||||
if result.success and result.profile:
|
||||
return jsonify(result.model_dump())
|
||||
else:
|
||||
return jsonify(result.model_dump()), 422
|
||||
|
||||
|
||||
@app.route("/python-leadgen/batch", methods=["POST"])
|
||||
async def batch():
|
||||
try:
|
||||
body = request.get_json(silent=True) or {}
|
||||
except Exception:
|
||||
return jsonify({"success": False, "error": "Invalid JSON body"}), 400
|
||||
|
||||
urls = body.get("urls", [])
|
||||
if not urls or not isinstance(urls, list):
|
||||
return jsonify({"success": False, "error": "Missing required field: urls (list)"}), 400
|
||||
|
||||
if len(urls) > 10:
|
||||
return jsonify({"success": False, "error": "Maximum 10 URLs per batch"}), 400
|
||||
|
||||
engine = get_engine()
|
||||
try:
|
||||
results = await engine.batch_analyze(urls)
|
||||
except Exception as exc:
|
||||
logger.exception("Batch analysis failed")
|
||||
return jsonify({"success": False, "error": str(exc)}), 500
|
||||
|
||||
return jsonify({
|
||||
"success": True,
|
||||
"total": len(urls),
|
||||
"results": [r.model_dump() for r in results],
|
||||
})
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
port = int(os.getenv("LEADGEN_PORT", "3003"))
|
||||
debug = os.getenv("LEADGEN_DEBUG", "0") == "1"
|
||||
app.run(host="0.0.0.0", port=port, debug=debug)
|
||||
Reference in New Issue
Block a user