feat: 增强 core/agents 工具和 API

- 新增 loop.py Agent 运行循环
- 优化 memory.py 记忆模块
- 扩展 api/routes.py 接口
- 更新 tools 模块:builtin.py, manager.py, __init__.py
- 新增 .env.example 配置示例
- 更新 requirements.txt 依赖

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-15 19:49:40 +08:00
parent 31f0feafb5
commit 1afa88e812
8 changed files with 231 additions and 17 deletions

View File

@@ -21,19 +21,55 @@ from agents.tools.builtin import (
from agents.tools.manager import ToolManager
def create_default_registry() -> ToolRegistry:
def create_default_registry(use_sandbox: bool = False) -> ToolRegistry:
"""Create a tool registry with default tools.
Args:
use_sandbox: Whether to use sandbox for shell execution
Returns:
Tool registry with built-in tools
"""
registry = ToolRegistry()
# Register built-in tools
for tool in get_builtin_tools():
for tool in get_builtin_tools(use_sandbox=use_sandbox):
registry.register(tool)
return registry
# Import sandbox tools from nanobot (optional)
try:
from nanobot.agent.tools.sandbox_execution import (
SandboxType,
SandboxCodeExecutionTool,
SandboxBashTool,
get_sandbox_tools,
)
from nanobot.agent.tools.bwrap_sandbox import (
BwrapSandbox,
get_bwrap_sandbox,
execute_in_bwrap,
)
from nanobot.agent.tools.gvisor_sandbox import (
GvisorSandbox,
get_gvisor_sandbox,
execute_in_gvisor,
)
SANDBOX_AVAILABLE = True
except ImportError as e:
SandboxType = None
SandboxCodeExecutionTool = None
SandboxBashTool = None
get_sandbox_tools = None
BwrapSandbox = None
get_bwrap_sandbox = None
execute_in_bwrap = None
GvisorSandbox = None
get_gvisor_sandbox = None
execute_in_gvisor = None
SANDBOX_AVAILABLE = False
__all__ = [
"Tool",
"ToolRegistry",
@@ -48,4 +84,16 @@ __all__ = [
"CalculatorTool",
"GetTimeTool",
"BashTool",
# Sandbox tools
"SANDBOX_AVAILABLE",
"SandboxType",
"SandboxCodeExecutionTool",
"SandboxBashTool",
"get_sandbox_tools",
"BwrapSandbox",
"GvisorSandbox",
"get_bwrap_sandbox",
"get_gvisor_sandbox",
"execute_in_bwrap",
"execute_in_gvisor",
]

View File

@@ -2,12 +2,24 @@
import asyncio
import json
import os
import re
from pathlib import Path
from typing import Any
from nanobot.agent.tools.base import Tool
# Import sandbox (optional - graceful fallback if not available)
try:
from nanobot.agent.tools.bwrap_sandbox import BwrapSandbox, get_bwrap_sandbox
from nanobot.agent.tools.sandbox_execution import SandboxType
SANDBOX_AVAILABLE = True
except ImportError:
BwrapSandbox = None
get_bwrap_sandbox = None
SandboxType = None
SANDBOX_AVAILABLE = False
class ReadFileTool(Tool):
"""Read file contents."""
@@ -361,8 +373,18 @@ class GetTimeTool(Tool):
class BashTool(Tool):
"""Execute bash commands."""
def __init__(self, workspace: Path | None = None):
def __init__(self, workspace: Path | None = None, use_sandbox: bool = False):
"""Initialize bash tool.
Args:
workspace: Workspace path
use_sandbox: Whether to use sandbox for execution (recommended for untrusted code)
"""
self._workspace = workspace
self._use_sandbox = use_sandbox
self._sandbox = None
if use_sandbox and SANDBOX_AVAILABLE:
self._sandbox = get_bwrap_sandbox()
@property
def name(self) -> str:
@@ -370,11 +392,13 @@ class BashTool(Tool):
@property
def description(self) -> str:
if self._use_sandbox:
return "Execute a bash command in an isolated sandbox and return its output."
return "Execute a bash command and return its output."
@property
def parameters(self) -> dict[str, Any]:
return {
params = {
"type": "object",
"properties": {
"command": {"type": "string", "description": "Command to execute"},
@@ -386,8 +410,17 @@ class BashTool(Tool):
},
"required": ["command"],
}
return params
async def execute(self, command: str, timeout: int = 30, **kwargs: Any) -> str:
# Use sandbox if enabled
if self._use_sandbox and self._sandbox:
try:
return await self._sandbox.execute_command(command, timeout)
except Exception as e:
return f"Error executing in sandbox: {str(e)}\nFalling back to direct execution."
# Direct execution (no sandbox)
try:
process = await asyncio.create_subprocess_shell(
command,
@@ -410,11 +443,12 @@ class BashTool(Tool):
return f"Error executing command: {str(e)}"
def get_builtin_tools(workspace: Path | None = None) -> list[Tool]:
def get_builtin_tools(workspace: Path | None = None, use_sandbox: bool = False) -> list[Tool]:
"""Get list of all built-in tools.
Args:
workspace: Optional workspace path for file operations
use_sandbox: Whether to use sandbox for shell execution (recommended for untrusted code)
Returns:
List of Tool instances
@@ -427,5 +461,5 @@ def get_builtin_tools(workspace: Path | None = None) -> list[Tool]:
WebSearchTool(),
CalculatorTool(),
GetTimeTool(),
BashTool(workspace),
BashTool(workspace, use_sandbox=use_sandbox),
]

View File

@@ -14,22 +14,24 @@ logger = logging.getLogger(__name__)
class ToolManager:
"""Manages tools for the agent."""
def __init__(self, workspace: Path | None = None):
def __init__(self, workspace: Path | None = None, use_sandbox: bool = False):
"""Initialize tool manager.
Args:
workspace: Optional workspace path
use_sandbox: Whether to use sandbox for shell execution (recommended for untrusted code)
"""
self.workspace = workspace
self.use_sandbox = use_sandbox
self.registry = ToolRegistry()
self._load_builtin_tools()
def _load_builtin_tools(self) -> None:
"""Load all built-in tools."""
tools = get_builtin_tools(self.workspace)
tools = get_builtin_tools(self.workspace, use_sandbox=self.use_sandbox)
for tool in tools:
self.registry.register(tool)
logger.info(f"Loaded {len(tools)} built-in tools")
logger.info(f"Loaded {len(tools)} built-in tools (sandbox: {self.use_sandbox})")
def register_tool(self, tool: Any) -> None:
"""Register a custom tool.