UNPKG

@statelyai/agent

Version:

Stateful agents that make decisions based on finite-state machine models

500 lines (453 loc) 12.8 kB
import { ActorLogic, ActorRefFrom, AnyActorRef, AnyEventObject, AnyStateMachine, EventFrom, EventObject, PromiseActorLogic, SnapshotFrom, StateValue, Subscription, TransitionSnapshot, Values, } from 'xstate'; import { CoreMessage, CoreTool, generateText, GenerateTextResult, LanguageModel, streamText, StreamTextResult, } from 'ai'; import { ZodContextMapping, ZodEventMapping } from './schemas'; import { TypeOf } from 'zod'; export type GenerateTextOptions = Parameters<typeof generateText>[0]; export type StreamTextOptions = Parameters<typeof streamText>[0]; export type AgentPlanInput<TEvent extends EventObject> = Omit< GenerateTextOptions, 'prompt' | 'messages' | 'tools' > & { /** * The currently observed state. */ state: ObservedState; /** * The goal for the agent to accomplish. * The agent will create a plan based on this goal. */ goal: string; /** * The events that the agent can trigger. This is a mapping of * event types to Zod event schemas. */ events: ZodEventMapping; /** * The state machine that represents the environment the agent * is interacting with. */ machine?: AnyStateMachine; /** * The previous plan. */ previousPlan?: AgentPlan<TEvent>; }; export type AgentPlan<TEvent extends EventObject> = { goal: string; state: ObservedState; content?: string; /** * Executes the plan based on the given `state` and resolves with * a potential next `event` to trigger to achieve the `goal`. */ execute: (state: ObservedState) => Promise<TEvent | undefined>; nextEvent: TEvent | undefined; sessionId: string; timestamp: number; }; export interface TransitionData { eventType: string; description?: string; guard?: { type: string }; target?: any; } export type PromptTemplate<TEvents extends EventObject> = (data: { goal: string; /** * The observed state */ state?: ObservedState; /** * The context to provide. * This overrides the observed state.context, if provided. */ context?: any; /** * The state machine model of the observed environment */ machine?: unknown; /** * The potential next transitions that can be taken * in the state machine */ transitions?: TransitionData[]; /** * Past observations */ observations?: AgentObservation<any>[]; // TODO feedback?: AgentFeedback[]; messages?: AgentMessage[]; plans?: AgentPlan<TEvents>[]; }) => string; export type AgentPlanner<T extends AnyAgent> = ( agent: T, input: AgentPlanInput<T['types']['events']> ) => Promise<AgentPlan<T['types']['events']> | undefined>; export type AgentDecideOptions = { goal: string; model?: LanguageModel; context?: any; state: ObservedState; machine: AnyStateMachine; execute?: (event: AnyEventObject) => Promise<void>; planner?: AgentPlanner<any>; events?: ZodEventMapping; } & Omit< Parameters<typeof generateText>[0], 'model' | 'tools' | 'prompt' | 'messages' >; export interface AgentFeedback { goal?: string; observationId?: string; /** * The message correlation that the feedback is relevant for */ correlationId?: string; attributes: Record<string, any>; reward: number; timestamp: number; sessionId: string; } export interface AgentFeedbackInput { goal?: string; observationId?: string; correlationId?: string; attributes?: Record<string, any>; timestamp?: number; reward?: number; } export type AgentMessage = CoreMessage & { timestamp: number; id: string; /** * The response ID of the message, which references * which message this message is responding to, if any. */ responseId?: string; result?: GenerateTextResult<any>; sessionId: string; correlationId: string; parentCorrelationId?: string; }; export type AgentMessageInput = CoreMessage & { timestamp?: number; id?: string; /** * The response ID of the message, which references * which message this message is responding to, if any. */ responseId?: string; correlationId?: string; parentCorrelationId?: string; result?: GenerateTextResult<any>; }; export interface AgentObservation<TActor extends AnyActorRef> { id: string; prevState: SnapshotFrom<TActor> | undefined; event: EventFrom<TActor>; state: SnapshotFrom<TActor>; machineHash: string | undefined; sessionId: string; timestamp: number; } export interface AgentObservationInput { id?: string; prevState: ObservedState | undefined; event: AnyEventObject; state: ObservedState; machine?: AnyStateMachine; timestamp?: number; } export type AgentDecisionInput = { goal: string; model?: LanguageModel; context?: any; } & Omit<Parameters<typeof generateText>[0], 'model' | 'tools' | 'prompt'>; export type AgentDecisionLogic<TEvents extends EventObject> = PromiseActorLogic< AgentPlan<TEvents> | undefined, AgentDecisionInput | string >; export type AgentEmitted<TEvents extends EventObject> = | { type: 'feedback'; feedback: AgentFeedback; } | { type: 'observation'; observation: AgentObservation<any>; // TODO } | { type: 'message'; message: AgentMessage; } | { type: 'plan'; plan: AgentPlan<TEvents>; }; export type AgentLogic<TEvents extends EventObject> = ActorLogic< TransitionSnapshot<AgentMemoryContext>, | { type: 'agent.feedback'; feedback: AgentFeedback; } | { type: 'agent.observe'; observation: AgentObservation<any>; // TODO } | { type: 'agent.message'; message: AgentMessage; } | { type: 'agent.plan'; plan: AgentPlan<TEvents>; }, any, // TODO: input any, AgentEmitted<TEvents> >; export type EventsFromZodEventMapping<TEventSchemas extends ZodEventMapping> = Values<{ [K in keyof TEventSchemas & string]: { type: K; } & TypeOf<TEventSchemas[K]>; }>; export type ContextFromZodContextMapping< TContextSchema extends ZodContextMapping > = { [K in keyof TContextSchema & string]: TypeOf<TContextSchema[K]>; }; export type Agent<TContext, TEvents extends EventObject> = ActorRefFrom< AgentLogic<TEvents> > & { /** * The name of the agent. All agents with the same name are related and * able to share experiences (observations, feedback) with each other. */ name?: string; /** * The unique identifier for the agent. */ id?: string; description?: string; events: ZodEventMapping; types: { events: TEvents; context: Compute<TContext>; }; model: LanguageModel; defaultOptions: GenerateTextOptions; memory: AgentLongTermMemory | undefined; /** * The adapter used to perform LLM actions such as * `.generateText(…)` and `.streamText(…)`. * * Defaults to the Vercel AI SDK. */ adapter: AIAdapter; /** * Resolves with an `AgentPlan` based on the information provided in the `options`, including: * * - The `goal` for the agent to achieve * - The observed current `state` * - The `machine` (e.g. a state machine) that specifies what can happen next * - Additional `context` */ decide: ( options: AgentDecideOptions ) => Promise<AgentPlan<TEvents> | undefined>; // Generate text generateText: ( options: AgentGenerateTextOptions ) => Promise<AgentGenerateTextResult>; // Stream text streamText: ( options: AgentStreamTextOptions ) => Promise<AgentStreamTextResult>; addObservation: ( observationInput: AgentObservationInput ) => AgentObservation<any>; // TODO addMessage: (messageInput: AgentMessageInput) => AgentMessage; addFeedback: (feedbackInput: AgentFeedbackInput) => AgentFeedback; addPlan: (plan: AgentPlan<TEvents>) => void; /** * Called whenever the agent (LLM assistant) receives or sends a message. */ onMessage: (callback: (message: AgentMessage) => void) => void; /** * Selects agent data from its context. * * @deprecated Select from `agent.getSnapshot().context` directly or: * - `agent.getMessages()` * - `agent.getObservations()` * - `agent.getFeedback()` * - `agent.getPlans()` */ select: <T>(selector: (context: AgentMemoryContext) => T) => T; /** * Retrieves messages from the agent's short-term (local) memory. */ getMessages: () => AgentMessage[]; /** * Retrieves observations from the agent's short-term (local) memory. */ getObservations: () => AgentObservation<Agent<TContext, TEvents>>[]; /** * Retrieves feedback from the agent's short-term (local) memory. */ getFeedback: () => AgentFeedback[]; /** * Retrieves strategies from the agent's short-term (local) memory. */ getPlans: () => AgentPlan<TEvents>[]; /** * Interacts with this state machine actor by inspecting state transitions and storing them as observations. * * Observations contain the `prevState`, `event`, and current `state` of this * actor, as well as other properties that are useful when recalled. * These observations are stored in the `agent`'s short-term (local) memory * and can be retrieved via `agent.getObservations()`. * * @example * ```ts * // Only observes the actor's state transitions * agent.interact(actor); * * actor.start(); * ``` */ interact<TActor extends AnyActorRef>(actorRef: TActor): Subscription; /** * Interacts with this state machine actor by: * 1. Inspecting state transitions and storing them as observations * 2. Deciding what to do next (which event to send the actor) based on * the agent input returned from `getInput(observation)`, if `getInput(…)` is provided as the 2nd argument. * * Observations contain the `prevState`, `event`, and current `state` of this * actor, as well as other properties that are useful when recalled. * These observations are stored in the `agent`'s short-term (local) memory * and can be retrieved via `agent.getObservations()`. * * @example * ```ts * // Observes the actor's state transitions and * // makes a decision if on the "summarize" state * agent.interact(actor, observed => { * if (observed.state.matches('summarize')) { * return { * context: observed.state.context, * goal: 'Summarize the message' * } * } * }); * * actor.start(); * ``` */ interact<TActor extends AnyActorRef>( actorRef: TActor, getInput: ( observation: AgentObservation<TActor> ) => AgentDecisionInput | undefined ): Subscription; }; export type AnyAgent = Agent<any, any>; export type FromAgent<T> = T | ((agent: AnyAgent) => T | Promise<T>); export type CommonTextOptions = { prompt: FromAgent<string>; model?: LanguageModel; context?: Record<string, any>; messages?: FromAgent<CoreMessage[]>; template?: PromptTemplate<any>; correlationId?: string; parentCorrelationId?: string; }; export type TextResultMeta = { correlationId: string; parentCorrelationId?: string; }; export type AgentGenerateTextOptions = Omit< GenerateTextOptions, 'model' | 'prompt' | 'messages' > & CommonTextOptions; export type AgentGenerateTextResult = GenerateTextResult<any> & TextResultMeta; export type AgentStreamTextOptions = Omit< StreamTextOptions, 'model' | 'prompt' | 'messages' > & CommonTextOptions; export type AgentStreamTextResult = StreamTextResult<any> & TextResultMeta; export interface ObservedState { /** * The current state value of the state machine, e.g. * `"loading"` or `"processing"` or `"ready"` */ value: StateValue; /** * Additional contextual data related to the current state */ context: Record<string, unknown>; } export type ObservedStateFrom<TActor extends AnyActorRef> = Pick< SnapshotFrom<TActor>, 'value' | 'context' >; export type AgentMemoryContext = { observations: AgentObservation<any>[]; // TODO messages: AgentMessage[]; plans: AgentPlan<any>[]; feedback: AgentFeedback[]; }; export type AgentMemory = AppendOnlyStorage<AgentMemoryContext>; export interface AppendOnlyStorage<T extends Record<string, any[]>> { append<K extends keyof T>( sessionId: string, key: K, item: T[K][0] ): Promise<void>; getAll<K extends keyof T>( sessionId: string, key: K ): Promise<T[K] | undefined>; } export interface AgentLongTermMemory { get<K extends keyof AgentMemoryContext>( key: K ): Promise<AgentMemoryContext[K]>; append<K extends keyof AgentMemoryContext>( key: K, item: AgentMemoryContext[K][0] ): Promise<void>; set<K extends keyof AgentMemoryContext>( key: K, items: AgentMemoryContext[K] ): Promise<void>; } export interface AIAdapter { generateText: typeof generateText; streamText: typeof streamText; } export type Compute<A extends any> = { [K in keyof A]: A[K] } & unknown;