ForcePilot/backend/package/yuxi/channels/adapters/zalo_user/vision.py
Kris 9285da5c55 feat(zalo-user): 实现完整的Zalo用户频道适配器
新增了Zalo用户频道的完整适配器实现,包括:
- 基础的适配器初始化与导出结构
- 群组同步与成员获取功能
- 请求限流与退避重试机制
- 健康检查与状态探针
- 消息反应/表情处理工具
- 贴纸缓存与消息去重功能
- 消息ID格式化与追踪
- TTS语音合成支持
- 消息发送权限校验
- 长文本分块发送
- 操作审批流程
- 常量配置与国际化支持
- 图像视觉分析功能
- 贴纸消息处理
- 登录与配置向导
- 群组上下文缓存
- 网关连接管理
- 配置Schema校验
- 状态问题与安全审计
- 内联按钮与交互组件
- 交互式回调分发
- 联系人与群组目录管理
- 富媒体卡片消息支持
2026-05-12 00:53:13 +08:00

81 lines
2.2 KiB
Python

from __future__ import annotations
from typing import Any
from yuxi.channels.models import ChannelMessage
from yuxi.utils.logging_config import logger
async def analyze_image(
bridge: Any,
image_url: str,
prompt: str = "Describe this image in detail.",
) -> dict[str, Any]:
try:
resp = await bridge.post(
"/vision/analyze",
json={
"image_url": image_url,
"prompt": prompt,
},
)
return resp.json()
except Exception as e:
logger.warning(f"[ZaloUser] Vision analysis failed: {e}")
return {"error": str(e)}
async def analyze_media(
bridge: Any,
media_url: str,
media_type: str = "image",
prompt: str = "",
) -> dict[str, Any]:
if media_type == "image":
default_prompt = "Describe this image in detail."
elif media_type == "video":
default_prompt = "Describe this video content."
else:
default_prompt = "Analyze this media content."
return await analyze_image(bridge, media_url, prompt or default_prompt)
def has_vision_support(config: dict[str, Any]) -> bool:
return config.get("vision", {}).get("enabled", False)
async def augment_message_with_vision(
bridge: Any,
message: ChannelMessage,
config: dict[str, Any],
) -> ChannelMessage:
if not has_vision_support(config):
return message
image_attachments = [a for a in message.attachments if a.type == "image"]
if not image_attachments:
return message
descriptions: list[str] = []
for att in image_attachments:
if not att.url:
continue
try:
result = await analyze_image(bridge, att.url)
if result.get("description"):
descriptions.append(f"[Image: {result['description']}]")
elif result.get("text"):
descriptions.append(f"[Image OCR: {result['text']}]")
except Exception:
pass
if descriptions:
vision_text = "\n".join(descriptions)
if message.content:
message.content = f"{message.content}\n{vision_text}"
else:
message.content = vision_text
return message