chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
# Semantic Kernel Agents - Getting Started
|
||||
|
||||
This project contains a step by step guide to get started with _Semantic Kernel Agents_ in Python.
|
||||
|
||||
#### PyPI:
|
||||
|
||||
- For the use of Chat Completion agents, the minimum allowed Semantic Kernel pypi version is 1.3.0.
|
||||
- For the use of OpenAI Assistant agents, the minimum allowed Semantic Kernel pypi version is 1.4.0.
|
||||
- For the use of Agent Group Chat, the minimum allowed Semantic kernel pypi version is 1.6.0.
|
||||
- For the use of Streaming OpenAI Assistant agents, the minimum allowed Semantic Kernel pypi version is 1.11.0
|
||||
- For the use of OpenAI Responses agents, the minimum allowed Semantic Kernel pypi version is 1.27.0.
|
||||
|
||||
#### Source
|
||||
|
||||
- [Semantic Kernel Agent Framework](../../semantic_kernel/agents/)
|
||||
|
||||
## Examples
|
||||
|
||||
The getting started with agents examples include:
|
||||
|
||||
## Chat Completion
|
||||
|
||||
Example|Description
|
||||
---|---
|
||||
[step01_chat_completion_agent_simple](../getting_started_with_agents/chat_completion/step01_chat_completion_agent_simple.py)|How to create and use a simple chat completion agent.
|
||||
[step02_chat_completion_agent_thread_management](../getting_started_with_agents/chat_completion/step02_chat_completion_agent_thread_management.py)|How to create and use a chat completion with a thread.
|
||||
[step03_chat_completion_agent_with_kernel](../getting_started_with_agents/chat_completion/step03_chat_completion_agent_with_kernel.py)|How to create and use a a chat completion agent with the AI service created on the kernel.
|
||||
[step04_chat_completion_agent_plugin_simple](../getting_started_with_agents/chat_completion/step04_chat_completion_agent_plugin_simple.py)|How to create a simple chat completion agent and specify plugins via the constructor with a kernel.
|
||||
[step05_chat_completion_agent_plugin_with_kernel](../getting_started_with_agents/chat_completion/step05_chat_completion_agent_plugin_with_kernel.py)|How to create and use a chat completion agent by registering plugins on the kernel.
|
||||
[step06_chat_completion_agent_group_chat](../getting_started_with_agents/chat_completion/step06_chat_completion_agent_group_chat.py)|How to create a conversation between agents.
|
||||
[step07_kernel_function_strategies](../getting_started_with_agents/chat_completion/step07_kernel_function_strategies.py)|How to utilize a `KernelFunction` as a chat strategy.
|
||||
[step08_chat_completion_agent_json_result](../getting_started_with_agents/chat_completion/step08_chat_completion_agent_json_result.py)|How to have an agent produce JSON.
|
||||
[step09_chat_completion_agent_logging](../getting_started_with_agents/chat_completion/step09_chat_completion_agent_logging.py)|How to enable logging for agents.
|
||||
[step10_chat_completion_agent_structured_outputs](../getting_started_with_agents/chat_completion/step10_chat_completion_agent_structured_outputs.py)|How to use have a chat completion agent use structured outputs
|
||||
[step11_chat_completion_agent_declarative](../getting_started_with_agents/chat_completion/step11_chat_completion_agent_declarative.py)|How to create a chat compltion agent from a declarative spec.
|
||||
|
||||
## Azure AI Agent
|
||||
|
||||
Example|Description
|
||||
---|---
|
||||
[step1_azure_ai_agent](../getting_started_with_agents/azure_ai_agent/step01_azure_ai_agent.py)|How to create an Azure AI Agent and invoke a Semantic Kernel plugin.
|
||||
[step2_azure_ai_agent_plugin](../getting_started_with_agents/azure_ai_agent/step02_azure_ai_agent_plugin.py)|How to create an Azure AI Agent with plugins.
|
||||
[step3_azure_ai_agent_group_chat](../getting_started_with_agents/azure_ai_agent/step03_azure_ai_agent_group_chat.py)|How to create an agent group chat with Azure AI Agents.
|
||||
[step4_azure_ai_agent_code_interpreter](../getting_started_with_agents/azure_ai_agent/step04_azure_ai_agent_code_interpreter.py)|How to use the code-interpreter tool for an Azure AI agent.
|
||||
[step5_azure_ai_agent_file_search](../getting_started_with_agents/azure_ai_agent/step05_azure_ai_agent_file_search.py)|How to use the file-search tool for an Azure AI agent.
|
||||
[step6_azure_ai_agent_openapi](../getting_started_with_agents/azure_ai_agent/step06_azure_ai_agent_openapi.py)|How to use the Open API tool for an Azure AI agent.
|
||||
[step7_azure_ai_agent_retrieval](../getting_started_with_agents/azure_ai_agent/step07_azure_ai_agent_retrieval.py)|How to reference an existing Azure AI Agent.
|
||||
[step8_azure_ai_agent_declarative](../getting_started_with_agents/azure_ai_agent/step08_azure_ai_agent_declarative.py)|How to create an Azure AI Agent from a declarative spec.
|
||||
|
||||
_Note: For details on configuring an Azure AI Agent, please see [here](../getting_started_with_agents/azure_ai_agent/README.md)._
|
||||
|
||||
|
||||
## OpenAI Assistant Agent
|
||||
|
||||
Example|Description
|
||||
---|---
|
||||
[step1_assistant](../getting_started_with_agents/openai_assistant/step1_assistant.py)|How to create and use an OpenAI Assistant agent.
|
||||
[step2_assistant_plugins](../getting_started_with_agents/openai_assistant/step2_assistant_plugins.py)| How to create and use an OpenAI Assistant agent with plugins.
|
||||
[step3_assistant_vision](../getting_started_with_agents/openai_assistant/step3_assistant_vision.py)|How to provide an image as input to an OpenAI Assistant agent.
|
||||
[step4_assistant_tool_code_interpreter](../getting_started_with_agents/openai_assistant/step4_assistant_tool_code_interpreter.py)|How to use the code-interpreter tool for an OpenAI Assistant agent.
|
||||
[step5_assistant_tool_file_search](../getting_started_with_agents/openai_assistant/step5_assistant_tool_file_search.py)|How to use the file-search tool for an OpenAI Assistant agent.
|
||||
[step6_assistant](../getting_started_with_agents/openai_assistant/step6_assistant_declarative.py)|How to create an Assistant Agent from a declarative spec.
|
||||
|
||||
## OpenAI Responses Agent
|
||||
|
||||
Example|Description
|
||||
---|---
|
||||
[step1_responses_agent](../getting_started_with_agents/openai_responses/step1_responses_agent.py)|How to create and use an OpenAI Responses agent in the most simple way.
|
||||
[step2_responses_agent_thread_management](../getting_started_with_agents/openai_responses/step2_responses_agent_thread_management.py)| How to create and use a `ResponsesAgentThread` agent to maintain conversation context.
|
||||
[step3_responses_agent_plugins](../getting_started_with_agents/openai_responses/step3_responses_agent_plugins.py)|How to create and use an OpenAI Responses agent with plugins.
|
||||
[step4_responses_agent_web_search](../getting_started_with_agents/openai_responses/step4_responses_agent_web_search.py)|How to use the web search preview tool with an OpenAI Responses agent.
|
||||
[step5_responses_agent_file_search](../getting_started_with_agents/openai_responses/step5_responses_agent_file_search.py)|How to use the file-search tool with an OpenAI Responses agent.
|
||||
[step6_responses_agent_vision](../getting_started_with_agents/openai_responses/step6_responses_agent_vision.py)|How to provide an image as input to an OpenAI Responses agent.
|
||||
[step7_responses_agent_structured_outputs](../getting_started_with_agents/openai_responses/step7_responses_agent_structured_outputs.py)|How to use have an OpenAI Responses agent use structured outputs.
|
||||
[step8_assistant](../getting_started_with_agents/openai_responses/step8_responses_agent_declarative.py)|How to create a Responses Agent from a declarative spec.
|
||||
|
||||
## Multi-Agent Orchestration
|
||||
|
||||
Example|Description
|
||||
---|---
|
||||
[step1_concurrent](../getting_started_with_agents/multi_agent_orchestration/step1_concurrent.py)|How to run agents in parallel on the same task.
|
||||
[step1a_concurrent_structure_output](../getting_started_with_agents/multi_agent_orchestration/step1a_concurrent_structure_output.py)|How to run agents in parallel on the same task and return structured output.
|
||||
[step2_sequential](../getting_started_with_agents/multi_agent_orchestration/step2_sequential.py)|How to run agents in sequence to complete a task.
|
||||
[step2a_sequential_cancellation_token](../getting_started_with_agents/multi_agent_orchestration/step2a_sequential_cancellation_token.py)|How to cancel an invocation while it is in progress.
|
||||
[step3_group_chat](../getting_started_with_agents/multi_agent_orchestration/step3_group_chat.py)|How to run agents in a group chat to complete a task.
|
||||
[step3a_group_chat_human_in_the_loop](../getting_started_with_agents/multi_agent_orchestration/step3a_group_chat_human_in_the_loop.py)|How to run agents in a group chat with human in the loop.
|
||||
[step3b_group_chat_with_chat_completion_manager](../getting_started_with_agents/multi_agent_orchestration/step3b_group_chat_with_chat_completion_manager.py)|How to run agents in a group chat with a more dynamic manager.
|
||||
[step4_handoff](../getting_started_with_agents/multi_agent_orchestration/step4_handoff.py)|How to run agents in a handoff orchestration to complete a task.
|
||||
[step4a_handoff_structure_input](../getting_started_with_agents/multi_agent_orchestration/step4a_handoff_structure_input.py)|How to run agents in a handoff orchestration to complete a task with structured input.
|
||||
[step5_magentic](../getting_started_with_agents/multi_agent_orchestration/step5_magentic.py)|How to run agents in a Magentic orchestration to complete a task.
|
||||
|
||||
## Configuring the Kernel
|
||||
|
||||
Similar to the Semantic Kernel Python concept samples, it is necessary to configure the secrets
|
||||
and keys used by the kernel. See the follow "Configuring the Kernel" [guide](../concepts/README.md#configuring-the-kernel) for
|
||||
more information.
|
||||
|
||||
## Running Concept Samples
|
||||
|
||||
Concept samples can be run in an IDE or via the command line. After setting up the required api key
|
||||
for your AI connector, the samples run without any extra command line arguments.
|
||||
@@ -0,0 +1,6 @@
|
||||
AZURE_AI_AGENT_PROJECT_CONNECTION_STRING = "<example-connection-string>"
|
||||
AZURE_AI_AGENT_MODEL_DEPLOYMENT_NAME = "<example-model-deployment-name>"
|
||||
AZURE_AI_AGENT_ENDPOINT = "<example-endpoint>"
|
||||
AZURE_AI_AGENT_SUBSCRIPTION_ID = "<example-subscription-id>"
|
||||
AZURE_AI_AGENT_RESOURCE_GROUP_NAME = "<example-resource-group-name>"
|
||||
AZURE_AI_AGENT_PROJECT_NAME = "<example-project-name>"
|
||||
@@ -0,0 +1,124 @@
|
||||
## Azure AI Agents
|
||||
|
||||
The following getting started samples show how to use Azure AI Agents with Semantic Kernel.
|
||||
|
||||
To set up the required resources, follow the "Quickstart: Create a new agent" guide [here](https://learn.microsoft.com/en-us/azure/ai-services/agents/quickstart?pivots=programming-language-python-azure).
|
||||
|
||||
You will need to install the optional Semantic Kernel `azure` dependencies if you haven't already via:
|
||||
|
||||
```bash
|
||||
pip install semantic-kernel
|
||||
```
|
||||
|
||||
Before running an Azure AI Agent, modify your .env file to include:
|
||||
|
||||
```bash
|
||||
AZURE_AI_AGENT_ENDPOINT = "<example-endpoint-string>"
|
||||
AZURE_AI_AGENT_MODEL_DEPLOYMENT_NAME = "<example-deployment-name>"
|
||||
AZURE_AI_AGENT_API_VERSION = "<example-api-version>"
|
||||
```
|
||||
|
||||
The endpoint can be found listed as part of the Azure Foundry [portal](https://ai.azure.com) in the format of: `https://<resource>.services.ai.azure.com/api/projects/<project-name>`.
|
||||
|
||||
The .env should be placed in the root directory.
|
||||
|
||||
## Configuring the AI Project Client
|
||||
|
||||
Ensure that your Azure AI Agent resources are configured with at least a Basic or Standard SKU.
|
||||
|
||||
To begin, create the project client as follows:
|
||||
|
||||
```python
|
||||
async with (
|
||||
AzureCliCredential() as creds,
|
||||
AzureAIAgent.create_client(credential=creds) as client,
|
||||
):
|
||||
# Your operational code here
|
||||
```
|
||||
|
||||
Before running the example, make sure to run `az login` command in shell using Azure CLI to authenticate and get access to Azure services.
|
||||
|
||||
### Required Imports
|
||||
|
||||
The required imports for the `Azure AI Agent` include async libraries:
|
||||
|
||||
```python
|
||||
from azure.identity.aio import AzureCliCredential
|
||||
```
|
||||
|
||||
### Initializing the Agent
|
||||
|
||||
You can pass in an endpoint, along with an optional api-version, to create the client:
|
||||
|
||||
```python
|
||||
|
||||
ai_agent_settings = AzureAIAgentSettings()
|
||||
|
||||
async with (
|
||||
AzureCliCredential() as creds,
|
||||
AzureAIAgent.create_client(
|
||||
credential=creds,
|
||||
endpoint=ai_agent_settings.endpoint,
|
||||
api_version=ai_agent_settings.api_version,
|
||||
) as client,
|
||||
):
|
||||
# operational logic
|
||||
```
|
||||
|
||||
### Creating an Agent Definition
|
||||
|
||||
Once the client is initialized, you can define the agent:
|
||||
|
||||
```python
|
||||
# Create agent definition
|
||||
agent_definition = await client.agents.create_agent(
|
||||
model=ai_agent_settings.model_deployment_name,
|
||||
name=AGENT_NAME,
|
||||
instructions=AGENT_INSTRUCTIONS,
|
||||
)
|
||||
```
|
||||
|
||||
Then, instantiate the `AzureAIAgent` with the `client` and `agent_definition`:
|
||||
|
||||
```python
|
||||
# Create the AzureAI Agent
|
||||
agent = AzureAIAgent(
|
||||
client=client,
|
||||
definition=agent_definition,
|
||||
)
|
||||
```
|
||||
|
||||
Now, you can create a thread, add chat messages to the agent, and invoke it with given inputs and optional parameters.
|
||||
|
||||
### Reusing an Agent Definition
|
||||
|
||||
In certain scenarios, you may prefer to reuse an existing agent definition rather than creating a new one. This can be done by calling `await client.agents.get_agent(...)` instead of `await client.agents.create_agent(...)`.
|
||||
|
||||
For a practical example, refer to the [`step7_azure_ai_agent_retrieval`](./step7_azure_ai_agent_retrieval.py) sample.
|
||||
|
||||
## Requests and Rate Limits
|
||||
|
||||
### Managing API Request Frequency
|
||||
|
||||
Your default request limits may be low, affecting how often you can poll the status of a run. You have two options:
|
||||
|
||||
1. Adjust the `polling_options` of the `AzureAIAgent`
|
||||
|
||||
By default, the polling interval is 250 ms. You can slow it down to 1 second (or another preferred value) to reduce the number of API calls:
|
||||
|
||||
```python
|
||||
# Required imports
|
||||
from datetime import timedelta
|
||||
from semantic_kernel.agents.run_polling_options import RunPollingOptions
|
||||
|
||||
# Configure the polling options as part of the `AzureAIAgent`
|
||||
agent = AzureAIAgent(
|
||||
client=client,
|
||||
definition=agent_definition,
|
||||
polling_options=RunPollingOptions(run_polling_interval=timedelta(seconds=1)),
|
||||
)
|
||||
```
|
||||
|
||||
2. Increase Rate Limits in Azure AI Foundry
|
||||
|
||||
You can also adjust your deployment's Rate Limit (Tokens per minute), which impacts the Rate Limit (Requests per minute). This can be configured in Azure AI Foundry under your project's deployment settings for the "Connected Azure OpenAI Service Resource."
|
||||
@@ -0,0 +1,77 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
|
||||
from azure.identity.aio import AzureCliCredential
|
||||
|
||||
from semantic_kernel.agents import AzureAIAgent, AzureAIAgentSettings, AzureAIAgentThread
|
||||
|
||||
"""
|
||||
The following sample demonstrates how to create an Azure AI agent that answers
|
||||
user questions. This sample demonstrates the basic steps to create an agent
|
||||
and simulate a conversation with the agent.
|
||||
|
||||
The interaction with the agent is via the `get_response` method, which sends a
|
||||
user input to the agent and receives a response from the agent. The conversation
|
||||
history is maintained by the agent service, i.e. the responses are automatically
|
||||
associated with the thread. Therefore, client code does not need to maintain the
|
||||
conversation history.
|
||||
"""
|
||||
|
||||
|
||||
# Simulate a conversation with the agent
|
||||
USER_INPUTS = [
|
||||
"Hello, I am John Doe.",
|
||||
"What is your name?",
|
||||
"What is my name?",
|
||||
]
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
async with (
|
||||
AzureCliCredential() as creds,
|
||||
AzureAIAgent.create_client(credential=creds) as client,
|
||||
):
|
||||
# 1. Create an agent on the Azure AI agent service
|
||||
agent_definition = await client.agents.create_agent(
|
||||
model=AzureAIAgentSettings().model_deployment_name,
|
||||
name="Assistant",
|
||||
instructions="Answer the user's questions.",
|
||||
)
|
||||
|
||||
# 2. Create a Semantic Kernel agent for the Azure AI agent
|
||||
agent = AzureAIAgent(
|
||||
client=client,
|
||||
definition=agent_definition,
|
||||
)
|
||||
|
||||
# 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: AzureAIAgentThread = None
|
||||
|
||||
try:
|
||||
for user_input in USER_INPUTS:
|
||||
print(f"# User: {user_input}")
|
||||
# 4. Invoke the agent with the specified message for response
|
||||
response = await agent.get_response(messages=user_input, thread=thread)
|
||||
print(f"# {response.name}: {response}")
|
||||
thread = response.thread
|
||||
finally:
|
||||
# 6. Cleanup: Delete the thread and agent
|
||||
await thread.delete() if thread else None
|
||||
await client.agents.delete_agent(agent.id)
|
||||
|
||||
"""
|
||||
Sample Output:
|
||||
# User: Hello, I am John Doe.
|
||||
# Assistant: Hello, John! How can I assist you today?
|
||||
# User: What is your name?
|
||||
# Assistant: I'm here as your assistant, so you can just call me Assistant. How can I help you today?
|
||||
# User: What is my name?
|
||||
# Assistant: Your name is John Doe. How can I assist you today, John?
|
||||
"""
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
from typing import Annotated
|
||||
|
||||
from azure.identity.aio import AzureCliCredential
|
||||
|
||||
from semantic_kernel.agents import AzureAIAgent, AzureAIAgentSettings
|
||||
from semantic_kernel.functions import kernel_function
|
||||
|
||||
"""
|
||||
The following sample demonstrates how to create an Azure AI agent that answers
|
||||
questions about a sample menu using a Semantic Kernel Plugin.
|
||||
"""
|
||||
|
||||
|
||||
# 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) -> 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"
|
||||
|
||||
|
||||
# Simulate a conversation with the agent
|
||||
USER_INPUTS = [
|
||||
"Hello",
|
||||
"What is the special soup?",
|
||||
"How much does that cost?",
|
||||
"Thank you",
|
||||
]
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
async with (
|
||||
AzureCliCredential() as creds,
|
||||
AzureAIAgent.create_client(credential=creds) as client,
|
||||
):
|
||||
# 1. Create an agent on the Azure AI agent service
|
||||
agent_definition = await client.agents.create_agent(
|
||||
model=AzureAIAgentSettings().model_deployment_name,
|
||||
name="Host",
|
||||
instructions="Answer questions about the menu.",
|
||||
)
|
||||
|
||||
# 2. Create a Semantic Kernel agent for the Azure AI agent
|
||||
agent = AzureAIAgent(
|
||||
client=client,
|
||||
definition=agent_definition,
|
||||
plugins=[MenuPlugin()], # Add the plugin to the agent
|
||||
)
|
||||
|
||||
# 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"# User: {user_input}")
|
||||
# 4. Invoke the agent for the specified thread for response
|
||||
async for response in agent.invoke(
|
||||
messages=user_input,
|
||||
thread=thread,
|
||||
):
|
||||
print(f"# {response.name}: {response}")
|
||||
thread = response.thread
|
||||
finally:
|
||||
# 5. Cleanup: Delete the thread and agent
|
||||
await thread.delete() if thread else None
|
||||
await client.agents.delete_agent(agent.id)
|
||||
|
||||
"""
|
||||
Sample Output:
|
||||
# User: Hello
|
||||
# Agent: Hello! How can I assist you today?
|
||||
# User: What is the special soup?
|
||||
# ...
|
||||
"""
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
+119
@@ -0,0 +1,119 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
|
||||
from azure.identity.aio import AzureCliCredential
|
||||
|
||||
from semantic_kernel.agents import AgentGroupChat, AzureAIAgent, AzureAIAgentSettings
|
||||
from semantic_kernel.agents.strategies import TerminationStrategy
|
||||
from semantic_kernel.contents import AuthorRole
|
||||
|
||||
"""
|
||||
The following sample demonstrates how to create an OpenAI assistant using either
|
||||
Azure OpenAI or OpenAI, a chat completion agent and have them participate in a
|
||||
group chat to work towards the user's requirement.
|
||||
|
||||
Note: This sample use the `AgentGroupChat` feature of Semantic Kernel, which is
|
||||
no longer maintained. For a replacement, consider using the `GroupChatOrchestration`.
|
||||
|
||||
Read more about the `GroupChatOrchestration` here:
|
||||
https://learn.microsoft.com/semantic-kernel/frameworks/agent/agent-orchestration/group-chat?pivots=programming-language-python
|
||||
|
||||
Here is a migration guide from `AgentGroupChat` to `GroupChatOrchestration`:
|
||||
https://learn.microsoft.com/semantic-kernel/support/migration/group-chat-orchestration-migration-guide?pivots=programming-language-python
|
||||
"""
|
||||
|
||||
|
||||
class ApprovalTerminationStrategy(TerminationStrategy):
|
||||
"""A strategy for determining when an agent should terminate."""
|
||||
|
||||
async def should_agent_terminate(self, agent, history):
|
||||
"""Check if the agent should terminate."""
|
||||
return "approved" in history[-1].content.lower()
|
||||
|
||||
|
||||
REVIEWER_NAME = "ArtDirector"
|
||||
REVIEWER_INSTRUCTIONS = """
|
||||
You are an art director who has opinions about copywriting born of a love for David Ogilvy.
|
||||
The goal is to determine if the given copy is acceptable to print.
|
||||
If so, state that it is approved. Do not use the word "approve" unless you are giving approval.
|
||||
If not, provide insight on how to refine suggested copy without example.
|
||||
"""
|
||||
|
||||
COPYWRITER_NAME = "CopyWriter"
|
||||
COPYWRITER_INSTRUCTIONS = """
|
||||
You are a copywriter with ten years of experience and are known for brevity and a dry humor.
|
||||
The goal is to refine and decide on the single best copy as an expert in the field.
|
||||
Only provide a single proposal per response.
|
||||
You're laser focused on the goal at hand.
|
||||
Don't waste time with chit chat.
|
||||
Consider suggestions when refining an idea.
|
||||
"""
|
||||
|
||||
TASK = "a slogan for a new line of electric cars."
|
||||
|
||||
|
||||
async def main():
|
||||
ai_agent_settings = AzureAIAgentSettings()
|
||||
|
||||
async with (
|
||||
AzureCliCredential() as creds,
|
||||
AzureAIAgent.create_client(credential=creds) as client,
|
||||
):
|
||||
# 1. Create the reviewer agent on the Azure AI agent service
|
||||
reviewer_agent_definition = await client.agents.create_agent(
|
||||
model=ai_agent_settings.model_deployment_name,
|
||||
name=REVIEWER_NAME,
|
||||
instructions=REVIEWER_INSTRUCTIONS,
|
||||
)
|
||||
|
||||
# 2. Create a Semantic Kernel agent for the reviewer Azure AI agent
|
||||
agent_reviewer = AzureAIAgent(
|
||||
client=client,
|
||||
definition=reviewer_agent_definition,
|
||||
)
|
||||
|
||||
# 3. Create the copy writer agent on the Azure AI agent service
|
||||
copy_writer_agent_definition = await client.agents.create_agent(
|
||||
model=ai_agent_settings.model_deployment_name,
|
||||
name=COPYWRITER_NAME,
|
||||
instructions=COPYWRITER_INSTRUCTIONS,
|
||||
)
|
||||
|
||||
# 4. Create a Semantic Kernel agent for the copy writer Azure AI agent
|
||||
agent_writer = AzureAIAgent(
|
||||
client=client,
|
||||
definition=copy_writer_agent_definition,
|
||||
)
|
||||
|
||||
# 5. Place the agents in a group chat with a custom termination strategy
|
||||
chat = AgentGroupChat(
|
||||
agents=[agent_writer, agent_reviewer],
|
||||
termination_strategy=ApprovalTerminationStrategy(agents=[agent_reviewer], maximum_iterations=10),
|
||||
)
|
||||
|
||||
try:
|
||||
# 6. Add the task as a message to the group chat
|
||||
await chat.add_chat_message(message=TASK)
|
||||
print(f"# {AuthorRole.USER}: '{TASK}'")
|
||||
# 7. Invoke the chat
|
||||
async for content in chat.invoke():
|
||||
print(f"# {content.role} - {content.name or '*'}: '{content.content}'")
|
||||
finally:
|
||||
# 8. Cleanup: Delete the agents
|
||||
await chat.reset()
|
||||
await client.agents.delete_agent(agent_reviewer.id)
|
||||
await client.agents.delete_agent(agent_writer.id)
|
||||
|
||||
"""
|
||||
Sample Output:
|
||||
# AuthorRole.USER: 'a slogan for a new line of electric cars.'
|
||||
# AuthorRole.ASSISTANT - CopyWriter: '"Charge Ahead: Drive the Future."'
|
||||
# AuthorRole.ASSISTANT - ArtDirector: 'This slogan has a nice ring to it and captures the ...'
|
||||
# AuthorRole.ASSISTANT - CopyWriter: '"Plug In. Drive Green."'
|
||||
...
|
||||
"""
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
+133
@@ -0,0 +1,133 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
|
||||
from azure.ai.agents.models import CodeInterpreterTool, FilePurpose
|
||||
from azure.identity.aio import AzureCliCredential
|
||||
|
||||
from semantic_kernel.agents import AzureAIAgent, AzureAIAgentSettings, AzureAIAgentThread
|
||||
from semantic_kernel.contents import AuthorRole
|
||||
|
||||
"""
|
||||
The following sample demonstrates how to create a simple, Azure AI agent that
|
||||
uses the code interpreter tool to answer a coding question.
|
||||
"""
|
||||
|
||||
TASK = "What's the total sum of all sales for all segments using Python?"
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
async with (
|
||||
AzureCliCredential() as creds,
|
||||
AzureAIAgent.create_client(credential=creds) as client,
|
||||
):
|
||||
# 1. Create an agent with a code interpreter on the Azure AI agent service
|
||||
csv_file_path = os.path.join(
|
||||
os.path.dirname(os.path.dirname(os.path.realpath(__file__))), "resources", "sales.csv"
|
||||
)
|
||||
|
||||
# 2. Upload the CSV file to the Azure AI agent service
|
||||
file = await client.agents.files.upload_and_poll(file_path=csv_file_path, purpose=FilePurpose.AGENTS)
|
||||
|
||||
# 3. Create a code interpreter tool referencing the uploaded file
|
||||
code_interpreter = CodeInterpreterTool(file_ids=[file.id])
|
||||
agent_definition = await client.agents.create_agent(
|
||||
model=AzureAIAgentSettings().model_deployment_name,
|
||||
tools=code_interpreter.definitions,
|
||||
tool_resources=code_interpreter.resources,
|
||||
)
|
||||
|
||||
# 4. Create a Semantic Kernel agent for the Azure AI agent
|
||||
agent = AzureAIAgent(
|
||||
client=client,
|
||||
definition=agent_definition,
|
||||
)
|
||||
|
||||
# 5. Create a thread for the agent
|
||||
# If no thread is provided, a new thread will be
|
||||
# created and returned with the initial response
|
||||
thread: AzureAIAgentThread | None = None
|
||||
|
||||
try:
|
||||
print(f"# User: '{TASK}'")
|
||||
# 6. Invoke the agent for the specified thread for response
|
||||
async for response in agent.invoke(messages=TASK, thread=thread):
|
||||
if response.role != AuthorRole.TOOL:
|
||||
print(f"# Agent: {response}")
|
||||
thread = response.thread
|
||||
finally:
|
||||
# 7. Cleanup: Delete the thread, agent, and file
|
||||
await thread.delete() if thread else None
|
||||
await client.agents.delete_agent(agent.id)
|
||||
await client.agents.files.delete(file.id)
|
||||
|
||||
"""
|
||||
Sample Output:
|
||||
# User: 'Give me the code to calculate the total sales for all segments.'
|
||||
|
||||
# AuthorRole.ASSISTANT: Let me first load the uploaded file to understand its contents before providing
|
||||
tailored code.
|
||||
|
||||
```python
|
||||
import pandas as pd
|
||||
|
||||
# Load the uploaded file
|
||||
file_path = '/mnt/data/assistant-GBaUAF6AKds3sfdfSpxJZG'
|
||||
data = pd.read_excel(file_path) # Attempting to load as an Excel file initially
|
||||
|
||||
# Display the first few rows and understand its structure
|
||||
data.head(), data.info()
|
||||
```
|
||||
|
||||
# AuthorRole.ASSISTANT: The file format couldn't be automatically determined. Let me attempt to load it as a
|
||||
CSV or other type.
|
||||
|
||||
```python
|
||||
# Attempt to load the file as a CSV
|
||||
data = pd.read_csv(file_path)
|
||||
|
||||
# Display the first few rows of the dataset and its information
|
||||
data.head(), data.info()
|
||||
```
|
||||
|
||||
# AuthorRole.ASSISTANT: <class 'pandas.core.frame.DataFrame'>
|
||||
RangeIndex: 700 entries, 0 to 699
|
||||
Data columns (total 14 columns):
|
||||
# Column Non-Null Count Dtype
|
||||
--- ------ -------------- -----
|
||||
0 Segment 700 non-null object
|
||||
1 Country 700 non-null object
|
||||
2 Product 700 non-null object
|
||||
3 Units Sold 700 non-null float64
|
||||
4 Sale Price 700 non-null float64
|
||||
5 Gross Sales 700 non-null float64
|
||||
6 Discounts 700 non-null float64
|
||||
7 Sales 700 non-null float64
|
||||
8 COGS 700 non-null float64
|
||||
9 Profit 700 non-null float64
|
||||
10 Date 700 non-null object
|
||||
11 Month Number 700 non-null int64
|
||||
12 Month Name 700 non-null object
|
||||
13 Year 700 non-null int64
|
||||
dtypes: float64(7), int64(2), object(5)
|
||||
memory usage: 76.7+ KB
|
||||
The dataset has been successfully loaded and contains information regarding sales, profits, and related metrics
|
||||
for various segments. To calculate the total sales across all segments, we can use the "Sales" column.
|
||||
|
||||
Here's the code to calculate the total sales:
|
||||
|
||||
```python
|
||||
# Calculate the total sales for all segments
|
||||
total_sales = data['Sales'].sum()
|
||||
|
||||
total_sales
|
||||
```
|
||||
|
||||
# AuthorRole.ASSISTANT: The total sales for all segments amount to approximately **118,726,350.29**. Let me
|
||||
know if you need additional analysis or code for manipulating this data!
|
||||
"""
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
|
||||
from azure.ai.agents.models import FileInfo, FileSearchTool, VectorStore
|
||||
from azure.identity.aio import AzureCliCredential
|
||||
|
||||
from semantic_kernel.agents import AzureAIAgent, AzureAIAgentSettings, AzureAIAgentThread
|
||||
from semantic_kernel.contents import AuthorRole
|
||||
|
||||
"""
|
||||
The following sample demonstrates how to create a simple, Azure AI agent that
|
||||
uses a file search tool to answer user questions.
|
||||
"""
|
||||
|
||||
# Simulate a conversation with the agent
|
||||
USER_INPUTS = [
|
||||
"Who is the youngest employee?",
|
||||
"Who works in sales?",
|
||||
"I have a customer request, who can help me?",
|
||||
]
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
async with (
|
||||
AzureCliCredential() as creds,
|
||||
AzureAIAgent.create_client(credential=creds) as client,
|
||||
):
|
||||
# 1. Read and upload the file to the Azure AI agent service
|
||||
pdf_file_path = os.path.join(
|
||||
os.path.dirname(os.path.dirname(os.path.realpath(__file__))), "resources", "employees.pdf"
|
||||
)
|
||||
file: FileInfo = await client.agents.files.upload_and_poll(file_path=pdf_file_path, purpose="assistants")
|
||||
vector_store: VectorStore = await client.agents.vector_stores.create_and_poll(
|
||||
file_ids=[file.id], name="my_vectorstore"
|
||||
)
|
||||
|
||||
# 2. Create file search tool with uploaded resources
|
||||
file_search = FileSearchTool(vector_store_ids=[vector_store.id])
|
||||
|
||||
# 3. Create an agent on the Azure AI agent service with the file search tool
|
||||
agent_definition = await client.agents.create_agent(
|
||||
model=AzureAIAgentSettings().model_deployment_name,
|
||||
tools=file_search.definitions,
|
||||
tool_resources=file_search.resources,
|
||||
)
|
||||
|
||||
# 4. Create a Semantic Kernel agent for the Azure AI agent
|
||||
agent = AzureAIAgent(
|
||||
client=client,
|
||||
definition=agent_definition,
|
||||
)
|
||||
|
||||
# 5. Create a thread for the agent
|
||||
# If no thread is provided, a new thread will be
|
||||
# created and returned with the initial response
|
||||
thread: AzureAIAgentThread = None
|
||||
|
||||
try:
|
||||
for user_input in USER_INPUTS:
|
||||
print(f"# User: '{user_input}'")
|
||||
# 6. Invoke the agent for the specified thread for response
|
||||
async for response in agent.invoke(messages=user_input, thread=thread):
|
||||
if response.role != AuthorRole.TOOL:
|
||||
print(f"# Agent: {response}")
|
||||
thread = response.thread
|
||||
finally:
|
||||
# 7. Cleanup: Delete the thread and agent and other resources
|
||||
await thread.delete() if thread else None
|
||||
await client.agents.vector_stores.delete(vector_store.id)
|
||||
await client.agents.files.delete(file.id)
|
||||
await client.agents.delete_agent(agent.id)
|
||||
|
||||
"""
|
||||
Sample Output:
|
||||
# User: 'Who is the youngest employee?'
|
||||
# Agent: The youngest employee is Teodor Britton, who is an accountant and was born on January 9, 1997...
|
||||
# User: 'Who works in sales?'
|
||||
"""
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
+119
@@ -0,0 +1,119 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
import os
|
||||
|
||||
from azure.ai.agents.models import OpenApiAnonymousAuthDetails, OpenApiTool
|
||||
from azure.identity.aio import AzureCliCredential
|
||||
|
||||
from semantic_kernel.agents import AzureAIAgent, AzureAIAgentSettings, AzureAIAgentThread
|
||||
from semantic_kernel.contents import ChatMessageContent, FunctionCallContent, FunctionResultContent
|
||||
|
||||
"""
|
||||
The following sample demonstrates how to create a simple, Azure AI agent that
|
||||
uses OpenAPI tools to answer user questions.
|
||||
"""
|
||||
|
||||
|
||||
# Simulate a conversation with the agent
|
||||
USER_INPUTS = [
|
||||
"What is the name and population of the country that uses currency with abbreviation THB",
|
||||
"What is the current weather in the capital city of the country?",
|
||||
]
|
||||
|
||||
|
||||
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}")
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
async with (
|
||||
AzureCliCredential() as creds,
|
||||
AzureAIAgent.create_client(credential=creds) as client,
|
||||
):
|
||||
# 1. Read in the OpenAPI spec files
|
||||
openapi_spec_file_path = os.path.join(
|
||||
os.path.dirname(os.path.dirname(os.path.realpath(__file__))),
|
||||
"resources",
|
||||
)
|
||||
with open(os.path.join(openapi_spec_file_path, "weather.json")) as weather_file:
|
||||
weather_openapi_spec = json.loads(weather_file.read())
|
||||
with open(os.path.join(openapi_spec_file_path, "countries.json")) as countries_file:
|
||||
countries_openapi_spec = json.loads(countries_file.read())
|
||||
|
||||
# 2. Create OpenAPI tools
|
||||
# Note that connection or managed identity auth setup requires additional setup in Azure
|
||||
auth = OpenApiAnonymousAuthDetails()
|
||||
openapi_weather = OpenApiTool(
|
||||
name="get_weather",
|
||||
spec=weather_openapi_spec,
|
||||
description="Retrieve weather information for a location",
|
||||
auth=auth,
|
||||
)
|
||||
openapi_countries = OpenApiTool(
|
||||
name="get_country",
|
||||
spec=countries_openapi_spec,
|
||||
description="Retrieve country information",
|
||||
auth=auth,
|
||||
)
|
||||
|
||||
# 3. Create an agent on the Azure AI agent service with the OpenAPI tools
|
||||
agent_definition = await client.agents.create_agent(
|
||||
model=AzureAIAgentSettings().model_deployment_name,
|
||||
tools=openapi_weather.definitions + openapi_countries.definitions,
|
||||
)
|
||||
|
||||
# 4. Create a Semantic Kernel agent for the Azure AI agent
|
||||
agent = AzureAIAgent(
|
||||
client=client,
|
||||
definition=agent_definition,
|
||||
)
|
||||
|
||||
# 5. Create a thread for the agent
|
||||
# If no thread is provided, a new thread will be
|
||||
# created and returned with the initial response
|
||||
thread: AzureAIAgentThread = None
|
||||
|
||||
try:
|
||||
for user_input in USER_INPUTS:
|
||||
print(f"# User: '{user_input}'")
|
||||
# 7. Invoke the agent for the specified thread for response
|
||||
async for response in agent.invoke(messages=user_input, thread=thread):
|
||||
print(f"# Agent: {response}")
|
||||
thread = response.thread
|
||||
finally:
|
||||
# 8. Cleanup: Delete the thread and agent
|
||||
await client.agents.threads.delete(thread.id) if thread else None
|
||||
await client.agents.delete_agent(agent.id)
|
||||
|
||||
"""
|
||||
Sample Output:
|
||||
# User: 'What is the name and population of the country that uses currency with abbreviation THB'
|
||||
# Agent: It seems I encountered an issue while trying to retrieve data about the country that uses the ...
|
||||
|
||||
As of the latest estimates, the population of Thailand is approximately 69 million people. If you ...
|
||||
# User: 'What is the current weather in the capital city of the country?'
|
||||
# Agent: The current weather in Bangkok, Thailand, the capital city, is as follows:
|
||||
|
||||
- **Temperature**: 24°C (76°F)
|
||||
- **Feels Like**: 26°C (79°F)
|
||||
- **Weather Description**: Light rain
|
||||
- **Humidity**: 69%
|
||||
- **Cloud Cover**: 75%
|
||||
- **Pressure**: 1017 hPa
|
||||
- **Wind Speed**: 8 km/h (5 mph) from the east-northeast (ENE)
|
||||
- **Visibility**: 10 km (approximately 6 miles)
|
||||
|
||||
This weather information reflects the current conditions as of the latest observation. If you need ...
|
||||
"""
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
|
||||
from azure.identity.aio import AzureCliCredential
|
||||
|
||||
from semantic_kernel.agents import AzureAIAgent, AzureAIAgentThread
|
||||
from semantic_kernel.contents import ChatMessageContent, FunctionCallContent, FunctionResultContent
|
||||
|
||||
"""
|
||||
The following sample demonstrates how to use an already existing
|
||||
Azure AI Agent within Semantic Kernel. This sample requires that you
|
||||
have an existing agent created either previously in code or via the
|
||||
Azure Portal (or CLI).
|
||||
"""
|
||||
|
||||
|
||||
# Simulate a conversation with the agent
|
||||
USER_INPUTS = [
|
||||
"Using the provided doc, tell me about the evolution of RAG.",
|
||||
]
|
||||
|
||||
|
||||
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}")
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
async with (
|
||||
AzureCliCredential() as creds,
|
||||
AzureAIAgent.create_client(credential=creds) as client,
|
||||
):
|
||||
# 1. Retrieve the agent definition based on the `agent_id`
|
||||
# Replace the "your-agent-id" with the actual agent ID
|
||||
# you want to use.
|
||||
agent_definition = await client.agents.get_agent(
|
||||
agent_id="<your-agent-id>",
|
||||
)
|
||||
|
||||
# 2. Create a Semantic Kernel agent for the Azure AI agent
|
||||
agent = AzureAIAgent(
|
||||
client=client,
|
||||
definition=agent_definition,
|
||||
)
|
||||
|
||||
# 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: AzureAIAgentThread = None
|
||||
|
||||
try:
|
||||
for user_input in USER_INPUTS:
|
||||
print(f"# User: '{user_input}'")
|
||||
# 4. Invoke the agent for the specified thread for response
|
||||
async for response in agent.invoke_stream(
|
||||
messages=user_input,
|
||||
thread=thread,
|
||||
on_intermediate_message=handle_streaming_intermediate_steps,
|
||||
):
|
||||
# Print the agent's response
|
||||
print(f"{response}", end="", flush=True)
|
||||
# Update the thread for subsequent messages
|
||||
thread = response.thread
|
||||
finally:
|
||||
# 5. Cleanup: Delete the thread and agent
|
||||
await thread.delete() if thread else None
|
||||
# Do not clean up the agent so it can be used again
|
||||
|
||||
"""
|
||||
Sample Output:
|
||||
# User: 'Why is the sky blue?'
|
||||
# Agent: The sky appears blue because molecules in the Earth's atmosphere scatter sunlight,
|
||||
and blue light is scattered more than other colors due to its shorter wavelength.
|
||||
"""
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
+110
@@ -0,0 +1,110 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
from typing import Annotated
|
||||
|
||||
from azure.identity.aio import AzureCliCredential
|
||||
|
||||
from semantic_kernel.agents import AgentRegistry, AzureAIAgent, AzureAIAgentSettings
|
||||
from semantic_kernel.functions import kernel_function
|
||||
from semantic_kernel.kernel import Kernel
|
||||
|
||||
"""
|
||||
The following sample demonstrates how to create an Azure AI agent that answers
|
||||
questions about a sample menu using a Semantic Kernel Plugin. The agent is created
|
||||
using a yaml declarative spec.
|
||||
"""
|
||||
|
||||
|
||||
# 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) -> 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"
|
||||
|
||||
|
||||
# Simulate a conversation with the agent
|
||||
USER_INPUTS = [
|
||||
"Hello",
|
||||
"What is the special soup?",
|
||||
"How much does that cost?",
|
||||
"Thank you",
|
||||
]
|
||||
|
||||
# Define the YAML string for the sample
|
||||
SPEC = """
|
||||
type: foundry_agent
|
||||
name: Host
|
||||
instructions: Respond politely to the user's questions.
|
||||
model:
|
||||
id: ${AzureAI:ChatModelId}
|
||||
tools:
|
||||
- id: MenuPlugin.get_specials
|
||||
type: function
|
||||
- id: MenuPlugin.get_item_price
|
||||
type: function
|
||||
"""
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
settings = AzureAIAgentSettings()
|
||||
async with (
|
||||
AzureCliCredential() as creds,
|
||||
AzureAIAgent.create_client(credential=creds) as client,
|
||||
):
|
||||
# 1. Create a Kernel instance
|
||||
# For declarative agents, the kernel is required to resolve the plugin(s)
|
||||
kernel = Kernel()
|
||||
kernel.add_plugin(MenuPlugin())
|
||||
|
||||
# 2. Create a Semantic Kernel agent for the Azure AI agent
|
||||
agent: AzureAIAgent = await AgentRegistry.create_from_yaml(
|
||||
SPEC,
|
||||
kernel=kernel,
|
||||
settings=settings,
|
||||
client=client,
|
||||
)
|
||||
|
||||
# 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"# User: {user_input}")
|
||||
# 4. Invoke the agent for the specified thread for response
|
||||
async for response in agent.invoke(
|
||||
messages=user_input,
|
||||
thread=thread,
|
||||
):
|
||||
print(f"# {response.name}: {response}")
|
||||
thread = response.thread
|
||||
finally:
|
||||
# 5. Cleanup: Delete the thread and agent
|
||||
await thread.delete() if thread else None
|
||||
await client.agents.delete_agent(agent.id)
|
||||
|
||||
"""
|
||||
Sample Output:
|
||||
# User: Hello
|
||||
# Agent: Hello! How can I assist you today?
|
||||
# User: What is the special soup?
|
||||
# ...
|
||||
"""
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
+119
@@ -0,0 +1,119 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
|
||||
from azure.ai.agents.models import McpTool
|
||||
from azure.identity.aio import AzureCliCredential
|
||||
|
||||
from semantic_kernel.agents import AzureAIAgent, AzureAIAgentSettings, AzureAIAgentThread
|
||||
from semantic_kernel.contents import ChatMessageContent, FunctionCallContent, FunctionResultContent
|
||||
|
||||
"""
|
||||
The following sample demonstrates how to create a simple, Azure AI agent that
|
||||
uses the mcp tool to connect to an mcp server.
|
||||
"""
|
||||
|
||||
TASK = "Please summarize the Azure REST API specifications Readme"
|
||||
|
||||
|
||||
async def handle_intermediate_messages(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}")
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
async with (
|
||||
AzureCliCredential() as creds,
|
||||
AzureAIAgent.create_client(credential=creds) as client,
|
||||
):
|
||||
# 1. Define the MCP tool with the server URL
|
||||
mcp_tool = McpTool(
|
||||
server_label="github",
|
||||
server_url="https://gitmcp.io/Azure/azure-rest-api-specs",
|
||||
allowed_tools=[], # Specify allowed tools if needed
|
||||
)
|
||||
|
||||
# Optionally you may configure to require approval
|
||||
# Allowed values are "never" or "always"
|
||||
mcp_tool.set_approval_mode("never")
|
||||
|
||||
# 2. Create an agent with the MCP tool on the Azure AI agent service
|
||||
agent_definition = await client.agents.create_agent(
|
||||
model=AzureAIAgentSettings().model_deployment_name,
|
||||
tools=mcp_tool.definitions,
|
||||
instructions="You are a helpful agent that can use MCP tools to assist users.",
|
||||
)
|
||||
|
||||
# 3. Create a Semantic Kernel agent for the Azure AI agent
|
||||
agent = AzureAIAgent(
|
||||
client=client,
|
||||
definition=agent_definition,
|
||||
)
|
||||
|
||||
# 4. Create a thread for the agent
|
||||
# If no thread is provided, a new thread will be
|
||||
# created and returned with the initial response
|
||||
thread: AzureAIAgentThread | None = None
|
||||
|
||||
try:
|
||||
print(f"# User: '{TASK}'")
|
||||
# 5. Invoke the agent for the specified thread for response
|
||||
async for response in agent.invoke(
|
||||
messages=TASK, thread=thread, on_intermediate_message=handle_intermediate_messages
|
||||
):
|
||||
print(f"# Agent: {response}")
|
||||
thread = response.thread
|
||||
finally:
|
||||
# 6. Cleanup: Delete the thread, agent, and file
|
||||
await thread.delete() if thread else None
|
||||
await client.agents.delete_agent(agent.id)
|
||||
|
||||
"""
|
||||
Sample Output:
|
||||
|
||||
# User: 'Please summarize the Azure REST API specifications Readme'
|
||||
Function Call:> fetch_azure_rest_api_docs with arguments: {}
|
||||
The Azure REST API specifications Readme provides comprehensive documentation and guidelines for designing,
|
||||
authoring, validating, and evolving Azure REST APIs. It covers key areas including:
|
||||
|
||||
1. Breaking changes and versioning: Guidelines to manage API changes that break backward compatibility, when to
|
||||
increment API versions, and how to maintain smooth API evolution.
|
||||
|
||||
2. OpenAPI/Swagger specifications: How to author REST APIs using OpenAPI specification 2.0 (Swagger), including
|
||||
structure, conventions, validation tools, and extensions used by AutoRest for generating client SDKs.
|
||||
|
||||
3. TypeSpec language: Introduction to TypeSpec, a powerful language for describing and generating REST API
|
||||
specifications and client SDKs with extensibility to other API styles.
|
||||
|
||||
4. Directory structure and uniform versioning: Organizing service specifications by teams, resource provider
|
||||
namespaces, and following uniform versioning to keep API versions consistent across documentation and SDKs.
|
||||
|
||||
5. Validation and tooling: Tools and processes like OAV, AutoRest, RESTler, and CI checks used to validate API
|
||||
specs, generate SDKs, detect breaking changes, lint specifications, and test service contract accuracy.
|
||||
|
||||
6. Authoring best practices: Manual and automated guidelines for quality API spec authoring, including writing
|
||||
effective descriptions, resource modeling, naming conventions, and examples.
|
||||
|
||||
7. Code generation configurations: How to configure readme files to generate SDKs for various languages
|
||||
including .NET, Java, Python, Go, Typescript, and Azure CLI using AutoRest.
|
||||
|
||||
8. API Scenarios and testing: Defining API scenario test files for end-to-end REST API workflows, including
|
||||
variables, ARM template integration, and usage of test-proxy for recording traffic.
|
||||
|
||||
9. SDK automation and release requests: Workflows for SDK generation validation, suppressing breaking change
|
||||
warnings, and requesting official Azure SDK releases.
|
||||
|
||||
Overall, the Readme acts as a central hub providing references, guidelines, examples, and tools for maintaining
|
||||
high-quality Azure REST API specifications and seamless SDK generation across multiple languages and
|
||||
platforms. It ensures consistent API design, versioning, validation, and developer experience in the Azure
|
||||
ecosystem.
|
||||
"""
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
+143
@@ -0,0 +1,143 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
from datetime import timedelta
|
||||
|
||||
from azure.ai.agents.models import DeepResearchTool
|
||||
from azure.identity.aio import AzureCliCredential
|
||||
|
||||
from semantic_kernel.agents import AzureAIAgent, AzureAIAgentSettings, AzureAIAgentThread, RunPollingOptions
|
||||
from semantic_kernel.contents import AnnotationContent, ChatMessageContent, FunctionCallContent, FunctionResultContent
|
||||
|
||||
"""
|
||||
The following sample demonstrates how to create an AzureAIAgent along
|
||||
with the Deep Research Tool. Please visit the following documentation for more info
|
||||
on what is required to run the sample: https://aka.ms/agents-deep-research. Please pay
|
||||
attention to the purple `Note` boxes in the Azure docs.
|
||||
|
||||
Note that when you use your Bing Connection ID, it needs to be the connection ID from the project, not the resource.
|
||||
It has the following format:
|
||||
|
||||
'/subscriptions/<sub_id>/resourceGroups/<rg_name>/providers/<provider_name>/accounts/<account_name>/projects/<project_name>/connections/<connection_name>'
|
||||
"""
|
||||
|
||||
TASK = (
|
||||
"Research the current state of studies on orca intelligence and orca language, "
|
||||
"including what is currently known about orcas' cognitive capabilities and communication systems."
|
||||
)
|
||||
|
||||
|
||||
async def handle_intermediate_messages(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}")
|
||||
print()
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
async with (
|
||||
AzureCliCredential() as creds,
|
||||
AzureAIAgent.create_client(credential=creds) as client,
|
||||
):
|
||||
azure_ai_agent_settings = AzureAIAgentSettings()
|
||||
# 1. Define the Deep Research tool
|
||||
deep_research_tool = DeepResearchTool(
|
||||
bing_grounding_connection_id=azure_ai_agent_settings.bing_connection_id,
|
||||
deep_research_model=azure_ai_agent_settings.deep_research_model,
|
||||
)
|
||||
|
||||
# 2. Create an agent with the tool on the Azure AI agent service
|
||||
agent_definition = await client.agents.create_agent(
|
||||
model="gpt-4o",
|
||||
tools=deep_research_tool.definitions,
|
||||
instructions="You are a helpful Agent that assists in researching scientific topics.",
|
||||
)
|
||||
|
||||
# 3. Create a Semantic Kernel agent for the Azure AI agent
|
||||
agent = AzureAIAgent(client=client, definition=agent_definition, name="DeepResearchAgent")
|
||||
|
||||
# 4. Create a thread for the agent
|
||||
# If no thread is provided, a new thread will be
|
||||
# created and returned with the initial response
|
||||
thread: AzureAIAgentThread | None = None
|
||||
|
||||
try:
|
||||
print(f"# User: '{TASK}'")
|
||||
# 5. Invoke the agent for the specified thread for response
|
||||
async for response in agent.invoke(
|
||||
messages=TASK,
|
||||
thread=thread,
|
||||
on_intermediate_message=handle_intermediate_messages,
|
||||
polling_options=RunPollingOptions(run_polling_timeout=timedelta(minutes=10)),
|
||||
):
|
||||
# Note that the underlying Deep Research Tool uses the o3 reasoning model.
|
||||
# When using the non-streaming invoke, it is normal for there to be
|
||||
# several minutes of processing before agent response(s) appear.
|
||||
# For a fast response, consider using the streaming invoke method.
|
||||
# A sample exists in the `concepts/agents/azure_ai_agent` directory.
|
||||
print(f"# {response.name}: {response}")
|
||||
for item in response.items or []:
|
||||
if isinstance(item, AnnotationContent):
|
||||
label = item.title or item.url or (item.quote or "Annotation")
|
||||
print(f"\n[Annotation] {label} -> {item.url}")
|
||||
thread = response.thread
|
||||
finally:
|
||||
# 6. Cleanup: Delete the thread, agent, and file
|
||||
await thread.delete() if thread else None
|
||||
await client.agents.delete_agent(agent.id)
|
||||
|
||||
"""
|
||||
Sample Output:
|
||||
|
||||
# User: 'Research the current state of studies on orca intelligence and orca language, including what is
|
||||
currently known about orcas' cognitive capabilities and communication systems.'
|
||||
|
||||
Function Call:> deep_research with arguments: {'input': '{"prompt": "Research the current state of studies on
|
||||
orca intelligence and orca language. Include up-to-date findings on both their cognitive capabilities and
|
||||
communication systems. Structure the findings as a detailed report with well-formatted headers that break down
|
||||
topics such as orcas\' cognitive skills (e.g., problem-solving, memory, and social intelligence), their
|
||||
language or communication methods (e.g., vocalizations, dialects, and symbolic communication), and comparisons
|
||||
to other highly intelligent species. Include sources, prioritize recent peer-reviewed studies and reputable
|
||||
marine biology publications, and ensure the report is well-cited and clear."}'}
|
||||
|
||||
# DeepResearchAgent: Got it! I'll gather recent studies and findings on orca intelligence and their
|
||||
# communication systems, focusing on cognitive abilities and the mechanisms of their language.
|
||||
# I'll update you with a comprehensive overview soon.
|
||||
|
||||
Title: Current Research on Orca Intelligence and Language
|
||||
|
||||
Starting deep research...
|
||||
|
||||
# DeepResearchAgent: cot_summary: **Examining orca intelligence**
|
||||
The task involves looking into orca cognitive abilities, communication methods, and comparisons with
|
||||
other intelligent species. It includes recent peer-reviewed studies and credible sources. 【1†Bing Search】
|
||||
|
||||
|
||||
[Annotation] Bing Search: '2024 orca intelligence cognitive study research' -> https://www.bing.com/search?q=2024%20orca%20intelligence%20cognitive%20study%20research
|
||||
# DeepResearchAgent: cot_summary: **Investigating orca intelligence**
|
||||
|
||||
I'm curious about headlines discussing orcas' cognitive abilities and behaviors, especially their tool use,
|
||||
recent attacks, and comparisons with dolphins. 【1†Bing Search】
|
||||
|
||||
|
||||
[Annotation] Bing Search: 'orca cognition recent peer-reviewed studies orca communication recent study' -> https://www.bing.com/search?q=orca%20cognition%20recent%20peer-reviewed%20studies%20orca%20communication%20recent%20study
|
||||
# DeepResearchAgent: cot_summary: **Researching social dynamics**
|
||||
|
||||
I'm gathering info on killer whale social dynamics from what appears to be a 2024 article on ResearchGate.
|
||||
|
||||
# DeepResearchAgent: cot_summary: **Assessing publication type**
|
||||
I'm exploring if the ResearchGate link points to a preprint, student paper, or journal article.
|
||||
Progress is steady as I look into its initiation and source.
|
||||
|
||||
# DeepResearchAgent: cot_summary: **Investigating PDF issues**
|
||||
|
||||
...
|
||||
"""
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,44 @@
|
||||
# Chat Completion Agents
|
||||
|
||||
The following samples demonstrate how to get started with Chat Completion Agents using Semantic Kernel.
|
||||
|
||||
## Configuring a Chat Completion Agent
|
||||
|
||||
The `ChatCompletionAgent` relies on an underlying AI service connector. Depending on the AI service you choose, you may need to install additional packages. Refer to the [official SK documentation](https://learn.microsoft.com/en-us/semantic-kernel/concepts/ai-services/chat-completion/?tabs=csharp-AzureOpenAI%2Cpython-AzureOpenAI%2Cjava-AzureOpenAI&pivots=programming-language-python#installing-the-necessary-packages-1) for guidance on which extras are required.
|
||||
|
||||
Next, follow this [configuration guide](../../concepts/README.md#configuring-the-kernel) to set up your environment for running the sample code.
|
||||
|
||||
If you're developing outside the Semantic Kernel repository, it's recommended to place your `.env` file at the root of your project. When using VSCode, this allows the IDE to automatically load the `.env` file and make the environment variables available to your application.
|
||||
|
||||
This setup enables the following code to work without explicitly passing keyword arguments to the AI service constructor:
|
||||
|
||||
```python
|
||||
from semantic_kernel.agents import ChatCompletionAgent
|
||||
from semantic_kernel.connectors.ai.open_ai import AzureChatCompletion
|
||||
|
||||
agent = ChatCompletionAgent(
|
||||
service=AzureChatCompletion(), # No explicit kwargs needed due to environment variable configuration
|
||||
name="Assistant",
|
||||
instructions="Answer questions about the world in one sentence.",
|
||||
)
|
||||
```
|
||||
|
||||
If you prefer to configure the service manually, you can do the following:
|
||||
|
||||
```python
|
||||
from semantic_kernel.agents import ChatCompletionAgent
|
||||
from semantic_kernel.connectors.ai.open_ai import AzureChatCompletion
|
||||
|
||||
agent = ChatCompletionAgent(
|
||||
service=AzureChatCompletion(
|
||||
api_key="your-api-key",
|
||||
endpoint="your-aoai-endpoint",
|
||||
deployment_name="your-deployment-name",
|
||||
api_version="2025-03-01-preview" # Replace with your desired API version
|
||||
),
|
||||
name="Assistant",
|
||||
instructions="Answer questions about the world in one sentence.",
|
||||
)
|
||||
```
|
||||
|
||||
For more information about the `ChatCompletionAgent` see Semantic Kernel's official documentation [here](https://learn.microsoft.com/en-us/semantic-kernel/frameworks/agent/chat-completion-agent?pivots=programming-language-python).
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
|
||||
from azure.identity import AzureCliCredential
|
||||
|
||||
from semantic_kernel.agents import ChatCompletionAgent
|
||||
from semantic_kernel.connectors.ai.open_ai import AzureChatCompletion
|
||||
|
||||
"""
|
||||
The following sample demonstrates how to create a chat completion agent that
|
||||
answers user questions using the Azure Chat Completion service. The Chat Completion
|
||||
Service is passed directly via the ChatCompletionAgent constructor. This sample
|
||||
demonstrates the basic steps to create an agent and simulate a conversation
|
||||
with the agent.
|
||||
|
||||
The interaction with the agent is via the `get_response` method, which sends a
|
||||
user input to the agent and receives a response from the agent.
|
||||
"""
|
||||
|
||||
# Simulate a conversation with the agent
|
||||
USER_INPUTS = [
|
||||
"Why is the sky blue?",
|
||||
"What is the capital of France?",
|
||||
]
|
||||
|
||||
|
||||
async def main():
|
||||
# 1. Create the agent by specifying the service
|
||||
agent = ChatCompletionAgent(
|
||||
service=AzureChatCompletion(credential=AzureCliCredential()),
|
||||
name="Assistant",
|
||||
instructions="Answer questions about the world in one sentence.",
|
||||
)
|
||||
|
||||
for user_input in USER_INPUTS:
|
||||
print(f"# User: {user_input}")
|
||||
# 2. Invoke the agent for a response
|
||||
response = await agent.get_response(
|
||||
messages=user_input,
|
||||
)
|
||||
# 3. Print the response
|
||||
print(f"# {response.name}: {response}")
|
||||
|
||||
"""
|
||||
Sample output:
|
||||
# User: Why is the sky blue?
|
||||
# Assistant: The sky appears blue because molecules in the Earth's atmosphere scatter shorter wavelengths of
|
||||
sunlight, like blue, more than the longer wavelengths, causing the sky to look blue to our eyes.
|
||||
# User: What is the capital of France?
|
||||
# Assistant: The capital of France is Paris.
|
||||
"""
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
|
||||
from azure.identity import AzureCliCredential
|
||||
|
||||
from semantic_kernel.agents import (
|
||||
ChatCompletionAgent,
|
||||
ChatHistoryAgentThread,
|
||||
)
|
||||
from semantic_kernel.connectors.ai.open_ai import AzureChatCompletion
|
||||
|
||||
"""
|
||||
The following sample demonstrates how to create a chat completion agent that
|
||||
answers user questions using the Azure Chat Completion service. The Chat Completion
|
||||
Service is passed directly via the ChatCompletionAgent constructor. This sample
|
||||
demonstrates the basic steps to create an agent and simulate a conversation
|
||||
with the agent.
|
||||
|
||||
The interaction with the agent is via the `get_response` method, which sends a
|
||||
user input to the agent and receives a response from the agent. The conversation
|
||||
history needs to be maintained by the caller in the chat history object.
|
||||
"""
|
||||
|
||||
# Simulate a conversation with the agent
|
||||
USER_INPUTS = [
|
||||
"Hello, I am John Doe.",
|
||||
"What is your name?",
|
||||
"What is my name?",
|
||||
]
|
||||
|
||||
|
||||
async def main():
|
||||
# 1. Create the agent by specifying the service
|
||||
agent = ChatCompletionAgent(
|
||||
service=AzureChatCompletion(credential=AzureCliCredential()),
|
||||
name="Assistant",
|
||||
instructions="Answer the user's questions.",
|
||||
)
|
||||
|
||||
# 2. Create a thread to hold the conversation
|
||||
# If no thread is provided, a new thread will be
|
||||
# created and returned with the initial response
|
||||
thread: ChatHistoryAgentThread = None
|
||||
|
||||
for user_input in USER_INPUTS:
|
||||
print(f"# User: {user_input}")
|
||||
# 3. Invoke the agent for a response
|
||||
response = await agent.get_response(
|
||||
messages=user_input,
|
||||
thread=thread,
|
||||
)
|
||||
print(f"# {response.name}: {response}")
|
||||
# 4. Store the thread, which allows the agent to
|
||||
# maintain conversation history across multiple messages.
|
||||
thread = response.thread
|
||||
|
||||
# 5. Cleanup: Clear the thread
|
||||
await thread.delete() if thread else None
|
||||
|
||||
"""
|
||||
Sample output:
|
||||
# User: Hello, I am John Doe.
|
||||
# Assistant: Hello, John Doe! How can I assist you today?
|
||||
# User: What is your name?
|
||||
# Assistant: I don't have a personal name like a human does, but you can call me Assistant.?
|
||||
# User: What is my name?
|
||||
# Assistant: You mentioned that your name is John Doe. How can I assist you further, John?
|
||||
"""
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
|
||||
from azure.identity import AzureCliCredential
|
||||
|
||||
from semantic_kernel import Kernel
|
||||
from semantic_kernel.agents import ChatCompletionAgent, ChatHistoryAgentThread
|
||||
from semantic_kernel.connectors.ai.open_ai import AzureChatCompletion
|
||||
|
||||
"""
|
||||
The following sample demonstrates how to create a chat completion agent that
|
||||
answers user questions using the Azure Chat Completion service. The Chat Completion
|
||||
Service is first added to the kernel, and the kernel is passed in to the
|
||||
ChatCompletionAgent constructor. This sample demonstrates the basic steps to
|
||||
create an agent and simulate a conversation with the agent.
|
||||
|
||||
Note: if both a service and a kernel are provided, the service will be used.
|
||||
|
||||
The interaction with the agent is via the `get_response` method, which sends a
|
||||
user input to the agent and receives a response from the agent. The conversation
|
||||
history needs to be maintained by the caller in the chat history object.
|
||||
"""
|
||||
|
||||
# Simulate a conversation with the agent
|
||||
USER_INPUTS = [
|
||||
"Hello, I am John Doe.",
|
||||
"What is your name?",
|
||||
"What is my name?",
|
||||
]
|
||||
|
||||
|
||||
async def main():
|
||||
# 1. Create the instance of the Kernel to register an AI service
|
||||
kernel = Kernel()
|
||||
kernel.add_service(AzureChatCompletion(credential=AzureCliCredential()))
|
||||
|
||||
# 2. Create the agent
|
||||
agent = ChatCompletionAgent(
|
||||
kernel=kernel,
|
||||
name="Assistant",
|
||||
instructions="Answer the user's questions.",
|
||||
)
|
||||
|
||||
# 3. Create a thread to hold the conversation
|
||||
# If no thread is provided, a new thread will be
|
||||
# created and returned with the initial response
|
||||
thread: ChatHistoryAgentThread = None
|
||||
|
||||
for user_input in USER_INPUTS:
|
||||
print(f"# User: {user_input}")
|
||||
# 4. Invoke the agent for a response
|
||||
response = await agent.get_response(
|
||||
messages=user_input,
|
||||
thread=thread,
|
||||
)
|
||||
print(f"# {response.name}: {response}")
|
||||
thread = response.thread
|
||||
|
||||
# 4. Cleanup: Clear the thread
|
||||
await thread.delete() if thread else None
|
||||
|
||||
"""
|
||||
Sample output:
|
||||
# User: Hello, I am John Doe.
|
||||
# Assistant: Hello, John Doe! How can I assist you today?
|
||||
# User: What is your name?
|
||||
# Assistant: I don't have a personal name like a human does, but you can call me Assistant.?
|
||||
# User: What is my name?
|
||||
# Assistant: You mentioned that your name is John Doe. How can I assist you further, John?
|
||||
"""
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
from typing import Annotated
|
||||
|
||||
from azure.identity import AzureCliCredential
|
||||
|
||||
from semantic_kernel.agents import ChatCompletionAgent, ChatHistoryAgentThread
|
||||
from semantic_kernel.connectors.ai.open_ai import AzureChatCompletion
|
||||
from semantic_kernel.functions import kernel_function
|
||||
|
||||
"""
|
||||
The following sample demonstrates how to create a chat completion agent that
|
||||
answers questions about a sample menu using a Semantic Kernel Plugin. The Chat
|
||||
Completion Service is passed directly via the ChatCompletionAgent constructor.
|
||||
Additionally, the plugin is supplied via the constructor.
|
||||
"""
|
||||
|
||||
|
||||
# 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) -> 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"
|
||||
|
||||
|
||||
# Simulate a conversation with the agent
|
||||
USER_INPUTS = [
|
||||
"Hello",
|
||||
"What is the special soup?",
|
||||
"What does that cost?",
|
||||
"Thank you",
|
||||
]
|
||||
|
||||
|
||||
async def main():
|
||||
# 1. Create the agent
|
||||
agent = ChatCompletionAgent(
|
||||
service=AzureChatCompletion(credential=AzureCliCredential()),
|
||||
name="Host",
|
||||
instructions="Answer questions about the menu.",
|
||||
plugins=[MenuPlugin()],
|
||||
)
|
||||
|
||||
# 2. Create a thread to hold the conversation
|
||||
# If no thread is provided, a new thread will be
|
||||
# created and returned with the initial response
|
||||
thread: ChatHistoryAgentThread = None
|
||||
|
||||
for user_input in USER_INPUTS:
|
||||
print(f"# User: {user_input}")
|
||||
# 4. Invoke the agent for a response
|
||||
response = await agent.get_response(messages=user_input, thread=thread)
|
||||
print(f"# {response.name}: {response} ")
|
||||
thread = response.thread
|
||||
|
||||
# 4. Cleanup: Clear the thread
|
||||
await thread.delete() if thread else None
|
||||
|
||||
"""
|
||||
Sample output:
|
||||
# User: Hello
|
||||
# Host: Hello! How can I assist you today?
|
||||
# User: What is the special soup?
|
||||
# Host: The special soup is Clam Chowder.
|
||||
# User: What does that cost?
|
||||
# Host: The special soup, Clam Chowder, costs $9.99.
|
||||
# User: Thank you
|
||||
# Host: You're welcome! If you have any more questions, feel free to ask. Enjoy your day!
|
||||
"""
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
from typing import Annotated
|
||||
|
||||
from azure.identity import AzureCliCredential
|
||||
|
||||
from semantic_kernel import Kernel
|
||||
from semantic_kernel.agents import ChatCompletionAgent, ChatHistoryAgentThread
|
||||
from semantic_kernel.connectors.ai import FunctionChoiceBehavior
|
||||
from semantic_kernel.connectors.ai.open_ai import AzureChatCompletion
|
||||
from semantic_kernel.functions import KernelArguments, kernel_function
|
||||
|
||||
"""
|
||||
The following sample demonstrates how to create a chat completion agent that
|
||||
answers questions about a sample menu using a Semantic Kernel Plugin. The Chat
|
||||
Completion Service is first added to the kernel, and the kernel is passed in to the
|
||||
ChatCompletionAgent constructor. Additionally, the plugin is supplied via the kernel.
|
||||
To enable auto-function calling, the prompt execution settings are retrieved from the kernel
|
||||
using the specified `service_id`. The function choice behavior is set to `Auto` to allow the
|
||||
agent to automatically execute the plugin's functions when needed.
|
||||
"""
|
||||
|
||||
|
||||
# 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) -> 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"
|
||||
|
||||
|
||||
# Simulate a conversation with the agent
|
||||
USER_INPUTS = [
|
||||
"Hello",
|
||||
"What is the special soup?",
|
||||
"What does that cost?",
|
||||
"Thank you",
|
||||
]
|
||||
|
||||
|
||||
async def main():
|
||||
# 1. Create the instance of the Kernel to register the plugin and service
|
||||
service_id = "agent"
|
||||
kernel = Kernel()
|
||||
kernel.add_plugin(MenuPlugin(), plugin_name="menu")
|
||||
kernel.add_service(AzureChatCompletion(service_id=service_id, credential=AzureCliCredential()))
|
||||
|
||||
# 2. Configure the function choice behavior to auto invoke kernel functions
|
||||
# so that the agent can automatically execute the menu plugin functions when needed
|
||||
settings = kernel.get_prompt_execution_settings_from_service_id(service_id=service_id)
|
||||
settings.function_choice_behavior = FunctionChoiceBehavior.Auto()
|
||||
|
||||
# 3. Create the agent
|
||||
agent = ChatCompletionAgent(
|
||||
kernel=kernel,
|
||||
name="Host",
|
||||
instructions="Answer questions about the menu.",
|
||||
arguments=KernelArguments(settings=settings),
|
||||
)
|
||||
|
||||
# 4. Create a thread to hold the conversation
|
||||
# If no thread is provided, a new thread will be
|
||||
# created and returned with the initial response
|
||||
thread: ChatHistoryAgentThread = None
|
||||
|
||||
for user_input in USER_INPUTS:
|
||||
print(f"# User: {user_input}")
|
||||
# 5. Invoke the agent for a response
|
||||
async for response in agent.invoke(messages=user_input, thread=thread):
|
||||
print(f"# {response.name}: {response}")
|
||||
thread = response.thread
|
||||
|
||||
# 6. Cleanup: Clear the thread
|
||||
await thread.delete() if thread else None
|
||||
|
||||
"""
|
||||
Sample output:
|
||||
# User: Hello
|
||||
# Host: Hello! How can I assist you today?
|
||||
# User: What is the special soup?
|
||||
# Host: The special soup is Clam Chowder.
|
||||
# User: What does that cost?
|
||||
# Host: The special soup, Clam Chowder, costs $9.99.
|
||||
# User: Thank you
|
||||
# Host: You're welcome! If you have any more questions, feel free to ask. Enjoy your day!
|
||||
"""
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
+110
@@ -0,0 +1,110 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
|
||||
from azure.identity import AzureCliCredential
|
||||
|
||||
from semantic_kernel import Kernel
|
||||
from semantic_kernel.agents import AgentGroupChat, ChatCompletionAgent
|
||||
from semantic_kernel.agents.strategies import TerminationStrategy
|
||||
from semantic_kernel.connectors.ai.open_ai import AzureChatCompletion
|
||||
|
||||
"""
|
||||
The following sample demonstrates how to create a simple, agent group chat that
|
||||
utilizes An Art Director Chat Completion Agent along with a Copy Writer Chat
|
||||
Completion Agent to complete a task.
|
||||
|
||||
Note: This sample use the `AgentGroupChat` feature of Semantic Kernel, which is
|
||||
no longer maintained. For a replacement, consider using the `GroupChatOrchestration`.
|
||||
|
||||
Read more about the `GroupChatOrchestration` here:
|
||||
https://learn.microsoft.com/semantic-kernel/frameworks/agent/agent-orchestration/group-chat?pivots=programming-language-python
|
||||
|
||||
Here is a migration guide from `AgentGroupChat` to `GroupChatOrchestration`:
|
||||
https://learn.microsoft.com/semantic-kernel/support/migration/group-chat-orchestration-migration-guide?pivots=programming-language-python
|
||||
"""
|
||||
|
||||
|
||||
def _create_kernel_with_chat_completion(service_id: str) -> Kernel:
|
||||
kernel = Kernel()
|
||||
kernel.add_service(AzureChatCompletion(service_id=service_id, credential=AzureCliCredential()))
|
||||
return kernel
|
||||
|
||||
|
||||
class ApprovalTerminationStrategy(TerminationStrategy):
|
||||
"""A strategy for determining when an agent should terminate."""
|
||||
|
||||
async def should_agent_terminate(self, agent, history):
|
||||
"""Check if the agent should terminate."""
|
||||
last_message = history[-1].content.lower()
|
||||
return "approved" in last_message and "not approved" not in last_message
|
||||
|
||||
|
||||
REVIEWER_NAME = "ArtDirector"
|
||||
REVIEWER_INSTRUCTIONS = """
|
||||
You are an art director who has opinions about copywriting born of a love for David Ogilvy.
|
||||
The goal is to determine if the given copy is acceptable to print.
|
||||
If so, state that it is approved.
|
||||
If not, provide insight on how to refine suggested copy without example.
|
||||
"""
|
||||
|
||||
COPYWRITER_NAME = "CopyWriter"
|
||||
COPYWRITER_INSTRUCTIONS = """
|
||||
You are a copywriter with ten years of experience and are known for brevity and a dry humor.
|
||||
The goal is to refine and decide on the single best copy as an expert in the field.
|
||||
Only provide a single proposal per response.
|
||||
You're laser focused on the goal at hand.
|
||||
Don't waste time with chit chat.
|
||||
Consider suggestions when refining an idea.
|
||||
"""
|
||||
|
||||
TASK = "a slogan for a new line of electric cars."
|
||||
|
||||
|
||||
async def main():
|
||||
# 1. Create the reviewer agent based on the chat completion service
|
||||
agent_reviewer = ChatCompletionAgent(
|
||||
kernel=_create_kernel_with_chat_completion("artdirector"),
|
||||
name=REVIEWER_NAME,
|
||||
instructions=REVIEWER_INSTRUCTIONS,
|
||||
)
|
||||
|
||||
# 2. Create the copywriter agent based on the chat completion service
|
||||
agent_writer = ChatCompletionAgent(
|
||||
kernel=_create_kernel_with_chat_completion("copywriter"),
|
||||
name=COPYWRITER_NAME,
|
||||
instructions=COPYWRITER_INSTRUCTIONS,
|
||||
)
|
||||
|
||||
# 3. Place the agents in a group chat with a custom termination strategy
|
||||
group_chat = AgentGroupChat(
|
||||
agents=[
|
||||
agent_writer,
|
||||
agent_reviewer,
|
||||
],
|
||||
termination_strategy=ApprovalTerminationStrategy(
|
||||
agents=[agent_reviewer],
|
||||
maximum_iterations=10,
|
||||
),
|
||||
)
|
||||
|
||||
# 4. Add the task as a message to the group chat
|
||||
await group_chat.add_chat_message(message=TASK)
|
||||
print(f"# User: {TASK}")
|
||||
|
||||
# 5. Invoke the chat
|
||||
async for content in group_chat.invoke():
|
||||
print(f"# {content.name}: {content.content}")
|
||||
|
||||
"""
|
||||
Sample output:
|
||||
# User: a slogan for a new line of electric cars.
|
||||
# CopyWriter: "Drive the Future: Shockingly Efficient."
|
||||
# ArtDirector: This slogan has potential but could benefit from refinement to create a stronger ...
|
||||
# CopyWriter: "Electrify Your Drive."
|
||||
# ArtDirector: Approved. This slogan is concise, memorable, and effectively communicates the ...
|
||||
"""
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
+145
@@ -0,0 +1,145 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
|
||||
from azure.identity import AzureCliCredential
|
||||
|
||||
from semantic_kernel import Kernel
|
||||
from semantic_kernel.agents import AgentGroupChat, ChatCompletionAgent
|
||||
from semantic_kernel.agents.strategies import KernelFunctionSelectionStrategy, KernelFunctionTerminationStrategy
|
||||
from semantic_kernel.connectors.ai.open_ai import AzureChatCompletion
|
||||
from semantic_kernel.functions import KernelFunctionFromPrompt
|
||||
|
||||
"""
|
||||
The following sample demonstrates how to create a simple, agent group chat that utilizes
|
||||
An Art Director Chat Completion Agent along with a Copy Writer Chat Completion Agent to
|
||||
complete a task. The sample also shows how to specify a Kernel Function termination and
|
||||
selection strategy to determine when to end the chat or how to select the next agent to
|
||||
take a turn in the conversation.
|
||||
|
||||
Note: This sample use the `AgentGroupChat` feature of Semantic Kernel, which is
|
||||
no longer maintained. For a replacement, consider using the `GroupChatOrchestration`.
|
||||
|
||||
Read more about the `GroupChatOrchestration` here:
|
||||
https://learn.microsoft.com/semantic-kernel/frameworks/agent/agent-orchestration/group-chat?pivots=programming-language-python
|
||||
|
||||
Here is a migration guide from `AgentGroupChat` to `GroupChatOrchestration`:
|
||||
https://learn.microsoft.com/semantic-kernel/support/migration/group-chat-orchestration-migration-guide?pivots=programming-language-python
|
||||
"""
|
||||
|
||||
|
||||
def _create_kernel_with_chat_completion(service_id: str) -> Kernel:
|
||||
kernel = Kernel()
|
||||
kernel.add_service(AzureChatCompletion(service_id=service_id, credential=AzureCliCredential()))
|
||||
return kernel
|
||||
|
||||
|
||||
REVIEWER_NAME = "ArtDirector"
|
||||
REVIEWER_INSTRUCTIONS = """
|
||||
You are an art director who has opinions about copywriting born of a love for David Ogilvy.
|
||||
The goal is to determine if the given copy is acceptable to print.
|
||||
If so, state that it is approved.
|
||||
If not, provide insight on how to refine suggested copy without example.
|
||||
"""
|
||||
|
||||
COPYWRITER_NAME = "CopyWriter"
|
||||
COPYWRITER_INSTRUCTIONS = """
|
||||
You are a copywriter with ten years of experience and are known for brevity and a dry humor.
|
||||
The goal is to refine and decide on the single best copy as an expert in the field.
|
||||
Only provide a single proposal per response.
|
||||
You're laser focused on the goal at hand.
|
||||
Don't waste time with chit chat.
|
||||
Consider suggestions when refining an idea.
|
||||
"""
|
||||
|
||||
TASK = "a slogan for a new line of electric cars."
|
||||
|
||||
|
||||
async def main():
|
||||
# 1. Create the reviewer agent based on the chat completion service
|
||||
agent_reviewer = ChatCompletionAgent(
|
||||
kernel=_create_kernel_with_chat_completion("artdirector"),
|
||||
name=REVIEWER_NAME,
|
||||
instructions=REVIEWER_INSTRUCTIONS,
|
||||
)
|
||||
|
||||
# 2. Create the copywriter agent based on the chat completion service
|
||||
agent_writer = ChatCompletionAgent(
|
||||
kernel=_create_kernel_with_chat_completion("copywriter"),
|
||||
name=COPYWRITER_NAME,
|
||||
instructions=COPYWRITER_INSTRUCTIONS,
|
||||
)
|
||||
|
||||
# 3. Create a Kernel Function to determine if the copy has been approved
|
||||
termination_function = KernelFunctionFromPrompt(
|
||||
function_name="termination",
|
||||
prompt="""
|
||||
Determine if the copy has been approved. If so, respond with a single word: yes
|
||||
|
||||
History:
|
||||
{{$history}}
|
||||
""",
|
||||
)
|
||||
|
||||
# 4. Create a Kernel Function to determine which agent should take the next turn
|
||||
selection_function = KernelFunctionFromPrompt(
|
||||
function_name="selection",
|
||||
prompt=f"""
|
||||
Determine which participant takes the next turn in a conversation based on the the most recent participant.
|
||||
State only the name of the participant to take the next turn.
|
||||
No participant should take more than one turn in a row.
|
||||
|
||||
Choose only from these participants:
|
||||
- {REVIEWER_NAME}
|
||||
- {COPYWRITER_NAME}
|
||||
|
||||
Always follow these rules when selecting the next participant:
|
||||
- After user input, it is {COPYWRITER_NAME}'s turn.
|
||||
- After {COPYWRITER_NAME} replies, it is {REVIEWER_NAME}'s turn.
|
||||
- After {REVIEWER_NAME} provides feedback, it is {COPYWRITER_NAME}'s turn.
|
||||
|
||||
History:
|
||||
{{{{$history}}}}
|
||||
""",
|
||||
)
|
||||
|
||||
# 5. Place the agents in a group chat with the custom termination and selection strategies
|
||||
chat = AgentGroupChat(
|
||||
agents=[agent_writer, agent_reviewer],
|
||||
termination_strategy=KernelFunctionTerminationStrategy(
|
||||
agents=[agent_reviewer],
|
||||
function=termination_function,
|
||||
kernel=_create_kernel_with_chat_completion("termination"),
|
||||
result_parser=lambda result: str(result.value[0]).lower() == "yes",
|
||||
history_variable_name="history",
|
||||
maximum_iterations=10,
|
||||
),
|
||||
selection_strategy=KernelFunctionSelectionStrategy(
|
||||
function=selection_function,
|
||||
kernel=_create_kernel_with_chat_completion("selection"),
|
||||
result_parser=lambda result: str(result.value[0]) if result.value is not None else COPYWRITER_NAME,
|
||||
agent_variable_name="agents",
|
||||
history_variable_name="history",
|
||||
),
|
||||
)
|
||||
|
||||
# 6. Add the task as a message to the group chat
|
||||
await chat.add_chat_message(message=TASK)
|
||||
print(f"# User: {TASK}")
|
||||
|
||||
# 7. Invoke the chat
|
||||
async for content in chat.invoke():
|
||||
print(f"# {content.name}: {content.content}")
|
||||
|
||||
"""
|
||||
Sample Output:
|
||||
# User: a slogan for a new line of electric cars.
|
||||
# CopyWriter: "Electrify your drive. Spare the gas, not the thrill."
|
||||
# ArtDirector: This slogan captures the essence of electric cars but could use refinement to ...
|
||||
# CopyWriter: "Go electric. Enjoy the thrill. Skip the gas."
|
||||
# ArtDirector: Approved. This slogan is clear, concise, and effectively communicates the ...
|
||||
"""
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
+111
@@ -0,0 +1,111 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
|
||||
from pydantic import BaseModel, ValidationError
|
||||
|
||||
from semantic_kernel import Kernel
|
||||
from semantic_kernel.agents import AgentGroupChat, ChatCompletionAgent
|
||||
from semantic_kernel.agents.strategies import TerminationStrategy
|
||||
from semantic_kernel.connectors.ai.open_ai import OpenAIChatCompletion, OpenAIChatPromptExecutionSettings
|
||||
from semantic_kernel.functions import KernelArguments
|
||||
|
||||
"""
|
||||
The following sample demonstrates how to configure an Agent Group Chat, and invoke an
|
||||
agent with only a single turn.A custom termination strategy is provided where the model
|
||||
is to rate the user input on creativity and expressiveness and end the chat when a score
|
||||
of 70 or higher is provided.
|
||||
|
||||
Note: This sample use the `AgentGroupChat` feature of Semantic Kernel, which is
|
||||
no longer maintained. For a replacement, consider using the `GroupChatOrchestration`.
|
||||
|
||||
Read more about the `GroupChatOrchestration` here:
|
||||
https://learn.microsoft.com/semantic-kernel/frameworks/agent/agent-orchestration/group-chat?pivots=programming-language-python
|
||||
|
||||
Here is a migration guide from `AgentGroupChat` to `GroupChatOrchestration`:
|
||||
https://learn.microsoft.com/semantic-kernel/support/migration/group-chat-orchestration-migration-guide?pivots=programming-language-python
|
||||
"""
|
||||
|
||||
|
||||
def _create_kernel_with_chat_completion(service_id: str) -> Kernel:
|
||||
kernel = Kernel()
|
||||
kernel.add_service(OpenAIChatCompletion(service_id=service_id))
|
||||
return kernel
|
||||
|
||||
|
||||
class InputScore(BaseModel):
|
||||
"""A model for the input score."""
|
||||
|
||||
score: int
|
||||
notes: str
|
||||
|
||||
|
||||
class ThresholdTerminationStrategy(TerminationStrategy):
|
||||
"""A strategy for determining when an agent should terminate."""
|
||||
|
||||
threshold: int = 70
|
||||
|
||||
async def should_agent_terminate(self, agent, history):
|
||||
"""Check if the agent should terminate."""
|
||||
try:
|
||||
result = InputScore.model_validate_json(history[-1].content or "")
|
||||
return result.score >= self.threshold
|
||||
except ValidationError:
|
||||
return False
|
||||
|
||||
|
||||
INSTRUCTION = """
|
||||
Think step-by-step and rate the user input on creativity and expressiveness from 1-100 with some notes on improvements.
|
||||
"""
|
||||
|
||||
# Simulate a conversation with the agent
|
||||
USER_INPUTS = {
|
||||
"The sunset is very colorful.",
|
||||
"The sunset is setting over the mountains.",
|
||||
"The sunset is setting over the mountains and fills the sky with a deep red flame, setting the clouds ablaze.",
|
||||
}
|
||||
|
||||
|
||||
async def main():
|
||||
# 1. Create the instance of the Kernel to register a service
|
||||
service_id = "agent"
|
||||
kernel = _create_kernel_with_chat_completion(service_id)
|
||||
|
||||
# 2. Configure the prompt execution settings to return the score in the desired format
|
||||
settings = kernel.get_prompt_execution_settings_from_service_id(service_id)
|
||||
assert isinstance(settings, OpenAIChatPromptExecutionSettings) # nosec
|
||||
settings.response_format = InputScore
|
||||
|
||||
# 3. Create the agent
|
||||
agent = ChatCompletionAgent(
|
||||
kernel=kernel,
|
||||
name="Tutor",
|
||||
instructions=INSTRUCTION,
|
||||
arguments=KernelArguments(settings),
|
||||
)
|
||||
|
||||
# 4. Create the group chat with the custom termination strategy
|
||||
group_chat = AgentGroupChat(termination_strategy=ThresholdTerminationStrategy(maximum_iterations=10))
|
||||
|
||||
for user_input in USER_INPUTS:
|
||||
# 5. Add the user input to the chat history
|
||||
await group_chat.add_chat_message(message=user_input)
|
||||
print(f"# User: {user_input}")
|
||||
|
||||
# 6. Invoke the chat with the agent for a response
|
||||
async for content in group_chat.invoke_single_turn(agent):
|
||||
print(f"# {content.name}: {content.content}")
|
||||
|
||||
"""
|
||||
Sample output:
|
||||
# User: The sunset is very colorful.
|
||||
# Tutor: {"score":45,"notes":"The sentence 'The sunset is very colorful' is simple and direct. While it ..."}
|
||||
# User: The sunset is setting over the mountains.
|
||||
# Tutor: {"score":50,"notes":"This sentence provides a basic scene of a sunset over mountains, which ..."}
|
||||
# User: The sunset is setting over the mountains and fills the sky with a deep red flame, setting the clouds ablaze.
|
||||
# Tutor: {"score":75,"notes":"This sentence demonstrates improved creativity and expressiveness by ..."}
|
||||
"""
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
+123
@@ -0,0 +1,123 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
from azure.identity import AzureCliCredential
|
||||
|
||||
from semantic_kernel import Kernel
|
||||
from semantic_kernel.agents import AgentGroupChat, ChatCompletionAgent
|
||||
from semantic_kernel.agents.strategies import TerminationStrategy
|
||||
from semantic_kernel.connectors.ai.open_ai import AzureChatCompletion
|
||||
|
||||
"""
|
||||
The following sample demonstrates how to create a simple, agent group chat that
|
||||
utilizes An Art Director Chat Completion Agent along with a Copy Writer Chat
|
||||
Completion Agent to complete a task. The main point of this sample is to note
|
||||
how to enable logging to view all interactions between the agents and the model.
|
||||
|
||||
Note: This sample use the `AgentGroupChat` feature of Semantic Kernel, which is
|
||||
no longer maintained. For a replacement, consider using the `GroupChatOrchestration`.
|
||||
|
||||
Read more about the `GroupChatOrchestration` here:
|
||||
https://learn.microsoft.com/semantic-kernel/frameworks/agent/agent-orchestration/group-chat?pivots=programming-language-python
|
||||
|
||||
Here is a migration guide from `AgentGroupChat` to `GroupChatOrchestration`:
|
||||
https://learn.microsoft.com/semantic-kernel/support/migration/group-chat-orchestration-migration-guide?pivots=programming-language-python
|
||||
"""
|
||||
|
||||
# 0. Enable logging
|
||||
# NOTE: This is all that is required to enable logging.
|
||||
# Set the desired level to INFO, DEBUG, etc.
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
|
||||
class ApprovalTerminationStrategy(TerminationStrategy):
|
||||
"""A strategy for determining when an agent should terminate."""
|
||||
|
||||
async def should_agent_terminate(self, agent, history):
|
||||
"""Check if the agent should terminate."""
|
||||
return "approved" in history[-1].content.lower()
|
||||
|
||||
|
||||
def _create_kernel_with_chat_completion(service_id: str) -> Kernel:
|
||||
kernel = Kernel()
|
||||
kernel.add_service(AzureChatCompletion(service_id=service_id, credential=AzureCliCredential()))
|
||||
return kernel
|
||||
|
||||
|
||||
REVIEWER_NAME = "ArtDirector"
|
||||
REVIEWER_INSTRUCTIONS = """
|
||||
You are an art director who has opinions about copywriting born of a love for David Ogilvy.
|
||||
The goal is to determine if the given copy is acceptable to print.
|
||||
If so, state that it is approved.
|
||||
If not, provide insight on how to refine suggested copy without example.
|
||||
"""
|
||||
|
||||
COPYWRITER_NAME = "CopyWriter"
|
||||
COPYWRITER_INSTRUCTIONS = """
|
||||
You are a copywriter with ten years of experience and are known for brevity and a dry humor.
|
||||
The goal is to refine and decide on the single best copy as an expert in the field.
|
||||
Only provide a single proposal per response.
|
||||
You're laser focused on the goal at hand.
|
||||
Don't waste time with chit chat.
|
||||
Consider suggestions when refining an idea.
|
||||
"""
|
||||
|
||||
TASK = "a slogan for a new line of electric cars."
|
||||
|
||||
|
||||
async def main():
|
||||
# 1. Create the reviewer agent based on the chat completion service
|
||||
agent_reviewer = ChatCompletionAgent(
|
||||
kernel=_create_kernel_with_chat_completion("artdirector"),
|
||||
name=REVIEWER_NAME,
|
||||
instructions=REVIEWER_INSTRUCTIONS,
|
||||
)
|
||||
|
||||
# 2. Create the copywriter agent based on the chat completion service
|
||||
agent_writer = ChatCompletionAgent(
|
||||
kernel=_create_kernel_with_chat_completion("copywriter"),
|
||||
name=COPYWRITER_NAME,
|
||||
instructions=COPYWRITER_INSTRUCTIONS,
|
||||
)
|
||||
|
||||
# 3. Place the agents in a group chat with a custom termination strategy
|
||||
group_chat = AgentGroupChat(
|
||||
agents=[agent_writer, agent_reviewer],
|
||||
termination_strategy=ApprovalTerminationStrategy(agents=[agent_reviewer], maximum_iterations=10),
|
||||
)
|
||||
|
||||
# 4. Add the task as a message to the group chat
|
||||
await group_chat.add_chat_message(message=TASK)
|
||||
print(f"# User: {TASK}")
|
||||
|
||||
# 5. Invoke the chat
|
||||
async for content in group_chat.invoke():
|
||||
print(f"# {content.name}: {content.content}")
|
||||
|
||||
"""
|
||||
Sample output:
|
||||
INFO:semantic_kernel.agents.group_chat.agent_chat:Adding `1` agent chat messages
|
||||
# User: a slogan for a new line of electric cars.
|
||||
INFO:semantic_kernel.agents.strategies.selection.sequential_selection_strategy:Selected agent at index 0 (ID: ...
|
||||
INFO:semantic_kernel.agents.group_chat.agent_chat:Invoking agent CopyWriter
|
||||
INFO:semantic_kernel.utils.telemetry.model_diagnostics.decorators:{"role": "system", "content": "\nYou are a ...
|
||||
INFO:semantic_kernel.utils.telemetry.model_diagnostics.decorators:{"role": "user", "content": "a slogan for ...
|
||||
INFO:semantic_kernel.connectors.ai.open_ai.services.open_ai_handler:OpenAI usage: CompletionUsage(completion_...
|
||||
INFO:semantic_kernel.utils.telemetry.model_diagnostics.decorators:{"message": {"role": "assistant", "content": ...
|
||||
INFO:semantic_kernel.agents.chat_completion.chat_completion_agent:[ChatCompletionAgent] Invoked AzureChatCompl...
|
||||
INFO:semantic_kernel.agents.strategies.termination.termination_strategy:Evaluating termination criteria for ...
|
||||
INFO:semantic_kernel.agents.strategies.termination.termination_strategy:Agent 598d827e-ce5e-44fa-879b-42793bb...
|
||||
# CopyWriter: "Drive Change. Literally."
|
||||
INFO:semantic_kernel.agents.strategies.selection.sequential_selection_strategy:Selected agent at index 1 (ID: ...
|
||||
INFO:semantic_kernel.agents.group_chat.agent_chat:Invoking agent ArtDirector
|
||||
INFO:semantic_kernel.utils.telemetry.model_diagnostics.decorators:{"role": "system", "content": "\nYou are an ...
|
||||
INFO:semantic_kernel.utils.telemetry.model_diagnostics.decorators:{"role": "user", "content": "a slogan for a ...
|
||||
INFO:semantic_kernel.utils.telemetry.model_diagnostics.decorators:{"role": "assistant", "content": "\"Drive ...
|
||||
...
|
||||
"""
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
+110
@@ -0,0 +1,110 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
|
||||
from azure.identity import AzureCliCredential
|
||||
from pydantic import BaseModel
|
||||
|
||||
from semantic_kernel.agents import ChatCompletionAgent, ChatHistoryAgentThread
|
||||
from semantic_kernel.connectors.ai.open_ai import AzureChatCompletion, AzureChatPromptExecutionSettings
|
||||
from semantic_kernel.functions.kernel_arguments import KernelArguments
|
||||
|
||||
"""
|
||||
The following sample demonstrates how to create a chat completion agent that
|
||||
answers user questions using structured outputs. The `Reasoning` model is defined
|
||||
on the prompt execution settings. The settings are then passed into the agent
|
||||
via the `KernelArguments` object.
|
||||
|
||||
The interaction with the agent is via the `get_response` method, which sends a
|
||||
user input to the agent and receives a response from the agent. The conversation
|
||||
history needs to be maintained by the caller in the chat history object.
|
||||
"""
|
||||
|
||||
|
||||
# Define the BaseModel we will use for structured outputs
|
||||
class Step(BaseModel):
|
||||
explanation: str
|
||||
output: str
|
||||
|
||||
|
||||
class Reasoning(BaseModel):
|
||||
steps: list[Step]
|
||||
final_answer: str
|
||||
|
||||
|
||||
# Simulate a conversation with the agent
|
||||
USER_INPUT = "how can I solve 8x + 7y = -23, and 4x=12?"
|
||||
|
||||
|
||||
async def main():
|
||||
# 1. Create the prompt settings
|
||||
settings = AzureChatPromptExecutionSettings()
|
||||
settings.response_format = Reasoning
|
||||
|
||||
# 2. Create the agent by specifying the service
|
||||
agent = ChatCompletionAgent(
|
||||
service=AzureChatCompletion(credential=AzureCliCredential()),
|
||||
name="Assistant",
|
||||
instructions="Answer the user's questions.",
|
||||
arguments=KernelArguments(settings=settings),
|
||||
)
|
||||
|
||||
# 3. Create a thread to hold the conversation
|
||||
# If no thread is provided, a new thread will be
|
||||
# created and returned with the initial response
|
||||
thread: ChatHistoryAgentThread = None
|
||||
|
||||
print(f"# User: {USER_INPUT}")
|
||||
# 4. Invoke the agent for a response
|
||||
response = await agent.get_response(messages=USER_INPUT, thread=thread)
|
||||
# 5. Validate the response and print the structured output
|
||||
reasoned_result = Reasoning.model_validate(json.loads(response.message.content))
|
||||
print(f"# {response.name}:\n\n{reasoned_result.model_dump_json(indent=4)}")
|
||||
|
||||
# 6. Cleanup: Clear the thread
|
||||
await thread.delete() if thread else None
|
||||
|
||||
"""
|
||||
Sample output:
|
||||
# User: how can I solve 8x + 7y = -23, and 4x=12?
|
||||
# Assistant:
|
||||
|
||||
{
|
||||
"steps": [
|
||||
{
|
||||
"explanation": "The second equation 4x = 12 can be solved for x by dividing both sides by 4.",
|
||||
"output": "x = 3."
|
||||
},
|
||||
{
|
||||
"explanation": "Substitute x = 3 from the second equation into the first equation 8x + 7y = -23.",
|
||||
"output": "8(3) + 7y = -23."
|
||||
},
|
||||
{
|
||||
"explanation": "Calculate 8 times 3 to simplify the equation.",
|
||||
"output": "24 + 7y = -23."
|
||||
},
|
||||
{
|
||||
"explanation": "Subtract 24 from both sides to isolate the term with y.",
|
||||
"output": "7y = -23 - 24."
|
||||
},
|
||||
{
|
||||
"explanation": "Perform the subtraction.",
|
||||
"output": "7y = -47."
|
||||
},
|
||||
{
|
||||
"explanation": "Divide both sides by 7 to solve for y.",
|
||||
"output": "y = -47 / 7."
|
||||
},
|
||||
{
|
||||
"explanation": "Simplify the division to get the value of y.",
|
||||
"output": "y = -6.714285714285714 (approximately -6.71)."
|
||||
}
|
||||
],
|
||||
"final_answer": "The solution to the system of equations is x = 3 and y = -6.71."
|
||||
}
|
||||
"""
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
+126
@@ -0,0 +1,126 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
from typing import Annotated
|
||||
|
||||
from azure.identity import AzureCliCredential
|
||||
from pydantic import BaseModel
|
||||
|
||||
from semantic_kernel import Kernel
|
||||
from semantic_kernel.agents import AgentRegistry, ChatHistoryAgentThread
|
||||
from semantic_kernel.agents.chat_completion.chat_completion_agent import ChatCompletionAgent
|
||||
from semantic_kernel.connectors.ai.open_ai import (
|
||||
AzureChatCompletion,
|
||||
AzureChatPromptExecutionSettings,
|
||||
)
|
||||
from semantic_kernel.functions import KernelArguments, kernel_function
|
||||
|
||||
"""
|
||||
The following sample demonstrates how to create a chat completion agent using a
|
||||
declarative approach. The Chat Completion Agent is created from a YAML spec,
|
||||
with a specific service and plugins. The agent is then used to answer user questions.
|
||||
|
||||
This sample also demonstrates how to properly pass execution settings (like response format)
|
||||
when using AgentRegistry.create_from_yaml().
|
||||
"""
|
||||
|
||||
|
||||
# Example structure for structured output
|
||||
class StructuredResult(BaseModel):
|
||||
"""Example structure for demonstrating response format."""
|
||||
|
||||
response: str
|
||||
category: str
|
||||
|
||||
|
||||
# 1. Define a Sample Plugin
|
||||
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) -> 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"
|
||||
|
||||
|
||||
# 2. Define the YAML string
|
||||
AGENT_YAML = """
|
||||
type: chat_completion_agent
|
||||
name: Assistant
|
||||
description: A helpful assistant.
|
||||
instructions: Answer the user's questions using the menu functions.
|
||||
tools:
|
||||
- id: MenuPlugin.get_specials
|
||||
type: function
|
||||
- id: MenuPlugin.get_item_price
|
||||
type: function
|
||||
model:
|
||||
options:
|
||||
temperature: 0.7
|
||||
"""
|
||||
|
||||
# 3. Define your simulated conversation
|
||||
USER_INPUTS = [
|
||||
"Hello",
|
||||
"What is the special soup?",
|
||||
"What does that cost?",
|
||||
"Thank you",
|
||||
]
|
||||
|
||||
|
||||
async def main():
|
||||
# 4. Create a Kernel and add the plugin
|
||||
# For declarative agents, the kernel is required to resolve the plugin
|
||||
kernel = Kernel()
|
||||
kernel.add_plugin(MenuPlugin(), plugin_name="MenuPlugin")
|
||||
|
||||
# 5. Create execution settings with structured output
|
||||
execution_settings = AzureChatPromptExecutionSettings()
|
||||
execution_settings.response_format = StructuredResult
|
||||
|
||||
# 6. Create KernelArguments with the execution settings
|
||||
arguments = KernelArguments(settings=execution_settings)
|
||||
|
||||
# 7. Create the agent from YAML + inject the AI service
|
||||
agent: ChatCompletionAgent = await AgentRegistry.create_from_yaml(
|
||||
AGENT_YAML, kernel=kernel, service=AzureChatCompletion(credential=AzureCliCredential()), arguments=arguments
|
||||
)
|
||||
|
||||
# 8. Create a thread to hold the conversation
|
||||
thread: ChatHistoryAgentThread | None = None
|
||||
|
||||
for user_input in USER_INPUTS:
|
||||
print(f"# User: {user_input}")
|
||||
# 9. Invoke the agent for a response
|
||||
response = await agent.get_response(messages=user_input, thread=thread)
|
||||
print(f"# {response.name}: {response}")
|
||||
thread = response.thread
|
||||
|
||||
# 10. Cleanup the thread
|
||||
await thread.delete() if thread else None
|
||||
|
||||
"""
|
||||
# Sample output:
|
||||
|
||||
# User: Hello
|
||||
# Assistant: {"response":"Hello! How can I help you today? If you have any questions about the menu, feel free to ask!","category":"Greeting"}
|
||||
# User: What is the special soup?
|
||||
# Assistant: {"response":"Today's special soup is Clam Chowder. Would you like to know more about it or see other specials?","category":"Menu Specials"}
|
||||
# User: What does that cost?
|
||||
# Assistant: {"response":"The Clam Chowder special soup costs $9.99.","category":"Menu Pricing"}
|
||||
# User: Thank you
|
||||
# Assistant: {"response":"You're welcome! If you have any more questions or need assistance with the menu, just let me know. Enjoy your meal!","category":"Polite Closing"}
|
||||
""" # noqa: E501
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
|
||||
from azure.identity import AzureCliCredential
|
||||
|
||||
from semantic_kernel.agents import ChatCompletionAgent
|
||||
from semantic_kernel.connectors.ai.open_ai import AzureChatCompletion
|
||||
from semantic_kernel.contents import ChatMessageContent, FunctionCallContent, FunctionResultContent
|
||||
from semantic_kernel.core_plugins import SessionsPythonTool
|
||||
|
||||
"""
|
||||
The following sample demonstrates how to create a chat completion agent with
|
||||
code interpreter capabilities using the Azure Container Apps session pool service.
|
||||
"""
|
||||
|
||||
|
||||
async def handle_intermediate_steps(message: ChatMessageContent) -> None:
|
||||
for item in message.items or []:
|
||||
if isinstance(item, FunctionResultContent):
|
||||
print(f"# Function Result:> {item.result}")
|
||||
elif isinstance(item, FunctionCallContent):
|
||||
print(f"# Function Call:> {item.name} with arguments: {item.arguments}")
|
||||
else:
|
||||
print(f"# {message.name}: {message} ")
|
||||
|
||||
|
||||
async def main():
|
||||
credential = AzureCliCredential()
|
||||
|
||||
# Define the resources directory for file uploads
|
||||
resources_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), "resources")
|
||||
|
||||
# 1. Create the python code interpreter tool using the SessionsPythonTool
|
||||
# allowed_upload_directories restricts which local directories can be accessed for uploads
|
||||
python_code_interpreter = SessionsPythonTool(
|
||||
credential=credential,
|
||||
allowed_upload_directories=[resources_dir],
|
||||
)
|
||||
|
||||
# 2. Create the agent
|
||||
agent = ChatCompletionAgent(
|
||||
service=AzureChatCompletion(credential=credential),
|
||||
name="Host",
|
||||
instructions="Answer questions about the menu.",
|
||||
plugins=[python_code_interpreter],
|
||||
)
|
||||
|
||||
# 3. Upload a CSV file to the session
|
||||
csv_file_path = os.path.join(resources_dir, "sales.csv")
|
||||
file_metadata = await python_code_interpreter.upload_file(local_file_path=csv_file_path)
|
||||
|
||||
# 4. Invoke the agent for a response to a task
|
||||
TASK = (
|
||||
"What's the total sum of all sales for all segments using Python? "
|
||||
f"Use the uploaded file {file_metadata.full_path} for reference."
|
||||
)
|
||||
print(f"# User: '{TASK}'")
|
||||
async for response in agent.invoke(
|
||||
messages=TASK,
|
||||
on_intermediate_message=handle_intermediate_steps,
|
||||
):
|
||||
print(f"# {response.name}: {response} ")
|
||||
|
||||
"""
|
||||
Sample output:
|
||||
# User: 'What's the total sum of all sales for all segments using Python?
|
||||
Use the uploaded file /mnt/data/sales.csv for reference.'
|
||||
# Function Call:> SessionsPythonTool-execute_code with arguments: {
|
||||
"code": "
|
||||
import pandas as pd
|
||||
|
||||
# Load the sales data
|
||||
file_path = '/mnt/data/sales.csv'
|
||||
sales_data = pd.read_csv(file_path)
|
||||
|
||||
# Calculate the total sum of sales
|
||||
# Assuming there's a column named 'Sales' which contains the sales amounts
|
||||
total_sales = sales_data['Sales'].sum()
|
||||
total_sales"
|
||||
}
|
||||
# Function Result:> Status:
|
||||
Success
|
||||
Result:
|
||||
118726350.28999999
|
||||
Stdout:
|
||||
|
||||
Stderr:
|
||||
# Host: The total sum of all sales for all segments is approximately $118,726,350.29.
|
||||
"""
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,119 @@
|
||||
# Semantic Kernel - CopilotStudioAgent Quickstart
|
||||
|
||||
This README provides an overview on how to use the [CopilotStudioAgent](../../../semantic_kernel/agents/copilot_studio/copilot_studio_agent.py) within Semantic Kernel.
|
||||
This agent allows you to interact with Microsoft Copilot Studio agents through programmatic APIs.
|
||||
|
||||
> ℹ️ **Note:** Knowledge sources must be configured **within** Microsoft Copilot Studio first. Streaming responses are **not currently supported**.
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Prerequisites
|
||||
|
||||
1. Python 3.10+
|
||||
2. Install Semantic Kernel with Copilot Studio dependencies:
|
||||
```bash
|
||||
pip install semantic-kernel
|
||||
pip install microsoft-agents-hosting-core microsoft-agents-copilotstudio-client
|
||||
```
|
||||
3. An agent created in **Microsoft Copilot Studio**
|
||||
4. Ability to create an application identity in Azure for a **Public Client/Native App Registration**,
|
||||
or access to an existing app registration with the `CopilotStudio.Copilots.Invoke` API permission assigned.
|
||||
|
||||
## Create a Copilot Agent in Copilot Studio
|
||||
|
||||
1. Go to [Microsoft Copilot Studio](https://copilotstudio.microsoft.com).
|
||||
2. Create a new **Agent**.
|
||||
3. Publish your newly created Agent.
|
||||
4. In Copilot Studio, navigate to:
|
||||
`Settings` → `Advanced` → `Metadata`
|
||||
|
||||
Save the following values:
|
||||
- `Schema Name` (maps to `agent_identifier`)
|
||||
- `Environment ID`
|
||||
|
||||
## Create an Application Registration in Entra ID – User Interactive Login
|
||||
|
||||
> This step requires permissions to create application identities in your Azure tenant.
|
||||
|
||||
You will create a **Native Client Application Identity** (no client secret required).
|
||||
|
||||
1. Open [Azure Portal](https://portal.azure.com)
|
||||
2. Navigate to **Entra ID**
|
||||
3. Go to **App registrations** → **New registration**
|
||||
4. Fill out:
|
||||
- **Name**: Any name you like
|
||||
- **Supported account types**: `Accounts in this organization directory only`
|
||||
- **Redirect URI**:
|
||||
- Platform: `Public client/native (mobile & desktop)`
|
||||
- URI: `http://localhost`
|
||||
5. Click **Register**
|
||||
6. From the **Overview** page, note:
|
||||
- `Application (client) ID`
|
||||
- `Directory (tenant) ID`
|
||||
7. Go to: `Manage` → `API permissions`
|
||||
- Click **Add permission**
|
||||
- Choose **APIs my organization uses**
|
||||
- Search for: **Power Platform API**
|
||||
|
||||
If it's not listed, see **Tip** below.
|
||||
|
||||
8. Choose:
|
||||
- **Delegated Permissions**
|
||||
- Expand `CopilotStudio`
|
||||
- Select `CopilotStudio.Copilots.Invoke`
|
||||
9. Click **Add permissions**
|
||||
10. (Optional) Click **Grant admin consent**
|
||||
|
||||
### Tip
|
||||
|
||||
If you **do not see Power Platform API**, follow [Step 2 in Power Platform API Authentication](https://learn.microsoft.com/en-us/power-platform/admin/programmability-authentication-v2) to add the API to your tenant.
|
||||
|
||||
---
|
||||
|
||||
### Configure the Example Application - User Interactive Login
|
||||
|
||||
Once you've collected all required values:
|
||||
|
||||
1. Set the following environment variables in your terminal or .env file:
|
||||
|
||||
```env
|
||||
COPILOT_STUDIO_AGENT_APP_CLIENT_ID=<your-app-client-id>
|
||||
COPILOT_STUDIO_AGENT_TENANT_ID=<your-tenant-id>
|
||||
COPILOT_STUDIO_AGENT_ENVIRONMENT_ID=<your-env-id>
|
||||
COPILOT_STUDIO_AGENT_AGENT_IDENTIFIER=<your-agent-id>
|
||||
COPILOT_STUDIO_AGENT_AUTH_MODE=interactive
|
||||
```
|
||||
|
||||
## Create an Application Registration in Entra ID – Service Principal Login
|
||||
|
||||
> **Warning**: Service Principal login is **not yet supported** in the current version of the `CopilotStudioClient`.
|
||||
|
||||
## Creating a `CopilotStudioAgent` Client
|
||||
|
||||
If all required environment variables are set correctly, you don't need to manually create or pass a `client`. Semantic Kernel will automatically construct the client using the environment configuration.
|
||||
|
||||
However, if you need to override any environment variables—such as when specifying custom credentials or cloud settings—you should create the `client` explicitly using `CopilotStudioAgent.create_client(...)` and then pass it to the agent constructor.
|
||||
|
||||
```python
|
||||
client: CopilotClient = CopilotStudioAgent.create_client(
|
||||
auth_mode: CopilotStudioAgentAuthMode | Literal["interactive", "service"] | None = None,
|
||||
agent_identifier: str | None = None,
|
||||
app_client_id: str | None = None,
|
||||
client_secret: str | None = None,
|
||||
client_certificate: str | None = None,
|
||||
cloud: PowerPlatformCloud | None = None,
|
||||
copilot_agent_type: AgentType | None = None,
|
||||
custom_power_platform_cloud: str | None = None,
|
||||
env_file_encoding: str | None = None,
|
||||
env_file_path: str | None = None,
|
||||
environment_id: str | None = None,
|
||||
tenant_id: str | None = None,
|
||||
user_assertion: str | None = None,
|
||||
)
|
||||
|
||||
agent = CopilotStudioAgent(
|
||||
client=client,
|
||||
name="<name>",
|
||||
instructions="<instructions>",
|
||||
)
|
||||
```
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
|
||||
from semantic_kernel.agents import CopilotStudioAgent
|
||||
|
||||
"""
|
||||
This sample demonstrates how to use the Copilot Studio agent to answer questions about physics.
|
||||
It does not use a thread to maintain context between user inputs.
|
||||
"""
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
# 1. Create the agent
|
||||
agent = CopilotStudioAgent(
|
||||
name="PhysicsAgent",
|
||||
instructions="You help answer questions about physics. ",
|
||||
)
|
||||
|
||||
# 2. Create a list of user inputs
|
||||
USER_INPUTS = [
|
||||
"Why is the sky blue?",
|
||||
"What is the speed of light?",
|
||||
]
|
||||
|
||||
# 3. Loop through the user inputs and get responses from the agent
|
||||
for user_input in USER_INPUTS:
|
||||
print(f"# User: {user_input}")
|
||||
response = await agent.get_response(messages=user_input)
|
||||
print(f"# {response.name}: {response}")
|
||||
|
||||
"""
|
||||
Sample output:
|
||||
|
||||
# User: Why is the sky blue?
|
||||
# PhysicsAgent: The sky appears blue because of the way Earth's atmosphere scatters sunlight.
|
||||
When sunlight enters the atmosphere, it is made up of different colors, each with different wavelengths.
|
||||
Blue light has shorter wavelengths and is scattered in all directions by the gases and particles in the
|
||||
atmosphere more than other colors. This scattered blue light is what we see when we look up at the sky.
|
||||
This phenomenon is known as Rayleigh scattering.
|
||||
AI-generated content may be incorrect
|
||||
# User: What is the speed of light?
|
||||
# PhysicsAgent: The speed of light in a vacuum is approximately 299,792,458 meters per second (m/s). This is often
|
||||
rounded to 300,000 kilometers per second (km/s) for simplicity.
|
||||
AI-generated content may be incorrect
|
||||
"""
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
|
||||
from microsoft_agents.copilotstudio.client import (
|
||||
CopilotClient,
|
||||
)
|
||||
|
||||
from semantic_kernel.agents import CopilotStudioAgent, CopilotStudioAgentThread
|
||||
|
||||
"""
|
||||
This sample demonstrates how to use the Copilot Studio agent to answer questions from the user.
|
||||
It demonstrates how to use a thread to maintain context between user inputs.
|
||||
"""
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
# As an example, manually create the client and pass it in to the agent
|
||||
# 1. Create the client
|
||||
client: CopilotClient = CopilotStudioAgent.create_client(auth_mode="interactive")
|
||||
|
||||
# 2. Create the agent
|
||||
agent = CopilotStudioAgent(
|
||||
client=client,
|
||||
name="PhysicsAgent",
|
||||
instructions="You are help answer questions about physics.",
|
||||
)
|
||||
|
||||
# 3. Create a list of user inputs
|
||||
USER_INPUTS = [
|
||||
"Hello! Who are you? My name is John Doe.",
|
||||
"What is the speed of light?",
|
||||
"What have we been talking about?",
|
||||
"What is my name?",
|
||||
]
|
||||
|
||||
# 4. Create a thread to maintain context between user inputs
|
||||
# If no thread is provided, a new thread will be created
|
||||
# and returned in the response
|
||||
thread: CopilotStudioAgentThread | None = None
|
||||
|
||||
# 5. Loop through the user inputs and get responses from the agent
|
||||
for user_input in USER_INPUTS:
|
||||
print(f"# User: {user_input}")
|
||||
response = await agent.get_response(messages=user_input, thread=thread)
|
||||
print(f"# {response.name}: {response}")
|
||||
thread = response.thread
|
||||
|
||||
# 6. If a thread was created, delete it when done
|
||||
if thread:
|
||||
await thread.delete()
|
||||
|
||||
"""
|
||||
Sample output:
|
||||
|
||||
# User: Hello! Who are you? My name is John Doe.
|
||||
# PhysicsAgent: Hello, John! I'm an AI assistant here to help you with any questions you might have.
|
||||
How can I assist you today?
|
||||
AI-generated content may be incorrect
|
||||
# User: What is the speed of light?
|
||||
# PhysicsAgent: The speed of light in a vacuum is approximately 299,792,458 meters per second (m/s).
|
||||
This is often rounded to 300,000 kilometers per second (km/s) for simplicity. If you have any more questions,
|
||||
feel free to ask!
|
||||
AI-generated content may be incorrect
|
||||
# User: What have we been talking about?
|
||||
# PhysicsAgent: Sure, John! So far, we've had the following conversation:
|
||||
|
||||
1. You introduced yourself and asked who I am.
|
||||
2. I introduced myself as an AI assistant and asked how I could assist you.
|
||||
3. You asked about the speed of light, and I provided the information that it is approximately 299,792,458 meters
|
||||
per second in a vacuum.
|
||||
|
||||
If you have any more questions or need further assistance, feel free to ask!
|
||||
AI-generated content may be incorrect
|
||||
# User: What is my name?
|
||||
# PhysicsAgent: Based on our conversation, your name is John Doe. How can I assist you further today?
|
||||
AI-generated content may be incorrect
|
||||
"""
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
|
||||
from semantic_kernel.agents import CopilotStudioAgent, CopilotStudioAgentThread
|
||||
from semantic_kernel.contents import ChatMessageContent
|
||||
from semantic_kernel.functions import KernelArguments
|
||||
from semantic_kernel.prompt_template import PromptTemplateConfig
|
||||
|
||||
"""
|
||||
This sample demonstrates how to use the Copilot Studio agent to answer questions from the user.
|
||||
It demonstrates how to use a thread to maintain context between user inputs.
|
||||
It also demonstrates how to use a custom prompt template.
|
||||
"""
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
# 1. Create the agent
|
||||
agent = CopilotStudioAgent(
|
||||
name="JokeAgent",
|
||||
instructions="You are a joker. Tell kid-friendly jokes.",
|
||||
prompt_template_config=PromptTemplateConfig(template="Craft jokes about {{$topic}}"),
|
||||
)
|
||||
|
||||
# 2. Create a list of user inputs
|
||||
USER_INPUTS = [ChatMessageContent(role="user", content="Tell me a joke to make me laugh.")]
|
||||
|
||||
# 3. Create a thread to maintain context between user inputs
|
||||
thread: CopilotStudioAgentThread | None = None
|
||||
|
||||
# 4. Loop through the user inputs and get responses from the agent
|
||||
for user_input in USER_INPUTS:
|
||||
print(f"# User: {user_input}")
|
||||
response = await agent.get_response(
|
||||
messages=user_input, thread=thread, arguments=KernelArguments(topic="pirate")
|
||||
)
|
||||
print(f"# {response.name}: {response}")
|
||||
thread = response.thread
|
||||
|
||||
# 5. If a thread was created, delete it when done
|
||||
if thread:
|
||||
await thread.delete()
|
||||
|
||||
"""
|
||||
# User: Tell me a joke to make me laugh.
|
||||
# JokeAgent: Sure, here are a few pirate jokes for you:
|
||||
|
||||
1. Why don't pirates shower before they walk the plank?
|
||||
Because they'll just wash up on shore later!
|
||||
|
||||
2. How do pirates prefer to communicate?
|
||||
Aye to aye!
|
||||
|
||||
3. What's a pirate's favorite letter?
|
||||
You might think it's "R," but their true love is the "C"!
|
||||
|
||||
Hope these made you smile!
|
||||
"""
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
|
||||
from semantic_kernel.agents import CopilotStudioAgent, CopilotStudioAgentThread
|
||||
|
||||
"""
|
||||
This sample demonstrates how to use the Copilot Studio agent to perform a web search.
|
||||
In Copilot Studio, for the specified agent, you must enable the "Web Search" capability.
|
||||
If not already enabled, make sure to (re-)publish the agent so the changes take effect.
|
||||
"""
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
# 1. Create the agent
|
||||
agent = CopilotStudioAgent(
|
||||
name="WebSearchAgent",
|
||||
instructions="Help answer the user's questions by searching the web.",
|
||||
)
|
||||
|
||||
# 2. Create a list of user inputs
|
||||
USER_INPUTS = [
|
||||
"Which team won the 2025 NCAA Basketball championship?",
|
||||
]
|
||||
|
||||
# 3. Create a thread to maintain context between user inputs
|
||||
thread: CopilotStudioAgentThread | None = None
|
||||
|
||||
# 4. Loop through the user inputs and get responses from the agent
|
||||
for user_input in USER_INPUTS:
|
||||
print(f"# User: {user_input}")
|
||||
async for response in agent.invoke(messages=user_input, thread=thread):
|
||||
print(f"# {response.name}: {response}")
|
||||
thread = response.thread
|
||||
|
||||
# 5. If a thread was created, delete it when done
|
||||
if thread:
|
||||
await thread.delete()
|
||||
"""
|
||||
Sample output:
|
||||
|
||||
# User: Which team won the 2025 NCAA Basketball championship?
|
||||
# WebSearchAgent: The Florida Gators won the 2025 NCAA Basketball championship by defeating the Houston Cougars
|
||||
with a score of 65-63 [1].
|
||||
|
||||
[1]: https://www.ncaa.com/news/basketball-men/mml-official-bracket/2025-04-06/latest-bracket-schedule-and-scores-2025-ncaa-mens-tournament
|
||||
"Latest bracket, schedule and scores for the 2025 NCAA men's tournament"
|
||||
"""
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,33 @@
|
||||
# Multi-agent orchestration
|
||||
|
||||
The Semantic Kernel Agent Framework now supports orchestrating multiple agents to work together to complete a task.
|
||||
|
||||
## Background
|
||||
|
||||
The following samples are beneficial if you are just getting started with Semantic Kernel.
|
||||
|
||||
- [Chat Completion](../../concepts/chat_completion/)
|
||||
- [Auto Function Calling](../../concepts/auto_function_calling/)
|
||||
- [Structured Output](../../concepts/structured_output/)
|
||||
- [Getting Started with Agents](../../getting_started_with_agents/)
|
||||
- [More advanced agent samples](../../concepts/agents/)
|
||||
|
||||
## Prerequisites
|
||||
|
||||
The following environment variables are required to run the samples:
|
||||
|
||||
- OPENAI_API_KEY
|
||||
- OPENAI_CHAT_MODEL_ID
|
||||
|
||||
However, if you are using other model services, feel free to switch to those in the samples.
|
||||
Refer to [here](../../concepts/setup/README.md) on how to set up the environment variables for your model service.
|
||||
|
||||
## Orchestrations
|
||||
|
||||
| **Orchestrations** | **Description** |
|
||||
| ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **Concurrent** | Useful for tasks that will benefit from independent analysis from multiple agents. |
|
||||
| **Sequential** | Useful for tasks that require a well-defined step-by-step approach. |
|
||||
| **Handoff** | Useful for tasks that are dynamic in nature and don't have a well-defined step-by-step approach. |
|
||||
| **GroupChat** | Useful for tasks that will benefit from inputs from multiple agents and a highly configurable conversation flow. |
|
||||
| **Magentic** | GroupChat like with a planner based manager. Inspired by [Magentic One](https://www.microsoft.com/en-us/research/articles/magentic-one-a-generalist-multi-agent-system-for-solving-complex-tasks/). |
|
||||
@@ -0,0 +1,94 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import logging
|
||||
import os
|
||||
|
||||
from azure.monitor.opentelemetry.exporter import AzureMonitorLogExporter, AzureMonitorTraceExporter
|
||||
from dotenv import load_dotenv
|
||||
from opentelemetry import trace
|
||||
from opentelemetry._logs import set_logger_provider
|
||||
from opentelemetry.sdk._logs import LoggerProvider, LoggingHandler
|
||||
from opentelemetry.sdk._logs.export import BatchLogRecordProcessor
|
||||
from opentelemetry.sdk.resources import Resource
|
||||
from opentelemetry.sdk.trace import TracerProvider
|
||||
from opentelemetry.sdk.trace.export import BatchSpanProcessor
|
||||
from opentelemetry.semconv.resource import ResourceAttributes
|
||||
from opentelemetry.trace import set_tracer_provider
|
||||
from opentelemetry.trace.span import format_trace_id
|
||||
|
||||
load_dotenv()
|
||||
|
||||
APPINSIGHTS_CONNECTION_STRING = os.getenv("APPINSIGHTS_CONNECTION_STRING")
|
||||
|
||||
resource = Resource.create({ResourceAttributes.SERVICE_NAME: "multi_agent_orchestration_sample"})
|
||||
|
||||
|
||||
def set_up_logging():
|
||||
class KernelFilter(logging.Filter):
|
||||
"""A filter to not process records from semantic_kernel."""
|
||||
|
||||
# These are the namespaces that we want to exclude from logging for the purposes of this demo.
|
||||
namespaces_to_exclude: list[str] = [
|
||||
"semantic_kernel.functions.kernel_plugin",
|
||||
"semantic_kernel.prompt_template.kernel_prompt_template",
|
||||
]
|
||||
|
||||
def filter(self, record):
|
||||
return not any([record.name.startswith(namespace) for namespace in self.namespaces_to_exclude])
|
||||
|
||||
exporters = []
|
||||
exporters.append(AzureMonitorLogExporter(connection_string=APPINSIGHTS_CONNECTION_STRING))
|
||||
|
||||
# Create and set a global logger provider for the application.
|
||||
logger_provider = LoggerProvider(resource=resource)
|
||||
# Log processors are initialized with an exporter which is responsible
|
||||
# for sending the telemetry data to a particular backend.
|
||||
for log_exporter in exporters:
|
||||
logger_provider.add_log_record_processor(BatchLogRecordProcessor(log_exporter))
|
||||
# Sets the global default logger provider
|
||||
set_logger_provider(logger_provider)
|
||||
|
||||
# Create a logging handler to write logging records, in OTLP format, to the exporter.
|
||||
handler = LoggingHandler()
|
||||
# Add filters to the handler to only process records from semantic_kernel.
|
||||
handler.addFilter(logging.Filter("semantic_kernel"))
|
||||
handler.addFilter(KernelFilter())
|
||||
# Attach the handler to the root logger. `getLogger()` with no arguments returns the root logger.
|
||||
# Events from all child loggers will be processed by this handler.
|
||||
logger = logging.getLogger()
|
||||
logger.addHandler(handler)
|
||||
# Set the logging level to NOTSET to allow all records to be processed by the handler.
|
||||
logger.setLevel(logging.NOTSET)
|
||||
|
||||
|
||||
def set_up_tracing():
|
||||
exporters = []
|
||||
exporters.append(AzureMonitorTraceExporter(connection_string=APPINSIGHTS_CONNECTION_STRING))
|
||||
|
||||
# Initialize a trace provider for the application. This is a factory for creating tracers.
|
||||
tracer_provider = TracerProvider(resource=resource)
|
||||
# Span processors are initialized with an exporter which is responsible
|
||||
# for sending the telemetry data to a particular backend.
|
||||
for exporter in exporters:
|
||||
tracer_provider.add_span_processor(BatchSpanProcessor(exporter))
|
||||
# Sets the global default tracer provider
|
||||
set_tracer_provider(tracer_provider)
|
||||
|
||||
|
||||
def enable_observability(func):
|
||||
"""A decorator to enable observability for the samples."""
|
||||
|
||||
async def wrapper(*args, **kwargs):
|
||||
if not APPINSIGHTS_CONNECTION_STRING:
|
||||
# If the connection string is not set, skip observability setup.
|
||||
return await func(*args, **kwargs)
|
||||
|
||||
set_up_logging()
|
||||
set_up_tracing()
|
||||
|
||||
tracer = trace.get_tracer(__name__)
|
||||
with tracer.start_as_current_span("main") as current_span:
|
||||
print(f"Trace ID: {format_trace_id(current_span.get_span_context().trace_id)}")
|
||||
return await func(*args, **kwargs)
|
||||
|
||||
return wrapper
|
||||
+111
@@ -0,0 +1,111 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
|
||||
from azure.identity import AzureCliCredential
|
||||
|
||||
from semantic_kernel.agents import Agent, ChatCompletionAgent, ConcurrentOrchestration
|
||||
from semantic_kernel.agents.runtime import InProcessRuntime
|
||||
from semantic_kernel.connectors.ai.open_ai import AzureChatCompletion
|
||||
|
||||
"""
|
||||
The following sample demonstrates how to create a concurrent orchestration for
|
||||
executing multiple agents on the same task in parallel.
|
||||
|
||||
This sample demonstrates the basic steps of creating and starting a runtime, creating
|
||||
a concurrent orchestration with multiple agents, invoking the orchestration, and finally
|
||||
waiting for the results.
|
||||
"""
|
||||
|
||||
|
||||
def get_agents() -> list[Agent]:
|
||||
"""Return a list of agents that will participate in the concurrent orchestration.
|
||||
|
||||
Feel free to add or remove agents.
|
||||
"""
|
||||
credential = AzureCliCredential()
|
||||
|
||||
physics_agent = ChatCompletionAgent(
|
||||
name="PhysicsExpert",
|
||||
instructions="You are an expert in physics. You answer questions from a physics perspective.",
|
||||
service=AzureChatCompletion(credential=credential),
|
||||
)
|
||||
chemistry_agent = ChatCompletionAgent(
|
||||
name="ChemistryExpert",
|
||||
instructions="You are an expert in chemistry. You answer questions from a chemistry perspective.",
|
||||
service=AzureChatCompletion(credential=credential),
|
||||
)
|
||||
|
||||
return [physics_agent, chemistry_agent]
|
||||
|
||||
|
||||
async def main():
|
||||
"""Main function to run the agents."""
|
||||
# 1. Create a concurrent orchestration with multiple agents
|
||||
agents = get_agents()
|
||||
concurrent_orchestration = ConcurrentOrchestration(members=agents)
|
||||
|
||||
# 2. Create a runtime and start it
|
||||
runtime = InProcessRuntime()
|
||||
runtime.start()
|
||||
|
||||
# 3. Invoke the orchestration with a task and the runtime
|
||||
orchestration_result = await concurrent_orchestration.invoke(
|
||||
task="What is temperature?",
|
||||
runtime=runtime,
|
||||
)
|
||||
|
||||
# 4. Wait for the results
|
||||
# Note: the order of the results is not guaranteed to be the same
|
||||
# as the order of the agents in the orchestration.
|
||||
value = await orchestration_result.get(timeout=20)
|
||||
for item in value:
|
||||
print(f"# {item.name}: {item.content}")
|
||||
|
||||
# 5. Stop the runtime after the invocation is complete
|
||||
await runtime.stop_when_idle()
|
||||
|
||||
"""
|
||||
Sample output:
|
||||
# PhysicsExpert: Temperature is a physical quantity that represents the average kinetic energy of the particles in
|
||||
a substance. It is an indicator of how hot or cold an object is and determines the direction of heat transfer
|
||||
between two objects. Heat flows from a region of higher temperature to a region of lower temperature until
|
||||
thermal equilibrium is reached.
|
||||
|
||||
In terms of molecular dynamics, at higher temperatures, particles move more vigorously and have higher kinetic
|
||||
energy, whereas at lower temperatures, their motion is less energetic. Temperature scales such as Celsius,
|
||||
Fahrenheit, and Kelvin are used to quantify temperature. The Kelvin scale is particularly important in
|
||||
scientific contexts because it starts at absolute zero—the theoretical point where particle motion would cease
|
||||
completely.
|
||||
|
||||
Temperature also affects various physical properties of materials, such as their state (solid, liquid, or gas),
|
||||
density, viscosity, and electrical conductivity. It is a crucial parameter in many areas of physics, from
|
||||
thermodynamics to statistical mechanics and beyond.
|
||||
# ChemistryExpert: Temperature is a fundamental concept in chemistry and physics, representing a measure of the
|
||||
average kinetic energy of the particles in a substance. It reflects how hot or cold an object is and determines
|
||||
the direction of heat transfer between substances. In more specific terms:
|
||||
|
||||
1. **Kinetic Energy Perspective:** At the molecular level, temperature is linked to the motions of the particles
|
||||
comprising a substance. The greater the motion (translational, rotational, vibrational), the higher the
|
||||
temperature. For example, in gases, temperature is directly related to the average kinetic energy of the gas
|
||||
particles.
|
||||
|
||||
2. **Thermodynamic View:** Temperature is an intensive property and a state function, meaning it doesn't depend
|
||||
on the amount of substance present. It is a critical parameter in the laws of thermodynamics, especially in
|
||||
determining the spontaneity of processes and the distribution of energy in a system.
|
||||
|
||||
3. **Scales:** Temperature is measured using various scales, including Celsius (°C), Fahrenheit (°F), and
|
||||
Kelvin (K). The Kelvin scale is the SI unit for temperature and starts at absolute zero (0 K), the theoretical
|
||||
point where all molecular motion ceases.
|
||||
|
||||
4. **Effect on Chemical Reactions:** Temperature affects reaction rates, equilibrium positions, and the
|
||||
solubility of substances. Generally, increasing temperature speeds up chemical reactions due to increased
|
||||
molecular collisions and energy overcoming activation barriers.
|
||||
|
||||
Understanding temperature is essential in predicting and explaining chemical behavior and interactions in
|
||||
reactions, phases changes, and even biological processes.
|
||||
"""
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
+137
@@ -0,0 +1,137 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
|
||||
from azure.core.credentials import TokenCredential
|
||||
from azure.identity import AzureCliCredential
|
||||
from pydantic import BaseModel
|
||||
|
||||
from semantic_kernel.agents import Agent, ChatCompletionAgent, ConcurrentOrchestration
|
||||
from semantic_kernel.agents.orchestration.tools import structured_outputs_transform
|
||||
from semantic_kernel.agents.runtime import InProcessRuntime
|
||||
from semantic_kernel.connectors.ai.open_ai import AzureChatCompletion
|
||||
|
||||
"""
|
||||
The following sample demonstrates how to create a concurrent orchestration for
|
||||
executing multiple agents on the same task in parallel and returning a structured output.
|
||||
|
||||
This sample demonstrates the basic steps of creating and starting a runtime, creating
|
||||
a concurrent orchestration with multiple agents with a structure output transform,
|
||||
invoking the orchestration, and finally waiting for the results.
|
||||
"""
|
||||
|
||||
|
||||
class ArticleAnalysis(BaseModel):
|
||||
"""A model to hold the analysis of an article."""
|
||||
|
||||
themes: list[str]
|
||||
sentiments: list[str]
|
||||
entities: list[str]
|
||||
|
||||
|
||||
def get_agents(credential: TokenCredential) -> list[Agent]:
|
||||
"""Return a list of agents that will participate in the concurrent orchestration.
|
||||
|
||||
Feel free to add or remove agents.
|
||||
"""
|
||||
theme_agent = ChatCompletionAgent(
|
||||
name="ThemeAgent",
|
||||
instructions="You are an expert in identifying themes in articles. Given an article, identify the main themes.",
|
||||
service=AzureChatCompletion(credential=credential),
|
||||
)
|
||||
sentiment_agent = ChatCompletionAgent(
|
||||
name="SentimentAgent",
|
||||
instructions="You are an expert in sentiment analysis. Given an article, identify the sentiment.",
|
||||
service=AzureChatCompletion(credential=credential),
|
||||
)
|
||||
entity_agent = ChatCompletionAgent(
|
||||
name="EntityAgent",
|
||||
instructions="You are an expert in entity recognition. Given an article, extract the entities.",
|
||||
service=AzureChatCompletion(credential=credential),
|
||||
)
|
||||
|
||||
return [theme_agent, sentiment_agent, entity_agent]
|
||||
|
||||
|
||||
async def main():
|
||||
"""Main function to run the agents."""
|
||||
# 1. Create a concurrent orchestration with multiple agents
|
||||
# and a structure output transform.
|
||||
# To enable structured output, you must specify the output transform
|
||||
# and the generic types for the orchestration.
|
||||
# Note: the chat completion service and model provided to the
|
||||
# structure output transform must support structured output.
|
||||
credential = AzureCliCredential()
|
||||
agents = get_agents(credential)
|
||||
concurrent_orchestration = ConcurrentOrchestration[str, ArticleAnalysis](
|
||||
members=agents,
|
||||
output_transform=structured_outputs_transform(ArticleAnalysis, AzureChatCompletion(credential=credential)),
|
||||
)
|
||||
|
||||
# 2. Read the task from a file
|
||||
with open(os.path.join(os.path.dirname(__file__), "../resources", "Hamlet_full_play_summary.txt")) as file:
|
||||
task = file.read()
|
||||
|
||||
# 3. Create a runtime and start it
|
||||
runtime = InProcessRuntime()
|
||||
runtime.start()
|
||||
|
||||
# 4. Invoke the orchestration with a task and the runtime
|
||||
orchestration_result = await concurrent_orchestration.invoke(
|
||||
task=task,
|
||||
runtime=runtime,
|
||||
)
|
||||
|
||||
# 5. Wait for the results
|
||||
value = await orchestration_result.get(timeout=20)
|
||||
if isinstance(value, ArticleAnalysis):
|
||||
print(value.model_dump_json(indent=2))
|
||||
else:
|
||||
print("Unexpected result type:", type(value))
|
||||
|
||||
# 6. Stop the runtime after the invocation is complete
|
||||
await runtime.stop_when_idle()
|
||||
|
||||
"""
|
||||
Sample output:
|
||||
{
|
||||
"themes": [
|
||||
"Revenge and Justice",
|
||||
"Madness",
|
||||
"Corruption and Power",
|
||||
"Death and Mortality",
|
||||
"Appearance vs. Reality",
|
||||
"Family and Loyalty"
|
||||
],
|
||||
"sentiments": [
|
||||
"dark",
|
||||
"somber",
|
||||
"negative"
|
||||
],
|
||||
"entities": [
|
||||
"Elsinore Castle",
|
||||
"Denmark",
|
||||
"Horatio",
|
||||
"King Hamlet",
|
||||
"Claudius",
|
||||
"Queen Gertrude",
|
||||
"Prince Hamlet",
|
||||
"Rosencrantz",
|
||||
"Guildenstern",
|
||||
"Polonius",
|
||||
"Ophelia",
|
||||
"Laertes",
|
||||
"England",
|
||||
"King of England",
|
||||
"France",
|
||||
"Osric",
|
||||
"Fortinbras",
|
||||
"Poland"
|
||||
]
|
||||
}
|
||||
"""
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
+137
@@ -0,0 +1,137 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
|
||||
from azure.identity import AzureCliCredential
|
||||
|
||||
from semantic_kernel.agents import Agent, ChatCompletionAgent, SequentialOrchestration
|
||||
from semantic_kernel.agents.runtime import InProcessRuntime
|
||||
from semantic_kernel.connectors.ai.open_ai import AzureChatCompletion
|
||||
from semantic_kernel.contents import ChatMessageContent
|
||||
|
||||
"""
|
||||
The following sample demonstrates how to create a sequential orchestration for
|
||||
executing multiple agents in sequence, i.e. the output of one agent is the input
|
||||
to the next agent.
|
||||
|
||||
This sample demonstrates the basic steps of creating and starting a runtime, creating
|
||||
a sequential orchestration, invoking the orchestration, and finally waiting for the
|
||||
results.
|
||||
"""
|
||||
|
||||
|
||||
def get_agents() -> list[Agent]:
|
||||
"""Return a list of agents that will participate in the sequential orchestration.
|
||||
|
||||
Feel free to add or remove agents.
|
||||
"""
|
||||
credential = AzureCliCredential()
|
||||
|
||||
concept_extractor_agent = ChatCompletionAgent(
|
||||
name="ConceptExtractorAgent",
|
||||
instructions=(
|
||||
"You are a marketing analyst. Given a product description, identify:\n"
|
||||
"- Key features\n"
|
||||
"- Target audience\n"
|
||||
"- Unique selling points\n\n"
|
||||
),
|
||||
service=AzureChatCompletion(credential=credential),
|
||||
)
|
||||
writer_agent = ChatCompletionAgent(
|
||||
name="WriterAgent",
|
||||
instructions=(
|
||||
"You are a marketing copywriter. Given a block of text describing features, audience, and USPs, "
|
||||
"compose a compelling marketing copy (like a newsletter section) that highlights these points. "
|
||||
"Output should be short (around 150 words), output just the copy as a single text block."
|
||||
),
|
||||
service=AzureChatCompletion(credential=credential),
|
||||
)
|
||||
format_proof_agent = ChatCompletionAgent(
|
||||
name="FormatProofAgent",
|
||||
instructions=(
|
||||
"You are an editor. Given the draft copy, correct grammar, improve clarity, ensure consistent tone, "
|
||||
"give format and make it polished. Output the final improved copy as a single text block."
|
||||
),
|
||||
service=AzureChatCompletion(credential=credential),
|
||||
)
|
||||
|
||||
# The order of the agents in the list will be the order in which they are executed
|
||||
return [concept_extractor_agent, writer_agent, format_proof_agent]
|
||||
|
||||
|
||||
def agent_response_callback(message: ChatMessageContent) -> None:
|
||||
"""Observer function to print the messages from the agents."""
|
||||
print(f"# {message.name}\n{message.content}")
|
||||
|
||||
|
||||
async def main():
|
||||
"""Main function to run the agents."""
|
||||
# 1. Create a sequential orchestration with multiple agents and an agent
|
||||
# response callback to observe the output from each agent.
|
||||
agents = get_agents()
|
||||
sequential_orchestration = SequentialOrchestration(
|
||||
members=agents,
|
||||
agent_response_callback=agent_response_callback,
|
||||
)
|
||||
|
||||
# 2. Create a runtime and start it
|
||||
runtime = InProcessRuntime()
|
||||
runtime.start()
|
||||
|
||||
# 3. Invoke the orchestration with a task and the runtime
|
||||
orchestration_result = await sequential_orchestration.invoke(
|
||||
task="An eco-friendly stainless steel water bottle that keeps drinks cold for 24 hours",
|
||||
runtime=runtime,
|
||||
)
|
||||
|
||||
# 4. Wait for the results
|
||||
value = await orchestration_result.get(timeout=20)
|
||||
print(f"***** Final Result *****\n{value}")
|
||||
|
||||
# 5. Stop the runtime when idle
|
||||
await runtime.stop_when_idle()
|
||||
|
||||
"""
|
||||
Sample output:
|
||||
# ConceptExtractorAgent
|
||||
- Key Features:
|
||||
- Made of eco-friendly stainless steel
|
||||
- Keeps drinks cold for 24 hours
|
||||
|
||||
- Target Audience:
|
||||
- Environmentally conscious consumers
|
||||
- People who need a reliable way to keep their drinks cold for extended periods, such as athletes, travelers, and
|
||||
outdoor enthusiasts
|
||||
|
||||
- Unique Selling Points:
|
||||
- Environmentally sustainable material
|
||||
- Exceptionally long-lasting cold temperature retention (24 hours)
|
||||
# WriterAgent
|
||||
Keep your beverages refreshingly chilled all day long with our eco-friendly stainless steel bottles. Perfect for
|
||||
those who care about the planet, our bottles not only reduce waste but also promise to keep your drinks cold for
|
||||
an impressive 24 hours. Whether you're an athlete pushing your limits, a traveler on the go, or simply an outdoor
|
||||
enthusiast enjoying nature's beauty, this is the accessory you can't do without. Built from sustainable materials,
|
||||
our bottles ensure both environmental responsibility and remarkable performance. Stay refreshed, stay green, and
|
||||
make every sip a testament to your planet-friendly lifestyle. Join us in the journey towards a cooler, sustainable
|
||||
tomorrow.
|
||||
# FormatProofAgent
|
||||
Keep your beverages refreshingly chilled all day long with our eco-friendly stainless steel bottles. Perfect for
|
||||
those who care about the planet, our bottles not only reduce waste but also promise to keep your drinks cold for
|
||||
an impressive 24 hours. Whether you're an athlete pushing your limits, a traveler on the go, or simply an outdoor
|
||||
enthusiast enjoying nature's beauty, this is the accessory you can't do without. Built from sustainable materials,
|
||||
our bottles ensure both environmental responsibility and remarkable performance. Stay refreshed, stay green, and
|
||||
make every sip a testament to your planet-friendly lifestyle. Join us in the journey towards a cooler, sustainable
|
||||
tomorrow.
|
||||
***** Final Result *****
|
||||
Keep your beverages refreshingly chilled all day long with our eco-friendly stainless steel bottles. Perfect for
|
||||
those who care about the planet, our bottles not only reduce waste but also promise to keep your drinks cold for
|
||||
an impressive 24 hours. Whether you're an athlete pushing your limits, a traveler on the go, or simply an outdoor
|
||||
enthusiast enjoying nature's beauty, this is the accessory you can't do without. Built from sustainable materials,
|
||||
our bottles ensure both environmental responsibility and remarkable performance. Stay refreshed, stay green, and
|
||||
make every sip a testament to your planet-friendly lifestyle. Join us in the journey towards a cooler, sustainable
|
||||
tomorrow.
|
||||
"""
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
+113
@@ -0,0 +1,113 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
from azure.identity import AzureCliCredential
|
||||
|
||||
from semantic_kernel.agents import Agent, ChatCompletionAgent, SequentialOrchestration
|
||||
from semantic_kernel.agents.runtime import InProcessRuntime
|
||||
from semantic_kernel.connectors.ai.open_ai import AzureChatCompletion
|
||||
|
||||
"""
|
||||
The following sample demonstrates how to cancel an invocation of an orchestration
|
||||
that is still running.
|
||||
|
||||
This sample demonstrates the basic steps of creating and starting a runtime, creating
|
||||
a sequential orchestration, invoking the orchestration, and cancelling it before it
|
||||
finishes.
|
||||
"""
|
||||
|
||||
# Set up logging to see the invocation process
|
||||
logging.basicConfig(level=logging.WARNING) # Set default level to WARNING
|
||||
logging.getLogger("semantic_kernel.agents.orchestration.sequential").setLevel(logging.DEBUG)
|
||||
|
||||
|
||||
def get_agents() -> list[Agent]:
|
||||
"""Return a list of agents that will participate in the sequential orchestration.
|
||||
|
||||
Feel free to add or remove agents.
|
||||
"""
|
||||
credential = AzureCliCredential()
|
||||
|
||||
concept_extractor_agent = ChatCompletionAgent(
|
||||
name="ConceptExtractorAgent",
|
||||
instructions=(
|
||||
"You are a marketing analyst. Given a product description, identify:\n"
|
||||
"- Key features\n"
|
||||
"- Target audience\n"
|
||||
"- Unique selling points\n\n"
|
||||
),
|
||||
service=AzureChatCompletion(credential=credential),
|
||||
)
|
||||
writer_agent = ChatCompletionAgent(
|
||||
name="WriterAgent",
|
||||
instructions=(
|
||||
"You are a marketing copywriter. Given a block of text describing features, audience, and USPs, "
|
||||
"compose a compelling marketing copy (like a newsletter section) that highlights these points. "
|
||||
"Output should be short (around 150 words), output just the copy as a single text block."
|
||||
),
|
||||
service=AzureChatCompletion(credential=credential),
|
||||
)
|
||||
format_proof_agent = ChatCompletionAgent(
|
||||
name="FormatProofAgent",
|
||||
instructions=(
|
||||
"You are an editor. Given the draft copy, correct grammar, improve clarity, ensure consistent tone, "
|
||||
"give format and make it polished. Output the final improved copy as a single text block."
|
||||
),
|
||||
service=AzureChatCompletion(credential=credential),
|
||||
)
|
||||
|
||||
# The order of the agents in the list will be the order in which they are executed
|
||||
return [concept_extractor_agent, writer_agent, format_proof_agent]
|
||||
|
||||
|
||||
async def main():
|
||||
"""Main function to run the agents."""
|
||||
# 1. Create a sequential orchestration with multiple agents
|
||||
agents = get_agents()
|
||||
sequential_orchestration = SequentialOrchestration(members=agents)
|
||||
|
||||
# 2. Create a runtime and start it
|
||||
runtime = InProcessRuntime()
|
||||
runtime.start()
|
||||
|
||||
# 3. Invoke the orchestration with a task and the runtime
|
||||
orchestration_result = await sequential_orchestration.invoke(
|
||||
task="An eco-friendly stainless steel water bottle that keeps drinks cold for 24 hours",
|
||||
runtime=runtime,
|
||||
)
|
||||
|
||||
# 4. Cancel the orchestration before it finishes
|
||||
await asyncio.sleep(1) # Simulate some delay before cancellation
|
||||
orchestration_result.cancel()
|
||||
|
||||
try:
|
||||
# Attempt to get the result will result in an exception due to cancellation
|
||||
_ = await orchestration_result.get(timeout=20)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
finally:
|
||||
# 5. Stop the runtime
|
||||
await runtime.stop_when_idle()
|
||||
|
||||
"""
|
||||
Sample output:
|
||||
DEBUG:semantic_kernel.agents.orchestration.sequential:Registered agent actor of type
|
||||
FormatProofAgent_5efa69d39306414c91325ef82145ec19
|
||||
DEBUG:semantic_kernel.agents.orchestration.sequential:Registered agent actor of type
|
||||
WriterAgent_5efa69d39306414c91325ef82145ec19
|
||||
DEBUG:semantic_kernel.agents.orchestration.sequential:Registered agent actor of type
|
||||
ConceptExtractorAgent_5efa69d39306414c91325ef82145ec19
|
||||
DEBUG:semantic_kernel.agents.orchestration.sequential:Sequential actor
|
||||
(Actor ID: ConceptExtractorAgent_5efa69d39306414c91325ef82145ec19/default; Agent name: ConceptExtractorAgent)
|
||||
started processing...
|
||||
The invocation was canceled before it could complete.
|
||||
DEBUG:semantic_kernel.agents.orchestration.sequential:Sequential actor
|
||||
(Actor ID: ConceptExtractorAgent_5efa69d39306414c91325ef82145ec19/default; Agent name: ConceptExtractorAgent)
|
||||
finished processing.
|
||||
"""
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
+163
@@ -0,0 +1,163 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
|
||||
from azure.identity import AzureCliCredential
|
||||
|
||||
from semantic_kernel.agents import Agent, ChatCompletionAgent, SequentialOrchestration
|
||||
from semantic_kernel.agents.runtime import InProcessRuntime
|
||||
from semantic_kernel.connectors.ai.open_ai import AzureChatCompletion
|
||||
from semantic_kernel.contents import StreamingChatMessageContent
|
||||
|
||||
"""
|
||||
The following sample demonstrates how to create a sequential orchestration for
|
||||
executing multiple agents in sequence, i.e. the output of one agent is the input
|
||||
to the next agent.
|
||||
|
||||
This sample demonstrates the basic steps of creating and starting a runtime, creating
|
||||
a sequential orchestration, invoking the orchestration, and finally waiting for the
|
||||
results.
|
||||
"""
|
||||
|
||||
|
||||
def get_agents() -> list[Agent]:
|
||||
"""Return a list of agents that will participate in the sequential orchestration.
|
||||
|
||||
Feel free to add or remove agents.
|
||||
"""
|
||||
credential = AzureCliCredential()
|
||||
|
||||
concept_extractor_agent = ChatCompletionAgent(
|
||||
name="ConceptExtractorAgent",
|
||||
instructions=(
|
||||
"You are a marketing analyst. Given a product description, identify:\n"
|
||||
"- Key features\n"
|
||||
"- Target audience\n"
|
||||
"- Unique selling points\n\n"
|
||||
),
|
||||
service=AzureChatCompletion(credential=credential),
|
||||
)
|
||||
writer_agent = ChatCompletionAgent(
|
||||
name="WriterAgent",
|
||||
instructions=(
|
||||
"You are a marketing copywriter. Given a block of text describing features, audience, and USPs, "
|
||||
"compose a compelling marketing copy (like a newsletter section) that highlights these points. "
|
||||
"Output should be short (around 150 words), output just the copy as a single text block."
|
||||
),
|
||||
service=AzureChatCompletion(credential=credential),
|
||||
)
|
||||
format_proof_agent = ChatCompletionAgent(
|
||||
name="FormatProofAgent",
|
||||
instructions=(
|
||||
"You are an editor. Given the draft copy, correct grammar, improve clarity, ensure consistent tone, "
|
||||
"give format and make it polished. Output the final improved copy as a single text block."
|
||||
),
|
||||
service=AzureChatCompletion(credential=credential),
|
||||
)
|
||||
|
||||
# The order of the agents in the list will be the order in which they are executed
|
||||
return [concept_extractor_agent, writer_agent, format_proof_agent]
|
||||
|
||||
|
||||
# Flag to indicate if a new message is being received
|
||||
is_new_message = True
|
||||
|
||||
|
||||
def streaming_agent_response_callback(message: StreamingChatMessageContent, is_final: bool) -> None:
|
||||
"""Observer function to print the messages from the agents.
|
||||
|
||||
Args:
|
||||
message (StreamingChatMessageContent): The streaming message content from the agent.
|
||||
is_final (bool): Indicates if this is the final part of the message.
|
||||
"""
|
||||
global is_new_message
|
||||
if is_new_message:
|
||||
print(f"# {message.name}")
|
||||
is_new_message = False
|
||||
print(message.content, end="", flush=True)
|
||||
if is_final:
|
||||
print()
|
||||
is_new_message = True
|
||||
|
||||
|
||||
async def main():
|
||||
"""Main function to run the agents."""
|
||||
# 1. Create a sequential orchestration with multiple agents and an agent
|
||||
# response callback to observe the output from each agent as they stream
|
||||
# their responses.
|
||||
agents = get_agents()
|
||||
sequential_orchestration = SequentialOrchestration(
|
||||
members=agents,
|
||||
streaming_agent_response_callback=streaming_agent_response_callback,
|
||||
)
|
||||
|
||||
# 2. Create a runtime and start it
|
||||
runtime = InProcessRuntime()
|
||||
runtime.start()
|
||||
|
||||
# 3. Invoke the orchestration with a task and the runtime
|
||||
orchestration_result = await sequential_orchestration.invoke(
|
||||
task="An eco-friendly stainless steel water bottle that keeps drinks cold for 24 hours",
|
||||
runtime=runtime,
|
||||
)
|
||||
|
||||
# 4. Wait for the results
|
||||
value = await orchestration_result.get(timeout=20)
|
||||
print(f"***** Final Result *****\n{value}")
|
||||
|
||||
# 5. Stop the runtime when idle
|
||||
await runtime.stop_when_idle()
|
||||
|
||||
"""
|
||||
Sample output:
|
||||
# ConceptExtractorAgent
|
||||
**Key Features:**
|
||||
- Made from eco-friendly stainless steel
|
||||
- Insulation technology that keeps drinks cold for up to 24 hours
|
||||
- Reusable design, promoting sustainability
|
||||
- Possible variations in sizes and colors
|
||||
|
||||
**Target Audience:**
|
||||
- Environmentally conscious consumers
|
||||
- Active individuals and outdoor enthusiasts
|
||||
- Health-conscious individuals looking to stay hydrated
|
||||
- Students and professionals looking for stylish and functional drinkware
|
||||
|
||||
**Unique Selling Points:**
|
||||
- Combines eco-friendliness with high performance in temperature retention
|
||||
- Durable and reusable, reducing reliance on single-use plastics
|
||||
- Sleek design that appeals to modern aesthetics while being functional
|
||||
- Supporting sustainability initiatives through responsible manufacturing practices
|
||||
# WriterAgent
|
||||
Sip sustainably with our eco-friendly stainless steel water bottles, designed for the conscious consumer who values
|
||||
both performance and aesthetics. Our bottles feature advanced insulation technology that keeps your drinks icy cold
|
||||
for up to 24 hours, making them perfect for outdoor adventures, gym sessions, or a busy day at the office. Choose
|
||||
from various sizes and stunning colors to match your personal style while making a positive impact on the planet.
|
||||
Each reusable bottle helps reduce single-use plastics, supporting a cleaner, greener world. Join the movement toward
|
||||
sustainability without compromising on style or functionality. Stay hydrated, look great, and make a difference—get
|
||||
your eco-friendly water bottle today!
|
||||
# FormatProofAgent
|
||||
Sip sustainably with our eco-friendly stainless steel water bottles, designed for the conscious consumer who values
|
||||
both performance and aesthetics. Our bottles utilize advanced insulation technology to keep your beverages icy cold
|
||||
for up to 24 hours, making them perfect for outdoor adventures, gym sessions, or a busy day at the office.
|
||||
|
||||
Choose from a variety of sizes and stunning colors to match your personal style while positively impacting the
|
||||
planet. Each reusable bottle helps reduce single-use plastics, supporting a cleaner, greener world.
|
||||
|
||||
Join the movement towards sustainability without compromising on style or functionality. Stay hydrated, look great,
|
||||
and make a difference—get your eco-friendly water bottle today!
|
||||
***** Final Result *****
|
||||
Sip sustainably with our eco-friendly stainless steel water bottles, designed for the conscious consumer who values
|
||||
both performance and aesthetics. Our bottles utilize advanced insulation technology to keep your beverages icy cold
|
||||
for up to 24 hours, making them perfect for outdoor adventures, gym sessions, or a busy day at the office.
|
||||
|
||||
Choose from a variety of sizes and stunning colors to match your personal style while positively impacting the
|
||||
planet. Each reusable bottle helps reduce single-use plastics, supporting a cleaner, greener world.
|
||||
|
||||
Join the movement towards sustainability without compromising on style or functionality. Stay hydrated, look great,
|
||||
and make a difference—get your eco-friendly water bottle today!
|
||||
"""
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
+120
@@ -0,0 +1,120 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
|
||||
from azure.identity import AzureCliCredential
|
||||
|
||||
from semantic_kernel.agents import Agent, ChatCompletionAgent, GroupChatOrchestration, RoundRobinGroupChatManager
|
||||
from semantic_kernel.agents.runtime import InProcessRuntime
|
||||
from semantic_kernel.connectors.ai.open_ai import AzureChatCompletion
|
||||
from semantic_kernel.contents import ChatMessageContent
|
||||
|
||||
"""
|
||||
The following sample demonstrates how to create a group chat orchestration with a default
|
||||
round robin manager for controlling the flow of conversation in a round robin fashion.
|
||||
|
||||
Think of the group chat manager as a state machine, with the following possible states:
|
||||
- Request for user message
|
||||
- Termination, after which the manager will try to filter a result from the conversation
|
||||
- Continuation, at which the manager will select the next agent to speak
|
||||
|
||||
This sample demonstrates the basic steps of creating and starting a runtime, creating
|
||||
a group chat orchestration with a group chat manager, invoking the orchestration,
|
||||
and finally waiting for the results.
|
||||
|
||||
There are two agents in this orchestration: a writer and a reviewer. They work iteratively
|
||||
to refine a slogan for a new electric SUV.
|
||||
"""
|
||||
|
||||
|
||||
def get_agents() -> list[Agent]:
|
||||
"""Return a list of agents that will participate in the group style discussion.
|
||||
|
||||
Feel free to add or remove agents.
|
||||
"""
|
||||
credential = AzureCliCredential()
|
||||
|
||||
writer = ChatCompletionAgent(
|
||||
name="Writer",
|
||||
description="A content writer.",
|
||||
instructions=(
|
||||
"You are an excellent content writer. You create new content and edit contents based on the feedback."
|
||||
),
|
||||
service=AzureChatCompletion(credential=credential),
|
||||
)
|
||||
reviewer = ChatCompletionAgent(
|
||||
name="Reviewer",
|
||||
description="A content reviewer.",
|
||||
instructions=(
|
||||
"You are an excellent content reviewer. You review the content and provide feedback to the writer."
|
||||
),
|
||||
service=AzureChatCompletion(credential=credential),
|
||||
)
|
||||
|
||||
# The order of the agents in the list will be the order in which they will be picked by the round robin manager
|
||||
return [writer, reviewer]
|
||||
|
||||
|
||||
def agent_response_callback(message: ChatMessageContent) -> None:
|
||||
"""Observer function to print the messages from the agents."""
|
||||
print(f"**{message.name}**\n{message.content}")
|
||||
|
||||
|
||||
async def main():
|
||||
"""Main function to run the agents."""
|
||||
# 1. Create a group chat orchestration with a round robin manager
|
||||
agents = get_agents()
|
||||
group_chat_orchestration = GroupChatOrchestration(
|
||||
members=agents,
|
||||
# max_rounds is odd, so that the writer gets the last round
|
||||
manager=RoundRobinGroupChatManager(max_rounds=5),
|
||||
agent_response_callback=agent_response_callback,
|
||||
)
|
||||
|
||||
# 2. Create a runtime and start it
|
||||
runtime = InProcessRuntime()
|
||||
runtime.start()
|
||||
|
||||
# 3. Invoke the orchestration with a task and the runtime
|
||||
orchestration_result = await group_chat_orchestration.invoke(
|
||||
task="Create a slogan for a new electric SUV that is affordable and fun to drive.",
|
||||
runtime=runtime,
|
||||
)
|
||||
|
||||
# 4. Wait for the results
|
||||
value = await orchestration_result.get()
|
||||
print(f"***** Result *****\n{value}")
|
||||
|
||||
# 5. Stop the runtime after the invocation is complete
|
||||
await runtime.stop_when_idle()
|
||||
|
||||
"""
|
||||
Sample output:
|
||||
**Writer**
|
||||
"Drive Tomorrow: Affordable Adventure Starts Today!"
|
||||
**Reviewer**
|
||||
This slogan, "Drive Tomorrow: Affordable Adventure Starts Today!", effectively communicates the core attributes of
|
||||
the new electric SUV being promoted: affordability, fun, and forward-thinking. Here's some feedback:
|
||||
|
||||
...
|
||||
|
||||
Overall, the slogan captures the essence of an innovative, enjoyable, and accessible vehicle. Great job!
|
||||
**Writer**
|
||||
"Embrace the Future: Your Affordable Electric Adventure Awaits!"
|
||||
**Reviewer**
|
||||
This revised slogan, "Embrace the Future: Your Affordable Electric Adventure Awaits!", further enhances the message
|
||||
of the electric SUV. Here's an evaluation:
|
||||
|
||||
...
|
||||
|
||||
Overall, this version of the slogan effectively communicates the vehicle's benefits while maintaining a positive
|
||||
and engaging tone. Keep up the good work!
|
||||
**Writer**
|
||||
"Feel the Charge: Adventure Meets Affordability in Your New Electric SUV!"
|
||||
***** Result *****
|
||||
"Feel the Charge: Adventure Meets Affordability in Your New Electric SUV!"
|
||||
"""
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
+161
@@ -0,0 +1,161 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
import sys
|
||||
|
||||
from azure.identity import AzureCliCredential
|
||||
|
||||
from semantic_kernel.agents import Agent, ChatCompletionAgent, GroupChatOrchestration
|
||||
from semantic_kernel.agents.orchestration.group_chat import BooleanResult, RoundRobinGroupChatManager
|
||||
from semantic_kernel.agents.runtime import InProcessRuntime
|
||||
from semantic_kernel.connectors.ai.open_ai import AzureChatCompletion
|
||||
from semantic_kernel.contents import AuthorRole, ChatHistory, ChatMessageContent
|
||||
|
||||
if sys.version_info >= (3, 12):
|
||||
from typing import override # pragma: no cover
|
||||
else:
|
||||
from typing_extensions import override # pragma: no cover
|
||||
|
||||
"""
|
||||
The following sample demonstrates how to create a group chat orchestration with human
|
||||
in the loop. Human in the loop is achieved by overriding the default round robin manager
|
||||
to allow user input after the reviewer agent's message.
|
||||
|
||||
Think of the group chat manager as a state machine, with the following possible states:
|
||||
- Request for user message
|
||||
- Termination, after which the manager will try to filter a result from the conversation
|
||||
- Continuation, at which the manager will select the next agent to speak
|
||||
|
||||
This sample demonstrates the basic steps of customizing the group chat manager to enter
|
||||
the user input state, creating a human response function to get user input, and providing
|
||||
it to the group chat manager.
|
||||
|
||||
There are two agents in this orchestration: a writer and a reviewer. They work iteratively
|
||||
to refine a slogan for a new electric SUV.
|
||||
"""
|
||||
|
||||
|
||||
def get_agents() -> list[Agent]:
|
||||
"""Return a list of agents that will participate in the group style discussion.
|
||||
|
||||
Feel free to add or remove agents.
|
||||
"""
|
||||
credential = AzureCliCredential()
|
||||
|
||||
writer = ChatCompletionAgent(
|
||||
name="Writer",
|
||||
description="A content writer.",
|
||||
instructions=(
|
||||
"You are an excellent content writer. You create new content and edit contents based on the feedback."
|
||||
),
|
||||
service=AzureChatCompletion(credential=credential),
|
||||
)
|
||||
reviewer = ChatCompletionAgent(
|
||||
name="Reviewer",
|
||||
description="A content reviewer.",
|
||||
instructions=(
|
||||
"You are an excellent content reviewer. You review the content and provide feedback to the writer."
|
||||
),
|
||||
service=AzureChatCompletion(credential=credential),
|
||||
)
|
||||
|
||||
# The order of the agents in the list will be the order in which they will be picked by the round robin manager
|
||||
return [writer, reviewer]
|
||||
|
||||
|
||||
class CustomRoundRobinGroupChatManager(RoundRobinGroupChatManager):
|
||||
"""Custom round robin group chat manager to enable user input."""
|
||||
|
||||
@override
|
||||
async def should_request_user_input(self, chat_history: ChatHistory) -> BooleanResult:
|
||||
"""Override the default behavior to request user input after the reviewer's message.
|
||||
|
||||
The manager will check if input from human is needed after each agent message.
|
||||
"""
|
||||
if len(chat_history.messages) == 0:
|
||||
return BooleanResult(
|
||||
result=False,
|
||||
reason="No agents have spoken yet.",
|
||||
)
|
||||
last_message = chat_history.messages[-1]
|
||||
if last_message.name == "Reviewer":
|
||||
return BooleanResult(
|
||||
result=True,
|
||||
reason="User input is needed after the reviewer's message.",
|
||||
)
|
||||
|
||||
return BooleanResult(
|
||||
result=False,
|
||||
reason="User input is not needed if the last message is not from the reviewer.",
|
||||
)
|
||||
|
||||
|
||||
def agent_response_callback(message: ChatMessageContent) -> None:
|
||||
"""Observer function to print the messages from the agents."""
|
||||
print(f"**{message.name}**\n{message.content}")
|
||||
|
||||
|
||||
async def human_response_function(chat_histoy: ChatHistory) -> ChatMessageContent:
|
||||
"""Function to get user input."""
|
||||
user_input = input("User: ")
|
||||
return ChatMessageContent(role=AuthorRole.USER, content=user_input)
|
||||
|
||||
|
||||
async def main():
|
||||
"""Main function to run the agents."""
|
||||
# 1. Create a group chat orchestration with a round robin manager
|
||||
agents = get_agents()
|
||||
group_chat_orchestration = GroupChatOrchestration(
|
||||
members=agents,
|
||||
# max_rounds is odd, so that the writer gets the last round
|
||||
manager=CustomRoundRobinGroupChatManager(
|
||||
max_rounds=5,
|
||||
human_response_function=human_response_function,
|
||||
),
|
||||
agent_response_callback=agent_response_callback,
|
||||
)
|
||||
|
||||
# 2. Create a runtime and start it
|
||||
runtime = InProcessRuntime()
|
||||
runtime.start()
|
||||
|
||||
# 3. Invoke the orchestration with a task and the runtime
|
||||
orchestration_result = await group_chat_orchestration.invoke(
|
||||
task="Create a slogan for a new electric SUV that is affordable and fun to drive.",
|
||||
runtime=runtime,
|
||||
)
|
||||
|
||||
# 4. Wait for the results
|
||||
value = await orchestration_result.get()
|
||||
print(f"***** Result *****\n{value}")
|
||||
|
||||
# 5. Stop the runtime after the invocation is complete
|
||||
await runtime.stop_when_idle()
|
||||
|
||||
"""
|
||||
**Writer**
|
||||
"Electrify Your Journey: Affordable Adventure Awaits!"
|
||||
**Reviewer**
|
||||
Your slogan captures the essence of being both affordable and fun, which is great! However, you might want to ...
|
||||
User: I'd like to also make it rhyme
|
||||
**Writer**
|
||||
Sure! Here are a few rhyming slogan options for your electric SUV:
|
||||
|
||||
1. "Zoom Through the Streets, Feel the Beats!"
|
||||
2. "Charge and Drive, Feel the Jive!"
|
||||
3. "Electrify Your Ride, Let Fun Be Your Guide!"
|
||||
4. "Zoom in Style, Drive with a Smile!"
|
||||
|
||||
Let me know if you'd like more options or variations!
|
||||
**Reviewer**
|
||||
These rhyming slogans are creative and energetic! They effectively capture the fun aspect while promoting ...
|
||||
User: Please continue with the reviewer's suggestions
|
||||
**Writer**
|
||||
Absolutely! Let's refine and expand on the reviewer's suggestions for a more polished and appealing set of rhym...
|
||||
***** Result *****
|
||||
Absolutely! Let's refine and expand on the reviewer's suggestions for a more polished and appealing set of rhym...
|
||||
"""
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
+383
@@ -0,0 +1,383 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
import sys
|
||||
|
||||
from azure.core.credentials import TokenCredential
|
||||
from azure.identity import AzureCliCredential
|
||||
|
||||
from semantic_kernel.agents import Agent, ChatCompletionAgent, GroupChatOrchestration
|
||||
from semantic_kernel.agents.orchestration.group_chat import BooleanResult, GroupChatManager, MessageResult, StringResult
|
||||
from semantic_kernel.agents.runtime import InProcessRuntime
|
||||
from semantic_kernel.connectors.ai.chat_completion_client_base import ChatCompletionClientBase
|
||||
from semantic_kernel.connectors.ai.open_ai import AzureChatCompletion
|
||||
from semantic_kernel.connectors.ai.prompt_execution_settings import PromptExecutionSettings
|
||||
from semantic_kernel.contents import AuthorRole, ChatHistory, ChatMessageContent
|
||||
from semantic_kernel.functions import KernelArguments
|
||||
from semantic_kernel.kernel import Kernel
|
||||
from semantic_kernel.prompt_template import KernelPromptTemplate, PromptTemplateConfig
|
||||
|
||||
if sys.version_info >= (3, 12):
|
||||
from typing import override # pragma: no cover
|
||||
else:
|
||||
from typing_extensions import override # pragma: no cover
|
||||
|
||||
|
||||
"""
|
||||
The following sample demonstrates how to create a group chat orchestration with a
|
||||
group chat manager that uses a chat completion service to control the flow of the
|
||||
conversation.
|
||||
|
||||
This sample creates a group of agents that represent different perspectives and put
|
||||
them in a group chat to discuss a topic. The group chat manager is responsible for
|
||||
controlling the flow of the conversation, selecting the next agent to speak, and
|
||||
filtering the results of the conversation, which is a summary of the discussion.
|
||||
"""
|
||||
|
||||
|
||||
def get_agents(credential: TokenCredential) -> list[Agent]:
|
||||
"""Return a list of agents that will participate in the group style discussion.
|
||||
|
||||
Feel free to add or remove agents.
|
||||
"""
|
||||
|
||||
farmer = ChatCompletionAgent(
|
||||
name="Farmer",
|
||||
description="A rural farmer from Southeast Asia.",
|
||||
instructions=(
|
||||
"You're a farmer from Southeast Asia. "
|
||||
"Your life is deeply connected to land and family. "
|
||||
"You value tradition and sustainability. "
|
||||
"You are in a debate. Feel free to challenge the other participants with respect."
|
||||
),
|
||||
service=AzureChatCompletion(credential=credential),
|
||||
)
|
||||
developer = ChatCompletionAgent(
|
||||
name="Developer",
|
||||
description="An urban software developer from the United States.",
|
||||
instructions=(
|
||||
"You're a software developer from the United States. "
|
||||
"Your life is fast-paced and technology-driven. "
|
||||
"You value innovation, freedom, and work-life balance. "
|
||||
"You are in a debate. Feel free to challenge the other participants with respect."
|
||||
),
|
||||
service=AzureChatCompletion(credential=credential),
|
||||
)
|
||||
teacher = ChatCompletionAgent(
|
||||
name="Teacher",
|
||||
description="A retired history teacher from Eastern Europe",
|
||||
instructions=(
|
||||
"You're a retired history teacher from Eastern Europe. "
|
||||
"You bring historical and philosophical perspectives to discussions. "
|
||||
"You value legacy, learning, and cultural continuity. "
|
||||
"You are in a debate. Feel free to challenge the other participants with respect."
|
||||
),
|
||||
service=AzureChatCompletion(credential=credential),
|
||||
)
|
||||
activist = ChatCompletionAgent(
|
||||
name="Activist",
|
||||
description="A young activist from South America.",
|
||||
instructions=(
|
||||
"You're a young activist from South America. "
|
||||
"You focus on social justice, environmental rights, and generational change. "
|
||||
"You are in a debate. Feel free to challenge the other participants with respect."
|
||||
),
|
||||
service=AzureChatCompletion(credential=credential),
|
||||
)
|
||||
spiritual_leader = ChatCompletionAgent(
|
||||
name="SpiritualLeader",
|
||||
description="A spiritual leader from the Middle East.",
|
||||
instructions=(
|
||||
"You're a spiritual leader from the Middle East. "
|
||||
"You provide insights grounded in religion, morality, and community service. "
|
||||
"You are in a debate. Feel free to challenge the other participants with respect."
|
||||
),
|
||||
service=AzureChatCompletion(credential=credential),
|
||||
)
|
||||
artist = ChatCompletionAgent(
|
||||
name="Artist",
|
||||
description="An artist from Africa.",
|
||||
instructions=(
|
||||
"You're an artist from Africa. "
|
||||
"You view life through creative expression, storytelling, and collective memory. "
|
||||
"You are in a debate. Feel free to challenge the other participants with respect."
|
||||
),
|
||||
service=AzureChatCompletion(credential=credential),
|
||||
)
|
||||
immigrant = ChatCompletionAgent(
|
||||
name="Immigrant",
|
||||
description="An immigrant entrepreneur from Asia living in Canada.",
|
||||
instructions=(
|
||||
"You're an immigrant entrepreneur from Asia living in Canada. "
|
||||
"You balance trandition with adaption. "
|
||||
"You focus on family success, risk, and opportunity. "
|
||||
"You are in a debate. Feel free to challenge the other participants with respect."
|
||||
),
|
||||
service=AzureChatCompletion(credential=credential),
|
||||
)
|
||||
doctor = ChatCompletionAgent(
|
||||
name="Doctor",
|
||||
description="A doctor from Scandinavia.",
|
||||
instructions=(
|
||||
"You're a doctor from Scandinavia. "
|
||||
"Your perspective is shaped by public health, equity, and structured societal support. "
|
||||
"You are in a debate. Feel free to challenge the other participants with respect."
|
||||
),
|
||||
service=AzureChatCompletion(credential=credential),
|
||||
)
|
||||
|
||||
return [farmer, developer, teacher, activist, spiritual_leader, artist, immigrant, doctor]
|
||||
|
||||
|
||||
class ChatCompletionGroupChatManager(GroupChatManager):
|
||||
"""A simple chat completion base group chat manager.
|
||||
|
||||
This chat completion service requires a model that supports structured output.
|
||||
"""
|
||||
|
||||
service: ChatCompletionClientBase
|
||||
|
||||
topic: str
|
||||
|
||||
termination_prompt: str = (
|
||||
"You are mediator that guides a discussion on the topic of '{{$topic}}'. "
|
||||
"You need to determine if the discussion has reached a conclusion. "
|
||||
"If you would like to end the discussion, please respond with True. Otherwise, respond with False."
|
||||
)
|
||||
|
||||
selection_prompt: str = (
|
||||
"You are mediator that guides a discussion on the topic of '{{$topic}}'. "
|
||||
"You need to select the next participant to speak. "
|
||||
"Here are the names and descriptions of the participants: "
|
||||
"{{$participants}}\n"
|
||||
"Please respond with only the name of the participant you would like to select."
|
||||
)
|
||||
|
||||
result_filter_prompt: str = (
|
||||
"You are mediator that guides a discussion on the topic of '{{$topic}}'. "
|
||||
"You have just concluded the discussion. "
|
||||
"Please summarize the discussion and provide a closing statement."
|
||||
)
|
||||
|
||||
def __init__(self, topic: str, service: ChatCompletionClientBase, **kwargs) -> None:
|
||||
"""Initialize the group chat manager."""
|
||||
super().__init__(topic=topic, service=service, **kwargs)
|
||||
|
||||
async def _render_prompt(self, prompt: str, arguments: KernelArguments) -> str:
|
||||
"""Helper to render a prompt with arguments."""
|
||||
prompt_template_config = PromptTemplateConfig(template=prompt)
|
||||
prompt_template = KernelPromptTemplate(prompt_template_config=prompt_template_config)
|
||||
return await prompt_template.render(Kernel(), arguments=arguments)
|
||||
|
||||
@override
|
||||
async def should_request_user_input(self, chat_history: ChatHistory) -> BooleanResult:
|
||||
"""Provide concrete implementation for determining if user input is needed.
|
||||
|
||||
The manager will check if input from human is needed after each agent message.
|
||||
"""
|
||||
return BooleanResult(
|
||||
result=False,
|
||||
reason="This group chat manager does not require user input.",
|
||||
)
|
||||
|
||||
@override
|
||||
async def should_terminate(self, chat_history: ChatHistory) -> BooleanResult:
|
||||
"""Provide concrete implementation for determining if the discussion should end.
|
||||
|
||||
The manager will check if the conversation should be terminated after each agent message
|
||||
or human input (if applicable).
|
||||
"""
|
||||
should_terminate = await super().should_terminate(chat_history)
|
||||
if should_terminate.result:
|
||||
return should_terminate
|
||||
|
||||
chat_history.messages.insert(
|
||||
0,
|
||||
ChatMessageContent(
|
||||
role=AuthorRole.SYSTEM,
|
||||
content=await self._render_prompt(
|
||||
self.termination_prompt,
|
||||
KernelArguments(topic=self.topic),
|
||||
),
|
||||
),
|
||||
)
|
||||
chat_history.add_message(
|
||||
ChatMessageContent(role=AuthorRole.USER, content="Determine if the discussion should end."),
|
||||
)
|
||||
|
||||
response = await self.service.get_chat_message_content(
|
||||
chat_history,
|
||||
settings=PromptExecutionSettings(response_format=BooleanResult),
|
||||
)
|
||||
|
||||
termination_with_reason = BooleanResult.model_validate_json(response.content)
|
||||
|
||||
print("*********************")
|
||||
print(f"Should terminate: {termination_with_reason.result}\nReason: {termination_with_reason.reason}.")
|
||||
print("*********************")
|
||||
|
||||
return termination_with_reason
|
||||
|
||||
@override
|
||||
async def select_next_agent(
|
||||
self,
|
||||
chat_history: ChatHistory,
|
||||
participant_descriptions: dict[str, str],
|
||||
) -> StringResult:
|
||||
"""Provide concrete implementation for selecting the next agent to speak.
|
||||
|
||||
The manager will select the next agent to speak after each agent message
|
||||
or human input (if applicable) if the conversation is not terminated.
|
||||
"""
|
||||
chat_history.messages.insert(
|
||||
0,
|
||||
ChatMessageContent(
|
||||
role=AuthorRole.SYSTEM,
|
||||
content=await self._render_prompt(
|
||||
self.selection_prompt,
|
||||
KernelArguments(
|
||||
topic=self.topic,
|
||||
participants="\n".join([f"{k}: {v}" for k, v in participant_descriptions.items()]),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
chat_history.add_message(
|
||||
ChatMessageContent(role=AuthorRole.USER, content="Now select the next participant to speak."),
|
||||
)
|
||||
|
||||
response = await self.service.get_chat_message_content(
|
||||
chat_history,
|
||||
settings=PromptExecutionSettings(response_format=StringResult),
|
||||
)
|
||||
|
||||
participant_name_with_reason = StringResult.model_validate_json(response.content)
|
||||
|
||||
print("*********************")
|
||||
print(
|
||||
f"Next participant: {participant_name_with_reason.result}\nReason: {participant_name_with_reason.reason}."
|
||||
)
|
||||
print("*********************")
|
||||
|
||||
if participant_name_with_reason.result in participant_descriptions:
|
||||
return participant_name_with_reason
|
||||
|
||||
raise RuntimeError(f"Unknown participant selected: {response.content}.")
|
||||
|
||||
@override
|
||||
async def filter_results(
|
||||
self,
|
||||
chat_history: ChatHistory,
|
||||
) -> MessageResult:
|
||||
"""Provide concrete implementation for filtering the results of the discussion.
|
||||
|
||||
The manager will filter the results of the conversation after the conversation is terminated.
|
||||
"""
|
||||
if not chat_history.messages:
|
||||
raise RuntimeError("No messages in the chat history.")
|
||||
|
||||
chat_history.messages.insert(
|
||||
0,
|
||||
ChatMessageContent(
|
||||
role=AuthorRole.SYSTEM,
|
||||
content=await self._render_prompt(
|
||||
self.result_filter_prompt,
|
||||
KernelArguments(topic=self.topic),
|
||||
),
|
||||
),
|
||||
)
|
||||
chat_history.add_message(
|
||||
ChatMessageContent(role=AuthorRole.USER, content="Please summarize the discussion."),
|
||||
)
|
||||
|
||||
response = await self.service.get_chat_message_content(
|
||||
chat_history,
|
||||
settings=PromptExecutionSettings(response_format=StringResult),
|
||||
)
|
||||
string_with_reason = StringResult.model_validate_json(response.content)
|
||||
|
||||
return MessageResult(
|
||||
result=ChatMessageContent(role=AuthorRole.ASSISTANT, content=string_with_reason.result),
|
||||
reason=string_with_reason.reason,
|
||||
)
|
||||
|
||||
|
||||
def agent_response_callback(message: ChatMessageContent) -> None:
|
||||
"""Callback function to retrieve agent responses."""
|
||||
print(f"**{message.name}**\n{message.content}")
|
||||
|
||||
|
||||
async def main():
|
||||
"""Main function to run the agents."""
|
||||
# 1. Create a group chat orchestration with the custom group chat manager
|
||||
credential = AzureCliCredential()
|
||||
agents = get_agents(credential)
|
||||
group_chat_orchestration = GroupChatOrchestration(
|
||||
members=agents,
|
||||
manager=ChatCompletionGroupChatManager(
|
||||
topic="What does a good life mean to you personally?",
|
||||
service=AzureChatCompletion(credential=credential),
|
||||
max_rounds=10,
|
||||
),
|
||||
agent_response_callback=agent_response_callback,
|
||||
)
|
||||
|
||||
# 2. Create a runtime and start it
|
||||
runtime = InProcessRuntime()
|
||||
runtime.start()
|
||||
|
||||
# 3. Invoke the orchestration with a task and the runtime
|
||||
orchestration_result = await group_chat_orchestration.invoke(
|
||||
task="Please start the discussion.",
|
||||
runtime=runtime,
|
||||
)
|
||||
|
||||
# 4. Wait for the results
|
||||
value = await orchestration_result.get()
|
||||
print(value)
|
||||
|
||||
# 5. Stop the runtime after the invocation is complete
|
||||
await runtime.stop_when_idle()
|
||||
|
||||
"""
|
||||
Sample output:
|
||||
*********************
|
||||
Should terminate: False
|
||||
Reason: The discussion on what a good life means personally has not begun, meaning participants have not yet...
|
||||
*********************
|
||||
*********************
|
||||
Next participant: Farmer
|
||||
Reason: The Farmer from Southeast Asia can provide a perspective that highlights the importance of a connection...
|
||||
*********************
|
||||
**Farmer**
|
||||
Thank you for the opportunity to share my perspective. As a farmer from Southeast Asia, my life is intricately...
|
||||
*********************
|
||||
Should terminate: False
|
||||
Reason: The discussion has just started and only one perspective has been shared. There is room for further...
|
||||
*********************
|
||||
*********************
|
||||
Next participant: Developer
|
||||
Reason: To provide a contrast between rural and urban perspectives on what constitutes a good life, following the...
|
||||
*********************
|
||||
**Developer**
|
||||
Thank you for the opportunity to join the discussion. As a software developer living in a technology-driven...
|
||||
*********************
|
||||
Should terminate: False
|
||||
Reason: The discussion has just started with perspectives from both a farmer and a developer regarding the...
|
||||
*********************
|
||||
*********************
|
||||
Next participant: Teacher
|
||||
Reason: The Teacher, with their extensive experience and historical perspective, can provide valuable insights...
|
||||
*********************
|
||||
**Teacher**
|
||||
As a retired history teacher from Eastern Europe, I find it fascinating to explore how the threads of history,...
|
||||
*********************
|
||||
Should terminate: True
|
||||
Reason: The participants, representing diverse perspectives—a farmer, a developer, and a teacher—have each shared...
|
||||
*********************
|
||||
Our discussion on what constitutes a good life revolved around key perspectives from a farmer, a developer, and a...
|
||||
"""
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
+223
@@ -0,0 +1,223 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
|
||||
from azure.identity import AzureCliCredential
|
||||
|
||||
from semantic_kernel.agents import Agent, ChatCompletionAgent, HandoffOrchestration, OrchestrationHandoffs
|
||||
from semantic_kernel.agents.runtime import InProcessRuntime
|
||||
from semantic_kernel.connectors.ai.open_ai import AzureChatCompletion
|
||||
from semantic_kernel.contents import AuthorRole, ChatMessageContent, FunctionCallContent, FunctionResultContent
|
||||
from semantic_kernel.functions import kernel_function
|
||||
|
||||
"""
|
||||
The following sample demonstrates how to create a handoff orchestration that represents
|
||||
a customer support triage system. The orchestration consists of 4 agents, each specialized
|
||||
in a different area of customer support: triage, refunds, order status, and order returns.
|
||||
|
||||
Depending on the customer's request, agents can hand off the conversation to the appropriate
|
||||
agent.
|
||||
|
||||
Human in the loop is achieved via a callback function similar to the one used in group chat
|
||||
orchestration. Except that in the handoff orchestration, all agents have access to the
|
||||
human response function, whereas in the group chat orchestration, only the manager has access
|
||||
to the human response function.
|
||||
|
||||
This sample demonstrates the basic steps of creating and starting a runtime, creating
|
||||
a handoff orchestration, invoking the orchestration, and finally waiting for the results.
|
||||
"""
|
||||
|
||||
|
||||
class OrderStatusPlugin:
|
||||
@kernel_function
|
||||
def check_order_status(self, order_id: str) -> str:
|
||||
"""Check the status of an order."""
|
||||
# Simulate checking the order status
|
||||
return f"Order {order_id} is shipped and will arrive in 2-3 days."
|
||||
|
||||
|
||||
class OrderRefundPlugin:
|
||||
@kernel_function
|
||||
def process_refund(self, order_id: str, reason: str) -> str:
|
||||
"""Process a refund for an order."""
|
||||
# Simulate processing a refund
|
||||
print(f"Processing refund for order {order_id} due to: {reason}")
|
||||
return f"Refund for order {order_id} has been processed successfully."
|
||||
|
||||
|
||||
class OrderReturnPlugin:
|
||||
@kernel_function
|
||||
def process_return(self, order_id: str, reason: str) -> str:
|
||||
"""Process a return for an order."""
|
||||
# Simulate processing a return
|
||||
print(f"Processing return for order {order_id} due to: {reason}")
|
||||
return f"Return for order {order_id} has been processed successfully."
|
||||
|
||||
|
||||
def get_agents() -> tuple[list[Agent], OrchestrationHandoffs]:
|
||||
"""Return a list of agents that will participate in the Handoff orchestration and the handoff relationships.
|
||||
|
||||
Feel free to add or remove agents and handoff connections.
|
||||
"""
|
||||
credential = AzureCliCredential()
|
||||
|
||||
support_agent = ChatCompletionAgent(
|
||||
name="TriageAgent",
|
||||
description="A customer support agent that triages issues.",
|
||||
instructions="Handle customer requests.",
|
||||
service=AzureChatCompletion(credential=credential),
|
||||
)
|
||||
|
||||
refund_agent = ChatCompletionAgent(
|
||||
name="RefundAgent",
|
||||
description="A customer support agent that handles refunds.",
|
||||
instructions="Handle refund requests.",
|
||||
service=AzureChatCompletion(credential=credential),
|
||||
plugins=[OrderRefundPlugin()],
|
||||
)
|
||||
|
||||
order_status_agent = ChatCompletionAgent(
|
||||
name="OrderStatusAgent",
|
||||
description="A customer support agent that checks order status.",
|
||||
instructions="Handle order status requests.",
|
||||
service=AzureChatCompletion(credential=credential),
|
||||
plugins=[OrderStatusPlugin()],
|
||||
)
|
||||
|
||||
order_return_agent = ChatCompletionAgent(
|
||||
name="OrderReturnAgent",
|
||||
description="A customer support agent that handles order returns.",
|
||||
instructions="Handle order return requests.",
|
||||
service=AzureChatCompletion(credential=credential),
|
||||
plugins=[OrderReturnPlugin()],
|
||||
)
|
||||
|
||||
# Define the handoff relationships between agents
|
||||
handoffs = (
|
||||
OrchestrationHandoffs()
|
||||
.add_many(
|
||||
source_agent=support_agent.name,
|
||||
target_agents={
|
||||
refund_agent.name: "Transfer to this agent if the issue is refund related",
|
||||
order_status_agent.name: "Transfer to this agent if the issue is order status related",
|
||||
order_return_agent.name: "Transfer to this agent if the issue is order return related",
|
||||
},
|
||||
)
|
||||
.add(
|
||||
source_agent=refund_agent.name,
|
||||
target_agent=support_agent.name,
|
||||
description="Transfer to this agent if the issue is not refund related",
|
||||
)
|
||||
.add(
|
||||
source_agent=order_status_agent.name,
|
||||
target_agent=support_agent.name,
|
||||
description="Transfer to this agent if the issue is not order status related",
|
||||
)
|
||||
.add(
|
||||
source_agent=order_return_agent.name,
|
||||
target_agent=support_agent.name,
|
||||
description="Transfer to this agent if the issue is not order return related",
|
||||
)
|
||||
)
|
||||
|
||||
return [support_agent, refund_agent, order_status_agent, order_return_agent], handoffs
|
||||
|
||||
|
||||
def agent_response_callback(message: ChatMessageContent) -> None:
|
||||
"""Observer function to print the messages from the agents.
|
||||
|
||||
Please note that this function is called whenever the agent generates a response,
|
||||
including the internal processing messages (such as tool calls) that are not visible
|
||||
to other agents in the orchestration.
|
||||
"""
|
||||
print(f"{message.name}: {message.content}")
|
||||
for item in message.items:
|
||||
if isinstance(item, FunctionCallContent):
|
||||
print(f"Calling '{item.name}' with arguments '{item.arguments}'")
|
||||
if isinstance(item, FunctionResultContent):
|
||||
print(f"Result from '{item.name}' is '{item.result}'")
|
||||
|
||||
|
||||
def human_response_function() -> ChatMessageContent:
|
||||
"""Observer function to print the messages from the agents."""
|
||||
user_input = input("User: ")
|
||||
return ChatMessageContent(role=AuthorRole.USER, content=user_input)
|
||||
|
||||
|
||||
async def main():
|
||||
"""Main function to run the agents."""
|
||||
# 1. Create a handoff orchestration with multiple agents
|
||||
agents, handoffs = get_agents()
|
||||
handoff_orchestration = HandoffOrchestration(
|
||||
members=agents,
|
||||
handoffs=handoffs,
|
||||
agent_response_callback=agent_response_callback,
|
||||
human_response_function=human_response_function,
|
||||
)
|
||||
|
||||
# 2. Create a runtime and start it
|
||||
runtime = InProcessRuntime()
|
||||
runtime.start()
|
||||
|
||||
# 3. Invoke the orchestration with a task and the runtime
|
||||
orchestration_result = await handoff_orchestration.invoke(
|
||||
task="Greet the customer who is reaching out for support.",
|
||||
runtime=runtime,
|
||||
)
|
||||
|
||||
# 4. Wait for the results
|
||||
value = await orchestration_result.get()
|
||||
print(value)
|
||||
|
||||
# 5. Stop the runtime after the invocation is complete
|
||||
await runtime.stop_when_idle()
|
||||
|
||||
"""
|
||||
Sample output:
|
||||
TriageAgent: Hello! Thank you for reaching out for support. How can I assist you today?
|
||||
User: I'd like to track the status of my order
|
||||
TriageAgent:
|
||||
Calling 'Handoff-transfer_to_OrderStatusAgent' with arguments '{}'
|
||||
TriageAgent:
|
||||
Result from 'Handoff-transfer_to_OrderStatusAgent' is 'None'
|
||||
OrderStatusAgent: Could you please provide me with your order ID so I can check the status for you?
|
||||
User: My order ID is 123
|
||||
OrderStatusAgent:
|
||||
Calling 'OrderStatusPlugin-check_order_status' with arguments '{"order_id":"123"}'
|
||||
OrderStatusAgent:
|
||||
Result from 'OrderStatusPlugin-check_order_status' is 'Order 123 is shipped and will arrive in 2-3 days.'
|
||||
OrderStatusAgent: Your order with ID 123 has been shipped and is expected to arrive in 2-3 days. If you have any
|
||||
more questions, feel free to ask!
|
||||
User: I want to return another order of mine
|
||||
OrderStatusAgent: I can help you with that. Could you please provide me with the order ID of the order you want
|
||||
to return?
|
||||
User: Order ID 321
|
||||
OrderStatusAgent:
|
||||
Calling 'Handoff-transfer_to_TriageAgent' with arguments '{}'
|
||||
OrderStatusAgent:
|
||||
Result from 'Handoff-transfer_to_TriageAgent' is 'None'
|
||||
TriageAgent:
|
||||
Calling 'Handoff-transfer_to_OrderReturnAgent' with arguments '{}'
|
||||
TriageAgent:
|
||||
Result from 'Handoff-transfer_to_OrderReturnAgent' is 'None'
|
||||
OrderReturnAgent: Could you please provide me with the reason for the return for order ID 321?
|
||||
User: Broken item
|
||||
Processing return for order 321 due to: Broken item
|
||||
OrderReturnAgent:
|
||||
Calling 'OrderReturnPlugin-process_return' with arguments '{"order_id":"321","reason":"Broken item"}'
|
||||
OrderReturnAgent:
|
||||
Result from 'OrderReturnPlugin-process_return' is 'Return for order 321 has been processed successfully.'
|
||||
OrderReturnAgent: The return for order ID 321 has been processed successfully due to a broken item. If you need
|
||||
further assistance or have any other questions, feel free to let me know!
|
||||
User: No, bye
|
||||
Task is completed with summary: Processed the return request for order ID 321 due to a broken item.
|
||||
OrderReturnAgent:
|
||||
Calling 'Handoff-complete_task' with arguments '{"task_summary":"Processed the return request for order ID 321
|
||||
due to a broken item."}'
|
||||
OrderReturnAgent:
|
||||
Result from 'Handoff-complete_task' is 'None'
|
||||
"""
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
+202
@@ -0,0 +1,202 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
from enum import Enum
|
||||
|
||||
from azure.identity import AzureCliCredential
|
||||
from pydantic import BaseModel
|
||||
|
||||
from semantic_kernel.agents import Agent, ChatCompletionAgent, HandoffOrchestration, OrchestrationHandoffs
|
||||
from semantic_kernel.agents.runtime import InProcessRuntime
|
||||
from semantic_kernel.connectors.ai.open_ai import AzureChatCompletion
|
||||
from semantic_kernel.contents import AuthorRole, ChatMessageContent
|
||||
from semantic_kernel.functions import kernel_function
|
||||
|
||||
"""
|
||||
The following sample demonstrates how to create a handoff orchestration that can triage
|
||||
GitHub issues based on their content. The orchestration consists of 3 agents, each
|
||||
specialized in a different area.
|
||||
|
||||
The input to the orchestration is not longer a string or a chat message, but a Pydantic
|
||||
model (i.e. structured inputs). The model will get transformed into a chat message before
|
||||
being passed to the agents. This allows the orchestration to become more flexible and
|
||||
easier reusable.
|
||||
|
||||
This sample demonstrates the basic steps of creating and starting a runtime, creating
|
||||
a handoff orchestration, invoking the orchestration, and finally waiting for the results.
|
||||
"""
|
||||
|
||||
|
||||
class GitHubLabels(Enum):
|
||||
"""Enum representing GitHub labels."""
|
||||
|
||||
PYTHON = "python"
|
||||
DOTNET = ".NET"
|
||||
BUG = "bug"
|
||||
ENHANCEMENT = "enhancement"
|
||||
QUESTION = "question"
|
||||
VECTORSTORE = "vectorstore"
|
||||
AGENT = "agent"
|
||||
|
||||
|
||||
class GithubIssue(BaseModel):
|
||||
"""Model representing a GitHub issue."""
|
||||
|
||||
id: str
|
||||
title: str
|
||||
body: str
|
||||
labels: list[str] = []
|
||||
|
||||
|
||||
class Plan(BaseModel):
|
||||
"""Model representing a plan for resolving a GitHub issue."""
|
||||
|
||||
tasks: list[str]
|
||||
|
||||
|
||||
class GithubPlugin:
|
||||
"""Plugin for GitHub related operations."""
|
||||
|
||||
@kernel_function
|
||||
async def add_labels(self, issue_id: str, labels: list[GitHubLabels]) -> None:
|
||||
"""Add labels to a GitHub issue."""
|
||||
await asyncio.sleep(1) # Simulate network delay
|
||||
print(f"Adding labels {labels} to issue {issue_id}")
|
||||
|
||||
@kernel_function(description="Create a plan to resolve the issue.")
|
||||
async def create_plan(self, issue_id: str, plan: Plan) -> None:
|
||||
"""Create tasks for a GitHub issue."""
|
||||
await asyncio.sleep(1) # Simulate network delay
|
||||
print(f"Creating plan for issue {issue_id} with tasks:\n{plan.model_dump_json(indent=2)}")
|
||||
|
||||
|
||||
def get_agents() -> tuple[list[Agent], OrchestrationHandoffs]:
|
||||
"""Return a list of agents that will participate in the Handoff orchestration and the handoff relationships.
|
||||
|
||||
Feel free to add or remove agents and handoff connections.
|
||||
"""
|
||||
credential = AzureCliCredential()
|
||||
|
||||
triage_agent = ChatCompletionAgent(
|
||||
name="TriageAgent",
|
||||
description="An agent that triages GitHub issues",
|
||||
instructions="Given a GitHub issue, triage it.",
|
||||
service=AzureChatCompletion(credential=credential),
|
||||
)
|
||||
python_agent = ChatCompletionAgent(
|
||||
name="PythonAgent",
|
||||
description="An agent that handles Python related issues",
|
||||
instructions="You are an agent that handles Python related GitHub issues.",
|
||||
service=AzureChatCompletion(credential=credential),
|
||||
plugins=[GithubPlugin()],
|
||||
)
|
||||
dotnet_agent = ChatCompletionAgent(
|
||||
name="DotNetAgent",
|
||||
description="An agent that handles .NET related issues",
|
||||
instructions="You are an agent that handles .NET related GitHub issues.",
|
||||
service=AzureChatCompletion(credential=credential),
|
||||
plugins=[GithubPlugin()],
|
||||
)
|
||||
|
||||
# Define the handoff relationships between agents
|
||||
handoffs = {
|
||||
triage_agent.name: {
|
||||
python_agent.name: "Transfer to this agent if the issue is Python related",
|
||||
dotnet_agent.name: "Transfer to this agent if the issue is .NET related",
|
||||
},
|
||||
}
|
||||
|
||||
return [triage_agent, python_agent, dotnet_agent], handoffs
|
||||
|
||||
|
||||
GithubIssueSample = GithubIssue(
|
||||
id="12345",
|
||||
title=(
|
||||
"Bug: SQLite Error 1: 'ambiguous column name:' when including VectorStoreRecordKey in "
|
||||
"VectorSearchOptions.Filter"
|
||||
),
|
||||
body=(
|
||||
"Describe the bug"
|
||||
"When using column names marked as [VectorStoreRecordData(IsFilterable = true)] in "
|
||||
"VectorSearchOptions.Filter, the query runs correctly."
|
||||
"However, using the column name marked as [VectorStoreRecordKey] in VectorSearchOptions.Filter, the query "
|
||||
"throws exception 'SQLite Error 1: ambiguous column name: StartUTC"
|
||||
""
|
||||
"To Reproduce"
|
||||
"Add a filter for the column marked [VectorStoreRecordKey]. Since that same column exists in both the "
|
||||
"vec_TestTable and TestTable, the data for both columns cannot be returned."
|
||||
""
|
||||
"Expected behavior"
|
||||
"The query should explicitly list the vec_TestTable column names to retrieve and should omit the "
|
||||
"[VectorStoreRecordKey] column since it will be included in the primary TestTable columns."
|
||||
""
|
||||
"Platform"
|
||||
""
|
||||
"Microsoft.SemanticKernel.Connectors.Sqlite v1.46.0-preview"
|
||||
"Additional context"
|
||||
"Normal DBContext logging shows only normal context queries. Queries run by VectorizedSearchAsync() don't "
|
||||
"appear in those logs and I could not find a way to enable logging in semantic search so that I could "
|
||||
"actually see the exact query that is failing. It would have been very useful to see the failing semantic "
|
||||
"query."
|
||||
),
|
||||
labels=[],
|
||||
)
|
||||
|
||||
|
||||
# The default input transform will attempt to serialize an object into a string by using
|
||||
# `json.dump()`. However, an object of a Pydantic model type cannot be directly serialize
|
||||
# by `json.dump()`. Thus, we will need a custom transform.
|
||||
def custom_input_transform(input_message: GithubIssue) -> ChatMessageContent:
|
||||
return ChatMessageContent(role=AuthorRole.USER, content=input_message.model_dump_json())
|
||||
|
||||
|
||||
async def main():
|
||||
"""Main function to run the agents."""
|
||||
# 1. Create a handoff orchestration with multiple agents
|
||||
# and a custom input transform.
|
||||
# To enable structured input, you must specify the input transform
|
||||
# and the generic types for the orchestration,
|
||||
agents, handoffs = get_agents()
|
||||
handoff_orchestration = HandoffOrchestration[GithubIssue, ChatMessageContent](
|
||||
members=agents,
|
||||
handoffs=handoffs,
|
||||
input_transform=custom_input_transform,
|
||||
)
|
||||
|
||||
# 2. Create a runtime and start it
|
||||
runtime = InProcessRuntime()
|
||||
runtime.start()
|
||||
|
||||
# 3. Invoke the orchestration with a task and the runtime
|
||||
orchestration_result = await handoff_orchestration.invoke(
|
||||
task=GithubIssueSample,
|
||||
runtime=runtime,
|
||||
)
|
||||
|
||||
# 4. Wait for the results
|
||||
value = await orchestration_result.get(timeout=100)
|
||||
print(value)
|
||||
|
||||
# 5. Stop the runtime when idle
|
||||
await runtime.stop_when_idle()
|
||||
|
||||
"""
|
||||
Sample output:
|
||||
Adding labels [<GitHubLabels.BUG: 'bug'>, <GitHubLabels.DOTNET: '.NET'>, <GitHubLabels.VECTORSTORE: 'vectorstore'>]
|
||||
to issue 12345
|
||||
Creating plan for issue 12345 with tasks:
|
||||
{
|
||||
"tasks": [
|
||||
"Investigate the issue to confirm the ambiguity in the SQL query when using VectorStoreRecordKey in filters.",
|
||||
"Modify the query generation logic to explicitly list column names for vec_TestTable and prevent ambiguity.",
|
||||
"Test the solution to ensure VectorStoreRecordKey can be used in filters without causing SQLite errors.",
|
||||
"Update documentation to provide guidance on using VectorStoreRecordKey in filters to avoid similar issues.",
|
||||
"Consider adding logging capability to track semantic search queries for easier debugging in the future."
|
||||
]
|
||||
}
|
||||
Task is completed with summary: No handoff agent name provided and no human response function set. Ending task.
|
||||
"""
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
+240
@@ -0,0 +1,240 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
|
||||
from azure.identity import AzureCliCredential
|
||||
|
||||
from semantic_kernel.agents import Agent, ChatCompletionAgent, HandoffOrchestration, OrchestrationHandoffs
|
||||
from semantic_kernel.agents.runtime import InProcessRuntime
|
||||
from semantic_kernel.connectors.ai.open_ai import AzureChatCompletion
|
||||
from semantic_kernel.contents import (
|
||||
AuthorRole,
|
||||
ChatMessageContent,
|
||||
FunctionCallContent,
|
||||
FunctionResultContent,
|
||||
StreamingChatMessageContent,
|
||||
)
|
||||
from semantic_kernel.functions import kernel_function
|
||||
|
||||
"""
|
||||
The following sample demonstrates how to create a handoff orchestration that represents
|
||||
a customer support triage system. The orchestration consists of 4 agents, each specialized
|
||||
in a different area of customer support: triage, refunds, order status, and order returns.
|
||||
|
||||
The orchestration is configured with a streaming agent response callback that prints the
|
||||
messages from the agents as they are generated.
|
||||
|
||||
Depending on the customer's request, agents can hand off the conversation to the appropriate
|
||||
agent.
|
||||
|
||||
Human in the loop is achieved via a callback function similar to the one used in group chat
|
||||
orchestration. Except that in the handoff orchestration, all agents have access to the
|
||||
human response function, whereas in the group chat orchestration, only the manager has access
|
||||
to the human response function.
|
||||
|
||||
This sample demonstrates the basic steps of creating and starting a runtime, creating
|
||||
a handoff orchestration, invoking the orchestration, and finally waiting for the results.
|
||||
"""
|
||||
|
||||
|
||||
class OrderStatusPlugin:
|
||||
@kernel_function
|
||||
def check_order_status(self, order_id: str) -> str:
|
||||
"""Check the status of an order."""
|
||||
# Simulate checking the order status
|
||||
return f"Order {order_id} is shipped and will arrive in 2-3 days."
|
||||
|
||||
|
||||
class OrderRefundPlugin:
|
||||
@kernel_function
|
||||
def process_refund(self, order_id: str, reason: str) -> str:
|
||||
"""Process a refund for an order."""
|
||||
# Simulate processing a refund
|
||||
print(f"Processing refund for order {order_id} due to: {reason}")
|
||||
return f"Refund for order {order_id} has been processed successfully."
|
||||
|
||||
|
||||
class OrderReturnPlugin:
|
||||
@kernel_function
|
||||
def process_return(self, order_id: str, reason: str) -> str:
|
||||
"""Process a return for an order."""
|
||||
# Simulate processing a return
|
||||
print(f"Processing return for order {order_id} due to: {reason}")
|
||||
return f"Return for order {order_id} has been processed successfully."
|
||||
|
||||
|
||||
def get_agents() -> tuple[list[Agent], OrchestrationHandoffs]:
|
||||
"""Return a list of agents that will participate in the Handoff orchestration and the handoff relationships.
|
||||
|
||||
Feel free to add or remove agents and handoff connections.
|
||||
"""
|
||||
credential = AzureCliCredential()
|
||||
|
||||
support_agent = ChatCompletionAgent(
|
||||
name="TriageAgent",
|
||||
description="A customer support agent that triages issues.",
|
||||
instructions="Handle customer requests.",
|
||||
service=AzureChatCompletion(credential=credential),
|
||||
)
|
||||
|
||||
refund_agent = ChatCompletionAgent(
|
||||
name="RefundAgent",
|
||||
description="A customer support agent that handles refunds.",
|
||||
instructions="Handle refund requests.",
|
||||
service=AzureChatCompletion(credential=credential),
|
||||
plugins=[OrderRefundPlugin()],
|
||||
)
|
||||
|
||||
order_status_agent = ChatCompletionAgent(
|
||||
name="OrderStatusAgent",
|
||||
description="A customer support agent that checks order status.",
|
||||
instructions="Handle order status requests.",
|
||||
service=AzureChatCompletion(credential=credential),
|
||||
plugins=[OrderStatusPlugin()],
|
||||
)
|
||||
|
||||
order_return_agent = ChatCompletionAgent(
|
||||
name="OrderReturnAgent",
|
||||
description="A customer support agent that handles order returns.",
|
||||
instructions="Handle order return requests.",
|
||||
service=AzureChatCompletion(credential=credential),
|
||||
plugins=[OrderReturnPlugin()],
|
||||
)
|
||||
|
||||
# Define the handoff relationships between agents
|
||||
handoffs = (
|
||||
OrchestrationHandoffs()
|
||||
.add_many(
|
||||
source_agent=support_agent.name,
|
||||
target_agents={
|
||||
refund_agent.name: "Transfer to this agent if the issue is refund related",
|
||||
order_status_agent.name: "Transfer to this agent if the issue is order status related",
|
||||
order_return_agent.name: "Transfer to this agent if the issue is order return related",
|
||||
},
|
||||
)
|
||||
.add(
|
||||
source_agent=refund_agent.name,
|
||||
target_agent=support_agent.name,
|
||||
description="Transfer to this agent if the issue is not refund related",
|
||||
)
|
||||
.add(
|
||||
source_agent=order_status_agent.name,
|
||||
target_agent=support_agent.name,
|
||||
description="Transfer to this agent if the issue is not order status related",
|
||||
)
|
||||
.add(
|
||||
source_agent=order_return_agent.name,
|
||||
target_agent=support_agent.name,
|
||||
description="Transfer to this agent if the issue is not order return related",
|
||||
)
|
||||
)
|
||||
|
||||
return [support_agent, refund_agent, order_status_agent, order_return_agent], handoffs
|
||||
|
||||
|
||||
# Flag to indicate if a new message is being received
|
||||
is_new_message = True
|
||||
|
||||
|
||||
def streaming_agent_response_callback(message: StreamingChatMessageContent, is_final: bool) -> None:
|
||||
"""Observer function to print the messages from the agents.
|
||||
|
||||
Please note that this function is called whenever the agent generates a response,
|
||||
including the internal processing messages (such as tool calls) that are not visible
|
||||
to other agents in the orchestration.
|
||||
|
||||
In streaming mode, the FunctionCallContent and FunctionResultContent are provided as a
|
||||
complete message.
|
||||
|
||||
Args:
|
||||
message (StreamingChatMessageContent): The streaming message content from the agent.
|
||||
is_final (bool): Indicates if this is the final part of the message.
|
||||
"""
|
||||
global is_new_message
|
||||
if is_new_message:
|
||||
print(f"{message.name}: ", end="", flush=True)
|
||||
is_new_message = False
|
||||
print(message.content, end="", flush=True)
|
||||
|
||||
for item in message.items:
|
||||
if isinstance(item, FunctionCallContent):
|
||||
print(f"Calling '{item.name}' with arguments '{item.arguments}'", end="", flush=True)
|
||||
if isinstance(item, FunctionResultContent):
|
||||
print(f"Result from '{item.name}' is '{item.result}'", end="", flush=True)
|
||||
|
||||
if is_final:
|
||||
print()
|
||||
is_new_message = True
|
||||
|
||||
|
||||
def human_response_function() -> ChatMessageContent:
|
||||
"""Observer function to print the messages from the agents."""
|
||||
user_input = input("User: ")
|
||||
return ChatMessageContent(role=AuthorRole.USER, content=user_input)
|
||||
|
||||
|
||||
async def main():
|
||||
"""Main function to run the agents."""
|
||||
# 1. Create a handoff orchestration with multiple agents
|
||||
agents, handoffs = get_agents()
|
||||
handoff_orchestration = HandoffOrchestration(
|
||||
members=agents,
|
||||
handoffs=handoffs,
|
||||
streaming_agent_response_callback=streaming_agent_response_callback,
|
||||
human_response_function=human_response_function,
|
||||
)
|
||||
|
||||
# 2. Create a runtime and start it
|
||||
runtime = InProcessRuntime()
|
||||
runtime.start()
|
||||
|
||||
# 3. Invoke the orchestration with a task and the runtime
|
||||
orchestration_result = await handoff_orchestration.invoke(
|
||||
task="Greet the customer who is reaching out for support.",
|
||||
runtime=runtime,
|
||||
)
|
||||
|
||||
# 4. Wait for the results
|
||||
value = await orchestration_result.get()
|
||||
print(value)
|
||||
|
||||
# 5. Stop the runtime after the invocation is complete
|
||||
await runtime.stop_when_idle()
|
||||
|
||||
"""
|
||||
Sample output:
|
||||
TriageAgent: Hello! Thank you for reaching out for support. How can I assist you today?
|
||||
User: I'd like to track the status of my order
|
||||
TriageAgent: Calling 'Handoff-transfer_to_OrderStatusAgent' with arguments '{}'
|
||||
TriageAgent: Result from 'Handoff-transfer_to_OrderStatusAgent' is 'None'
|
||||
OrderStatusAgent: Could you please provide me with your order ID? This will help me check the status of your order.
|
||||
User: My order ID is 123
|
||||
OrderStatusAgent: Calling 'OrderStatusPlugin-check_order_status' with arguments '{"order_id":"123"}'
|
||||
OrderStatusAgent: Result from 'OrderStatusPlugin-check_order_status' is 'Order 123 is shipped and will arrive in
|
||||
2-3 days.'
|
||||
OrderStatusAgent: Your order with ID 123 has been shipped and is expected to arrive in 2-3 days. If you have any
|
||||
more questions, feel free to ask!
|
||||
User: I want to return another order of mine
|
||||
OrderStatusAgent: Calling 'Handoff-transfer_to_TriageAgent' with arguments '{}'
|
||||
OrderStatusAgent: Result from 'Handoff-transfer_to_TriageAgent' is 'None'
|
||||
TriageAgent: Calling 'Handoff-transfer_to_OrderReturnAgent' with arguments '{}'
|
||||
TriageAgent: Result from 'Handoff-transfer_to_OrderReturnAgent' is 'None'
|
||||
OrderReturnAgent: Could you please provide me with the order ID for the order you would like to return, as well
|
||||
as the reason for the return?
|
||||
User: Order ID 321
|
||||
OrderReturnAgent: What is the reason for returning order ID 321?
|
||||
User: Broken item
|
||||
Processing return for order 321 due to: Broken item
|
||||
OrderReturnAgent: Calling 'OrderReturnPlugin-process_return' with arguments '{"order_id":"321","reason":"Broken
|
||||
item"}'
|
||||
OrderReturnAgent: Result from 'OrderReturnPlugin-process_return' is 'Return for order 321 has been processed
|
||||
successfully.'
|
||||
OrderReturnAgent: Task is completed with summary: Processed return for order ID 321 due to a broken item.
|
||||
Calling 'Handoff-complete_task' with arguments '{"task_summary":"Processed return for order ID 321 due to a
|
||||
broken item."}'
|
||||
OrderReturnAgent: Result from 'Handoff-complete_task' is 'None'
|
||||
"""
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
+272
@@ -0,0 +1,272 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
|
||||
from azure.ai.projects.aio import AIProjectClient
|
||||
from azure.identity import AzureCliCredential
|
||||
from azure.identity.aio import AzureCliCredential as AsyncAzureCliCredential
|
||||
from opentelemetry.trace import NoOpTracerProvider
|
||||
|
||||
from samples.getting_started_with_agents.multi_agent_orchestration.observability import enable_observability
|
||||
from semantic_kernel.agents import (
|
||||
Agent,
|
||||
AzureAIAgent,
|
||||
AzureAIAgentSettings,
|
||||
AzureAssistantAgent,
|
||||
ChatCompletionAgent,
|
||||
HandoffOrchestration,
|
||||
OrchestrationHandoffs,
|
||||
)
|
||||
from semantic_kernel.agents.open_ai.azure_responses_agent import AzureResponsesAgent
|
||||
from semantic_kernel.agents.runtime import InProcessRuntime
|
||||
from semantic_kernel.connectors.ai.open_ai import AzureChatCompletion, AzureOpenAISettings
|
||||
from semantic_kernel.contents import AuthorRole, ChatMessageContent, FunctionCallContent, FunctionResultContent
|
||||
from semantic_kernel.functions import kernel_function
|
||||
|
||||
"""
|
||||
The following sample replicates sample "step4_handoff.py" but uses different agent types.
|
||||
The following agent types are used:
|
||||
- ChatCompletionAgent: A Chat Completion agent that is backed by an Azure OpenAI service.
|
||||
- AzureAssistantAgent: An Azure Assistant agent that is backed by the Azure OpenAI Assistant API.
|
||||
- AzureAIAgent: An Azure AI agent that is backed by the Azure AI Agent (a.k.a Foundry Agent) service.
|
||||
- OpenAIResponsesAgent: An Azure Responses agent that is backed by the Azure OpenAI Responses API.
|
||||
|
||||
The Handoff orchestration doesn't support the following agent types:
|
||||
- BedrockAgent
|
||||
- CopilotStudioAgent
|
||||
"""
|
||||
|
||||
azure_credential: AsyncAzureCliCredential | None = None
|
||||
azure_ai_agent_client: AIProjectClient | None = None
|
||||
|
||||
|
||||
async def init_azure_ai_agent_clients():
|
||||
global azure_credential, azure_ai_agent_client
|
||||
azure_credential = AsyncAzureCliCredential()
|
||||
azure_ai_agent_client = AzureAIAgent.create_client(credential=azure_credential)
|
||||
|
||||
|
||||
async def close_azure_ai_agent_clients():
|
||||
global azure_credential, azure_ai_agent_client
|
||||
if azure_credential:
|
||||
await azure_credential.close()
|
||||
if azure_ai_agent_client:
|
||||
await azure_ai_agent_client.close()
|
||||
|
||||
|
||||
class OrderStatusPlugin:
|
||||
@kernel_function
|
||||
def check_order_status(self, order_id: str) -> str:
|
||||
"""Check the status of an order."""
|
||||
# Simulate checking the order status
|
||||
return f"Order {order_id} is shipped and will arrive in 2-3 days."
|
||||
|
||||
|
||||
class OrderRefundPlugin:
|
||||
@kernel_function
|
||||
def process_refund(self, order_id: str, reason: str) -> str:
|
||||
"""Process a refund for an order."""
|
||||
# Simulate processing a refund
|
||||
print(f"Processing refund for order {order_id} due to: {reason}")
|
||||
return f"Refund for order {order_id} has been processed successfully."
|
||||
|
||||
|
||||
class OrderReturnPlugin:
|
||||
@kernel_function
|
||||
def process_return(self, order_id: str, reason: str) -> str:
|
||||
"""Process a return for an order."""
|
||||
# Simulate processing a return
|
||||
print(f"Processing return for order {order_id} due to: {reason}")
|
||||
return f"Return for order {order_id} has been processed successfully."
|
||||
|
||||
|
||||
async def get_agents() -> tuple[list[Agent], OrchestrationHandoffs]:
|
||||
"""Return a list of agents that will participate in the Handoff orchestration and the handoff relationships.
|
||||
|
||||
Feel free to add or remove agents and handoff connections.
|
||||
"""
|
||||
credential = AzureCliCredential()
|
||||
|
||||
# A Chat Completion agent that is backed by an Azure OpenAI service
|
||||
support_agent = ChatCompletionAgent(
|
||||
name="TriageAgent",
|
||||
description="A customer support agent that triages issues.",
|
||||
instructions="Handle customer requests.",
|
||||
service=AzureChatCompletion(credential=credential),
|
||||
)
|
||||
|
||||
# An Azure Assistant agent that is backed by the Azure OpenAI Assistant API
|
||||
azure_assistant_agent_client = AzureAssistantAgent.create_client(credential=credential)
|
||||
azure_assistant_agent_definition = await azure_assistant_agent_client.beta.assistants.create(
|
||||
model=AzureOpenAISettings().chat_deployment_name,
|
||||
description="A customer support agent that handles refunds.",
|
||||
instructions="Handle refund requests.",
|
||||
name="RefundAgent",
|
||||
)
|
||||
refund_agent = AzureAssistantAgent(
|
||||
client=azure_assistant_agent_client,
|
||||
definition=azure_assistant_agent_definition,
|
||||
plugins=[OrderRefundPlugin()],
|
||||
)
|
||||
|
||||
# An Azure Responses agent that is backed by the Azure OpenAI Responses API
|
||||
azure_responses_agent_client = AzureResponsesAgent.create_client(credential=credential)
|
||||
order_status_agent = AzureResponsesAgent(
|
||||
ai_model_id=AzureOpenAISettings().responses_deployment_name,
|
||||
client=azure_responses_agent_client,
|
||||
instructions="Handle order status requests.",
|
||||
description="A customer support agent that checks order status.",
|
||||
name="OrderStatusAgent",
|
||||
plugins=[OrderStatusPlugin()],
|
||||
)
|
||||
|
||||
# An Azure AI agent that is backed by the Azure AI Agent (a.k.a Foundry Agent) service
|
||||
azure_ai_agent_definition = await azure_ai_agent_client.agents.create_agent(
|
||||
model=AzureAIAgentSettings().model_deployment_name,
|
||||
name="OrderReturnAgent",
|
||||
instructions="Handle order return requests.",
|
||||
description="A customer support agent that handles order returns.",
|
||||
)
|
||||
order_return_agent = AzureAIAgent(
|
||||
client=azure_ai_agent_client,
|
||||
definition=azure_ai_agent_definition,
|
||||
plugins=[OrderReturnPlugin()],
|
||||
)
|
||||
|
||||
# Define the handoff relationships between agents
|
||||
handoffs = (
|
||||
OrchestrationHandoffs()
|
||||
.add_many(
|
||||
source_agent=support_agent.name,
|
||||
target_agents={
|
||||
refund_agent.name: "Transfer to this agent if the issue is refund related",
|
||||
order_status_agent.name: "Transfer to this agent if the issue is order status related",
|
||||
order_return_agent.name: "Transfer to this agent if the issue is order return related",
|
||||
},
|
||||
)
|
||||
.add(
|
||||
source_agent=refund_agent.name,
|
||||
target_agent=support_agent.name,
|
||||
description="Transfer to this agent if the issue is not refund related",
|
||||
)
|
||||
.add(
|
||||
source_agent=order_status_agent.name,
|
||||
target_agent=support_agent.name,
|
||||
description="Transfer to this agent if the issue is not order status related",
|
||||
)
|
||||
.add(
|
||||
source_agent=order_return_agent.name,
|
||||
target_agent=support_agent.name,
|
||||
description="Transfer to this agent if the issue is not order return related",
|
||||
)
|
||||
)
|
||||
|
||||
return [support_agent, refund_agent, order_status_agent, order_return_agent], handoffs
|
||||
|
||||
|
||||
def agent_response_callback(message: ChatMessageContent) -> None:
|
||||
"""Observer function to print the messages from the agents.
|
||||
|
||||
Please note that this function is called whenever the agent generates a response,
|
||||
including the internal processing messages (such as tool calls) that are not visible
|
||||
to other agents in the orchestration.
|
||||
"""
|
||||
print(f"{message.name}: {message.content}")
|
||||
for item in message.items:
|
||||
if isinstance(item, FunctionCallContent):
|
||||
print(f"Calling '{item.name}' with arguments '{item.arguments}'")
|
||||
if isinstance(item, FunctionResultContent):
|
||||
print(f"Result from '{item.name}' is '{item.result}'")
|
||||
|
||||
|
||||
def human_response_function() -> ChatMessageContent:
|
||||
"""Observer function to print the messages from the agents."""
|
||||
user_input = input("User: ")
|
||||
return ChatMessageContent(role=AuthorRole.USER, content=user_input)
|
||||
|
||||
|
||||
@enable_observability
|
||||
async def main():
|
||||
"""Main function to run the agents."""
|
||||
# 0. Initialize the Azure AI agent clients
|
||||
await init_azure_ai_agent_clients()
|
||||
|
||||
# 1. Create a handoff orchestration with multiple agents
|
||||
agents, handoffs = await get_agents()
|
||||
handoff_orchestration = HandoffOrchestration(
|
||||
members=agents,
|
||||
handoffs=handoffs,
|
||||
agent_response_callback=agent_response_callback,
|
||||
human_response_function=human_response_function,
|
||||
)
|
||||
|
||||
# 2. Create a runtime and start it
|
||||
runtime = InProcessRuntime(tracer_provider=NoOpTracerProvider())
|
||||
runtime.start()
|
||||
|
||||
try:
|
||||
# 3. Invoke the orchestration with a task and the runtime
|
||||
orchestration_result = await handoff_orchestration.invoke(
|
||||
task="Greet the customer who is reaching out for support.",
|
||||
runtime=runtime,
|
||||
)
|
||||
|
||||
# 4. Wait for the results
|
||||
value = await orchestration_result.get()
|
||||
print(value)
|
||||
finally:
|
||||
# 5. Stop the runtime after the invocation is complete
|
||||
await runtime.stop_when_idle()
|
||||
|
||||
# 6. Clean up the resources
|
||||
await close_azure_ai_agent_clients()
|
||||
|
||||
"""
|
||||
Sample output:
|
||||
TriageAgent: Hello! Thank you for reaching out for support. How can I assist you today?
|
||||
User: I'd like to track the status of my order
|
||||
TriageAgent:
|
||||
Calling 'Handoff-transfer_to_OrderStatusAgent' with arguments '{}'
|
||||
TriageAgent:
|
||||
Result from 'Handoff-transfer_to_OrderStatusAgent' is 'None'
|
||||
OrderStatusAgent: Could you please provide me with your order ID so I can check the status for you?
|
||||
User: My order ID is 123
|
||||
OrderStatusAgent:
|
||||
Calling 'OrderStatusPlugin-check_order_status' with arguments '{"order_id":"123"}'
|
||||
OrderStatusAgent:
|
||||
Result from 'OrderStatusPlugin-check_order_status' is 'Order 123 is shipped and will arrive in 2-3 days.'
|
||||
OrderStatusAgent: Your order with ID 123 has been shipped and is expected to arrive in 2-3 days. If you have any
|
||||
more questions, feel free to ask!
|
||||
User: I want to return another order of mine
|
||||
OrderStatusAgent: I can help you with that. Could you please provide me with the order ID of the order you want
|
||||
to return?
|
||||
User: Order ID 321
|
||||
OrderStatusAgent:
|
||||
Calling 'Handoff-transfer_to_TriageAgent' with arguments '{}'
|
||||
OrderStatusAgent:
|
||||
Result from 'Handoff-transfer_to_TriageAgent' is 'None'
|
||||
TriageAgent:
|
||||
Calling 'Handoff-transfer_to_OrderReturnAgent' with arguments '{}'
|
||||
TriageAgent:
|
||||
Result from 'Handoff-transfer_to_OrderReturnAgent' is 'None'
|
||||
OrderReturnAgent: Could you please provide me with the reason for the return for order ID 321?
|
||||
User: Broken item
|
||||
Processing return for order 321 due to: Broken item
|
||||
OrderReturnAgent:
|
||||
Calling 'OrderReturnPlugin-process_return' with arguments '{"order_id":"321","reason":"Broken item"}'
|
||||
OrderReturnAgent:
|
||||
Result from 'OrderReturnPlugin-process_return' is 'Return for order 321 has been processed successfully.'
|
||||
OrderReturnAgent: The return for order ID 321 has been processed successfully due to a broken item. If you need
|
||||
further assistance or have any other questions, feel free to let me know!
|
||||
User: No, bye
|
||||
Task is completed with summary: Processed the return request for order ID 321 due to a broken item.
|
||||
OrderReturnAgent:
|
||||
Calling 'Handoff-complete_task' with arguments '{"task_summary":"Processed the return request for order ID 321
|
||||
due to a broken item."}'
|
||||
OrderReturnAgent:
|
||||
Result from 'Handoff-complete_task' is 'None'
|
||||
"""
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
+199
@@ -0,0 +1,199 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
|
||||
from semantic_kernel.agents import (
|
||||
Agent,
|
||||
ChatCompletionAgent,
|
||||
MagenticOrchestration,
|
||||
OpenAIAssistantAgent,
|
||||
StandardMagenticManager,
|
||||
)
|
||||
from semantic_kernel.agents.runtime import InProcessRuntime
|
||||
from semantic_kernel.connectors.ai.open_ai import OpenAIChatCompletion, OpenAISettings
|
||||
from semantic_kernel.contents import ChatMessageContent
|
||||
|
||||
"""
|
||||
The following sample demonstrates how to create a Magentic orchestration with two agents:
|
||||
- A Research agent that can perform web searches
|
||||
- A Coder agent that can run code using the code interpreter
|
||||
|
||||
Read more about Magentic here:
|
||||
https://www.microsoft.com/en-us/research/articles/magentic-one-a-generalist-multi-agent-system-for-solving-complex-tasks/
|
||||
|
||||
This sample demonstrates the basic steps of creating and starting a runtime, creating
|
||||
a Magentic orchestration with two agents and a Magentic manager, invoking the
|
||||
orchestration, and finally waiting for the results.
|
||||
|
||||
The Magentic manager requires a chat completion model that supports structured output.
|
||||
"""
|
||||
|
||||
|
||||
async def agents() -> list[Agent]:
|
||||
"""Return a list of agents that will participate in the Magentic orchestration.
|
||||
|
||||
Feel free to add or remove agents.
|
||||
"""
|
||||
research_agent = ChatCompletionAgent(
|
||||
name="ResearchAgent",
|
||||
description="A helpful assistant with access to web search. Ask it to perform web searches.",
|
||||
instructions=(
|
||||
"You are a Researcher. You find information without additional computation or quantitative analysis."
|
||||
),
|
||||
# This agent requires the gpt-4o-search-preview model to perform web searches.
|
||||
# Feel free to explore with other agents that support web search, for example,
|
||||
# the `OpenAIResponseAgent` or `AzureAIAgent` with bing grounding.
|
||||
service=OpenAIChatCompletion(ai_model_id="gpt-4o-search-preview"),
|
||||
)
|
||||
|
||||
# Create an OpenAI Assistant agent with code interpreter capability
|
||||
client = OpenAIAssistantAgent.create_client()
|
||||
code_interpreter_tool, code_interpreter_tool_resources = OpenAIAssistantAgent.configure_code_interpreter_tool()
|
||||
definition = await client.beta.assistants.create(
|
||||
model=OpenAISettings().chat_model_id,
|
||||
name="CoderAgent",
|
||||
description="A helpful assistant that writes and executes code to process and analyze data.",
|
||||
instructions="You solve questions using code. Please provide detailed analysis and computation process.",
|
||||
tools=code_interpreter_tool,
|
||||
tool_resources=code_interpreter_tool_resources,
|
||||
)
|
||||
coder_agent = OpenAIAssistantAgent(
|
||||
client=client,
|
||||
definition=definition,
|
||||
)
|
||||
|
||||
return [research_agent, coder_agent]
|
||||
|
||||
|
||||
def agent_response_callback(message: ChatMessageContent) -> None:
|
||||
"""Observer function to print the messages from the agents."""
|
||||
print(f"**{message.name}**\n{message.content}")
|
||||
|
||||
|
||||
async def main():
|
||||
"""Main function to run the agents."""
|
||||
# 1. Create a Magentic orchestration with two agents and a Magentic manager
|
||||
# Note, the Standard Magentic manager uses prompts that have been tuned very
|
||||
# carefully but it accepts custom prompts for advanced users and scenarios.
|
||||
# For even more advanced scenarios, you can subclass the MagenticManagerBase
|
||||
# and implement your own manager logic.
|
||||
# The standard manager also requires a chat completion model that supports
|
||||
# structured output.
|
||||
magentic_orchestration = MagenticOrchestration(
|
||||
members=await agents(),
|
||||
manager=StandardMagenticManager(chat_completion_service=OpenAIChatCompletion()),
|
||||
agent_response_callback=agent_response_callback,
|
||||
)
|
||||
|
||||
# 2. Create a runtime and start it
|
||||
runtime = InProcessRuntime()
|
||||
runtime.start()
|
||||
|
||||
# 3. Invoke the orchestration with a task and the runtime
|
||||
orchestration_result = await magentic_orchestration.invoke(
|
||||
task=(
|
||||
"I am preparing a report on the energy efficiency of different machine learning model architectures. "
|
||||
"Compare the estimated training and inference energy consumption of ResNet-50, BERT-base, and GPT-2 "
|
||||
"on standard datasets (e.g., ImageNet for ResNet, GLUE for BERT, WebText for GPT-2). "
|
||||
"Then, estimate the CO2 emissions associated with each, assuming training on an Azure Standard_NC6s_v3 VM "
|
||||
"for 24 hours. Provide tables for clarity, and recommend the most energy-efficient model "
|
||||
"per task type (image classification, text classification, and text generation)."
|
||||
),
|
||||
runtime=runtime,
|
||||
)
|
||||
|
||||
# 4. Wait for the results
|
||||
value = await orchestration_result.get()
|
||||
|
||||
print(f"\nFinal result:\n{value}")
|
||||
|
||||
# 5. Stop the runtime when idle
|
||||
await runtime.stop_when_idle()
|
||||
|
||||
"""
|
||||
Sample output:
|
||||
**ResearchAgent**
|
||||
Estimating the energy consumption and associated CO₂ emissions for training and inference of ResNet-50, BERT-base...
|
||||
|
||||
**CoderAgent**
|
||||
Here is the comparison of energy consumption and CO₂ emissions for each model (ResNet-50, BERT-base, and GPT-2)
|
||||
over a 24-hour period:
|
||||
|
||||
| Model | Training Energy (kWh) | Inference Energy (kWh) | Total Energy (kWh) | CO₂ Emissions (kg) |
|
||||
|-----------|------------------------|------------------------|---------------------|---------------------|
|
||||
| ResNet-50 | 21.11 | 0.08232 | 21.19232 | 19.50 |
|
||||
| BERT-base | 0.048 | 0.23736 | 0.28536 | 0.26 |
|
||||
| GPT-2 | 42.22 | 0.35604 | 42.57604 | 39.17 |
|
||||
|
||||
### Recommendations:
|
||||
...
|
||||
|
||||
**CoderAgent**
|
||||
Here are the recalibrated results for energy consumption and CO₂ emissions, assuming a more conservative approach
|
||||
for models like GPT-2:
|
||||
|
||||
| Model | Training Energy (kWh) | Inference Energy (kWh) | Total Energy (kWh) | CO₂ Emissions (kg) |
|
||||
|------------------|------------------------|------------------------|---------------------|---------------------|
|
||||
| ResNet-50 | 21.11 | 0.08232 | 21.19232 | 19.50 |
|
||||
| BERT-base | 0.048 | 0.23736 | 0.28536 | 0.26 |
|
||||
| GPT-2 (Adjusted) | 42.22 | 0.35604 | 42.57604 | 39.17 |
|
||||
|
||||
...
|
||||
|
||||
**ResearchAgent**
|
||||
Estimating the energy consumption and associated CO₂ emissions for training and inference of machine learning ...
|
||||
|
||||
**ResearchAgent**
|
||||
Estimating the energy consumption and CO₂ emissions of training and inference for ResNet-50, BERT-base, and ...
|
||||
|
||||
**CoderAgent**
|
||||
Here is the estimated energy use and CO₂ emissions for a full day of operation for each model on an Azure ...
|
||||
|
||||
**ResearchAgent**
|
||||
Recent analyses have highlighted the substantial energy consumption and carbon emissions associated with ...
|
||||
|
||||
**CoderAgent**
|
||||
Here's the refined estimation for the energy use and CO₂ emissions for optimized models on an Azure ...
|
||||
|
||||
**CoderAgent**
|
||||
To provide precise estimates for CO₂ emissions based on Azure's regional data centers' carbon intensity, we need ...
|
||||
|
||||
**ResearchAgent**
|
||||
To refine the CO₂ emission estimates for training and inference of ResNet-50, BERT-base, and GPT-2 on an Azure ...
|
||||
|
||||
**CoderAgent**
|
||||
Here's the refined comparative table for energy consumption and CO₂ emissions for ResNet-50, BERT-base, and GPT-2,
|
||||
taking into account carbon intensity data for Azure's West Europe and Sweden Central regions:
|
||||
|
||||
| Model | Energy (kWh) | CO₂ Emissions West Europe (kg) | CO₂ Emissions Sweden Central (kg) |
|
||||
|------------|--------------|--------------------------------|-----------------------------------|
|
||||
| ResNet-50 | 5.76 | 0.639 | 0.086 |
|
||||
| BERT-base | 9.18 | 1.019 | 0.138 |
|
||||
| GPT-2 | 12.96 | 1.439 | 0.194 |
|
||||
|
||||
**Refined Recommendations:**
|
||||
|
||||
...
|
||||
|
||||
Final result:
|
||||
Here is the comprehensive report on energy efficiency and CO₂ emissions for ResNet-50, BERT-base, and GPT-2 models
|
||||
when trained and inferred on an Azure Standard_NC6s_v3 VM for 24 hours.
|
||||
|
||||
### Energy Consumption and CO₂ Emissions:
|
||||
|
||||
Based on refined analyses, here are the estimated energy consumption and CO₂ emissions for each model:
|
||||
|
||||
| Model | Energy (kWh) | CO₂ Emissions West Europe (kg) | CO₂ Emissions Sweden Central (kg) |
|
||||
|------------|--------------|--------------------------------|-----------------------------------|
|
||||
| ResNet-50 | 5.76 | 0.639 | 0.086 |
|
||||
| BERT-base | 9.18 | 1.019 | 0.138 |
|
||||
| GPT-2 | 12.96 | 1.439 | 0.194 |
|
||||
|
||||
### Recommendations for Energy Efficiency:
|
||||
|
||||
...
|
||||
"""
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,110 @@
|
||||
## OpenAI Assistant Agents
|
||||
|
||||
The following getting started samples show how to use OpenAI Assistant agents with Semantic Kernel.
|
||||
|
||||
## Assistants API Overview
|
||||
|
||||
The Assistants API is a robust solution from OpenAI that empowers developers to integrate powerful, purpose-built AI assistants into their applications. It streamlines the development process by handling conversation histories, managing threads, and providing seamless access to advanced tools.
|
||||
|
||||
### Key Features
|
||||
|
||||
- **Purpose-Built AI Assistants:**
|
||||
Assistants are specialized AIs that leverage OpenAI’s models to interact with users, access files, maintain persistent threads, and call additional tools. This enables highly tailored and effective user interactions.
|
||||
|
||||
- **Simplified Conversation Management:**
|
||||
The concept of a **thread** -- a dedicated conversation session between an assistant and a user -- ensures that message history is managed automatically. Threads optimize the conversation context by storing and truncating messages as needed.
|
||||
|
||||
- **Integrated Tool Access:**
|
||||
The API provides built-in tools such as:
|
||||
- **Code Interpreter:** Allows the assistant to execute code, enhancing its ability to solve complex tasks.
|
||||
- **File Search:** Implements best practices for retrieving data from uploaded files, including advanced chunking and embedding techniques.
|
||||
|
||||
- **Enhanced Function Calling:**
|
||||
With improved support for third-party tool integration, the Assistants API enables assistants to extend their capabilities beyond native functions.
|
||||
|
||||
For more detailed technical information, refer to the [Assistants API](https://platform.openai.com/docs/assistants/overview).
|
||||
|
||||
### Semantic Kernel OpenAI Assistant Agents
|
||||
|
||||
OpenAI Assistant Agents are created in the following way:
|
||||
|
||||
```python
|
||||
from semantic_kernel.agents import OpenAIAssistantAgent
|
||||
from semantic_kernel.connectors.ai.open_ai import OpenAISettings
|
||||
|
||||
# Create the client using OpenAI resources and configuration
|
||||
client = OpenAIAssistantAgent.create_client()
|
||||
|
||||
# Create the assistant definition
|
||||
definition = await client.beta.assistants.create(
|
||||
model=OpenAISettings().chat_model_id,
|
||||
instructions="<instructions>",
|
||||
name="<name>",
|
||||
)
|
||||
|
||||
# Define the Semantic Kernel OpenAI Assistant Agent
|
||||
agent = OpenAIAssistantAgent(
|
||||
client=client,
|
||||
definition=definition,
|
||||
)
|
||||
|
||||
# Define a thread to hold the conversation's context
|
||||
# If a thread is not created initially it will be created
|
||||
# and returned as part of the first response
|
||||
thread = None
|
||||
|
||||
# Get the agent response
|
||||
response = await agent.get_response(messages="Why is the sky blue?", thread=thread)
|
||||
thread = response.thread
|
||||
|
||||
# or use the agent.invoke(...) method
|
||||
async for response in agent.invoke(messages="Why is the sky blue?", thread=thread):
|
||||
print(f"# {response.role}: {response.content}")
|
||||
thread = response.thread
|
||||
```
|
||||
|
||||
### Semantic Kernel Azure Assistant Agents
|
||||
|
||||
Azure Assistant Agents are currently in preview and require a `-preview` API version (minimum version: `2024-05-01-preview`). As new features are introduced, API versions will be updated accordingly. For the latest versioning details, please refer to the [Azure OpenAI API preview lifecycle](https://learn.microsoft.com/azure/ai-services/openai/api-version-deprecation).
|
||||
|
||||
To specify the correct API version, set the following environment variable (for example, in your `.env` file):
|
||||
|
||||
```bash
|
||||
AZURE_OPENAI_API_VERSION="2025-01-01-preview"
|
||||
```
|
||||
|
||||
Alternatively, you can pass the `api_version` parameter when creating an `AzureAssistantAgent`:
|
||||
|
||||
```python
|
||||
from semantic_kernel.agents import AzureAssistantAgent
|
||||
|
||||
# Create the client using Azure OpenAI resources and configuration
|
||||
client = AzureAssistantAgent.create_client()
|
||||
|
||||
# Create the assistant definition
|
||||
definition = await client.beta.assistants.create(
|
||||
model=model,
|
||||
instructions="<instructions>",
|
||||
name="<name>",
|
||||
)
|
||||
|
||||
# Define the Semantic Kernel Azure OpenAI Assistant Agent
|
||||
agent = AzureAssistantAgent(
|
||||
client=client,
|
||||
definition=definition,
|
||||
)
|
||||
|
||||
# Define a thread to hold the conversation's context
|
||||
# If a thread is not created initially it will be created
|
||||
# and returned as part of the first response
|
||||
thread = None
|
||||
|
||||
# Get the agent response
|
||||
response = await agent.get_response(messages="Why is the sky blue?", thread=thread)
|
||||
thread = response.thread
|
||||
|
||||
# or use the agent.invoke(...) method
|
||||
async for response in agent.invoke(messages="Why is the sky blue?", thread=thread):
|
||||
print(f"# {response.role}: {response.content}")
|
||||
thread = response.thread
|
||||
```
|
||||
@@ -0,0 +1,76 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
import asyncio
|
||||
|
||||
from azure.identity import AzureCliCredential
|
||||
|
||||
from semantic_kernel.agents import AssistantAgentThread, AzureAssistantAgent
|
||||
from semantic_kernel.connectors.ai.open_ai import AzureOpenAISettings
|
||||
|
||||
"""
|
||||
The following sample demonstrates how to create an OpenAI assistant using either
|
||||
Azure OpenAI or OpenAI. The sample shows how to have the assistant answrer
|
||||
questions about the world.
|
||||
|
||||
The interaction with the agent is via the `get_response` method, which sends a
|
||||
user input to the agent and receives a response from the agent. The conversation
|
||||
history is maintained by the agent service, i.e. the responses are automatically
|
||||
associated with the thread. Therefore, client code does not need to maintain the
|
||||
conversation history.
|
||||
"""
|
||||
|
||||
# Simulate a conversation with the agent
|
||||
USER_INPUTS = [
|
||||
"Why is the sky blue?",
|
||||
"What is the speed of light?",
|
||||
"What have we been talking about?",
|
||||
]
|
||||
|
||||
|
||||
async def main():
|
||||
# 1. Create the client using Azure OpenAI resources and configuration
|
||||
client = AzureAssistantAgent.create_client(credential=AzureCliCredential())
|
||||
|
||||
# 2. Create the assistant on the Azure OpenAI service
|
||||
definition = await client.beta.assistants.create(
|
||||
model=AzureOpenAISettings().chat_deployment_name,
|
||||
instructions="Answer questions about the world in one sentence.",
|
||||
name="Assistant",
|
||||
)
|
||||
|
||||
# 3. Create a Semantic Kernel agent for the Azure OpenAI assistant
|
||||
agent = AzureAssistantAgent(
|
||||
client=client,
|
||||
definition=definition,
|
||||
)
|
||||
|
||||
# 4. Create a new thread for use with the assistant
|
||||
# If no thread is provided, a new thread will be
|
||||
# created and returned with the initial response
|
||||
thread: AssistantAgentThread = None
|
||||
|
||||
try:
|
||||
for user_input in USER_INPUTS:
|
||||
print(f"# User: '{user_input}'")
|
||||
# 6. Invoke the agent for the current thread and print the response
|
||||
response = await agent.get_response(messages=user_input, thread=thread)
|
||||
print(f"# {response.name}: {response}")
|
||||
thread = response.thread
|
||||
finally:
|
||||
# 7. Clean up the resources
|
||||
await thread.delete() if thread else None
|
||||
await agent.client.beta.assistants.delete(assistant_id=agent.id)
|
||||
|
||||
"""
|
||||
You should see output similar to the following:
|
||||
|
||||
# User: 'Why is the sky blue?'
|
||||
# Agent: The sky appears blue because molecules in the atmosphere scatter sunlight in all directions, and blue
|
||||
light is scattered more than other colors because it travels in shorter, smaller waves.
|
||||
# User: 'What is the speed of light?'
|
||||
# Agent: The speed of light in a vacuum is approximately 299,792,458 meters per second
|
||||
(about 186,282 miles per second).
|
||||
"""
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
import asyncio
|
||||
from typing import Annotated
|
||||
|
||||
from azure.identity import AzureCliCredential
|
||||
|
||||
from semantic_kernel.agents import AssistantAgentThread, AzureAssistantAgent
|
||||
from semantic_kernel.connectors.ai.open_ai import AzureOpenAISettings
|
||||
from semantic_kernel.functions import kernel_function
|
||||
|
||||
"""
|
||||
The following sample demonstrates how to create an OpenAI
|
||||
assistant using either Azure OpenAI or OpenAI. The sample
|
||||
shows how to use a Semantic Kernel plugin as part of the
|
||||
OpenAI Assistant.
|
||||
"""
|
||||
|
||||
|
||||
# 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) -> 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"
|
||||
|
||||
|
||||
# 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 Azure OpenAI resources and configuration
|
||||
client = AzureAssistantAgent.create_client(credential=AzureCliCredential())
|
||||
|
||||
# 2. Create the assistant on the Azure OpenAI service
|
||||
definition = await client.beta.assistants.create(
|
||||
model=AzureOpenAISettings().chat_deployment_name,
|
||||
instructions="Answer questions about the menu.",
|
||||
name="Host",
|
||||
)
|
||||
|
||||
# 3. Create a Semantic Kernel agent for the Azure OpenAI assistant
|
||||
agent = AzureAssistantAgent(
|
||||
client=client,
|
||||
definition=definition,
|
||||
plugins=[MenuPlugin()], # The plugins can be passed in as a list to the constructor
|
||||
)
|
||||
# Note: plugins can also be configured on the Kernel and passed in as a parameter to the OpenAIAssistantAgent
|
||||
|
||||
# 4. Create a new thread for use with the assistant
|
||||
# If no thread is provided, a new thread will be
|
||||
# created and returned with the initial response
|
||||
thread: AssistantAgentThread = None
|
||||
|
||||
try:
|
||||
for user_input in USER_INPUTS:
|
||||
print(f"# User: '{user_input}'")
|
||||
# 6. Invoke the agent for the current thread and print the response
|
||||
async for response in agent.invoke(messages=user_input, thread=thread):
|
||||
print(f"# Agent: {response}")
|
||||
thread = response.thread
|
||||
finally:
|
||||
# 7. Clean up the resources
|
||||
await thread.delete() if thread else None
|
||||
await agent.client.beta.assistants.delete(assistant_id=agent.id)
|
||||
|
||||
"""
|
||||
You should see output similar to the following:
|
||||
|
||||
# User: 'Hello'
|
||||
# Agent: Hello! How can I assist you today?
|
||||
# User: 'What is the special soup?'
|
||||
# Agent: The special soup today is Clam Chowder. Would you like to know more about any other menu items?
|
||||
# User: 'What is the special drink?'
|
||||
# Agent: The special drink today is Chai Tea. Would you like more information on anything else?
|
||||
# User: 'Thank you'
|
||||
# Agent: You're welcome! If you have any more questions or need further assistance, feel free to ask.
|
||||
Enjoy your day!
|
||||
"""
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,89 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
|
||||
from semantic_kernel.agents import AssistantAgentThread, OpenAIAssistantAgent
|
||||
from semantic_kernel.connectors.ai.open_ai import OpenAISettings
|
||||
from semantic_kernel.contents import AuthorRole, ChatMessageContent, FileReferenceContent, ImageContent, TextContent
|
||||
|
||||
"""
|
||||
The following sample demonstrates how to create an OpenAI
|
||||
assistant using OpenAI configuration, and leverage the
|
||||
multi-modal content types to have the assistant describe images
|
||||
and answer questions about them. This sample uses non-streaming responses.
|
||||
"""
|
||||
|
||||
|
||||
async def main():
|
||||
# 1. Create the OpenAI Assistant Agent client
|
||||
# Note Azure OpenAI doesn't support vision files yet
|
||||
client = OpenAIAssistantAgent.create_client()
|
||||
|
||||
# 2. Load a sample image of a cat used for the assistant to describe
|
||||
file_path = os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), "resources", "cat.jpg")
|
||||
|
||||
with open(file_path, "rb") as file:
|
||||
file = await client.files.create(file=file, purpose="assistants")
|
||||
|
||||
# 3. Create the assistant on the OpenAI service
|
||||
definition = await client.beta.assistants.create(
|
||||
model=OpenAISettings().chat_model_id,
|
||||
instructions="Answer questions about the provided images.",
|
||||
name="Vision",
|
||||
)
|
||||
|
||||
# 4. Create a Semantic Kernel agent for the OpenAI assistant
|
||||
agent = OpenAIAssistantAgent(
|
||||
client=client,
|
||||
definition=definition,
|
||||
)
|
||||
|
||||
# 5. Create a new thread for use with the assistant
|
||||
# If no thread is provided, a new thread will be
|
||||
# created and returned with the initial response
|
||||
thread: AssistantAgentThread = None
|
||||
|
||||
# 6. Define the user messages with the image content to simulate the conversation
|
||||
user_messages = {
|
||||
ChatMessageContent(
|
||||
role=AuthorRole.USER,
|
||||
items=[
|
||||
TextContent(text="Describe this image."),
|
||||
ImageContent(
|
||||
uri="https://upload.wikimedia.org/wikipedia/commons/thumb/4/47/New_york_times_square-terabass.jpg/1200px-New_york_times_square-terabass.jpg"
|
||||
),
|
||||
],
|
||||
),
|
||||
ChatMessageContent(
|
||||
role=AuthorRole.USER,
|
||||
items=[
|
||||
TextContent(text="What is the main color in this image?"),
|
||||
ImageContent(uri="https://upload.wikimedia.org/wikipedia/commons/5/56/White_shark.jpg"),
|
||||
],
|
||||
),
|
||||
ChatMessageContent(
|
||||
role=AuthorRole.USER,
|
||||
items=[
|
||||
TextContent(text="Is there an animal in this image?"),
|
||||
FileReferenceContent(file_id=file.id),
|
||||
],
|
||||
),
|
||||
}
|
||||
|
||||
try:
|
||||
for message in user_messages:
|
||||
print(f"# User: {str(message)}") # type: ignore
|
||||
# 8. Invoke the agent for the current thread and print the response
|
||||
async for response in agent.invoke(messages=message, thread=thread):
|
||||
print(f"# Agent: {response}\n")
|
||||
thread = response.thread
|
||||
finally:
|
||||
# 9. Clean up the resources
|
||||
await client.files.delete(file.id)
|
||||
await thread.delete() if thread else None
|
||||
await agent.client.beta.assistants.delete(assistant_id=agent.id)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
import asyncio
|
||||
|
||||
from azure.identity import AzureCliCredential
|
||||
|
||||
from semantic_kernel.agents import AssistantAgentThread, AzureAssistantAgent
|
||||
from semantic_kernel.connectors.ai.open_ai import AzureOpenAISettings
|
||||
|
||||
"""
|
||||
The following sample demonstrates how to create an OpenAI
|
||||
assistant using either Azure OpenAI or OpenAI and leverage the
|
||||
assistant's code interpreter functionality to have it write
|
||||
Python code to print Fibonacci numbers.
|
||||
"""
|
||||
|
||||
TASK = "Use code to determine the values in the Fibonacci sequence that that are less than the value of 101?"
|
||||
|
||||
|
||||
async def main():
|
||||
# 1. Create the client using Azure OpenAI resources and configuration
|
||||
client = AzureAssistantAgent.create_client(credential=AzureCliCredential())
|
||||
|
||||
# 2. Configure the code interpreter tool and resources for the Assistant
|
||||
code_interpreter_tool, code_interpreter_tool_resources = AzureAssistantAgent.configure_code_interpreter_tool()
|
||||
|
||||
# 3. Create the assistant on the Azure OpenAI service
|
||||
definition = await client.beta.assistants.create(
|
||||
model=AzureOpenAISettings().chat_deployment_name,
|
||||
name="CodeRunner",
|
||||
instructions="Run the provided request as code and return the result.",
|
||||
tools=code_interpreter_tool,
|
||||
tool_resources=code_interpreter_tool_resources,
|
||||
)
|
||||
|
||||
# 4. Create a Semantic Kernel agent for the Azure OpenAI assistant
|
||||
agent = AzureAssistantAgent(
|
||||
client=client,
|
||||
definition=definition,
|
||||
)
|
||||
|
||||
# 5. Create a new thread for use with the assistant
|
||||
# If no thread is provided, a new thread will be
|
||||
# created and returned with the initial response
|
||||
thread: AssistantAgentThread = None
|
||||
|
||||
print(f"# User: '{TASK}'")
|
||||
try:
|
||||
# 6. Invoke the agent for the current thread and print the response
|
||||
async for response in agent.invoke(messages=TASK, thread=thread):
|
||||
print(f"# Agent: {response}")
|
||||
thread = response.thread
|
||||
finally:
|
||||
# 7. Clean up the resources
|
||||
await thread.delete() if thread else None
|
||||
await agent.client.beta.assistants.delete(agent.id)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
|
||||
from azure.identity import AzureCliCredential
|
||||
|
||||
from semantic_kernel.agents import AssistantAgentThread, AzureAssistantAgent
|
||||
from semantic_kernel.connectors.ai.open_ai import AzureOpenAISettings
|
||||
|
||||
"""
|
||||
The following sample demonstrates how to create an OpenAI
|
||||
Assistant using either Azure OpenAI or OpenAI and leverage the
|
||||
assistant's file search functionality.
|
||||
"""
|
||||
|
||||
# Simulate a conversation with the agent
|
||||
USER_INPUTS = {
|
||||
"Who is the youngest employee?",
|
||||
"Who works in sales?",
|
||||
"I have a customer request, who can help me?",
|
||||
}
|
||||
|
||||
|
||||
async def main():
|
||||
# 1. Create the client using Azure OpenAI resources and configuration
|
||||
client = AzureAssistantAgent.create_client(credential=AzureCliCredential())
|
||||
|
||||
# 2. Read and upload the file to the Azure OpenAI assistant service
|
||||
pdf_file_path = os.path.join(
|
||||
os.path.dirname(os.path.dirname(os.path.realpath(__file__))), "resources", "employees.pdf"
|
||||
)
|
||||
|
||||
with open(pdf_file_path, "rb") as file:
|
||||
file = await client.files.create(file=file, purpose="assistants")
|
||||
|
||||
vector_store = await client.vector_stores.create(
|
||||
name="step4_assistant_file_search",
|
||||
file_ids=[file.id],
|
||||
)
|
||||
|
||||
# 3. Create file search tool with uploaded resources
|
||||
file_search_tool, file_search_tool_resources = AzureAssistantAgent.configure_file_search_tool(vector_store.id)
|
||||
|
||||
# 4. Create the assistant on the Azure OpenAI service with the file search tool
|
||||
definition = await client.beta.assistants.create(
|
||||
model=AzureOpenAISettings().chat_deployment_name,
|
||||
instructions="Find answers to the user's questions in the provided file.",
|
||||
name="FileSearch",
|
||||
tools=file_search_tool,
|
||||
tool_resources=file_search_tool_resources,
|
||||
)
|
||||
|
||||
# 5. Create a Semantic Kernel agent for the Azure OpenAI assistant
|
||||
agent = AzureAssistantAgent(
|
||||
client=client,
|
||||
definition=definition,
|
||||
)
|
||||
|
||||
# 6. Create a new thread for use with the assistant
|
||||
# If no thread is provided, a new thread will be
|
||||
# created and returned with the initial response
|
||||
thread: AssistantAgentThread = None
|
||||
|
||||
try:
|
||||
for user_input in USER_INPUTS:
|
||||
print(f"# User: '{user_input}'")
|
||||
# 7. Invoke the agent for the current thread and print the response
|
||||
async for response in agent.invoke(messages=user_input, thread=thread):
|
||||
print(f"# Agent: {response}")
|
||||
thread = response.thread
|
||||
finally:
|
||||
# 9. Clean up the resources
|
||||
await client.files.delete(file.id)
|
||||
await client.vector_stores.delete(vector_store.id)
|
||||
await client.beta.threads.delete(thread.id)
|
||||
await client.beta.assistants.delete(agent.id)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
import asyncio
|
||||
from typing import Annotated
|
||||
|
||||
from semantic_kernel.agents import AgentRegistry, OpenAIAssistantAgent
|
||||
from semantic_kernel.functions import kernel_function
|
||||
|
||||
"""
|
||||
The following sample demonstrates how to create an OpenAI Assistant agent that answers
|
||||
questions about a sample menu using a Semantic Kernel Plugin. The agent is created
|
||||
using a yaml declarative spec.
|
||||
"""
|
||||
|
||||
# Simulate a conversation with the agent
|
||||
USER_INPUTS = [
|
||||
"Hello",
|
||||
"What is the special soup?",
|
||||
"How much does that cost?",
|
||||
"Thank you",
|
||||
]
|
||||
|
||||
# Define the YAML string for the sample
|
||||
SPEC = """
|
||||
type: openai_assistant
|
||||
name: Host
|
||||
instructions: Respond politely to the user's questions.
|
||||
model:
|
||||
id: ${OpenAI:ChatModelId}
|
||||
tools:
|
||||
- id: MenuPlugin.get_specials
|
||||
type: function
|
||||
- id: MenuPlugin.get_item_price
|
||||
type: 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) -> 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"
|
||||
|
||||
|
||||
async def main():
|
||||
# 1. Create the client using Azure OpenAI resources and configuration
|
||||
client = OpenAIAssistantAgent.create_client()
|
||||
|
||||
# 2. Create the assistant on the Azure OpenAI service
|
||||
agent: OpenAIAssistantAgent = await AgentRegistry.create_from_yaml(
|
||||
SPEC,
|
||||
plugins=[MenuPlugin()],
|
||||
client=client,
|
||||
)
|
||||
|
||||
# 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"# User: {user_input}")
|
||||
# 4. Invoke the agent for the specified thread for response
|
||||
async for response in agent.invoke(
|
||||
messages=user_input,
|
||||
thread=thread,
|
||||
):
|
||||
print(f"# {response.name}: {response}")
|
||||
thread = response.thread
|
||||
finally:
|
||||
# 5. Clean up the resources
|
||||
await thread.delete() if thread else None
|
||||
await agent.client.beta.assistants.delete(assistant_id=agent.id)
|
||||
|
||||
"""
|
||||
Sample Output:
|
||||
|
||||
# User: Hello
|
||||
# Agent: Hello! How can I assist you today?
|
||||
# User: What is the special soup?
|
||||
# ...
|
||||
"""
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,30 @@
|
||||
## Semantic Kernel OpenAI Responses Agent
|
||||
|
||||
The responses API is OpenAI's latest core API and an agentic API primitive. See more details [here](https://platform.openai.com/docs/guides/responses-vs-chat-completions).
|
||||
|
||||
### OpenAI Responses Agent
|
||||
|
||||
In Semantic Kernel, we don't currently support the Computer User Agent Tool. This is coming soon.
|
||||
|
||||
#### Environment Variables / Config
|
||||
|
||||
`OPENAI_RESPONSES_MODEL_ID=""`
|
||||
|
||||
### Azure Responses Agent
|
||||
|
||||
The Semantic Kernel Azure Responses Agent leverages Azure OpenAI's new stateful API.
|
||||
It brings together the best capabilities from the chat completions and assistants API in one unified experience.
|
||||
|
||||
For `AzureResponsesAgent` limitations, please see the latest [Azure OpenAI Responses API Docs](https://learn.microsoft.com/en-us/azure/ai-foundry/openai/how-to/responses?tabs=python-secure).
|
||||
|
||||
#### API Support
|
||||
|
||||
`2025-03-01-preview` or later, therefore please use `AZURE_OPENAI_API_VERSION="2025-03-01-preview"`.
|
||||
|
||||
Please visit the following [link](https://learn.microsoft.com/en-us/azure/ai-services/openai/how-to/responses?tabs=python-secure) to view region availability, model support, and further details.
|
||||
|
||||
#### Environment Variables / Config
|
||||
|
||||
`AZURE_OPENAI_RESPONSES_DEPLOYMENT_NAME=""`
|
||||
|
||||
The other Azure OpenAI config values used for AzureAssistantAgent or AzureChatCompletion, like `AZURE_OPENAI_API_VERSION` or `AZURE_OPENAI_ENDPOINT` are still valid for the `AzureResponsesAgent`.
|
||||
@@ -0,0 +1,67 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
import asyncio
|
||||
|
||||
from azure.identity import AzureCliCredential
|
||||
|
||||
from semantic_kernel.agents import AzureResponsesAgent
|
||||
from semantic_kernel.connectors.ai.open_ai import AzureOpenAISettings
|
||||
|
||||
"""
|
||||
The following sample demonstrates how to create an OpenAI Responses Agent using either
|
||||
Azure OpenAI or OpenAI. The sample shows how to have the agent answer
|
||||
questions about the world.
|
||||
|
||||
Note, in this sample, a thread is not used. This creates a stateless agent. It will
|
||||
not be able to recall previous messages, which is expected behavior.
|
||||
|
||||
The interaction with the agent is via the `get_response` method, which sends a
|
||||
user input to the agent and receives a response from the agent. The conversation
|
||||
history is maintained by the agent service, i.e. the responses are automatically
|
||||
associated with the thread. Therefore, client code does not need to maintain the
|
||||
conversation history.
|
||||
"""
|
||||
|
||||
USER_INPUTS = [
|
||||
"Hi, my name is John Doe.",
|
||||
"Why is the sky blue?",
|
||||
"What is the speed of light?",
|
||||
"What is my name?",
|
||||
]
|
||||
|
||||
|
||||
async def main():
|
||||
# 1. Create the client using Azure OpenAI resources and configuration
|
||||
client = AzureResponsesAgent.create_client(credential=AzureCliCredential())
|
||||
|
||||
# 2. Create a Semantic Kernel agent for the OpenAI Responses API
|
||||
agent = AzureResponsesAgent(
|
||||
ai_model_id=AzureOpenAISettings().responses_deployment_name,
|
||||
client=client,
|
||||
instructions="Answer questions about the world in one sentence.",
|
||||
name="Expert",
|
||||
)
|
||||
|
||||
for user_input in USER_INPUTS:
|
||||
print(f"# User: '{user_input}'")
|
||||
# 3. Invoke the agent for the current message and print the response
|
||||
response = await agent.get_response(messages=user_input)
|
||||
# We are not using a thread for context, so there will be no memory
|
||||
print(f"# {response.name}: {response.content}")
|
||||
|
||||
"""
|
||||
You should see output similar to the following:
|
||||
|
||||
# User: 'Hi, my name is John Doe.'
|
||||
# Expert: Hello, John Doe! How can I assist you today?
|
||||
# User: 'Why is the sky blue?'
|
||||
# Expert: The sky appears blue because of Rayleigh scattering, where shorter blue light wavelengths are scattered
|
||||
more than other colors by the gases in Earth's atmosphere.
|
||||
# User: 'What is the speed of light?'
|
||||
# Expert: The speed of light in a vacuum is approximately 299,792 kilometers per second (km/s).
|
||||
# User: 'What is my name?'
|
||||
# Expert: I'm sorry, I can't determine your name from our conversation.
|
||||
"""
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
import asyncio
|
||||
|
||||
from azure.identity import AzureCliCredential
|
||||
|
||||
from semantic_kernel.agents import AzureResponsesAgent
|
||||
from semantic_kernel.connectors.ai.open_ai import AzureOpenAISettings
|
||||
|
||||
"""
|
||||
The following sample demonstrates how to create an OpenAI Responses Agent.
|
||||
The sample shows how to have the agent answer questions about the world.
|
||||
|
||||
The interaction with the agent is via the `get_response` method, which sends a
|
||||
user input to the agent and receives a response from the agent. The conversation
|
||||
history is maintained by the agent service, i.e. the responses are automatically
|
||||
associated with the thread. Therefore, client code does not need to maintain the
|
||||
conversation history.
|
||||
"""
|
||||
|
||||
USER_INPUTS = [
|
||||
"My name is John Doe.",
|
||||
"Tell me a joke",
|
||||
"Explain why this is funny.",
|
||||
"What have we been talking about?",
|
||||
]
|
||||
|
||||
|
||||
async def main():
|
||||
# 1. Create the client using Azure OpenAI resources and configuration
|
||||
client = AzureResponsesAgent.create_client(credential=AzureCliCredential())
|
||||
|
||||
# 2. Create a Semantic Kernel agent for the OpenAI Responses API
|
||||
agent = AzureResponsesAgent(
|
||||
ai_model_id=AzureOpenAISettings().responses_deployment_name,
|
||||
client=client,
|
||||
instructions="Answer questions about from the user.",
|
||||
name="Joker",
|
||||
)
|
||||
|
||||
# 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
|
||||
|
||||
for user_input in USER_INPUTS:
|
||||
print(f"# User: '{user_input}'")
|
||||
# 4. Invoke the agent for the current message and print the response
|
||||
response = await agent.get_response(messages=user_input, thread=thread)
|
||||
print(f"# {response.name}: {response.content}")
|
||||
# 5. Update the thread so the previous response id is used
|
||||
thread = response.thread
|
||||
|
||||
"""
|
||||
You should see output similar to the following:
|
||||
|
||||
# User: 'My name is John Doe.'
|
||||
# Joker: Hello, John! How can I assist you today?
|
||||
# User: 'Tell me a joke'
|
||||
# Joker: Sure! Why don't scientists trust atoms?
|
||||
|
||||
Because they make up everything!
|
||||
# User: 'Explain why this is funny.'
|
||||
# Joker: The joke is funny because it plays on the double meaning of "make up." In one sense, atoms are the
|
||||
building blocks of all matter, so they literally "make up" everything. In another sense, "make up" can mean
|
||||
to fabricate or lie, humorously suggesting that atoms are untrustworthy because they "invent" or "fabricate"
|
||||
everything. This clever wordplay is what makes the joke amusing.
|
||||
# User: 'What have we been talking about?'
|
||||
# Joker: We've been discussing a joke about atoms and its humor, focusing on wordplay and double meanings.
|
||||
"""
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
+93
@@ -0,0 +1,93 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
import asyncio
|
||||
from typing import Annotated
|
||||
|
||||
from azure.identity import AzureCliCredential
|
||||
|
||||
from semantic_kernel.agents import AzureResponsesAgent
|
||||
from semantic_kernel.connectors.ai.open_ai import AzureOpenAISettings
|
||||
from semantic_kernel.functions import kernel_function
|
||||
|
||||
"""
|
||||
The following sample demonstrates how to create an OpenAI Responses Agent.
|
||||
The sample shows how to have the agent answer questions about the sample menu.
|
||||
|
||||
The interaction with the agent is via the `get_response` method, which sends a
|
||||
user input to the agent and receives a response from the agent. The conversation
|
||||
history is maintained by the agent service, i.e. the responses are automatically
|
||||
associated with the thread. Therefore, client code does not need to maintain the
|
||||
conversation history.
|
||||
"""
|
||||
|
||||
|
||||
# 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) -> 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"
|
||||
|
||||
|
||||
# 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 Azure OpenAI resources and configuration
|
||||
client = AzureResponsesAgent.create_client(credential=AzureCliCredential())
|
||||
|
||||
# 2. Create a Semantic Kernel agent for the OpenAI Responses API
|
||||
agent = AzureResponsesAgent(
|
||||
ai_model_id=AzureOpenAISettings().responses_deployment_name,
|
||||
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
|
||||
|
||||
for user_input in USER_INPUTS:
|
||||
print(f"# User: '{user_input}'")
|
||||
# 4. Invoke the agent for the current message and print the response
|
||||
response = await agent.get_response(messages=user_input, thread=thread)
|
||||
print(f"# {response.name}: {response.content}")
|
||||
thread = response.thread
|
||||
"""
|
||||
You should see output similar to the following:
|
||||
|
||||
# User: 'Hello'
|
||||
# Host: Hi there! How can I assist you today?
|
||||
# User: 'What is the special soup?'
|
||||
# Host: The special soup is Clam Chowder.
|
||||
# User: 'What is the special drink?'
|
||||
# Host: The special drink is Chai Tea.
|
||||
# User: 'How much is it?'
|
||||
# Host: The Chai Tea costs $9.99.
|
||||
# User: 'Thank you'
|
||||
# Host: You're welcome! If you have any more questions, feel free to ask.
|
||||
"""
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
import asyncio
|
||||
|
||||
from semantic_kernel.agents import OpenAIResponsesAgent
|
||||
from semantic_kernel.connectors.ai.open_ai import OpenAISettings
|
||||
|
||||
"""
|
||||
The following sample demonstrates how to create an OpenAI Responses Agent.
|
||||
The sample shows how to have the agent answer questions using the web search
|
||||
preview tool with streaming responses.
|
||||
|
||||
The interaction with the agent is via the `get_response` method, which sends a
|
||||
user input to the agent and receives a response from the agent. The conversation
|
||||
history is maintained by the agent service, i.e. the responses are automatically
|
||||
associated with the thread. Therefore, client code does not need to maintain the
|
||||
conversation history.
|
||||
"""
|
||||
|
||||
|
||||
# Simulate a conversation with the agent
|
||||
USER_INPUTS = [
|
||||
"Find me news articles about the latest technology trends.",
|
||||
]
|
||||
|
||||
|
||||
async def main():
|
||||
# 1. Create the client using OpenAI resources and configuration
|
||||
# Note: the Azure OpenAI Responses API does not yet support the web search tool.
|
||||
client = OpenAIResponsesAgent.create_client()
|
||||
|
||||
web_search_tool = OpenAIResponsesAgent.configure_web_search_tool()
|
||||
|
||||
# 2. Create a Semantic Kernel agent for the OpenAI Responses API
|
||||
agent = OpenAIResponsesAgent(
|
||||
ai_model_id=OpenAISettings().responses_model_id,
|
||||
client=client,
|
||||
instructions="Answer questions from the user about performing web searches for news.",
|
||||
name="NewsSearcher",
|
||||
tools=[web_search_tool],
|
||||
)
|
||||
|
||||
# 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
|
||||
|
||||
for user_input in USER_INPUTS:
|
||||
print(f"# User: '{user_input}'")
|
||||
response = await agent.get_response(messages=user_input, thread=thread)
|
||||
print(f"# {response.name}: {response.content}")
|
||||
thread = response.thread
|
||||
|
||||
"""
|
||||
You should see output similar to the following:
|
||||
|
||||
# User: 'Find me news articles about the latest technology trends.'
|
||||
# NewsSearcher: Recent developments in technology have highlighted several key trends shaping various industries:
|
||||
|
||||
**Artificial Intelligence (AI) Integration**: AI continues to revolutionize sectors by automating tasks,
|
||||
enhancing real-time analytics, and improving content delivery. At the 2025 NAB Show, AI's influence is
|
||||
evident across creator platforms, sports technology, streaming solutions, and cloud architectures.
|
||||
([tvtechnology.com](https://www.tvtechnology.com/news/nab-show-2025-exhibitor-insight-black-box?utm_source=openai))
|
||||
...
|
||||
"""
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
import asyncio
|
||||
import os
|
||||
|
||||
from azure.identity import AzureCliCredential
|
||||
|
||||
from semantic_kernel.agents import AzureResponsesAgent
|
||||
from semantic_kernel.connectors.ai.open_ai import AzureOpenAISettings
|
||||
|
||||
"""
|
||||
The following sample demonstrates how to create an OpenAI Responses Agent.
|
||||
The sample shows how to have the agent answer questions about the provided
|
||||
document.
|
||||
|
||||
The interaction with the agent is via the `get_response` method, which sends a
|
||||
user input to the agent and receives a response from the agent. The conversation
|
||||
history is maintained by the agent service, i.e. the responses are automatically
|
||||
associated with the thread. Therefore, client code does not need to maintain the
|
||||
conversation history.
|
||||
"""
|
||||
|
||||
|
||||
# Simulate a conversation with the agent
|
||||
USER_INPUTS = [
|
||||
"By birthday, who is the youngest employee?",
|
||||
"Who works in sales?",
|
||||
"I have a customer request, who can help me?",
|
||||
]
|
||||
|
||||
|
||||
async def main():
|
||||
# 1. Create the client using Azure OpenAI resources and configuration
|
||||
client = AzureResponsesAgent.create_client(credential=AzureCliCredential())
|
||||
|
||||
pdf_file_path = os.path.join(
|
||||
os.path.dirname(os.path.dirname(os.path.realpath(__file__))), "resources", "employees.pdf"
|
||||
)
|
||||
|
||||
with open(pdf_file_path, "rb") as file:
|
||||
file = await client.files.create(file=file, purpose="assistants")
|
||||
|
||||
vector_store = await client.vector_stores.create(
|
||||
name="step4_responses_agent_file_search",
|
||||
file_ids=[file.id],
|
||||
)
|
||||
|
||||
file_search_tool = AzureResponsesAgent.configure_file_search_tool(vector_store.id)
|
||||
|
||||
# 2. Create a Semantic Kernel agent for the OpenAI Responses API
|
||||
agent = AzureResponsesAgent(
|
||||
ai_model_id=AzureOpenAISettings().responses_deployment_name,
|
||||
client=client,
|
||||
instructions="Find answers to the user's questions in the provided file.",
|
||||
name="FileSearch",
|
||||
tools=[file_search_tool],
|
||||
)
|
||||
|
||||
# 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"# User: '{user_input}'")
|
||||
# 4. Invoke the agent for the current message and print the response
|
||||
async for response in agent.invoke(messages=user_input, thread=thread):
|
||||
print(f"# Agent: {response.content}")
|
||||
thread = response.thread
|
||||
finally:
|
||||
# 5. Clean up the resources
|
||||
await client.vector_stores.delete(vector_store.id)
|
||||
await client.files.delete(file.id)
|
||||
|
||||
"""
|
||||
# User: 'By birthday, who is the youngest employee?'
|
||||
# Agent: The youngest employee by birthday is Teodor Britton, born on January 9, 1997.
|
||||
# User: 'Who works in sales?'
|
||||
# Agent: The employees who work in sales are:
|
||||
|
||||
- Mariam Jaslyn, Sales Representative
|
||||
- Hicran Bea, Sales Manager
|
||||
- Angelino Embla, Sales Representative.
|
||||
# User: 'I have a customer request, who can help me?'
|
||||
# Agent: For a customer request, you could reach out to the following people in the sales department:
|
||||
|
||||
- Mariam Jaslyn, Sales Representative
|
||||
- Hicran Bea, Sales Manager
|
||||
- Angelino Embla, Sales Representative.
|
||||
"""
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
|
||||
from semantic_kernel.agents import OpenAIResponsesAgent
|
||||
from semantic_kernel.connectors.ai.open_ai import OpenAISettings
|
||||
from semantic_kernel.contents import ChatMessageContent
|
||||
from semantic_kernel.contents.image_content import ImageContent
|
||||
from semantic_kernel.contents.text_content import TextContent
|
||||
from semantic_kernel.contents.utils.author_role import AuthorRole
|
||||
|
||||
"""
|
||||
The following sample demonstrates how to create an OpenAI Responses Agent.
|
||||
The sample shows how to have the agent answer questions about the provided images.
|
||||
|
||||
The interaction with the agent is via the `get_response` method, which sends a
|
||||
user input to the agent and receives a response from the agent. The conversation
|
||||
history is maintained by the chat history. Therefore, client code does need to
|
||||
maintain the conversation history if conversation context is desired.
|
||||
"""
|
||||
|
||||
|
||||
async def main():
|
||||
# 1. Create the client using OpenAI resources and configuration
|
||||
client = OpenAIResponsesAgent.create_client()
|
||||
|
||||
# 2. Define a file path for an image that will be used in the conversation
|
||||
file_path = os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), "resources", "cat.jpg")
|
||||
|
||||
# 3. Create a Semantic Kernel agent for the OpenAI Responses API
|
||||
agent = OpenAIResponsesAgent(
|
||||
ai_model_id=OpenAISettings().responses_model_id,
|
||||
client=client,
|
||||
instructions="Answer questions about the provided images.",
|
||||
name="VisionAgent",
|
||||
)
|
||||
|
||||
# 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
|
||||
|
||||
# 4. Define a list of user messages that include text and image content for the vision task
|
||||
user_messages = [
|
||||
ChatMessageContent(
|
||||
role=AuthorRole.USER,
|
||||
items=[
|
||||
TextContent(text="Describe this image."),
|
||||
ImageContent(
|
||||
uri="https://upload.wikimedia.org/wikipedia/commons/thumb/4/47/New_york_times_square-terabass.jpg/1200px-New_york_times_square-terabass.jpg"
|
||||
),
|
||||
],
|
||||
),
|
||||
ChatMessageContent(
|
||||
role=AuthorRole.USER,
|
||||
items=[
|
||||
TextContent(text="What is the main color in this image?"),
|
||||
ImageContent(uri="https://upload.wikimedia.org/wikipedia/commons/5/56/White_shark.jpg"),
|
||||
],
|
||||
),
|
||||
ChatMessageContent(
|
||||
role=AuthorRole.USER,
|
||||
items=[
|
||||
TextContent(text="Is there an animal in this image?"),
|
||||
ImageContent.from_image_file(file_path),
|
||||
],
|
||||
),
|
||||
]
|
||||
|
||||
for user_input in user_messages:
|
||||
print(f"# User: {str(user_input)}") # type: ignore
|
||||
# 5. Invoke the agent with the current chat history and print the response
|
||||
response = await agent.get_response(messages=user_input, thread=thread)
|
||||
print(f"# Agent: {response.content}\n")
|
||||
thread = response.thread
|
||||
"""
|
||||
You should see output similar to the following:
|
||||
|
||||
# User: Describe this image.
|
||||
# Agent: The image depicts a bustling scene of Times Square in New York City...
|
||||
|
||||
# User: What is the main color in this image?
|
||||
# Agent: The main color in the image is blue.
|
||||
|
||||
# User: Is there an animal in this image?
|
||||
# Agent: Yes, there is a cat in the image.
|
||||
"""
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from semantic_kernel.agents import OpenAIResponsesAgent
|
||||
from semantic_kernel.connectors.ai.open_ai import OpenAISettings
|
||||
|
||||
"""
|
||||
The following sample demonstrates how to create an OpenAI Responses Agent.
|
||||
The sample shows how to have the agent provide response using structured outputs.
|
||||
|
||||
The interaction with the agent is via the `get_response` method, which sends a
|
||||
user input to the agent and receives a response from the agent. The conversation
|
||||
history is maintained by the chat history. Therefore, client code does need to
|
||||
maintain the conversation history if conversation context is desired.
|
||||
"""
|
||||
|
||||
user_inputs = ["how can I solve 8x + 7y = -23, and 4x=12?"]
|
||||
|
||||
|
||||
# Define the BaseModel we will use for structured outputs
|
||||
class Step(BaseModel):
|
||||
explanation: str
|
||||
output: str
|
||||
|
||||
|
||||
class Reasoning(BaseModel):
|
||||
steps: list[Step]
|
||||
final_answer: str
|
||||
|
||||
|
||||
async def main():
|
||||
# 1. Create the client using OpenAI resources and configuration
|
||||
# Note: the Azure OpenAI Responses API does not yet support structured outputs.
|
||||
client = OpenAIResponsesAgent.create_client()
|
||||
|
||||
# 2. Create a Semantic Kernel agent for the OpenAI Responses API
|
||||
agent = OpenAIResponsesAgent(
|
||||
ai_model_id=OpenAISettings().responses_model_id,
|
||||
client=client,
|
||||
instructions="Answer the user's questions.",
|
||||
name="StructuredOutputsAgent",
|
||||
text=OpenAIResponsesAgent.configure_response_format(Reasoning),
|
||||
)
|
||||
|
||||
# 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
|
||||
|
||||
for user_input in user_inputs:
|
||||
print(f"# User: {str(user_input)}") # type: ignore
|
||||
# 5. Invoke the agent with the current chat history and print the response
|
||||
response = await agent.get_response(messages=user_input, thread=thread)
|
||||
reasoned_result = Reasoning.model_validate_json(response.message.content)
|
||||
print(f"# {response.name}:\n\n{json.dumps(reasoned_result.model_dump(), indent=4, ensure_ascii=False)}")
|
||||
thread = response.thread
|
||||
|
||||
# 6. Clean up the thread
|
||||
await thread.delete() if thread else None
|
||||
|
||||
"""
|
||||
# User: how can I solve 8x + 7y = -23, and 4x=12?
|
||||
# StructuredOutputsAgent:
|
||||
|
||||
{
|
||||
"steps": [
|
||||
{
|
||||
"explanation": "First, solve the equation 4x = 12 to find the value of x.",
|
||||
"output": "4x = 12\nx = 12 / 4\nx = 3"
|
||||
},
|
||||
{
|
||||
"explanation": "Substitute x = 3 into the first equation 8x + 7y = -23.",
|
||||
"output": "8(3) + 7y = -23"
|
||||
},
|
||||
{
|
||||
"explanation": "Perform the multiplication and simplify the equation.",
|
||||
"output": "24 + 7y = -23"
|
||||
},
|
||||
{
|
||||
"explanation": "Subtract 24 from both sides to isolate the term with y.",
|
||||
"output": "7y = -23 - 24\n7y = -47"
|
||||
},
|
||||
{
|
||||
"explanation": "Divide by 7 to solve for y.",
|
||||
"output": "y = -47 / 7\ny = -6.71 (rounded to two decimal places)"
|
||||
}
|
||||
],
|
||||
"final_answer": "x = 3 and y = -6.71 (rounded to two decimal places)"
|
||||
}
|
||||
"""
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
+97
@@ -0,0 +1,97 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
import asyncio
|
||||
from typing import Annotated
|
||||
|
||||
from semantic_kernel.agents import AgentRegistry, OpenAIResponsesAgent
|
||||
from semantic_kernel.functions import kernel_function
|
||||
|
||||
"""
|
||||
The following sample demonstrates how to create an OpenAI Assistant agent that answers
|
||||
questions about a sample menu using a Semantic Kernel Plugin. The agent is created
|
||||
using a yaml declarative spec.
|
||||
"""
|
||||
|
||||
# Simulate a conversation with the agent
|
||||
USER_INPUTS = [
|
||||
"Hello",
|
||||
"What is the special soup?",
|
||||
"How much does that cost?",
|
||||
"Thank you",
|
||||
]
|
||||
|
||||
# Define the YAML string for the sample
|
||||
SPEC = """
|
||||
type: openai_responses
|
||||
name: Host
|
||||
instructions: Respond politely to the user's questions.
|
||||
model:
|
||||
id: ${OpenAI:ChatModelId}
|
||||
tools:
|
||||
- id: MenuPlugin.get_specials
|
||||
type: function
|
||||
- id: MenuPlugin.get_item_price
|
||||
type: 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) -> 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"
|
||||
|
||||
|
||||
async def main():
|
||||
# 1. Create the client using Azure OpenAI resources and configuration
|
||||
client = OpenAIResponsesAgent.create_client()
|
||||
|
||||
# 2. Create the assistant on the Azure OpenAI service
|
||||
agent: OpenAIResponsesAgent = await AgentRegistry.create_from_yaml(
|
||||
SPEC,
|
||||
plugins=[MenuPlugin()],
|
||||
client=client,
|
||||
)
|
||||
|
||||
# 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"# User: {user_input}")
|
||||
# 4. Invoke the agent for the specified thread for response
|
||||
async for response in agent.invoke(
|
||||
messages=user_input,
|
||||
thread=thread,
|
||||
):
|
||||
print(f"# {response.name}: {response}")
|
||||
thread = response.thread
|
||||
finally:
|
||||
# 5. Clean up the resources
|
||||
await thread.delete() if thread else None
|
||||
|
||||
"""
|
||||
Sample Output:
|
||||
|
||||
# User: Hello
|
||||
# Agent: Hello! How can I assist you today?
|
||||
# User: What is the special soup?
|
||||
# ...
|
||||
"""
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,13 @@
|
||||
On a dark winter night, a ghost walks the ramparts of Elsinore Castle in Denmark. Discovered first by a pair of watchmen, then by the scholar Horatio, the ghost resembles the recently deceased King Hamlet, whose brother Claudius has inherited the throne and married the king’s widow, Queen Gertrude. When Horatio and the watchmen bring Prince Hamlet, the son of Gertrude and the dead king, to see the ghost, it speaks to him, declaring ominously that it is indeed his father’s spirit, and that he was murdered by none other than Claudius. Ordering Hamlet to seek revenge on the man who usurped his throne and married his wife, the ghost disappears with the dawn.
|
||||
|
||||
Prince Hamlet devotes himself to avenging his father’s death, but, because he is contemplative and thoughtful by nature, he delays, entering into a deep melancholy and even apparent madness. Claudius and Gertrude worry about the prince’s erratic behavior and attempt to discover its cause. They employ a pair of Hamlet’s friends, Rosencrantz and Guildenstern, to watch him. When Polonius, the pompous Lord Chamberlain, suggests that Hamlet may be mad with love for his daughter, Ophelia, Claudius agrees to spy on Hamlet in conversation with the girl. But though Hamlet certainly seems mad, he does not seem to love Ophelia: he orders her to enter a nunnery and declares that he wishes to ban marriages.
|
||||
|
||||
A group of traveling actors comes to Elsinore, and Hamlet seizes upon an idea to test his uncle’s guilt. He will have the players perform a scene closely resembling the sequence by which Hamlet imagines his uncle to have murdered his father, so that if Claudius is guilty, he will surely react. When the moment of the murder arrives in the theater, Claudius leaps up and leaves the room. Hamlet and Horatio agree that this proves his guilt. Hamlet goes to kill Claudius but finds him praying. Since he believes that killing Claudius while in prayer would send Claudius’s soul to heaven, Hamlet considers that it would be an inadequate revenge and decides to wait. Claudius, now frightened of Hamlet’s madness and fearing for his own safety, orders that Hamlet be sent to England at once.
|
||||
|
||||
Hamlet goes to confront his mother, in whose bedchamber Polonius has hidden behind a tapestry. Hearing a noise from behind the tapestry, Hamlet believes the king is hiding there. He draws his sword and stabs through the fabric, killing Polonius. For this crime, he is immediately dispatched to England with Rosencrantz and Guildenstern. However, Claudius’s plan for Hamlet includes more than banishment, as he has given Rosencrantz and Guildenstern sealed orders for the King of England demanding that Hamlet be put to death.
|
||||
|
||||
In the aftermath of her father’s death, Ophelia goes mad with grief and drowns in the river. Polonius’s son, Laertes, who has been staying in France, returns to Denmark in a rage. Claudius convinces him that Hamlet is to blame for his father’s and sister’s deaths. When Horatio and the king receive letters from Hamlet indicating that the prince has returned to Denmark after pirates attacked his ship en route to England, Claudius concocts a plan to use Laertes’ desire for revenge to secure Hamlet’s death. Laertes will fence with Hamlet in innocent sport, but Claudius will poison Laertes’ blade so that if he draws blood, Hamlet will die. As a backup plan, the king decides to poison a goblet, which he will give Hamlet to drink should Hamlet score the first or second hits of the match. Hamlet returns to the vicinity of Elsinore just as Ophelia’s funeral is taking place. Stricken with grief, he attacks Laertes and declares that he had in fact always loved Ophelia. Back at the castle, he tells Horatio that he believes one must be prepared to die, since death can come at any moment. A foolish courtier named Osric arrives on Claudius’s orders to arrange the fencing match between Hamlet and Laertes.
|
||||
|
||||
The sword-fighting begins. Hamlet scores the first hit, but declines to drink from the king’s proffered goblet. Instead, Gertrude takes a drink from it and is swiftly killed by the poison. Laertes succeeds in wounding Hamlet, though Hamlet does not die of the poison immediately. First, Laertes is cut by his own sword’s blade, and, after revealing to Hamlet that Claudius is responsible for the queen’s death, he dies from the blade’s poison. Hamlet then stabs Claudius through with the poisoned sword and forces him to drink down the rest of the poisoned wine. Claudius dies, and Hamlet dies immediately after achieving his revenge.
|
||||
|
||||
At this moment, a Norwegian prince named Fortinbras, who has led an army to Denmark and attacked Poland earlier in the play, enters with ambassadors from England, who report that Rosencrantz and Guildenstern are dead. Fortinbras is stunned by the gruesome sight of the entire royal family lying sprawled on the floor dead. He moves to take power of the kingdom. Horatio, fulfilling Hamlet’s last request, tells him Hamlet’s tragic story. Fortinbras orders that Hamlet be carried away in a manner befitting a fallen soldier.
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 37 KiB |
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"openapi": "3.1.0",
|
||||
"info": {
|
||||
"title": "RestCountries.NET API",
|
||||
"description": "Web API version 3.1 for managing country items, based on previous implementations from restcountries.eu and restcountries.com.",
|
||||
"version": "v3.1"
|
||||
},
|
||||
"servers": [
|
||||
{ "url": "https://restcountries.net" }
|
||||
],
|
||||
"auth": [],
|
||||
"paths": {
|
||||
"/v3.1/currency": {
|
||||
"get": {
|
||||
"description": "Search by currency.",
|
||||
"operationId": "LookupCountryByCurrency",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "currency",
|
||||
"in": "query",
|
||||
"description": "The currency to search for.",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Success",
|
||||
"content": {
|
||||
"text/plain": {
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemes": {}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
@@ -0,0 +1,701 @@
|
||||
Segment,Country,Product,Units Sold,Sale Price,Gross Sales,Discounts,Sales,COGS,Profit,Date,Month Number,Month Name,Year
|
||||
Government,Canada,Carretera,1618.5,20.00,32370.00,0.00,32370.00,16185.00,16185.00,1/1/2014,1,January,2014
|
||||
Government,Germany,Carretera,1321,20.00,26420.00,0.00,26420.00,13210.00,13210.00,1/1/2014,1,January,2014
|
||||
Midmarket,France,Carretera,2178,15.00,32670.00,0.00,32670.00,21780.00,10890.00,6/1/2014,6,June,2014
|
||||
Midmarket,Germany,Carretera,888,15.00,13320.00,0.00,13320.00,8880.00,4440.00,6/1/2014,6,June,2014
|
||||
Midmarket,Mexico,Carretera,2470,15.00,37050.00,0.00,37050.00,24700.00,12350.00,6/1/2014,6,June,2014
|
||||
Government,Germany,Carretera,1513,350.00,529550.00,0.00,529550.00,393380.00,136170.00,12/1/2014,12,December,2014
|
||||
Midmarket,Germany,Montana,921,15.00,13815.00,0.00,13815.00,9210.00,4605.00,3/1/2014,3,March,2014
|
||||
Channel Partners,Canada,Montana,2518,12.00,30216.00,0.00,30216.00,7554.00,22662.00,6/1/2014,6,June,2014
|
||||
Government,France,Montana,1899,20.00,37980.00,0.00,37980.00,18990.00,18990.00,6/1/2014,6,June,2014
|
||||
Channel Partners,Germany,Montana,1545,12.00,18540.00,0.00,18540.00,4635.00,13905.00,6/1/2014,6,June,2014
|
||||
Midmarket,Mexico,Montana,2470,15.00,37050.00,0.00,37050.00,24700.00,12350.00,6/1/2014,6,June,2014
|
||||
Enterprise,Canada,Montana,2665.5,125.00,333187.50,0.00,333187.50,319860.00,13327.50,7/1/2014,7,July,2014
|
||||
Small Business,Mexico,Montana,958,300.00,287400.00,0.00,287400.00,239500.00,47900.00,8/1/2014,8,August,2014
|
||||
Government,Germany,Montana,2146,7.00,15022.00,0.00,15022.00,10730.00,4292.00,9/1/2014,9,September,2014
|
||||
Enterprise,Canada,Montana,345,125.00,43125.00,0.00,43125.00,41400.00,1725.00,10/1/2013,10,October,2013
|
||||
Midmarket,United States of America,Montana,615,15.00,9225.00,0.00,9225.00,6150.00,3075.00,12/1/2014,12,December,2014
|
||||
Government,Canada,Paseo,292,20.00,5840.00,0.00,5840.00,2920.00,2920.00,2/1/2014,2,February,2014
|
||||
Midmarket,Mexico,Paseo,974,15.00,14610.00,0.00,14610.00,9740.00,4870.00,2/1/2014,2,February,2014
|
||||
Channel Partners,Canada,Paseo,2518,12.00,30216.00,0.00,30216.00,7554.00,22662.00,6/1/2014,6,June,2014
|
||||
Government,Germany,Paseo,1006,350.00,352100.00,0.00,352100.00,261560.00,90540.00,6/1/2014,6,June,2014
|
||||
Channel Partners,Germany,Paseo,367,12.00,4404.00,0.00,4404.00,1101.00,3303.00,7/1/2014,7,July,2014
|
||||
Government,Mexico,Paseo,883,7.00,6181.00,0.00,6181.00,4415.00,1766.00,8/1/2014,8,August,2014
|
||||
Midmarket,France,Paseo,549,15.00,8235.00,0.00,8235.00,5490.00,2745.00,9/1/2013,9,September,2013
|
||||
Small Business,Mexico,Paseo,788,300.00,236400.00,0.00,236400.00,197000.00,39400.00,9/1/2013,9,September,2013
|
||||
Midmarket,Mexico,Paseo,2472,15.00,37080.00,0.00,37080.00,24720.00,12360.00,9/1/2014,9,September,2014
|
||||
Government,United States of America,Paseo,1143,7.00,8001.00,0.00,8001.00,5715.00,2286.00,10/1/2014,10,October,2014
|
||||
Government,Canada,Paseo,1725,350.00,603750.00,0.00,603750.00,448500.00,155250.00,11/1/2013,11,November,2013
|
||||
Channel Partners,United States of America,Paseo,912,12.00,10944.00,0.00,10944.00,2736.00,8208.00,11/1/2013,11,November,2013
|
||||
Midmarket,Canada,Paseo,2152,15.00,32280.00,0.00,32280.00,21520.00,10760.00,12/1/2013,12,December,2013
|
||||
Government,Canada,Paseo,1817,20.00,36340.00,0.00,36340.00,18170.00,18170.00,12/1/2014,12,December,2014
|
||||
Government,Germany,Paseo,1513,350.00,529550.00,0.00,529550.00,393380.00,136170.00,12/1/2014,12,December,2014
|
||||
Government,Mexico,Velo,1493,7.00,10451.00,0.00,10451.00,7465.00,2986.00,1/1/2014,1,January,2014
|
||||
Enterprise,France,Velo,1804,125.00,225500.00,0.00,225500.00,216480.00,9020.00,2/1/2014,2,February,2014
|
||||
Channel Partners,Germany,Velo,2161,12.00,25932.00,0.00,25932.00,6483.00,19449.00,3/1/2014,3,March,2014
|
||||
Government,Germany,Velo,1006,350.00,352100.00,0.00,352100.00,261560.00,90540.00,6/1/2014,6,June,2014
|
||||
Channel Partners,Germany,Velo,1545,12.00,18540.00,0.00,18540.00,4635.00,13905.00,6/1/2014,6,June,2014
|
||||
Enterprise,United States of America,Velo,2821,125.00,352625.00,0.00,352625.00,338520.00,14105.00,8/1/2014,8,August,2014
|
||||
Enterprise,Canada,Velo,345,125.00,43125.00,0.00,43125.00,41400.00,1725.00,10/1/2013,10,October,2013
|
||||
Small Business,Canada,VTT,2001,300.00,600300.00,0.00,600300.00,500250.00,100050.00,2/1/2014,2,February,2014
|
||||
Channel Partners,Germany,VTT,2838,12.00,34056.00,0.00,34056.00,8514.00,25542.00,4/1/2014,4,April,2014
|
||||
Midmarket,France,VTT,2178,15.00,32670.00,0.00,32670.00,21780.00,10890.00,6/1/2014,6,June,2014
|
||||
Midmarket,Germany,VTT,888,15.00,13320.00,0.00,13320.00,8880.00,4440.00,6/1/2014,6,June,2014
|
||||
Government,France,VTT,1527,350.00,534450.00,0.00,534450.00,397020.00,137430.00,9/1/2013,9,September,2013
|
||||
Small Business,France,VTT,2151,300.00,645300.00,0.00,645300.00,537750.00,107550.00,9/1/2014,9,September,2014
|
||||
Government,Canada,VTT,1817,20.00,36340.00,0.00,36340.00,18170.00,18170.00,12/1/2014,12,December,2014
|
||||
Government,France,Amarilla,2750,350.00,962500.00,0.00,962500.00,715000.00,247500.00,2/1/2014,2,February,2014
|
||||
Channel Partners,United States of America,Amarilla,1953,12.00,23436.00,0.00,23436.00,5859.00,17577.00,4/1/2014,4,April,2014
|
||||
Enterprise,Germany,Amarilla,4219.5,125.00,527437.50,0.00,527437.50,506340.00,21097.50,4/1/2014,4,April,2014
|
||||
Government,France,Amarilla,1899,20.00,37980.00,0.00,37980.00,18990.00,18990.00,6/1/2014,6,June,2014
|
||||
Government,Germany,Amarilla,1686,7.00,11802.00,0.00,11802.00,8430.00,3372.00,7/1/2014,7,July,2014
|
||||
Channel Partners,United States of America,Amarilla,2141,12.00,25692.00,0.00,25692.00,6423.00,19269.00,8/1/2014,8,August,2014
|
||||
Government,United States of America,Amarilla,1143,7.00,8001.00,0.00,8001.00,5715.00,2286.00,10/1/2014,10,October,2014
|
||||
Midmarket,United States of America,Amarilla,615,15.00,9225.00,0.00,9225.00,6150.00,3075.00,12/1/2014,12,December,2014
|
||||
Government,France,Paseo,3945,7.00,27615.00,276.15,27338.85,19725.00,7613.85,1/1/2014,1,January,2014
|
||||
Midmarket,France,Paseo,2296,15.00,34440.00,344.40,34095.60,22960.00,11135.60,2/1/2014,2,February,2014
|
||||
Government,France,Paseo,1030,7.00,7210.00,72.10,7137.90,5150.00,1987.90,5/1/2014,5,May,2014
|
||||
Government,France,Velo,639,7.00,4473.00,44.73,4428.27,3195.00,1233.27,11/1/2014,11,November,2014
|
||||
Government,Canada,VTT,1326,7.00,9282.00,92.82,9189.18,6630.00,2559.18,3/1/2014,3,March,2014
|
||||
Channel Partners,United States of America,Carretera,1858,12.00,22296.00,222.96,22073.04,5574.00,16499.04,2/1/2014,2,February,2014
|
||||
Government,Mexico,Carretera,1210,350.00,423500.00,4235.00,419265.00,314600.00,104665.00,3/1/2014,3,March,2014
|
||||
Government,United States of America,Carretera,2529,7.00,17703.00,177.03,17525.97,12645.00,4880.97,7/1/2014,7,July,2014
|
||||
Channel Partners,Canada,Carretera,1445,12.00,17340.00,173.40,17166.60,4335.00,12831.60,9/1/2014,9,September,2014
|
||||
Enterprise,United States of America,Carretera,330,125.00,41250.00,412.50,40837.50,39600.00,1237.50,9/1/2013,9,September,2013
|
||||
Channel Partners,France,Carretera,2671,12.00,32052.00,320.52,31731.48,8013.00,23718.48,9/1/2014,9,September,2014
|
||||
Channel Partners,Germany,Carretera,766,12.00,9192.00,91.92,9100.08,2298.00,6802.08,10/1/2013,10,October,2013
|
||||
Small Business,Mexico,Carretera,494,300.00,148200.00,1482.00,146718.00,123500.00,23218.00,10/1/2013,10,October,2013
|
||||
Government,Mexico,Carretera,1397,350.00,488950.00,4889.50,484060.50,363220.00,120840.50,10/1/2014,10,October,2014
|
||||
Government,France,Carretera,2155,350.00,754250.00,7542.50,746707.50,560300.00,186407.50,12/1/2014,12,December,2014
|
||||
Midmarket,Mexico,Montana,2214,15.00,33210.00,332.10,32877.90,22140.00,10737.90,3/1/2014,3,March,2014
|
||||
Small Business,United States of America,Montana,2301,300.00,690300.00,6903.00,683397.00,575250.00,108147.00,4/1/2014,4,April,2014
|
||||
Government,France,Montana,1375.5,20.00,27510.00,275.10,27234.90,13755.00,13479.90,7/1/2014,7,July,2014
|
||||
Government,Canada,Montana,1830,7.00,12810.00,128.10,12681.90,9150.00,3531.90,8/1/2014,8,August,2014
|
||||
Small Business,United States of America,Montana,2498,300.00,749400.00,7494.00,741906.00,624500.00,117406.00,9/1/2013,9,September,2013
|
||||
Enterprise,United States of America,Montana,663,125.00,82875.00,828.75,82046.25,79560.00,2486.25,10/1/2013,10,October,2013
|
||||
Midmarket,United States of America,Paseo,1514,15.00,22710.00,227.10,22482.90,15140.00,7342.90,2/1/2014,2,February,2014
|
||||
Government,United States of America,Paseo,4492.5,7.00,31447.50,314.48,31133.03,22462.50,8670.53,4/1/2014,4,April,2014
|
||||
Enterprise,United States of America,Paseo,727,125.00,90875.00,908.75,89966.25,87240.00,2726.25,6/1/2014,6,June,2014
|
||||
Enterprise,France,Paseo,787,125.00,98375.00,983.75,97391.25,94440.00,2951.25,6/1/2014,6,June,2014
|
||||
Enterprise,Mexico,Paseo,1823,125.00,227875.00,2278.75,225596.25,218760.00,6836.25,7/1/2014,7,July,2014
|
||||
Midmarket,Germany,Paseo,747,15.00,11205.00,112.05,11092.95,7470.00,3622.95,9/1/2014,9,September,2014
|
||||
Channel Partners,Germany,Paseo,766,12.00,9192.00,91.92,9100.08,2298.00,6802.08,10/1/2013,10,October,2013
|
||||
Small Business,United States of America,Paseo,2905,300.00,871500.00,8715.00,862785.00,726250.00,136535.00,11/1/2014,11,November,2014
|
||||
Government,France,Paseo,2155,350.00,754250.00,7542.50,746707.50,560300.00,186407.50,12/1/2014,12,December,2014
|
||||
Government,France,Velo,3864,20.00,77280.00,772.80,76507.20,38640.00,37867.20,4/1/2014,4,April,2014
|
||||
Government,Mexico,Velo,362,7.00,2534.00,25.34,2508.66,1810.00,698.66,5/1/2014,5,May,2014
|
||||
Enterprise,Canada,Velo,923,125.00,115375.00,1153.75,114221.25,110760.00,3461.25,8/1/2014,8,August,2014
|
||||
Enterprise,United States of America,Velo,663,125.00,82875.00,828.75,82046.25,79560.00,2486.25,10/1/2013,10,October,2013
|
||||
Government,Canada,Velo,2092,7.00,14644.00,146.44,14497.56,10460.00,4037.56,11/1/2013,11,November,2013
|
||||
Government,Germany,VTT,263,7.00,1841.00,18.41,1822.59,1315.00,507.59,3/1/2014,3,March,2014
|
||||
Government,Canada,VTT,943.5,350.00,330225.00,3302.25,326922.75,245310.00,81612.75,4/1/2014,4,April,2014
|
||||
Enterprise,United States of America,VTT,727,125.00,90875.00,908.75,89966.25,87240.00,2726.25,6/1/2014,6,June,2014
|
||||
Enterprise,France,VTT,787,125.00,98375.00,983.75,97391.25,94440.00,2951.25,6/1/2014,6,June,2014
|
||||
Small Business,Germany,VTT,986,300.00,295800.00,2958.00,292842.00,246500.00,46342.00,9/1/2014,9,September,2014
|
||||
Small Business,Mexico,VTT,494,300.00,148200.00,1482.00,146718.00,123500.00,23218.00,10/1/2013,10,October,2013
|
||||
Government,Mexico,VTT,1397,350.00,488950.00,4889.50,484060.50,363220.00,120840.50,10/1/2014,10,October,2014
|
||||
Enterprise,France,VTT,1744,125.00,218000.00,2180.00,215820.00,209280.00,6540.00,11/1/2014,11,November,2014
|
||||
Channel Partners,United States of America,Amarilla,1989,12.00,23868.00,238.68,23629.32,5967.00,17662.32,9/1/2013,9,September,2013
|
||||
Midmarket,France,Amarilla,321,15.00,4815.00,48.15,4766.85,3210.00,1556.85,11/1/2013,11,November,2013
|
||||
Enterprise,Canada,Carretera,742.5,125.00,92812.50,1856.25,90956.25,89100.00,1856.25,4/1/2014,4,April,2014
|
||||
Channel Partners,Canada,Carretera,1295,12.00,15540.00,310.80,15229.20,3885.00,11344.20,10/1/2014,10,October,2014
|
||||
Small Business,Germany,Carretera,214,300.00,64200.00,1284.00,62916.00,53500.00,9416.00,10/1/2013,10,October,2013
|
||||
Government,France,Carretera,2145,7.00,15015.00,300.30,14714.70,10725.00,3989.70,11/1/2013,11,November,2013
|
||||
Government,Canada,Carretera,2852,350.00,998200.00,19964.00,978236.00,741520.00,236716.00,12/1/2014,12,December,2014
|
||||
Channel Partners,United States of America,Montana,1142,12.00,13704.00,274.08,13429.92,3426.00,10003.92,6/1/2014,6,June,2014
|
||||
Government,United States of America,Montana,1566,20.00,31320.00,626.40,30693.60,15660.00,15033.60,10/1/2014,10,October,2014
|
||||
Channel Partners,Mexico,Montana,690,12.00,8280.00,165.60,8114.40,2070.00,6044.40,11/1/2014,11,November,2014
|
||||
Enterprise,Mexico,Montana,1660,125.00,207500.00,4150.00,203350.00,199200.00,4150.00,11/1/2013,11,November,2013
|
||||
Midmarket,Canada,Paseo,2363,15.00,35445.00,708.90,34736.10,23630.00,11106.10,2/1/2014,2,February,2014
|
||||
Small Business,France,Paseo,918,300.00,275400.00,5508.00,269892.00,229500.00,40392.00,5/1/2014,5,May,2014
|
||||
Small Business,Germany,Paseo,1728,300.00,518400.00,10368.00,508032.00,432000.00,76032.00,5/1/2014,5,May,2014
|
||||
Channel Partners,United States of America,Paseo,1142,12.00,13704.00,274.08,13429.92,3426.00,10003.92,6/1/2014,6,June,2014
|
||||
Enterprise,Mexico,Paseo,662,125.00,82750.00,1655.00,81095.00,79440.00,1655.00,6/1/2014,6,June,2014
|
||||
Channel Partners,Canada,Paseo,1295,12.00,15540.00,310.80,15229.20,3885.00,11344.20,10/1/2014,10,October,2014
|
||||
Enterprise,Germany,Paseo,809,125.00,101125.00,2022.50,99102.50,97080.00,2022.50,10/1/2013,10,October,2013
|
||||
Enterprise,Mexico,Paseo,2145,125.00,268125.00,5362.50,262762.50,257400.00,5362.50,10/1/2013,10,October,2013
|
||||
Channel Partners,France,Paseo,1785,12.00,21420.00,428.40,20991.60,5355.00,15636.60,11/1/2013,11,November,2013
|
||||
Small Business,Canada,Paseo,1916,300.00,574800.00,11496.00,563304.00,479000.00,84304.00,12/1/2014,12,December,2014
|
||||
Government,Canada,Paseo,2852,350.00,998200.00,19964.00,978236.00,741520.00,236716.00,12/1/2014,12,December,2014
|
||||
Enterprise,Canada,Paseo,2729,125.00,341125.00,6822.50,334302.50,327480.00,6822.50,12/1/2014,12,December,2014
|
||||
Midmarket,United States of America,Paseo,1925,15.00,28875.00,577.50,28297.50,19250.00,9047.50,12/1/2013,12,December,2013
|
||||
Government,United States of America,Paseo,2013,7.00,14091.00,281.82,13809.18,10065.00,3744.18,12/1/2013,12,December,2013
|
||||
Channel Partners,France,Paseo,1055,12.00,12660.00,253.20,12406.80,3165.00,9241.80,12/1/2014,12,December,2014
|
||||
Channel Partners,Mexico,Paseo,1084,12.00,13008.00,260.16,12747.84,3252.00,9495.84,12/1/2014,12,December,2014
|
||||
Government,United States of America,Velo,1566,20.00,31320.00,626.40,30693.60,15660.00,15033.60,10/1/2014,10,October,2014
|
||||
Government,Germany,Velo,2966,350.00,1038100.00,20762.00,1017338.00,771160.00,246178.00,10/1/2013,10,October,2013
|
||||
Government,Germany,Velo,2877,350.00,1006950.00,20139.00,986811.00,748020.00,238791.00,10/1/2014,10,October,2014
|
||||
Enterprise,Germany,Velo,809,125.00,101125.00,2022.50,99102.50,97080.00,2022.50,10/1/2013,10,October,2013
|
||||
Enterprise,Mexico,Velo,2145,125.00,268125.00,5362.50,262762.50,257400.00,5362.50,10/1/2013,10,October,2013
|
||||
Channel Partners,France,Velo,1055,12.00,12660.00,253.20,12406.80,3165.00,9241.80,12/1/2014,12,December,2014
|
||||
Government,Mexico,Velo,544,20.00,10880.00,217.60,10662.40,5440.00,5222.40,12/1/2013,12,December,2013
|
||||
Channel Partners,Mexico,Velo,1084,12.00,13008.00,260.16,12747.84,3252.00,9495.84,12/1/2014,12,December,2014
|
||||
Enterprise,Mexico,VTT,662,125.00,82750.00,1655.00,81095.00,79440.00,1655.00,6/1/2014,6,June,2014
|
||||
Small Business,Germany,VTT,214,300.00,64200.00,1284.00,62916.00,53500.00,9416.00,10/1/2013,10,October,2013
|
||||
Government,Germany,VTT,2877,350.00,1006950.00,20139.00,986811.00,748020.00,238791.00,10/1/2014,10,October,2014
|
||||
Enterprise,Canada,VTT,2729,125.00,341125.00,6822.50,334302.50,327480.00,6822.50,12/1/2014,12,December,2014
|
||||
Government,United States of America,VTT,266,350.00,93100.00,1862.00,91238.00,69160.00,22078.00,12/1/2013,12,December,2013
|
||||
Government,Mexico,VTT,1940,350.00,679000.00,13580.00,665420.00,504400.00,161020.00,12/1/2013,12,December,2013
|
||||
Small Business,Germany,Amarilla,259,300.00,77700.00,1554.00,76146.00,64750.00,11396.00,3/1/2014,3,March,2014
|
||||
Small Business,Mexico,Amarilla,1101,300.00,330300.00,6606.00,323694.00,275250.00,48444.00,3/1/2014,3,March,2014
|
||||
Enterprise,Germany,Amarilla,2276,125.00,284500.00,5690.00,278810.00,273120.00,5690.00,5/1/2014,5,May,2014
|
||||
Government,Germany,Amarilla,2966,350.00,1038100.00,20762.00,1017338.00,771160.00,246178.00,10/1/2013,10,October,2013
|
||||
Government,United States of America,Amarilla,1236,20.00,24720.00,494.40,24225.60,12360.00,11865.60,11/1/2014,11,November,2014
|
||||
Government,France,Amarilla,941,20.00,18820.00,376.40,18443.60,9410.00,9033.60,11/1/2014,11,November,2014
|
||||
Small Business,Canada,Amarilla,1916,300.00,574800.00,11496.00,563304.00,479000.00,84304.00,12/1/2014,12,December,2014
|
||||
Enterprise,France,Carretera,4243.5,125.00,530437.50,15913.13,514524.38,509220.00,5304.38,4/1/2014,4,April,2014
|
||||
Government,Germany,Carretera,2580,20.00,51600.00,1548.00,50052.00,25800.00,24252.00,4/1/2014,4,April,2014
|
||||
Small Business,Germany,Carretera,689,300.00,206700.00,6201.00,200499.00,172250.00,28249.00,6/1/2014,6,June,2014
|
||||
Channel Partners,United States of America,Carretera,1947,12.00,23364.00,700.92,22663.08,5841.00,16822.08,9/1/2014,9,September,2014
|
||||
Channel Partners,Canada,Carretera,908,12.00,10896.00,326.88,10569.12,2724.00,7845.12,12/1/2013,12,December,2013
|
||||
Government,Germany,Montana,1958,7.00,13706.00,411.18,13294.82,9790.00,3504.82,2/1/2014,2,February,2014
|
||||
Channel Partners,France,Montana,1901,12.00,22812.00,684.36,22127.64,5703.00,16424.64,6/1/2014,6,June,2014
|
||||
Government,France,Montana,544,7.00,3808.00,114.24,3693.76,2720.00,973.76,9/1/2014,9,September,2014
|
||||
Government,Germany,Montana,1797,350.00,628950.00,18868.50,610081.50,467220.00,142861.50,9/1/2013,9,September,2013
|
||||
Enterprise,France,Montana,1287,125.00,160875.00,4826.25,156048.75,154440.00,1608.75,12/1/2014,12,December,2014
|
||||
Enterprise,Germany,Montana,1706,125.00,213250.00,6397.50,206852.50,204720.00,2132.50,12/1/2014,12,December,2014
|
||||
Small Business,France,Paseo,2434.5,300.00,730350.00,21910.50,708439.50,608625.00,99814.50,1/1/2014,1,January,2014
|
||||
Enterprise,Canada,Paseo,1774,125.00,221750.00,6652.50,215097.50,212880.00,2217.50,3/1/2014,3,March,2014
|
||||
Channel Partners,France,Paseo,1901,12.00,22812.00,684.36,22127.64,5703.00,16424.64,6/1/2014,6,June,2014
|
||||
Small Business,Germany,Paseo,689,300.00,206700.00,6201.00,200499.00,172250.00,28249.00,6/1/2014,6,June,2014
|
||||
Enterprise,Germany,Paseo,1570,125.00,196250.00,5887.50,190362.50,188400.00,1962.50,6/1/2014,6,June,2014
|
||||
Channel Partners,United States of America,Paseo,1369.5,12.00,16434.00,493.02,15940.98,4108.50,11832.48,7/1/2014,7,July,2014
|
||||
Enterprise,Canada,Paseo,2009,125.00,251125.00,7533.75,243591.25,241080.00,2511.25,10/1/2014,10,October,2014
|
||||
Midmarket,Germany,Paseo,1945,15.00,29175.00,875.25,28299.75,19450.00,8849.75,10/1/2013,10,October,2013
|
||||
Enterprise,France,Paseo,1287,125.00,160875.00,4826.25,156048.75,154440.00,1608.75,12/1/2014,12,December,2014
|
||||
Enterprise,Germany,Paseo,1706,125.00,213250.00,6397.50,206852.50,204720.00,2132.50,12/1/2014,12,December,2014
|
||||
Enterprise,Canada,Velo,2009,125.00,251125.00,7533.75,243591.25,241080.00,2511.25,10/1/2014,10,October,2014
|
||||
Small Business,United States of America,VTT,2844,300.00,853200.00,25596.00,827604.00,711000.00,116604.00,2/1/2014,2,February,2014
|
||||
Channel Partners,Mexico,VTT,1916,12.00,22992.00,689.76,22302.24,5748.00,16554.24,4/1/2014,4,April,2014
|
||||
Enterprise,Germany,VTT,1570,125.00,196250.00,5887.50,190362.50,188400.00,1962.50,6/1/2014,6,June,2014
|
||||
Small Business,Canada,VTT,1874,300.00,562200.00,16866.00,545334.00,468500.00,76834.00,8/1/2014,8,August,2014
|
||||
Government,Mexico,VTT,1642,350.00,574700.00,17241.00,557459.00,426920.00,130539.00,8/1/2014,8,August,2014
|
||||
Midmarket,Germany,VTT,1945,15.00,29175.00,875.25,28299.75,19450.00,8849.75,10/1/2013,10,October,2013
|
||||
Government,Canada,Carretera,831,20.00,16620.00,498.60,16121.40,8310.00,7811.40,5/1/2014,5,May,2014
|
||||
Government,Mexico,Paseo,1760,7.00,12320.00,369.60,11950.40,8800.00,3150.40,9/1/2013,9,September,2013
|
||||
Government,Canada,Velo,3850.5,20.00,77010.00,2310.30,74699.70,38505.00,36194.70,4/1/2014,4,April,2014
|
||||
Channel Partners,Germany,VTT,2479,12.00,29748.00,892.44,28855.56,7437.00,21418.56,1/1/2014,1,January,2014
|
||||
Midmarket,Mexico,Montana,2031,15.00,30465.00,1218.60,29246.40,20310.00,8936.40,10/1/2014,10,October,2014
|
||||
Midmarket,Mexico,Paseo,2031,15.00,30465.00,1218.60,29246.40,20310.00,8936.40,10/1/2014,10,October,2014
|
||||
Midmarket,France,Paseo,2261,15.00,33915.00,1356.60,32558.40,22610.00,9948.40,12/1/2013,12,December,2013
|
||||
Government,United States of America,Velo,736,20.00,14720.00,588.80,14131.20,7360.00,6771.20,9/1/2013,9,September,2013
|
||||
Government,Canada,Carretera,2851,7.00,19957.00,798.28,19158.72,14255.00,4903.72,10/1/2013,10,October,2013
|
||||
Small Business,Germany,Carretera,2021,300.00,606300.00,24252.00,582048.00,505250.00,76798.00,10/1/2014,10,October,2014
|
||||
Government,United States of America,Carretera,274,350.00,95900.00,3836.00,92064.00,71240.00,20824.00,12/1/2014,12,December,2014
|
||||
Midmarket,Canada,Montana,1967,15.00,29505.00,1180.20,28324.80,19670.00,8654.80,3/1/2014,3,March,2014
|
||||
Small Business,Germany,Montana,1859,300.00,557700.00,22308.00,535392.00,464750.00,70642.00,8/1/2014,8,August,2014
|
||||
Government,Canada,Montana,2851,7.00,19957.00,798.28,19158.72,14255.00,4903.72,10/1/2013,10,October,2013
|
||||
Small Business,Germany,Montana,2021,300.00,606300.00,24252.00,582048.00,505250.00,76798.00,10/1/2014,10,October,2014
|
||||
Enterprise,Mexico,Montana,1138,125.00,142250.00,5690.00,136560.00,136560.00,0.00,12/1/2014,12,December,2014
|
||||
Government,Canada,Paseo,4251,7.00,29757.00,1190.28,28566.72,21255.00,7311.72,1/1/2014,1,January,2014
|
||||
Enterprise,Germany,Paseo,795,125.00,99375.00,3975.00,95400.00,95400.00,0.00,3/1/2014,3,March,2014
|
||||
Small Business,Germany,Paseo,1414.5,300.00,424350.00,16974.00,407376.00,353625.00,53751.00,4/1/2014,4,April,2014
|
||||
Small Business,United States of America,Paseo,2918,300.00,875400.00,35016.00,840384.00,729500.00,110884.00,5/1/2014,5,May,2014
|
||||
Government,United States of America,Paseo,3450,350.00,1207500.00,48300.00,1159200.00,897000.00,262200.00,7/1/2014,7,July,2014
|
||||
Enterprise,France,Paseo,2988,125.00,373500.00,14940.00,358560.00,358560.00,0.00,7/1/2014,7,July,2014
|
||||
Midmarket,Canada,Paseo,218,15.00,3270.00,130.80,3139.20,2180.00,959.20,9/1/2014,9,September,2014
|
||||
Government,Canada,Paseo,2074,20.00,41480.00,1659.20,39820.80,20740.00,19080.80,9/1/2014,9,September,2014
|
||||
Government,United States of America,Paseo,1056,20.00,21120.00,844.80,20275.20,10560.00,9715.20,9/1/2014,9,September,2014
|
||||
Midmarket,United States of America,Paseo,671,15.00,10065.00,402.60,9662.40,6710.00,2952.40,10/1/2013,10,October,2013
|
||||
Midmarket,Mexico,Paseo,1514,15.00,22710.00,908.40,21801.60,15140.00,6661.60,10/1/2013,10,October,2013
|
||||
Government,United States of America,Paseo,274,350.00,95900.00,3836.00,92064.00,71240.00,20824.00,12/1/2014,12,December,2014
|
||||
Enterprise,Mexico,Paseo,1138,125.00,142250.00,5690.00,136560.00,136560.00,0.00,12/1/2014,12,December,2014
|
||||
Channel Partners,United States of America,Velo,1465,12.00,17580.00,703.20,16876.80,4395.00,12481.80,3/1/2014,3,March,2014
|
||||
Government,Canada,Velo,2646,20.00,52920.00,2116.80,50803.20,26460.00,24343.20,9/1/2013,9,September,2013
|
||||
Government,France,Velo,2177,350.00,761950.00,30478.00,731472.00,566020.00,165452.00,10/1/2014,10,October,2014
|
||||
Channel Partners,France,VTT,866,12.00,10392.00,415.68,9976.32,2598.00,7378.32,5/1/2014,5,May,2014
|
||||
Government,United States of America,VTT,349,350.00,122150.00,4886.00,117264.00,90740.00,26524.00,9/1/2013,9,September,2013
|
||||
Government,France,VTT,2177,350.00,761950.00,30478.00,731472.00,566020.00,165452.00,10/1/2014,10,October,2014
|
||||
Midmarket,Mexico,VTT,1514,15.00,22710.00,908.40,21801.60,15140.00,6661.60,10/1/2013,10,October,2013
|
||||
Government,Mexico,Amarilla,1865,350.00,652750.00,26110.00,626640.00,484900.00,141740.00,2/1/2014,2,February,2014
|
||||
Enterprise,Mexico,Amarilla,1074,125.00,134250.00,5370.00,128880.00,128880.00,0.00,4/1/2014,4,April,2014
|
||||
Government,Germany,Amarilla,1907,350.00,667450.00,26698.00,640752.00,495820.00,144932.00,9/1/2014,9,September,2014
|
||||
Midmarket,United States of America,Amarilla,671,15.00,10065.00,402.60,9662.40,6710.00,2952.40,10/1/2013,10,October,2013
|
||||
Government,Canada,Amarilla,1778,350.00,622300.00,24892.00,597408.00,462280.00,135128.00,12/1/2013,12,December,2013
|
||||
Government,Germany,Montana,1159,7.00,8113.00,405.65,7707.35,5795.00,1912.35,10/1/2013,10,October,2013
|
||||
Government,Germany,Paseo,1372,7.00,9604.00,480.20,9123.80,6860.00,2263.80,1/1/2014,1,January,2014
|
||||
Government,Canada,Paseo,2349,7.00,16443.00,822.15,15620.85,11745.00,3875.85,9/1/2013,9,September,2013
|
||||
Government,Mexico,Paseo,2689,7.00,18823.00,941.15,17881.85,13445.00,4436.85,10/1/2014,10,October,2014
|
||||
Channel Partners,Canada,Paseo,2431,12.00,29172.00,1458.60,27713.40,7293.00,20420.40,12/1/2014,12,December,2014
|
||||
Channel Partners,Canada,Velo,2431,12.00,29172.00,1458.60,27713.40,7293.00,20420.40,12/1/2014,12,December,2014
|
||||
Government,Mexico,VTT,2689,7.00,18823.00,941.15,17881.85,13445.00,4436.85,10/1/2014,10,October,2014
|
||||
Government,Mexico,Amarilla,1683,7.00,11781.00,589.05,11191.95,8415.00,2776.95,7/1/2014,7,July,2014
|
||||
Channel Partners,Mexico,Amarilla,1123,12.00,13476.00,673.80,12802.20,3369.00,9433.20,8/1/2014,8,August,2014
|
||||
Government,Germany,Amarilla,1159,7.00,8113.00,405.65,7707.35,5795.00,1912.35,10/1/2013,10,October,2013
|
||||
Channel Partners,France,Carretera,1865,12.00,22380.00,1119.00,21261.00,5595.00,15666.00,2/1/2014,2,February,2014
|
||||
Channel Partners,Germany,Carretera,1116,12.00,13392.00,669.60,12722.40,3348.00,9374.40,2/1/2014,2,February,2014
|
||||
Government,France,Carretera,1563,20.00,31260.00,1563.00,29697.00,15630.00,14067.00,5/1/2014,5,May,2014
|
||||
Small Business,United States of America,Carretera,991,300.00,297300.00,14865.00,282435.00,247750.00,34685.00,6/1/2014,6,June,2014
|
||||
Government,Germany,Carretera,1016,7.00,7112.00,355.60,6756.40,5080.00,1676.40,11/1/2013,11,November,2013
|
||||
Midmarket,Mexico,Carretera,2791,15.00,41865.00,2093.25,39771.75,27910.00,11861.75,11/1/2014,11,November,2014
|
||||
Government,United States of America,Carretera,570,7.00,3990.00,199.50,3790.50,2850.00,940.50,12/1/2014,12,December,2014
|
||||
Government,France,Carretera,2487,7.00,17409.00,870.45,16538.55,12435.00,4103.55,12/1/2014,12,December,2014
|
||||
Government,France,Montana,1384.5,350.00,484575.00,24228.75,460346.25,359970.00,100376.25,1/1/2014,1,January,2014
|
||||
Enterprise,United States of America,Montana,3627,125.00,453375.00,22668.75,430706.25,435240.00,-4533.75,7/1/2014,7,July,2014
|
||||
Government,Mexico,Montana,720,350.00,252000.00,12600.00,239400.00,187200.00,52200.00,9/1/2013,9,September,2013
|
||||
Channel Partners,Germany,Montana,2342,12.00,28104.00,1405.20,26698.80,7026.00,19672.80,11/1/2014,11,November,2014
|
||||
Small Business,Mexico,Montana,1100,300.00,330000.00,16500.00,313500.00,275000.00,38500.00,12/1/2013,12,December,2013
|
||||
Government,France,Paseo,1303,20.00,26060.00,1303.00,24757.00,13030.00,11727.00,2/1/2014,2,February,2014
|
||||
Enterprise,United States of America,Paseo,2992,125.00,374000.00,18700.00,355300.00,359040.00,-3740.00,3/1/2014,3,March,2014
|
||||
Enterprise,France,Paseo,2385,125.00,298125.00,14906.25,283218.75,286200.00,-2981.25,3/1/2014,3,March,2014
|
||||
Small Business,Mexico,Paseo,1607,300.00,482100.00,24105.00,457995.00,401750.00,56245.00,4/1/2014,4,April,2014
|
||||
Government,United States of America,Paseo,2327,7.00,16289.00,814.45,15474.55,11635.00,3839.55,5/1/2014,5,May,2014
|
||||
Small Business,United States of America,Paseo,991,300.00,297300.00,14865.00,282435.00,247750.00,34685.00,6/1/2014,6,June,2014
|
||||
Government,United States of America,Paseo,602,350.00,210700.00,10535.00,200165.00,156520.00,43645.00,6/1/2014,6,June,2014
|
||||
Midmarket,France,Paseo,2620,15.00,39300.00,1965.00,37335.00,26200.00,11135.00,9/1/2014,9,September,2014
|
||||
Government,Canada,Paseo,1228,350.00,429800.00,21490.00,408310.00,319280.00,89030.00,10/1/2013,10,October,2013
|
||||
Government,Canada,Paseo,1389,20.00,27780.00,1389.00,26391.00,13890.00,12501.00,10/1/2013,10,October,2013
|
||||
Enterprise,United States of America,Paseo,861,125.00,107625.00,5381.25,102243.75,103320.00,-1076.25,10/1/2014,10,October,2014
|
||||
Enterprise,France,Paseo,704,125.00,88000.00,4400.00,83600.00,84480.00,-880.00,10/1/2013,10,October,2013
|
||||
Government,Canada,Paseo,1802,20.00,36040.00,1802.00,34238.00,18020.00,16218.00,12/1/2013,12,December,2013
|
||||
Government,United States of America,Paseo,2663,20.00,53260.00,2663.00,50597.00,26630.00,23967.00,12/1/2014,12,December,2014
|
||||
Government,France,Paseo,2136,7.00,14952.00,747.60,14204.40,10680.00,3524.40,12/1/2013,12,December,2013
|
||||
Midmarket,Germany,Paseo,2116,15.00,31740.00,1587.00,30153.00,21160.00,8993.00,12/1/2013,12,December,2013
|
||||
Midmarket,United States of America,Velo,555,15.00,8325.00,416.25,7908.75,5550.00,2358.75,1/1/2014,1,January,2014
|
||||
Midmarket,Mexico,Velo,2861,15.00,42915.00,2145.75,40769.25,28610.00,12159.25,1/1/2014,1,January,2014
|
||||
Enterprise,Germany,Velo,807,125.00,100875.00,5043.75,95831.25,96840.00,-1008.75,2/1/2014,2,February,2014
|
||||
Government,United States of America,Velo,602,350.00,210700.00,10535.00,200165.00,156520.00,43645.00,6/1/2014,6,June,2014
|
||||
Government,United States of America,Velo,2832,20.00,56640.00,2832.00,53808.00,28320.00,25488.00,8/1/2014,8,August,2014
|
||||
Government,France,Velo,1579,20.00,31580.00,1579.00,30001.00,15790.00,14211.00,8/1/2014,8,August,2014
|
||||
Enterprise,United States of America,Velo,861,125.00,107625.00,5381.25,102243.75,103320.00,-1076.25,10/1/2014,10,October,2014
|
||||
Enterprise,France,Velo,704,125.00,88000.00,4400.00,83600.00,84480.00,-880.00,10/1/2013,10,October,2013
|
||||
Government,France,Velo,1033,20.00,20660.00,1033.00,19627.00,10330.00,9297.00,12/1/2013,12,December,2013
|
||||
Small Business,Germany,Velo,1250,300.00,375000.00,18750.00,356250.00,312500.00,43750.00,12/1/2014,12,December,2014
|
||||
Government,Canada,VTT,1389,20.00,27780.00,1389.00,26391.00,13890.00,12501.00,10/1/2013,10,October,2013
|
||||
Government,United States of America,VTT,1265,20.00,25300.00,1265.00,24035.00,12650.00,11385.00,11/1/2013,11,November,2013
|
||||
Government,Germany,VTT,2297,20.00,45940.00,2297.00,43643.00,22970.00,20673.00,11/1/2013,11,November,2013
|
||||
Government,United States of America,VTT,2663,20.00,53260.00,2663.00,50597.00,26630.00,23967.00,12/1/2014,12,December,2014
|
||||
Government,United States of America,VTT,570,7.00,3990.00,199.50,3790.50,2850.00,940.50,12/1/2014,12,December,2014
|
||||
Government,France,VTT,2487,7.00,17409.00,870.45,16538.55,12435.00,4103.55,12/1/2014,12,December,2014
|
||||
Government,Germany,Amarilla,1350,350.00,472500.00,23625.00,448875.00,351000.00,97875.00,2/1/2014,2,February,2014
|
||||
Government,Canada,Amarilla,552,350.00,193200.00,9660.00,183540.00,143520.00,40020.00,8/1/2014,8,August,2014
|
||||
Government,Canada,Amarilla,1228,350.00,429800.00,21490.00,408310.00,319280.00,89030.00,10/1/2013,10,October,2013
|
||||
Small Business,Germany,Amarilla,1250,300.00,375000.00,18750.00,356250.00,312500.00,43750.00,12/1/2014,12,December,2014
|
||||
Midmarket,France,Paseo,3801,15.00,57015.00,3420.90,53594.10,38010.00,15584.10,4/1/2014,4,April,2014
|
||||
Government,United States of America,Carretera,1117.5,20.00,22350.00,1341.00,21009.00,11175.00,9834.00,1/1/2014,1,January,2014
|
||||
Midmarket,Canada,Carretera,2844,15.00,42660.00,2559.60,40100.40,28440.00,11660.40,6/1/2014,6,June,2014
|
||||
Channel Partners,Mexico,Carretera,562,12.00,6744.00,404.64,6339.36,1686.00,4653.36,9/1/2014,9,September,2014
|
||||
Channel Partners,Canada,Carretera,2299,12.00,27588.00,1655.28,25932.72,6897.00,19035.72,10/1/2013,10,October,2013
|
||||
Midmarket,United States of America,Carretera,2030,15.00,30450.00,1827.00,28623.00,20300.00,8323.00,11/1/2014,11,November,2014
|
||||
Government,United States of America,Carretera,263,7.00,1841.00,110.46,1730.54,1315.00,415.54,11/1/2013,11,November,2013
|
||||
Enterprise,Germany,Carretera,887,125.00,110875.00,6652.50,104222.50,106440.00,-2217.50,12/1/2013,12,December,2013
|
||||
Government,Mexico,Montana,980,350.00,343000.00,20580.00,322420.00,254800.00,67620.00,4/1/2014,4,April,2014
|
||||
Government,Germany,Montana,1460,350.00,511000.00,30660.00,480340.00,379600.00,100740.00,5/1/2014,5,May,2014
|
||||
Government,France,Montana,1403,7.00,9821.00,589.26,9231.74,7015.00,2216.74,10/1/2013,10,October,2013
|
||||
Channel Partners,United States of America,Montana,2723,12.00,32676.00,1960.56,30715.44,8169.00,22546.44,11/1/2014,11,November,2014
|
||||
Government,France,Paseo,1496,350.00,523600.00,31416.00,492184.00,388960.00,103224.00,6/1/2014,6,June,2014
|
||||
Channel Partners,Canada,Paseo,2299,12.00,27588.00,1655.28,25932.72,6897.00,19035.72,10/1/2013,10,October,2013
|
||||
Government,United States of America,Paseo,727,350.00,254450.00,15267.00,239183.00,189020.00,50163.00,10/1/2013,10,October,2013
|
||||
Enterprise,Canada,Velo,952,125.00,119000.00,7140.00,111860.00,114240.00,-2380.00,2/1/2014,2,February,2014
|
||||
Enterprise,United States of America,Velo,2755,125.00,344375.00,20662.50,323712.50,330600.00,-6887.50,2/1/2014,2,February,2014
|
||||
Midmarket,Germany,Velo,1530,15.00,22950.00,1377.00,21573.00,15300.00,6273.00,5/1/2014,5,May,2014
|
||||
Government,France,Velo,1496,350.00,523600.00,31416.00,492184.00,388960.00,103224.00,6/1/2014,6,June,2014
|
||||
Government,Mexico,Velo,1498,7.00,10486.00,629.16,9856.84,7490.00,2366.84,6/1/2014,6,June,2014
|
||||
Small Business,France,Velo,1221,300.00,366300.00,21978.00,344322.00,305250.00,39072.00,10/1/2013,10,October,2013
|
||||
Government,France,Velo,2076,350.00,726600.00,43596.00,683004.00,539760.00,143244.00,10/1/2013,10,October,2013
|
||||
Midmarket,Canada,VTT,2844,15.00,42660.00,2559.60,40100.40,28440.00,11660.40,6/1/2014,6,June,2014
|
||||
Government,Mexico,VTT,1498,7.00,10486.00,629.16,9856.84,7490.00,2366.84,6/1/2014,6,June,2014
|
||||
Small Business,France,VTT,1221,300.00,366300.00,21978.00,344322.00,305250.00,39072.00,10/1/2013,10,October,2013
|
||||
Government,Mexico,VTT,1123,20.00,22460.00,1347.60,21112.40,11230.00,9882.40,11/1/2013,11,November,2013
|
||||
Small Business,Canada,VTT,2436,300.00,730800.00,43848.00,686952.00,609000.00,77952.00,12/1/2013,12,December,2013
|
||||
Enterprise,France,Amarilla,1987.5,125.00,248437.50,14906.25,233531.25,238500.00,-4968.75,1/1/2014,1,January,2014
|
||||
Government,Mexico,Amarilla,1679,350.00,587650.00,35259.00,552391.00,436540.00,115851.00,9/1/2014,9,September,2014
|
||||
Government,United States of America,Amarilla,727,350.00,254450.00,15267.00,239183.00,189020.00,50163.00,10/1/2013,10,October,2013
|
||||
Government,France,Amarilla,1403,7.00,9821.00,589.26,9231.74,7015.00,2216.74,10/1/2013,10,October,2013
|
||||
Government,France,Amarilla,2076,350.00,726600.00,43596.00,683004.00,539760.00,143244.00,10/1/2013,10,October,2013
|
||||
Government,France,Montana,1757,20.00,35140.00,2108.40,33031.60,17570.00,15461.60,10/1/2013,10,October,2013
|
||||
Midmarket,United States of America,Paseo,2198,15.00,32970.00,1978.20,30991.80,21980.00,9011.80,8/1/2014,8,August,2014
|
||||
Midmarket,Germany,Paseo,1743,15.00,26145.00,1568.70,24576.30,17430.00,7146.30,8/1/2014,8,August,2014
|
||||
Midmarket,United States of America,Paseo,1153,15.00,17295.00,1037.70,16257.30,11530.00,4727.30,10/1/2014,10,October,2014
|
||||
Government,France,Paseo,1757,20.00,35140.00,2108.40,33031.60,17570.00,15461.60,10/1/2013,10,October,2013
|
||||
Government,Germany,Velo,1001,20.00,20020.00,1201.20,18818.80,10010.00,8808.80,8/1/2014,8,August,2014
|
||||
Government,Mexico,Velo,1333,7.00,9331.00,559.86,8771.14,6665.00,2106.14,11/1/2014,11,November,2014
|
||||
Midmarket,United States of America,VTT,1153,15.00,17295.00,1037.70,16257.30,11530.00,4727.30,10/1/2014,10,October,2014
|
||||
Channel Partners,Mexico,Carretera,727,12.00,8724.00,610.68,8113.32,2181.00,5932.32,2/1/2014,2,February,2014
|
||||
Channel Partners,Canada,Carretera,1884,12.00,22608.00,1582.56,21025.44,5652.00,15373.44,8/1/2014,8,August,2014
|
||||
Government,Mexico,Carretera,1834,20.00,36680.00,2567.60,34112.40,18340.00,15772.40,9/1/2013,9,September,2013
|
||||
Channel Partners,Mexico,Montana,2340,12.00,28080.00,1965.60,26114.40,7020.00,19094.40,1/1/2014,1,January,2014
|
||||
Channel Partners,France,Montana,2342,12.00,28104.00,1967.28,26136.72,7026.00,19110.72,11/1/2014,11,November,2014
|
||||
Government,France,Paseo,1031,7.00,7217.00,505.19,6711.81,5155.00,1556.81,9/1/2013,9,September,2013
|
||||
Midmarket,Canada,Velo,1262,15.00,18930.00,1325.10,17604.90,12620.00,4984.90,5/1/2014,5,May,2014
|
||||
Government,Canada,Velo,1135,7.00,7945.00,556.15,7388.85,5675.00,1713.85,6/1/2014,6,June,2014
|
||||
Government,United States of America,Velo,547,7.00,3829.00,268.03,3560.97,2735.00,825.97,11/1/2014,11,November,2014
|
||||
Government,Canada,Velo,1582,7.00,11074.00,775.18,10298.82,7910.00,2388.82,12/1/2014,12,December,2014
|
||||
Channel Partners,France,VTT,1738.5,12.00,20862.00,1460.34,19401.66,5215.50,14186.16,4/1/2014,4,April,2014
|
||||
Channel Partners,Germany,VTT,2215,12.00,26580.00,1860.60,24719.40,6645.00,18074.40,9/1/2013,9,September,2013
|
||||
Government,Canada,VTT,1582,7.00,11074.00,775.18,10298.82,7910.00,2388.82,12/1/2014,12,December,2014
|
||||
Government,Canada,Amarilla,1135,7.00,7945.00,556.15,7388.85,5675.00,1713.85,6/1/2014,6,June,2014
|
||||
Government,United States of America,Carretera,1761,350.00,616350.00,43144.50,573205.50,457860.00,115345.50,3/1/2014,3,March,2014
|
||||
Small Business,France,Carretera,448,300.00,134400.00,9408.00,124992.00,112000.00,12992.00,6/1/2014,6,June,2014
|
||||
Small Business,France,Carretera,2181,300.00,654300.00,45801.00,608499.00,545250.00,63249.00,10/1/2014,10,October,2014
|
||||
Government,France,Montana,1976,20.00,39520.00,2766.40,36753.60,19760.00,16993.60,10/1/2014,10,October,2014
|
||||
Small Business,France,Montana,2181,300.00,654300.00,45801.00,608499.00,545250.00,63249.00,10/1/2014,10,October,2014
|
||||
Enterprise,Germany,Montana,2500,125.00,312500.00,21875.00,290625.00,300000.00,-9375.00,11/1/2013,11,November,2013
|
||||
Small Business,Canada,Paseo,1702,300.00,510600.00,35742.00,474858.00,425500.00,49358.00,5/1/2014,5,May,2014
|
||||
Small Business,France,Paseo,448,300.00,134400.00,9408.00,124992.00,112000.00,12992.00,6/1/2014,6,June,2014
|
||||
Enterprise,Germany,Paseo,3513,125.00,439125.00,30738.75,408386.25,421560.00,-13173.75,7/1/2014,7,July,2014
|
||||
Midmarket,France,Paseo,2101,15.00,31515.00,2206.05,29308.95,21010.00,8298.95,8/1/2014,8,August,2014
|
||||
Midmarket,United States of America,Paseo,2931,15.00,43965.00,3077.55,40887.45,29310.00,11577.45,9/1/2013,9,September,2013
|
||||
Government,France,Paseo,1535,20.00,30700.00,2149.00,28551.00,15350.00,13201.00,9/1/2014,9,September,2014
|
||||
Small Business,Germany,Paseo,1123,300.00,336900.00,23583.00,313317.00,280750.00,32567.00,9/1/2013,9,September,2013
|
||||
Small Business,Canada,Paseo,1404,300.00,421200.00,29484.00,391716.00,351000.00,40716.00,11/1/2013,11,November,2013
|
||||
Channel Partners,Mexico,Paseo,2763,12.00,33156.00,2320.92,30835.08,8289.00,22546.08,11/1/2013,11,November,2013
|
||||
Government,Germany,Paseo,2125,7.00,14875.00,1041.25,13833.75,10625.00,3208.75,12/1/2013,12,December,2013
|
||||
Small Business,France,Velo,1659,300.00,497700.00,34839.00,462861.00,414750.00,48111.00,7/1/2014,7,July,2014
|
||||
Government,Mexico,Velo,609,20.00,12180.00,852.60,11327.40,6090.00,5237.40,8/1/2014,8,August,2014
|
||||
Enterprise,Germany,Velo,2087,125.00,260875.00,18261.25,242613.75,250440.00,-7826.25,9/1/2014,9,September,2014
|
||||
Government,France,Velo,1976,20.00,39520.00,2766.40,36753.60,19760.00,16993.60,10/1/2014,10,October,2014
|
||||
Government,United States of America,Velo,1421,20.00,28420.00,1989.40,26430.60,14210.00,12220.60,12/1/2013,12,December,2013
|
||||
Small Business,United States of America,Velo,1372,300.00,411600.00,28812.00,382788.00,343000.00,39788.00,12/1/2014,12,December,2014
|
||||
Government,Germany,Velo,588,20.00,11760.00,823.20,10936.80,5880.00,5056.80,12/1/2013,12,December,2013
|
||||
Channel Partners,Canada,VTT,3244.5,12.00,38934.00,2725.38,36208.62,9733.50,26475.12,1/1/2014,1,January,2014
|
||||
Small Business,France,VTT,959,300.00,287700.00,20139.00,267561.00,239750.00,27811.00,2/1/2014,2,February,2014
|
||||
Small Business,Mexico,VTT,2747,300.00,824100.00,57687.00,766413.00,686750.00,79663.00,2/1/2014,2,February,2014
|
||||
Enterprise,Canada,Amarilla,1645,125.00,205625.00,14393.75,191231.25,197400.00,-6168.75,5/1/2014,5,May,2014
|
||||
Government,France,Amarilla,2876,350.00,1006600.00,70462.00,936138.00,747760.00,188378.00,9/1/2014,9,September,2014
|
||||
Enterprise,Germany,Amarilla,994,125.00,124250.00,8697.50,115552.50,119280.00,-3727.50,9/1/2013,9,September,2013
|
||||
Government,Canada,Amarilla,1118,20.00,22360.00,1565.20,20794.80,11180.00,9614.80,11/1/2014,11,November,2014
|
||||
Small Business,United States of America,Amarilla,1372,300.00,411600.00,28812.00,382788.00,343000.00,39788.00,12/1/2014,12,December,2014
|
||||
Government,Canada,Montana,488,7.00,3416.00,273.28,3142.72,2440.00,702.72,2/1/2014,2,February,2014
|
||||
Government,United States of America,Montana,1282,20.00,25640.00,2051.20,23588.80,12820.00,10768.80,6/1/2014,6,June,2014
|
||||
Government,Canada,Paseo,257,7.00,1799.00,143.92,1655.08,1285.00,370.08,5/1/2014,5,May,2014
|
||||
Government,United States of America,Amarilla,1282,20.00,25640.00,2051.20,23588.80,12820.00,10768.80,6/1/2014,6,June,2014
|
||||
Enterprise,Mexico,Carretera,1540,125.00,192500.00,15400.00,177100.00,184800.00,-7700.00,8/1/2014,8,August,2014
|
||||
Midmarket,France,Carretera,490,15.00,7350.00,588.00,6762.00,4900.00,1862.00,11/1/2014,11,November,2014
|
||||
Government,Mexico,Carretera,1362,350.00,476700.00,38136.00,438564.00,354120.00,84444.00,12/1/2014,12,December,2014
|
||||
Midmarket,France,Montana,2501,15.00,37515.00,3001.20,34513.80,25010.00,9503.80,3/1/2014,3,March,2014
|
||||
Government,Canada,Montana,708,20.00,14160.00,1132.80,13027.20,7080.00,5947.20,6/1/2014,6,June,2014
|
||||
Government,Germany,Montana,645,20.00,12900.00,1032.00,11868.00,6450.00,5418.00,7/1/2014,7,July,2014
|
||||
Small Business,France,Montana,1562,300.00,468600.00,37488.00,431112.00,390500.00,40612.00,8/1/2014,8,August,2014
|
||||
Small Business,Canada,Montana,1283,300.00,384900.00,30792.00,354108.00,320750.00,33358.00,9/1/2013,9,September,2013
|
||||
Midmarket,Germany,Montana,711,15.00,10665.00,853.20,9811.80,7110.00,2701.80,12/1/2014,12,December,2014
|
||||
Enterprise,Mexico,Paseo,1114,125.00,139250.00,11140.00,128110.00,133680.00,-5570.00,3/1/2014,3,March,2014
|
||||
Government,Germany,Paseo,1259,7.00,8813.00,705.04,8107.96,6295.00,1812.96,4/1/2014,4,April,2014
|
||||
Government,Germany,Paseo,1095,7.00,7665.00,613.20,7051.80,5475.00,1576.80,5/1/2014,5,May,2014
|
||||
Government,Germany,Paseo,1366,20.00,27320.00,2185.60,25134.40,13660.00,11474.40,6/1/2014,6,June,2014
|
||||
Small Business,Mexico,Paseo,2460,300.00,738000.00,59040.00,678960.00,615000.00,63960.00,6/1/2014,6,June,2014
|
||||
Government,United States of America,Paseo,678,7.00,4746.00,379.68,4366.32,3390.00,976.32,8/1/2014,8,August,2014
|
||||
Government,Germany,Paseo,1598,7.00,11186.00,894.88,10291.12,7990.00,2301.12,8/1/2014,8,August,2014
|
||||
Government,Germany,Paseo,2409,7.00,16863.00,1349.04,15513.96,12045.00,3468.96,9/1/2013,9,September,2013
|
||||
Government,Germany,Paseo,1934,20.00,38680.00,3094.40,35585.60,19340.00,16245.60,9/1/2014,9,September,2014
|
||||
Government,Mexico,Paseo,2993,20.00,59860.00,4788.80,55071.20,29930.00,25141.20,9/1/2014,9,September,2014
|
||||
Government,Germany,Paseo,2146,350.00,751100.00,60088.00,691012.00,557960.00,133052.00,11/1/2013,11,November,2013
|
||||
Government,Mexico,Paseo,1946,7.00,13622.00,1089.76,12532.24,9730.00,2802.24,12/1/2013,12,December,2013
|
||||
Government,Mexico,Paseo,1362,350.00,476700.00,38136.00,438564.00,354120.00,84444.00,12/1/2014,12,December,2014
|
||||
Channel Partners,Canada,Velo,598,12.00,7176.00,574.08,6601.92,1794.00,4807.92,3/1/2014,3,March,2014
|
||||
Government,United States of America,Velo,2907,7.00,20349.00,1627.92,18721.08,14535.00,4186.08,6/1/2014,6,June,2014
|
||||
Government,Germany,Velo,2338,7.00,16366.00,1309.28,15056.72,11690.00,3366.72,6/1/2014,6,June,2014
|
||||
Small Business,France,Velo,386,300.00,115800.00,9264.00,106536.00,96500.00,10036.00,11/1/2013,11,November,2013
|
||||
Small Business,Mexico,Velo,635,300.00,190500.00,15240.00,175260.00,158750.00,16510.00,12/1/2014,12,December,2014
|
||||
Government,France,VTT,574.5,350.00,201075.00,16086.00,184989.00,149370.00,35619.00,4/1/2014,4,April,2014
|
||||
Government,Germany,VTT,2338,7.00,16366.00,1309.28,15056.72,11690.00,3366.72,6/1/2014,6,June,2014
|
||||
Government,France,VTT,381,350.00,133350.00,10668.00,122682.00,99060.00,23622.00,8/1/2014,8,August,2014
|
||||
Government,Germany,VTT,422,350.00,147700.00,11816.00,135884.00,109720.00,26164.00,8/1/2014,8,August,2014
|
||||
Small Business,Canada,VTT,2134,300.00,640200.00,51216.00,588984.00,533500.00,55484.00,9/1/2014,9,September,2014
|
||||
Small Business,United States of America,VTT,808,300.00,242400.00,19392.00,223008.00,202000.00,21008.00,12/1/2013,12,December,2013
|
||||
Government,Canada,Amarilla,708,20.00,14160.00,1132.80,13027.20,7080.00,5947.20,6/1/2014,6,June,2014
|
||||
Government,United States of America,Amarilla,2907,7.00,20349.00,1627.92,18721.08,14535.00,4186.08,6/1/2014,6,June,2014
|
||||
Government,Germany,Amarilla,1366,20.00,27320.00,2185.60,25134.40,13660.00,11474.40,6/1/2014,6,June,2014
|
||||
Small Business,Mexico,Amarilla,2460,300.00,738000.00,59040.00,678960.00,615000.00,63960.00,6/1/2014,6,June,2014
|
||||
Government,Germany,Amarilla,1520,20.00,30400.00,2432.00,27968.00,15200.00,12768.00,11/1/2014,11,November,2014
|
||||
Midmarket,Germany,Amarilla,711,15.00,10665.00,853.20,9811.80,7110.00,2701.80,12/1/2014,12,December,2014
|
||||
Channel Partners,Mexico,Amarilla,1375,12.00,16500.00,1320.00,15180.00,4125.00,11055.00,12/1/2013,12,December,2013
|
||||
Small Business,Mexico,Amarilla,635,300.00,190500.00,15240.00,175260.00,158750.00,16510.00,12/1/2014,12,December,2014
|
||||
Government,United States of America,VTT,436.5,20.00,8730.00,698.40,8031.60,4365.00,3666.60,7/1/2014,7,July,2014
|
||||
Small Business,Canada,Carretera,1094,300.00,328200.00,29538.00,298662.00,273500.00,25162.00,6/1/2014,6,June,2014
|
||||
Channel Partners,Mexico,Carretera,367,12.00,4404.00,396.36,4007.64,1101.00,2906.64,10/1/2013,10,October,2013
|
||||
Small Business,Canada,Montana,3802.5,300.00,1140750.00,102667.50,1038082.50,950625.00,87457.50,4/1/2014,4,April,2014
|
||||
Government,France,Montana,1666,350.00,583100.00,52479.00,530621.00,433160.00,97461.00,5/1/2014,5,May,2014
|
||||
Small Business,France,Montana,322,300.00,96600.00,8694.00,87906.00,80500.00,7406.00,9/1/2013,9,September,2013
|
||||
Channel Partners,Canada,Montana,2321,12.00,27852.00,2506.68,25345.32,6963.00,18382.32,11/1/2014,11,November,2014
|
||||
Enterprise,France,Montana,1857,125.00,232125.00,20891.25,211233.75,222840.00,-11606.25,11/1/2013,11,November,2013
|
||||
Government,Canada,Montana,1611,7.00,11277.00,1014.93,10262.07,8055.00,2207.07,12/1/2013,12,December,2013
|
||||
Enterprise,United States of America,Montana,2797,125.00,349625.00,31466.25,318158.75,335640.00,-17481.25,12/1/2014,12,December,2014
|
||||
Small Business,Germany,Montana,334,300.00,100200.00,9018.00,91182.00,83500.00,7682.00,12/1/2013,12,December,2013
|
||||
Small Business,Mexico,Paseo,2565,300.00,769500.00,69255.00,700245.00,641250.00,58995.00,1/1/2014,1,January,2014
|
||||
Government,Mexico,Paseo,2417,350.00,845950.00,76135.50,769814.50,628420.00,141394.50,1/1/2014,1,January,2014
|
||||
Midmarket,United States of America,Paseo,3675,15.00,55125.00,4961.25,50163.75,36750.00,13413.75,4/1/2014,4,April,2014
|
||||
Small Business,Canada,Paseo,1094,300.00,328200.00,29538.00,298662.00,273500.00,25162.00,6/1/2014,6,June,2014
|
||||
Midmarket,France,Paseo,1227,15.00,18405.00,1656.45,16748.55,12270.00,4478.55,10/1/2014,10,October,2014
|
||||
Channel Partners,Mexico,Paseo,367,12.00,4404.00,396.36,4007.64,1101.00,2906.64,10/1/2013,10,October,2013
|
||||
Small Business,France,Paseo,1324,300.00,397200.00,35748.00,361452.00,331000.00,30452.00,11/1/2014,11,November,2014
|
||||
Channel Partners,Germany,Paseo,1775,12.00,21300.00,1917.00,19383.00,5325.00,14058.00,11/1/2013,11,November,2013
|
||||
Enterprise,United States of America,Paseo,2797,125.00,349625.00,31466.25,318158.75,335640.00,-17481.25,12/1/2014,12,December,2014
|
||||
Midmarket,Mexico,Velo,245,15.00,3675.00,330.75,3344.25,2450.00,894.25,5/1/2014,5,May,2014
|
||||
Small Business,Canada,Velo,3793.5,300.00,1138050.00,102424.50,1035625.50,948375.00,87250.50,7/1/2014,7,July,2014
|
||||
Government,Germany,Velo,1307,350.00,457450.00,41170.50,416279.50,339820.00,76459.50,7/1/2014,7,July,2014
|
||||
Enterprise,Canada,Velo,567,125.00,70875.00,6378.75,64496.25,68040.00,-3543.75,9/1/2014,9,September,2014
|
||||
Enterprise,Mexico,Velo,2110,125.00,263750.00,23737.50,240012.50,253200.00,-13187.50,9/1/2014,9,September,2014
|
||||
Government,Canada,Velo,1269,350.00,444150.00,39973.50,404176.50,329940.00,74236.50,10/1/2014,10,October,2014
|
||||
Channel Partners,United States of America,VTT,1956,12.00,23472.00,2112.48,21359.52,5868.00,15491.52,1/1/2014,1,January,2014
|
||||
Small Business,Germany,VTT,2659,300.00,797700.00,71793.00,725907.00,664750.00,61157.00,2/1/2014,2,February,2014
|
||||
Government,United States of America,VTT,1351.5,350.00,473025.00,42572.25,430452.75,351390.00,79062.75,4/1/2014,4,April,2014
|
||||
Channel Partners,Germany,VTT,880,12.00,10560.00,950.40,9609.60,2640.00,6969.60,5/1/2014,5,May,2014
|
||||
Small Business,United States of America,VTT,1867,300.00,560100.00,50409.00,509691.00,466750.00,42941.00,9/1/2014,9,September,2014
|
||||
Channel Partners,France,VTT,2234,12.00,26808.00,2412.72,24395.28,6702.00,17693.28,9/1/2013,9,September,2013
|
||||
Midmarket,France,VTT,1227,15.00,18405.00,1656.45,16748.55,12270.00,4478.55,10/1/2014,10,October,2014
|
||||
Enterprise,Mexico,VTT,877,125.00,109625.00,9866.25,99758.75,105240.00,-5481.25,11/1/2014,11,November,2014
|
||||
Government,United States of America,Amarilla,2071,350.00,724850.00,65236.50,659613.50,538460.00,121153.50,9/1/2014,9,September,2014
|
||||
Government,Canada,Amarilla,1269,350.00,444150.00,39973.50,404176.50,329940.00,74236.50,10/1/2014,10,October,2014
|
||||
Midmarket,Germany,Amarilla,970,15.00,14550.00,1309.50,13240.50,9700.00,3540.50,11/1/2013,11,November,2013
|
||||
Government,Mexico,Amarilla,1694,20.00,33880.00,3049.20,30830.80,16940.00,13890.80,11/1/2014,11,November,2014
|
||||
Government,Germany,Carretera,663,20.00,13260.00,1193.40,12066.60,6630.00,5436.60,5/1/2014,5,May,2014
|
||||
Government,Canada,Carretera,819,7.00,5733.00,515.97,5217.03,4095.00,1122.03,7/1/2014,7,July,2014
|
||||
Channel Partners,Germany,Carretera,1580,12.00,18960.00,1706.40,17253.60,4740.00,12513.60,9/1/2014,9,September,2014
|
||||
Government,Mexico,Carretera,521,7.00,3647.00,328.23,3318.77,2605.00,713.77,12/1/2014,12,December,2014
|
||||
Government,United States of America,Paseo,973,20.00,19460.00,1751.40,17708.60,9730.00,7978.60,3/1/2014,3,March,2014
|
||||
Government,Mexico,Paseo,1038,20.00,20760.00,1868.40,18891.60,10380.00,8511.60,6/1/2014,6,June,2014
|
||||
Government,Germany,Paseo,360,7.00,2520.00,226.80,2293.20,1800.00,493.20,10/1/2014,10,October,2014
|
||||
Channel Partners,France,Velo,1967,12.00,23604.00,2124.36,21479.64,5901.00,15578.64,3/1/2014,3,March,2014
|
||||
Midmarket,Mexico,Velo,2628,15.00,39420.00,3547.80,35872.20,26280.00,9592.20,4/1/2014,4,April,2014
|
||||
Government,Germany,VTT,360,7.00,2520.00,226.80,2293.20,1800.00,493.20,10/1/2014,10,October,2014
|
||||
Government,France,VTT,2682,20.00,53640.00,4827.60,48812.40,26820.00,21992.40,11/1/2013,11,November,2013
|
||||
Government,Mexico,VTT,521,7.00,3647.00,328.23,3318.77,2605.00,713.77,12/1/2014,12,December,2014
|
||||
Government,Mexico,Amarilla,1038,20.00,20760.00,1868.40,18891.60,10380.00,8511.60,6/1/2014,6,June,2014
|
||||
Midmarket,Canada,Amarilla,1630.5,15.00,24457.50,2201.18,22256.33,16305.00,5951.33,7/1/2014,7,July,2014
|
||||
Channel Partners,France,Amarilla,306,12.00,3672.00,330.48,3341.52,918.00,2423.52,12/1/2013,12,December,2013
|
||||
Channel Partners,United States of America,Carretera,386,12.00,4632.00,463.20,4168.80,1158.00,3010.80,10/1/2013,10,October,2013
|
||||
Government,United States of America,Montana,2328,7.00,16296.00,1629.60,14666.40,11640.00,3026.40,9/1/2014,9,September,2014
|
||||
Channel Partners,United States of America,Paseo,386,12.00,4632.00,463.20,4168.80,1158.00,3010.80,10/1/2013,10,October,2013
|
||||
Enterprise,United States of America,Carretera,3445.5,125.00,430687.50,43068.75,387618.75,413460.00,-25841.25,4/1/2014,4,April,2014
|
||||
Enterprise,France,Carretera,1482,125.00,185250.00,18525.00,166725.00,177840.00,-11115.00,12/1/2013,12,December,2013
|
||||
Government,United States of America,Montana,2313,350.00,809550.00,80955.00,728595.00,601380.00,127215.00,5/1/2014,5,May,2014
|
||||
Enterprise,United States of America,Montana,1804,125.00,225500.00,22550.00,202950.00,216480.00,-13530.00,11/1/2013,11,November,2013
|
||||
Midmarket,France,Montana,2072,15.00,31080.00,3108.00,27972.00,20720.00,7252.00,12/1/2014,12,December,2014
|
||||
Government,France,Paseo,1954,20.00,39080.00,3908.00,35172.00,19540.00,15632.00,3/1/2014,3,March,2014
|
||||
Small Business,Mexico,Paseo,591,300.00,177300.00,17730.00,159570.00,147750.00,11820.00,5/1/2014,5,May,2014
|
||||
Midmarket,France,Paseo,2167,15.00,32505.00,3250.50,29254.50,21670.00,7584.50,10/1/2013,10,October,2013
|
||||
Government,Germany,Paseo,241,20.00,4820.00,482.00,4338.00,2410.00,1928.00,10/1/2014,10,October,2014
|
||||
Midmarket,Germany,Velo,681,15.00,10215.00,1021.50,9193.50,6810.00,2383.50,1/1/2014,1,January,2014
|
||||
Midmarket,Germany,Velo,510,15.00,7650.00,765.00,6885.00,5100.00,1785.00,4/1/2014,4,April,2014
|
||||
Midmarket,United States of America,Velo,790,15.00,11850.00,1185.00,10665.00,7900.00,2765.00,5/1/2014,5,May,2014
|
||||
Government,France,Velo,639,350.00,223650.00,22365.00,201285.00,166140.00,35145.00,7/1/2014,7,July,2014
|
||||
Enterprise,United States of America,Velo,1596,125.00,199500.00,19950.00,179550.00,191520.00,-11970.00,9/1/2014,9,September,2014
|
||||
Small Business,United States of America,Velo,2294,300.00,688200.00,68820.00,619380.00,573500.00,45880.00,10/1/2013,10,October,2013
|
||||
Government,Germany,Velo,241,20.00,4820.00,482.00,4338.00,2410.00,1928.00,10/1/2014,10,October,2014
|
||||
Government,Germany,Velo,2665,7.00,18655.00,1865.50,16789.50,13325.00,3464.50,11/1/2014,11,November,2014
|
||||
Enterprise,Canada,Velo,1916,125.00,239500.00,23950.00,215550.00,229920.00,-14370.00,12/1/2013,12,December,2013
|
||||
Small Business,France,Velo,853,300.00,255900.00,25590.00,230310.00,213250.00,17060.00,12/1/2014,12,December,2014
|
||||
Enterprise,Mexico,VTT,341,125.00,42625.00,4262.50,38362.50,40920.00,-2557.50,5/1/2014,5,May,2014
|
||||
Midmarket,Mexico,VTT,641,15.00,9615.00,961.50,8653.50,6410.00,2243.50,7/1/2014,7,July,2014
|
||||
Government,United States of America,VTT,2807,350.00,982450.00,98245.00,884205.00,729820.00,154385.00,8/1/2014,8,August,2014
|
||||
Small Business,Mexico,VTT,432,300.00,129600.00,12960.00,116640.00,108000.00,8640.00,9/1/2014,9,September,2014
|
||||
Small Business,United States of America,VTT,2294,300.00,688200.00,68820.00,619380.00,573500.00,45880.00,10/1/2013,10,October,2013
|
||||
Midmarket,France,VTT,2167,15.00,32505.00,3250.50,29254.50,21670.00,7584.50,10/1/2013,10,October,2013
|
||||
Enterprise,Canada,VTT,2529,125.00,316125.00,31612.50,284512.50,303480.00,-18967.50,11/1/2014,11,November,2014
|
||||
Government,Germany,VTT,1870,350.00,654500.00,65450.00,589050.00,486200.00,102850.00,12/1/2013,12,December,2013
|
||||
Enterprise,United States of America,Amarilla,579,125.00,72375.00,7237.50,65137.50,69480.00,-4342.50,1/1/2014,1,January,2014
|
||||
Government,Canada,Amarilla,2240,350.00,784000.00,78400.00,705600.00,582400.00,123200.00,2/1/2014,2,February,2014
|
||||
Small Business,United States of America,Amarilla,2993,300.00,897900.00,89790.00,808110.00,748250.00,59860.00,3/1/2014,3,March,2014
|
||||
Channel Partners,Canada,Amarilla,3520.5,12.00,42246.00,4224.60,38021.40,10561.50,27459.90,4/1/2014,4,April,2014
|
||||
Government,Mexico,Amarilla,2039,20.00,40780.00,4078.00,36702.00,20390.00,16312.00,5/1/2014,5,May,2014
|
||||
Channel Partners,Germany,Amarilla,2574,12.00,30888.00,3088.80,27799.20,7722.00,20077.20,8/1/2014,8,August,2014
|
||||
Government,Canada,Amarilla,707,350.00,247450.00,24745.00,222705.00,183820.00,38885.00,9/1/2014,9,September,2014
|
||||
Midmarket,France,Amarilla,2072,15.00,31080.00,3108.00,27972.00,20720.00,7252.00,12/1/2014,12,December,2014
|
||||
Small Business,France,Amarilla,853,300.00,255900.00,25590.00,230310.00,213250.00,17060.00,12/1/2014,12,December,2014
|
||||
Channel Partners,France,Carretera,1198,12.00,14376.00,1581.36,12794.64,3594.00,9200.64,10/1/2013,10,October,2013
|
||||
Government,France,Paseo,2532,7.00,17724.00,1949.64,15774.36,12660.00,3114.36,4/1/2014,4,April,2014
|
||||
Channel Partners,France,Paseo,1198,12.00,14376.00,1581.36,12794.64,3594.00,9200.64,10/1/2013,10,October,2013
|
||||
Midmarket,Canada,Velo,384,15.00,5760.00,633.60,5126.40,3840.00,1286.40,1/1/2014,1,January,2014
|
||||
Channel Partners,Germany,Velo,472,12.00,5664.00,623.04,5040.96,1416.00,3624.96,10/1/2014,10,October,2014
|
||||
Government,United States of America,VTT,1579,7.00,11053.00,1215.83,9837.17,7895.00,1942.17,3/1/2014,3,March,2014
|
||||
Channel Partners,Mexico,VTT,1005,12.00,12060.00,1326.60,10733.40,3015.00,7718.40,9/1/2013,9,September,2013
|
||||
Midmarket,United States of America,Amarilla,3199.5,15.00,47992.50,5279.18,42713.33,31995.00,10718.33,7/1/2014,7,July,2014
|
||||
Channel Partners,Germany,Amarilla,472,12.00,5664.00,623.04,5040.96,1416.00,3624.96,10/1/2014,10,October,2014
|
||||
Channel Partners,Canada,Carretera,1937,12.00,23244.00,2556.84,20687.16,5811.00,14876.16,2/1/2014,2,February,2014
|
||||
Government,Germany,Carretera,792,350.00,277200.00,30492.00,246708.00,205920.00,40788.00,3/1/2014,3,March,2014
|
||||
Small Business,Germany,Carretera,2811,300.00,843300.00,92763.00,750537.00,702750.00,47787.00,7/1/2014,7,July,2014
|
||||
Enterprise,France,Carretera,2441,125.00,305125.00,33563.75,271561.25,292920.00,-21358.75,10/1/2014,10,October,2014
|
||||
Midmarket,Canada,Carretera,1560,15.00,23400.00,2574.00,20826.00,15600.00,5226.00,11/1/2013,11,November,2013
|
||||
Government,Mexico,Carretera,2706,7.00,18942.00,2083.62,16858.38,13530.00,3328.38,11/1/2013,11,November,2013
|
||||
Government,Germany,Montana,766,350.00,268100.00,29491.00,238609.00,199160.00,39449.00,1/1/2014,1,January,2014
|
||||
Government,Germany,Montana,2992,20.00,59840.00,6582.40,53257.60,29920.00,23337.60,10/1/2013,10,October,2013
|
||||
Midmarket,Mexico,Montana,2157,15.00,32355.00,3559.05,28795.95,21570.00,7225.95,12/1/2014,12,December,2014
|
||||
Small Business,Canada,Paseo,873,300.00,261900.00,28809.00,233091.00,218250.00,14841.00,1/1/2014,1,January,2014
|
||||
Government,Mexico,Paseo,1122,20.00,22440.00,2468.40,19971.60,11220.00,8751.60,3/1/2014,3,March,2014
|
||||
Government,Canada,Paseo,2104.5,350.00,736575.00,81023.25,655551.75,547170.00,108381.75,7/1/2014,7,July,2014
|
||||
Channel Partners,Canada,Paseo,4026,12.00,48312.00,5314.32,42997.68,12078.00,30919.68,7/1/2014,7,July,2014
|
||||
Channel Partners,France,Paseo,2425.5,12.00,29106.00,3201.66,25904.34,7276.50,18627.84,7/1/2014,7,July,2014
|
||||
Government,Canada,Paseo,2394,20.00,47880.00,5266.80,42613.20,23940.00,18673.20,8/1/2014,8,August,2014
|
||||
Midmarket,Mexico,Paseo,1984,15.00,29760.00,3273.60,26486.40,19840.00,6646.40,8/1/2014,8,August,2014
|
||||
Enterprise,France,Paseo,2441,125.00,305125.00,33563.75,271561.25,292920.00,-21358.75,10/1/2014,10,October,2014
|
||||
Government,Germany,Paseo,2992,20.00,59840.00,6582.40,53257.60,29920.00,23337.60,10/1/2013,10,October,2013
|
||||
Small Business,Canada,Paseo,1366,300.00,409800.00,45078.00,364722.00,341500.00,23222.00,11/1/2014,11,November,2014
|
||||
Government,France,Velo,2805,20.00,56100.00,6171.00,49929.00,28050.00,21879.00,9/1/2013,9,September,2013
|
||||
Midmarket,Mexico,Velo,655,15.00,9825.00,1080.75,8744.25,6550.00,2194.25,9/1/2013,9,September,2013
|
||||
Government,Mexico,Velo,344,350.00,120400.00,13244.00,107156.00,89440.00,17716.00,10/1/2013,10,October,2013
|
||||
Government,Canada,Velo,1808,7.00,12656.00,1392.16,11263.84,9040.00,2223.84,11/1/2014,11,November,2014
|
||||
Channel Partners,France,VTT,1734,12.00,20808.00,2288.88,18519.12,5202.00,13317.12,1/1/2014,1,January,2014
|
||||
Enterprise,Mexico,VTT,554,125.00,69250.00,7617.50,61632.50,66480.00,-4847.50,1/1/2014,1,January,2014
|
||||
Government,Canada,VTT,2935,20.00,58700.00,6457.00,52243.00,29350.00,22893.00,11/1/2013,11,November,2013
|
||||
Enterprise,Germany,Amarilla,3165,125.00,395625.00,43518.75,352106.25,379800.00,-27693.75,1/1/2014,1,January,2014
|
||||
Government,Mexico,Amarilla,2629,20.00,52580.00,5783.80,46796.20,26290.00,20506.20,1/1/2014,1,January,2014
|
||||
Enterprise,France,Amarilla,1433,125.00,179125.00,19703.75,159421.25,171960.00,-12538.75,5/1/2014,5,May,2014
|
||||
Enterprise,Mexico,Amarilla,947,125.00,118375.00,13021.25,105353.75,113640.00,-8286.25,9/1/2013,9,September,2013
|
||||
Government,Mexico,Amarilla,344,350.00,120400.00,13244.00,107156.00,89440.00,17716.00,10/1/2013,10,October,2013
|
||||
Midmarket,Mexico,Amarilla,2157,15.00,32355.00,3559.05,28795.95,21570.00,7225.95,12/1/2014,12,December,2014
|
||||
Government,United States of America,Paseo,380,7.00,2660.00,292.60,2367.40,1900.00,467.40,9/1/2013,9,September,2013
|
||||
Government,Mexico,Carretera,886,350.00,310100.00,37212.00,272888.00,230360.00,42528.00,6/1/2014,6,June,2014
|
||||
Enterprise,Canada,Carretera,2416,125.00,302000.00,36240.00,265760.00,289920.00,-24160.00,9/1/2013,9,September,2013
|
||||
Enterprise,Mexico,Carretera,2156,125.00,269500.00,32340.00,237160.00,258720.00,-21560.00,10/1/2014,10,October,2014
|
||||
Midmarket,Canada,Carretera,2689,15.00,40335.00,4840.20,35494.80,26890.00,8604.80,11/1/2014,11,November,2014
|
||||
Midmarket,United States of America,Montana,677,15.00,10155.00,1218.60,8936.40,6770.00,2166.40,3/1/2014,3,March,2014
|
||||
Small Business,France,Montana,1773,300.00,531900.00,63828.00,468072.00,443250.00,24822.00,4/1/2014,4,April,2014
|
||||
Government,Mexico,Montana,2420,7.00,16940.00,2032.80,14907.20,12100.00,2807.20,9/1/2014,9,September,2014
|
||||
Government,Canada,Montana,2734,7.00,19138.00,2296.56,16841.44,13670.00,3171.44,10/1/2014,10,October,2014
|
||||
Government,Mexico,Montana,1715,20.00,34300.00,4116.00,30184.00,17150.00,13034.00,10/1/2013,10,October,2013
|
||||
Small Business,France,Montana,1186,300.00,355800.00,42696.00,313104.00,296500.00,16604.00,12/1/2013,12,December,2013
|
||||
Small Business,United States of America,Paseo,3495,300.00,1048500.00,125820.00,922680.00,873750.00,48930.00,1/1/2014,1,January,2014
|
||||
Government,Mexico,Paseo,886,350.00,310100.00,37212.00,272888.00,230360.00,42528.00,6/1/2014,6,June,2014
|
||||
Enterprise,Mexico,Paseo,2156,125.00,269500.00,32340.00,237160.00,258720.00,-21560.00,10/1/2014,10,October,2014
|
||||
Government,Mexico,Paseo,905,20.00,18100.00,2172.00,15928.00,9050.00,6878.00,10/1/2014,10,October,2014
|
||||
Government,Mexico,Paseo,1715,20.00,34300.00,4116.00,30184.00,17150.00,13034.00,10/1/2013,10,October,2013
|
||||
Government,France,Paseo,1594,350.00,557900.00,66948.00,490952.00,414440.00,76512.00,11/1/2014,11,November,2014
|
||||
Small Business,Germany,Paseo,1359,300.00,407700.00,48924.00,358776.00,339750.00,19026.00,11/1/2014,11,November,2014
|
||||
Small Business,Mexico,Paseo,2150,300.00,645000.00,77400.00,567600.00,537500.00,30100.00,11/1/2014,11,November,2014
|
||||
Government,Mexico,Paseo,1197,350.00,418950.00,50274.00,368676.00,311220.00,57456.00,11/1/2014,11,November,2014
|
||||
Midmarket,Mexico,Paseo,380,15.00,5700.00,684.00,5016.00,3800.00,1216.00,12/1/2013,12,December,2013
|
||||
Government,Mexico,Paseo,1233,20.00,24660.00,2959.20,21700.80,12330.00,9370.80,12/1/2014,12,December,2014
|
||||
Government,Mexico,Velo,1395,350.00,488250.00,58590.00,429660.00,362700.00,66960.00,7/1/2014,7,July,2014
|
||||
Government,United States of America,Velo,986,350.00,345100.00,41412.00,303688.00,256360.00,47328.00,10/1/2014,10,October,2014
|
||||
Government,Mexico,Velo,905,20.00,18100.00,2172.00,15928.00,9050.00,6878.00,10/1/2014,10,October,2014
|
||||
Channel Partners,Canada,VTT,2109,12.00,25308.00,3036.96,22271.04,6327.00,15944.04,5/1/2014,5,May,2014
|
||||
Midmarket,France,VTT,3874.5,15.00,58117.50,6974.10,51143.40,38745.00,12398.40,7/1/2014,7,July,2014
|
||||
Government,Canada,VTT,623,350.00,218050.00,26166.00,191884.00,161980.00,29904.00,9/1/2013,9,September,2013
|
||||
Government,United States of America,VTT,986,350.00,345100.00,41412.00,303688.00,256360.00,47328.00,10/1/2014,10,October,2014
|
||||
Enterprise,United States of America,VTT,2387,125.00,298375.00,35805.00,262570.00,286440.00,-23870.00,11/1/2014,11,November,2014
|
||||
Government,Mexico,VTT,1233,20.00,24660.00,2959.20,21700.80,12330.00,9370.80,12/1/2014,12,December,2014
|
||||
Government,United States of America,Amarilla,270,350.00,94500.00,11340.00,83160.00,70200.00,12960.00,2/1/2014,2,February,2014
|
||||
Government,France,Amarilla,3421.5,7.00,23950.50,2874.06,21076.44,17107.50,3968.94,7/1/2014,7,July,2014
|
||||
Government,Canada,Amarilla,2734,7.00,19138.00,2296.56,16841.44,13670.00,3171.44,10/1/2014,10,October,2014
|
||||
Midmarket,United States of America,Amarilla,2548,15.00,38220.00,4586.40,33633.60,25480.00,8153.60,11/1/2013,11,November,2013
|
||||
Government,France,Carretera,2521.5,20.00,50430.00,6051.60,44378.40,25215.00,19163.40,1/1/2014,1,January,2014
|
||||
Channel Partners,Mexico,Montana,2661,12.00,31932.00,3831.84,28100.16,7983.00,20117.16,5/1/2014,5,May,2014
|
||||
Government,Germany,Paseo,1531,20.00,30620.00,3674.40,26945.60,15310.00,11635.60,12/1/2014,12,December,2014
|
||||
Government,France,VTT,1491,7.00,10437.00,1252.44,9184.56,7455.00,1729.56,3/1/2014,3,March,2014
|
||||
Government,Germany,VTT,1531,20.00,30620.00,3674.40,26945.60,15310.00,11635.60,12/1/2014,12,December,2014
|
||||
Channel Partners,Canada,Amarilla,2761,12.00,33132.00,3975.84,29156.16,8283.00,20873.16,9/1/2013,9,September,2013
|
||||
Midmarket,United States of America,Carretera,2567,15.00,38505.00,5005.65,33499.35,25670.00,7829.35,6/1/2014,6,June,2014
|
||||
Midmarket,United States of America,VTT,2567,15.00,38505.00,5005.65,33499.35,25670.00,7829.35,6/1/2014,6,June,2014
|
||||
Government,Canada,Carretera,923,350.00,323050.00,41996.50,281053.50,239980.00,41073.50,3/1/2014,3,March,2014
|
||||
Government,France,Carretera,1790,350.00,626500.00,81445.00,545055.00,465400.00,79655.00,3/1/2014,3,March,2014
|
||||
Government,Germany,Carretera,442,20.00,8840.00,1149.20,7690.80,4420.00,3270.80,9/1/2013,9,September,2013
|
||||
Government,United States of America,Montana,982.5,350.00,343875.00,44703.75,299171.25,255450.00,43721.25,1/1/2014,1,January,2014
|
||||
Government,United States of America,Montana,1298,7.00,9086.00,1181.18,7904.82,6490.00,1414.82,2/1/2014,2,February,2014
|
||||
Channel Partners,Mexico,Montana,604,12.00,7248.00,942.24,6305.76,1812.00,4493.76,6/1/2014,6,June,2014
|
||||
Government,Mexico,Montana,2255,20.00,45100.00,5863.00,39237.00,22550.00,16687.00,7/1/2014,7,July,2014
|
||||
Government,Canada,Montana,1249,20.00,24980.00,3247.40,21732.60,12490.00,9242.60,10/1/2014,10,October,2014
|
||||
Government,United States of America,Paseo,1438.5,7.00,10069.50,1309.04,8760.47,7192.50,1567.97,1/1/2014,1,January,2014
|
||||
Small Business,Germany,Paseo,807,300.00,242100.00,31473.00,210627.00,201750.00,8877.00,1/1/2014,1,January,2014
|
||||
Government,United States of America,Paseo,2641,20.00,52820.00,6866.60,45953.40,26410.00,19543.40,2/1/2014,2,February,2014
|
||||
Government,Germany,Paseo,2708,20.00,54160.00,7040.80,47119.20,27080.00,20039.20,2/1/2014,2,February,2014
|
||||
Government,Canada,Paseo,2632,350.00,921200.00,119756.00,801444.00,684320.00,117124.00,6/1/2014,6,June,2014
|
||||
Enterprise,Canada,Paseo,1583,125.00,197875.00,25723.75,172151.25,189960.00,-17808.75,6/1/2014,6,June,2014
|
||||
Channel Partners,Mexico,Paseo,571,12.00,6852.00,890.76,5961.24,1713.00,4248.24,7/1/2014,7,July,2014
|
||||
Government,France,Paseo,2696,7.00,18872.00,2453.36,16418.64,13480.00,2938.64,8/1/2014,8,August,2014
|
||||
Midmarket,Canada,Paseo,1565,15.00,23475.00,3051.75,20423.25,15650.00,4773.25,10/1/2014,10,October,2014
|
||||
Government,Canada,Paseo,1249,20.00,24980.00,3247.40,21732.60,12490.00,9242.60,10/1/2014,10,October,2014
|
||||
Government,Germany,Paseo,357,350.00,124950.00,16243.50,108706.50,92820.00,15886.50,11/1/2014,11,November,2014
|
||||
Channel Partners,Germany,Paseo,1013,12.00,12156.00,1580.28,10575.72,3039.00,7536.72,12/1/2014,12,December,2014
|
||||
Midmarket,France,Velo,3997.5,15.00,59962.50,7795.13,52167.38,39975.00,12192.38,1/1/2014,1,January,2014
|
||||
Government,Canada,Velo,2632,350.00,921200.00,119756.00,801444.00,684320.00,117124.00,6/1/2014,6,June,2014
|
||||
Government,France,Velo,1190,7.00,8330.00,1082.90,7247.10,5950.00,1297.10,6/1/2014,6,June,2014
|
||||
Channel Partners,Mexico,Velo,604,12.00,7248.00,942.24,6305.76,1812.00,4493.76,6/1/2014,6,June,2014
|
||||
Midmarket,Germany,Velo,660,15.00,9900.00,1287.00,8613.00,6600.00,2013.00,9/1/2013,9,September,2013
|
||||
Channel Partners,Mexico,Velo,410,12.00,4920.00,639.60,4280.40,1230.00,3050.40,10/1/2014,10,October,2014
|
||||
Small Business,Mexico,Velo,2605,300.00,781500.00,101595.00,679905.00,651250.00,28655.00,11/1/2013,11,November,2013
|
||||
Channel Partners,Germany,Velo,1013,12.00,12156.00,1580.28,10575.72,3039.00,7536.72,12/1/2014,12,December,2014
|
||||
Enterprise,Canada,VTT,1583,125.00,197875.00,25723.75,172151.25,189960.00,-17808.75,6/1/2014,6,June,2014
|
||||
Midmarket,Canada,VTT,1565,15.00,23475.00,3051.75,20423.25,15650.00,4773.25,10/1/2014,10,October,2014
|
||||
Enterprise,Canada,Amarilla,1659,125.00,207375.00,26958.75,180416.25,199080.00,-18663.75,1/1/2014,1,January,2014
|
||||
Government,France,Amarilla,1190,7.00,8330.00,1082.90,7247.10,5950.00,1297.10,6/1/2014,6,June,2014
|
||||
Channel Partners,Mexico,Amarilla,410,12.00,4920.00,639.60,4280.40,1230.00,3050.40,10/1/2014,10,October,2014
|
||||
Channel Partners,Germany,Amarilla,1770,12.00,21240.00,2761.20,18478.80,5310.00,13168.80,12/1/2013,12,December,2013
|
||||
Government,Mexico,Carretera,2579,20.00,51580.00,7221.20,44358.80,25790.00,18568.80,4/1/2014,4,April,2014
|
||||
Government,United States of America,Carretera,1743,20.00,34860.00,4880.40,29979.60,17430.00,12549.60,5/1/2014,5,May,2014
|
||||
Government,United States of America,Carretera,2996,7.00,20972.00,2936.08,18035.92,14980.00,3055.92,10/1/2013,10,October,2013
|
||||
Government,Germany,Carretera,280,7.00,1960.00,274.40,1685.60,1400.00,285.60,12/1/2014,12,December,2014
|
||||
Government,France,Montana,293,7.00,2051.00,287.14,1763.86,1465.00,298.86,2/1/2014,2,February,2014
|
||||
Government,United States of America,Montana,2996,7.00,20972.00,2936.08,18035.92,14980.00,3055.92,10/1/2013,10,October,2013
|
||||
Midmarket,Germany,Paseo,278,15.00,4170.00,583.80,3586.20,2780.00,806.20,2/1/2014,2,February,2014
|
||||
Government,Canada,Paseo,2428,20.00,48560.00,6798.40,41761.60,24280.00,17481.60,3/1/2014,3,March,2014
|
||||
Midmarket,United States of America,Paseo,1767,15.00,26505.00,3710.70,22794.30,17670.00,5124.30,9/1/2014,9,September,2014
|
||||
Channel Partners,France,Paseo,1393,12.00,16716.00,2340.24,14375.76,4179.00,10196.76,10/1/2014,10,October,2014
|
||||
Government,Germany,VTT,280,7.00,1960.00,274.40,1685.60,1400.00,285.60,12/1/2014,12,December,2014
|
||||
Channel Partners,France,Amarilla,1393,12.00,16716.00,2340.24,14375.76,4179.00,10196.76,10/1/2014,10,October,2014
|
||||
Channel Partners,United States of America,Amarilla,2015,12.00,24180.00,3385.20,20794.80,6045.00,14749.80,12/1/2013,12,December,2013
|
||||
Small Business,Mexico,Carretera,801,300.00,240300.00,33642.00,206658.00,200250.00,6408.00,7/1/2014,7,July,2014
|
||||
Enterprise,France,Carretera,1023,125.00,127875.00,17902.50,109972.50,122760.00,-12787.50,9/1/2013,9,September,2013
|
||||
Small Business,Canada,Carretera,1496,300.00,448800.00,62832.00,385968.00,374000.00,11968.00,10/1/2014,10,October,2014
|
||||
Small Business,United States of America,Carretera,1010,300.00,303000.00,42420.00,260580.00,252500.00,8080.00,10/1/2014,10,October,2014
|
||||
Midmarket,Germany,Carretera,1513,15.00,22695.00,3177.30,19517.70,15130.00,4387.70,11/1/2014,11,November,2014
|
||||
Midmarket,Canada,Carretera,2300,15.00,34500.00,4830.00,29670.00,23000.00,6670.00,12/1/2014,12,December,2014
|
||||
Enterprise,Mexico,Carretera,2821,125.00,352625.00,49367.50,303257.50,338520.00,-35262.50,12/1/2013,12,December,2013
|
||||
Government,Canada,Montana,2227.5,350.00,779625.00,109147.50,670477.50,579150.00,91327.50,1/1/2014,1,January,2014
|
||||
Government,Germany,Montana,1199,350.00,419650.00,58751.00,360899.00,311740.00,49159.00,4/1/2014,4,April,2014
|
||||
Government,Canada,Montana,200,350.00,70000.00,9800.00,60200.00,52000.00,8200.00,5/1/2014,5,May,2014
|
||||
Government,Canada,Montana,388,7.00,2716.00,380.24,2335.76,1940.00,395.76,9/1/2014,9,September,2014
|
||||
Government,Mexico,Montana,1727,7.00,12089.00,1692.46,10396.54,8635.00,1761.54,10/1/2013,10,October,2013
|
||||
Midmarket,Canada,Montana,2300,15.00,34500.00,4830.00,29670.00,23000.00,6670.00,12/1/2014,12,December,2014
|
||||
Government,Mexico,Paseo,260,20.00,5200.00,728.00,4472.00,2600.00,1872.00,2/1/2014,2,February,2014
|
||||
Midmarket,Canada,Paseo,2470,15.00,37050.00,5187.00,31863.00,24700.00,7163.00,9/1/2013,9,September,2013
|
||||
Midmarket,Canada,Paseo,1743,15.00,26145.00,3660.30,22484.70,17430.00,5054.70,10/1/2013,10,October,2013
|
||||
Channel Partners,United States of America,Paseo,2914,12.00,34968.00,4895.52,30072.48,8742.00,21330.48,10/1/2014,10,October,2014
|
||||
Government,France,Paseo,1731,7.00,12117.00,1696.38,10420.62,8655.00,1765.62,10/1/2014,10,October,2014
|
||||
Government,Canada,Paseo,700,350.00,245000.00,34300.00,210700.00,182000.00,28700.00,11/1/2014,11,November,2014
|
||||
Channel Partners,Canada,Paseo,2222,12.00,26664.00,3732.96,22931.04,6666.00,16265.04,11/1/2013,11,November,2013
|
||||
Government,United States of America,Paseo,1177,350.00,411950.00,57673.00,354277.00,306020.00,48257.00,11/1/2014,11,November,2014
|
||||
Government,France,Paseo,1922,350.00,672700.00,94178.00,578522.00,499720.00,78802.00,11/1/2013,11,November,2013
|
||||
Enterprise,Mexico,Velo,1575,125.00,196875.00,27562.50,169312.50,189000.00,-19687.50,2/1/2014,2,February,2014
|
||||
Government,United States of America,Velo,606,20.00,12120.00,1696.80,10423.20,6060.00,4363.20,4/1/2014,4,April,2014
|
||||
Small Business,United States of America,Velo,2460,300.00,738000.00,103320.00,634680.00,615000.00,19680.00,7/1/2014,7,July,2014
|
||||
Small Business,Canada,Velo,269,300.00,80700.00,11298.00,69402.00,67250.00,2152.00,10/1/2013,10,October,2013
|
||||
Small Business,Germany,Velo,2536,300.00,760800.00,106512.00,654288.00,634000.00,20288.00,11/1/2013,11,November,2013
|
||||
Government,Mexico,VTT,2903,7.00,20321.00,2844.94,17476.06,14515.00,2961.06,3/1/2014,3,March,2014
|
||||
Small Business,United States of America,VTT,2541,300.00,762300.00,106722.00,655578.00,635250.00,20328.00,8/1/2014,8,August,2014
|
||||
Small Business,Canada,VTT,269,300.00,80700.00,11298.00,69402.00,67250.00,2152.00,10/1/2013,10,October,2013
|
||||
Small Business,Canada,VTT,1496,300.00,448800.00,62832.00,385968.00,374000.00,11968.00,10/1/2014,10,October,2014
|
||||
Small Business,United States of America,VTT,1010,300.00,303000.00,42420.00,260580.00,252500.00,8080.00,10/1/2014,10,October,2014
|
||||
Government,France,VTT,1281,350.00,448350.00,62769.00,385581.00,333060.00,52521.00,12/1/2013,12,December,2013
|
||||
Small Business,Canada,Amarilla,888,300.00,266400.00,37296.00,229104.00,222000.00,7104.00,3/1/2014,3,March,2014
|
||||
Enterprise,United States of America,Amarilla,2844,125.00,355500.00,49770.00,305730.00,341280.00,-35550.00,5/1/2014,5,May,2014
|
||||
Channel Partners,France,Amarilla,2475,12.00,29700.00,4158.00,25542.00,7425.00,18117.00,8/1/2014,8,August,2014
|
||||
Midmarket,Canada,Amarilla,1743,15.00,26145.00,3660.30,22484.70,17430.00,5054.70,10/1/2013,10,October,2013
|
||||
Channel Partners,United States of America,Amarilla,2914,12.00,34968.00,4895.52,30072.48,8742.00,21330.48,10/1/2014,10,October,2014
|
||||
Government,France,Amarilla,1731,7.00,12117.00,1696.38,10420.62,8655.00,1765.62,10/1/2014,10,October,2014
|
||||
Government,Mexico,Amarilla,1727,7.00,12089.00,1692.46,10396.54,8635.00,1761.54,10/1/2013,10,October,2013
|
||||
Midmarket,Mexico,Amarilla,1870,15.00,28050.00,3927.00,24123.00,18700.00,5423.00,11/1/2013,11,November,2013
|
||||
Enterprise,France,Carretera,1174,125.00,146750.00,22012.50,124737.50,140880.00,-16142.50,8/1/2014,8,August,2014
|
||||
Enterprise,Germany,Carretera,2767,125.00,345875.00,51881.25,293993.75,332040.00,-38046.25,8/1/2014,8,August,2014
|
||||
Enterprise,Germany,Carretera,1085,125.00,135625.00,20343.75,115281.25,130200.00,-14918.75,10/1/2014,10,October,2014
|
||||
Small Business,Mexico,Montana,546,300.00,163800.00,24570.00,139230.00,136500.00,2730.00,10/1/2014,10,October,2014
|
||||
Government,Germany,Paseo,1158,20.00,23160.00,3474.00,19686.00,11580.00,8106.00,3/1/2014,3,March,2014
|
||||
Midmarket,Canada,Paseo,1614,15.00,24210.00,3631.50,20578.50,16140.00,4438.50,4/1/2014,4,April,2014
|
||||
Government,Mexico,Paseo,2535,7.00,17745.00,2661.75,15083.25,12675.00,2408.25,4/1/2014,4,April,2014
|
||||
Government,Mexico,Paseo,2851,350.00,997850.00,149677.50,848172.50,741260.00,106912.50,5/1/2014,5,May,2014
|
||||
Midmarket,Canada,Paseo,2559,15.00,38385.00,5757.75,32627.25,25590.00,7037.25,8/1/2014,8,August,2014
|
||||
Government,United States of America,Paseo,267,20.00,5340.00,801.00,4539.00,2670.00,1869.00,10/1/2013,10,October,2013
|
||||
Enterprise,Germany,Paseo,1085,125.00,135625.00,20343.75,115281.25,130200.00,-14918.75,10/1/2014,10,October,2014
|
||||
Midmarket,Germany,Paseo,1175,15.00,17625.00,2643.75,14981.25,11750.00,3231.25,10/1/2014,10,October,2014
|
||||
Government,United States of America,Paseo,2007,350.00,702450.00,105367.50,597082.50,521820.00,75262.50,11/1/2013,11,November,2013
|
||||
Government,Mexico,Paseo,2151,350.00,752850.00,112927.50,639922.50,559260.00,80662.50,11/1/2013,11,November,2013
|
||||
Channel Partners,United States of America,Paseo,914,12.00,10968.00,1645.20,9322.80,2742.00,6580.80,12/1/2014,12,December,2014
|
||||
Government,France,Paseo,293,20.00,5860.00,879.00,4981.00,2930.00,2051.00,12/1/2014,12,December,2014
|
||||
Channel Partners,Mexico,Velo,500,12.00,6000.00,900.00,5100.00,1500.00,3600.00,3/1/2014,3,March,2014
|
||||
Midmarket,France,Velo,2826,15.00,42390.00,6358.50,36031.50,28260.00,7771.50,5/1/2014,5,May,2014
|
||||
Enterprise,France,Velo,663,125.00,82875.00,12431.25,70443.75,79560.00,-9116.25,9/1/2014,9,September,2014
|
||||
Small Business,United States of America,Velo,2574,300.00,772200.00,115830.00,656370.00,643500.00,12870.00,11/1/2013,11,November,2013
|
||||
Enterprise,United States of America,Velo,2438,125.00,304750.00,45712.50,259037.50,292560.00,-33522.50,12/1/2013,12,December,2013
|
||||
Channel Partners,United States of America,Velo,914,12.00,10968.00,1645.20,9322.80,2742.00,6580.80,12/1/2014,12,December,2014
|
||||
Government,Canada,VTT,865.5,20.00,17310.00,2596.50,14713.50,8655.00,6058.50,7/1/2014,7,July,2014
|
||||
Midmarket,Germany,VTT,492,15.00,7380.00,1107.00,6273.00,4920.00,1353.00,7/1/2014,7,July,2014
|
||||
Government,United States of America,VTT,267,20.00,5340.00,801.00,4539.00,2670.00,1869.00,10/1/2013,10,October,2013
|
||||
Midmarket,Germany,VTT,1175,15.00,17625.00,2643.75,14981.25,11750.00,3231.25,10/1/2014,10,October,2014
|
||||
Enterprise,Canada,VTT,2954,125.00,369250.00,55387.50,313862.50,354480.00,-40617.50,11/1/2013,11,November,2013
|
||||
Enterprise,Germany,VTT,552,125.00,69000.00,10350.00,58650.00,66240.00,-7590.00,11/1/2014,11,November,2014
|
||||
Government,France,VTT,293,20.00,5860.00,879.00,4981.00,2930.00,2051.00,12/1/2014,12,December,2014
|
||||
Small Business,France,Amarilla,2475,300.00,742500.00,111375.00,631125.00,618750.00,12375.00,3/1/2014,3,March,2014
|
||||
Small Business,Mexico,Amarilla,546,300.00,163800.00,24570.00,139230.00,136500.00,2730.00,10/1/2014,10,October,2014
|
||||
Government,Mexico,Montana,1368,7.00,9576.00,1436.40,8139.60,6840.00,1299.60,2/1/2014,2,February,2014
|
||||
Government,Canada,Paseo,723,7.00,5061.00,759.15,4301.85,3615.00,686.85,4/1/2014,4,April,2014
|
||||
Channel Partners,United States of America,VTT,1806,12.00,21672.00,3250.80,18421.20,5418.00,13003.20,5/1/2014,5,May,2014
|
||||
|
@@ -0,0 +1,62 @@
|
||||
{
|
||||
"openapi": "3.1.0",
|
||||
"info": {
|
||||
"title": "get weather data",
|
||||
"description": "Retrieves current weather data for a location based on wttr.in.",
|
||||
"version": "v1.0.0"
|
||||
},
|
||||
"servers": [
|
||||
{
|
||||
"url": "https://wttr.in"
|
||||
}
|
||||
],
|
||||
"auth": [],
|
||||
"paths": {
|
||||
"/{location}": {
|
||||
"get": {
|
||||
"description": "Get weather information for a specific location",
|
||||
"operationId": "GetCurrentWeather",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "location",
|
||||
"in": "path",
|
||||
"description": "City or location to retrieve the weather for",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "format",
|
||||
"in": "query",
|
||||
"description": "Always use j1 value for this parameter",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"default": "j1"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful response",
|
||||
"content": {
|
||||
"text/plain": {
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Location not found"
|
||||
}
|
||||
},
|
||||
"deprecated": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemes": {}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user