智能体
Flue 是一个用于构建自主智能体的框架。它们是 Flue 存在的核心原因,因此理解它们是什么以及如何工作非常重要。本指南涵盖创建智能体、组合其能力和环境,以及安全地向用户暴露它。
什么是智能体?
智能体由三个关键部分组成,它们协同工作:LLM、运行时和专业上下文。 Flue 旨在帮助你自定义和组合所有三个元素,以构建强大的、真正自主的智能体,为各种产品和内部工作流提供支持。
Flue 附带两个核心原语来帮助你组合强大的智能体:智能体函数和智能体 Hooks。
智能体函数
智能体函数在 Flue 中代表一个智能体。在 Flue 中,智能体是一个 JavaScript 函数,返回智能体的 system 提示指令。这些指令会被渲染,然后与构成某个智能体对话或工作流的用户和助手消息一起传递给 LLM。
// 示例:一个用 Flue 编写的简单智能体。
function TriageAgent() {
return "调查用户的问题并推荐下一步操作。";
}智能体始终使用 ID 初始化。你可以通过 flue run 的 --id 标志(可选)或托管智能体的 POST /:id 路由(必需)提供一个。ID 的含义由你决定——用户 ID、支持工单、GitHub issue 编号,或只是随机字符串。每个智能体实例都通过 ID 持久化,因此你可以使用智能体 ID 随时间与特定智能体进行消息交互。
function TriageAgent({ id }) {
return `调查 GitHub issue #${id} 并推荐下一步操作。`;
}还有其他方式将(结构化)数据传递给你的智能体——请参阅智能体 Hooks 指南中的向智能体传递数据。
智能体函数在每个回合都会_重新渲染_。也就是说,每次模型即将被调用时,Flue 会再次运行你的函数并从头开始重建其指令。你返回的字符串始终反映智能体在该时刻的当前状态:
function AssistantAgent() {
// 警告:将动态数据插入智能体指令可能会破坏 LLM 用于提供更便宜推理 token 的缓存。
// 在生产智能体中避免这样做通常是最佳实践,特别是为了节省成本。
return `你是一个有用的助手。现在的时间是 ${Date.now()}。`;
}如果有助于理解,你可以将智能体函数视为类似于 React 组件的渲染函数。这并非偶然,正如你将在下面看到的:Flue 智能体函数有意模仿 React 进行建模,以帮助解锁更具表现力、更强大的智能体功能。
智能体 Hooks
智能体函数本身用处不大。它返回指令,但一个工作的智能体需要的不仅仅是文字——一个模型、工具、工作空间、记忆。要解锁所有这些,你需要使用 Flue 的第二个核心原语:智能体 hooks。
Hook 是一个普通函数,你在智能体函数体内调用它来为你的智能体赋予一项新能力。所有 hook 都以 use 开头,命名理念是:每个 hook 让你的智能体_钩入_Flue 运行时的不同功能:
- 模型(
useModel)选择驱动智能体的 LLM。 - 沙盒(
useSandbox)提供其文件系统和命令执行环境。 - 工具(
useTool)让它调用应用程序代码并影响外部系统。 - MCP 服务器(
useMcpConnection)从开放的 MCP 生态系统挂载工具。 - 技能(
useSkill)提供它可以在需要时加载的专业知识。 - 子智能体(
useSubagent)让它将专注的工作委托给其他智能体。 - 持久化状态(
usePersistentState)在智能体生命周期内保留自定义数据。 - 事件 Hooks(
useAgentStart、useAgentFinish等)在不同生命周期事件上触发逻辑。
import { useModel, useSandbox, useSkill, useTool } from '@flue/runtime';
import { local } from '@flue/runtime/node';
import { searchIssues } from '../tools/search-issues.ts';
import reviewChecklist from '../skills/review-checklist/SKILL.md';
function Triage() {
useModel('anthropic/claude-sonnet-4-6');
useSandbox(local());
useTool(searchIssues);
useSkill(reviewChecklist);
return '调查报告的问题并推荐下一步操作。';
}要了解更多关于智能体 hooks 的信息,请参阅智能体 Hooks。
“use agent” 指令
上面的示例定义了智能体函数,但你的应用程序还不知道它们。要注册一个智能体,请用 'use agent' 指令标记其模块并导出该函数:
'use agent';
import { useModel } from '@flue/runtime';
export function TriageAgent() {
useModel('anthropic/claude-sonnet-4-6');
return '调查报告的问题并推荐下一步操作。';
}就像 JavaScript 中的 'use strict' 或 React 中的 'use client' 一样,该指令是文件顶部的一个普通字符串,在任何导入或其他语句之前。在构建时,Flue 会扫描你的项目以查找标记的文件,并将每个导出的、大写开头的函数注册为智能体。一个文件可以导出多个智能体。
注册使智能体可以被应用程序的其余部分寻址:dispatch(...) 可以向它发送消息,createAgentRouter(...) 可以通过 HTTP 提供服务。导出函数的名称也成为智能体的持久化标识,它在持久化数据库中键控其对话存储。要在不进行数据库迁移的情况下重命名函数,请使用 agentName 静态属性固定标识。设置显式的智能体名称被一些 Flue 开发者视为最佳实践。
'use agent';
import { useModel } from '@flue/runtime';
export function TriageAgent() {
useModel('anthropic/claude-sonnet-4-6');
return '调查报告的问题并推荐下一步操作。';
}
TriageAgent.agentName = 'triage-agent';与你的智能体交互
有几种方式可以与智能体交互。所有方式都运行相同的智能体和持久化 API——它们仅在运行时启动方式和是否存在 HTTP 服务器方面有所不同。
CLI
与智能体交互的最简单方式是在本地使用 flue run CLI 命令:
flue run src/agents/triage-agent.ts --message "Triage issue 17307"
这直接运行一个智能体模块——没有服务器,没有应用程序构建。传递 --id 来命名对话,以便你可以在调用之间继续它;没有它,每次运行都会开始一个新的对话并打印其生成的 ID:
flue run src/agents/triage-agent.ts --id issue-17307 --message "Look at issue 17307"
flue run src/agents/triage-agent.ts --id issue-17307 --message "Any update?"
对话在运行之间持久化——在你项目配置的数据库中,或在没有数据库时的本地缓存文件中。请参阅 flue run 参考 了解智能体选择、结构化输出和完整的标志列表。
HTTP
在你的应用程序中挂载的智能体通过 HTTP 提供服务(挂载在路由中介绍)。每个对话都有自己的 URL,以对话 ID 结尾。向它 POST 一条消息:
POST /agents/support-assistant/ticket-8472 HTTP/1.1
Content-Type: application/json
{
"kind": "user",
"body": "你能总结一下我案例中的开放问题吗?"
}提示是即发即忘的:服务器立即响应 202,智能体的回复从对话中读取——GET 同一个 URL 来跟踪其事件,或使用Flue 智能体 SDK,它围绕一个对话 URL 封装了整个表面(send()、wait()、observe()、history())。
任何能够访问对话 URL 的人都可以与该对话交谈。使用你应用程序的正常中间件来保护挂载:验证调用者,并检查他们是否被允许访问该对话 ID。请参阅路由了解完整模式。
dispatch()
当你的应用程序异步接收到智能体的事件时使用 dispatch(...),例如 webhook、队列消息、聊天事件或通知。例如,应用程序路由可以验证传入的支持系统 webhook 并将评论分派给该工单的智能体:
import { dispatch } from '@flue/runtime';
import { Hono } from 'hono';
import { SupportAssistant } from './agents/support-assistant.ts';
import { verifySupportWebhook } from './shared/support-webhooks.ts';
const app = new Hono();
app.post('/webhooks/support-comments', async (c) => {
const event = await verifySupportWebhook(c.req.raw);
const receipt = await dispatch(SupportAssistant, {
id: event.ticketId,
message: {
kind: 'signal',
type: 'support.comment.created',
body: event.text,
attributes: { commentId: event.commentId },
},
});
return c.json(receipt, 202);
});
export default app;你的应用程序在分派事件之前选择智能体对话。dispatch(...) 接受它进行异步处理,而不是等待智能体响应。因为注册来自 'use agent' 扫描,仅通过 dispatch(...) 使用的智能体根本不需要挂载。请参阅通道了解经过验证的提供商入口和应用程序拥有的出站行为。
独立脚本
最后,你可以完全在 Flue 应用程序之外运行智能体——没有服务器,没有 app.ts——使用更高级的 start() API。它在你自己的 Node.js 进程中启动 Flue 运行时,这对于 cron 作业、一次性脚本和测试很有用:
import { init } from '@flue/runtime';
import { sqlite, start } from '@flue/runtime/node';
import { Reporter } from '../src/agents/reporter.ts';
await using flue = await start({
agents: [Reporter],
db: sqlite('./nightly.db'),
});
const reporter = init(Reporter, { id: 'nightly-2026-07-16' });
const receipt = await reporter.dispatch('生成每日报告。');
const reply = await reporter.read(receipt);
console.log(reply.text);提供商凭据来自进程环境,db 选项决定对话是否比脚本存活更久:省略它以使用内存状态,或传递像 sqlite() 这样的适配器,以便后续运行可以继续同一个对话。在已经运行的 Flue 应用程序内部没有 start()——直接调用 init() 或 dispatch()。工作流指南深入介绍了这个脚本表面,从 CI 流水线到持久化编排。