agentscope-ai / agentscope-ai/agentscope
Feature request: extensible agent assembly hook in ChatService
- Vorherrschende Sprache
- Python
- Sterne
- 31.5k
- Forks
- 3.5k
- Ø Merge
- 1 T. 23 Std.
- Gemergte PRs (30 T.)
- 95
Beschreibung
## Problem
Currently, `ChatService.stream_chat` calls the module-level `get_agent` function directly:
```python
# agentscope/app/_service/_chat.py
from ._agent import get_agent
class ChatService:
async def stream_chat(self, ...):
agent = await get_agent(
storage=self._storage,
workspace_manager=self._workspace_manager,
...
)
```
Because `get_agent` is a free function (not a method on `ChatService`), there is no clean extension point for downstream applications that need to:
1. **Inject additional tools** after agent assembly (e.g. tools that require `model` or `session_id` context not available at the `Workspace.list_tools()` level)
2. **Attach middlewares** to the assembled agent (e.g. dynamic system prompt injection based on per-session state)
3. **Replace `ChatService` entirely** with a subclass to handle frontend interaction differently, without duplicating the full `stream_chat` implementation
## Current workaround
The only working approach is monkey-patching the module-level reference:
```python
import agentscope.app._service._chat as _chat_module
async def _patched_get_agent(storage, workspace_manager, user_id, agent_id, session_id, middlewares=None):
agent = await _orig(storage, workspace_manager, user_id, agent_id, session_id, middlewares)
# inject tools, middlewares, MCPs here
return agent
_chat_module.get_agent = _patched_get_agent
```
This works but is fragile — it depends on the internal module structure and breaks IDE navigation, type checking, and testability.
## Proposed solutions
Any of the following would resolve the issue:
### Option A: Extract `_build_agent` as an overridable method on `ChatService`
```python
class ChatService:
async def _build_agent(self, user_id, agent_id, session_id, middlewares):
return await get_agent(
storage=self._storage,
workspace_manager=self._workspace_manager,
user_id=user_id,
agent_id=agent_id,
session_id=session_id,
middlewares=middlewares,
)
async def stream_chat(self, ...):
agent = await self._build_agent(user_id, agent_id, session_id, middlewares)
```
Subclasses can then override `_build_agent` cleanly.
### Option B: Injectable `ChatService` factory in the chat router
```python
# Allow apps to register a custom ChatService class
app.state.chat_service_class = VulnDigChatService
```
### Option C: `on_agent_created` callback on `WorkspaceManagerBase`
```python
class WorkspaceManagerBase:
async def on_agent_created(self, agent: Agent, user_id: str, session_id: str) -> Agent:
return agent # default: no-op
```
Called by `get_agent` after assembly, giving workspace managers a post-assembly hook.
---
Option A is the smallest change and requires no new abstractions — it simply makes the existing `get_agent` call overridable by subclasses.
## Context
This came up while building a production application on top of agentscope v2 that needs per-session tool injection (workdir-scoped bash, memory tools, spawn-agent delegation). The `Workspace.list_tools()` hook handles most cases but cannot inject tools that depend on the assembled agent's `model` instance.
Beitragsleitfaden
Bewertung
Dieses Issue wurde noch nicht bewertet.