fix(agent-runner): clean plugin review issues

This commit is contained in:
huanghuoguoguo
2026-06-06 14:34:40 +08:00
parent 754f7197c5
commit cb33bf3260
18 changed files with 71 additions and 73 deletions

View File

@@ -12,7 +12,7 @@ class MCPRouterGroup(group.RouterGroup):
async def initialize(self) -> None:
@self.route('/servers', methods=['GET', 'POST'], auth_type=group.AuthType.USER_TOKEN)
async def _() -> str:
"""获取MCP服务器列表"""
"""List MCP servers or create a new MCP server."""
if quart.request.method == 'GET':
servers = await self.ap.mcp_service.get_mcp_servers(contain_runtime_info=True)
@@ -30,7 +30,7 @@ class MCPRouterGroup(group.RouterGroup):
@self.route('/servers/<server_name>', methods=['GET', 'PUT', 'DELETE'], auth_type=group.AuthType.USER_TOKEN)
async def _(server_name: str) -> str:
"""获取、更新或删除MCP服务器配置"""
"""Get, update, or delete an MCP server configuration."""
from urllib.parse import unquote
server_name = unquote(server_name)
@@ -59,7 +59,7 @@ class MCPRouterGroup(group.RouterGroup):
@self.route('/servers/<server_name>/test', methods=['POST'], auth_type=group.AuthType.USER_TOKEN)
async def _(server_name: str) -> str:
"""测试MCP服务器连接"""
"""Test an MCP server connection."""
from urllib.parse import unquote
server_name = unquote(server_name)

View File

@@ -137,7 +137,7 @@ class MCPService:
await self.ap.tool_mgr.mcp_tool_loader.remove_mcp_server(server_name)
async def test_mcp_server(self, server_name: str, server_data: dict) -> int:
"""测试 MCP 服务器连接并返回任务 ID"""
"""Test an MCP server connection and return the task ID."""
runtime_mcp_session: RuntimeMCPSession | None = None

View File

@@ -211,7 +211,7 @@ class PreProcessor(stage.PipelineStage):
# time instead of the first message/creation time.
conversation.update_time = now
# 设置query
# Attach resolved session state to the query.
query.session = session
query.prompt = conversation.prompt.copy()
query.messages = await self._resolve_history_messages(runner_id, conversation)
@@ -292,7 +292,7 @@ class PreProcessor(stage.PipelineStage):
if me.base64 is not None:
content_list.append(provider_message.ContentElement.from_image_base64(me.base64))
elif isinstance(me, platform_message.Voice):
# 转成文件链接,让下游 runner 上传到目标模型
# Convert voice input into file content for downstream model upload.
if me.base64:
content_list.append(provider_message.ContentElement.from_file_base64(me.base64, 'voice.silk'))
elif me.url:
@@ -334,7 +334,7 @@ class PreProcessor(stage.PipelineStage):
runner_config,
)
# =========== 触发事件 PromptPreProcessing
# Emit PromptPreProcessing before the runner receives the query.
event = events.PromptPreProcessing(
session_name=f'{query.session.launcher_type.value}_{query.session.launcher_id}',

View File

@@ -468,7 +468,12 @@ class PluginRuntimeConnector(ManagedRuntimeConnector):
)
file_bytes = download_resp.content
self._extract_deps_metadata(file_bytes, task_context)
plugin_author, plugin_name = self._inspect_plugin_package(
file_bytes,
task_context,
)
if task_context is not None and plugin_author and plugin_name:
task_context.metadata['plugin_name'] = f'{plugin_author}/{plugin_name}'
file_key = await self.handler.send_file(file_bytes, 'lbpkg')
install_info['plugin_file_key'] = file_key
self.ap.logger.info(f'Transfered file {file_key} to plugin runtime')

View File

@@ -3,8 +3,6 @@ Legacy Coze API Runner.
DEPRECATED: This runner has been migrated to the AgentRunner plugin format.
Use the official `langbot/coze-agent` plugin instead.
Migration target: /home/glwuy/langbot-app/langbot-agent-runner/coze-agent/
"""
from __future__ import annotations

View File

@@ -3,8 +3,6 @@ Legacy DashScope (阿里云百炼) API Runner.
DEPRECATED: This runner has been migrated to the AgentRunner plugin format.
Use the official `langbot/dashscope-agent` plugin instead.
Migration target: /home/glwuy/langbot-app/langbot-agent-runner/dashscope-agent/
"""
from __future__ import annotations

View File

@@ -3,8 +3,6 @@ Legacy Dify Service API Runner.
DEPRECATED: This runner has been migrated to the AgentRunner plugin format.
Use the official `langbot/dify-agent` plugin instead.
Migration target: /home/glwuy/langbot-app/langbot-agent-runner/dify-agent/
"""
from __future__ import annotations

View File

@@ -3,8 +3,6 @@ Legacy Langflow API Runner.
DEPRECATED: This runner has been migrated to the AgentRunner plugin format.
Use the official `langbot/langflow-agent` plugin instead.
Migration target: /home/glwuy/langbot-app/langbot-agent-runner/langflow-agent/
"""
from __future__ import annotations

