HR-ATS-Portal/backend/inbox_classifier/execute_agent.py

57 lines
2.3 KiB
Python

"""Intake-gate entrypoint — one Responses call per email.
Pure module: no FastAPI imports and no HTTPException.
Called from inbox.views.Email; no HTTP surface of its own.
Returns (verdict, error_code) and never raises, mirroring
inbox/plugins.extract_resume_text's (text, error) shape. A provider outage must be a
policy decision at the call site (INBOX_TRIAGE_FAIL_OPEN in plugins.should_ingest), not
a 500 on /email/fetch.
"""
from __future__ import annotations
import logging
from app.core.errors import ATSError, classify_error
from inbox_classifier.agent_setup import get_classifier
from inbox_classifier.decorators import email_signals
from inbox_classifier.plugins import TRIAGE_MAX_BODY_CHARS, TRIAGE_MAX_SUBJECT_CHARS
logger=logging.getLogger("inbox.triage")
# No subject and no body: there is nothing to judge, so this is unclassifiable rather
# than a "no". It routes through the fail policy, which under the default fail-open
# means the mail is ingested — a signal-free message is never silently dropped.
EMPTY_MESSAGE="empty_message"
async def classify_email(email_data) -> tuple:
"""Judge one email from its subject and body. Never raises.
(verdict, "") on success; (None, error_code) when the model could not be consulted
or returned something unusable.
"""
subject,body=email_signals(email_data,TRIAGE_MAX_SUBJECT_CHARS,TRIAGE_MAX_BODY_CHARS)
if not subject and not body:
return None,EMPTY_MESSAGE
try:
# get_classifier() is inside the try on purpose: a missing OPENAI_API_KEY raises
# RuntimeError from llm_setup.get_client(), and a stale OPENAI_MODEL raises
# pydantic ValidationError from Settings. Both belong on the fail policy, not on
# a 500 for the whole fetch round.
classifier=get_classifier()
verdict=await classifier.classify(subject,body)
return verdict,""
except ATSError as e:
logger.warning("triage failed: code=%s",e.error_code)
return None,e.error_code
except Exception as e:
# classify_error never returns provider text. Log the exception TYPE and the code
# only — never the message, which can carry prompt or body content.
code,_=classify_error(e)
logger.warning("triage failed: code=%s exc=%s",code,type(e).__name__)
return None,code