Partager via


Comment transmettre des réponses de l’agent

Qu’est-ce qu’une réponse diffusée en continu ?

Une réponse diffusée fournit le contenu du message en petits blocs de données petit à petit. Cette approche améliore l’expérience utilisateur en leur permettant d’afficher et d’interagir avec le message au fur et à mesure qu’il se déroule, plutôt que d’attendre la totalité de la réponse à charger. Les utilisateurs peuvent commencer à traiter immédiatement les informations, ce qui améliore le sens de la réactivité et de l’interactivité. Par conséquent, il réduit les retards et maintient les utilisateurs plus engagés tout au long du processus de communication.

Références de streaming

Streaming dans le noyau sémantique

Les services IA qui prennent en charge la diffusion en continu dans le noyau sémantique utilisent différents types de contenu par rapport à ceux utilisés pour les messages entièrement formés. Ces types de contenu sont spécifiquement conçus pour gérer la nature incrémentielle des données de streaming. Les mêmes types de contenu sont également utilisés dans l’Infrastructure de l’agent à des fins similaires. Cela garantit la cohérence et l’efficacité entre les deux systèmes lors de la gestion des informations de diffusion en continu.

Fonctionnalité actuellement indisponible en Java.

Réponse diffusée en continu à partir de ChatCompletionAgent

Lors de l'appel d'une réponse diffusée en continu à partir d'un ChatCompletionAgent, le ChatHistory dans le AgentThread est mis à jour après la réception de la réponse complète. Bien que la réponse soit diffusée de manière incrémentielle, l’historique enregistre uniquement le message complet. Cela garantit que les ChatHistory reflètent des réponses complètement formées pour assurer la cohérence.

// Define agent
ChatCompletionAgent agent = ...;

ChatHistoryAgentThread agentThread = new();

// Create a user message
var message = ChatMessageContent(AuthorRole.User, "<user input>");

// Generate the streamed agent response(s)
await foreach (StreamingChatMessageContent response in agent.InvokeStreamingAsync(message, agentThread))
{
  // Process streamed response(s)...
}

// It's also possible to read the messages that were added to the ChatHistoryAgentThread.
await foreach (ChatMessageContent response in agentThread.GetMessagesAsync())
{
  // Process messages...
}
from semantic_kernel.agents import ChatCompletionAgent, ChatHistoryAgentThread

# Define agent
agent = ChatCompletionAgent(...)

# Create a thread object to maintain the conversation state.
# If no thread is provided one will be created and returned with
# the initial response.
thread: ChatHistoryAgentThread = None

# Generate the streamed agent response(s)
async for response in agent.invoke_stream(messages="user input", thread=thread)
{
  # Process streamed response(s)...
  thread = response.thread
}

Fonctionnalité actuellement indisponible en Java.

Réponse diffusée en continu à partir de OpenAIAssistantAgent

Lors de l’appel d’une réponse en flux continu à partir d’un OpenAIAssistantAgent, l’Assistant conserve l’état de la conversation en tant que fil distant. Il est possible de lire les messages à partir du thread distant si nécessaire.

// Define agent
OpenAIAssistantAgent agent = ...;

// Create a thread for the agent conversation.
OpenAIAssistantAgentThread agentThread = new(assistantClient);

// Create a user message
var message = new ChatMessageContent(AuthorRole.User, "<user input>");

// Generate the streamed agent response(s)
await foreach (StreamingChatMessageContent response in agent.InvokeStreamingAsync(message, agentThread))
{
  // Process streamed response(s)...
}

// It's possible to read the messages from the remote thread.
await foreach (ChatMessageContent response in agentThread.GetMessagesAsync())
{
  // Process messages...
}

// Delete the thread when it is no longer needed
await agentThread.DeleteAsync();

Pour créer un thread à l’aide d’un thread existant Id, passez-le au constructeur de OpenAIAssistantAgentThread:

// Define agent
OpenAIAssistantAgent agent = ...;

// Create a thread for the agent conversation.
OpenAIAssistantAgentThread agentThread = new(assistantClient, "your-existing-thread-id");

// Create a user message
var message = new ChatMessageContent(AuthorRole.User, "<user input>");

// Generate the streamed agent response(s)
await foreach (StreamingChatMessageContent response in agent.InvokeStreamingAsync(message, agentThread))
{
  // Process streamed response(s)...
}

// It's possible to read the messages from the remote thread.
await foreach (ChatMessageContent response in agentThread.GetMessagesAsync())
{
  // Process messages...
}

// Delete the thread when it is no longer needed
await agentThread.DeleteAsync();
from semantic_kernel.agents import AssistantAgentThread, AzureAssistantAgent, OpenAIAssistantAgent

# Define agent
agent = OpenAIAssistantAgent(...)  # or = AzureAssistantAgent(...)

# Create a thread for the agent conversation.
# If no thread is provided one will be created and returned with
# the initial response.
thread: AssistantAgentThread = None

# Generate the streamed agent response(s)
async for response in agent.invoke_stream(messages="user input", thread=thread):
  # Process streamed response(s)...
  thread = response.thread

# Read the messages from the remote thread
async for response in thread.get_messages():
  # Process messages

# Delete the thread
await thread.delete()

Pour créer un thread à l’aide d’un thread existant thread_id, passez-le au constructeur de AssistantAgentThread:

from semantic_kernel.agents import AssistantAgentThread, AzureAssistantAgent, OpenAIAssistantAgent

# Define agent
agent = OpenAIAssistantAgent(...)  # or = AzureAssistantAgent(...)

# Create a thread for the agent conversation.
# If no thread is provided one will be created and returned with
# the initial response.
thread = AssistantAgentThread(client=client, thread_id="your-existing-thread-id")

# Generate the streamed agent response(s)
async for response in agent.invoke_stream(messages="user input", thread=thread):
  # Process streamed response(s)...
  thread = response.thread

# Delete the thread
await thread.delete()

Fonctionnalité actuellement indisponible en Java.

Gestion des messages intermédiaires avec une réponse en flux

La nature des réponses de diffusion en continu permet aux modèles LLM de retourner des blocs de texte incrémentiels, ce qui permet un rendu plus rapide dans une interface utilisateur ou une console sans attendre la fin de la réponse entière. En outre, un appelant peut vouloir gérer du contenu intermédiaire, tel que les résultats des appels de fonction. Pour ce faire, fournissez une fonction de rappel lors de l’utilisation de la réponse en flux. La fonction de rappel reçoit les messages complets encapsulés dans ChatMessageContent.

La documentation du callback pour AzureAIAgent sera bientôt disponible.

La configuration de la fonction de rappel on_intermediate_message permet à l'appelant de agent.invoke_stream(...) recevoir des messages intermédiaires générés durant la formulation de la réponse finale de l'agent.

import asyncio
from typing import Annotated

from semantic_kernel.agents import AzureResponsesAgent
from semantic_kernel.contents import ChatMessageContent, FunctionCallContent, FunctionResultContent
from semantic_kernel.functions import kernel_function


# Define a sample plugin for the sample
class MenuPlugin:
    """A sample Menu Plugin used for the concept sample."""

    @kernel_function(description="Provides a list of specials from the menu.")
    def get_specials(self, menu_item: str) -> Annotated[str, "Returns the specials from the menu."]:
        return """
        Special Soup: Clam Chowder
        Special Salad: Cobb Salad
        Special Drink: Chai Tea
        """

    @kernel_function(description="Provides the price of the requested menu item.")
    def get_item_price(
        self, menu_item: Annotated[str, "The name of the menu item."]
    ) -> Annotated[str, "Returns the price of the menu item."]:
        return "$9.99"

# This callback function will be called for each intermediate message,
# which will allow one to handle FunctionCallContent and FunctionResultContent.
# If the callback is not provided, the agent will return the final response
# with no intermediate tool call steps.
async def handle_streaming_intermediate_steps(message: ChatMessageContent) -> None:
    for item in message.items or []:
        if isinstance(item, FunctionResultContent):
            print(f"Function Result:> {item.result} for function: {item.name}")
        elif isinstance(item, FunctionCallContent):
            print(f"Function Call:> {item.name} with arguments: {item.arguments}")
        else:
            print(f"{item}")

# Simulate a conversation with the agent
USER_INPUTS = [
    "Hello",
    "What is the special soup?",
    "What is the special drink?",
    "How much is it?",
    "Thank you",
]


async def main():
    # 1. Create the client using OpenAI resources and configuration
    client, model = AzureResponsesAgent.setup_resources()

    # 2. Create a Semantic Kernel agent for the OpenAI Responses API
    agent = AzureResponsesAgent(
        ai_model_id=model,
        client=client,
        instructions="Answer questions about the menu.",
        name="Host",
        plugins=[MenuPlugin()],
    )

    # 3. Create a thread for the agent
    # If no thread is provided, a new thread will be
    # created and returned with the initial response
    thread = None

    try:
        for user_input in user_inputs:
            print(f"# {AuthorRole.USER}: '{user_input}'")

            first_chunk = True
            async for response in agent.invoke_stream(
                messages=user_input,
                thread=thread,
                on_intermediate_message=handle_streaming_intermediate_steps,
            ):
                thread = response.thread
                if first_chunk:
                    print(f"# {response.name}: ", end="", flush=True)
                    first_chunk = False
                print(response.content, end="", flush=True)
            print()
    finally:
        await thread.delete() if thread else None

if __name__ == "__main__":
    asyncio.run(main())

Voici un exemple de résultat du processus d'invocation de l'agent :

Sample Output:

# AuthorRole.USER: 'Hello'
# Host: Hello! How can I assist you with the menu today?
# AuthorRole.USER: 'What is the special soup?'
Function Call:> MenuPlugin-get_specials with arguments: {}
Function Result:>
        Special Soup: Clam Chowder
        Special Salad: Cobb Salad
        Special Drink: Chai Tea
        for function: MenuPlugin-get_specials
# Host: The special soup today is Clam Chowder. Would you like to know more about it or hear about other specials?
# AuthorRole.USER: 'What is the special drink?'
# Host: The special drink today is Chai Tea. Would you like more details or are you interested in ordering it?
# AuthorRole.USER: 'How much is that?'
Function Call:> MenuPlugin-get_item_price with arguments: {"menu_item":"Chai Tea"}
Function Result:> $9.99 for function: MenuPlugin-get_item_price
# Host: The special drink, Chai Tea, is $9.99. Would you like to order one or need information on something else?
# AuthorRole.USER: 'Thank you'
# Host: You're welcome! If you have any more questions or need help with the menu, just let me know. Enjoy your day!

Fonctionnalité actuellement indisponible en Java.

Étapes suivantes