fix: align deerflow runner with deerflow 2.0#7500
fix: align deerflow runner with deerflow 2.0#7500zouyonghe merged 2 commits intoAstrBotDevs:masterfrom
Conversation
There was a problem hiding this comment.
Code Review
This pull request updates the DeerFlow Agent Runner to support DeerFlow 2.0. Key changes include refactoring the API payload to use the config.configurable structure (while maintaining legacy context support) and implementing remote thread cleanup when resetting or deleting conversations. The API client now includes a delete_thread method, and documentation and configuration hints have been updated accordingly. Feedback was provided regarding a potential URL inconsistency in the new thread deletion endpoint to ensure it matches the existing API patterns.
|
|
||
| async def delete_thread(self, thread_id: str, timeout: float = 20) -> None: | ||
| session = self._get_session() | ||
| url = f"{self.api_base}/api/threads/{thread_id}" |
There was a problem hiding this comment.
The URL for deleting a thread seems inconsistent with other LangGraph API endpoints in this client. Existing methods like create_thread (line 144) and stream_run (line 182) use the /api/langgraph/threads prefix. It is highly likely that delete_thread should also include the /langgraph segment to correctly target the DeerFlow/LangGraph backend.
| url = f"{self.api_base}/api/threads/{thread_id}" | |
| url = f"{self.api_base}/api/langgraph/threads/{thread_id}" |
There was a problem hiding this comment.
Hey - I've found 2 issues, and left some high level feedback:
- The DeerFlow provider config lookup logic in
_cleanup_deerflow_thread_if_presentmanually iteratesproviders_configand then callsget_merged_provider_config; consider encapsulating this into a reusable helper (e.g.,get_provider_config_by_id) onprovider_managerto avoid duplication and reduce the chance of inconsistent behavior if the lookup logic evolves. - In
DeerFlowAPIClient.delete_thread, a genericExceptionis raised for non-success statuses; using a more specific custom exception type (similar to other client errors in this module, if present) would make it easier for callers to distinguish API failures from other runtime issues and handle them appropriately.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The DeerFlow provider config lookup logic in `_cleanup_deerflow_thread_if_present` manually iterates `providers_config` and then calls `get_merged_provider_config`; consider encapsulating this into a reusable helper (e.g., `get_provider_config_by_id`) on `provider_manager` to avoid duplication and reduce the chance of inconsistent behavior if the lookup logic evolves.
- In `DeerFlowAPIClient.delete_thread`, a generic `Exception` is raised for non-success statuses; using a more specific custom exception type (similar to other client errors in this module, if present) would make it easier for callers to distinguish API failures from other runtime issues and handle them appropriately.
## Individual Comments
### Comment 1
<location path="astrbot/builtin_stars/builtin_commands/commands/conversation.py" line_range="27-36" />
<code_context>
+async def _cleanup_deerflow_thread_if_present(
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Consider ensuring that failures during provider resolution or client instantiation do not prevent subsequent state cleanup.
In `_cleanup_deerflow_thread_if_present`, any exception before the `try` (e.g., during `context.get_config`, provider resolution, or `DeerFlowAPIClient` construction) will propagate and, in `_clear_third_party_agent_runner_state`, can prevent `sp.remove_async` from running, leaving the session key behind. If local cleanup must always run, consider wrapping the entire body of `_cleanup_deerflow_thread_if_present` (or the call site) in `try/except` so remote failures never block local state deletion.
Suggested implementation:
```python
import logging
from astrbot.core.agent.runners.deerflow.deerflow_api_client import DeerFlowAPIClient
from astrbot.core.platform.astr_message_event import MessageSession
from astrbot.core.platform.message_type import MessageType
from astrbot.core.utils.active_event_registry import active_event_registry
logger = logging.getLogger(__name__)
THIRD_PARTY_AGENT_RUNNER_STR = ", ".join(THIRD_PARTY_AGENT_RUNNER_KEY.keys())
```
```python
async def _cleanup_deerflow_thread_if_present(
context: star.Context,
umo: str,
) -> None:
try:
thread_id = await sp.get_async(
scope="umo",
scope_id=umo,
key=DEERFLOW_THREAD_ID_KEY,
default="",
)
if not thread_id:
```
To complete the change, you should:
1. Indent the *rest of the body* of `_cleanup_deerflow_thread_if_present` by one level so it is inside the `try:` block (all provider resolution, `DeerFlowAPIClient` construction, and remote cleanup calls should be within the `try`).
2. Add a matching `except` block at the *end* of `_cleanup_deerflow_thread_if_present`:
```python
except Exception:
# Log and swallow the error so local state cleanup (e.g., sp.remove_async in
# _clear_third_party_agent_runner_state) is never blocked by remote failures.
logger.exception(
"Failed to clean up DeerFlow thread for umo=%s; "
"ignoring error to allow local state cleanup",
umo,
)
```
3. Ensure `_cleanup_deerflow_thread_if_present` does not re-raise any exceptions so that `_clear_third_party_agent_runner_state` (or any caller that performs `sp.remove_async`) always runs its local state cleanup even if remote cleanup fails.
</issue_to_address>
### Comment 2
<location path="astrbot/core/agent/runners/deerflow/deerflow_api_client.py" line_range="160-169" />
<code_context>
)
return await resp.json()
+ async def delete_thread(self, thread_id: str, timeout: float = 20) -> None:
+ session = self._get_session()
+ url = f"{self.api_base}/api/threads/{thread_id}"
+ async with session.delete(
+ url,
+ headers=self.headers,
+ timeout=timeout,
+ proxy=self.proxy,
+ ) as resp:
+ if resp.status not in (200, 202, 204, 404):
+ text = await resp.text()
+ raise Exception(
+ f"DeerFlow delete thread failed: {resp.status}. {text}",
+ )
+
</code_context>
<issue_to_address>
**suggestion:** The error raised by `delete_thread` could include more context such as the `thread_id`.
Right now the raised `Exception` only includes the HTTP status and body. Please add the `thread_id` (and optionally the `url`) to the message to make debugging easier when dealing with multiple concurrent threads and aggregated logs.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
astrbot/builtin_stars/builtin_commands/commands/conversation.py
Outdated
Show resolved
Hide resolved
946a6cb to
9503597
Compare
|
@sourcery-ai review |
Summary
This PR aligns AstrBot's DeerFlow integration with the current DeerFlow 2.0 runtime contract on the
mainbranch.Previously AstrBot only sent runtime overrides through the legacy top-level
contextfield and only cleared its local session marker when users reset or recreated a DeerFlow conversation. That still worked against DeerFlow's compatibility layer, but it diverged from the 2.0 contract and could leave remote DeerFlow threads, uploads, and artifacts behind after/reset,/new, and/del.This change updates the DeerFlow runner to send runtime overrides through
config.configurablewhile preserving the legacycontextmirror for compatibility. It also adds thread deletion support in the DeerFlow API client and uses it when AstrBot clears DeerFlow-backed conversations, falling back to local cleanup if the gateway is unavailable. The user-facing config hints and docs are updated to describe DeerFlow 2.0 semantics, and regression tests cover both the new payload shape and the remote cleanup behavior.Testing
uv run ruff format .uv run ruff check .uv run pytest tests/test_deerflow_agent_runner.py tests/test_conversation_commands.py -qSummary by Sourcery
Align DeerFlow agent runner and conversation handling with DeerFlow 2.0 runtime semantics and remote thread lifecycle management.
New Features:
Bug Fixes:
Enhancements:
Documentation:
Tests: