mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-10-26 13:16:24 +00:00
fix(startup): detect extras for browser-configured IM channels (#7280)
* fix(startup): detect extras for browser-configured IM channels * docs(startup): clarify channel extra provider source --------- Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
This commit is contained in:
parent
859d45db7e
commit
64bfd9ffd5
@ -1053,6 +1053,12 @@ anonymous `tmp*.tmp` files require manual inspection because their writer cannot
|
|||||||
|
|
||||||
DeerFlow can also expose user-owned IM channel connections in the workspace UI. When `channel_connections` is enabled, logged-in users can bind Telegram, Slack, Discord, Feishu/Lark, DingTalk, WeChat, WeCom, QQ, or Buzz from the sidebar / Settings > Channels. It reuses the existing outbound `channels.*` transports, so no public IP or provider callback URL is required. Incoming IM messages then run under the connected DeerFlow user account. See [IM Channel Connections](backend/docs/IM_CHANNEL_CONNECTIONS.md) for setup and security notes. Buzz backlog paging stops with a warning if timestamp-based queries cannot advance, and channel removal closes both live and backlog subscriptions.
|
DeerFlow can also expose user-owned IM channel connections in the workspace UI. When `channel_connections` is enabled, logged-in users can bind Telegram, Slack, Discord, Feishu/Lark, DingTalk, WeChat, WeCom, QQ, or Buzz from the sidebar / Settings > Channels. It reuses the existing outbound `channels.*` transports, so no public IP or provider callback URL is required. Incoming IM messages then run under the connected DeerFlow user account. See [IM Channel Connections](backend/docs/IM_CHANNEL_CONNECTIONS.md) for setup and security notes. Buzz backlog paging stops with a warning if timestamp-based queries cannot advance, and channel removal closes both live and backlog subscriptions.
|
||||||
|
|
||||||
|
For Discord and Buzz, the local and Docker launchers detect the required optional
|
||||||
|
dependencies when both `channel_connections.enabled` and the corresponding
|
||||||
|
`channel_connections.<provider>.enabled` are `true`. Selecting Discord in
|
||||||
|
`make setup` sets these flags. A nonempty `UV_EXTRAS` overrides configuration
|
||||||
|
auto-detection, so include `discord` or `buzz` there if you set it manually.
|
||||||
|
|
||||||
| Channel | Transport | Difficulty |
|
| Channel | Transport | Difficulty |
|
||||||
|---------|-----------|------------|
|
|---------|-----------|------------|
|
||||||
| Telegram | Bot API (long-polling) | Easy |
|
| Telegram | Bot API (long-polling) | Easy |
|
||||||
|
|||||||
@ -318,6 +318,76 @@ def test_detect_from_config_combines_discord_with_postgres(tmp_path):
|
|||||||
assert detect.detect_from_config(cfg) == ["discord", "postgres"]
|
assert detect.detect_from_config(cfg) == ["discord", "postgres"]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("provider", ["discord", "buzz"])
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("global_enabled", "provider_enabled", "expected"),
|
||||||
|
[("true", "true", True), ("false", "true", False), (None, "true", False), ("true", "false", False), ("true", None, False)],
|
||||||
|
)
|
||||||
|
def test_detect_from_config_channel_connections_requires_both_flags(tmp_path, provider, global_enabled, provider_enabled, expected):
|
||||||
|
cfg = tmp_path / "config.yaml"
|
||||||
|
content = "channel_connections:\n"
|
||||||
|
if global_enabled is not None:
|
||||||
|
content += f" enabled: {global_enabled}\n"
|
||||||
|
content += f" {provider}:\n enabled: {provider_enabled}\n" if provider_enabled is not None else f" {provider}: {{}}\n"
|
||||||
|
cfg.write_text(content, encoding="utf-8")
|
||||||
|
|
||||||
|
assert detect.detect_from_config(cfg) == ([provider] if expected else [])
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("provider", ["discord", "buzz"])
|
||||||
|
@pytest.mark.parametrize("connections_enabled", ["true", "false"])
|
||||||
|
def test_detect_from_config_preserves_and_deduplicates_legacy_channels(tmp_path, provider, connections_enabled):
|
||||||
|
cfg = tmp_path / "config.yaml"
|
||||||
|
cfg.write_text(
|
||||||
|
f"channels:\n {provider}:\n enabled: true\nchannel_connections:\n enabled: {connections_enabled}\n {provider}:\n enabled: true\n",
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
|
||||||
|
assert detect.detect_from_config(cfg) == [provider]
|
||||||
|
|
||||||
|
|
||||||
|
def test_detect_from_config_combines_channel_connections_with_other_extras(tmp_path):
|
||||||
|
cfg = tmp_path / "config.yaml"
|
||||||
|
cfg.write_text(
|
||||||
|
"database:\n backend: postgres\nchannels:\n discord:\n enabled: false\n buzz:\n enabled: false\nchannel_connections:\n enabled: true\n discord:\n enabled: true\n buzz:\n enabled: true\n",
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
|
||||||
|
assert detect.detect_from_config(cfg) == ["buzz", "discord", "postgres"]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("provider", ["discord", "buzz"])
|
||||||
|
def test_detect_from_config_ignores_commented_channel_connections(tmp_path, provider):
|
||||||
|
cfg = tmp_path / "config.yaml"
|
||||||
|
cfg.write_text(
|
||||||
|
f"channel_connections:\n enabled: true\n # {provider}:\n # enabled: true\n",
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
|
||||||
|
assert detect.detect_from_config(cfg) == []
|
||||||
|
|
||||||
|
|
||||||
|
def test_detect_discord_in_setup_wizard_output_without_site_packages(isolated_cwd):
|
||||||
|
from wizard.writer import build_minimal_config
|
||||||
|
|
||||||
|
cfg = isolated_cwd / "config.yaml"
|
||||||
|
cfg.write_text(
|
||||||
|
build_minimal_config(
|
||||||
|
provider_use="langchain_openai:ChatOpenAI",
|
||||||
|
model_name="test-model",
|
||||||
|
display_name="Test model",
|
||||||
|
api_key_field="api_key",
|
||||||
|
env_var=None,
|
||||||
|
channel_connection_providers=["discord"],
|
||||||
|
),
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
|
||||||
|
assert detect.detect_from_config(cfg) == ["discord"]
|
||||||
|
result = subprocess.run([sys.executable, "-S", str(DETECT_SCRIPT_PATH)], capture_output=True, text=True, check=True)
|
||||||
|
assert result.stdout == "--extra discord"
|
||||||
|
|
||||||
|
|
||||||
def test_detect_from_config_memory_stream_bridge_returns_no_extras(tmp_path):
|
def test_detect_from_config_memory_stream_bridge_returns_no_extras(tmp_path):
|
||||||
cfg = tmp_path / "config.yaml"
|
cfg = tmp_path / "config.yaml"
|
||||||
cfg.write_text("stream_bridge:\n type: memory\n queue_maxsize: 256\n")
|
cfg.write_text("stream_bridge:\n type: memory\n queue_maxsize: 256\n")
|
||||||
|
|||||||
@ -140,6 +140,20 @@ def test_no_explicit_extras_uses_the_runtime_selected_config(tmp_path: Path):
|
|||||||
assert proc.stdout.strip() == "--extra browser --extra postgres"
|
assert proc.stdout.strip() == "--extra browser --extra postgres"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("uv_extras, expected", [(None, "--extra buzz --extra discord"), ("postgres", "--extra postgres")])
|
||||||
|
def test_channel_connections_extras_respect_explicit_override(tmp_path: Path, uv_extras, expected):
|
||||||
|
config_path = tmp_path / "deployment.yaml"
|
||||||
|
config_path.write_text(
|
||||||
|
"channel_connections:\n enabled: true\n discord:\n enabled: true\n buzz:\n enabled: true\n",
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
|
||||||
|
proc = _run(uv_extras, config_path=config_path)
|
||||||
|
|
||||||
|
assert proc.returncode == 0, proc.stderr
|
||||||
|
assert proc.stdout.strip() == expected
|
||||||
|
|
||||||
|
|
||||||
def test_single_extra():
|
def test_single_extra():
|
||||||
proc = _run("postgres")
|
proc = _run("postgres")
|
||||||
assert proc.returncode == 0
|
assert proc.returncode == 0
|
||||||
|
|||||||
@ -81,6 +81,15 @@ with or without a leading BOM so the first section remains detectable.
|
|||||||
`DEER_FLOW_PROJECT_ROOT/config.yaml`, before the legacy checkout candidates.
|
`DEER_FLOW_PROJECT_ROOT/config.yaml`, before the legacy checkout candidates.
|
||||||
Keep this lookup standard-library-only; test shadowing checkout configs and
|
Keep this lookup standard-library-only; test shadowing checkout configs and
|
||||||
the CLI under `python -S` in `backend/tests/test_detect_uv_extras.py`.
|
the CLI under `python -S` in `backend/tests/test_detect_uv_extras.py`.
|
||||||
|
For Discord and Buzz, detect the provider extra when either legacy
|
||||||
|
`channels.<provider>.enabled` is true or both `channel_connections.enabled`
|
||||||
|
and `channel_connections.<provider>.enabled` are true. Keep these gates aligned
|
||||||
|
with `enabled_transport_providers()` in the channel service, including setup
|
||||||
|
wizard output. The provider set includes only channel providers declared in
|
||||||
|
`backend/pyproject.toml` under `[project.optional-dependencies]`; update it when
|
||||||
|
a channel gains or loses an extra. Providers whose SDKs are core dependencies
|
||||||
|
must not be added to this set. Nonempty `UV_EXTRAS` continues to override
|
||||||
|
config-derived extras.
|
||||||
`serve.sh`'s config-presence gate also accepts the project-root file when the
|
`serve.sh`'s config-presence gate also accepts the project-root file when the
|
||||||
checkout has no config. `tests/test_serve_config_presence.py` runs the full
|
checkout has no config. `tests/test_serve_config_presence.py` runs the full
|
||||||
launcher through dependency detection with service operations stubbed.
|
launcher through dependency detection with service operations stubbed.
|
||||||
|
|||||||
@ -15,6 +15,8 @@ Order of resolution:
|
|||||||
- sandbox.ownership.type == redis -> redis
|
- sandbox.ownership.type == redis -> redis
|
||||||
- channels.discord.enabled == true -> discord
|
- channels.discord.enabled == true -> discord
|
||||||
- channels.buzz.enabled == true -> buzz
|
- channels.buzz.enabled == true -> buzz
|
||||||
|
- channel_connections.enabled and channel_connections.discord.enabled -> discord
|
||||||
|
- channel_connections.enabled and channel_connections.buzz.enabled -> buzz
|
||||||
- models[].use == langchain_ollama:* -> ollama
|
- models[].use == langchain_ollama:* -> ollama
|
||||||
3. Runtime environment toggles that enable optional backends:
|
3. Runtime environment toggles that enable optional backends:
|
||||||
- DEER_FLOW_STREAM_BRIDGE_REDIS_URL -> redis
|
- DEER_FLOW_STREAM_BRIDGE_REDIS_URL -> redis
|
||||||
@ -363,10 +365,13 @@ def detect_from_config(path: Path) -> list[str]:
|
|||||||
extras.add("redis")
|
extras.add("redis")
|
||||||
if (nested_section_value(lines, "sandbox.ownership", "type") or "").lower() == "redis":
|
if (nested_section_value(lines, "sandbox.ownership", "type") or "").lower() == "redis":
|
||||||
extras.add("redis")
|
extras.add("redis")
|
||||||
if (nested_section_value(lines, "channels.discord", "enabled") or "").lower() == "true":
|
connections_enabled = (section_value(lines, "channel_connections", "enabled") or "").lower() == "true"
|
||||||
extras.add("discord")
|
# Only channel providers with extras in backend/pyproject.toml [project.optional-dependencies] belong here; keep this set in sync.
|
||||||
if (nested_section_value(lines, "channels.buzz", "enabled") or "").lower() == "true":
|
for provider in ("discord", "buzz"):
|
||||||
extras.add("buzz")
|
legacy_enabled = (nested_section_value(lines, f"channels.{provider}", "enabled") or "").lower() == "true"
|
||||||
|
browser_enabled = connections_enabled and (nested_section_value(lines, f"channel_connections.{provider}", "enabled") or "").lower() == "true"
|
||||||
|
if legacy_enabled or browser_enabled:
|
||||||
|
extras.add(provider)
|
||||||
if tools_include_name(lines, "browser_navigate"):
|
if tools_include_name(lines, "browser_navigate"):
|
||||||
extras.add("browser")
|
extras.add("browser")
|
||||||
for provider in models_use_providers(lines):
|
for provider in models_use_providers(lines):
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user