router: enforce direct image inputs for plant tools and inject runtime image_data

This commit is contained in:
NODA1 System
2026-02-21 11:52:50 +01:00
parent f3d2aa6499
commit 50dfcd7390
2 changed files with 63 additions and 4 deletions

View File

@@ -2007,6 +2007,19 @@ async def agent_infer(agent_id: str, request: InferRequest):
tool_args = {"params": {"count": 3, "timezone": "Europe/Kyiv"}}
logger.info("🛠️ oneok: auto-filled schedule_propose_slots.params")
# Plant tools: inject runtime image payload from current request to avoid
# hallucinated page URLs (e.g. t.me/<chat>/<msg>) that are not direct images.
if tool_name in {"nature_id_identify", "plantnet_lookup"}:
if not isinstance(tool_args, dict):
tool_args = {}
runtime_image_data = None
if isinstance(request.images, list) and request.images:
first_image = request.images[0]
if isinstance(first_image, str) and first_image.startswith("data:image/") and ";base64," in first_image:
runtime_image_data = first_image
if runtime_image_data:
tool_args["_runtime_image_data"] = runtime_image_data
result = await tool_manager.execute_tool(
tool_name,
tool_args,

View File

@@ -19,6 +19,7 @@ from typing import Dict, List, Any, Optional
from dataclasses import dataclass
from io import BytesIO, StringIO
from pathlib import PurePath
from urllib.parse import urlparse
import xml.etree.ElementTree as ET
from xml.sax.saxutils import escape as xml_escape
from zipfile import ZIP_DEFLATED, ZipFile
@@ -164,8 +165,7 @@ TOOL_DEFINITIONS = [
"description": "Поріг confidence для fallback на GBIF",
"default": 0.65
}
},
"required": ["image_url"]
}
}
}
},
@@ -791,6 +791,27 @@ class ToolManager:
tool_names = [t.get("function", {}).get("name") for t in filtered]
logger.debug(f"Agent {agent_id} has {len(filtered)} tools: {tool_names}")
return filtered
@staticmethod
def _is_image_data_url(value: str) -> bool:
v = str(value or "").strip()
return bool(v.startswith("data:image/") and ";base64," in v)
@staticmethod
def _is_known_non_direct_image_url(url: str) -> bool:
u = str(url or "").strip()
if not u:
return False
try:
p = urlparse(u)
except Exception:
return True
host = (p.netloc or "").lower()
if host in {"t.me", "telegram.me"}:
return True
if "web.telegram.org" in host:
return True
return False
async def execute_tool(
self,
@@ -2652,6 +2673,10 @@ class ToolManager:
"""Plant identification via Pl@ntNet API (skeleton adapter)."""
query = str(args.get("query", "") or "").strip()
image_url = str(args.get("image_url", "") or "").strip()
image_data = str(args.get("image_data", "") or "").strip()
runtime_image_data = str(args.get("_runtime_image_data", "") or "").strip()
if not image_data and self._is_image_data_url(runtime_image_data):
image_data = runtime_image_data
organ = str(args.get("organ", "auto") or "auto").strip().lower()
top_k = max(1, min(int(args.get("top_k", 3)), 5))
@@ -2687,8 +2712,15 @@ class ToolManager:
except Exception as e:
return ToolResult(success=False, result=None, error=f"plantnet_error: {e}")
if image_url:
ni = await self._nature_id_identify({"image_url": image_url, "top_k": top_k})
if image_url or image_data:
ni_args: Dict[str, Any] = {"top_k": top_k}
if image_data:
ni_args["image_data"] = image_data
else:
ni_args["image_url"] = image_url
if runtime_image_data:
ni_args["_runtime_image_data"] = runtime_image_data
ni = await self._nature_id_identify(ni_args)
if ni.success:
return ni
@@ -2705,9 +2737,23 @@ class ToolManager:
"""Open-source plant identification via self-hosted nature-id compatible endpoint."""
image_url = str(args.get("image_url", "") or "").strip()
image_data = str(args.get("image_data", "") or "").strip()
runtime_image_data = str(args.get("_runtime_image_data", "") or "").strip()
if not image_data and self._is_image_data_url(runtime_image_data):
image_data = runtime_image_data
top_k = max(1, min(int(args.get("top_k", 3)), 10))
min_confidence = float(args.get("min_confidence", os.getenv("NATURE_ID_MIN_CONFIDENCE", "0.65")))
if image_url and self._is_known_non_direct_image_url(image_url):
if image_data:
logger.info("nature_id_identify: replacing non-direct image_url with runtime image_data")
image_url = ""
else:
return ToolResult(
success=False,
result=None,
error="image_url is not direct image URL; provide image_data or direct Telegram file URL",
)
if not image_url and not image_data:
return ToolResult(success=False, result=None, error="image_url or image_data is required")