Freigeben über


Benutzerdefinierte Agenten

Microsoft Agent Framework unterstützt das Erstellen von benutzerdefinierten Agents, indem sie von der AIAgent Klasse erben und die erforderlichen Methoden implementieren.

In diesem Artikel wird erläutert, wie Sie einen einfachen benutzerdefinierten Agent erstellen, der die Benutzereingaben in Großbuchstaben zurückgibt. In den meisten Fällen umfasst das Erstellen Ihres eigenen Agents komplexere Logik und Integration in einen KI-Dienst.

Erste Schritte

Fügen Sie dem Projekt die erforderlichen NuGet-Pakete hinzu.

dotnet add package Microsoft.Agents.AI.Abstractions --prerelease

Erstellen eines benutzerdefinierten Agents

Die Agentsitzung

Zum Erstellen eines benutzerdefinierten Agents benötigen Sie auch eine Sitzung, die zum Nachverfolgen des Status einer einzelnen Unterhaltung verwendet wird, einschließlich des Nachrichtenverlaufs und aller anderen Status, den der Agent verwalten muss.

Um den Einstieg zu erleichtern, können Sie von verschiedenen Basisklassen erben, die allgemeine Sitzungsspeichermechanismen implementieren.

  1. InMemoryAgentSession – speichert den Chatverlauf im Arbeitsspeicher und kann in JSON serialisiert werden.
  2. ServiceIdAgentSession – speichert keinen Chatverlauf, ermöglicht ihnen jedoch die Zuordnung einer ID zur Sitzung, unter der der Chatverlauf extern gespeichert werden kann.

In diesem Beispiel verwenden Sie die InMemoryAgentSession Basisklasse für die benutzerdefinierte Sitzung.

internal sealed class CustomAgentSession : InMemoryAgentSession
{
    internal CustomAgentSession() : base() { }
    internal CustomAgentSession(JsonElement serializedSessionState, JsonSerializerOptions? jsonSerializerOptions = null)
        : base(serializedSessionState, jsonSerializerOptions) { }
}

Die Agent-Klasse

Erstellen Sie als Nächstes die Agentklasse selbst, indem Sie von der AIAgent Klasse erben.

internal sealed class UpperCaseParrotAgent : AIAgent
{
}

Erstellen von Sitzungen

Sitzungen werden immer über zwei Factorymethoden für die Agentklasse erstellt. Dadurch kann der Agent steuern, wie Sitzungen erstellt und deserialisiert werden. Agents können daher alle zusätzlichen Zustände oder Verhaltensweisen anfügen, die beim Erstellen der Sitzung erforderlich sind.

Es sind zwei Methoden erforderlich, die implementiert werden müssen:

    public override Task<AgentSession> GetNewSessionAsync(CancellationToken cancellationToken = default) 
        => Task.FromResult<AgentSession>(new CustomAgentSession());

    public override Task<AgentSession> DeserializeSessionAsync(JsonElement serializedSession, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default)
        => Task.FromResult<AgentSession>(new CustomAgentSession(serializedSession, jsonSerializerOptions));

Kern-Agent-Logik

Die Kernlogik des Agents besteht darin, eingabemeldungen zu übernehmen, den Text in Großbuchstaben zu konvertieren und als Antwortnachrichten zurückzugeben.

Fügen Sie die folgende Methode hinzu, um diese Logik zu enthalten. Die Eingabemeldungen werden geklont, da verschiedene Aspekte der Eingabemeldungen geändert werden müssen, um gültige Antwortnachrichten zu sein. Zum Beispiel muss die Rolle in Assistant geändert werden.

    private static IEnumerable<ChatMessage> CloneAndToUpperCase(IEnumerable<ChatMessage> messages, string agentName) => messages.Select(x =>
        {
            var messageClone = x.Clone();
            messageClone.Role = ChatRole.Assistant;
            messageClone.MessageId = Guid.NewGuid().ToString();
            messageClone.AuthorName = agentName;
            messageClone.Contents = x.Contents.Select(c => c is TextContent tc ? new TextContent(tc.Text.ToUpperInvariant())
            {
                AdditionalProperties = tc.AdditionalProperties,
                Annotations = tc.Annotations,
                RawRepresentation = tc.RawRepresentation
            } : c).ToList();
            return messageClone;
        });

Agent-Ausführungsmethoden

Schließlich müssen Sie die beiden Kernmethoden implementieren, die verwendet werden, um den Agenten auszuführen: eine für Nicht-Streaming und eine für Streaming.

Für beide Methoden müssen Sie sicherstellen, dass eine Sitzung bereitgestellt wird, und falls nicht, eine neue Sitzung erstellen. Nachrichten können abgerufen und an die ChatHistoryProvider Sitzung übergeben werden. Wenn Sie dies nicht tun, kann der Benutzer keine Multi-Turn-Unterhaltung mit einem Agenten führen, und jede Ausführung wird als eine neue Interaktion betrachtet.

    public override async Task<AgentResponse> RunAsync(IEnumerable<ChatMessage> messages, AgentSession? session = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default)
    {
        session ??= await this.GetNewSessionAsync(cancellationToken);

        // Get existing messages from the store
        var invokingContext = new ChatHistoryProvider.InvokingContext(messages);
        var storeMessages = await typedSession.ChatHistoryProvider.InvokingAsync(invokingContext, cancellationToken);

        List<ChatMessage> responseMessages = CloneAndToUpperCase(messages, this.DisplayName).ToList();

        // Notify the session of the input and output messages.
        var invokedContext = new ChatHistoryProvider.InvokedContext(messages, storeMessages)
        {
            ResponseMessages = responseMessages
        };
        await typedSession.ChatHistoryProvider.InvokedAsync(invokedContext, cancellationToken);

        return new AgentResponse
        {
            AgentId = this.Id,
            ResponseId = Guid.NewGuid().ToString(),
            Messages = responseMessages
        };
    }

    public override async IAsyncEnumerable<AgentResponseUpdate> RunStreamingAsync(IEnumerable<ChatMessage> messages, AgentSession? session = null, AgentRunOptions? options = null, [EnumeratorCancellation] CancellationToken cancellationToken = default)
    {
        session ??= await this.GetNewSessionAsync(cancellationToken);

        // Get existing messages from the store
        var invokingContext = new ChatHistoryProvider.InvokingContext(messages);
        var storeMessages = await typedSession.ChatHistoryProvider.InvokingAsync(invokingContext, cancellationToken);

        List<ChatMessage> responseMessages = CloneAndToUpperCase(messages, this.DisplayName).ToList();

        // Notify the session of the input and output messages.
        var invokedContext = new ChatHistoryProvider.InvokedContext(messages, storeMessages)
        {
            ResponseMessages = responseMessages
        };
        await typedSession.ChatHistoryProvider.InvokedAsync(invokedContext, cancellationToken);

        foreach (var message in responseMessages)
        {
            yield return new AgentResponseUpdate
            {
                AgentId = this.Id,
                AuthorName = this.DisplayName,
                Role = ChatRole.Assistant,
                Contents = message.Contents,
                ResponseId = Guid.NewGuid().ToString(),
                MessageId = Guid.NewGuid().ToString()
            };
        }
    }

Den Agent verwenden

Wenn die AIAgent-Methoden alle ordnungsgemäß implementiert sind, wäre der Agent ein Standardagent und würde Standardbetriebsfunktionen eines Agents unterstützen.

Weitere Informationen zum Ausführen und Interagieren mit Agenten finden Sie in den Agenten-Einführungstutorials.

Microsoft Agent Framework unterstützt das Erstellen von benutzerdefinierten Agents, indem sie von der BaseAgent Klasse erben und die erforderlichen Methoden implementieren.

Dieses Dokument zeigt, wie Sie einen einfachen benutzerdefinierten Agent erstellen, der die Benutzereingabe mit einem Präfix wiedergibt. In den meisten Fällen umfasst das Erstellen Ihres eigenen Agents komplexere Logik und Integration in einen KI-Dienst.

Erste Schritte

Fügen Sie ihrem Projekt die erforderlichen Python-Pakete hinzu.

pip install agent-framework-core --pre

Erstellen eines benutzerdefinierten Agents

Das Agent-Protokoll

Das Framework stellt das AgentProtocol Protokoll bereit, das die Schnittstelle definiert, die alle Agents implementieren müssen. Benutzerdefinierte Agents können dieses Protokoll entweder direkt implementieren oder die BaseAgent Klasse zur Vereinfachung erweitern.

from agent_framework import AgentProtocol, AgentResponse, AgentResponseUpdate, AgentThread, ChatMessage
from collections.abc import AsyncIterable
from typing import Any

class MyCustomAgent(AgentProtocol):
    """A custom agent that implements the AgentProtocol directly."""

    @property
    def id(self) -> str:
        """Returns the ID of the agent."""
        ...

    async def run(
        self,
        messages: str | ChatMessage | list[str] | list[ChatMessage] | None = None,
        *,
        thread: AgentThread | None = None,
        **kwargs: Any,
    ) -> AgentResponse:
        """Execute the agent and return a complete response."""
        ...

    def run_stream(
        self,
        messages: str | ChatMessage | list[str] | list[ChatMessage] | None = None,
        *,
        thread: AgentThread | None = None,
        **kwargs: Any,
    ) -> AsyncIterable[AgentResponseUpdate]:
        """Execute the agent and yield streaming response updates."""
        ...

