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

@@ -9,6 +9,7 @@ from __future__ import annotations
import json
from collections.abc import Callable
from copy import deepcopy
from typing import Any
from loguru import logger
@@ -24,6 +25,32 @@ STAY_ON_CURRENT_AGENT = STAY_ON_CURRENT_NODE
MAX_ROUTING_HISTORY_ENTRIES = 20
def _routing_user_message(
routing_input: str,
current_user_message: dict[str, Any] | None,
) -> dict[str, Any]:
"""Combine routing metadata with the current text or multimodal turn."""
if not current_user_message:
return {"role": "user", "content": routing_input}
content = current_user_message.get("content")
if not isinstance(content, list):
current_text = str(content or "").strip()
suffix = f"\n\n[当前用户输入]\n{current_text}" if current_text else ""
return {"role": "user", "content": f"{routing_input}{suffix}"}
return {
"role": "user",
"content": [
{
"type": "text",
"text": f"{routing_input}\n\n[当前用户输入如下]",
},
*deepcopy(content),
],
}
class WorkflowLLMRouter:
"""Select one LLM edge without allowing the router to speak."""
@@ -40,6 +67,7 @@ class WorkflowLLMRouter:
variables: dict[str, Any],
edge_name: Callable[[dict[str, Any]], str],
edge_description: Callable[[dict[str, Any]], str],
current_user_message: dict[str, Any] | None = None,
) -> LLMRouteResult:
"""Return a typed match, no-match or technical error."""
if not edges:
@@ -85,7 +113,13 @@ class WorkflowLLMRouter:
f"当前节点任务:{node_prompt or '未配置'}\n"
f"转移条件:\n{ordered_conditions}"
)
recent_history = history[-MAX_ROUTING_HISTORY_ENTRIES:]
# WorkflowBrain records the current turn before routing. When the full
# current message is supplied separately, keep only earlier history so
# the text is not duplicated and the image remains attached to its turn.
routing_history = (
history[:-1] if current_user_message and history else history
)
recent_history = routing_history[-MAX_ROUTING_HISTORY_ENTRIES:]
routing_input = json.dumps(
{
"conversation": recent_history,
@@ -108,7 +142,7 @@ class WorkflowLLMRouter:
model=self._cfg.model,
messages=[
{"role": "system", "content": router_prompt},
{"role": "user", "content": routing_input},
_routing_user_message(routing_input, current_user_message),
],
tools=tools,
tool_choice="required",