feat: route workflow image inputs natively

This commit is contained in:
Xin Wang
2026-08-03 10:55:57 +08:00
parent 2e84de0798
commit f3439b21d1
10 changed files with 412 additions and 33 deletions

View File

@@ -141,6 +141,25 @@ def _vision_uses_main_llm(cfg: AssistantConfig) -> bool:
return not cfg.vision_model_resource_id and cfg.llm_support_image_input
def _workflow_vision_uses_main_llm(
cfg: AssistantConfig,
scope: dict[str, Any],
) -> bool:
"""Resolve native versus auxiliary vision for the active Workflow Agent."""
if not scope.get("enabled"):
raise ValueError("当前 Workflow Agent 节点未启用视觉能力")
if scope.get("vision_model_resource_id"):
return False
llm_resource_id = str(scope.get("llm_resource_id") or "")
resource = cfg.workflow_model_resources.get(llm_resource_id)
if not resource:
raise ValueError(f"当前 Workflow Agent 的 LLM 资源未加载:{llm_resource_id}")
if not resource.support_image_input:
raise ValueError("当前 Workflow Agent 的 LLM 不支持图片输入")
return True
def _image_data_uri(frame: UserImageRawFrame) -> str:
if not frame.format:
raise ValueError("摄像头图片帧缺少 format,无法编码给视觉模型")
@@ -388,6 +407,11 @@ async def run_pipeline(
"vision_model_resource_id": None,
"llm_resource_id": None,
}
def active_vision_uses_main_llm() -> bool:
if cfg.type == "workflow":
return _workflow_vision_uses_main_llm(cfg, workflow_vision_scope)
return vision_native_mode
vision_schema = FunctionSchema(
name=VISION_TOOL_NAME,
description=(
@@ -510,6 +534,27 @@ async def run_pipeline(
raise ValueError(f"视觉模型资源未加载:{vision_resource_id}")
if cfg.type == "workflow" and vision_enabled:
async def native_flow_fetch_user_image(params: FunctionCallParams) -> None:
question = str(params.arguments.get("question") or "请描述当前画面。")
user_id = vision_state.get("client_id")
if not user_id:
await params.result_callback(
{
"status": "no_video_client",
"message": "当前还没有可用的摄像头视频流。",
}
)
return
request = UserImageRequestFrame(
user_id=user_id,
text=question,
append_to_context=True,
function_name=params.function_name,
tool_call_id=params.tool_call_id,
result_callback=params.result_callback,
)
await params.llm.push_frame(request, FrameDirection.UPSTREAM)
async def flow_fetch_user_image(args, _flow_manager):
if not workflow_vision_scope.get("enabled"):
return {
@@ -547,6 +592,20 @@ async def run_pipeline(
logger.warning(f"Workflow 视觉理解失败:{exc}")
return {"status": "error", "message": "视觉理解暂时不可用。"}
# ConfiguredFlowManager keeps the Flows handler for auxiliary models,
# but uses Pipecat's native function-call image path when the active
# Agent selected its own visual-capable LLM.
setattr(
flow_fetch_user_image,
"_workflow_native_vision_handler",
native_flow_fetch_user_image,
)
setattr(
flow_fetch_user_image,
"_workflow_native_vision_enabled",
active_vision_uses_main_llm,
)
workflow_vision_function = FlowsFunctionSchema(
name=VISION_TOOL_NAME,
description=vision_schema.description,
@@ -710,7 +769,8 @@ async def run_pipeline(
user_id = vision_state.get("client_id")
if not user_id:
raise ValueError("当前没有可用的摄像头视频流")
analysis_cfg = None if vision_native_mode else active_vision_config()
native_vision = active_vision_uses_main_llm()
analysis_cfg = None if native_vision else active_vision_config()
request = UserImageRequestFrame(
user_id=user_id,
@@ -722,7 +782,7 @@ async def run_pipeline(
except asyncio.TimeoutError as exc:
raise ValueError("等待摄像头视频帧超时") from exc
if vision_native_mode:
if native_vision:
image_frame.text = value.prompt_text
image_frame.append_to_context = True
image_frame.request = None