agentscope-ai / agentscope-ai/agentscope

[Bug]: DeveloperOrientedException can emit an empty success and leave tool calls unfinished

未关闭
#2,618 1 条评论 0 个 reaction 已指派 0 人 在 GitHub 查看
主要语言
Python
星标
31.6k
派生
3.5k
平均合并
1 天 16 小时
30 天内合并 PR
103

描述

### Prerequisites

- [x] I have searched the existing [issues](https://github.com/agentscope-ai/agentscope/issues) and [discussions](https://github.com/agentscope-ai/agentscope/discussions), and this is not a duplicate.
- [x] This is a bug, not a usage question. (For questions, please use [Discussions](https://github.com/agentscope-ai/agentscope/discussions/new?category=general) instead.)

### Background / Description

DeveloperOrientedException is intentionally fatal: Toolkit.call_tool re-raises it so programming and framework-contract errors reach the developer instead of being converted into agent-facing tool errors.

The fatal path currently violates the tool-call lifecycle in two steps:

1. Toolkit.call_tool yields the accumulated ToolResponse from finally. When the tool raises DeveloperOrientedException, the generator yields an empty ToolResponse(state=SUCCESS) before the exception continues propagating. A middleware that treats ToolResponse as terminal can stop consuming the generator and hide the fatal error.
2. Once the empty-success emission is removed and the exception reaches the Agent, the reply terminates while its ToolCallBlock can remain unfinished. In Agent Service this leaves the session in awaiting_permission / running state even though the reply has failed.

Concurrent tool execution wraps the fatal exception in ExceptionGroup. The service error classifier checks only the outer exception for DeveloperOrientedException, so the Web UI reports an unknown failure instead of a sanitized internal error.

Expected lifecycle:

```text
DeveloperOrientedException
-> no SUCCESS tool response
-> close every unfinished tool call as FINISHED
-> append a matching ToolResultBlock(INTERRUPTED)
-> terminate the reply with ReplyEnd(ERROR, INTERNAL)
-> keep the raw traceback in server logs with session/reply/tool identifiers
```

This does not change recoverable tool-error behavior. Ordinary exceptions must still become ToolResultState.ERROR results that the agent can observe and reason about.

Related work:

- #1888 / #1905 recover orphan tool calls after mid-stream failures. This report adds the concrete DeveloperOrientedException path and the premature empty-success emission in Toolkit.call_tool.
- #1985 discussed recoverable transport/tool failures. This report preserves the fatal semantics of DeveloperOrientedException.

## Proposed contribution

Because the proposed cleanup changes the core Agent._reply_impl failure path, I would appreciate the maintainers' review of the intended lifecycle and scope before opening a pull request. I already have a working local patch and focused regression tests for the Toolkit response boundary, unfinished tool-call cleanup, and ExceptionGroup classification. If this is considered a valid issue and the approach is acceptable, I would be glad to contribute the fix and adjust it based on your feedback.

### Error Messages

```shell
Before removing the finally emission:

ToolResponse(state=success, content=[])

After allowing the fatal exception to propagate, but before lifecycle cleanup:

ExceptionGroup: One or more tool calls raised an exception
+- DeveloperOrientedException: boom: fatal tool failure

Observed service state after the failed reply:

session status: awaiting_permission
tool call state: asking
Web UI: Reply failed - unknown error
```

### Steps to Reproduce

1. Register a deterministic fatal test tool in Agent Service:

```python
from agentscope.exception import DeveloperOrientedException
from agentscope.tool import FunctionTool, ToolChunk

async def boom_tool() -> ToolChunk:
raise DeveloperOrientedException("boom: fatal tool failure")

async def extra_agent_tools(user_id, agent_id, session_id):
del user_id, agent_id, session_id
return [FunctionTool(boom_tool, description="Fatal error reproduction")]
```

2. Pass `extra_agent_tools=extra_agent_tools` to `create_app` and start the bundled Agent Service and Web UI.
3. In a fresh session, ask the agent to call `boom_tool` exactly once.
4. Approve the tool call when the HITL confirmation card appears.
5. Observe the empty successful result on the original code, or the unfinished tool call / `awaiting_permission` state after only moving the final response out of `finally`.

### Environment

- AgentScope Version: 2.0.8, source checkout at b82253ba1b680a67091c4de32f074477c30ba416
- Python Version: 3.11.15
- OS: macOS 26.6.2 arm64

贡献指南

打开贡献指南

调研方向

Start at Toolkit.call_tool and Agent._reply_impl, then inspect the Agent Service error classifier and the focused regression tests mentioned in the report. Trace fatal and ordinary exceptions separately; done means no empty SUCCESS response, unfinished calls close with matching interruption results, ReplyEnd is INTERNAL, and ExceptionGroup classification is sanitized while raw tracebacks remain logged.

由索引模型根据 Issue 内容生成。

评估

技术栈
python
领域
api, backend, testing
Issue 类型
缺陷
难度
4/5
预计耗时
3-5 天
活跃度
活跃
描述清晰度
描述清楚
新手友好度
35/100

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。