agromatrix: deploy context/photo learning + deterministic excel policy
This commit is contained in:
@@ -11,7 +11,6 @@ import httpx
|
||||
import logging
|
||||
import hashlib
|
||||
import time # For latency metrics
|
||||
from datetime import datetime
|
||||
|
||||
# CrewAI Integration
|
||||
try:
|
||||
@@ -236,18 +235,6 @@ def _build_image_fallback_response(agent_id: str, prompt: str = "") -> str:
|
||||
return "Я поки не бачу достатньо деталей на фото. Надішли, будь ласка, чіткіше фото або крупний план об'єкта."
|
||||
|
||||
|
||||
def _parse_tool_json_result(raw: Any) -> Dict[str, Any]:
|
||||
if isinstance(raw, dict):
|
||||
return raw
|
||||
if isinstance(raw, str):
|
||||
try:
|
||||
parsed = json.loads(raw)
|
||||
return parsed if isinstance(parsed, dict) else {}
|
||||
except Exception:
|
||||
return {}
|
||||
return {}
|
||||
|
||||
|
||||
|
||||
def _looks_like_image_question(prompt: str) -> bool:
|
||||
if not prompt:
|
||||
@@ -1351,32 +1338,20 @@ async def agent_infer(agent_id: str, request: InferRequest):
|
||||
logger.info(f"ℹ️ No system_prompt in request for agent {agent_id}, loading from configured sources")
|
||||
|
||||
if not system_prompt:
|
||||
if not (CITY_SERVICE_URL or '').strip():
|
||||
try:
|
||||
from prompt_builder import get_agent_system_prompt
|
||||
system_prompt = await get_agent_system_prompt(
|
||||
agent_id,
|
||||
city_service_url=CITY_SERVICE_URL,
|
||||
router_config=router_config
|
||||
)
|
||||
logger.info(f"✅ Loaded system prompt from database for {agent_id}")
|
||||
except Exception as e:
|
||||
logger.warning(f"⚠️ Could not load prompt from database: {e}")
|
||||
# Fallback to config
|
||||
system_prompt_source = "router_config"
|
||||
agent_config = router_config.get("agents", {}).get(agent_id, {})
|
||||
system_prompt = agent_config.get("system_prompt")
|
||||
logger.info(f"ℹ️ CITY_SERVICE_URL is empty; loaded system prompt from router_config for {agent_id}")
|
||||
else:
|
||||
try:
|
||||
from prompt_builder import get_agent_system_prompt
|
||||
system_prompt = await get_agent_system_prompt(
|
||||
agent_id,
|
||||
city_service_url=CITY_SERVICE_URL,
|
||||
router_config=router_config
|
||||
)
|
||||
logger.info(f"✅ Loaded system prompt from city service/config for {agent_id}")
|
||||
except Exception as e:
|
||||
logger.warning(f"⚠️ Could not load prompt via prompt_builder: {e}")
|
||||
# Fallback to config
|
||||
system_prompt_source = "router_config"
|
||||
agent_config = router_config.get("agents", {}).get(agent_id, {})
|
||||
system_prompt = agent_config.get("system_prompt")
|
||||
|
||||
if system_prompt and system_prompt_source == "city_service":
|
||||
# prompt_builder may silently fall back to router config; reflect actual source in metadata/logs
|
||||
cfg_prompt = (router_config.get("agents", {}).get(agent_id, {}) or {}).get("system_prompt")
|
||||
if cfg_prompt and (system_prompt or "").strip() == str(cfg_prompt).strip():
|
||||
system_prompt_source = "router_config"
|
||||
|
||||
if not system_prompt:
|
||||
system_prompt_source = "empty"
|
||||
@@ -1399,109 +1374,6 @@ async def agent_infer(agent_id: str, request: InferRequest):
|
||||
# Use router config to get default model for agent, fallback to qwen3:8b
|
||||
agent_config = router_config.get("agents", {}).get(agent_id, {})
|
||||
|
||||
# =========================================================================
|
||||
# AGROMATRIX PLANT PRE-VISION (edge tool before CrewAI)
|
||||
# =========================================================================
|
||||
crewai_profile = str(effective_metadata.get("crewai_profile", "") or "").strip().lower()
|
||||
is_agromatrix_plant = request_agent_id == "agromatrix" and crewai_profile == "plant_intel"
|
||||
|
||||
if is_agromatrix_plant and http_client and user_id and chat_id and not request.images:
|
||||
# Follow-up path: reuse last structured plant identification from fact-memory.
|
||||
fact_key = f"last_plant:{request_agent_id}:{chat_id}"
|
||||
try:
|
||||
fact_resp = await http_client.get(
|
||||
f"http://memory-service:8000/facts/{fact_key}",
|
||||
params={"user_id": user_id},
|
||||
timeout=8.0,
|
||||
)
|
||||
if fact_resp.status_code == 200:
|
||||
fact_data = fact_resp.json() or {}
|
||||
last_plant = fact_data.get("fact_value_json") or {}
|
||||
if isinstance(last_plant, str):
|
||||
try:
|
||||
last_plant = json.loads(last_plant)
|
||||
except Exception:
|
||||
last_plant = {}
|
||||
if isinstance(last_plant, dict) and last_plant.get("top_k"):
|
||||
effective_metadata["last_plant"] = last_plant
|
||||
# Give deterministic context to synthesis without exposing internals to end user.
|
||||
request.prompt = (
|
||||
f"{request.prompt}\n\n"
|
||||
f"[PREVIOUS_PLANT_IDENTIFICATION] {json.dumps(last_plant, ensure_ascii=False)}"
|
||||
)
|
||||
logger.info(
|
||||
f"🌿 Plant follow-up context loaded: top1={((last_plant.get('top_k') or [{}])[0]).get('scientific_name', 'N/A')}"
|
||||
)
|
||||
except Exception as e:
|
||||
logger.warning(f"⚠️ Plant follow-up context load failed: {e}")
|
||||
|
||||
if is_agromatrix_plant and request.images and len(request.images) > 0 and TOOL_MANAGER_AVAILABLE and tool_manager:
|
||||
first_image = request.images[0]
|
||||
tool_args: Dict[str, Any] = {"top_k": 5}
|
||||
if isinstance(first_image, str) and first_image.startswith("data:"):
|
||||
tool_args["image_data"] = first_image
|
||||
elif isinstance(first_image, str):
|
||||
tool_args["image_url"] = first_image
|
||||
|
||||
try:
|
||||
tool_res = await tool_manager.execute_tool(
|
||||
"nature_id_identify",
|
||||
tool_args,
|
||||
agent_id=request_agent_id,
|
||||
chat_id=chat_id,
|
||||
user_id=user_id,
|
||||
)
|
||||
if tool_res and tool_res.success and tool_res.result:
|
||||
plant_vision = _parse_tool_json_result(tool_res.result)
|
||||
if plant_vision:
|
||||
top_k_rows = plant_vision.get("top_k") or []
|
||||
top1 = top_k_rows[0] if top_k_rows else {}
|
||||
confidence = float(plant_vision.get("confidence") or top1.get("confidence") or 0.0)
|
||||
effective_metadata["plant_vision"] = plant_vision
|
||||
effective_metadata["plant_top_k"] = top_k_rows
|
||||
effective_metadata["plant_confidence"] = confidence
|
||||
request.prompt = (
|
||||
f"{request.prompt}\n\n"
|
||||
f"[PLANT_VISION_PREPROCESSED] {json.dumps(plant_vision, ensure_ascii=False)}"
|
||||
)
|
||||
if top1:
|
||||
logger.info(
|
||||
f"🌿 Vision pre-process: {confidence:.2f}% {top1.get('scientific_name') or top1.get('name') or 'unknown'}"
|
||||
)
|
||||
else:
|
||||
logger.info("🌿 Vision pre-process: no candidates")
|
||||
|
||||
if plant_vision.get("recommend_fallback"):
|
||||
logger.info("🌿 Vision pre-process: low confidence -> GBIF fallback enabled")
|
||||
|
||||
# Persist structured plant result for follow-up questions.
|
||||
if http_client and user_id and chat_id:
|
||||
fact_key = f"last_plant:{request_agent_id}:{chat_id}"
|
||||
try:
|
||||
await http_client.post(
|
||||
"http://memory-service:8000/facts/upsert",
|
||||
json={
|
||||
"user_id": user_id,
|
||||
"fact_key": fact_key,
|
||||
"fact_value": (top1.get("scientific_name") if isinstance(top1, dict) else None),
|
||||
"fact_value_json": {
|
||||
"top_k": top_k_rows,
|
||||
"confidence": confidence,
|
||||
"recommend_fallback": bool(plant_vision.get("recommend_fallback")),
|
||||
"gbif_validation": plant_vision.get("gbif_validation"),
|
||||
"identified_at": datetime.utcnow().isoformat(),
|
||||
"agent_id": request_agent_id,
|
||||
"chat_id": chat_id,
|
||||
"source": "plant_vision_preprocess",
|
||||
},
|
||||
},
|
||||
timeout=8.0,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.warning(f"⚠️ Failed to store last_plant fact: {e}")
|
||||
except Exception as e:
|
||||
logger.warning(f"⚠️ Plant pre-vision failed: {e}")
|
||||
|
||||
# =========================================================================
|
||||
# CREWAI DECISION: Use orchestration or direct LLM?
|
||||
# =========================================================================
|
||||
@@ -1587,10 +1459,6 @@ async def agent_infer(agent_id: str, request: InferRequest):
|
||||
},
|
||||
"metadata": effective_metadata,
|
||||
"runtime_envelope": runtime_envelope,
|
||||
"plant_vision": effective_metadata.get("plant_vision"),
|
||||
"plant_top_k": effective_metadata.get("plant_top_k"),
|
||||
"plant_confidence": effective_metadata.get("plant_confidence"),
|
||||
"last_plant": effective_metadata.get("last_plant"),
|
||||
},
|
||||
team=crewai_cfg.get("team"),
|
||||
profile=effective_metadata.get("crewai_profile")
|
||||
|
||||
Reference in New Issue
Block a user