refactor(box): clean up sandbox subsystem code quality and efficiency

- Fix O(n²) stderr trimming in runtime.py with running length tracker
  - Remove dead code: RESERVED_CONTAINER_PATHS, _subprocess_wait_task,
    unused config_hash computation, unused imports
  - Deduplicate connection callback in BoxRuntimeConnector, parse URL once
  - Use enum comparison instead of stringly-typed spec.network.value check
  - Replace manual _result_to_dict/_session_to_dict with model_dump()
  - Cache NativeToolLoader tool definition and sandbox system guidance
  - Extract _is_path_under() helper to eliminate duplicated path checks
  - Import SANDBOX_EXEC_TOOL_NAME from native.py instead of redefining
  - Add JSON startswith guard in logging_utils to skip futile json.loads
  - Fix ruff lint errors (F401 unused imports, F841 unused variables)
This commit is contained in:
youhuanghe
2026-03-22 02:28:25 +00:00
committed by WangCham
parent fbe6e145ec
commit 76fbd08680
10 changed files with 101 additions and 149 deletions

View File

@@ -5,6 +5,7 @@ import copy
import typing
from .. import runner
from ..modelmgr import requester as modelmgr_requester
from ..tools.loaders.native import SANDBOX_EXEC_TOOL_NAME
import langbot_plugin.api.entities.builtin.pipeline.query as pipeline_query
import langbot_plugin.api.entities.builtin.provider.message as provider_message
import langbot_plugin.api.entities.builtin.rag.context as rag_context
@@ -24,7 +25,6 @@ Respond in the same language as the user's input.
</user_message>
"""
SANDBOX_EXEC_TOOL_NAME = 'sandbox_exec'
SANDBOX_EXEC_SYSTEM_GUIDANCE = (
'When sandbox_exec is available, use it for exact calculations, statistics, structured data parsing, '
'and code execution instead of estimating mentally. If the user provides numbers, tables, CSV-like text, '
@@ -43,13 +43,19 @@ SANDBOX_EXEC_WORKSPACE_GUIDANCE = (
class LocalAgentRunner(runner.RequestRunner):
"""Local agent request runner"""
_cached_sandbox_guidance: str | None = None
def _build_sandbox_system_guidance(self) -> str:
if self._cached_sandbox_guidance is not None:
return self._cached_sandbox_guidance
from langbot.pkg.box.models import get_box_config
guidance = SANDBOX_EXEC_SYSTEM_GUIDANCE
default_host_workspace = str(
getattr(getattr(self.ap, 'instance_config', None), 'data', {}).get('box', {}).get('default_host_workspace', '')
).strip()
default_host_workspace = str(get_box_config(self.ap).get('default_host_workspace', '')).strip()
if default_host_workspace:
guidance = f'{guidance} {SANDBOX_EXEC_WORKSPACE_GUIDANCE}'
self._cached_sandbox_guidance = guidance
return guidance
def _build_request_messages(

View File

@@ -150,7 +150,7 @@ class RuntimeMCPSession:
session_payload,
skip_host_mount_validation=True,
)
except Exception as e:
except Exception:
self.error_phase = MCPSessionErrorPhase.SESSION_CREATE
raise
@@ -169,7 +169,7 @@ class RuntimeMCPSession:
result = await box_service.client.execute(
box_service.build_spec(exec_payload, skip_host_mount_validation=True)
)
except Exception as e:
except Exception:
self.error_phase = MCPSessionErrorPhase.DEP_INSTALL
raise
if not result.ok:
@@ -186,7 +186,7 @@ class RuntimeMCPSession:
session_id,
self._build_box_process_payload(host_path),
)
except Exception as e:
except Exception:
self.error_phase = MCPSessionErrorPhase.PROCESS_START
raise
@@ -196,14 +196,14 @@ class RuntimeMCPSession:
transport = await self.exit_stack.enter_async_context(websocket_client(websocket_url))
read_stream, write_stream = transport
self.session = await self.exit_stack.enter_async_context(ClientSession(read_stream, write_stream))
except Exception as e:
except Exception:
self.error_phase = MCPSessionErrorPhase.RELAY_CONNECT
raise
# Phase: MCP protocol initialization
try:
await self.session.initialize()
except Exception as e:
except Exception:
self.error_phase = MCPSessionErrorPhase.MCP_INIT
raise
@@ -813,12 +813,13 @@ class MCPLoader(loader.ToolLoader):
"""获取所有服务器的信息"""
info = {}
for server_name, session in self.sessions.items():
tools = session.get_tools()
info[server_name] = {
'name': server_name,
'mode': session.server_config.get('mode'),
'enable': session.enable,
'tools_count': len(session.get_tools()),
'tool_names': [f.name for f in session.get_tools()],
'tools_count': len(tools),
'tool_names': [f.name for f in tools],
}
return info

View File

@@ -5,20 +5,28 @@ import json
import langbot_plugin.api.entities.builtin.resource.tool as resource_tool
from langbot_plugin.api.entities.events import pipeline_query
from langbot.pkg.box.models import BoxNetworkMode
from .. import loader
SANDBOX_EXEC_TOOL_NAME = 'sandbox_exec'
class NativeToolLoader(loader.ToolLoader):
SANDBOX_EXEC_TOOL_NAME = 'sandbox_exec'
def __init__(self, ap):
super().__init__(ap)
self._sandbox_exec_tool: resource_tool.LLMTool | None = None
async def get_tools(self, bound_plugins: list[str] | None = None) -> list[resource_tool.LLMTool]:
return [self._build_sandbox_exec_tool()]
if self._sandbox_exec_tool is None:
self._sandbox_exec_tool = self._build_sandbox_exec_tool()
return [self._sandbox_exec_tool]
async def has_tool(self, name: str) -> bool:
return name == self.SANDBOX_EXEC_TOOL_NAME
return name == SANDBOX_EXEC_TOOL_NAME
async def invoke_tool(self, name: str, parameters: dict, query: pipeline_query.Query):
if name != self.SANDBOX_EXEC_TOOL_NAME:
if name != SANDBOX_EXEC_TOOL_NAME:
raise ValueError(f'未找到工具: {name}')
self.ap.logger.info(
'sandbox_exec tool invoked: '
@@ -32,7 +40,7 @@ class NativeToolLoader(loader.ToolLoader):
def _build_sandbox_exec_tool(self) -> resource_tool.LLMTool:
return resource_tool.LLMTool(
name=self.SANDBOX_EXEC_TOOL_NAME,
name=SANDBOX_EXEC_TOOL_NAME,
human_desc='Execute a command inside the LangBot Box sandbox',
description=(
'Run shell commands only inside the isolated LangBot Box sandbox. '
@@ -60,7 +68,7 @@ class NativeToolLoader(loader.ToolLoader):
'network': {
'type': 'string',
'description': 'Network policy for the sandbox session. Prefer off unless network is required.',
'enum': ['off', 'on'],
'enum': [e.value for e in BoxNetworkMode],
'default': 'off',
},
'env': {