url-09: fetched images become vision input — fetch_url og:image/body images ride as input_image, direct image urls ingested, data urls never in json tool text
This commit is contained in:
@@ -329,6 +329,17 @@ class OpenAIResponder(AIResponder, LeonardoAIDrawMixIn):
|
||||
items.append(data)
|
||||
return items
|
||||
|
||||
@staticmethod
|
||||
def _split_vision(function_result: Any) -> Tuple[Any, List[str]]:
|
||||
"""Detach cached image data URLs from a tool result (URL-09) — they
|
||||
ride to the model as image input, never as JSON text (a base64 data
|
||||
URL would blow the 8000-char sanitizer cap)."""
|
||||
if isinstance(function_result, dict) and function_result.get("vision"):
|
||||
return function_result, [str(url) for url in function_result.pop("vision")]
|
||||
if isinstance(function_result, dict):
|
||||
function_result.pop("vision", None)
|
||||
return function_result, []
|
||||
|
||||
@staticmethod
|
||||
def _responses_refused(result: Any) -> bool:
|
||||
for item in getattr(result, "output", []) or []:
|
||||
@@ -381,6 +392,7 @@ class OpenAIResponder(AIResponder, LeonardoAIDrawMixIn):
|
||||
function_args = json.loads(call.arguments) if call.arguments else {}
|
||||
logging.info(f"🔧 Executing tool: {call.name} with args: {function_args}")
|
||||
function_result = await self._dispatch_tool(call.name, function_args, author or "")
|
||||
function_result, vision = self._split_vision(function_result)
|
||||
logging.info(f"🔧 Tool result: {type(function_result)} - {str(function_result)[:200]}...")
|
||||
context.append(
|
||||
{
|
||||
@@ -390,6 +402,10 @@ class OpenAIResponder(AIResponder, LeonardoAIDrawMixIn):
|
||||
"output": sanitize_external_text(json.dumps(function_result), 8000) if function_result else "No results found",
|
||||
}
|
||||
)
|
||||
if vision:
|
||||
# fetched images become sight, not text (URL-09)
|
||||
logging.info(f"🔧 Tool returned {len(vision)} image(s) — attached as vision input")
|
||||
context.append({"role": "user", "content": [{"type": "input_image", "image_url": url} for url in vision]})
|
||||
kwargs["input"] = context
|
||||
rounds -= 1
|
||||
if rounds <= 0:
|
||||
@@ -504,6 +520,7 @@ class OpenAIResponder(AIResponder, LeonardoAIDrawMixIn):
|
||||
|
||||
# Route to the right provider (IGDB or URL reader)
|
||||
function_result = await self._dispatch_tool(function_name, function_args, self._last_author(messages) or "")
|
||||
function_result, vision = self._split_vision(function_result)
|
||||
|
||||
logging.info(f"🔧 Tool result: {type(function_result)} - {str(function_result)[:200]}...")
|
||||
|
||||
@@ -517,6 +534,12 @@ class OpenAIResponder(AIResponder, LeonardoAIDrawMixIn):
|
||||
),
|
||||
}
|
||||
)
|
||||
if vision:
|
||||
# fetched images become sight, not text (URL-09)
|
||||
logging.info(f"🔧 Tool returned {len(vision)} image(s) — attached as vision input")
|
||||
messages.append(
|
||||
{"role": "user", "content": [{"type": "image_url", "image_url": {"url": url}} for url in vision]}
|
||||
)
|
||||
|
||||
# Get final response after function execution - remove tools for final call
|
||||
final_chat_kwargs = {
|
||||
|
||||
Reference in New Issue
Block a user