Spaces:
Sleeping
Sleeping
File size: 10,965 Bytes
be9fd4a 970bad4 be9fd4a 970bad4 be9fd4a | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 | """AI feature phases only (no Redis, job queue, Temporal, or other backend scale).
Phase 1 — Core RAG generation (standard pipeline)
Phase 2 — Agentic inspector, vision, optional LLM validators / ingest sanitiser
Phase 3 — AI latency & retrieval quality (async LLM, speculation, prompt cache, hybrid RAG)
"""
from __future__ import annotations
from typing import Any
from app.agentic.runtime_status import inspector_public_status, is_openai_inspector_live
from app.agentic.speculative_executor import speculative_execution_active
from app.config import effective_openai_api_key, settings
from app.db.database import (
is_sqlite_database,
multi_section_parallel_enabled,
parallel_section_writes_safe,
)
from app.llm.prompt_cache import prompt_caching_active
from app.retrieval.semantic_cache import is_semantic_cache_active
def _openai_configured() -> bool:
return bool(effective_openai_api_key())
def ai_phase1_status() -> dict[str, Any]:
"""Embeddings, retrieval, standard generate / proofread / enhance."""
backend = (settings.vectorstore_backend or "faiss").strip().lower()
return {
"name": "core_rag_generation",
"openai_required": True,
"openai_configured": _openai_configured(),
"live": _openai_configured(),
"capabilities": {
"generate_proofread_enhance": _openai_configured(),
"notes_expansion_llm": _openai_configured(),
"style_analyzer_llm": _openai_configured(),
"postprocess_verify_llm": _openai_configured(),
"embeddings": {
"prefer_local": bool(settings.prefer_local_embeddings),
"local_model": settings.local_embedding_model,
"openai_model": settings.embedding_model if not settings.prefer_local_embeddings else None,
},
"vector_retrieval": {
"backend": backend,
"hierarchical_rag": bool(settings.hierarchical_rag_enabled),
"hybrid_retrieval": bool(settings.enable_hybrid_retrieval) and backend == "qdrant",
"semantic_cache": is_semantic_cache_active(),
},
"personalised_style_rag": bool(settings.personalised_style_rag_enabled),
"rag_upload_sanitisation": bool(settings.enable_rag_upload_sanitisation),
"rag_sanitisation_regex_only": bool(settings.enable_rag_upload_sanitisation)
and not bool(getattr(settings, "rag_sanitisation_use_llm", False)),
"notes_only_generation": bool(settings.notes_only_generation),
"interference_levels": True,
"llm_section_validator": bool(settings.llm_section_validator_enabled),
},
"flags": {
"chat_model": (settings.chat_model or "").strip() or None,
"prefer_local_embeddings": bool(settings.prefer_local_embeddings),
"vectorstore_backend": backend,
"personalised_style_rag_enabled": bool(settings.personalised_style_rag_enabled),
"enable_rag_upload_sanitisation": bool(settings.enable_rag_upload_sanitisation),
"notes_only_generation": bool(settings.notes_only_generation),
},
}
def ai_phase2_status() -> dict[str, Any]:
"""Inspector tool loop, agentic generate, section photo vision."""
key_ok = _openai_configured()
vision_on = bool(settings.section_photo_vision_enabled)
notes_only = bool(settings.notes_only_generation)
agentic_on_notes = bool(settings.agentic_inspector_when_notes_only)
post_generate_inspector = (not notes_only or agentic_on_notes) and is_openai_inspector_live()
return {
"name": "agentic_inspector_and_vision",
"openai_required": True,
"openai_configured": key_ok,
"live": key_ok and (is_openai_inspector_live() or vision_on),
"capabilities": {
"inspector_tool_agent": bool(settings.inspector_tool_agent),
"inspector_live": is_openai_inspector_live(),
"post_generate_uses_inspector": post_generate_inspector,
"agentic_generate_endpoint": key_ok and is_openai_inspector_live(),
"section_photo_vision": key_ok and vision_on,
"section_photo_analyze_on_upload": bool(
getattr(settings, "section_photo_analyze_on_upload", False)
),
"rag_sanitisation_llm_ingest": bool(
settings.enable_rag_upload_sanitisation
and getattr(settings, "rag_sanitisation_use_llm", False)
),
"llm_section_validator": bool(settings.llm_section_validator_enabled),
},
"flags": {
"inspector_tool_agent": bool(settings.inspector_tool_agent),
"inspector_body_model": (settings.inspector_body_model or "").strip() or None,
"section_photo_vision_model": (
(getattr(settings, "section_photo_vision_model", None) or "").strip()
or (settings.chat_model or "").strip()
or "gpt-4o"
),
"agentic_inspector_when_notes_only": agentic_on_notes,
"primary_generate_pipeline": (settings.primary_generate_pipeline or "agentic")
.strip()
.lower(),
"production_ai_profile": bool(getattr(settings, "production_ai_profile", False)),
},
"rics_inspector": inspector_public_status(),
}
def ai_phase3_status() -> dict[str, Any]:
"""Faster / cheaper OpenAI usage and stronger retrieval — not deployment queues."""
async_on = bool(settings.enable_async_pipeline)
backend = (settings.vectorstore_backend or "faiss").strip().lower()
sla_s = int(getattr(settings, "generation_sla_seconds", 600))
return {
"name": "ai_latency_and_retrieval_quality",
"openai_required": True,
"openai_configured": _openai_configured(),
"live": _openai_configured() and async_on,
"generation_sla_seconds": sla_s,
"generation_timeout_seconds": int(getattr(settings, "generation_timeout_seconds", 720)),
"capabilities": {
"async_llm_pipeline": async_on,
"parallel_multi_section": multi_section_parallel_enabled(),
"speculative_inspector_tools": speculative_execution_active(),
"openai_prompt_cache": prompt_caching_active(),
"hybrid_retrieval_active": bool(settings.enable_hybrid_retrieval)
and backend == "qdrant",
"semantic_retrieval_cache_active": is_semantic_cache_active(),
},
"flags": {
"enable_async_pipeline": async_on,
"enable_speculative_executor": bool(settings.enable_speculative_executor),
"enable_prompt_caching": bool(settings.enable_prompt_caching),
"max_concurrent_llm_calls": int(settings.max_concurrent_llm_calls),
"vectorstore_backend": backend,
"enable_hybrid_retrieval": bool(settings.enable_hybrid_retrieval),
"semantic_cache_enabled": bool(settings.semantic_cache_enabled),
},
}
def collect_ai_phases() -> dict[str, Any]:
return {
"phase1": ai_phase1_status(),
"phase2": ai_phase2_status(),
"phase3": ai_phase3_status(),
}
def collect_ai_phase_warnings() -> list[str]:
"""Misconfigurations that weaken AI behaviour (excludes Redis/Temporal/job queue)."""
warnings: list[str] = []
key_ok = _openai_configured()
if not key_ok:
warnings.append(
"OPENAI_API_KEY unset: all LLM phases (generation, inspector, vision) are off."
)
if key_ok and not settings.section_photo_vision_enabled:
warnings.append(
"SECTION_PHOTO_VISION_ENABLED=false: photos are stored but not analysed by vision at generate."
)
if key_ok and not settings.inspector_tool_agent:
warnings.append(
"INSPECTOR_TOOL_AGENT=false: agentic paths use the legacy fixed pipeline, not tool-calling."
)
if (
key_ok
and settings.notes_only_generation
and not settings.agentic_inspector_when_notes_only
):
warnings.append(
"Phase 2 inactive on POST /generate: NOTES_ONLY_GENERATION=true and "
"AGENTIC_INSPECTOR_WHEN_NOTES_ONLY=false. Use agentic/generate or enable "
"AGENTIC_INSPECTOR_WHEN_NOTES_ONLY."
)
if key_ok and settings.enable_rag_upload_sanitisation and getattr(
settings, "rag_sanitisation_use_llm", False
):
warnings.append(
"RAG_SANITISATION_USE_LLM=true: ingest competes with generation/vision quota; "
"use regex-only in production (auto-off on HF via production_ai_profile)."
)
if not settings.enable_rag_upload_sanitisation and settings.personalised_style_rag_enabled:
warnings.append(
"ENABLE_RAG_UPLOAD_SANITISATION=false with personalised RAG: uploads indexed without PII redaction."
)
if settings.enable_speculative_executor and not speculative_execution_active():
warnings.append(
"Phase 3: ENABLE_SPECULATIVE_EXECUTOR=true but speculation inactive "
"(requires ENABLE_ASYNC_PIPELINE=true and Phase 2 inspector path)."
)
if settings.enable_prompt_caching and not prompt_caching_active():
warnings.append(
"Phase 3: ENABLE_PROMPT_CACHING=true requires ENABLE_ASYNC_PIPELINE=true."
)
if settings.enable_async_pipeline and not key_ok:
warnings.append("Phase 3: ENABLE_ASYNC_PIPELINE=true has no effect without OPENAI_API_KEY.")
backend = (settings.vectorstore_backend or "faiss").strip().lower()
if settings.enable_hybrid_retrieval and backend != "qdrant":
warnings.append(
"Phase 3: ENABLE_HYBRID_RETRIEVAL=true needs VECTORSTORE_BACKEND=qdrant."
)
if settings.semantic_cache_enabled and not is_semantic_cache_active():
warnings.append(
"Phase 3: SEMANTIC_CACHE_ENABLED=true needs VECTORSTORE_BACKEND=qdrant."
)
sla_s = int(getattr(settings, "generation_sla_seconds", 600))
if key_ok and not multi_section_parallel_enabled():
warnings.append(
f"Full-report SLA ({sla_s}s): ENABLE_ASYNC_PIPELINE=true and parallel sections "
"are required; sequential generation often exceeds 10 minutes."
)
elif key_ok and is_sqlite_database() and not parallel_section_writes_safe():
warnings.append(
f"Full-report SLA ({sla_s}s): set ALLOW_SQLITE_PARALLEL_SECTIONS=true or use PostgreSQL."
)
if (
settings.enable_speculative_executor
and settings.enable_async_pipeline
and settings.notes_only_generation
and not settings.agentic_inspector_when_notes_only
):
warnings.append(
"Phase 3 speculation enabled but POST /generate skips the inspector (Phase 2 off on that path)."
)
return warnings
|