View File

@@ -3,8 +3,6 @@ Legacy Local Agent Runner.
DEPRECATED: This runner has been migrated to the AgentRunner plugin format.
Use the official `langbot/local-agent` plugin instead.
Migration target: /home/glwuy/langbot-app/langbot-local-agent/
"""
from __future__ import annotations

View File

@@ -3,8 +3,6 @@ Legacy n8n Service API Runner.
DEPRECATED: This runner has been migrated to the AgentRunner plugin format.
Use the official `langbot/n8n-agent` plugin instead.
Migration target: /home/glwuy/langbot-app/langbot-agent-runner/n8n-agent/
"""
from __future__ import annotations

View File

@@ -3,8 +3,6 @@ Legacy Tbox (蚂蚁百宝箱) API Runner.
DEPRECATED: This runner has been migrated to the AgentRunner plugin format.
Use the official `langbot/tbox-agent` plugin instead.
Migration target: /home/glwuy/langbot-app/langbot-agent-runner/tbox-agent/
"""
from __future__ import annotations

View File

@@ -0,0 +1,6 @@
class ToolNotFoundError(ValueError):
"""Raised when a requested tool cannot be found in any active loader."""
def __init__(self, name: str):
self.name = name
super().__init__(f'Tool not found: {name}')

View File

@@ -0,0 +1,18 @@
from __future__ import annotations
from typing import Any
async def is_box_backend_available(ap: Any) -> bool:
"""Return whether the configured Box backend is ready for tool execution."""
box_service = getattr(ap, 'box_service', None)
if box_service is None:
return False
if not getattr(box_service, 'available', False):
return False
try:
status = await box_service.get_status()
backend_info = status.get('backend', {})
return bool(backend_info.get('available', False))
except Exception:
return False

View File

@@ -30,7 +30,7 @@ class MCPSessionStatus(enum.Enum):
class RuntimeMCPSession:
"""运行时 MCP 会话"""
"""Runtime MCP session."""
ap: app.Application
@@ -352,12 +352,12 @@ class RuntimeMCPSession:
return info
async def shutdown(self):
"""关闭会话并清理资源"""
"""Close the session and release resources."""
try:
# 设置shutdown事件通知lifecycle任务退出
# Signal the lifecycle task to exit.
self._shutdown_event.set()
# 等待lifecycle任务完成带超时
# Wait for the lifecycle task with a bounded timeout.
if self._lifecycle_task and not self._lifecycle_task.done():
try:
await asyncio.wait_for(self._lifecycle_task, timeout=5.0)
@@ -411,9 +411,9 @@ class RuntimeMCPSession:
# @loader.loader_class('mcp')
class MCPLoader(loader.ToolLoader):
"""MCP 工具加载器。
"""MCP tool loader.
在此加载器中管理所有与 MCP Server 的连接。
This loader owns all active MCP server connections.
"""
sessions: dict[str, RuntimeMCPSession]
@@ -468,14 +468,14 @@ class MCPLoader(loader.ToolLoader):
self.ap.logger.debug(f'Started MCP server {server_config["name"]}({server_config["uuid"]})')
async def load_mcp_server(self, server_config: dict) -> RuntimeMCPSession:
"""加载 MCP 服务器到运行时
"""Load an MCP server into the runtime.
Args:
server_config: 服务器配置字典,必须包含:
- name: 服务器名称
- mode: 连接模式 (stdio/sse/http)
- enable: 是否启用
- extra_args: 额外的配置参数 (可选)
server_config: Server config dict. Must include:
- name: Server name.
- mode: Connection mode (stdio/sse/http).
- enable: Whether the server is enabled.
- extra_args: Optional extra config.
"""
uuid_ = server_config.get('uuid')
if not uuid_:
@@ -518,7 +518,7 @@ class MCPLoader(loader.ToolLoader):
return all_functions
async def has_tool(self, name: str) -> bool:
"""检查工具是否存在"""
"""Return whether a loaded MCP tool exists."""
for session in self.sessions.values():
for function in session.get_tools():
if function.name == name:
@@ -541,7 +541,7 @@ class MCPLoader(loader.ToolLoader):
return None
async def invoke_tool(self, name: str, parameters: dict, query: pipeline_query.Query | None) -> typing.Any:
"""执行工具调用"""
"""Invoke a loaded MCP tool."""
for session in self.sessions.values():
for function in session.get_tools():
if function.name == name:
@@ -557,7 +557,7 @@ class MCPLoader(loader.ToolLoader):
raise ValueError(f'Tool not found: {name}')
async def remove_mcp_server(self, server_name: str):
"""移除 MCP 服务器"""
"""Remove an MCP server from the runtime."""
if server_name not in self.sessions:
self.ap.logger.warning(f'MCP server {server_name} not found in sessions, skipping removal')
return
@@ -567,24 +567,24 @@ class MCPLoader(loader.ToolLoader):
self.ap.logger.info(f'Removed MCP server: {server_name}')
def get_session(self, server_name: str) -> RuntimeMCPSession | None:
"""获取指定名称的 MCP 会话"""
"""Get an MCP session by server name."""
return self.sessions.get(server_name)
def has_session(self, server_name: str) -> bool:
"""检查是否存在指定名称的 MCP 会话"""
"""Return whether a session exists for the server name."""
return server_name in self.sessions
def get_all_server_names(self) -> list[str]:
"""获取所有已加载的 MCP 服务器名称"""
"""Return all loaded MCP server names."""
return list(self.sessions.keys())
def get_server_tool_count(self, server_name: str) -> int:
"""获取指定服务器的工具数量"""
"""Return the number of tools exposed by one MCP server."""
session = self.get_session(server_name)
return len(session.get_tools()) if session else 0
def get_all_servers_info(self) -> dict[str, dict]:
"""获取所有服务器的信息"""
"""Return runtime information for all loaded MCP servers."""
info = {}
for server_name, session in self.sessions.items():
tools = session.get_tools()
@@ -598,7 +598,7 @@ class MCPLoader(loader.ToolLoader):
return info
async def shutdown(self):
"""关闭所有工具"""
"""Shut down all MCP sessions."""
self.ap.logger.info('Shutting down all MCP sessions...')
for server_name, session in list(self.sessions.items()):
try:

View File

@@ -8,6 +8,8 @@ import langbot_plugin.api.entities.builtin.resource.tool as resource_tool
from langbot_plugin.api.entities.events import pipeline_query
from .. import loader
from ..errors import ToolNotFoundError
from .availability import is_box_backend_available
from . import skill as skill_loader
EXEC_TOOL_NAME = 'exec'
@@ -52,18 +54,7 @@ class NativeToolLoader(loader.ToolLoader):
async def _check_backend_available(self) -> bool:
"""Check if the box backend is truly available (not just the runtime)."""
box_service = getattr(self.ap, 'box_service', None)
if box_service is None:
return False
if not getattr(box_service, 'available', False):
return False
# Check if backend is truly available via get_status
try:
status = await box_service.get_status()
backend_info = status.get('backend', {})
return backend_info.get('available', False)
except Exception:
return False
return await is_box_backend_available(self.ap)
async def get_tools(self, bound_plugins: list[str] | None = None) -> list[resource_tool.LLMTool]:
if not self._is_sandbox_available():
@@ -100,7 +91,7 @@ class NativeToolLoader(loader.ToolLoader):
return await self._invoke_glob(parameters, query)
if name == GREP_TOOL_NAME:
return await self._invoke_grep(parameters, query)
raise ValueError(f'未找到工具: {name}')
raise ToolNotFoundError(name)
async def shutdown(self):
pass

View File

@@ -6,6 +6,7 @@ import typing
import langbot_plugin.api.entities.builtin.resource.tool as resource_tool
from .. import loader
from .availability import is_box_backend_available
# Align with Claude Code's Skill tool design:
# - activate: Activate a skill via Tool Call, returns SKILL.md content
@@ -45,18 +46,7 @@ class SkillToolLoader(loader.ToolLoader):
async def _check_sandbox_available(self) -> bool:
"""Check if the box backend is truly available (not just the runtime)."""
box_service = getattr(self.ap, 'box_service', None)
if box_service is None:
return False
if not getattr(box_service, 'available', False):
return False
# Check if backend is truly available via get_status
try:
status = await box_service.get_status()
backend_info = status.get('backend', {})
return backend_info.get('available', False)
except Exception:
return False
return await is_box_backend_available(self.ap)
async def get_tools(self, bound_plugins: list[str] | None = None) -> list[resource_tool.LLMTool]:
if not self._is_available():

View File

@@ -6,6 +6,8 @@ from typing import TYPE_CHECKING
import langbot_plugin.api.entities.builtin.resource.tool as resource_tool
from langbot_plugin.api.entities.events import pipeline_query
from .errors import ToolNotFoundError
if TYPE_CHECKING:
from ...core import app
from langbot.pkg.provider.tools.loaders import (
@@ -128,7 +130,7 @@ class ToolManager:
return tools
async def execute_func_call(self, name: str, parameters: dict, query: pipeline_query.Query | None) -> typing.Any:
"""执行函数调用"""
"""Execute a tool call through the active tool loaders."""
if await self.native_tool_loader.has_tool(name):
return await self.native_tool_loader.invoke_tool(name, parameters, query)
@@ -138,7 +140,7 @@ class ToolManager:
return await self.mcp_tool_loader.invoke_tool(name, parameters, query)
if await self.skill_tool_loader.has_tool(name):
return await self.skill_tool_loader.invoke_tool(name, parameters, query)
raise ValueError(f'未找到工具: {name}')
raise ToolNotFoundError(name)
async def shutdown(self):
await self.native_tool_loader.shutdown()

View File

@@ -5,10 +5,10 @@ import typing
def import_modules_in_pkg(pkg: typing.Any) -> None:
"""
导入一个包内的所有模块
"""Import all Python modules inside a package.
Args:
pkg: 要导入的包对象
pkg: Package object to import from.
"""
pkg_path = os.path.dirname(pkg.__file__)
import_dir(pkg_path)