fix(backend): preserve viewed image reducer metadata (#1900)

Fix concurrent viewed_images state updates for multi-image input by preserving the reducer metadata in the vision middleware state schema.
This commit is contained in:
Zhou 2026-04-06 16:47:19 +08:00 committed by GitHub
parent f5088ed70d
commit 1ced6e977c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 14 additions and 7 deletions

View File

@ -1,22 +1,19 @@
"""Middleware for injecting image details into conversation before LLM call.""" """Middleware for injecting image details into conversation before LLM call."""
import logging import logging
from typing import NotRequired, override from typing import override
from langchain.agents import AgentState
from langchain.agents.middleware import AgentMiddleware from langchain.agents.middleware import AgentMiddleware
from langchain_core.messages import AIMessage, HumanMessage, ToolMessage from langchain_core.messages import AIMessage, HumanMessage, ToolMessage
from langgraph.runtime import Runtime from langgraph.runtime import Runtime
from deerflow.agents.thread_state import ViewedImageData from deerflow.agents.thread_state import ThreadState
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class ViewImageMiddlewareState(AgentState): class ViewImageMiddlewareState(ThreadState):
"""Compatible with the `ThreadState` schema.""" """Reuse the thread state so reducer-backed keys keep their annotations."""
viewed_images: NotRequired[dict[str, ViewedImageData] | None]
class ViewImageMiddleware(AgentMiddleware[ViewImageMiddlewareState]): class ViewImageMiddleware(AgentMiddleware[ViewImageMiddlewareState]):

View File

@ -1,11 +1,14 @@
"""Tests for create_deerflow_agent SDK entry point.""" """Tests for create_deerflow_agent SDK entry point."""
from typing import get_type_hints
from unittest.mock import MagicMock, patch from unittest.mock import MagicMock, patch
import pytest import pytest
from deerflow.agents.factory import create_deerflow_agent from deerflow.agents.factory import create_deerflow_agent
from deerflow.agents.features import Next, Prev, RuntimeFeatures from deerflow.agents.features import Next, Prev, RuntimeFeatures
from deerflow.agents.middlewares.view_image_middleware import ViewImageMiddleware
from deerflow.agents.thread_state import ThreadState
def _make_mock_model(): def _make_mock_model():
@ -127,6 +130,13 @@ def test_vision_injects_view_image_tool(mock_create_agent):
assert "view_image" in tool_names assert "view_image" in tool_names
def test_view_image_middleware_preserves_viewed_images_reducer():
middleware_hints = get_type_hints(ViewImageMiddleware.state_schema, include_extras=True)
thread_hints = get_type_hints(ThreadState, include_extras=True)
assert middleware_hints["viewed_images"] == thread_hints["viewed_images"]
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# 8. Subagent feature auto-injects task_tool # 8. Subagent feature auto-injects task_tool
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------