feat: support image inputs in assistant tests
This commit is contained in:
@@ -4,6 +4,7 @@ from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import base64
|
||||
from dataclasses import dataclass
|
||||
from io import BytesIO
|
||||
from typing import Any
|
||||
|
||||
@@ -30,6 +31,14 @@ VISION_ANALYSIS_SYSTEM_PROMPT = (
|
||||
)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class VisionInputResolution:
|
||||
"""The active model path for one explicit user image turn."""
|
||||
|
||||
native: bool
|
||||
config: AssistantConfig
|
||||
|
||||
|
||||
def _require(value: str, label: str) -> str:
|
||||
if value:
|
||||
return value
|
||||
@@ -246,3 +255,83 @@ def config_with_main_llm_as_vision(cfg: AssistantConfig) -> AssistantConfig:
|
||||
result.vision_llm_api_key = cfg.llm_api_key
|
||||
result.vision_llm_base_url = cfg.llm_base_url
|
||||
return result
|
||||
|
||||
|
||||
def resolve_vision_input(
|
||||
cfg: AssistantConfig,
|
||||
workflow_scope: dict[str, Any] | None = None,
|
||||
) -> VisionInputResolution:
|
||||
"""Resolve native versus auxiliary vision using the active runtime scope."""
|
||||
|
||||
if cfg.type != "workflow":
|
||||
if not cfg.vision_enabled:
|
||||
raise ValueError("当前助手未开启视觉能力")
|
||||
if cfg.vision_model_resource_id:
|
||||
if not cfg.vision_llm_support_image_input:
|
||||
raise ValueError("当前视觉模型不支持图片输入")
|
||||
return VisionInputResolution(native=False, config=cfg)
|
||||
if not cfg.llm_support_image_input:
|
||||
raise ValueError("当前大语言模型不支持图片输入")
|
||||
return VisionInputResolution(native=True, config=cfg)
|
||||
|
||||
scope = workflow_scope or {}
|
||||
if not scope.get("enabled"):
|
||||
raise ValueError("当前 Workflow Agent 节点未启用视觉能力")
|
||||
vision_resource_id = str(scope.get("vision_model_resource_id") or "")
|
||||
llm_resource_id = str(scope.get("llm_resource_id") or "")
|
||||
resource_id = vision_resource_id or llm_resource_id
|
||||
resource = cfg.workflow_model_resources.get(resource_id)
|
||||
if resource is not None:
|
||||
if not resource.support_image_input:
|
||||
raise ValueError(f"当前视觉模型不支持图片输入: {resource.id}")
|
||||
result = config_with_vision_resource(cfg, resource)
|
||||
elif not vision_resource_id:
|
||||
result = config_with_main_llm_as_vision(cfg)
|
||||
else:
|
||||
raise ValueError(f"视觉模型资源未加载: {vision_resource_id}")
|
||||
|
||||
result.vision_prompt_mode = normalize_vision_prompt_mode(
|
||||
scope.get("vision_prompt_mode")
|
||||
)
|
||||
result.vision_prompt = str(scope.get("vision_prompt") or "").strip()
|
||||
return VisionInputResolution(
|
||||
native=not bool(vision_resource_id),
|
||||
config=result,
|
||||
)
|
||||
|
||||
|
||||
async def build_image_user_message(
|
||||
cfg: AssistantConfig,
|
||||
image_bytes: bytes,
|
||||
prompt_text: str,
|
||||
*,
|
||||
workflow_scope: dict[str, Any] | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""Build the same model-facing user message used by an explicit photo turn."""
|
||||
|
||||
question = prompt_text.strip() or "请根据用户刚提交的图片进行回复。"
|
||||
frame = await asyncio.to_thread(image_frame_from_jpeg, image_bytes)
|
||||
resolution = resolve_vision_input(cfg, workflow_scope)
|
||||
if resolution.native:
|
||||
data_uri = await asyncio.to_thread(image_data_uri, frame)
|
||||
return {
|
||||
"role": "user",
|
||||
"content": [
|
||||
{"type": "text", "text": question},
|
||||
{"type": "image_url", "image_url": {"url": data_uri}},
|
||||
],
|
||||
}
|
||||
|
||||
observation = await analyze_image_with_vision_model(
|
||||
resolution.config,
|
||||
frame,
|
||||
question,
|
||||
)
|
||||
return {
|
||||
"role": "user",
|
||||
"content": (
|
||||
f"{question}\n\n"
|
||||
"[视觉模型对用户刚提交图片的观察]\n"
|
||||
f"{observation or '视觉模型没有返回有效观察结果。'}"
|
||||
),
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user