Skip to content

AI 代理

AI 代理是一个自主实体,它观察环境,决定做什么(基于其内部策略),并执行行动以实现其目标。在演员模型中,代理可以被视为一个可以:

  • 接收事件,例如下一步该做什么的指令、要实现的目标或对环境的观察
  • 发送事件,这将导致在环境中执行操作
  • 存储状态,可以用来记住关于环境的上下文信息
  • 生成其他代理,可以用来创建一个代理层次结构,这些代理可以协同工作并协调其行动以实现目标

Stately Agent (@statelyai/agent) 包使基于演员模型和状态机创建代理和代理行为变得简单。这些代理不仅可以生成文本和执行函数调用;Stately Agent 是一个框架,用于:

  • 存储消息历史,在使用生成文本功能时记录用户和助手之间的消息历史
  • 观察环境,记录转换(前一个状态、事件、下一个状态),以便理解环境
  • 接收反馈,关于其做出的决定,以便它可以检索和关联反馈,从而做出更明智的决定
  • 制定计划,在其决策过程中,不仅预测下一步要做的决定,还预测一系列理想情况下实现目标的决定
  • 短期和长期记忆,记住它所做的消息历史、观察、反馈和计划

安装

安装以下依赖项:

  • @statelyai/agent@beta – Stately.ai 代理,目前处于测试阶段
  • @ai-sdk/openai – Vercel AI SDK for OpenAI,提供对 OpenAI API 的访问
  • xstate – 用于管理状态机和状态图的库
  • zod – 用于类型安全的模式验证库
npm install @statelyai/agent @ai-sdk/openai xstate zod

快速开始

  1. 将您的提供商的 API 密钥添加到您的 .env 文件中。
OPENAI_API_KEY="sk-abCDE..."
  1. 创建一个代理。
import { openai } from '@ai-sdk/openai';
import { createAgent } from '@statelyai/agent';

const agent = createAgent({
name: 'todo',
model: openai('gpt-4-turbo'),
events: {}
});
  1. 使用 Zod 添加事件模式。这些是代理允许“引起”的事件(即发送给演员的事件)
import { openai } from '@ai-sdk/openai';
import { createAgent } from '@statelyai/agent';
import { z } from 'zod';

const agent = createAgent({
model: openai('gpt-4-turbo'),
name: 'todo',
events: {
'todo.add': z.object({
todo: z
.object({
title: z.string().describe('待办事项的标题'),
content: z.string().describe('待办事项的内容'),
completed: z
.boolean()
.describe('待办事项的完成状态')
.optional(),
})
.describe('添加一个新的待办事项'),
}),
'todo.toggle': z.object({
todoId: z.string().describe('要切换的待办事项的ID'),
completed: z.boolean().describe('新的完成状态').optional(),
}),
}
});
  1. 与接受这些事件的状态机演员进行交互。
import { setup, createActor } from 'xstate';
import { agent } from './agent';

const todoMachine = setup({
types: {
// 添加代理理解的事件类型
events: agent.types.events
},
// ...
}).createMachine({
// ...
});

const todoActor = createActor(todoMachine);

agent.interact(todoMachine);

todoActor.start();

创建代理

您可以使用 createAgent(settings) 函数创建一个代理。以下是必需的设置:

  • name - 代理的名称,用于日志记录和代理学习目的
  • model - 用于生成文本和进行工具调用的 AI SDK 语言模型
  • events - 代理可以触发的事件类型到 Zod 事件模式的映射(即它可以发送到某个实时环境的事件)
import { createAgent } from '@statelyai/agent';
import { openai } from '@ai-sdk/openai';
import { z } from 'zod';

const agent = createAgent({
name: 'barista',
model: openai('gpt-4-turbo'),
events: {
'barista.makeDrink': z.object({
drink: z.enum(['espresso', 'latte', 'cappuccino']),
}).describe('Makes a drink'),
// ...
}
});

您可以指定其他设置来自定义代理的行为:

  • description - 代理的描述,用于日志记录和代理学习目的,以及用于调用其他代理的代理(多代理系统)
  • planner - 一个异步函数,它接受 agent 和规划器 input 并解析为一个可能包含 stepsnextEventAgentPlan,以执行实现 input.goal 的步骤。此函数用于根据当前状态 (input.state) 和目标 (input.goal) 确定代理下一步应该做什么。
  • logic - 代理逻辑函数,用于确定代理在接收到代理事件(例如 "agent.feedback""agent.observe""agent.message""agent.plan")时的行为。

做出决策

Stately 代理最重要的功能是能够根据当前状态和目标做出决策。这是通过 agent.decide(input) 异步函数完成的,该函数接受一个包含当前状态、状态机和目标的 input 对象,并解析为一个 AgentPlan

例如,假设您有以下 baristaMachine 状态机:

import { createMachine } from 'xstate';

export const baristaMachine = createMachine({
initial: 'idle',
states: {
idle: {
on: {
'barista.makeDrink': 'makingDrink',
},
},
makingDrink: {
on: {
'barista.drinkMade': 'idle',
}
},
},
});

然后,您可以使用 agent.decide(input) 函数来确定代理接下来应该做什么:

import { createAgent } from '@statelyai/agent';
import { baristaMachine } from './baristaMachine';

const agent = createAgent({
name: 'barista',
model: openai('gpt-4-turbo'),
events: {
'barista.makeDrink': z.object({
drink: z.enum(['espresso', 'latte', 'cappuccino']),
}).describe('Makes a drink')
}
});

async function handleOrder(order, state) {
const resolvedState = baristaMachine.resolveState(state)
const plan = await agent.decide({
state: resolvedState,
machine: baristaMachine,
goal: `A customer made this order: ${order}`,
});

return plan;
}

handleOrder('I want a latte please', { value: 'idle' });
// 解析为包含以下内容的 `AgentPlan`:
// {
// // ...
// nextEvent: { type: 'barista.makeDrink', drink: 'latte' },
// }

代理记忆

Stately 代理可以有两种类型的记忆:短期(本地)记忆长期记忆

  • 短期(本地)记忆是可以同步检索的记忆,但可能不会被持久化。
  • 长期记忆是从持久存储(如数据库)异步检索的记忆。

代理在其记忆中记住四种事物:

  • 用户和助手之间的消息
  • 代理观察到的环境中发生的状态转换(前一个状态、事件、当前状态)的观察
  • 反馈
  • 计划

消息

agent.getMessages()

返回用户和助手之间在短期记忆中发生的聊天消息。

agent.addMessage(message)

如果您想手动将助手和用户之间的消息添加到代理记忆中,可以调用 agent.addMessage(message) 来实现。这在调用 agent.generateText(…)agent.streamText(…)fromText(…)fromTextStream(…) 演员逻辑创建器时会自动调用。您应该避免手动调用此方法。

观察

agent.getObservations()

返回代理从短期记忆中观察到的观察结果。

agent.addObservation(observation)

您可以通过调用 agent.addObservation(observation) 将观察({ prevState, event, state, … })添加到代理的记忆中。此函数返回一个 AgentObservation 对象,其中包括提供的观察细节以及一个观察 id,以便在适用时可以在反馈中引用该观察。

const observation = agent.addObservation({
prevState: { value: 'idle', context: {} },
event: { type: 'grindBeans' },
state: { value: 'grindingBeans', context: {} },
});

反馈

agent.getFeedback()

返回从短期记忆中给代理的反馈。

agent.addFeedback(feedback)

const observation = agent.addObservation({
// ...
});

const feedback = agent.addFeedback({
observationId: observation.id,
goal: '制作冰咖啡',
attributes: {
feedback: '制作冰咖啡时不应煮沸水',
score: -10
}
});

计划

agent.getPlans()

返回代理从短期记忆中制定的计划。

agent.addPlan(plan)

TODO

与状态机交互

代理可以与现有的状态机演员进行交互,以确定下一步该做什么。当状态机演员运行时,代理将执行以下循环:

  1. 代理观察状态变化
  • 观察结果会被记住在代理的状态中
  1. 代理确定是否需要根据当前状态做出决策
  2. 如果需要,代理会做出决策,形成一个 AgentPlan
  3. 如果形成了 AgentPlan,代理会在状态机演员上触发下一个事件 (plan.nextEvent)。
  • 计划会被记住在代理的状态中。
  1. 代理回到步骤1,循环继续。
import { createAgent } from '@statelyai/agent';
import { createActor } from 'xstate';
import { jokeMachine } from './jokeMachine';

const agent = createAgent({
name: 'joke-teller',
model: openai('gpt-4'),
events: {
'agent.tellJoke': z.object({
joke: z.string().describe('The joke text'),
}),
'agent.rateJoke': z.object({
rating: z.number().min(1).max(10),
explanation: z.string(),
}),
// ...
}
});

const jokeActor = createActor(jokeMachine).start();

agent.interact(jokeActor, (observed) => {
if (observed.state.matches('tellingJoke')) {
return { goal: `Tell a joke about ${observed.state.context.topic}` };
}
if (observed.state.matches('ratingJoke')) {
return { goal: `Rate this joke: ${observed.state.context.joke}` };
}
});

状态机代理

您可以将 Stately 代理作为状态机的一部分调用,确保它将遵循状态机指定的转换并触发适当的事件。这可以通过使用以下任意一个演员逻辑创建器来完成:

fromDecision(agent)

返回Promise 演员逻辑,该逻辑解析为应完成目标 (input.goal) 的代理计划,如果它能够创建一个计划的话。

当调用/生成时,此演员还会将用户和助手消息添加到代理记忆中,以及它创建的计划。

fromText(agent)

返回Promise 演员逻辑,该逻辑解析为来自 Vercel AI SDK 的生成文本结果。

当调用/生成时,此演员还会将用户和助手消息添加到代理记忆中。

import { createAgent, fromText } from '@statelyai/agent';
import { setup } from 'xstate';

const agent = createAgent(/* ... */);

const machine = setup({
actors: {
assistant: fromText(agent),
}
}).createMachine({
initial: 'greeting',
context: x => ({
time: x.input.time
}),
states: {
greeting: {
invoke: {
src: 'assistant',
input: ({ context }) => ({
context: {
time: context.time
},
goal: '根据一天中的时间生成问候语。'
}),
onDone: {
target: 'greeted',
actions: ({ event }) => {
console.log(event.output.text);
}
},
},
},
greeted: {
type: 'final'
}
}
});

const actor = createActor(machine, {
input: { time: Date.now() }
});

actor.start();

fromTextStream(agent)

返回可观察的演员逻辑,该逻辑从 Vercel AI SDK 流式传输文本。

当调用/生成时,此演员还会将用户和助手消息添加到代理记忆中。

TODO: 示例

可观察性

  • 可以通过 agent.on('message', (message) => {}) 观察观察结果、计划、消息和反馈
  • 可以通过 agent.addFeedback(…) 手动添加反馈观察结果

生成文本

您可以使用 agent.generateText(input) 方法从输入生成文本。这扩展了 Vercel AI SDKgenerateText(…) 函数,通过:

  • 将用户和助手消息添加到代理记忆中
  • 提供从代理记忆中检索先前的观察结果、反馈、计划和消息的能力

流式传输文本

您可以使用 agent.streamText(input) 方法从输入流式传输文本。这扩展了 Vercel AI SDKstreamText(…) 函数,通过:

  • 将用户和助手消息添加到代理记忆中
  • 提供从代理记忆中检索先前的观察结果、反馈、计划和消息的能力

代理逻辑

代理逻辑是演员逻辑,其特定目的是为代理执行 LLM 任务。代理逻辑不仅仅是一个包装器,还提供了使用代理的状态机智能确定下一步操作的能力。

代理逻辑在与状态机驱动的代理一起使用时最为强大,但您也可以从代理逻辑创建独立的演员,这对于测试和简单任务非常有用。

示例

请参阅 examples 目录 中的当前示例。