mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-09-25 14:06:18 +00:00
76 lines
3.7 KiB
Python
76 lines
3.7 KiB
Python
"""Opt-in verification of the same managed DeepSeek path before/after a fix.
|
|
|
|
From backend/ (test credentials are read only from the process environment):
|
|
DEER_FLOW_RUN_LIVE_TESTS=1 uv run --no-sync pytest \
|
|
tests/test_managed_deepseek_live.py -q -s
|
|
|
|
Set DEEPSEEK_TEST_API_KEY separately; optional DEEPSEEK_TEST_MODEL defaults to
|
|
"deepseek-flash". This sends a few short requests to api.deepseek.com and may
|
|
incur charges. It never starts an agent or modifies the real managed catalog.
|
|
"""
|
|
|
|
import os
|
|
from types import SimpleNamespace
|
|
|
|
import pytest
|
|
from langchain_core.messages import HumanMessage, ToolMessage
|
|
|
|
from app.gateway.routers import managed_models as router
|
|
from deerflow.config.app_config import AppConfig
|
|
from deerflow.config.managed_models import ManagedModel
|
|
from deerflow.models.factory import create_chat_model
|
|
|
|
pytestmark = [
|
|
pytest.mark.live,
|
|
pytest.mark.skipif(
|
|
os.getenv("DEER_FLOW_RUN_LIVE_TESTS") != "1" or not os.getenv("DEEPSEEK_TEST_API_KEY") or bool(os.getenv("CI")),
|
|
reason="Requires explicit live opt-in and DEEPSEEK_TEST_API_KEY; never runs in CI",
|
|
),
|
|
]
|
|
|
|
|
|
@pytest.fixture
|
|
def profile(tmp_path, monkeypatch):
|
|
monkeypatch.setenv("DEER_FLOW_HOME", str(tmp_path))
|
|
monkeypatch.setenv("LANGSMITH_TRACING", "false")
|
|
monkeypatch.setenv("LANGCHAIN_TRACING_V2", "false")
|
|
return ManagedModel(name="deepseek-live", model=os.getenv("DEEPSEEK_TEST_MODEL", "deepseek-flash"), base_url="https://api.deepseek.com", api_key=os.environ["DEEPSEEK_TEST_API_KEY"], max_tokens=512)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_managed_deepseek_connection_probe_live(profile, tmp_path):
|
|
request = SimpleNamespace(state=SimpleNamespace(user=SimpleNamespace(system_role="admin")))
|
|
result = await router.test_model(request, router.SaveModelRequest(config=profile))
|
|
assert result == {"ok": True, "message": "success"}
|
|
assert not (tmp_path / "managed-models").exists()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@pytest.mark.parametrize("thinking", [False, True])
|
|
async def test_managed_deepseek_tool_round_trip_live(profile, thinking, tmp_path):
|
|
config = AppConfig.model_validate({"sandbox": {"use": "test"}, "models": [profile.runtime_config().model_dump(exclude_none=True)]})
|
|
model = create_chat_model(profile.name, thinking_enabled=thinking, app_config=config, attach_tracing=False, timeout=30, max_retries=0)
|
|
tool = {"type": "function", "function": {"name": "connection_check", "description": "Check the connection", "parameters": {"type": "object", "properties": {}}}}
|
|
bound = model.bind_tools([tool])
|
|
history = [HumanMessage(content="Call connection_check exactly once. After its result, reply only OK.")]
|
|
first = None
|
|
async for chunk in bound.astream(history, config={"callbacks": []}):
|
|
first = chunk if first is None else first + chunk
|
|
assert first is not None and first.tool_calls
|
|
history.append(first)
|
|
history.extend(ToolMessage(content="Connection is healthy.", tool_call_id=call["id"]) for call in first.tool_calls)
|
|
payload = model._get_request_payload(history, **bound.kwargs)
|
|
assert payload["max_tokens"] == 512
|
|
assert "max_completion_tokens" not in payload
|
|
assert payload["extra_body"]["thinking"]["type"] == ("enabled" if thinking else "disabled")
|
|
assistant = payload["messages"][1]
|
|
assert assistant["content"] is not None
|
|
if thinking:
|
|
# Reasoning can be empty. Verify faithful replay, not model verbosity.
|
|
assert assistant["reasoning_content"] == first.additional_kwargs.get("reasoning_content", "")
|
|
final = None
|
|
async for chunk in bound.astream(history, config={"callbacks": []}):
|
|
final = chunk if final is None else final + chunk
|
|
assert final is not None and final.content and not final.tool_calls
|
|
assert not (tmp_path / "managed-models").exists()
|