Improve agent image capability routing

This commit is contained in:
jxxghp
2026-04-15 08:55:32 +08:00
parent bf127d6a70
commit 13c3c082b8
14 changed files with 417 additions and 57 deletions

View File

@@ -181,7 +181,9 @@ class DiscordModule(_ModuleBase, _MessageBase[Discord]):
return None
@staticmethod
def _extract_images(msg_json: dict) -> Optional[List[str]]:
def _extract_images(
msg_json: dict,
) -> Optional[List[CommingMessage.MessageImage]]:
"""
从Discord消息中提取图片URL
"""
@@ -200,7 +202,14 @@ class DiscordModule(_ModuleBase, _MessageBase[Discord]):
or content_type.startswith("image/")
or filename.endswith(DiscordModule._IMAGE_SUFFIXES)
):
images.append(url)
images.append(
CommingMessage.MessageImage(
ref=url,
name=attachment.get("filename"),
mime_type=attachment.get("content_type"),
size=attachment.get("size"),
)
)
return images if images else None
@classmethod

View File

@@ -155,8 +155,10 @@ class QQBotModule(_ModuleBase, _MessageBase[QQBot]):
return None
@classmethod
def _extract_images(cls, msg_body: dict) -> Optional[List[str]]:
images: List[str] = []
def _extract_images(
cls, msg_body: dict
) -> Optional[List[CommingMessage.MessageImage]]:
images: List[CommingMessage.MessageImage] = []
attachments = msg_body.get("attachments") or []
if isinstance(attachments, list):
for attachment in attachments:
@@ -176,26 +178,42 @@ class QQBotModule(_ModuleBase, _MessageBase[QQBot]):
or ""
).lower()
if content_type.startswith("image/") or filename.endswith(cls._IMAGE_SUFFIXES):
images.append(url)
images.append(
CommingMessage.MessageImage(
ref=url,
name=attachment.get("filename") or attachment.get("name"),
mime_type=attachment.get("content_type")
or attachment.get("mime_type"),
size=attachment.get("size"),
)
)
for key in ("image", "image_url", "pic_url"):
value = msg_body.get(key)
if isinstance(value, str) and value.startswith("http"):
images.append(value)
images.append(CommingMessage.MessageImage(ref=value))
extra_images = msg_body.get("images")
if isinstance(extra_images, list):
for item in extra_images:
if isinstance(item, str) and item.startswith("http"):
images.append(item)
images.append(CommingMessage.MessageImage(ref=item))
elif isinstance(item, dict):
url = item.get("url") or item.get("image_url")
if isinstance(url, str) and url.startswith("http"):
images.append(url)
images.append(
CommingMessage.MessageImage(
ref=url,
name=item.get("name") or item.get("filename"),
mime_type=item.get("content_type")
or item.get("mime_type"),
size=item.get("size"),
)
)
deduped = []
for image in images:
if image not in deduped:
if image.ref not in [item.ref for item in deduped]:
deduped.append(image)
return deduped or None

View File

@@ -301,7 +301,9 @@ class SlackModule(_ModuleBase, _MessageBase[Slack]):
return None
@staticmethod
def _extract_images(msg_json: dict) -> Optional[List[str]]:
def _extract_images(
msg_json: dict,
) -> Optional[List[CommingMessage.MessageImage]]:
"""
从Slack消息中提取图片URL
"""
@@ -320,7 +322,14 @@ class SlackModule(_ModuleBase, _MessageBase[Slack]):
):
url = file.get("url_private") or file.get("url_private_download")
if url:
images.append(url)
images.append(
CommingMessage.MessageImage(
ref=url,
name=file.get("name") or file.get("title"),
mime_type=file.get("mimetype"),
size=file.get("size"),
)
)
return images if images else None
@classmethod

View File

@@ -141,12 +141,14 @@ class SynologyChatModule(_ModuleBase, _MessageBase[SynologyChat]):
return None
@classmethod
def _extract_images(cls, message: dict) -> Optional[List[str]]:
def _extract_images(
cls, message: dict
) -> Optional[List[CommingMessage.MessageImage]]:
images = []
for key in ("file_url", "image_url", "pic_url"):
value = message.get(key)
if isinstance(value, str) and cls._looks_like_image(value):
images.append(value)
images.append(CommingMessage.MessageImage(ref=value))
for key in ("attachments", "files"):
raw_value = message.get(key)
@@ -159,15 +161,23 @@ class SynologyChatModule(_ModuleBase, _MessageBase[SynologyChat]):
items = parsed if isinstance(parsed, list) else [parsed]
for item in items:
if isinstance(item, str) and cls._looks_like_image(item):
images.append(item)
images.append(CommingMessage.MessageImage(ref=item))
elif isinstance(item, dict):
url = item.get("url") or item.get("file_url") or item.get("image_url")
if isinstance(url, str) and cls._looks_like_image(url):
images.append(url)
images.append(
CommingMessage.MessageImage(
ref=url,
name=item.get("name") or item.get("filename"),
mime_type=item.get("content_type")
or item.get("mime_type"),
size=item.get("size"),
)
)
deduped = []
for image in images:
if image not in deduped:
if image.ref not in [item.ref for item in deduped]:
deduped.append(image)
return deduped or None

