mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-05-24 08:55:59 +00:00
refactor(runtime): restructure runs module with new execution architecture
Major refactoring of deerflow/runtime/: - runs/callbacks/ - new callback system (builder, events, title, tokens) - runs/internal/ - execution internals (executor, supervisor, stream_logic, registry) - runs/internal/execution/ - execution artifacts and events handling - runs/facade.py - high-level run facade - runs/observer.py - run observation protocol - runs/types.py - type definitions - runs/store/ - simplified store interfaces (create, delete, query, event) Refactor stream_bridge/: - Replace old providers with contract.py and exceptions.py - Remove async_provider.py, base.py, memory.py Add documentation: - README.md and README_zh.md for runtime module Remove deprecated: - manager.py moved to internal/ - worker.py, schemas.py - user_context.py Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,138 @@
|
||||
"""Callback assembly for runs execution."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Iterable
|
||||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
|
||||
from langchain_core.callbacks import BaseCallbackHandler
|
||||
|
||||
from ..store import RunEventStore
|
||||
from ..types import RunRecord
|
||||
from .events import RunEventCallback
|
||||
from .title import RunTitleCallback
|
||||
from .tokens import RunCompletionData, RunTokenCallback
|
||||
|
||||
|
||||
@dataclass
|
||||
class RunCallbackArtifacts:
|
||||
"""Callbacks plus handles used by the executor after callbacks run."""
|
||||
|
||||
callbacks: list[BaseCallbackHandler]
|
||||
event_callback: RunEventCallback | None = None
|
||||
token_callback: RunTokenCallback | None = None
|
||||
title_callback: RunTitleCallback | None = None
|
||||
|
||||
async def flush(self) -> None:
|
||||
for callback in self.callbacks:
|
||||
flush = getattr(callback, "flush", None)
|
||||
if flush is None:
|
||||
continue
|
||||
result = flush()
|
||||
if hasattr(result, "__await__"):
|
||||
await result
|
||||
|
||||
def completion_data(self) -> RunCompletionData:
|
||||
if self.token_callback is None:
|
||||
return RunCompletionData()
|
||||
return self.token_callback.completion_data()
|
||||
|
||||
def title(self) -> str | None:
|
||||
if self.title_callback is None:
|
||||
return None
|
||||
return self.title_callback.title()
|
||||
|
||||
|
||||
def build_run_callbacks(
|
||||
*,
|
||||
record: RunRecord,
|
||||
graph_input: dict[str, Any],
|
||||
event_store: RunEventStore | None,
|
||||
existing_callbacks: Iterable[BaseCallbackHandler] = (),
|
||||
) -> RunCallbackArtifacts:
|
||||
"""Build execution callbacks for a run.
|
||||
|
||||
Reference callbacks are intentionally not assembled here yet; they remain
|
||||
in the existing artifacts path until that integration is migrated.
|
||||
"""
|
||||
callbacks = list(existing_callbacks)
|
||||
|
||||
event_callback = None
|
||||
if event_store is not None:
|
||||
event_callback = RunEventCallback(
|
||||
run_id=record.run_id,
|
||||
thread_id=record.thread_id,
|
||||
event_store=event_store,
|
||||
)
|
||||
callbacks.append(event_callback)
|
||||
|
||||
token_callback = RunTokenCallback(track_token_usage=True)
|
||||
_set_first_human_message(token_callback, graph_input)
|
||||
callbacks.append(token_callback)
|
||||
|
||||
title_callback = RunTitleCallback()
|
||||
callbacks.append(title_callback)
|
||||
|
||||
return RunCallbackArtifacts(
|
||||
callbacks=callbacks,
|
||||
event_callback=event_callback,
|
||||
token_callback=token_callback,
|
||||
title_callback=title_callback,
|
||||
)
|
||||
|
||||
|
||||
def _set_first_human_message(token_callback: RunTokenCallback, graph_input: dict[str, Any]) -> None:
|
||||
messages = graph_input.get("messages")
|
||||
if not isinstance(messages, list) or not messages:
|
||||
return
|
||||
|
||||
first = messages[0]
|
||||
content = _extract_first_human_text(first)
|
||||
if content:
|
||||
token_callback.set_first_human_message(content)
|
||||
|
||||
|
||||
def _extract_first_human_text(message: Any) -> str | None:
|
||||
if isinstance(message, str):
|
||||
return message
|
||||
|
||||
content = getattr(message, "content", None)
|
||||
if content is not None:
|
||||
return _extract_text_content(content)
|
||||
|
||||
if isinstance(message, dict):
|
||||
return _extract_text_content(message.get("content"))
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def _extract_text_content(content: Any) -> str | None:
|
||||
if isinstance(content, str):
|
||||
return content
|
||||
|
||||
if isinstance(content, list):
|
||||
parts: list[str] = []
|
||||
for item in content:
|
||||
if isinstance(item, str):
|
||||
parts.append(item)
|
||||
continue
|
||||
if not isinstance(item, dict):
|
||||
continue
|
||||
if item.get("type") == "text" and isinstance(item.get("text"), str):
|
||||
parts.append(item["text"])
|
||||
continue
|
||||
if isinstance(item.get("content"), str):
|
||||
parts.append(item["content"])
|
||||
joined = "".join(parts).strip()
|
||||
return joined or None
|
||||
|
||||
if isinstance(content, dict):
|
||||
text = content.get("text")
|
||||
if isinstance(text, str):
|
||||
return text
|
||||
nested = content.get("content")
|
||||
if isinstance(nested, str):
|
||||
return nested
|
||||
|
||||
return None
|
||||
Reference in New Issue
Block a user