Vercel Eve 实际上手初探

vercel 发布了 github.com/vercel/eve , 一个新的 agent 开发框架.

eve 采用和 nextjs 一脉相承的设计思路: filesystem as agent structure------目录结构本身就是 agent 的结构.

初步印象

前身 ai-sdk

vercel 本来就有了这样一个 agent 框架: ai-sdk, 也是 eve 的底层框架 (负责接入 ai provider, agent loop, hook).

这个也是 vercel 在前三年持续投入资源的主力 ai 框架.

ai-sdk 的职责很明确: 接入大模型 API, 统一配置差异, 然后在 v6 版本引入了 AgentLoop. 和同一时期出现的 langgraph, llamaindex 等框架不同, 它专注于底层操作的抽象, 不搞过度的封装.

eve 能干的所有事情, 基于 ai-sdk 写代码都能干. 我已经用它搓了很多对话/agent 项目, 也尝试过其它框架 (pi / claude agent sdk), 最终长期选择 ai-sdk, 可以说 ai-sdk 在构建效率开发体验上做到了最佳.

脏活成本抽掉

到了 2026 年年中, 用 ai-sdk 开发 agent 项目的过程中, 一些工作已经开始重复:

  • Skills: 实现 load_skill tool
  • 工具封装: 把搜索 REST API 封装成 tool, 或者 await MCP server 加载之后转换为 tool
  • 手动调试链路: 暴露一个 Openai-compatible API 接入到 Cherry Studio / Kelivo 这类工具, 或者直接搓一个对话 web 页面
  • 设计 schema 与实现对话持久化: conversation(or session) - message

虽然这些都可以让 codex / claude code 来写, 但还是要自己测试调试, 模型的不确定性偶尔会让你卡住. 这些边缘工作的整体成本依然很高.

第一眼看到 eve 时, 我的猜想就是: 它的设计目的就是为了把这些脏活的成本归零.

One more layer---only now.

Vercel 没有在 2024/2025 就推出一款像 langchain / langgraph / llamaindex / mastra 那样的 all-in-one, 过度抽象的框架, 然后最终变成很重, 带很多过时包袱, 积重难返的笨重东西.

而是在今天 (2026 年 6 月), agent 形态已经形成了一套统一共识 (api call loop until finish_reason:stop, workspace, skills, tools, subagent) 的情况下,

才推出这个 **优雅, 一眼就吸引人, 目标是开发体验最好 / 最佳实践合理抽象 ** 的框架.

eve 的优势

封装常需功能

skills, subagents 这些, 本质是很简单的东西, 只要你懂它的原理, 就能容易实现, 但也要花一些功夫.

在 eve 中直接开箱即用, 而且比手动实现的更加优雅.

比如 skills, 手动实现的话:

  1. 要么把 skills 内容作为字符串写死在代码中
  2. 要么用 fs 库加载文件, 还要修改打包配置把 skill 文本文件打包进 dist 中

而有了 eve:

  1. 可以直接像openclaw/codex中一样在skills/目录下创建静态的 .md 和代码文件 (会被直接打包进运行时的 workspace/skills 目录下)
  2. 也可以在一个 skills/skill-a.ts 文件中 export default defineSkill({description, markdown, files}) 构建动态 skill 内容

方便调试

bash 复制代码
#npm run dev
eve dev

开发时直接通过 TUI 进行对话, tool_call, turn 完整显示, 并且支持改动后热重启 / 热加载, 不用 exec a .ts 或者通过额外的 web 界面对话.

实际测试就这个 TUI 的对话入口就节省了大量时间.

Channel

没错, 就是 xxClaw 类产品的 Channel 概念, 但是 Eve 的 Channel 更简单轻量, 职责单一, 无论是接入还是扩展开发, 用户体验都极佳.

它是事件驱动型:

  • slack @ 了一个 bot
  • github 发布了一个 issue 或者 comment @bot (通过 github app webhook)

通过 curl POST 暴露的 Openai-compatible API 或者前端发送消息也是一个 Channel. 没错, channel 被设计为统一 trigger 入口.

实现一个 Channel, 你只需要 adapt 以下 hook:

ts 复制代码
import { defineChannel, GET, POST } from "eve/channels";
export default defineChannel({
  routes: [
    POST("/message", async (req, { send }) => {
      const body = await req.json();
      const session = await send(body.message, {
        auth: null,
        continuationToken: body.token,
      });
      return Response.json({ sessionId: session.id });
    }),
    GET("/sessions/:sessionId/stream", async (_req, { getSession, params }) => {
      const session = getSession(params.sessionId);
      const stream = await session.getEventStream();
      return new Response(stream, {
        headers: { "content-type": "application/x-ndjson; charset=utf-8" },
      });
    }),
  ],
  events: {
    "message.completed"(event, channel, ctx) {
      // deliver completed messages back to the surface that owns this channel
    },
  },
});

它可以在接收到事件后回复, 本质是通过 ctx + channel 的信息组合执行具体行为, 比如收到 github issue 这个事件后的处理流程:

txt 复制代码
   github webhook                       eve runtime
 ┌───────┐               ┌──────────────────┐
 │  issue created  trigger──▶ │   channel: gh    │
 └───────┘               └────────┬─────────┘
                                             │
                                  ctx = { issueId, repo, ... }
                                             │
                                             ▼
                                    ┌──────────────────┐
                                    │   LLM generate   │
                                    └────────┬─────────┘
                                             │
                                   credentials (channel)
                                             │
                                             ▼
                                    ┌──────────────────┐
                                    │  POST comment to │
                                    │   issue(ctx.id)  │
                                    └──────────────────┘

不具备 openclaw 那种定时触发任务后主动向 channel 发送消息的能力, 不过可以把定时任务的结果"搭便车"发送到某些 channel:

ts 复制代码
export default defineSchedule({
  cron: "0 9 * * 1-5",
  async run({ receive, waitUntil, appAuth }) {
    waitUntil(
      receive(slack, {
        message: "Summarize yesterday's activity and post the digest.",
        target: { channelId: "C0123ABC" },
        auth: appAuth,
      }),
    );
  },
});

eval of agent

这个功能非常强大. 以往想要对开发的 agent 功能进行 eval, 往往是大量的手动工作:

  • 把暴露的 openai-compatible api 接入到 langfuse 等平台, 再在平台内手动创建一堆测试用例
  • 只能 evaluate 最终回复, agent 执行过程还要手动查看日志 / 查看 trace
  • 往往到最后还是回归了手动 chat, 手动检查执行步骤

而 eve 提供了:

  1. 约定的 evaluations 存放目录结构
php 复制代码
evals/
├── evals.config.ts
├── smoke.eval.ts
└── weather/
    ├── weather-tool.eval.ts
    └── seoul-forecast-result-judge.eval.ts
  1. eval CLI
bash 复制代码
eve eval                          # 跑全部
eve eval weather smoke            # 按 id 或目录前缀筛选

有了 eval, 终于可以开箱即用的评测构建的 agent 了!

示例代码:

ts 复制代码
import { defineEval } from "eve/evals";
import { includes } from "eve/evals/expect";

export default defineEval({
  description:
    "Weather agent calls get_weather (after approval) and surfaces the forecast.",
  async test(t) {
    await t.send("首尔天气如何?");

    t.calledTool("get_weather"); //assert: 必须调用这个 tool
    t.completed();//assert: 必须正常结束
    t.check(t.reply ?? "", includes("72")); //assert: 结果中 text 必须包含 "72" 字符串
    t.judge.autoevals.closedQA('必须是72°F的, 必须包含首尔') //llm judge: 是否完成目标, 输出 "Y" or "N"
    // t.judge.autoevals.factuality('xxx') //llm judge: 完成程度, 输出小数
  },
});

Sandbox 支持

我尝试过手搓类似的功能, 但是和 skills, subagent 这些可以几句话让 claude 直出的功能不同, 这一块的实现目前 (2026 年 6 月) 还是一个成本黑洞, 很难凭借 codex / claude 稳稳搞定. 想要实现一套稳定 / 健壮的 Agent 隔离运行模式, 没有可预测的排期, 也很容易推倒设计重来.

eve 的 Sandbox 在设计上非常合理:

  • agent build 后运行在一个后端 (因为只有 HTTP Server 的职责, 所以无需隔离)
  • tool_call 运行在 sandbox 中 (通过 key 隔离, key 是标识符, 比如 github issue id, slack 对话 id)

并且有硬安全 (目前仅 vercel sandbox, microsandbox 两种 sandbox 环境支持):

  • credentials 不注入 sandbox 的环境变量 (xxClaw 现在做的, 安全隐患很大), 而是给 sandbox 发出的 HTTP 请求加上 Authorization 头

当前的 sandbox 一共支持四种 backend (运行环境):

  • vercel sandbox (自家云服务生态)
  • docker
  • microsandbox
  • just-bash (本机直接运行, 模拟的假 sandbox)

tool 在 sandbox 中运行是必须的, 底层设计就是必须在一个 sandbox 环境中运行, 默认的 backend 是从上方列表中的 4 个从上到下选择一个能用的.

Sandbox 想一想就知道底层实现非常复杂. microsandbox 应该是未来的刚需和第一支持 (只有它支持细粒度网络配置和 auth 隔离), 但是现在用起来有点问题. 现在 dockerjust-bash 用着没什么问题. 还有一个目前的问题是: 现在的 dockermicrosandbox backend 都不支持自动结束, 启动后就会一直运行.

eve 中的难受之处

单agent入口

如果是用 ai-sdk 在 nextjs 项目中开发 agent, 因为是自己手搓的, 所以可以开发多个同时存在. 但是 eve 在设计上就是一个项目一个入口, 所以无法支持一个项目暴露三个 path 作为三个不同 agent 的入口.

能想到的方法:

  1. 通过 subagents 区分: 虽然 subagents 的构建过程和主 agent 完全一模一样 (没有阉割, 不像 claude code 中只是一个提示词文件 😵‍💫), 但是也要过一遍主 agent 的对话再由主 agent 分配给子 agent 执行
  2. turborepo monorepo: 增加了复杂度

step hooks 模式变更

ai-sdkstreamText() 中, 可以直接控制各种 hook 做很多细粒度的控制:
chat.ts

ts 复制代码
// 透明直控: 在 streamText() 调用前后随意编排 DB / 鉴权 / 上限 / 落库
chatRoute.post("/", async (c) => {
  const { message, conversationId: incomingId } = await c.req.json();
  const userId = c.get("userUuid");

  // 1. 取/建会话
  const { id: conversationId, isNew } = await ensureConversation({
    conversationId: incomingId,
    userId,
  });

  // 4. 按 session id 查库拉全历史 -> ModelMessage (关键步骤: exec 前完成)
  const history = await getMessages({ conversationId, userId });
  const modelMessages = await convertToModelMessages(history);


  // 5. streamText 透明拿到 messages / tools / prepareStep / onFinish
  const result = streamText({
    model: deepseekV4Flash,
    system: SYSTEM_PROMPT,
    messages: modelMessages,
    tools: { search: crateBraveSearch() },
    stopWhen: stepCountIs(MAX_STEPS + 1),
    onFinish: ({ finishReason, totalUsage }) => {
      // lastFinishReason = finishReason;
      // lastUsage = totalUsage;
    },
    // 细粒度 step 控制: 达到上限 step 强制 toolChoice:none + 注入收尾指令
    prepareStep: ({ stepNumber, messages: stepMessages }) => {
      if (stepNumber >= MAX_STEPS) {
        return {
          toolChoice: "none",
          messages: [
            ...stepMessages,
            { role: "assistant", content: "工具调用已达上限, 直接出最终回复." },
          ],
        };
      }
    },
  });

  return result.toUIMessageStreamResponse({
    originalMessages: history,
    messageMetadata: ({ part }) => {
      if (part.type === "start") return { conversationId, isNewConversation: isNew };
      if (part.type === "finish")
        return { finishReason: part.finishReason, totalTokens: part.totalUsage?.totalTokens };
    },
    onFinish: async ({ messages: finalMessages, isAborted }) => {
      //.......
    },
  });
});

