mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-06-10 09:25:57 +00:00
This commit is contained in:
@@ -173,6 +173,8 @@ def _extract_response_text(result: dict | list) -> str:
|
|||||||
|
|
||||||
# Stop at the last human message — anything before it is a previous turn
|
# Stop at the last human message — anything before it is a previous turn
|
||||||
if msg_type == "human":
|
if msg_type == "human":
|
||||||
|
if _is_hidden_human_control_message(msg):
|
||||||
|
continue
|
||||||
break
|
break
|
||||||
|
|
||||||
# Check for tool messages from ask_clarification (interrupt case)
|
# Check for tool messages from ask_clarification (interrupt case)
|
||||||
@@ -313,6 +315,8 @@ def _extract_artifacts(result: dict | list) -> list[str]:
|
|||||||
continue
|
continue
|
||||||
# Stop at the last human message — anything before it is a previous turn
|
# Stop at the last human message — anything before it is a previous turn
|
||||||
if msg.get("type") == "human":
|
if msg.get("type") == "human":
|
||||||
|
if _is_hidden_human_control_message(msg):
|
||||||
|
continue
|
||||||
break
|
break
|
||||||
# Look for AI messages with present_files tool calls
|
# Look for AI messages with present_files tool calls
|
||||||
if msg.get("type") == "ai":
|
if msg.get("type") == "ai":
|
||||||
@@ -325,6 +329,18 @@ def _extract_artifacts(result: dict | list) -> list[str]:
|
|||||||
return artifacts
|
return artifacts
|
||||||
|
|
||||||
|
|
||||||
|
def _is_hidden_human_control_message(msg: Mapping[str, Any]) -> bool:
|
||||||
|
"""Return whether a human message is an internal control message hidden from UI."""
|
||||||
|
if msg.get("type") != "human":
|
||||||
|
return False
|
||||||
|
|
||||||
|
additional_kwargs = msg.get("additional_kwargs")
|
||||||
|
if not isinstance(additional_kwargs, Mapping):
|
||||||
|
return False
|
||||||
|
|
||||||
|
return additional_kwargs.get("hide_from_ui") is True
|
||||||
|
|
||||||
|
|
||||||
def _format_artifact_text(artifacts: list[str]) -> str:
|
def _format_artifact_text(artifacts: list[str]) -> str:
|
||||||
"""Format artifact paths into a human-readable text block listing filenames."""
|
"""Format artifact paths into a human-readable text block listing filenames."""
|
||||||
import posixpath
|
import posixpath
|
||||||
|
|||||||
@@ -372,6 +372,25 @@ class TestExtractResponseText:
|
|||||||
# Should return "" (no text in current turn), NOT "Hi there!" from previous turn
|
# Should return "" (no text in current turn), NOT "Hi there!" from previous turn
|
||||||
assert _extract_response_text(result) == ""
|
assert _extract_response_text(result) == ""
|
||||||
|
|
||||||
|
def test_ignores_hidden_human_control_messages(self):
|
||||||
|
"""Hidden control messages should not terminate current-turn response extraction."""
|
||||||
|
from app.channels.manager import _extract_response_text
|
||||||
|
|
||||||
|
result = {
|
||||||
|
"messages": [
|
||||||
|
{"type": "human", "content": "plan this"},
|
||||||
|
{"type": "ai", "content": "Here is the plan."},
|
||||||
|
{
|
||||||
|
"type": "human",
|
||||||
|
"name": "todo_reminder",
|
||||||
|
"content": "keep todos updated",
|
||||||
|
"additional_kwargs": {"hide_from_ui": True},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
assert _extract_response_text(result) == "Here is the plan."
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# ChannelManager tests
|
# ChannelManager tests
|
||||||
@@ -1678,6 +1697,31 @@ class TestExtractArtifacts:
|
|||||||
}
|
}
|
||||||
assert _extract_artifacts(result) == ["/mnt/user-data/outputs/a.txt", "/mnt/user-data/outputs/b.csv"]
|
assert _extract_artifacts(result) == ["/mnt/user-data/outputs/a.txt", "/mnt/user-data/outputs/b.csv"]
|
||||||
|
|
||||||
|
def test_ignores_hidden_human_control_messages(self):
|
||||||
|
"""Hidden control messages should not hide current-turn present_files artifacts."""
|
||||||
|
from app.channels.manager import _extract_artifacts
|
||||||
|
|
||||||
|
result = {
|
||||||
|
"messages": [
|
||||||
|
{"type": "human", "content": "export"},
|
||||||
|
{
|
||||||
|
"type": "ai",
|
||||||
|
"content": "Done.",
|
||||||
|
"tool_calls": [
|
||||||
|
{"name": "present_files", "args": {"filepaths": ["/mnt/user-data/outputs/plan.md"]}},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "human",
|
||||||
|
"name": "todo_completion_reminder",
|
||||||
|
"content": "mark tasks complete",
|
||||||
|
"additional_kwargs": {"hide_from_ui": True},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
assert _extract_artifacts(result) == ["/mnt/user-data/outputs/plan.md"]
|
||||||
|
|
||||||
|
|
||||||
class TestFormatArtifactText:
|
class TestFormatArtifactText:
|
||||||
def test_single_artifact(self):
|
def test_single_artifact(self):
|
||||||
@@ -1790,6 +1834,50 @@ class TestHandleChatWithArtifacts:
|
|||||||
|
|
||||||
_run(go())
|
_run(go())
|
||||||
|
|
||||||
|
def test_hidden_human_control_message_does_not_trigger_no_response_fallback(self):
|
||||||
|
"""Plan-mode hidden control messages should not mask the final AI response."""
|
||||||
|
from app.channels.manager import ChannelManager
|
||||||
|
|
||||||
|
async def go():
|
||||||
|
bus = MessageBus()
|
||||||
|
store = ChannelStore(path=Path(tempfile.mkdtemp()) / "store.json")
|
||||||
|
manager = ChannelManager(bus=bus, store=store)
|
||||||
|
|
||||||
|
run_result = {
|
||||||
|
"messages": [
|
||||||
|
{"type": "human", "content": "make a plan"},
|
||||||
|
{"type": "ai", "content": "Here is a concrete plan."},
|
||||||
|
{
|
||||||
|
"type": "human",
|
||||||
|
"name": "todo_reminder",
|
||||||
|
"content": "sync todos",
|
||||||
|
"additional_kwargs": {"hide_from_ui": True},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
mock_client = _make_mock_langgraph_client(run_result=run_result)
|
||||||
|
manager._client = mock_client
|
||||||
|
|
||||||
|
outbound_received = []
|
||||||
|
bus.subscribe_outbound(lambda msg: outbound_received.append(msg))
|
||||||
|
await manager.start()
|
||||||
|
|
||||||
|
await bus.publish_inbound(
|
||||||
|
InboundMessage(
|
||||||
|
channel_name="test",
|
||||||
|
chat_id="c1",
|
||||||
|
user_id="u1",
|
||||||
|
text="make a plan",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
await _wait_for(lambda: len(outbound_received) >= 1)
|
||||||
|
await manager.stop()
|
||||||
|
|
||||||
|
assert len(outbound_received) == 1
|
||||||
|
assert outbound_received[0].text == "Here is a concrete plan."
|
||||||
|
|
||||||
|
_run(go())
|
||||||
|
|
||||||
def test_only_last_turn_artifacts_returned(self):
|
def test_only_last_turn_artifacts_returned(self):
|
||||||
"""Only artifacts from the current turn's present_files calls should be included."""
|
"""Only artifacts from the current turn's present_files calls should be included."""
|
||||||
from app.channels.manager import ChannelManager
|
from app.channels.manager import ChannelManager
|
||||||
|
|||||||
Reference in New Issue
Block a user