router: enforce direct image inputs for plant tools and inject runtime image_data
This commit is contained in:
@@ -2007,6 +2007,19 @@ async def agent_infer(agent_id: str, request: InferRequest):
|
|||||||
tool_args = {"params": {"count": 3, "timezone": "Europe/Kyiv"}}
|
tool_args = {"params": {"count": 3, "timezone": "Europe/Kyiv"}}
|
||||||
logger.info("🛠️ oneok: auto-filled schedule_propose_slots.params")
|
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(
|
result = await tool_manager.execute_tool(
|
||||||
tool_name,
|
tool_name,
|
||||||
tool_args,
|
tool_args,
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ from typing import Dict, List, Any, Optional
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from io import BytesIO, StringIO
|
from io import BytesIO, StringIO
|
||||||
from pathlib import PurePath
|
from pathlib import PurePath
|
||||||
|
from urllib.parse import urlparse
|
||||||
import xml.etree.ElementTree as ET
|
import xml.etree.ElementTree as ET
|
||||||
from xml.sax.saxutils import escape as xml_escape
|
from xml.sax.saxutils import escape as xml_escape
|
||||||
from zipfile import ZIP_DEFLATED, ZipFile
|
from zipfile import ZIP_DEFLATED, ZipFile
|
||||||
@@ -164,8 +165,7 @@ TOOL_DEFINITIONS = [
|
|||||||
"description": "Поріг confidence для fallback на GBIF",
|
"description": "Поріг confidence для fallback на GBIF",
|
||||||
"default": 0.65
|
"default": 0.65
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
"required": ["image_url"]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -792,6 +792,27 @@ class ToolManager:
|
|||||||
logger.debug(f"Agent {agent_id} has {len(filtered)} tools: {tool_names}")
|
logger.debug(f"Agent {agent_id} has {len(filtered)} tools: {tool_names}")
|
||||||
return filtered
|
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(
|
async def execute_tool(
|
||||||
self,
|
self,
|
||||||
tool_name: str,
|
tool_name: str,
|
||||||
@@ -2652,6 +2673,10 @@ class ToolManager:
|
|||||||
"""Plant identification via Pl@ntNet API (skeleton adapter)."""
|
"""Plant identification via Pl@ntNet API (skeleton adapter)."""
|
||||||
query = str(args.get("query", "") or "").strip()
|
query = str(args.get("query", "") or "").strip()
|
||||||
image_url = str(args.get("image_url", "") 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()
|
organ = str(args.get("organ", "auto") or "auto").strip().lower()
|
||||||
top_k = max(1, min(int(args.get("top_k", 3)), 5))
|
top_k = max(1, min(int(args.get("top_k", 3)), 5))
|
||||||
|
|
||||||
@@ -2687,8 +2712,15 @@ class ToolManager:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
return ToolResult(success=False, result=None, error=f"plantnet_error: {e}")
|
return ToolResult(success=False, result=None, error=f"plantnet_error: {e}")
|
||||||
|
|
||||||
if image_url:
|
if image_url or image_data:
|
||||||
ni = await self._nature_id_identify({"image_url": image_url, "top_k": top_k})
|
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:
|
if ni.success:
|
||||||
return ni
|
return ni
|
||||||
|
|
||||||
@@ -2705,9 +2737,23 @@ class ToolManager:
|
|||||||
"""Open-source plant identification via self-hosted nature-id compatible endpoint."""
|
"""Open-source plant identification via self-hosted nature-id compatible endpoint."""
|
||||||
image_url = str(args.get("image_url", "") or "").strip()
|
image_url = str(args.get("image_url", "") or "").strip()
|
||||||
image_data = str(args.get("image_data", "") 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))
|
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")))
|
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:
|
if not image_url and not image_data:
|
||||||
return ToolResult(success=False, result=None, error="image_url or image_data is required")
|
return ToolResult(success=False, result=None, error="image_url or image_data is required")
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user