@addyosmani:你知道刷新页面会让 AI 聊天在响应中途中断吗?@triggerdotdev 的新聊天代理解决了这个问题,使其能存活…
摘要
Trigger.dev 的新聊天代理让开发者能够构建持久、有状态的 AI 聊天体验,这些体验在刷新和崩溃后依然存活,没有超时限制,并且可以在危险工具调用前暂停以请求许可。
查看缓存全文
缓存时间: 2026/08/10 17:34
你知道刷新页面会中断 AI 聊天的半途响应吗?@triggerdotdev 的新 chat agent 解决了这个问题——它能在崩溃、重新部署后继续运行,还可以在工具执行危险操作前暂停并请求许可。值得一看:https://t.co/ljbyVvqzYH https://t.co/8A6N5BYFCb
介绍 chat agent
来源:https://trigger.dev/changelog/chat-agent?utm_source=fnf&utm_medium=x&utm_campaign=august&utm_term=aosmani&utm_content=chat-agent-launch
Chat agent 是一种构建持久化 AI 聊天体验的方式,它运行在一台没有超时限制的机器上,并且能在刷新页面和崩溃后持续流式输出。没人打字时机器会休眠,然后从上次中断的地方恢复运行,你完全不需要管理任何状态。
这台机器在第一条消息到达时启动,运行时间取决于工作完成所需的时间,并在每轮对话之间将所有内容保存在内存中:你的变量、你的缓存、你四个问题之前生成的子代理。你将每一轮对话编写为一个 Trigger.dev 任务,接收消息并返回一个流,然后让 AI SDK 的 useChat 直接指向它。中间不需要 API 路由,整个后端就是这样的:
import { chat } from "@trigger.dev/sdk/ai";
import { streamText, stepCountIs } from "ai";
import { anthropic } from "@ai-sdk/anthropic";
export const myChat = chat.agent({
id: "my-chat",
run: async ({ messages, signal }) => {
return streamText({
model: anthropic("claude-sonnet-4-5"),
messages,
abortSignal: signal,
stopWhen: stepCountIs(15),
});
},
});
Arena 基于 chat.agent 构建了 Agent Mode(https://x.com/arena/status/2062565126600114484),并在生产环境中大规模运行。
“每段对话都有一台真实的机器,这让我们的持久化代理构建起来简单得多。内置的追踪和可观测性让查看和调试代理会话变得非常容易。”
Graham Tremper Arena
他们不是唯一的使用者。chat.agent 自 7 月 2 日起正式全面可用,6 月起已投入生产运行,到目前为止已经服务了数百万个会话和超过 84 年的计算时长。
为什么 chat.agent 更好
大多数聊天后端将工作与请求绑定在一起,而普通的 API 端点并不适合聊天代理(或者说任何代理,但那是另一个故事了)。请求/响应周期通常有超时限制,而且是无状态的。你必须通过将所有内容写入数据库、使用 Redis 来实现持久化流、将慢速工作排队到后台 worker 并自行协调来对抗这些限制。
如果你的模型响应时间超过了函数允许的运行时间,祝你好运。或者某个环节失败了,你想要重试。或者你想生成一个自主思考的子代理。或者你想关掉浏览器,几天后再回来。有状态的机器要合适得多:
一台真正的 Linux 机器(https://trigger.dev/changelog/chat-agent?utm_source=fnf&utm_medium=x&utm_campaign=august&utm_term=aosmani&utm_content=chat-agent-launch#a-real-linux-machine)。 想装什么就装什么,运行任何 CLI,自选 CPU 和内存。
持久化计算,持久化流(https://trigger.dev/changelog/chat-agent?utm_source=fnf&utm_medium=x&utm_campaign=august&utm_term=aosmani&utm_content=chat-agent-launch#durable-compute-durable-streams)。 单轮对话没有超时。一段对话可以关掉,几天后再回来继续。
快速的首轮响应(https://trigger.dev/changelog/chat-agent?utm_source=fnf&utm_medium=x&utm_campaign=august&utm_term=aosmani&utm_content=chat-agent-launch#fast-first-turns)。 首次 LLM 调用在你自己的热服务器上运行,同时代理并行启动,所以持久化不等于慢。
等待不花一分钱(https://trigger.dev/changelog/chat-agent?utm_source=fnf&utm_medium=x&utm_campaign=august&utm_term=aosmani&utm_content=chat-agent-launch#waiting-costs-nothing)。 代理可以停下来,等一个人批准某项操作,然后在那里挂机好几天而不产生任何费用。
内置追踪和指标(https://trigger.dev/changelog/chat-agent?utm_source=fnf&utm_medium=x&utm_campaign=august&utm_term=aosmani&utm_content=chat-agent-launch#tracing-and-metrics-built-in)。 每一轮对话都是一个 span,并且有一个 AI 指标仪表盘,展示成本、token 数和延迟。
而且它使用的是你可能已经在用的 AI SDK(https://trigger.dev/changelog/chat-agent?utm_source=fnf&utm_medium=x&utm_campaign=august&utm_term=aosmani&utm_content=chat-agent-launch#keep-using-the-ai-sdk)。
一台真正的 Linux 机器
对话背后的计算机是一台普通的 Linux 机器,而不是受限的运行时。想装什么就装什么。可以调用 ffmpeg、驱动无头浏览器、运行一个需要真实文件系统和真实进程树的 CLI。
import { tool } from "ai";
import { execFile } from "node:child_process";
import { promisify } from "node:util";
import { z } from "zod";
const exec = promisify(execFile);
export const transcode = tool({
description: "Transcode a video to MP4.",
inputSchema: z.object({ input: z.string(), output: z.string() }),
execute: async ({ input, output }) => {
await exec("ffmpeg", ["-i", input, "-c:v", "libx264", output]);
return { output };
},
});
你可以选择机器的大小。机器预设(https://trigger.dev/docs/machines)从 micro(0.25 vCPU)起步,每台都有专属的 CPU、内存和磁盘。可以在代理级别设置,也可以在每个会话中覆盖。
持久化计算,持久化流
单轮对话没有超时限制。它运行多久取决于工作需要多久,所以一个慢速工具、一长串工具调用,或者一个自主思考的子代理(https://trigger.dev/docs/ai-chat/patterns/sub-agents)都只是工作而已。你不需要把任何东西切成适合限制的小块,也不需要为慢的部分排队。
在生产环境中,每二十个代理运行中就有一个持续超过 36 分钟,远远超出普通请求会被切断的时间点。
内存会在休眠后保留。你在第三轮对话中放在变量里的东西,明天第二十轮时仍然在那里,所以你之前做过的昂贵查找不需要重做。
const embeddings = new Map();
export const myChat = chat.agent({
id: "my-chat",
run: async ({ messages, signal }) => {
const key = messages.at(-1)!.id;
const cached = embeddings.get(key) ?? (await embed(key));
embeddings.set(key, cached);
return streamText({ model, messages, abortSignal: signal });
},
});
如果机器崩溃了,你会得到一台新的,对话也会随之恢复,因为那部分是被持久化下来的。但堆内存不会:你的 Map 会重新从空开始。这和你运维过的任何长运行服务器的规则一样,所以把无法承受丢失的东西放进数据库,内存留给需要速度的地方。这里的不同之处在于,内存现在能持续整个对话,而不是单个请求。
流也是持久化的。一段对话是一个会话,而会话比服务于它的进程活得更久:run 是计算,session 是身份。在响应中途刷新页面,流会从你的浏览器停止读取的地方重放,而不会重新运行模型。关掉浏览器,几天后回来,对话仍然在那里。
这让故障场景变得平淡无奇。如果一个 run 被杀死或内存耗尽,下一条消息会启动一个新的 run 并恢复对话。部署不会中断任何东西:一个 run 会继续运行在它启动时的版本上,而将对话迁移到新代码是你自己决定并调用的(https://trigger.dev/docs/ai-chat/patterns/version-upgrades),使用 chat.requestUpgrade()。
快速的首轮响应
持久化不意味着慢。Head Start(https://trigger.dev/docs/ai-chat/fast-starts#head-start)是可选的,当你希望第一个 token 和普通端点一样快时可以使用它。它在你自己的热服务器上运行首次 LLM 调用,同时代理并行启动,然后在对话中途移交控制权,处理工具调用以及后续所有内容。用户看到的是一个连续的响应,完全感觉不到接缝。
import { chat } from "@trigger.dev/sdk/chat-server";
import { streamText } from "ai";
import { anthropic } from "@ai-sdk/anthropic";
import { headStartTools } from "@/lib/chat-tools/schemas";
export const chatHandler = chat.headStart({
agentId: "my-chat",
run: async ({ chat: helper }) =>
streamText({
...helper.toStreamTextOptions({ tools: headStartTools }),
model: anthropic("claude-sonnet-4-5"),
system: "You are a helpful assistant.",
}),
});
在我们的测试中,它大约将首 token 时间和整轮对话时长都缩短了一半。它返回一个普通的 Web Fetch handler,所以可以无需适配器直接挂载到 Next.js、Hono、SvelteKit、Remix、TanStack Start 等框架中。而当首轮是纯文本、没有工具调用时,代理启动后立即退出,从不调用模型,所以你只为对话实际需要的东西付费。
因为内存是快照而不是重放的,这一切都不会给你的代码强加确定性约束。没有需要重放的事件历史,所以 Date.now()、Math.random() 以及循环中间的 fetch 都只是普通代码。没什么需要做成确定性的,也没有重放日志需要做版本管理。
等待不花一分钱
没有 execute 函数的工具会在轮次结束时保持调用打开状态。
export const requestApproval = tool({
description: "Ask the user to approve an irreversible action.",
inputSchema: z.object({ action: z.string() }),
// No execute. The turn ends here and the agent suspends.
});
代理会挂起,人想花多久就花多久,他们的回答会恢复运行。因为是挂起状态,你不会为等待付费,所以一个审批可以挂一晚上或者一个周末。
常用的聊天控制功能也都有。可以在流式生成中途停止(https://trigger.dev/docs/ai-chat/frontend#stop-generation),在工具调用之间引导它(https://trigger.dev/docs/ai-chat/pending-messages),或者编辑、分支和重新生成(https://trigger.dev/docs/ai-chat/patterns/branching-conversations),一路回溯到更早的消息。
内置追踪和指标
每一轮对话在仪表盘中都是一个 span,所以你可以打开一段对话,看到提示词、响应、工具调用以及每项花费的时间。
同一段对话中的两轮:模型调用、工具调用,以及代理挂起时等待的审批。
每个项目都自带一个 AI 指标 仪表盘。总花费、总调用次数、平均首块时间、平均每秒 token 数,以及按时间变化的成本、按模型划分的成本、按时间变化的 token 数、按模型划分的延迟百分位数、结束原因、最昂贵的运行,以及按任务和按提供商划分的成本。
项目中调用过的每个模型都有对应的花费、吞吐量和延迟数据,无需编写任何埋点代码。
每个模型都有自己的行,所以你可以看到每个模型实际花了你多少钱,以及提示词缓存帮你省了多少。成本、缓存节省、首块时间和吞吐量,按模型展示。
这些图表背后的数据可以通过 TRQL(https://trigger.dev/changelog/query-and-dashboards)获取,所以你可以自己提问、构建自己的仪表盘。sessions.list 给你所有对话,足以构建一个收件箱。
继续使用 AI SDK
服务端用 streamText,客户端用 useChat。chat.agent 作为传输层嵌入在底层,两者之间的 API 路由就不需要了。
import { useChat } from "@ai-sdk/react";
import { useTriggerChatTransport } from "@trigger.dev/sdk/chat/react";
import type { myChat } from "@/trigger/chat";
import { mintChatAccessToken, startChatSession } from "@/app/actions";
export function Chat() {
const transport = useTriggerChatTransport({
task: "my-chat",
accessToken: ({ chatId }) => mintChatAccessToken(chatId),
startSession: ({ chatId, clientData }) =>
startChatSession({ chatId, clientData }),
});
const { messages, sendMessage } = useChat({ transport });
// ... render UI
}
只有新消息会通过网络传输。历史记录累积在服务器上,所以你不需要每轮对话都重新上传整个对话内容。如果你已经有一个聊天应用,迁移指南(https://trigger.dev/docs/ai-chat/migrating-from-a-route-handler)里有一段提示词,你可以直接交给你的编码代理。
还有更多
你不必只停留在顶层。chat.agent() 为你管理轮次循环,自定义代理(https://trigger.dev/docs/ai-chat/custom-agents)会把控制权交还给你,让你自己编写循环,再往下还有底层会话原语(https://trigger.dev/docs/ai-chat/backend),让你直接读写对话的流。从简单的开始,需要时再往下深入。
长对话也能保持经济实惠:压缩(https://trigger.dev/docs/ai-chat/compaction)会在接近上下文限制时总结历史记录,提示词缓存(https://trigger.dev/docs/ai-chat/prompt-caching)让每个请求的稳定前缀保持低成本。你决定何时触发以及摘要长什么样。
export const myChat = chat.agent({
id: "my-chat",
compaction: {
shouldCompact: ({ totalTokens }) => (totalTokens ?? 0) > 80_000,
summarize: async ({ messages }) => summariseWithHaiku(messages),
},
run: async ({ messages, signal }) => streamText({ model, messages, abortSignal: signal }),
});
你的提示词(https://trigger.dev/docs/ai/prompts)也可以放在代码库中。它们会在每次部署时进行版本管理,你可以从仪表盘覆盖文本或模型而无需重新部署,每次生成都会追溯到产生它的版本。
固定在 v5 上的覆盖配置:线上调用会解析到那里,而不是部署所请求的版本。
浏览器不是唯一的客户端。AgentChat(https://trigger.dev/docs/ai-chat/server-chat)可以从一个任务、一个脚本或另一个代理驱动对话,MCP 服务器(https://trigger.dev/docs/ai-chat/mcp)让你无需编写任何代码就能从 Claude Code 或 Cursor 与自己的代理对话。
const review = new AgentChat({ agent: "my-chat", id: `review-${prNumber}` });
const summary = await (await review.sendMessage(`Review PR #${prNumber}`)).text();
await review.close();
前端还有 actions(https://trigger.dev/docs/ai-chat/actions),用于不消耗一轮对话的状态变更命令。当出错时,被内存不足错误杀死的轮次会在更大的机器上自动重试(https://trigger.dev/docs/ai-chat/patterns/oom-resilience)而不丢失导致错误的消息,恢复启动(https://trigger.dev/docs/ai-chat/patterns/recovery-boot)会在崩溃或取消后恢复完整上下文,版本升级(https://trigger.dev/docs/ai-chat/patterns/version-upgrades)在你决定时机时将挂起的对话迁移到新代码上。你还可以给代理赋予技能(https://trigger.dev/docs/ai-chat/patterns/skills),即一组它可以按需发现和使用的指令和脚本文件夹。
测试,以及 Trigger.dev 的其余部分
你可以在单元测试中通过真实的轮次来驱动代理,从而测试代理(https://trigger.dev/docs/ai-chat/testing)。不需要网络,不需要任务运行时,我们的任何东西都不会在你背后被 mock 掉。
因为代理就是一个 Trigger.dev 任务,所有现有功能也都适用。可以从工具调用中触发其他任务并等待它们完成,将聊天放在带独立并发控制的队列后面,批量处理工作,调度任务,或者构建自己的 eval 作为任务,在每次部署时针对一组固定数据运行代理。这些都不需要你专门去采用一个独立的产品。
开源,以及费用
Trigger.dev 使用 Apache 2.0 许可证,chat.agent 就是其中的一部分。阅读代码(https://github.com/triggerdotdev/trigger.dev)了解这一切是如何运作的,如果你愿意,也可以自己部署运行整套系统(https://trigger.dev/docs/open-source-self-hosting)。聊天代理的计费方式为 com
相似文章
Trigger.dev 的 Chat Agent
Trigger.dev 推出了一款 Chat Agent,即使你关闭标签页,AI 对话也会持续运行。
@rohanpaul_ai: AI agents的最大弱点:每次有用的运行通常都会随会话结束而消失。想象一下,能够将AI工作…
KroWork将AI聊天交互转化为可重用的桌面应用程序,重启时无需消耗令牌即可运行,使非技术用户能够通过自然语言创建可部署的软件。
@DanKornas: 在独立的模型应用、工具界面和消息客户端之间运行严肃的AI工作会带来不必要的摩擦。……
DeepChat 是一款开源、本地优先的AI代理桌面客户端,它将模型、工具和代理运行时统一起来,支持会话历史、MCP协议以及通过消息平台进行远程控制。
我的智能体并没有忽视客户,是我自己的安全守卫吞掉了回复
一位开发者详细说明了他们的AI智能体为何保持沉默——原因是安全守卫故障关闭、超时以及嵌套JSON问题,并强调在面对客户的聊天机器人中,沉默故障比错误答案更糟糕。
聊天优先的AI工具在需要代理自主工作时就会崩溃
作者认为聊天优先的AI工具不足以构建自主代理工作流,并描述了替代原语,如定时触发器和子代理委派,主张从“带着工具聊天”转向“使用LLMs的自主流程”。