-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
feat:增加用户在webui创建和配置subagent的功能 #4697
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey - 我发现了 3 个问题,并给出了一些整体性的反馈:
dashboard/src/main.ts里的全局window.fetch包装器在第一个参数是Request对象时会丢失method、body和其他Request属性;建议检测参数是否为Request,如果是则原样转发(只克隆/增强 headers),以避免一些隐蔽的破坏性行为。- 在
SubAgentOrchestrator.reload_from_config中,通过func_list和remove_func手动移除/添加工具绕过了FunctionToolManager的正常注册不变式;更安全的做法是暴露并使用专门的 API 来注册HandoffTool实例,从而保证它们仍然可被发现,并与其它工具保持一致。 - 在
SubAgentRoute.update_config中保存子代理配置时,目前没有对 agent 名称做校验(例如非空、唯一性、允许字符集),这可能导致transfer_to_*工具名冲突或生成非法标识符;添加基本的服务端校验会让编排逻辑更健壮。
给 AI 代理的 Prompt
Please address the comments from this code review:
## Overall Comments
- `dashboard/src/main.ts` 里的全局 `window.fetch` 包装器在第一个参数是 `Request` 对象时会丢失 `method`、`body` 和其他 `Request` 属性;建议检测参数是否为 `Request`,如果是则原样转发(只克隆/增强 headers),以避免一些隐蔽的破坏性行为。
- 在 `SubAgentOrchestrator.reload_from_config` 中,通过 `func_list` 和 `remove_func` 手动移除/添加工具绕过了 `FunctionToolManager` 的正常注册不变式;更安全的做法是暴露并使用专门的 API 来注册 `HandoffTool` 实例,从而保证它们仍然可被发现,并与其它工具保持一致。
- 在 `SubAgentRoute.update_config` 中保存子代理配置时,目前没有对 agent 名称做校验(例如非空、唯一性、允许字符集),这可能导致 `transfer_to_*` 工具名冲突或生成非法标识符;添加基本的服务端校验会让编排逻辑更健壮。
## Individual Comments
### Comment 1
<location> `astrbot/dashboard/server.py:135-136` </location>
<code_context>
r.status_code = 401
return r
- token = token.removeprefix("Bearer ")
+ # Be tolerant of different header casing / formatting.
+ token = token.strip().removeprefix("Bearer ").strip()
try:
payload = jwt.decode(token, self._jwt_secret, algorithms=["HS256"])
</code_context>
<issue_to_address>
**suggestion:** 尽管注释里说明要兼容不同的 header 大小写,但当前 JWT 提取对 `Bearer` 前缀仍然是大小写敏感的。
代码只会移除精确匹配的 "Bearer " 前缀,因此像 `authorization: bearer <token>` 或 `Authorization: BEARER <token>` 这样的 header 会失败,这与注释不符。建议在去前缀前先做归一化,例如:
```py
raw = token.strip()
if raw.lower().startswith("bearer "):
raw = raw[7:]
token = raw.strip()
```
这样与注释中宣称的“对 header 大小写/格式更宽容”是一致的,也能避免来自非标准客户端的意外认证失败。
```suggestion
# Be tolerant of different header casing / formatting.
raw = token.strip()
if raw.lower().startswith("bearer "):
raw = raw[7:]
token = raw.strip()
```
</issue_to_address>
### Comment 2
<location> `astrbot/dashboard/routes/subagent.py:93-105` </location>
<code_context>
+ UI can use this to build a multi-select list for subagent tool assignment.
+ """
+ try:
+ tool_mgr = self.core_lifecycle.provider_manager.llm_tools
+ tools_dict = []
+ for tool in tool_mgr.func_list:
+ tools_dict.append(
+ {
+ "name": tool.name,
+ "description": tool.description,
+ "parameters": tool.parameters,
+ "active": tool.active,
+ "handler_module_path": tool.handler_module_path,
+ }
+ )
</code_context>
<issue_to_address>
**suggestion (bug_risk):** available-tools 接口会暴露 handoff 工具,使得子代理可以选择 `transfer_to_*` 工具,从而创建递归路由。
由于该接口返回 `func_list` 中的所有条目,它也会暴露 `HandoffTool` 实例(即 `transfer_to_*` 工具)。这让 UI 能够把 handoff 工具分配为子代理工具,从而可能形成令人困惑或递归的委托循环。为避免这种情况,请过滤掉这些工具——例如跳过 `handler_module_path == "core.subagent_orchestrator"` 的工具,或者如果可以的话,排除 `isinstance(tool, HandoffTool)` 的工具。
```suggestion
try:
tool_mgr = self.core_lifecycle.provider_manager.llm_tools
tools_dict = []
for tool in tool_mgr.func_list:
# Skip handoff tools (e.g., transfer_to_*), which live in the subagent orchestrator.
# These should not be assignable as subagent tools to avoid recursive delegation.
if getattr(tool, "handler_module_path", None) == "core.subagent_orchestrator":
continue
tools_dict.append(
{
"name": tool.name,
"description": tool.description,
"parameters": tool.parameters,
"active": tool.active,
"handler_module_path": tool.handler_module_path,
}
)
```
</issue_to_address>
### Comment 3
<location> `astrbot/core/subagent_orchestrator.py:37` </location>
<code_context>
+ self._tool_mgr = tool_mgr
+ self._registered_handoff_names: set[str] = set()
+
+ def reload_from_config(self, provider_settings: dict[str, Any]) -> None:
+ cfg = provider_settings.get("subagent_orchestrator", {})
+ enabled = bool(cfg.get("main_enable", False))
</code_context>
<issue_to_address>
**issue (complexity):** 建议重构 orchestrator,将工具注册/移除的职责委托给 `FunctionToolManager`,并通过 `SubAgentConfig` 统一子代理配置的解析逻辑,从而让 `reload_from_config` 更加简洁、聚焦。
可以通过加强与 `FunctionToolManager` 的交互,并真正把 `SubAgentConfig` 作为标准化后的配置类型来使用,从而显著简化当前实现。
### 1) 去掉 add/remove/append 的“舞蹈”
与其这样:
```python
self._tool_mgr.add_func(
name=handoff.name,
func_args=[...],
desc=handoff.description,
handler=handoff.handler,
)
self._tool_mgr.remove_func(handoff.name)
self._tool_mgr.func_list.append(handoff)
```
不如在 `FunctionToolManager` 中增加一个接受预构造工具并且负责替换的轻量 API:
```python
# in func_tool_manager.py
class FunctionToolManager:
...
def add_tool(self, tool: FunctionTool) -> None:
"""Register a pre-constructed tool, replacing by name if it exists."""
# remove any existing tool with same name
self.func_list = [t for t in self.func_list if t.name != tool.name]
self.func_list.append(tool)
```
这样你的 orchestrator 就变成单一、清晰的注册路径:
```python
# in SubAgentOrchestrator.reload_from_config
handoff = HandoffTool(agent=agent, description=public_description or None)
handoff.provider_id = provider_id
handoff.handler_module_path = "core.subagent_orchestrator"
self._tool_mgr.add_tool(handoff)
self._registered_handoff_names.add(handoff.name)
```
既保留了原有行为,又避免了对 `func_list` 的直接操作,以及“先 `add_func` 再立刻撤销”的模式。
### 2) 尽量避免手动状态跟踪
如果可以在工具对象中编码来源信息(你已经设置了 `handler_module_path`),就可以把移除逻辑集中到 manager 里,从而去掉 `_registered_handoff_names` 这套手动跟踪:
```python
# in func_tool_manager.py
class FunctionToolManager:
...
def remove_by_module(self, module_path: str) -> None:
self.func_list = [
t for t in self.func_list
if getattr(t, "handler_module_path", None) != module_path
]
```
然后在 orchestrator 中:
```python
def reload_from_config(self, provider_settings: dict[str, Any]) -> None:
cfg = provider_settings.get("subagent_orchestrator", {})
enabled = bool(cfg.get("main_enable", False))
# clean up previously registered tools from this orchestrator
self._tool_mgr.remove_by_module("core.subagent_orchestrator")
if not enabled:
return
...
self._tool_mgr.add_tool(handoff)
```
如果采用这种方式,就可以完全删除 `_registered_handoff_names`。
### 3) 使用 `SubAgentConfig` 简化解析逻辑
当前的 `reload_from_config` 混合了配置解析和编排逻辑。你已经有 `SubAgentConfig`,如果用它来承载标准化后的配置,方法会更短、更易读。
示例重构:
```python
@dataclass(frozen=True)
class SubAgentConfig:
name: str
instructions: str
public_description: str
tools: list[str]
provider_id: str | None = None
enabled: bool = True
```
解析辅助函数:
```python
def _parse_agent_config(self, item: dict[str, Any]) -> SubAgentConfig | None:
if not isinstance(item, dict):
return None
if not item.get("enabled", True):
return None
name = str(item.get("name", "")).strip()
if not name:
return None
instructions = str(item.get("system_prompt", "")).strip()
public_description = str(item.get("public_description", "")).strip()
provider_id = item.get("provider_id")
if provider_id is not None:
provider_id = str(provider_id).strip() or None
tools = item.get("tools", [])
if not isinstance(tools, list):
tools = []
tools = [str(t).strip() for t in tools if str(t).strip()]
return SubAgentConfig(
name=name,
instructions=instructions,
public_description=public_description,
tools=tools,
provider_id=provider_id,
enabled=True,
)
```
然后 `reload_from_config` 就可以聚焦在编排本身:
```python
def reload_from_config(self, provider_settings: dict[str, Any]) -> None:
cfg = provider_settings.get("subagent_orchestrator", {})
enabled = bool(cfg.get("main_enable", False))
self._tool_mgr.remove_by_module("core.subagent_orchestrator")
if not enabled:
return
agents = cfg.get("agents", [])
if not isinstance(agents, list):
logger.warning("subagent_orchestrator.agents must be a list")
return
for item in agents:
cfg_item = self._parse_agent_config(item)
if cfg_item is None:
continue
agent = Agent[AstrAgentContext](
name=cfg_item.name,
instructions=cfg_item.instructions,
tools=cfg_item.tools,
)
handoff = HandoffTool(
agent=agent,
description=cfg_item.public_description or None,
)
handoff.provider_id = cfg_item.provider_id
handoff.handler_module_path = "core.subagent_orchestrator"
self._tool_mgr.add_tool(handoff)
logger.info(f"Registered subagent handoff tool: {handoff.name}")
```
这样可以让 `SubAgentConfig` 成为真正的抽象(而不是闲置代码),在保持行为不变的前提下,降低 `reload_from_config` 的认知负担。
</issue_to_address>帮我变得更有用!请在每条评论上点 👍 或 👎,我会根据你的反馈改进评审质量。
Original comment in English
Hey - I've found 3 issues, and left some high level feedback:
- The global
window.fetchwrapper indashboard/src/main.tslosesmethod,body, and otherRequestproperties when the first argument is aRequestobject; consider detectingRequestand forwarding it unchanged (only cloning/augmenting headers) to avoid subtle breakage. - In
SubAgentOrchestrator.reload_from_config, manually removing/adding tools viafunc_listandremove_funcbypassesFunctionToolManager's normal registration invariants; it would be safer to expose and use a dedicated API for registeringHandoffToolinstances so they remain discoverable and consistent with other tools. - When saving subagent config in
SubAgentRoute.update_config, there is currently no validation of agent names (e.g., non-empty, uniqueness, allowed characters), which could lead to conflictingtransfer_to_*tool names or invalid identifiers; adding basic server-side validation would make the orchestration more robust.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The global `window.fetch` wrapper in `dashboard/src/main.ts` loses `method`, `body`, and other `Request` properties when the first argument is a `Request` object; consider detecting `Request` and forwarding it unchanged (only cloning/augmenting headers) to avoid subtle breakage.
- In `SubAgentOrchestrator.reload_from_config`, manually removing/adding tools via `func_list` and `remove_func` bypasses `FunctionToolManager`'s normal registration invariants; it would be safer to expose and use a dedicated API for registering `HandoffTool` instances so they remain discoverable and consistent with other tools.
- When saving subagent config in `SubAgentRoute.update_config`, there is currently no validation of agent names (e.g., non-empty, uniqueness, allowed characters), which could lead to conflicting `transfer_to_*` tool names or invalid identifiers; adding basic server-side validation would make the orchestration more robust.
## Individual Comments
### Comment 1
<location> `astrbot/dashboard/server.py:135-136` </location>
<code_context>
r.status_code = 401
return r
- token = token.removeprefix("Bearer ")
+ # Be tolerant of different header casing / formatting.
+ token = token.strip().removeprefix("Bearer ").strip()
try:
payload = jwt.decode(token, self._jwt_secret, algorithms=["HS256"])
</code_context>
<issue_to_address>
**suggestion:** JWT extraction is still case-sensitive for the 'Bearer' prefix despite the comment about header casing.
The code only removes an exact "Bearer " prefix, so headers like `authorization: bearer <token>` or `Authorization: BEARER <token>` will fail despite the comment. Consider normalizing before stripping, e.g.:
```py
raw = token.strip()
if raw.lower().startswith("bearer "):
raw = raw[7:]
token = raw.strip()
```
This matches the stated tolerance for header casing/formatting and avoids unexpected auth failures from non-standard clients.
```suggestion
# Be tolerant of different header casing / formatting.
raw = token.strip()
if raw.lower().startswith("bearer "):
raw = raw[7:]
token = raw.strip()
```
</issue_to_address>
### Comment 2
<location> `astrbot/dashboard/routes/subagent.py:93-105` </location>
<code_context>
+ UI can use this to build a multi-select list for subagent tool assignment.
+ """
+ try:
+ tool_mgr = self.core_lifecycle.provider_manager.llm_tools
+ tools_dict = []
+ for tool in tool_mgr.func_list:
+ tools_dict.append(
+ {
+ "name": tool.name,
+ "description": tool.description,
+ "parameters": tool.parameters,
+ "active": tool.active,
+ "handler_module_path": tool.handler_module_path,
+ }
+ )
</code_context>
<issue_to_address>
**suggestion (bug_risk):** available-tools endpoint exposes handoff tools, allowing subagents to select transfer_to_* tools and create recursive routing.
Because this endpoint returns all entries in `func_list`, it also exposes `HandoffTool` instances (the `transfer_to_*` tools). That lets the UI assign handoff tools as subagent tools, enabling confusing or recursive delegation loops. To avoid this, filter these out—for example, skip tools with `handler_module_path == "core.subagent_orchestrator"` or, if possible, exclude `isinstance(tool, HandoffTool)`.
```suggestion
try:
tool_mgr = self.core_lifecycle.provider_manager.llm_tools
tools_dict = []
for tool in tool_mgr.func_list:
# Skip handoff tools (e.g., transfer_to_*), which live in the subagent orchestrator.
# These should not be assignable as subagent tools to avoid recursive delegation.
if getattr(tool, "handler_module_path", None) == "core.subagent_orchestrator":
continue
tools_dict.append(
{
"name": tool.name,
"description": tool.description,
"parameters": tool.parameters,
"active": tool.active,
"handler_module_path": tool.handler_module_path,
}
)
```
</issue_to_address>
### Comment 3
<location> `astrbot/core/subagent_orchestrator.py:37` </location>
<code_context>
+ self._tool_mgr = tool_mgr
+ self._registered_handoff_names: set[str] = set()
+
+ def reload_from_config(self, provider_settings: dict[str, Any]) -> None:
+ cfg = provider_settings.get("subagent_orchestrator", {})
+ enabled = bool(cfg.get("main_enable", False))
</code_context>
<issue_to_address>
**issue (complexity):** Consider refactoring the orchestrator to delegate tool registration/removal to `FunctionToolManager` and normalize subagent parsing through `SubAgentConfig` for a cleaner, more focused `reload_from_config`.
You can simplify this substantially by tightening the interaction with `FunctionToolManager` and actually using `SubAgentConfig` as your normalized config type.
### 1) Remove the add/remove/append dance
Instead of:
```python
self._tool_mgr.add_func(
name=handoff.name,
func_args=[...],
desc=handoff.description,
handler=handoff.handler,
)
self._tool_mgr.remove_func(handoff.name)
self._tool_mgr.func_list.append(handoff)
```
add a small API to `FunctionToolManager` that accepts a pre‑built tool and handles replacement:
```python
# in func_tool_manager.py
class FunctionToolManager:
...
def add_tool(self, tool: FunctionTool) -> None:
"""Register a pre-constructed tool, replacing by name if it exists."""
# remove any existing tool with same name
self.func_list = [t for t in self.func_list if t.name != tool.name]
self.func_list.append(tool)
```
Then your orchestrator becomes a single, clear registration path:
```python
# in SubAgentOrchestrator.reload_from_config
handoff = HandoffTool(agent=agent, description=public_description or None)
handoff.provider_id = provider_id
handoff.handler_module_path = "core.subagent_orchestrator"
self._tool_mgr.add_tool(handoff)
self._registered_handoff_names.add(handoff.name)
```
This keeps all behavior but avoids reaching into `func_list` and calling `add_func` only to immediately undo it.
### 2) Avoid manual tracking where possible
If you can encode the origin in the tools (you already set `handler_module_path`), you can centralize removal in the manager and drop the `_registered_handoff_names` set:
```python
# in func_tool_manager.py
class FunctionToolManager:
...
def remove_by_module(self, module_path: str) -> None:
self.func_list = [
t for t in self.func_list
if getattr(t, "handler_module_path", None) != module_path
]
```
Then in the orchestrator:
```python
def reload_from_config(self, provider_settings: dict[str, Any]) -> None:
cfg = provider_settings.get("subagent_orchestrator", {})
enabled = bool(cfg.get("main_enable", False))
# clean up previously registered tools from this orchestrator
self._tool_mgr.remove_by_module("core.subagent_orchestrator")
if not enabled:
return
...
self._tool_mgr.add_tool(handoff)
```
If you adopt this, you can remove `_registered_handoff_names` completely.
### 3) Use `SubAgentConfig` to simplify parsing
Right now `reload_from_config` mixes config parsing with orchestration. You already have `SubAgentConfig`; using it makes the method shorter and easier to read.
Example refactor:
```python
@dataclass(frozen=True)
class SubAgentConfig:
name: str
instructions: str
public_description: str
tools: list[str]
provider_id: str | None = None
enabled: bool = True
```
Parsing helper:
```python
def _parse_agent_config(self, item: dict[str, Any]) -> SubAgentConfig | None:
if not isinstance(item, dict):
return None
if not item.get("enabled", True):
return None
name = str(item.get("name", "")).strip()
if not name:
return None
instructions = str(item.get("system_prompt", "")).strip()
public_description = str(item.get("public_description", "")).strip()
provider_id = item.get("provider_id")
if provider_id is not None:
provider_id = str(provider_id).strip() or None
tools = item.get("tools", [])
if not isinstance(tools, list):
tools = []
tools = [str(t).strip() for t in tools if str(t).strip()]
return SubAgentConfig(
name=name,
instructions=instructions,
public_description=public_description,
tools=tools,
provider_id=provider_id,
enabled=True,
)
```
Then `reload_from_config` focuses on orchestration:
```python
def reload_from_config(self, provider_settings: dict[str, Any]) -> None:
cfg = provider_settings.get("subagent_orchestrator", {})
enabled = bool(cfg.get("main_enable", False))
self._tool_mgr.remove_by_module("core.subagent_orchestrator")
if not enabled:
return
agents = cfg.get("agents", [])
if not isinstance(agents, list):
logger.warning("subagent_orchestrator.agents must be a list")
return
for item in agents:
cfg_item = self._parse_agent_config(item)
if cfg_item is None:
continue
agent = Agent[AstrAgentContext](
name=cfg_item.name,
instructions=cfg_item.instructions,
tools=cfg_item.tools,
)
handoff = HandoffTool(
agent=agent,
description=cfg_item.public_description or None,
)
handoff.provider_id = cfg_item.provider_id
handoff.handler_module_path = "core.subagent_orchestrator"
self._tool_mgr.add_tool(handoff)
logger.info(f"Registered subagent handoff tool: {handoff.name}")
```
This makes `SubAgentConfig` a real abstraction (not dead code), keeps behavior intact, and reduces the cognitive load in `reload_from_config`.
</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/dashboard/server.py
Outdated
| # Be tolerant of different header casing / formatting. | ||
| token = token.strip().removeprefix("Bearer ").strip() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion: 尽管注释里说明要兼容不同的 header 大小写,但当前 JWT 提取对 Bearer 前缀仍然是大小写敏感的。
代码只会移除精确匹配的 "Bearer " 前缀,因此像 authorization: bearer <token> 或 Authorization: BEARER <token> 这样的 header 会失败,这与注释不符。建议在去前缀前先做归一化,例如:
raw = token.strip()
if raw.lower().startswith("bearer "):
raw = raw[7:]
token = raw.strip()这样与注释中宣称的“对 header 大小写/格式更宽容”是一致的,也能避免来自非标准客户端的意外认证失败。
| # Be tolerant of different header casing / formatting. | |
| token = token.strip().removeprefix("Bearer ").strip() | |
| # Be tolerant of different header casing / formatting. | |
| raw = token.strip() | |
| if raw.lower().startswith("bearer "): | |
| raw = raw[7:] | |
| token = raw.strip() |
Original comment in English
suggestion: JWT extraction is still case-sensitive for the 'Bearer' prefix despite the comment about header casing.
The code only removes an exact "Bearer " prefix, so headers like authorization: bearer <token> or Authorization: BEARER <token> will fail despite the comment. Consider normalizing before stripping, e.g.:
raw = token.strip()
if raw.lower().startswith("bearer "):
raw = raw[7:]
token = raw.strip()This matches the stated tolerance for header casing/formatting and avoids unexpected auth failures from non-standard clients.
| # Be tolerant of different header casing / formatting. | |
| token = token.strip().removeprefix("Bearer ").strip() | |
| # Be tolerant of different header casing / formatting. | |
| raw = token.strip() | |
| if raw.lower().startswith("bearer "): | |
| raw = raw[7:] | |
| token = raw.strip() |
Soulter
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
just made some quick reviews
astrbot/dashboard/server.py
Outdated
| return r | ||
| token = token.removeprefix("Bearer ") | ||
| # Be tolerant of different header casing / formatting. | ||
| token = token.strip().removeprefix("Bearer ").strip() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
这一行就按原来那样吧
| name=f"transfer_to_{agent.name}", | ||
| parameters=parameters or self.default_parameters(), | ||
| description=agent.instructions or self.default_description(agent.name), | ||
| description=description, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
这里直接这样改吧:
- 新增可为None的关键字参数,"tool_description",然后这里就 tool_description or self.default_description(agent.name)
|
|
||
|
|
||
| @dataclass(frozen=True) | ||
| class SubAgentConfig: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
好像没有使用过可以去掉
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
感觉相关逻辑可以直接放到 tool_manager 里面,用一个方法代替。
astrbot/core/config/default.py
Outdated
| }, | ||
| # SubAgent orchestrator mode: the main LLM only delegates tasks to subagents | ||
| # (via transfer_to_{agent} tools). Domain tools are mounted on subagents. | ||
| "subagent_orchestrator": { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
感觉可以和provider_settings平级
Motivations / 动机
目前框架采取将所有tool全部挂载在主llm上,主llm既负责与用户对话,又要负责组织和调用大量工具。若tool数量多,容易导致prompt长度爆炸以及llm调用失误等情况。
若用户可以创建subagent,每个subagent负责挂载一定的工具,主llm只需负责聊天以及向subagent委派任务即可,具体工具由subagent负责调用。这样可以极大得减少主llm的prompt长度以及执行任务时的出错概率。
Modifications / 改动点
1.在webui上增加subagent编排,用户可在页面上创建subagent,指定该subagent使用的工具,subagent的systemprompt以及在主llm处subagent的描述,以及自定义subagent使用的模型
2.支持开关subagent功能,开启时,主 LLM 仅负责对话与委派,只会看到 transfer_to_* 这类委派工具;需要调用工具时,会把任务交给对应 SubAgent 执行。SubAgent 负责真正的工具调用与结果整理,并把结论回传给主 LLM。关闭时则像原来一样把全部工具挂载到主llm上。
3.注册的subagent会自动成为一个名为transfer_to_(subagent名称)的tool,复用框架原有handleoff功能,主llm调用transfer_to_(agent名称)即将任务委派给指定subagent。
Screenshots or Test Results / 运行截图或测试结果
Checklist / 检查清单
requirements.txt和pyproject.toml文件相应位置。/ I have ensured that no new dependencies are introduced, OR if new dependencies are introduced, they have been added to the appropriate locations inrequirements.txtandpyproject.toml.Summary by Sourcery
引入一种可配置的子代理(subagent)编排模式,在该模式下主 LLM 通过动态交接(handoff)工具进行委派,而不是直接挂载所有工具。
New Features:
transfer_to_*交接工具,支持可选的按子代理级别的 provider 覆盖。Enhancements:
fetch的仪表盘请求自动包含Authorization头。Build:
monaco-editor添加为仪表盘依赖,以在 UI 中提供更强大的编辑器能力。Original summary in English
Summary by Sourcery
Introduce a configurable subagent orchestration mode where the main LLM delegates via dynamic handoff tools instead of mounting all tools directly.
New Features:
Enhancements:
Build: