使用 Next.js、Supabase 和 Cron 构建智能体驱动应用(2026)
在2026年使用Next.js、Supabase和Cron构建Agent驱动的应用程序
在过去的半年里,我沉浸在为客户构建三个agent驱动的应用程序中。其中两个已经完全上线,每天自主处理数千个任务。那么,是什么在推动这些项目呢?是Next.js部署在Vercel上、Supabase用于持久化和实时需求,以及良好的基于cron的编排来保持agent对齐并防止过度API费用的强大组合。这不仅仅是理论——我会详细说明什么有效,陷阱是什么,以及我在深入之前希望拥有的所有见解。

2026年Agent驱动的应用程序实际上是什么样子
当我谈论"agent驱动的应用程序"时,我不仅仅是在讨论一个重复系统提示的聊天机器人。想象一下自主处理多步任务、做出实时数据驱动决策并按计划运行的软件,所有这些都无需人工推动。
这是在生产环境中运行的内容的一个示例:
- 内容管道扫描RSS源、生成摘要、起草社交帖子并将其排队等待审查。这个周期每30分钟重复一次,就像钟表一样。
- 数据协调agent每晚连接到多个API,嗅出不一致之处,尝试自动更正修复,并上报它们无法解决的问题。
- 客户入职agent由新注册webhook触发。这些agent配置资源、发送自定义序列,并根据他们学到的用户行为进行适应。
共同的线索是什么?他们需要状态持久化、计划执行、工具访问和人类查看和干预的方式。这是我们深入探讨的堆栈的最佳位置。
为什么这个堆栈有效
我已经尝试过其他设置。有LangChain配对Python后端,Temporal用于编排,以及自定义AWS队列系统。虽然它们都能完成工作,但它们带来了大多数团队到2026年就无法处理的大量操作复杂性。
这是我的直接比较:
| 堆栈 | 设置时间 | 操作开销 | 每天10K任务的成本 | 最适合 |
|---|---|---|---|---|
| Next.js + Supabase + Vercel Cron | 1-2天 | 低 | ~$85-150/月 | 大多数agent应用 |
| Python + Celery + Redis + AWS | 3-5天 | 高 | ~$200-400/月 | 重型计算任务 |
| Temporal + Custom Workers | 5-10天 | 非常高 | ~$300-600/月 | 复杂工作流 |
| Inngest + Next.js | 1-2天 | 低-中 | ~$100-200/月 | 事件驱动agent |
Next.js组合在速度方面绝对胜出。您可以获得无服务器函数、仪表板UI、数据库、身份验证、实时更新和定时执行,而无需为基础设施担忧。对于我们中绝大多数构建agent应用的人来说,那已经足够了。
架构概述
让我们绘制一幅在各个项目中都大受欢迎的架构图:
┌─────────────────┐ ┌──────────────────┐
│ Vercel Cron │────▶│ Next.js API Route │
│ (Scheduler) │ │ (Agent Runner) │
└─────────────────┘ └────────┬───────────┘
│
┌────────────┼────────────┐
▼ ▼ ▼
┌──────────┐ ┌─────────┐ ┌───────────┐
│ Supabase │ │ LLM API │ │ External │
│ (State + │ │(OpenAI/ │ │ Tools & │
│ Queue) │ │ Claude) │ │ APIs │
└──────────┘ └─────────┘ └───────────┘
│
▼
┌──────────┐
│ Supabase │
│ Realtime │──▶ Dashboard UI
└──────────┘
cron作业到达Next.js API路由,然后从Supabase拉取任务,执行agent逻辑,将结果写回,并完成。同时,您的仪表板通过Supabase Realtime提供实时更新来关注行动。
是的,足够简单,但魔鬼在于细节。

使用Next.js和Vercel建立基础
当您在2026年构建agent应用程序时,Next.js 15与App Router是必须的。由于其Server Actions和Route Handlers,它为您提供最干净的执行模式。在Social Animal,我们已经完善了这个Next.js技能,让我告诉您,App Router对于后端密集型项目来说已经成熟得非常好。
启动项目很直接:
pnpx create-next-app@latest agent-app --typescript --tailwind --app
cd agent-app
pnpm add @supabase/supabase-js ai @ai-sdk/openai
我正在使用Vercel AI SDK,感谢它在不同LLM提供商中的统一接口。到2026年,ai包的版本4.x原生支持结构化输出、工具调用和流——LangChain你好吗?
现在您的vercel.json需要看起来像这样的cron配置:
{
"crons": [
{
"path": "/api/agents/run",
"schedule": "*/15 * * * *"
},
{
"path": "/api/agents/cleanup",
"schedule": "0 2 * * *"
}
]
}
第一个cron每15分钟运行一次agent。第二个每晚清理。请记住,Vercel Pro($20/月)支持最高1分钟频率的cron作业。但是如果您在Hobby计划中,您只能限于每日cron,这对大多数agent驱动的需求来说是不够的。
Vercel函数配置
Agent任务可能比夏日大片还长(好吧,不完全是),但默认情况下需要超过10秒执行。像这样配置您的路由:
// app/api/agents/run/route.ts
export const maxDuration = 300; // Pro计划上5分钟
export const dynamic = 'force-dynamic';
export async function GET(request: Request) {
// 验证cron密钥以防止未授权访问
const authHeader = request.headers.get('authorization');
if (authHeader !== `Bearer ${process.env.CRON_SECRET}`) {
return new Response('Unauthorized', { status: 401 });
}
// Agent逻辑放在这里
return Response.json({ success: true });
}
那个maxDuration?这是至关重要的。Pro上最多300秒,如果您在Enterprise水域中游泳则900秒。任何超过5分钟的常规任务?是时候分段或重新考虑您的执行模型了。
Supabase作为您的Agent的大脑
Supabase——一个强大的工具。它充当您的任务队列、状态存储、审计日志和实时更新的通知程序。这是我随着时间推移精细调整的模式:
-- Agent任务队列
CREATE TABLE agent_tasks (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
agent_type TEXT NOT NULL,
status TEXT NOT NULL DEFAULT 'pending'
CHECK (status IN ('pending', 'running', 'completed', 'failed', 'cancelled')),
priority INTEGER DEFAULT 0,
payload JSONB NOT NULL DEFAULT '{}',
result JSONB,
error TEXT,
attempts INTEGER DEFAULT 0,
max_attempts INTEGER DEFAULT 3,
locked_until TIMESTAMPTZ,
scheduled_for TIMESTAMPTZ DEFAULT NOW(),
started_at TIMESTAMPTZ,
completed_at TIMESTAMPTZ,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- 队列轮询查询的索引
CREATE INDEX idx_agent_tasks_queue ON agent_tasks (
agent_type, status, priority DESC, scheduled_for
) WHERE status = 'pending';
-- Agent执行日志以用于可观测性
CREATE TABLE agent_logs (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
task_id UUID REFERENCES agent_tasks(id),
level TEXT NOT NULL DEFAULT 'info',
message TEXT NOT NULL,
metadata JSONB DEFAULT '{}',
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- Agent状态持久化(对于多步agent)
CREATE TABLE agent_state (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
agent_type TEXT NOT NULL,
agent_instance_id TEXT NOT NULL,
state JSONB NOT NULL DEFAULT '{}',
updated_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(agent_type, agent_instance_id)
);
那个locked_until列?这是关键。它模拟了一个临时的分布式锁。这意味着当cron作业选择一个任务时,它将locked_until向前设置5分钟。如果进程崩溃,锁过期,另一个周期可以再试一次。
任务拾取查询
这是真正的妙处。这是您无需重新处理就能抓取任务的黄金机会:
import { createClient } from '@supabase/supabase-js';
const supabase = createClient(
process.env.SUPABASE_URL!,
process.env.SUPABASE_SERVICE_ROLE_KEY!
);
async function claimTasks(agentType: string, limit: number = 5) {
const { data: tasks, error } = await supabase
.rpc('claim_agent_tasks', {
p_agent_type: agentType,
p_limit: limit,
p_lock_duration: '5 minutes'
});
if (error) throw error;
return tasks;
}
以及匹配的Postgres函数:
CREATE OR REPLACE FUNCTION claim_agent_tasks(
p_agent_type TEXT,
p_limit INTEGER,
p_lock_duration INTERVAL
)
RETURNS SETOF agent_tasks AS $$
BEGIN
RETURN QUERY
UPDATE agent_tasks
SET
status = 'running',
locked_until = NOW() + p_lock_duration,
started_at = NOW(),
attempts = attempts + 1,
updated_at = NOW()
WHERE id IN (
SELECT id FROM agent_tasks
WHERE agent_type = p_agent_type
AND status = 'pending'
AND scheduled_for <= NOW()
AND (locked_until IS NULL OR locked_until < NOW())
AND attempts < max_attempts
ORDER BY priority DESC, scheduled_for ASC
LIMIT p_limit
FOR UPDATE SKIP LOCKED
)
RETURNING *;
END;
$$ LANGUAGE plpgsql;
FOR UPDATE SKIP LOCKED是这里的无名英雄。它确保没有两个并发调用互相践踏。相信我,发现和修复重复处理不是一件愉快的事。
基于Cron的Agent编排
Vercel Cron是整个操作的稳定鼓声。每15分钟,您的agent运行程序春风吹拂,抓取任务,处理它们,然后在下一次调用之前打盹。
以下是完整的agent运行程序路由的样子:
// app/api/agents/run/route.ts
import { claimTasks, completeTask, failTask, logAgent } from '@/lib/agents';
import { runContentAgent } from '@/lib/agents/content';
import { runReconciliationAgent } from '@/lib/agents/reconciliation';
export const maxDuration = 300;
const AGENT_RUNNERS: Record<string, (task: AgentTask) => Promise<any>> = {
'content-pipeline': runContentAgent,
'data-reconciliation': runReconciliationAgent,
};
export async function GET(request: Request) {
const authHeader = request.headers.get('authorization');
if (authHeader !== `Bearer ${process.env.CRON_SECRET}`) {
return new Response('Unauthorized', { status: 401 });
}
const results = { processed: 0, failed: 0, skipped: 0 };
for (const [agentType, runner] of Object.entries(AGENT_RUNNERS)) {
const tasks = await claimTasks(agentType, 5);
for (const task of tasks) {
try {
await logAgent(task.id, 'info', `Starting ${agentType} task`);
const result = await runner(task);
await completeTask(task.id, result);
results.processed++;
} catch (error) {
const message = error instanceof Error ? error.message : 'Unknown error';
await failTask(task.id, message);
await logAgent(task.id, 'error', message);
results.failed++;
}
}
}
return Response.json(results);
}
注意每个agent类型的顺序处理,每次运行最多5个任务。这有助于保持函数执行时间在控制范围内。有50个待处理任务的堆积?在几个cron周期中分散它们。没有急事,没问题。
当Cron不够时
有时生活会快速袭来,cron就不够用了。在这些情况下,我将cron与Supabase数据库Webhook或Edge Functions集成——它们由数据库更改触发:
-- 当创建高优先级任务时触发edge函数
CREATE OR REPLACE FUNCTION notify_urgent_task()
RETURNS TRIGGER AS $$
BEGIN
IF NEW.priority > 5 THEN
PERFORM net.http_post(
url := 'https://your-app.vercel.app/api/agents/urgent',
headers := jsonb_build_object(
'Authorization', 'Bearer ' || current_setting('app.webhook_secret')
),
body := jsonb_build_object('task_id', NEW.id)
);
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
这个设置让您混合可预测的基于cron的处理与紧急任务的快速响应。
构建Agent循环
这是魔法发生的地方。使用Vercel AI SDK工具调用的示例agent实现:
// lib/agents/content.ts
import { generateText, tool } from 'ai';
import { openai } from '@ai-sdk/openai';
import { z } from 'zod';
import { supabase } from '@/lib/supabase';
export async function runContentAgent(task: AgentTask) {
const { sourceUrl, targetPlatform } = task.payload;
const { text, toolCalls, usage } = await generateText({
model: openai('gpt-4o-mini'),
system: `You are a content processing agent. Fetch the source content,
analyze it, and generate appropriate social media content for ${targetPlatform}.
Use the provided tools to complete your task.`,
prompt: `Process this source: ${sourceUrl}`,
tools: {
fetchContent: tool({
description: 'Fetch content from a URL',
parameters: z.object({ url: z.string().url() }),
execute: async ({ url }) => {
const res = await fetch(url);
const html = await res.text();
// Strip HTML, extract main content
return extractMainContent(html);
},
}),
saveContent: tool({
description: 'Save generated content for review',
parameters: z.object({
title: z.string(),
body: z.string(),
platform: z.string(),
suggestedPostTime: z.string(),
}),
execute: async (content) => {
const { data, error } = await supabase
.from('generated_content')
.insert({
...content,
task_id: task.id,
status: 'pending_review',
})
.select()
.single();
if (error) throw error;
return { saved: true, id: data.id };
},
}),
},
maxSteps: 5, // 允许最多5个工具调用轮次
});
return {
summary: text,
toolCallCount: toolCalls.length,
tokensUsed: usage.totalTokens,
};
}
设置maxSteps为5让LLM调用工具、评估结果并相应地处理——就像一个小工蜂在做它的事情。大多数内容任务在3–4个这样的调用中完成。
使用Supabase Realtime进行实时Agent监控
自主agent很好,但监控它们是最重要的。Supabase Realtime为仪表板提供动力,该仪表板在agent完成工作时实时更新:
// components/AgentDashboard.tsx
'use client';
import { useEffect, useState } from 'react';
import { createClient } from '@supabase/supabase-js';
const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
);
export function AgentDashboard() {
const [tasks, setTasks] = useState<AgentTask[]>([]);
const [logs, setLogs] = useState<AgentLog[]>([]);
useEffect(() => {
// 订阅任务更改
const taskChannel = supabase
.channel('agent-tasks')
.on(
'postgres_changes',
{ event: '*', schema: 'public', table: 'agent_tasks' },
(payload) => {
setTasks(prev => {
const updated = [...prev];
const idx = updated.findIndex(t => t.id === payload.new.id);
if (idx >= 0) updated[idx] = payload.new as AgentTask;
else updated.unshift(payload.new as AgentTask);
return updated.slice(0, 50);
});
}
)
.subscribe();
// 订阅日志流
const logChannel = supabase
.channel('agent-logs')
.on(
'postgres_changes',
{ event: 'INSERT', schema: 'public', table: 'agent_logs' },
(payload) => {
setLogs(prev => [payload.new as AgentLog, ...prev].slice(0, 100));
}
)
.subscribe();
return () => {
supabase.removeChannel(taskChannel);
supabase.removeChannel(logChannel);
};
}, []);
// 呈现您的仪表板UI...
}
这提供了agent活动的实时流。通常,我会添加指标、任务计数、错误率视觉效果,甚至一个"立即运行"按钮用于测试。Supabase Realtime包含在免费层中——无需额外付费。
处理故障和边界情况
为此做好准备——某些东西会爆炸。API消失不见,速率限制迎面击中您,或者服务只是发送回无意义的数据。这是我关于构建存活系统所学到的。
带指数退避的重试
我们整合的attempts和max_attempts列处理重试。如果任务失败,它停留在"失败"状态,但稍后可以手动或自动设置为重试:
async function failTask(taskId: string, error: string) {
const { data } = await supabase
.from('agent_tasks')
.select('attempts, max_attempts')
.eq('id', taskId)
.single();
const shouldRetry = data && data.attempts < data.max_attempts;
await supabase
.from('agent_tasks')
.update({
status: shouldRetry ? 'pending' : 'failed',
error,
locked_until: null,
// 指数退避:1分钟、4分钟、9分钟...
scheduled_for: shouldRetry
? new Date(Date.now() + Math.pow(data.attempts, 2) * 60000).toISOString()
: undefined,
updated_at: new Date().toISOString(),
})
.eq('id', taskId);
}
外部API的断路器
当外部API摇摇欲坠时,立即重试快速消耗重试。在Supabase中跟踪故障率,如果情况看起来严峻,您可以停止agent:
async function checkCircuitBreaker(service: string): Promise<boolean> {
const fiveMinutesAgo = new Date(Date.now() - 300000).toISOString();
const { count } = await supabase
.from('agent_logs')
.select('*', { count: 'exact', head: true })
.eq('level', 'error')
.ilike('message', `%${service}%`)
.gte('created_at', fiveMinutesAgo);
return (count ?? 0) < 10; // 如果5分钟内超过10个错误则断开
}
成本分析和优化
这是我们谈论实际情况的地方。在2026年运行生产agent应用程序需要多少成本?
| 组件 | 免费层 | 典型生产 | 重型使用 |
|---|---|---|---|
| Vercel Pro | - | $20/月 | $20/月 + 使用量 |
| Supabase Pro | - | $25/月 | $25/月 + 使用量 |
| OpenAI (GPT-4o-mini) | - | $30-80/月 | $200+/月 |
| Anthropic (Claude 3.5 Haiku) | - | $20-50/月 | $150+/月 |
| 总计 | ~$0 (开发) | $95-175/月 | $395+/月 |
那些LLM费用?他们掌管全局。以下是如何控制它们:
- 在可能的地方使用较小的模型。 GPT-4o-mini和Claude 3.5 Haiku以其较大同类产品的一小部分成本处理大多数任务。
- 在达到API之前缓存。 Supabase可以存储LLM响应,减少新API调用。
- 批处理、批处理、批处理。 而不是每个任务一个API调用,批处理相似的任务并将它们一起发送。
- 令牌限制?是的,请。 在您的AI SDK调用期间始终设置
maxTokens——防止肆无忌惮的生成。
生产部署模式
一旦您准备好部署,有几件事是至关重要的:
环境分离。 利用Supabase分支(需要Pro)为暂存和生产维护离散数据库。将Vercel预览部署与Supabase分支配对以完全隔离。
监控。 Vercel的可观测性提供cron执行历史和函数日志。将其与Supabase健康检查配对:
SELECT
agent_type,
status,
COUNT(*) as count,
AVG(EXTRACT(EPOCH FROM (completed_at - started_at))) as avg_duration_seconds
FROM agent_tasks
WHERE created_at > NOW() - INTERVAL '24 hours'
GROUP BY agent_type, status
ORDER BY agent_type, status;
秘密管理。 使用Vercel环境变量保护您的API密钥,永远不要对其进行硬编码。仅在服务器端使用Supabase的服务角色密钥,在客户端保持匿名密钥。
如果您需要帮助设置像这样的系统,我们的团队使用完全相同的堆栈交付了多个agent驱动的应用程序。查看我们的headless CMS开发和Next.js开发功能,或联系我们讨论您的项目。
常见问题
Vercel Cron能处理高频agent任务吗?
Vercel Cron可以管理Pro计划上的最短1分钟间隔。大多数agent任务适合5-15分钟周期。需要更快?考虑Supabase数据库Webhook或切换到Inngest用于事件驱动操作。
我如何防止使用cron重复任务处理?
这是PostgreSQL的FOR UPDATE SKIP LOCKED在您的任务声明查询中大放光彩的地方。即使cron计时重叠,它也是您的安全网,确保每个任务都是唯一声明的。locked_until如果执行中灾难发生,增加一层安全。
Supabase对生产agent工作负载可靠吗?
绝对的。Supabase Pro在专用Postgres实例上运行,具有自动备份和99.9% SLA的正常运行时间。实际上,对于这个任务队列系统,Postgres是完全合格的——能够轻松处理并发访问和复杂查询。
在生产环境中运行AI agent需要花费多少?
通常,每天处理5,000-10,000个任务将花费$95-175/月(Vercel Pro + Supabase Pro)加上$30-80/月用于LLM API调用,使用GPT-4o-mini等模型。变量层是您的LLM成本——取决于任务复杂性和令牌使用。
我应该对agent逻辑使用LangChain还是Vercel AI SDK?
到2026年,Vercel的AI SDK(v4.x)原生涵盖大多数agent需求——工具使用、多步推理、结构化输出和流。LangChain在您深入高级思维链模式、RAG设置或专门工具集成时变得有用。对于这个堆栈,AI SDK的更少复杂性统治至尊。
我可以使用Astro而不是Next.js来实现这个模式吗?
Astro可以处理cron触发的API路由,但您会错过Vercel的优化,包括cron配置和更长的超时。如果您的仪表板喜欢内容密集、低交互性,Astro适合前端。对于您的agent后端坚持Next.js。
我如何监控agent性能和故障?
混合三个层:Vercel的日志用于执行级洞察,Supabase中的agent_logs表用于应用程序日志跟踪,Supabase Realtime用于动态仪表板监控。为故障、执行时间和队列长度建立健康检查。Vercel无缝与PagerDuty和Slack等警报服务同步。
当我的agent任务超过Vercel的函数超时时会发生什么?
在Vercel Pro上,您有300秒的上限。如果任务经常超过这个,考虑在cron周期中将它们分解成小任务。例如,将内容过程分为"获取"、"分析"和"生成"任务。或者,探索Supabase Edge Functions以进行扩展执行——它们有150秒的限制,可以根据需要触发。