mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-06-11 09:55:59 +00:00
Show IM channel source on threads
This commit is contained in:
@@ -274,6 +274,22 @@ def _response_metadata(base_metadata: dict[str, Any], *, pending_clarification:
|
||||
return metadata
|
||||
|
||||
|
||||
def _thread_channel_metadata(msg: InboundMessage) -> dict[str, Any]:
|
||||
channel_source: dict[str, Any] = {
|
||||
"type": "im_channel",
|
||||
"provider": msg.channel_name,
|
||||
"chat_id": msg.chat_id,
|
||||
}
|
||||
if msg.topic_id:
|
||||
channel_source["topic_id"] = msg.topic_id
|
||||
if msg.thread_ts:
|
||||
channel_source["thread_ts"] = msg.thread_ts
|
||||
if msg.connection_id:
|
||||
channel_source["connection_id"] = msg.connection_id
|
||||
|
||||
return {"channel_source": channel_source}
|
||||
|
||||
|
||||
def _extract_text_content(content: Any) -> str:
|
||||
"""Extract text from a streaming payload content field."""
|
||||
if isinstance(content, str):
|
||||
@@ -943,16 +959,27 @@ class ChannelManager:
|
||||
|
||||
async def _create_thread(self, client, msg: InboundMessage) -> str:
|
||||
"""Create a new thread through Gateway and store the mapping."""
|
||||
metadata = _thread_channel_metadata(msg)
|
||||
owner_headers = _owner_headers(msg)
|
||||
if owner_headers:
|
||||
thread = await client.threads.create(headers=owner_headers)
|
||||
thread = await client.threads.create(metadata=metadata, headers=owner_headers)
|
||||
else:
|
||||
thread = await client.threads.create()
|
||||
thread = await client.threads.create(metadata=metadata)
|
||||
thread_id = thread["thread_id"]
|
||||
await self._store_thread_id(msg, thread_id)
|
||||
logger.info("[Manager] new thread created through Gateway: thread_id=%s for chat_id=%s topic_id=%s", thread_id, msg.chat_id, msg.topic_id)
|
||||
return thread_id
|
||||
|
||||
async def _update_thread_channel_metadata(self, client, msg: InboundMessage, thread_id: str) -> None:
|
||||
"""Best-effort source metadata backfill for existing IM-created threads."""
|
||||
update_kwargs: dict[str, Any] = {"metadata": _thread_channel_metadata(msg)}
|
||||
if owner_headers := _owner_headers(msg):
|
||||
update_kwargs["headers"] = owner_headers
|
||||
try:
|
||||
await client.threads.update(thread_id, **update_kwargs)
|
||||
except Exception:
|
||||
logger.debug("[Manager] failed to update channel metadata for thread_id=%s", thread_id, exc_info=True)
|
||||
|
||||
async def _handle_chat(self, msg: InboundMessage, extra_context: dict[str, Any] | None = None) -> None:
|
||||
client = self._get_client()
|
||||
|
||||
@@ -962,6 +989,7 @@ class ChannelManager:
|
||||
thread_id = await self._lookup_thread_id(msg)
|
||||
if thread_id:
|
||||
logger.info("[Manager] reusing thread: thread_id=%s for topic_id=%s", thread_id, msg.topic_id)
|
||||
await self._update_thread_channel_metadata(client, msg, thread_id)
|
||||
|
||||
# No existing thread found — create a new one
|
||||
if thread_id is None:
|
||||
@@ -1202,9 +1230,7 @@ class ChannelManager:
|
||||
if reply is None and command == "new":
|
||||
# Create a new thread through Gateway
|
||||
client = self._get_client()
|
||||
thread = await client.threads.create()
|
||||
new_thread_id = thread["thread_id"]
|
||||
await self._store_thread_id(msg, new_thread_id)
|
||||
await self._create_thread(client, msg)
|
||||
reply = "New conversation started."
|
||||
elif reply is None and command == "status":
|
||||
thread_id = await self._lookup_thread_id(msg)
|
||||
|
||||
@@ -487,6 +487,7 @@ def _make_mock_langgraph_client(thread_id="test-thread-123", run_result=None):
|
||||
|
||||
# threads.create() returns a Thread-like dict
|
||||
mock_client.threads.create = AsyncMock(return_value={"thread_id": thread_id})
|
||||
mock_client.threads.update = AsyncMock(return_value={"thread_id": thread_id})
|
||||
|
||||
# threads.get() returns thread info (succeeds by default)
|
||||
mock_client.threads.get = AsyncMock(return_value={"thread_id": thread_id})
|
||||
@@ -667,16 +668,34 @@ class TestChannelManager:
|
||||
|
||||
await manager.start()
|
||||
|
||||
inbound = InboundMessage(channel_name="test", chat_id="chat1", user_id="user1", text="hi")
|
||||
inbound = InboundMessage(
|
||||
channel_name="test",
|
||||
chat_id="chat1",
|
||||
user_id="user1",
|
||||
text="hi",
|
||||
topic_id="topic1",
|
||||
thread_ts="msg1",
|
||||
connection_id="conn1",
|
||||
)
|
||||
await bus.publish_inbound(inbound)
|
||||
await _wait_for(lambda: len(outbound_received) >= 1)
|
||||
await manager.stop()
|
||||
|
||||
# Thread should be created through Gateway
|
||||
mock_client.threads.create.assert_called_once()
|
||||
assert mock_client.threads.create.call_args.kwargs["metadata"] == {
|
||||
"channel_source": {
|
||||
"type": "im_channel",
|
||||
"provider": "test",
|
||||
"chat_id": "chat1",
|
||||
"topic_id": "topic1",
|
||||
"thread_ts": "msg1",
|
||||
"connection_id": "conn1",
|
||||
}
|
||||
}
|
||||
|
||||
# Thread ID should be stored
|
||||
thread_id = store.get_thread_id("test", "chat1")
|
||||
thread_id = store.get_thread_id("test", "chat1", topic_id="topic1")
|
||||
assert thread_id == "test-thread-123"
|
||||
|
||||
# runs.wait should be called with the thread_id
|
||||
@@ -2003,6 +2022,17 @@ class TestChannelManager:
|
||||
|
||||
# threads.create should be called only ONCE (second message reuses the thread)
|
||||
mock_client.threads.create.assert_called_once()
|
||||
mock_client.threads.update.assert_called_once_with(
|
||||
"topic-thread-1",
|
||||
metadata={
|
||||
"channel_source": {
|
||||
"type": "im_channel",
|
||||
"provider": "test",
|
||||
"chat_id": "chat1",
|
||||
"topic_id": "topic-root-123",
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
# Both runs.wait calls should use the same thread_id
|
||||
assert mock_client.runs.wait.call_count == 2
|
||||
|
||||
Reference in New Issue
Block a user