This commit is contained in:
2026-05-05 21:02:12 +03:30
parent 5301071df5
commit 1679825ae2
47 changed files with 1347 additions and 1403 deletions
+81 -14
View File
@@ -18,6 +18,7 @@ from rag.chat import (
build_rag_context,
)
from rag.config import RAGConfig, get_service_config, load_rag_config
from rag.failure_contract import RAGServiceError
from rag.user_data import build_plant_text
logger = logging.getLogger(__name__)
@@ -73,18 +74,47 @@ def _clean_json(raw: str) -> dict[str, Any]:
cleaned = cleaned[4:]
cleaned = cleaned.strip()
if not cleaned:
return {}
raise RAGServiceError(
error_code="empty_response",
message="Pest disease LLM response was empty.",
source="llm",
retriable=True,
details={"service_id": SERVICE_ID},
http_status=502,
)
try:
return json.loads(cleaned)
except (json.JSONDecodeError, ValueError):
parsed = json.loads(cleaned)
except (json.JSONDecodeError, ValueError) as exc:
logger.warning("Invalid JSON returned by pest_disease LLM: %s", cleaned[:500])
return {}
raise RAGServiceError(
error_code="invalid_json",
message="Pest disease LLM response was not valid JSON.",
source="llm",
retriable=True,
details={"service_id": SERVICE_ID},
http_status=502,
) from exc
if not isinstance(parsed, dict):
raise RAGServiceError(
error_code="invalid_schema",
message="Pest disease LLM response root must be a JSON object.",
source="llm",
details={"service_id": SERVICE_ID},
http_status=502,
)
return parsed
def _load_farm_or_error(farm_uuid: str) -> dict[str, Any]:
farm_details = get_farm_details(farm_uuid)
if farm_details is None:
raise ValueError("farm_uuid نامعتبر است یا اطلاعات مزرعه پیدا نشد.")
raise RAGServiceError(
error_code="farm_not_found",
message="farm_uuid نامعتبر است یا اطلاعات مزرعه پیدا نشد.",
source="farm_data",
details={"farm_uuid": farm_uuid},
http_status=404,
)
return farm_details
@@ -213,9 +243,12 @@ def _validate_detection_result(parsed: dict[str, Any]) -> dict[str, Any]:
}
missing = [key for key in required_keys if key not in parsed]
if missing:
raise ValueError(
"Pest disease detection response is missing required fields: "
+ ", ".join(missing)
raise RAGServiceError(
error_code="invalid_schema",
message="Pest disease detection response is missing required fields: " + ", ".join(missing),
source="llm",
details={"missing_fields": missing, "service_id": SERVICE_ID},
http_status=502,
)
return parsed
@@ -232,9 +265,12 @@ def _validate_risk_result(parsed: dict[str, Any]) -> dict[str, Any]:
}
missing = [key for key in required_keys if key not in parsed]
if missing:
raise ValueError(
"Pest disease risk response is missing required fields: "
+ ", ".join(missing)
raise RAGServiceError(
error_code="invalid_schema",
message="Pest disease risk response is missing required fields: " + ", ".join(missing),
source="llm",
details={"missing_fields": missing, "service_id": SERVICE_ID},
http_status=502,
)
return parsed
@@ -301,7 +337,12 @@ def get_pest_disease_detection(
) -> dict[str, Any]:
normalized_images = _normalize_images(images)
if not normalized_images:
raise ValueError("حداقل یک تصویر برای تشخیص لازم است.")
raise RAGServiceError(
error_code="missing_images",
message="حداقل یک تصویر برای تشخیص لازم است.",
source="request",
http_status=400,
)
cfg = load_rag_config()
service, client, model = _build_service_client(cfg)
@@ -338,12 +379,25 @@ def get_pest_disease_detection(
raw = response.choices[0].message.content.strip()
parsed = _clean_json(raw)
_complete_audit_log(audit_log, raw)
except RAGServiceError as exc:
logger.error("Pest disease detection failed for %s: %s", farm_uuid, exc)
_fail_audit_log(audit_log, str(exc))
raise
except Exception as exc:
logger.error("Pest disease detection failed for %s: %s", farm_uuid, exc)
_fail_audit_log(audit_log, str(exc))
raise RuntimeError(f"Pest disease detection failed for farm {farm_uuid}.") from exc
raise RAGServiceError(
error_code="upstream_failure",
message=f"Pest disease detection failed for farm {farm_uuid}.",
source="llm",
retriable=True,
details={"farm_uuid": farm_uuid, "service_id": SERVICE_ID},
http_status=503,
) from exc
parsed = _validate_detection_result(parsed)
parsed["status"] = "success"
parsed["source"] = "llm"
parsed["farm_uuid"] = farm_uuid
parsed["raw_response"] = raw
return parsed
@@ -392,12 +446,25 @@ def get_pest_disease_risk(
raw = response.choices[0].message.content.strip()
parsed = _clean_json(raw)
_complete_audit_log(audit_log, raw)
except RAGServiceError as exc:
logger.error("Pest disease risk prediction failed for %s: %s", farm_uuid, exc)
_fail_audit_log(audit_log, str(exc))
raise
except Exception as exc:
logger.error("Pest disease risk prediction failed for %s: %s", farm_uuid, exc)
_fail_audit_log(audit_log, str(exc))
raise RuntimeError(f"Pest disease risk prediction failed for farm {farm_uuid}.") from exc
raise RAGServiceError(
error_code="upstream_failure",
message=f"Pest disease risk prediction failed for farm {farm_uuid}.",
source="llm",
retriable=True,
details={"farm_uuid": farm_uuid, "service_id": SERVICE_ID},
http_status=503,
) from exc
parsed = _validate_risk_result(parsed)
parsed["status"] = "success"
parsed["source"] = "llm"
parsed["farm_uuid"] = farm_uuid
parsed["raw_response"] = raw
return parsed
+56 -9
View File
@@ -14,6 +14,7 @@ from rag.chat import (
build_rag_context,
)
from rag.config import RAGConfig, get_service_config, load_rag_config
from rag.failure_contract import RAGServiceError
logger = logging.getLogger(__name__)
@@ -39,18 +40,48 @@ def _clean_json(raw: str) -> dict[str, Any]:
cleaned = cleaned[4:]
cleaned = cleaned.strip()
if not cleaned:
return {}
raise RAGServiceError(
error_code="empty_response",
message="Soil anomaly LLM response was empty.",
source="llm",
retriable=True,
details={"service_id": SERVICE_ID},
http_status=502,
)
try:
return json.loads(cleaned)
except (json.JSONDecodeError, ValueError):
parsed = json.loads(cleaned)
except (json.JSONDecodeError, ValueError) as exc:
logger.warning("Invalid JSON returned by soil_anomaly LLM: %s", cleaned[:500])
return {}
raise RAGServiceError(
error_code="invalid_json",
message="Soil anomaly LLM response was not valid JSON.",
source="llm",
retriable=True,
details={"service_id": SERVICE_ID},
http_status=502,
) from exc
if not isinstance(parsed, dict):
raise RAGServiceError(
error_code="invalid_schema",
message="Soil anomaly LLM response root must be a JSON object.",
source="llm",
retriable=False,
details={"service_id": SERVICE_ID},
http_status=502,
)
return parsed
def _load_farm_or_error(farm_uuid: str) -> dict[str, Any]:
farm_details = get_farm_details(farm_uuid)
if farm_details is None:
raise ValueError("farm_uuid نامعتبر است یا اطلاعات مزرعه پیدا نشد.")
raise RAGServiceError(
error_code="farm_not_found",
message="farm_uuid نامعتبر است یا اطلاعات مزرعه پیدا نشد.",
source="farm_data",
details={"farm_uuid": farm_uuid},
http_status=404,
)
return farm_details
@@ -80,9 +111,12 @@ def _validate_anomaly_insight(parsed: dict[str, Any]) -> dict[str, Any]:
}
missing = [key for key in required_keys if key not in parsed]
if missing:
raise ValueError(
"Soil anomaly insight response is missing required fields: "
+ ", ".join(missing)
raise RAGServiceError(
error_code="invalid_schema",
message="Soil anomaly insight response is missing required fields: " + ", ".join(missing),
source="llm",
details={"missing_fields": missing, "service_id": SERVICE_ID},
http_status=502,
)
return parsed
@@ -156,12 +190,25 @@ def get_soil_anomaly_insight(
raw = response.choices[0].message.content.strip()
parsed = _clean_json(raw)
_complete_audit_log(audit_log, raw)
except RAGServiceError as exc:
logger.error("Soil anomaly insight failed for %s: %s", farm_uuid, exc)
_fail_audit_log(audit_log, str(exc))
raise
except Exception as exc:
logger.error("Soil anomaly insight failed for %s: %s", farm_uuid, exc)
_fail_audit_log(audit_log, str(exc))
raise RuntimeError(f"Soil anomaly insight failed for farm {farm_uuid}.") from exc
raise RAGServiceError(
error_code="upstream_failure",
message=f"Soil anomaly insight failed for farm {farm_uuid}.",
source="llm",
retriable=True,
details={"farm_uuid": farm_uuid, "service_id": SERVICE_ID},
http_status=503,
) from exc
parsed = _validate_anomaly_insight(parsed)
parsed["status"] = "success"
parsed["source"] = "llm"
parsed["farm_uuid"] = farm_uuid
parsed["raw_response"] = raw
return parsed
+55 -9
View File
@@ -14,6 +14,7 @@ from rag.chat import (
build_rag_context,
)
from rag.config import RAGConfig, get_service_config, load_rag_config
from rag.failure_contract import RAGServiceError
logger = logging.getLogger(__name__)
@@ -38,18 +39,47 @@ def _clean_json(raw: str) -> dict[str, Any]:
cleaned = cleaned[4:]
cleaned = cleaned.strip()
if not cleaned:
return {}
raise RAGServiceError(
error_code="empty_response",
message="Water need prediction LLM response was empty.",
source="llm",
retriable=True,
details={"service_id": SERVICE_ID},
http_status=502,
)
try:
return json.loads(cleaned)
except (json.JSONDecodeError, ValueError):
parsed = json.loads(cleaned)
except (json.JSONDecodeError, ValueError) as exc:
logger.warning("Invalid JSON returned by water_need_prediction LLM: %s", cleaned[:500])
return {}
raise RAGServiceError(
error_code="invalid_json",
message="Water need prediction LLM response was not valid JSON.",
source="llm",
retriable=True,
details={"service_id": SERVICE_ID},
http_status=502,
) from exc
if not isinstance(parsed, dict):
raise RAGServiceError(
error_code="invalid_schema",
message="Water need prediction LLM response root must be a JSON object.",
source="llm",
details={"service_id": SERVICE_ID},
http_status=502,
)
return parsed
def _load_farm_or_error(farm_uuid: str) -> dict[str, Any]:
farm_details = get_farm_details(farm_uuid)
if farm_details is None:
raise ValueError("farm_uuid نامعتبر است یا اطلاعات مزرعه پیدا نشد.")
raise RAGServiceError(
error_code="farm_not_found",
message="farm_uuid نامعتبر است یا اطلاعات مزرعه پیدا نشد.",
source="farm_data",
details={"farm_uuid": farm_uuid},
http_status=404,
)
return farm_details
@@ -78,9 +108,12 @@ def _validate_prediction_insight(parsed: dict[str, Any]) -> dict[str, Any]:
}
missing = [key for key in required_keys if key not in parsed]
if missing:
raise ValueError(
"Water need prediction insight response is missing required fields: "
+ ", ".join(missing)
raise RAGServiceError(
error_code="invalid_schema",
message="Water need prediction insight response is missing required fields: " + ", ".join(missing),
source="llm",
details={"missing_fields": missing, "service_id": SERVICE_ID},
http_status=502,
)
return parsed
@@ -154,12 +187,25 @@ def get_water_need_prediction_insight(
raw = response.choices[0].message.content.strip()
parsed = _clean_json(raw)
_complete_audit_log(audit_log, raw)
except RAGServiceError as exc:
logger.error("Water need prediction insight failed for %s: %s", farm_uuid, exc)
_fail_audit_log(audit_log, str(exc))
raise
except Exception as exc:
logger.error("Water need prediction insight failed for %s: %s", farm_uuid, exc)
_fail_audit_log(audit_log, str(exc))
raise RuntimeError(f"Water need prediction insight failed for farm {farm_uuid}.") from exc
raise RAGServiceError(
error_code="upstream_failure",
message=f"Water need prediction insight failed for farm {farm_uuid}.",
source="llm",
retriable=True,
details={"farm_uuid": farm_uuid, "service_id": SERVICE_ID},
http_status=503,
) from exc
parsed = _validate_prediction_insight(parsed)
parsed["status"] = "success"
parsed["source"] = "llm"
parsed["farm_uuid"] = farm_uuid
parsed["raw_response"] = raw
return parsed
+41 -5
View File
@@ -14,6 +14,7 @@ from rag.chat import (
_load_service_tone,
)
from rag.config import RAGConfig, get_service_config, load_rag_config
from rag.failure_contract import RAGServiceError
logger = logging.getLogger(__name__)
@@ -90,6 +91,8 @@ class YieldHarvestRAGService:
if audit_log is not None:
_complete_audit_log(audit_log, raw)
return {
"status": "success",
"source": "llm",
"season_highlights_subtitle": validated.season_highlights_subtitle,
"yield_prediction_explanation": validated.yield_prediction_explanation,
"harvest_readiness_summary": validated.harvest_readiness_summary,
@@ -99,12 +102,25 @@ class YieldHarvestRAGService:
logger.warning("Yield harvest narrative parsing failed for farm_uuid=%s: %s", farm_uuid, exc)
if audit_log is not None:
_fail_audit_log(audit_log, str(exc))
return {}
raise RAGServiceError(
error_code="invalid_payload",
message=f"Yield harvest narrative parsing failed for farm_uuid={farm_uuid or 'unknown'}.",
source="llm",
details={"farm_uuid": farm_uuid or "unknown", "service_id": SERVICE_ID},
http_status=502,
) from exc
except Exception as exc:
logger.error("Yield harvest narrative LLM call failed for farm_uuid=%s: %s", farm_uuid, exc)
if audit_log is not None:
_fail_audit_log(audit_log, str(exc))
return {}
raise RAGServiceError(
error_code="upstream_failure",
message=f"Yield harvest narrative generation failed for farm_uuid={farm_uuid or 'unknown'}.",
source="llm",
retriable=True,
details={"farm_uuid": farm_uuid or "unknown", "service_id": SERVICE_ID},
http_status=503,
) from exc
def _build_service_client(self, cfg: RAGConfig):
service = get_service_config(SERVICE_ID, cfg)
@@ -217,11 +233,31 @@ class YieldHarvestRAGService:
cleaned = cleaned[4:]
cleaned = cleaned.strip()
if not cleaned:
raise ValueError("Yield harvest narrative response was empty.")
raise RAGServiceError(
error_code="empty_response",
message="Yield harvest narrative response was empty.",
source="llm",
retriable=True,
details={"service_id": SERVICE_ID},
http_status=502,
)
try:
parsed = json.loads(cleaned)
except (json.JSONDecodeError, ValueError) as exc:
raise ValueError("Yield harvest narrative response was not valid JSON.") from exc
raise RAGServiceError(
error_code="invalid_json",
message="Yield harvest narrative response was not valid JSON.",
source="llm",
retriable=True,
details={"service_id": SERVICE_ID},
http_status=502,
) from exc
if not isinstance(parsed, dict):
raise ValueError("Yield harvest narrative response root must be a JSON object.")
raise RAGServiceError(
error_code="invalid_schema",
message="Yield harvest narrative response root must be a JSON object.",
source="llm",
details={"service_id": SERVICE_ID},
http_status=502,
)
return parsed