mirror of
https://gitee.com/wanwujie/deer-flow
synced 2026-04-03 06:12:14 +08:00
* Update uv.lock to sync with pyproject.toml * fix: update Interrupt object attribute access for LangGraph 1.0+ (#730) The Interrupt class in LangGraph 1.0 no longer has the 'ns' attribute. This change updates _create_interrupt_event() to use the new 'id' attribute instead, with a fallback to thread_id for compatibility. Changes: - Replace event_data["__interrupt__"][0].ns[0] with interrupt.id - Use getattr() with fallback for backward compatibility - Update debug log message from 'ns=' to 'id=' - Add unit tests for _create_interrupt_event function * fix the unit test error and address review comment --------- Co-authored-by: Willem Jiang <143703838+willem-bd@users.noreply.github.com>
This commit is contained in:
@@ -12,7 +12,12 @@ from langchain_core.messages import AIMessageChunk, ToolMessage
|
||||
from langgraph.types import Command
|
||||
|
||||
from src.config.report_style import ReportStyle
|
||||
from src.server.app import _astream_workflow_generator, _make_event, app
|
||||
from src.server.app import (
|
||||
_astream_workflow_generator,
|
||||
_create_interrupt_event,
|
||||
_make_event,
|
||||
app,
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@@ -657,9 +662,9 @@ class TestAstreamWorkflowGenerator:
|
||||
@pytest.mark.asyncio
|
||||
@patch("src.server.app.graph")
|
||||
async def test_astream_workflow_generator_interrupt_event(self, mock_graph):
|
||||
# Mock interrupt data
|
||||
# Mock interrupt data with the new 'id' attribute (LangGraph 1.0+)
|
||||
mock_interrupt = MagicMock()
|
||||
mock_interrupt.ns = ["interrupt_id"]
|
||||
mock_interrupt.id = "interrupt_id"
|
||||
mock_interrupt.value = "Plan requires approval"
|
||||
|
||||
interrupt_data = {"__interrupt__": [mock_interrupt]}
|
||||
@@ -920,3 +925,94 @@ class TestGenerateProseEndpoint:
|
||||
response = client.post("/api/prose/generate", json=request_data)
|
||||
assert response.status_code == 500
|
||||
assert response.json()["detail"] == "Internal Server Error"
|
||||
|
||||
|
||||
class TestCreateInterruptEvent:
|
||||
"""Tests for _create_interrupt_event function (Issue #730 fix)."""
|
||||
|
||||
def test_create_interrupt_event_with_id_attribute(self):
|
||||
"""Test that _create_interrupt_event works with LangGraph 1.0+ Interrupt objects that have 'id' attribute."""
|
||||
# Create a mock Interrupt object with the new 'id' attribute (LangGraph 1.0+)
|
||||
mock_interrupt = MagicMock()
|
||||
mock_interrupt.id = "interrupt-123"
|
||||
mock_interrupt.value = "Please review the research plan"
|
||||
|
||||
event_data = {"__interrupt__": [mock_interrupt]}
|
||||
thread_id = "thread-456"
|
||||
|
||||
result = _create_interrupt_event(thread_id, event_data)
|
||||
|
||||
# Verify the result is a properly formatted SSE event
|
||||
assert "event: interrupt\n" in result
|
||||
assert '"thread_id": "thread-456"' in result
|
||||
assert '"id": "interrupt-123"' in result
|
||||
assert '"content": "Please review the research plan"' in result
|
||||
assert '"finish_reason": "interrupt"' in result
|
||||
assert '"role": "assistant"' in result
|
||||
|
||||
def test_create_interrupt_event_fallback_to_thread_id(self):
|
||||
"""Test that _create_interrupt_event falls back to thread_id when 'id' attribute is None."""
|
||||
# Create a mock Interrupt object where id is None
|
||||
mock_interrupt = MagicMock()
|
||||
mock_interrupt.id = None
|
||||
mock_interrupt.value = "Plan review needed"
|
||||
|
||||
event_data = {"__interrupt__": [mock_interrupt]}
|
||||
thread_id = "thread-789"
|
||||
|
||||
result = _create_interrupt_event(thread_id, event_data)
|
||||
|
||||
# Verify it falls back to thread_id
|
||||
assert '"id": "thread-789"' in result
|
||||
assert '"thread_id": "thread-789"' in result
|
||||
assert '"content": "Plan review needed"' in result
|
||||
|
||||
def test_create_interrupt_event_without_id_attribute(self):
|
||||
"""Test that _create_interrupt_event handles objects without 'id' attribute (backward compatibility)."""
|
||||
# Create a mock object that doesn't have 'id' attribute at all
|
||||
class MockInterrupt:
|
||||
pass
|
||||
mock_interrupt = MockInterrupt()
|
||||
mock_interrupt.value = "Waiting for approval"
|
||||
|
||||
event_data = {"__interrupt__": [mock_interrupt]}
|
||||
thread_id = "thread-abc"
|
||||
|
||||
result = _create_interrupt_event(thread_id, event_data)
|
||||
|
||||
# Verify it falls back to thread_id when id attribute doesn't exist
|
||||
assert '"id": "thread-abc"' in result
|
||||
assert '"content": "Waiting for approval"' in result
|
||||
|
||||
def test_create_interrupt_event_options(self):
|
||||
"""Test that _create_interrupt_event includes correct options."""
|
||||
mock_interrupt = MagicMock()
|
||||
mock_interrupt.id = "int-001"
|
||||
mock_interrupt.value = "Review plan"
|
||||
|
||||
event_data = {"__interrupt__": [mock_interrupt]}
|
||||
thread_id = "thread-xyz"
|
||||
|
||||
result = _create_interrupt_event(thread_id, event_data)
|
||||
|
||||
# Verify options are included
|
||||
assert '"options":' in result
|
||||
assert '"text": "Edit plan"' in result
|
||||
assert '"value": "edit_plan"' in result
|
||||
assert '"text": "Start research"' in result
|
||||
assert '"value": "accepted"' in result
|
||||
|
||||
def test_create_interrupt_event_with_complex_value(self):
|
||||
"""Test that _create_interrupt_event handles complex content values."""
|
||||
mock_interrupt = MagicMock()
|
||||
mock_interrupt.id = "int-complex"
|
||||
mock_interrupt.value = {"plan": "Research AI", "steps": ["step1", "step2"]}
|
||||
|
||||
event_data = {"__interrupt__": [mock_interrupt]}
|
||||
thread_id = "thread-complex"
|
||||
|
||||
result = _create_interrupt_event(thread_id, event_data)
|
||||
|
||||
# Verify complex value is included (will be serialized as JSON)
|
||||
assert '"id": "int-complex"' in result
|
||||
assert "Research AI" in result or "plan" in result
|
||||
|
||||
Reference in New Issue
Block a user