View File

@@ -273,7 +273,7 @@ class TelegramModule(_ModuleBase, _MessageBase[Telegram]):
return None
@staticmethod
def _extract_images(msg: dict) -> Optional[List[str]]:
def _extract_images(msg: dict) -> Optional[List[CommingMessage.MessageImage]]:
"""
从Telegram消息中提取图片file_id
"""
@@ -283,14 +283,27 @@ class TelegramModule(_ModuleBase, _MessageBase[Telegram]):
largest_photo = photo[-1]
file_id = largest_photo.get("file_id")
if file_id:
images.append(f"tg://file_id/{file_id}")
images.append(
CommingMessage.MessageImage(
ref=f"tg://file_id/{file_id}",
mime_type="image/jpeg",
size=largest_photo.get("file_size"),
)
)
document = msg.get("document")
if document:
file_id = document.get("file_id")
mime_type = document.get("mime_type", "")
if file_id and mime_type.startswith("image/"):
images.append(f"tg://file_id/{file_id}")
images.append(
CommingMessage.MessageImage(
ref=f"tg://file_id/{file_id}",
name=document.get("file_name"),
mime_type=document.get("mime_type"),
size=document.get("file_size"),
)
)
return images if images else None

View File

@@ -162,7 +162,9 @@ class VoceChatModule(_ModuleBase, _MessageBase[VoceChat]):
return None
@classmethod
def _extract_images(cls, detail: dict) -> Optional[List[str]]:
def _extract_images(
cls, detail: dict
) -> Optional[List[CommingMessage.MessageImage]]:
content_type = detail.get("content_type") or ""
if content_type != "vocechat/file":
return None
@@ -194,9 +196,23 @@ class VoceChatModule(_ModuleBase, _MessageBase[VoceChat]):
if not is_image:
return None
if isinstance(direct_url, str) and direct_url.startswith("http"):
return [direct_url]
return [
CommingMessage.MessageImage(
ref=direct_url,
name=properties.get("name") or properties.get("filename"),
mime_type=mime_type or None,
size=properties.get("size"),
)
]
if isinstance(file_path, str) and file_path:
return [f"vocechat://file/{quote(file_path, safe='')}"]
return [
CommingMessage.MessageImage(
ref=f"vocechat://file/{quote(file_path, safe='')}",
name=properties.get("name") or properties.get("filename"),
mime_type=mime_type or None,
size=properties.get("size"),
)
]
return None
@classmethod

View File

@@ -189,9 +189,9 @@ class WechatModule(_ModuleBase, _MessageBase[WeChat]):
media_id = DomUtils.tag_value(root_node, "MediaId")
pic_url = DomUtils.tag_value(root_node, "PicUrl")
if media_id:
images = [f"wxwork://media_id/{media_id}"]
images = [CommingMessage.MessageImage(ref=f"wxwork://media_id/{media_id}")]
elif pic_url:
images = [pic_url]
images = [CommingMessage.MessageImage(ref=pic_url)]
logger.info(
f"收到来自 {client_config.name} 的微信图片消息userid={user_id}, images={len(images) if images else 0}"
)

View File

@@ -16,6 +16,7 @@ from app.core.config import settings
from app.core.context import MediaInfo, Context
from app.core.metainfo import MetaInfo
from app.log import logger
from app.schemas import CommingMessage
from app.utils.http import RequestUtils
from app.utils.string import StringUtils
@@ -359,27 +360,50 @@ class WeChatBot:
return f"wxbot://image/{encoded}"
@classmethod
def _extract_images_from_body(cls, body: dict) -> Optional[List[str]]:
images: List[str] = []
def _extract_images_from_body(
cls, body: dict
) -> Optional[List["CommingMessage.MessageImage"]]:
images: List["CommingMessage.MessageImage"] = []
msgtype = body.get("msgtype")
if msgtype == "image":
image_ref = cls._build_image_ref(body.get("image") or {})
image_payload = body.get("image") or {}
image_ref = cls._build_image_ref(image_payload)
if image_ref:
images.append(image_ref)
images.append(
CommingMessage.MessageImage(
ref=image_ref,
mime_type=image_payload.get("mime_type")
or image_payload.get("content_type"),
)
)
elif msgtype == "mixed":
for item in (body.get("mixed") or {}).get("msg_item") or []:
if item.get("msgtype") != "image":
continue
image_ref = cls._build_image_ref(item.get("image") or {})
image_payload = item.get("image") or {}
image_ref = cls._build_image_ref(image_payload)
if image_ref:
images.append(image_ref)
images.append(
CommingMessage.MessageImage(
ref=image_ref,
mime_type=image_payload.get("mime_type")
or image_payload.get("content_type"),
)
)
quote = body.get("quote") or {}
if not images and quote.get("msgtype") == "image":
image_ref = cls._build_image_ref(quote.get("image") or {})
image_payload = quote.get("image") or {}
image_ref = cls._build_image_ref(image_payload)
if image_ref:
images.append(image_ref)
images.append(
CommingMessage.MessageImage(
ref=image_ref,
mime_type=image_payload.get("mime_type")
or image_payload.get("content_type"),
)
)
return images or None