ForcePilot/backend/package/yuxi/channels/adapters/urbit/vision.py
Kris 80e3f66974 refactor(urbit): 整理导入顺序并修复多处代码细节
1.  统一调整多个文件的导入排序,将TYPE_CHECKING相关导入放在正确位置
2.  修复rate_limiter中当限制数<=0时直接返回false的逻辑
3.  为message_cache新增更新消息内容的方法
4.  重构extract_graph_content支持日记类型内容提取
5.  调整@提及匹配的正则表达式,避免误匹配
6.  完善invite_manager,添加客户端和凭据支持并实现自动接受群邀请逻辑
7.  调整adapter.py中的导入顺序和初始化逻辑
8.  修复monitor中的编辑事件处理,改为异步处理并实现消息更新缓存
9.  调整datetime导入顺序,统一使用UTC在前的格式
2026-05-13 16:16:27 +08:00

97 lines
2.7 KiB
Python

from __future__ import annotations
import base64
from typing import TYPE_CHECKING, Any
from yuxi.utils.logging_config import logger
if TYPE_CHECKING:
from .client import UrbitClient
async def analyze_image(
client: UrbitClient,
image_url: str,
prompt: str = "Describe this image in detail.",
*,
vision_api_url: str | None = None,
vision_api_key: str | None = None,
) -> str | None:
try:
from .media import download_media
image_data, content_type = await download_media(client, image_url)
if not image_data:
logger.warning(f"[Urbit] Vision: failed to download image from {image_url}")
return None
logger.info(f"[Urbit] Vision: downloaded {len(image_data)} bytes ({content_type})")
return await _invoke_vision_pipeline(
image_data,
content_type,
prompt,
vision_api_url=vision_api_url,
vision_api_key=vision_api_key,
)
except Exception as e:
logger.warning(f"[Urbit] Vision analysis failed: {e}")
return None
async def _invoke_vision_pipeline(
image_data: bytes,
content_type: str,
prompt: str,
*,
vision_api_url: str | None = None,
vision_api_key: str | None = None,
) -> str | None:
if not vision_api_url:
vision_api_url = "https://api.openai.com/v1/chat/completions"
base64_image = base64.b64encode(image_data).decode("utf-8")
data_url = f"data:{content_type};base64,{base64_image}"
payload = {
"model": "gpt-4o",
"messages": [
{
"role": "user",
"content": [
{"type": "text", "text": prompt},
{"type": "image_url", "image_url": {"url": data_url}},
],
}
],
"max_tokens": 300,
}
headers: dict[str, str] = {"Content-Type": "application/json"}
if vision_api_key:
headers["Authorization"] = f"Bearer {vision_api_key}"
import httpx
async with httpx.AsyncClient(timeout=httpx.Timeout(60.0)) as http:
r = await http.post(vision_api_url, json=payload, headers=headers)
r.raise_for_status()
data = r.json()
choice = data.get("choices", [{}])[0]
message = choice.get("message", {})
result = message.get("content", "")
if result:
logger.info(f"[Urbit] Vision: analysis complete ({len(result)} chars)")
return result or None
async def build_vision_context(
image_urls: list[str],
prompt: str = "What do you see in these images?",
) -> dict[str, Any]:
return {
"images": [{"url": url} for url in image_urls],
"prompt": prompt,
"source": "urbit_vision",
}