mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-11-15 07:41:20 +00:00
fix(channels): deliver each presented file once, in presentation order (#8209)
* fix(channels): deliver each presented file once, in presentation order _extract_artifacts scanned the turn newest-first and kept every present_files path, so a file presented twice in one turn was uploaded twice and files from separate calls arrived newest call first. Keep the first-presented order without repeats, as the merge_artifacts reducer does for the web UI. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(channels): pin dedup of a path repeated inside one present_files call Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
35d33bea75
commit
f086cce86e
@ -1243,13 +1243,18 @@ def _extract_artifacts(result: dict | list, *, thread_id: str | None = None) ->
|
||||
break
|
||||
# Look for AI messages with present_files tool calls
|
||||
if msg.get("type") == "ai":
|
||||
message_paths: list[str] = []
|
||||
for tc in msg.get("tool_calls", []):
|
||||
if isinstance(tc, dict) and tc.get("name") == "present_files":
|
||||
args = tc.get("args", {})
|
||||
paths = args.get("filepaths", [])
|
||||
if isinstance(paths, list):
|
||||
artifacts.extend(p for p in paths if isinstance(p, str))
|
||||
return artifacts
|
||||
message_paths.extend(p for p in paths if isinstance(p, str))
|
||||
# The scan runs newest-first; prepend so earlier calls stay first.
|
||||
artifacts[:0] = message_paths
|
||||
# Match the ``merge_artifacts`` reducer: first-presented order, no repeats,
|
||||
# so a file presented twice in one turn is uploaded once.
|
||||
return list(dict.fromkeys(artifacts))
|
||||
|
||||
|
||||
def _is_hidden_human_control_message(msg: Mapping[str, Any], *, thread_id: str | None = None) -> bool:
|
||||
|
||||
@ -7511,6 +7511,58 @@ class TestExtractArtifacts:
|
||||
}
|
||||
assert _extract_artifacts(result) == ["/mnt/user-data/outputs/a.txt", "/mnt/user-data/outputs/b.csv"]
|
||||
|
||||
def test_multiple_calls_keep_presentation_order_and_deduplicate(self):
|
||||
"""A file presented twice in one turn is delivered once, in the order presented.
|
||||
|
||||
``present_files`` relies on the ``merge_artifacts`` reducer, which keeps
|
||||
first-presented order and drops repeats; the IM reply must match it.
|
||||
"""
|
||||
from app.channels.manager import _extract_artifacts
|
||||
|
||||
result = {
|
||||
"messages": [
|
||||
{"type": "human", "content": "chart then report"},
|
||||
{
|
||||
"type": "ai",
|
||||
"content": "",
|
||||
"tool_calls": [{"name": "present_files", "args": {"filepaths": ["/mnt/user-data/outputs/chart.png"]}}],
|
||||
},
|
||||
{"type": "tool", "name": "present_files", "content": "Successfully presented files"},
|
||||
{
|
||||
"type": "ai",
|
||||
"content": "",
|
||||
"tool_calls": [
|
||||
{"name": "present_files", "args": {"filepaths": ["/mnt/user-data/outputs/report.md", "/mnt/user-data/outputs/chart.png"]}},
|
||||
],
|
||||
},
|
||||
{"type": "tool", "name": "present_files", "content": "Successfully presented files"},
|
||||
{"type": "ai", "content": "Done."},
|
||||
]
|
||||
}
|
||||
|
||||
assert _extract_artifacts(result) == ["/mnt/user-data/outputs/chart.png", "/mnt/user-data/outputs/report.md"]
|
||||
|
||||
def test_single_call_with_repeated_path_delivers_it_once(self):
|
||||
"""``merge_artifacts`` also drops repeats inside one ``present_files`` call."""
|
||||
from app.channels.manager import _extract_artifacts
|
||||
|
||||
result = {
|
||||
"messages": [
|
||||
{"type": "human", "content": "send the chart"},
|
||||
{
|
||||
"type": "ai",
|
||||
"content": "",
|
||||
"tool_calls": [
|
||||
{"name": "present_files", "args": {"filepaths": ["/mnt/user-data/outputs/chart.png", "/mnt/user-data/outputs/report.md", "/mnt/user-data/outputs/chart.png"]}},
|
||||
],
|
||||
},
|
||||
{"type": "tool", "name": "present_files", "content": "Successfully presented files"},
|
||||
{"type": "ai", "content": "Done."},
|
||||
]
|
||||
}
|
||||
|
||||
assert _extract_artifacts(result) == ["/mnt/user-data/outputs/chart.png", "/mnt/user-data/outputs/report.md"]
|
||||
|
||||
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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user