mirror of
https://github.com/ThearchyHelios/AgentLab.git
synced 2026-11-15 05:51:57 +00:00
964eeaa829
在画布上拖拽编排 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 个内置模板全部冒烟通过,含真实模型端到端验证。
76 lines
2.1 KiB
Python
76 lines
2.1 KiB
Python
from __future__ import annotations
|
||
|
||
import abc
|
||
from typing import Any
|
||
|
||
from pydantic import BaseModel, Field
|
||
|
||
from app.core.config import settings
|
||
|
||
|
||
class SandboxLimits(BaseModel):
|
||
timeout: int = Field(default_factory=lambda: settings.sandbox_timeout)
|
||
memory_mb: int = Field(default_factory=lambda: settings.sandbox_memory_mb)
|
||
cpus: float = Field(default_factory=lambda: settings.sandbox_cpus)
|
||
network: bool = Field(default_factory=lambda: settings.sandbox_network)
|
||
max_output: int = Field(default_factory=lambda: settings.sandbox_max_output)
|
||
|
||
|
||
class ExecResult(BaseModel):
|
||
ok: bool = True
|
||
exit_code: int = 0
|
||
stdout: str = ""
|
||
stderr: str = ""
|
||
duration_ms: int = 0
|
||
timed_out: bool = False
|
||
truncated: bool = False
|
||
backend: str = ""
|
||
files_written: list[str] = Field(default_factory=list)
|
||
error: str | None = None
|
||
|
||
def summary(self) -> str:
|
||
if self.timed_out:
|
||
return f"执行超时(>{'?'}s)"
|
||
if self.error:
|
||
return self.error
|
||
return self.stdout or self.stderr or "(无输出)"
|
||
|
||
|
||
class Sandbox(abc.ABC):
|
||
"""代码执行后端的统一接口。"""
|
||
|
||
name: str = "base"
|
||
|
||
@abc.abstractmethod
|
||
async def run(
|
||
self,
|
||
code: str,
|
||
*,
|
||
language: str = "python",
|
||
limits: SandboxLimits | None = None,
|
||
session_id: str | None = None,
|
||
env: dict[str, str] | None = None,
|
||
files: dict[str, str] | None = None,
|
||
) -> ExecResult:
|
||
"""执行一段代码。session_id 相同的调用共享同一个文件系统,
|
||
这样 agent 可以先写文件、再在下一步读回来。"""
|
||
|
||
async def health(self) -> dict[str, Any]:
|
||
return {"backend": self.name, "available": True}
|
||
|
||
async def cleanup(self, session_id: str) -> None:
|
||
return None
|
||
|
||
async def close(self) -> None:
|
||
return None
|
||
|
||
|
||
def truncate(text: str, limit: int) -> tuple[str, bool]:
|
||
if len(text) <= limit:
|
||
return text, False
|
||
half = limit // 2
|
||
return (
|
||
text[:half] + f"\n\n…(省略 {len(text) - limit} 字符)…\n\n" + text[-half:],
|
||
True,
|
||
)
|