mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-06-10 15:56:03 +00:00
fix(agent-runner): clean plugin review issues
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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}',
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
6
src/langbot/pkg/provider/tools/errors.py
Normal file
6
src/langbot/pkg/provider/tools/errors.py
Normal 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}')
|
||||
18
src/langbot/pkg/provider/tools/loaders/availability.py
Normal file
18
src/langbot/pkg/provider/tools/loaders/availability.py
Normal 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
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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():
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user