deploy Nous v16 update
This commit is contained in:
@@ -1043,6 +1043,82 @@ def ai_generate_message(prospect: dict[str, Any], offer: str) -> dict[str, Any]:
|
||||
return {"subject": str(parsed["subject"])[:200], "message": str(parsed["message"])[:8000], "rationale": str(parsed.get("rationale") or "")[:2000]}
|
||||
|
||||
|
||||
def _parse_ai_object(content: Any) -> dict[str, Any]:
|
||||
"""Parse a JSON object from models that occasionally add markdown fences."""
|
||||
text = str(content or "").strip()
|
||||
if text.startswith("```"):
|
||||
text = re.sub(r"^```(?:json)?\s*|\s*```$", "", text, flags=re.I | re.S).strip()
|
||||
try:
|
||||
value = json.loads(text)
|
||||
except (TypeError, ValueError, json.JSONDecodeError):
|
||||
start, end = text.find("{"), text.rfind("}")
|
||||
if start < 0 or end <= start:
|
||||
raise RuntimeError("ai_invalid_json_response")
|
||||
try:
|
||||
value = json.loads(text[start:end + 1])
|
||||
except (TypeError, ValueError, json.JSONDecodeError) as exc:
|
||||
raise RuntimeError("ai_invalid_json_response") from exc
|
||||
if not isinstance(value, dict):
|
||||
raise RuntimeError("ai_invalid_json_response")
|
||||
return value
|
||||
|
||||
|
||||
def ai_assess_prospect(prospect: dict[str, Any]) -> dict[str, Any]:
|
||||
"""Assess whether a prospect is worth human review/contact."""
|
||||
runtime = ai_runtime_settings()
|
||||
if runtime["provider"] == "nous" and not runtime["api_key"]:
|
||||
raise RuntimeError("nous_api_key_required_for_standalone_app")
|
||||
if not runtime["base_url"] or not runtime["model"]:
|
||||
raise RuntimeError("ai_not_configured_set_AI_BASE_URL_and_AI_MODEL")
|
||||
evidence = {
|
||||
"business": prospect.get("name"), "category": prospect.get("category"),
|
||||
"address": prospect.get("address"), "phone": prospect.get("phone"),
|
||||
"website": prospect.get("website"), "website_status": prospect.get("website_status"),
|
||||
"website_http_status": prospect.get("website_http_status"),
|
||||
"website_title": prospect.get("website_title"), "technology": prospect.get("website_technology"),
|
||||
"emails": prospect.get("emails"), "rating": prospect.get("rating"),
|
||||
"review_count": prospect.get("review_count"), "opportunity_score": prospect.get("opportunity_score"),
|
||||
"score_reasons": _json_list(prospect.get("score_reasons")),
|
||||
}
|
||||
system = (
|
||||
"You qualify prospects for a South African web hosting and web design business. "
|
||||
"Assess only the supplied public evidence. A high probability means the business appears active "
|
||||
"and has a plausible hosting, domain, website repair, or redesign need. Do not invent facts, "
|
||||
"intent, budget, decision-maker names, or contact details. Return strict JSON with exactly these "
|
||||
"keys: probability (integer 0-100), recommendation (one of contact, review, do_not_contact), "
|
||||
"reasoning (one concise paragraph)."
|
||||
)
|
||||
prompt = json.dumps({"prospect": evidence}, ensure_ascii=False)
|
||||
headers = {"Authorization": "Bearer " + runtime["api_key"]} if runtime["api_key"] else {}
|
||||
request_payload: dict[str, Any] = {
|
||||
"model": runtime["model"], "temperature": 0.1, "max_tokens": 450,
|
||||
"messages": [{"role": "system", "content": system}, {"role": "user", "content": prompt}],
|
||||
}
|
||||
if runtime["provider"] != "nous":
|
||||
request_payload["response_format"] = {"type": "json_object"}
|
||||
result = http_json("POST", runtime["base_url"] + "/chat/completions", request_payload, headers=headers, timeout=90, error_prefix="ai")
|
||||
content = (((result.get("choices") or [{}])[0].get("message") or {}).get("content") or "") if isinstance(result, dict) else ""
|
||||
parsed = _parse_ai_object(content)
|
||||
try:
|
||||
probability = max(0, min(100, int(round(float(parsed.get("probability", 0))))))
|
||||
except (TypeError, ValueError):
|
||||
raise RuntimeError("ai_invalid_probability")
|
||||
recommendation = str(parsed.get("recommendation") or "").strip().lower().replace(" ", "_")
|
||||
aliases = {
|
||||
"yes": "contact", "contact_now": "contact", "high": "contact",
|
||||
"maybe": "review", "needs_review": "review", "medium": "review",
|
||||
"no": "do_not_contact", "do_not_contact_now": "do_not_contact", "low": "do_not_contact",
|
||||
}
|
||||
recommendation = aliases.get(recommendation, recommendation)
|
||||
if recommendation not in {"contact", "review", "do_not_contact"}:
|
||||
recommendation = "contact" if probability >= 70 else "review" if probability >= 45 else "do_not_contact"
|
||||
return {
|
||||
"ai_probability": probability,
|
||||
"ai_recommendation": recommendation,
|
||||
"ai_reasoning": str(parsed.get("reasoning") or "").strip()[:3000],
|
||||
}
|
||||
|
||||
|
||||
class Handler(BaseHTTPRequestHandler):
|
||||
server_version = "PersonalProspectOS/2.0"
|
||||
|
||||
@@ -1207,6 +1283,29 @@ class Handler(BaseHTTPRequestHandler):
|
||||
return self.send_json(502, {"error": str(exc)})
|
||||
except Exception as exc:
|
||||
return self.send_json(502, {"error": "ai_test_failed:" + str(exc)[:200]})
|
||||
assess_match = re.fullmatch(r"/api/prospects/(\d+)/assess", urlparse(self.path).path)
|
||||
if assess_match:
|
||||
conn = None
|
||||
try:
|
||||
conn = db(); prospect_id = int(assess_match.group(1))
|
||||
row = conn.execute("SELECT * FROM prospects WHERE id=?", (prospect_id,)).fetchone()
|
||||
if not row:
|
||||
return self.send_json(404, {"error": "not_found"})
|
||||
assessment = ai_assess_prospect(dict(row))
|
||||
stamp = now()
|
||||
conn.execute(
|
||||
"UPDATE prospects SET ai_probability=?,ai_recommendation=?,ai_reasoning=?,ai_checked_at=? WHERE id=?",
|
||||
(assessment["ai_probability"], assessment["ai_recommendation"], assessment["ai_reasoning"], stamp, prospect_id),
|
||||
)
|
||||
conn.commit()
|
||||
return self.send_json(200, {**assessment, "ai_checked_at": stamp, "prospect_id": prospect_id})
|
||||
except RuntimeError as exc:
|
||||
return self.send_json(503, {"error": str(exc)})
|
||||
except (ValueError, TypeError, json.JSONDecodeError) as exc:
|
||||
return self.send_json(400, {"error": str(exc)})
|
||||
finally:
|
||||
if conn is not None:
|
||||
conn.close()
|
||||
message_match = re.fullmatch(r"/api/prospects/(\d+)/message", urlparse(self.path).path)
|
||||
if message_match:
|
||||
conn = None
|
||||
|
||||
Reference in New Issue
Block a user