mirror of
https://github.com/ThearchyHelios/AgentLab.git
synced 2026-11-15 04:11:58 +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 个内置模板全部冒烟通过,含真实模型端到端验证。
79 lines
2.5 KiB
Python
79 lines
2.5 KiB
Python
from __future__ import annotations
|
|
|
|
import json
|
|
from datetime import datetime, timezone
|
|
from typing import Any
|
|
from zoneinfo import ZoneInfo
|
|
|
|
from pydantic import BaseModel, Field
|
|
|
|
from app.engine.expressions import ExpressionError, eval_expression, resolve_path
|
|
from app.tools.registry import ToolContext, register
|
|
|
|
|
|
class CalculatorArgs(BaseModel):
|
|
expression: str = Field(description="数学表达式,例如 (13*7 + 2) / 3")
|
|
|
|
|
|
@register(
|
|
name="calculator",
|
|
category="数据",
|
|
description="计算一个数学表达式。支持 + - * / // % ** 和 min/max/abs/round/sum 等函数。",
|
|
args_schema=CalculatorArgs,
|
|
)
|
|
async def calculator(ctx: ToolContext, **kwargs: Any) -> dict[str, Any]:
|
|
args = CalculatorArgs(**kwargs)
|
|
try:
|
|
value = eval_expression(args.expression, {})
|
|
except ExpressionError as e:
|
|
return {"error": str(e)}
|
|
except ZeroDivisionError:
|
|
return {"error": "除以零"}
|
|
return {"expression": args.expression, "result": value}
|
|
|
|
|
|
class CurrentTimeArgs(BaseModel):
|
|
timezone_name: str = Field(default="Asia/Shanghai", description="IANA 时区名")
|
|
|
|
|
|
@register(
|
|
name="current_time",
|
|
category="数据",
|
|
description="获取当前日期时间。模型不知道此刻是什么时候,需要时间信息时用这个。",
|
|
args_schema=CurrentTimeArgs,
|
|
)
|
|
async def current_time(ctx: ToolContext, **kwargs: Any) -> dict[str, Any]:
|
|
args = CurrentTimeArgs(**kwargs)
|
|
try:
|
|
tz = ZoneInfo(args.timezone_name)
|
|
except Exception: # noqa: BLE001
|
|
tz = timezone.utc
|
|
now = datetime.now(tz)
|
|
return {
|
|
"iso": now.isoformat(),
|
|
"date": now.strftime("%Y-%m-%d"),
|
|
"time": now.strftime("%H:%M:%S"),
|
|
"weekday": ["周一", "周二", "周三", "周四", "周五", "周六", "周日"][now.weekday()],
|
|
"timezone": str(tz),
|
|
}
|
|
|
|
|
|
class JsonQueryArgs(BaseModel):
|
|
data: str = Field(description="JSON 字符串")
|
|
path: str = Field(description="点号路径,例如 results.0.title 或 items[2].name")
|
|
|
|
|
|
@register(
|
|
name="json_query",
|
|
category="数据",
|
|
description="从 JSON 里按路径取值,避免让模型自己在长 JSON 里数括号。",
|
|
args_schema=JsonQueryArgs,
|
|
)
|
|
async def json_query(ctx: ToolContext, **kwargs: Any) -> dict[str, Any]:
|
|
args = JsonQueryArgs(**kwargs)
|
|
try:
|
|
parsed = json.loads(args.data)
|
|
except json.JSONDecodeError as e:
|
|
return {"error": f"JSON 解析失败:{e}"}
|
|
return {"path": args.path, "value": resolve_path({"root": parsed, **(parsed if isinstance(parsed, dict) else {})}, args.path)}
|