Files
ThearchyHelios 964eeaa829 AgentLab:可视化 Agent 编排实验台
在画布上拖拽编排 agent 工作流,并实时看着它执行。

后端 FastAPI + LangGraph:把画布 JSON 编译成 StateGraph,15 种节点
(模型/Agent/多Agent、工具/沙箱、分支/循环/子图、记忆/检索/整形、
人工介入/结构校验)。选 LangGraph 是因为可视化编排真正难的是暂停、
恢复和回放,而 checkpointer + interrupt() 正好把这几件事做在了底层。

前端 React + React Flow:节点元数据驱动属性面板与连接桩,运行时
节点高亮、边流动画、卡片内流式 token 全部由同一份事件流驱动。

关键实现:
- Agent 内工具审批用 @task 缓存模型调用,interrupt 重放时不重复计费
- 沙箱保持只读根文件系统,代码经 exec+base64 写入(put_archive 会被
  只读容器拒绝),断网、非 root、内存/CPU/超时限额
- 检索为向量与 BM25 混合,本地哈希 embedding 零配置可用
- 分支条件走 AST 白名单求值器;HTTP 工具逐 IP 拦截内网与元数据地址
- Mock provider 让无 API Key 也能跑通完整链路

7 个内置模板全部冒烟通过,含真实模型端到端验证。
2026-09-13 17:27:00 +08:00

84 lines
2.6 KiB
Python

from __future__ import annotations
from typing import Any, Literal
from pydantic import BaseModel, Field
from app.sandbox.base import SandboxLimits
from app.sandbox.manager import sandbox_manager
from app.tools.registry import ToolContext, register
class PythonExecArgs(BaseModel):
code: str = Field(description="要执行的 Python 代码。用 print() 输出结果。")
timeout: int = Field(default=30, ge=1, le=300, description="超时秒数")
@register(
name="python_exec",
category="沙箱",
description=(
"在隔离沙箱里执行 Python 代码并返回 stdout/stderr。"
"默认断网、只读根文件系统,/workspace 可写且在同一次运行内持久。"
),
args_schema=PythonExecArgs,
dangerous=True,
)
async def python_exec(ctx: ToolContext, **kwargs: Any) -> dict[str, Any]:
args = PythonExecArgs(**kwargs)
result = await sandbox_manager.run(
args.code,
language="python",
limits=SandboxLimits(timeout=args.timeout),
session_id=ctx.sandbox_session,
)
return result.model_dump(exclude={"files_written"} if not result.files_written else set())
class ShellExecArgs(BaseModel):
command: str = Field(description="要执行的 shell 命令")
timeout: int = Field(default=30, ge=1, le=300)
@register(
name="shell_exec",
category="沙箱",
description="在隔离沙箱里执行 shell 命令。同样断网、非 root。",
args_schema=ShellExecArgs,
dangerous=True,
)
async def shell_exec(ctx: ToolContext, **kwargs: Any) -> dict[str, Any]:
args = ShellExecArgs(**kwargs)
result = await sandbox_manager.run(
args.command,
language="bash",
limits=SandboxLimits(timeout=args.timeout),
session_id=ctx.sandbox_session,
)
return result.model_dump()
class RunCodeArgs(BaseModel):
code: str
language: Literal["python", "bash", "node"] = "python"
timeout: int = Field(default=30, ge=1, le=300)
network: bool = Field(default=False, description="是否允许联网")
@register(
name="run_code",
category="沙箱",
description="在沙箱里执行任意支持语言的代码,可单独打开网络。",
args_schema=RunCodeArgs,
dangerous=True,
)
async def run_code(ctx: ToolContext, **kwargs: Any) -> dict[str, Any]:
args = RunCodeArgs(**kwargs)
result = await sandbox_manager.run(
args.code,
language=args.language,
limits=SandboxLimits(timeout=args.timeout, network=args.network),
session_id=ctx.sandbox_session,
)
return result.model_dump()