Verwenden von BaseAgent

Der empfohlene Ansatz besteht darin, die BaseAgent Klasse zu erweitern, die allgemeine Funktionen bereitstellt und die Implementierung vereinfacht:

from agent_framework import (
    BaseAgent,
    AgentResponse,
    AgentResponseUpdate,
    AgentThread,
    ChatMessage,
    Role,
    TextContent,
)
from collections.abc import AsyncIterable
from typing import Any


class EchoAgent(BaseAgent):
    """A simple custom agent that echoes user messages with a prefix."""

    echo_prefix: str = "Echo: "

    def __init__(
        self,
        *,
        name: str | None = None,
        description: str | None = None,
        echo_prefix: str = "Echo: ",
        **kwargs: Any,
    ) -> None:
        """Initialize the EchoAgent.

        Args:
            name: The name of the agent.
            description: The description of the agent.
            echo_prefix: The prefix to add to echoed messages.
            **kwargs: Additional keyword arguments passed to BaseAgent.
        """
        super().__init__(
            name=name,
            description=description,
            echo_prefix=echo_prefix,
            **kwargs,
        )

    async def run(
        self,
        messages: str | ChatMessage | list[str] | list[ChatMessage] | None = None,
        *,
        thread: AgentThread | None = None,
        **kwargs: Any,
    ) -> AgentResponse:
        """Execute the agent and return a complete response.

        Args:
            messages: The message(s) to process.
            thread: The conversation thread (optional).
            **kwargs: Additional keyword arguments.

        Returns:
            An AgentResponse containing the agent's reply.
        """
        # Normalize input messages to a list
        normalized_messages = self._normalize_messages(messages)

        if not normalized_messages:
            response_message = ChatMessage(
                role=Role.ASSISTANT,
                contents=[TextContent(text="Hello! I'm a custom echo agent. Send me a message and I'll echo it back.")],
            )
        else:
            # For simplicity, echo the last user message
            last_message = normalized_messages[-1]
            if last_message.text:
                echo_text = f"{self.echo_prefix}{last_message.text}"
            else:
                echo_text = f"{self.echo_prefix}[Non-text message received]"

            response_message = ChatMessage(role=Role.ASSISTANT, contents=[TextContent(text=echo_text)])

        # Notify the thread of new messages if provided
        if thread is not None:
            await self._notify_thread_of_new_messages(thread, normalized_messages, response_message)

        return AgentResponse(messages=[response_message])

    async def run_stream(
        self,
        messages: str | ChatMessage | list[str] | list[ChatMessage] | None = None,
        *,
        thread: AgentThread | None = None,
        **kwargs: Any,
    ) -> AsyncIterable[AgentResponseUpdate]:
        """Execute the agent and yield streaming response updates.

        Args:
            messages: The message(s) to process.
            thread: The conversation thread (optional).
            **kwargs: Additional keyword arguments.

        Yields:
            AgentResponseUpdate objects containing chunks of the response.
        """
        # Normalize input messages to a list
        normalized_messages = self._normalize_messages(messages)

        if not normalized_messages:
            response_text = "Hello! I'm a custom echo agent. Send me a message and I'll echo it back."
        else:
            # For simplicity, echo the last user message
            last_message = normalized_messages[-1]
            if last_message.text:
                response_text = f"{self.echo_prefix}{last_message.text}"
            else:
                response_text = f"{self.echo_prefix}[Non-text message received]"

        # Simulate streaming by yielding the response word by word
        words = response_text.split()
        for i, word in enumerate(words):
            # Add space before word except for the first one
            chunk_text = f" {word}" if i > 0 else word

            yield AgentResponseUpdate(
                contents=[TextContent(text=chunk_text)],
                role=Role.ASSISTANT,
            )

            # Small delay to simulate streaming
            await asyncio.sleep(0.1)

        # Notify the thread of the complete response if provided
        if thread is not None:
            complete_response = ChatMessage(role=Role.ASSISTANT, contents=[TextContent(text=response_text)])
            await self._notify_thread_of_new_messages(thread, normalized_messages, complete_response)

Den Agent verwenden

Wenn agent-Methoden alle ordnungsgemäß implementiert sind, würde der Agent alle Standard-Agent-Vorgänge unterstützen.

Weitere Informationen zum Ausführen und Interagieren mit Agenten finden Sie in den Agenten-Einführungstutorials.

Nächste Schritte