Skip to content

Commit

Permalink
initial agentchat impl
Browse files Browse the repository at this point in the history
  • Loading branch information
rysweet committed Feb 24, 2025
1 parent 3ac65f7 commit 20fd3fa
Show file tree
Hide file tree
Showing 23 changed files with 1,216 additions and 0 deletions.
105 changes: 105 additions & 0 deletions typescript/src/agentchat/abstractions/ChatAgent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import { AgentMessage, ChatMessage } from '../messages/Messages';

/**
* A valid name for an agent.
* To ensure parity with Python, we require agent names to be Python identifiers.
*/
export class AgentName {
private static readonly ID_START_CLASS = /[\p{Lu}\p{Ll}\p{Lt}\p{Lm}\p{Lo}\p{Nl}_\u1185-\u1186\u2118\u212E\u309B-\u309C]/u;
private static readonly ID_CONTINUE_CLASS = /[\w\p{Nl}\p{Mc}_\u1185-\u1186\u2118\u212E\u309B-\u309C\u00B7\u0387\u1369-\u1371\u19DA\u200C\u200D\u30FB\uFF65]/u;
private static readonly AGENT_NAME_REGEX = new RegExp(`^${AgentName.ID_START_CLASS.source}${AgentName.ID_CONTINUE_CLASS.source}*$`);

constructor(private readonly value: string) {
AgentName.checkValid(value);
}

public static isValid(name: string): boolean {
return AgentName.AGENT_NAME_REGEX.test(name);
}

public static checkValid(name: string): void {
if (!AgentName.isValid(name)) {
throw new Error(`Agent name '${name}' is not a valid identifier.`);
}
}

toString(): string {
return this.value;
}
}

/**
* A response from calling IChatAgent's handleAsync.
*/
export interface Response {
/**
* A chat message produced by the agent as a response.
*/
message: ChatMessage;

/**
* Inner messages produced by the agent.
*/
innerMessages?: AgentMessage[];
}

/**
* Base class for representing a stream of messages interspacing responses and internal processing messages.
* This functions as a discriminated union.
*/
export class StreamingFrame<TResponse, TInternalMessage extends AgentMessage> {
public type: 'InternalMessage' | 'Response' = 'Response'; // Set default value
public internalMessage?: TInternalMessage;
public response?: TResponse;
}

/**
* Base class for representing a stream of messages with internal messages of any AgentMessage subtype.
*/
export class BaseStreamingFrame<TResponse> extends StreamingFrame<TResponse, AgentMessage> {}

/**
* The stream frame for IChatAgent's streamAsync
*/
export class ChatStreamFrame extends BaseStreamingFrame<Response> {}

/**
* An agent that can participate in a chat.
*/
export interface IChatAgent {
/**
* The name of the agent. This is used by team to uniquely identify the agent.
* It should be unique within the team.
*/
readonly name: AgentName;

/**
* The description of the agent. This is used by team to make decisions about which agents to use.
* The description should describe the agent's capabilities and how to interact with it.
*/
readonly description: string;

/**
* The types of messages that the agent produces.
*/
readonly producedMessageTypes: Array<Function>;

/**
* Handles chat messages asynchronously and produces a response.
* @param messages The messages to handle
* @returns A promise resolving to the response
*/
handleAsync(messages: ChatMessage[]): Promise<Response>;

/**
* Handles chat messages asynchronously and produces a stream of frames.
* @param messages The messages to handle
* @returns An async iterable of chat stream frames
*/
streamAsync(messages: ChatMessage[]): AsyncIterable<ChatStreamFrame>;

/**
* Reset the agent to its initialization state.
*/
resetAsync(): Promise<void>;
}
57 changes: 57 additions & 0 deletions typescript/src/agentchat/abstractions/Events.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { AgentMessage, ChatMessage, StopMessage } from "../messages/Messages";
import { Response } from "./ChatAgent";

/**
* Base class for all group chat events.
*/
export abstract class GroupChatEventBase {}

/**
* A request to start a group chat.
*/
export class GroupChatStart extends GroupChatEventBase {
/**
* An optional list of messages to start the group chat.
*/
messages?: ChatMessage[];
}

/**
* A response published to a group chat.
*/
export class GroupChatAgentResponse extends GroupChatEventBase {
/**
* The response from an agent.
*/
agentResponse!: Response;
}

/**
* A request to publish a message to a group chat.
*/
export class GroupChatRequestPublish extends GroupChatEventBase {}

/**
* A message from a group chat.
*/
export class GroupChatMessage extends GroupChatEventBase {
/**
* The message that was published.
*/
message!: AgentMessage;
}

/**
* A message indicating that group chat was terminated.
*/
export class GroupChatTermination extends GroupChatEventBase {
/**
* The stop message that indicates the reason of termination.
*/
message!: StopMessage;
}

/**
* A request to reset the agents in the group chat.
*/
export class GroupChatReset extends GroupChatEventBase {}
12 changes: 12 additions & 0 deletions typescript/src/agentchat/abstractions/HandlingStream.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/**
* Interface for streaming handlers that can process an input type and produce an output type.
*/
export interface IHandleStream<TIn, TOut> {
/**
* Processes the input and produces a stream of output.
* @param input The input to process
* @param cancellation Optional token to cancel the operation
* @returns An async iterable of output items
*/
streamAsync(input: TIn, cancellation?: AbortSignal): AsyncIterable<TOut>;
}
23 changes: 23 additions & 0 deletions typescript/src/agentchat/abstractions/Handoff.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { AgentMessage } from "./Messages";

/**
* Message indicating that control should be handed off to another agent.
*/
export class HandoffMessage extends AgentMessage {
/**
* Gets or sets the target agent to hand off control to.
*/
targetAgent: string;

/**
* Gets or sets an optional message to send to the target agent.
*/
message?: string;

constructor(targetAgent: string, message?: string, source?: string) {
super();
this.targetAgent = targetAgent;
this.message = message;
this.source = source;
}
}
25 changes: 25 additions & 0 deletions typescript/src/agentchat/abstractions/ITeam.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { TaskFrame } from "./Tasks";

/**
* Defines a team of agents that can work together to accomplish tasks.
*/
export interface ITeam {
/**
* Gets a unique identifier for this team.
*/
readonly teamId: string;

/**
* Executes a task and returns a stream of frames containing intermediate messages and final results.
* @param task The task to execute, typically a string or message
* @param cancellation Optional cancellation token
* @returns An async iterable of task frames containing messages or results
*/
streamAsync(task: string | unknown, cancellation?: AbortSignal): AsyncIterable<TaskFrame>;

/**
* Resets the team to its initial state.
* @param cancellation Optional cancellation token
*/
resetAsync(cancellation?: AbortSignal): Promise<void>;
}
70 changes: 70 additions & 0 deletions typescript/src/agentchat/abstractions/Messages.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/**
* Base class for all messages in the agent chat system.
*/
export abstract class AgentMessage {
/**
* Gets or sets the source of the message (e.g., agent name, system, user).
*/
source?: string;
}

/**
* Represents a basic chat message with text content.
*/
export class ChatMessage extends AgentMessage {
/**
* Gets or sets the text content of the message.
*/
content: string;

constructor(content: string, source?: string) {
super();
this.content = content;
this.source = source;
}
}

/**
* Message indicating that the chat should stop.
*/
export class StopMessage extends AgentMessage {
/**
* Gets or sets the reason for stopping.
*/
content: string;

constructor(content: string, source?: string) {
super();
this.content = content;
this.source = source;
}
}

/**
* Represents metadata about a message exchange.
*/
export class MessageMetadata {
/**
* Gets or sets any model parameters used to generate the message.
*/
modelParameters?: Record<string, unknown>;
}

/**
* Interface for handlers that need to store message metadata.
*/
export interface IStoreMessageMetadata {
/**
* Gets the metadata associated with a message.
* @param messageId The unique identifier of the message.
* @returns The metadata associated with the message.
*/
getMetadata(messageId: string): MessageMetadata | undefined;

/**
* Associates metadata with a message.
* @param messageId The unique identifier of the message.
* @param metadata The metadata to store.
*/
setMetadata(messageId: string, metadata: MessageMetadata): void;
}
42 changes: 42 additions & 0 deletions typescript/src/agentchat/abstractions/Tasks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { AgentMessage } from "./Messages";

/**
* Represents a frame of task execution, containing either a message or result.
*/
export class TaskFrame {
public readonly isResult: boolean;
public readonly message?: AgentMessage;
public readonly result?: TaskResult;

/**
* Creates a new task frame.
* @param messageOrResult Either an AgentMessage or TaskResult
*/
constructor(messageOrResult: AgentMessage | TaskResult) {
if (messageOrResult instanceof TaskResult) {
this.isResult = true;
this.result = messageOrResult;
} else {
this.isResult = false;
this.message = messageOrResult;
}
}
}

/**
* Represents the result of a task execution, including all messages generated.
*/
export class TaskResult {
/**
* Gets the messages generated during task execution.
*/
public readonly messages: AgentMessage[];

/**
* Creates a new task result.
* @param messages The messages generated during task execution
*/
constructor(messages: AgentMessage[]) {
this.messages = messages;
}
}
33 changes: 33 additions & 0 deletions typescript/src/agentchat/abstractions/Termination.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { AgentMessage, StopMessage } from "./Messages";

/**
* Exception thrown when a chat has already terminated.
*/
export class TerminatedException extends Error {
constructor() {
super("The chat has already terminated.");
this.name = "TerminatedException";
}
}

/**
* Defines a condition that determines when a chat should terminate.
*/
export interface ITerminationCondition {
/**
* Gets whether the chat has already terminated.
*/
readonly isTerminated: boolean;

/**
* Checks if new messages should cause termination of the chat.
* @param messages The messages to check
* @returns A StopMessage if the chat should terminate, null otherwise
*/
checkAndUpdateAsync(messages: AgentMessage[]): Promise<StopMessage | null>;

/**
* Resets the termination condition to its initial state.
*/
reset(): void;
}
Loading

0 comments on commit 20fd3fa

Please sign in to comment.