mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-09-11 14:38:38 +00:00
* feat(mcp): add durable task runtime foundation * fix(chart): sync embedded config version * fix(mcp): isolate task polls during shutdown * feat(mcp): track consecutive poll errors on mcp_tasks poll_attempt_count grows on every claim (successful polls included), so it cannot drive a failure backoff without misjudging normal long tasks. Add consecutive_poll_error_count: incremented when a claim is released after a poll error, reset to zero by any applied snapshot. The backoff/terminal policy that consumes it lands with the first concrete driver. * fix(mcp): harden durable task lifecycle * feat(mcp): add ordinary durable task driver * test(mcp): address durable task review feedback * fix(mcp): preserve submit tool descriptions * fix(mcp): bound remote task calls * fix(mcp): bound persisted task payloads * fix(mcp): preserve task tool error details * fix(mcp): enforce durable task boundaries * test(mcp): cover task config snapshot lifecycle --------- Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
134 lines
4.5 KiB
Python
134 lines
4.5 KiB
Python
import pytest
|
|
from pydantic import ValidationError
|
|
|
|
from app.gateway.routers.mcp import McpServerConfigResponse
|
|
from deerflow.config.extensions_config import ExtensionsConfig
|
|
|
|
|
|
def test_task_toolsets_preserve_raw_tool_names_and_support_multiple_groups() -> None:
|
|
config = ExtensionsConfig.model_validate(
|
|
{
|
|
"mcpServers": {
|
|
"reports": {
|
|
"type": "http",
|
|
"url": "https://example.test/mcp",
|
|
"task_toolsets": [
|
|
{
|
|
"name": "report-generation",
|
|
"submit_tool": "submit_report",
|
|
"status_tool": "get_report_status",
|
|
"cancel_tool": "cancel_report",
|
|
},
|
|
{
|
|
"name": "data-export",
|
|
"submit_tool": "start_export",
|
|
"status_tool": "get_export_status",
|
|
"cancel_tool": "cancel_export",
|
|
},
|
|
],
|
|
}
|
|
}
|
|
}
|
|
)
|
|
|
|
toolsets = config.mcp_servers["reports"].task_toolsets
|
|
assert [toolset.name for toolset in toolsets] == ["report-generation", "data-export"]
|
|
assert toolsets[0].model_dump() == {
|
|
"name": "report-generation",
|
|
"submit_tool": "submit_report",
|
|
"status_tool": "get_report_status",
|
|
"cancel_tool": "cancel_report",
|
|
}
|
|
response = McpServerConfigResponse.model_validate(config.mcp_servers["reports"].model_dump())
|
|
assert response.task_toolsets[0].submit_tool == "submit_report"
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"duplicate_field,duplicate_value",
|
|
[
|
|
("status_tool", "submit_report"),
|
|
("cancel_tool", "submit_report"),
|
|
],
|
|
)
|
|
def test_task_toolsets_reject_reusing_one_raw_tool_in_multiple_roles(
|
|
duplicate_field: str,
|
|
duplicate_value: str,
|
|
) -> None:
|
|
toolset = {
|
|
"name": "report-generation",
|
|
"submit_tool": "submit_report",
|
|
"status_tool": "get_report_status",
|
|
"cancel_tool": "cancel_report",
|
|
}
|
|
toolset[duplicate_field] = duplicate_value
|
|
|
|
with pytest.raises(ValidationError, match="must be unique across task_toolsets"):
|
|
ExtensionsConfig.model_validate(
|
|
{
|
|
"mcpServers": {
|
|
"reports": {
|
|
"task_toolsets": [toolset],
|
|
}
|
|
}
|
|
}
|
|
)
|
|
|
|
|
|
def test_task_toolsets_reject_reusing_one_raw_tool_across_groups() -> None:
|
|
with pytest.raises(ValidationError, match="submit_report.*must be unique"):
|
|
ExtensionsConfig.model_validate(
|
|
{
|
|
"mcpServers": {
|
|
"reports": {
|
|
"task_toolsets": [
|
|
{
|
|
"name": "first",
|
|
"submit_tool": "submit_report",
|
|
"status_tool": "status_report",
|
|
"cancel_tool": "cancel_report",
|
|
},
|
|
{
|
|
"name": "second",
|
|
"submit_tool": "start_export",
|
|
"status_tool": "submit_report",
|
|
"cancel_tool": "cancel_export",
|
|
},
|
|
]
|
|
}
|
|
}
|
|
}
|
|
)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("server_name", "task_name", "match"),
|
|
[
|
|
(" ", "reports", "server name.*128"),
|
|
("s" * 129, "reports", "server name.*128"),
|
|
("reports", " ", "task toolset name must not be empty"),
|
|
("reports", "t" * 256, "at most 255"),
|
|
],
|
|
)
|
|
def test_task_toolsets_reject_names_that_do_not_fit_durable_storage(
|
|
server_name: str,
|
|
task_name: str,
|
|
match: str,
|
|
) -> None:
|
|
with pytest.raises(ValidationError, match=match):
|
|
ExtensionsConfig.model_validate(
|
|
{
|
|
"mcpServers": {
|
|
server_name: {
|
|
"task_toolsets": [
|
|
{
|
|
"name": task_name,
|
|
"submit_tool": "submit_report",
|
|
"status_tool": "status_report",
|
|
"cancel_tool": "cancel_report",
|
|
}
|
|
]
|
|
}
|
|
}
|
|
}
|
|
)
|