但是由于 eve 是新的架构, 这些操作不得不变成了"全局旁路"的方式, 没有原来直接操作底层 streamText() 那么透明:

ts 复制代码
import { defineHook } from "eve/hooks";
export default defineHook({
  events: {
    async "session.started"(_event, ctx) {
      console.info("session started", { sessionId: ctx.session.id });
    },
    async "message.completed"(event) {
      console.info("model finished", { length: event.data.message?.length ?? 0 });
    },
  },
});

对比

vs mastra, langchain, langgraph, deepagents

mastra / langchain / langgraph 太重了, 把所有东西都塞进去.

eve 显得很轻量, 抽象的恰到好处.

langchain.js / langgraph.js / deepagents.js 只是python主项目 的 javascript 复刻, 有一种"非原生"的使用感觉, 缺少 ai-sdk / eve 那种轻量感. 然后它们的前后端交互协议 相比 vercel 出品的那肯定也是差远了.

vs xxClaw, hermes

相当大一部分人在 xxClaw 和 hermes 上构建出了他们需要的 agent 功能, 主要通过扩展 skill 的方式. 这种模式现在的问题是太混沌了:

  • skill 提供的 cli 或者 bash 命令很容易运行失败
  • 想要扩展新功能, 调试跑通成本很高
  • 所有功能都放在一起, token wasting machine

对于开发者和有一定 vibe coding 能力的非技术人员来说, eve 是可以完美替代 xxClaw, hermes 功能的开发框架. 这种开发框架相比这些支持扩展的成品:

  1. 非常清晰, 开发体验极度舒适, 极低开发调试成本的 build 过程
  2. build 出来的功能运行更加稳定

vs pi, opencode

pi-coding-agent ➕ 开源 web 界面 或者 opencode (比 claude code 开放太多, 自带一个成熟的 web 界面支持远程访问), 可以通过扩展的方式定制为一个特定功能的 agent, 相比 ai-sdk 从零手搓节省了不少搭建 agent core 的初始成本, 可以直接本地运行也可以打包为镜像作为一个 build 成品 (打包意味着可复制可规模化).

但是这种套壳方式不支持更 advanced 的定制化功能, 比如:

  1. 自定义 tool execution 的可视化展示
  2. 作为一个侧边栏, 嵌入区域而不是完整的页面
  3. 给 tool 传入 ctx

有了 eve, 完全没必要使用 pi-coding-agent 或者 opencode 来节省基础设施搭建成本了. eve 自带这些基础设施且是可以任意开发的操作代码的东西.

至于 pi-agent-core , 适合和 ai-sdk 类似定位但完全不同架构的东西.

vs claude-agent-sdk

claude-agent-sdk 是和 ai-sdk 类似的定位, 但是更加简单易用 (傻瓜式) 意味着更少的可开发空间.

vs claude code or codex

不是同类定位. claude code or codex 是让人使用的成品工具, eve 是开发框架.

相关推荐
贵慜_Derek1 小时前
复杂系统没法一把梭重构:Semi-Autoresearch 怎么小步迁移还不掉功能
人工智能·agent·ai编程
用户5191495848451 小时前
利用ShellcodePack实现DLL劫持与COM对象劫持技术详解
人工智能·aigc
武子康1 小时前
调查研究-195 从 AmEx 支付系统看 Cell-based Architecture:真正的高可用,不是无限重试,而是控制失败边界
人工智能·openai·agent
米小虾1 小时前
Prompt Engineering —— 意图的精确表达
人工智能·agent
IT_陈寒2 小时前
React状态更新总是慢半拍?你可能忘了这个默认行为
前端·人工智能·后端
aneasystone本尊2 小时前
学习 turbovec 的混合检索与框架集成
人工智能
火山引擎开发者社区13 小时前
火山AgentPlan/CodingPlan同步上线GLM-5.2
人工智能
冬奇Lab13 小时前
Skill 系列(05):Skill 工作流串联——4 种模式实测,并发加速 1.5x
人工智能·开源
冬奇Lab14 小时前
每日一个开源项目(第141篇):hiring-agent - HackerRank 开源了他们的简历评分系统,你的简历能得几分?
人工智能·面试·开源