mirror of
https://github.com/microsoft/semantic-kernel.git
synced 2026-10-12 02:19:06 +00:00
Python: Support Declarative Agent Spec for ChatCompletionAgent & AzureAIAgent (#11982)
### Motivation and Context This is the first installment of a series of PRs related to adding single agent declarative spec support. SK .NET has had support for single agent declarative specs for a bit now, and Python needs the functionality as well. This PR adds the core abstractions and functionality to be able to define agents via a declarative yaml spec. Declarative specs require the use of a kernel, and if in the case of function calling, require that any plugins defined in the spec already be present in the kernel. For naming, there are placeholder names that are camel case. This needs to be the case so it can match .NET. <!-- Thank you for your contribution to the semantic-kernel repo! Please help reviewers and future users, providing the following information: 1. Why is this change required? 2. What problem does it solve? 3. What scenario does it contribute to? 4. If it fixes an open issue, please link to the issue here. --> ### Description Provide declarative spec support for ChatCompletionAgent and AzureAIAgent - Adds many samples to show the use of declarative specs to handle various AzureAIAgent + ChatCompletionAgent tools/plugins. - Closes #11978 <!-- Describe your changes, the overall approach, the underlying design. These notes will help understanding how your code works. Thanks! --> ### Contribution Checklist <!-- Before submitting this PR, please make sure: --> - [X] The code builds clean without any errors or warnings - [X] The PR follows the [SK Contribution Guidelines](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md) and the [pre-submission formatting script](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md#development-scripts) raises no violations - [X] All unit tests pass, and I have added new tests where possible - [X] I didn't break anyone 😄
This commit is contained in:
@@ -65,7 +65,7 @@ autogen = [
|
||||
"autogen-agentchat >= 0.2, <0.4"
|
||||
]
|
||||
aws = [
|
||||
"boto3>=1.36.4,<1.38.0",
|
||||
"boto3>=1.36.4,<1.39.0",
|
||||
]
|
||||
azure = [
|
||||
"azure-ai-inference >= 1.0.0b6",
|
||||
|
||||
@@ -13,6 +13,13 @@
|
||||
- [Azure AI Agent with Bing Grounding Streaming with Message Callback](./agents/azure_ai_agent/azure_ai_agent_bing_grounding_streaming_with_message_callback.py)
|
||||
- [Azure AI Agent with Bing Grounding](./agents/azure_ai_agent/azure_ai_agent_bing_grounding.py)
|
||||
- [Azure AI Agent with Code Interpreter Streaming with Message Callback](./agents/azure_ai_agent/azure_ai_agent_code_interpreter_streaming_with_message_callback.py)
|
||||
- [Azure AI Agent Declarative with Azure AI Search](./agents/azure_ai_agent/azure_ai_agent_declarative_azure_ai_search.py)
|
||||
- [Azure AI Agent Declarative with Bing Grounding](./agents/azure_ai_agent/azure_ai_agent_declarative_bing_grounding.py)
|
||||
- [Azure AI Agent Declarative with Code Interpreter](./agents/azure_ai_agent/azure_ai_agent_declarative_code_interpreter.py)
|
||||
- [Azure AI Agent Declarative with File Search](./agents/azure_ai_agent/azure_ai_agent_declarative_file_search.py)
|
||||
- [Azure AI Agent Declarative with Function Calling From File](./agents/azure_ai_agent/azure_ai_agent_declarative_function_calling_from_file.py)
|
||||
- [Azure AI Agent Declarative with OpenAPI Interpreter](./agents/azure_ai_agent/azure_ai_agent_declarative_openapi.py)
|
||||
- [Azure AI Agent Declarative with Existing Agent ID](./agents/azure_ai_agent/azure_ai_agent_declarative_with_existing_agent_id.py)
|
||||
- [Azure AI Agent File Manipulation](./agents/azure_ai_agent/azure_ai_agent_file_manipulation.py)
|
||||
- [Azure AI Agent Prompt Templating](./agents/azure_ai_agent/azure_ai_agent_prompt_templating.py)
|
||||
- [Azure AI Agent Message Callback Streaming](./agents/azure_ai_agent/azure_ai_agent_message_callback_streaming.py)
|
||||
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
|
||||
from azure.identity.aio import DefaultAzureCredential
|
||||
|
||||
from semantic_kernel.agents import AzureAIAgent, AzureAIAgentSettings
|
||||
from semantic_kernel.agents.agent import AgentRegistry
|
||||
from semantic_kernel.contents.chat_message_content import ChatMessageContent
|
||||
from semantic_kernel.contents.function_call_content import FunctionCallContent
|
||||
from semantic_kernel.contents.function_result_content import FunctionResultContent
|
||||
|
||||
"""
|
||||
The following sample demonstrates how to create an Azure AI agent that answers
|
||||
user questions using the Azure AI Search tool.
|
||||
|
||||
The agent is created using a YAML declarative spec that configures the
|
||||
Azure AI Search tool. The agent is then used to answer user questions
|
||||
that required grounding context from the Azure AI Search index.
|
||||
|
||||
Note: the `AzureAISearchConnectionId` is in the format of:
|
||||
/subscriptions/<sub-id>/resourceGroups/<rg>/providers/Microsoft.MachineLearningServices/workspaces/<workspace>/connections/AzureAISearch
|
||||
|
||||
It can either be configured as an env var `AZURE_AI_AGENT_BING_CONNECTION_ID` or passed in as an extra to
|
||||
`create_from_yaml`: extras={
|
||||
"AzureAISearchConnectionId": "<azure_ai_search_connection_id>",
|
||||
"AzureAISearchIndexName": "<azure_ai_search_index_name>"
|
||||
}
|
||||
"""
|
||||
|
||||
# Define the YAML string for the sample
|
||||
spec = """
|
||||
type: foundry_agent
|
||||
name: AzureAISearchAgent
|
||||
instructions: Answer questions using your index to provide grounding context.
|
||||
description: This agent answers questions using AI Search to provide grounding context.
|
||||
model:
|
||||
id: ${AzureAI:ChatModelId}
|
||||
options:
|
||||
temperature: 0.4
|
||||
tools:
|
||||
- type: azure_ai_search
|
||||
options:
|
||||
tool_connections:
|
||||
- ${AzureAI:AzureAISearchConnectionId}
|
||||
index_name: ${AzureAI:AzureAISearchIndexName}
|
||||
"""
|
||||
|
||||
settings = AzureAIAgentSettings() # ChatModelId comes from .env/env vars
|
||||
|
||||
|
||||
async def main():
|
||||
async with (
|
||||
DefaultAzureCredential() as creds,
|
||||
AzureAIAgent.create_client(credential=creds) as client,
|
||||
):
|
||||
try:
|
||||
# Create the AzureAI Agent from the YAML spec
|
||||
# Note: the extras can be provided in the short-format (shown below) or
|
||||
# in the long-format (as shown in the YAML spec, with the `AzureAI:` prefix).
|
||||
# The short-format is used here for brevity
|
||||
agent: AzureAIAgent = await AgentRegistry.create_from_yaml(
|
||||
yaml_str=spec,
|
||||
client=client,
|
||||
settings=settings,
|
||||
extras={
|
||||
"AzureAISearchConnectionId": "<azure-ai-search-connection-id>",
|
||||
"AzureAISearchIndexName": "<azure-ai-search-index-name>",
|
||||
},
|
||||
)
|
||||
|
||||
# Define the task for the agent
|
||||
TASK = "What is Semantic Kernel?"
|
||||
|
||||
print(f"# User: '{TASK}'")
|
||||
|
||||
# Define a callback function to handle intermediate messages
|
||||
async def on_intermediate_message(message: ChatMessageContent):
|
||||
if message.items:
|
||||
for item in message.items:
|
||||
if isinstance(item, FunctionCallContent):
|
||||
print(f"# FunctionCallContent: arguments={item.arguments}")
|
||||
elif isinstance(item, FunctionResultContent):
|
||||
print(f"# FunctionResultContent: result={item.result}")
|
||||
|
||||
# Invoke the agent for the specified task
|
||||
async for response in agent.invoke(messages=TASK, on_intermediate_message=on_intermediate_message):
|
||||
print(f"# {response.name}: {response}")
|
||||
finally:
|
||||
# Cleanup: Delete the agent
|
||||
await client.agents.delete_agent(agent.id)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
|
||||
from azure.identity.aio import DefaultAzureCredential
|
||||
|
||||
from semantic_kernel.agents import AzureAIAgent, AzureAIAgentSettings
|
||||
from semantic_kernel.agents.agent import AgentRegistry
|
||||
|
||||
"""
|
||||
The following sample demonstrates how to create an Azure AI agent that answers
|
||||
user questions using the Bing Grounding tool.
|
||||
|
||||
The agent is created using a YAML declarative spec that configures the
|
||||
Bing Grounding tool. The agent is then used to answer user questions
|
||||
that require web search to answer correctly.
|
||||
|
||||
Note: the `BingConnectionId` is in the format of:
|
||||
/subscriptions/<sub_id>/resourceGroups/<rg>/providers/Microsoft.MachineLearningServices/workspaces/<workspace>/connections/<bing_connection_id>
|
||||
|
||||
It can either be configured as an env var `AZURE_AI_AGENT_BING_CONNECTION_ID` or passed in as an extra to
|
||||
`create_from_yaml`: extras={"BingConnectionId": "<bing_connection_id>"}
|
||||
"""
|
||||
|
||||
# Define the YAML string for the sample
|
||||
spec = """
|
||||
type: foundry_agent
|
||||
name: BingAgent
|
||||
instructions: Answer questions using Bing to provide grounding context.
|
||||
description: This agent answers questions using Bing to provide grounding context.
|
||||
model:
|
||||
id: ${AzureAI:ChatModelId}
|
||||
options:
|
||||
temperature: 0.4
|
||||
tools:
|
||||
- type: bing_grounding
|
||||
options:
|
||||
tool_connections:
|
||||
- ${AzureAI:BingConnectionId}
|
||||
"""
|
||||
|
||||
settings = AzureAIAgentSettings() # ChatModelId & BingConnectionId come from .env/env vars
|
||||
|
||||
|
||||
async def main():
|
||||
async with (
|
||||
DefaultAzureCredential() as creds,
|
||||
AzureAIAgent.create_client(credential=creds) as client,
|
||||
):
|
||||
try:
|
||||
# Create the AzureAI Agent from the YAML spec
|
||||
agent: AzureAIAgent = await AgentRegistry.create_from_yaml(
|
||||
yaml_str=spec,
|
||||
client=client,
|
||||
settings=settings,
|
||||
)
|
||||
|
||||
# Define the task for the agent
|
||||
TASK = "Who won the 2025 NCAA basketball championship?"
|
||||
|
||||
print(f"# User: '{TASK}'")
|
||||
|
||||
# Invoke the agent for the specified task
|
||||
async for response in agent.invoke(
|
||||
messages=TASK,
|
||||
):
|
||||
print(f"# {response.name}: {response}")
|
||||
finally:
|
||||
# Cleanup: Delete the thread and agent
|
||||
await client.agents.delete_agent(agent.id)
|
||||
|
||||
"""
|
||||
Sample output:
|
||||
|
||||
# User: 'Who won the 2025 NCAA basketball championship?'
|
||||
# BingAgent: The Florida Gators won the 2025 NCAA men's basketball championship, narrowly defeating the Houston
|
||||
Cougars 65-63 in the final game. This marked Florida's first national title since
|
||||
2007【3:5†source】【3:9†source】.
|
||||
"""
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
+150
@@ -0,0 +1,150 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
|
||||
from azure.ai.projects.models import FilePurpose
|
||||
from azure.identity.aio import DefaultAzureCredential
|
||||
|
||||
from semantic_kernel.agents import AzureAIAgent, AzureAIAgentSettings
|
||||
from semantic_kernel.agents.agent import AgentRegistry
|
||||
|
||||
"""
|
||||
The following sample demonstrates how to create an Azure AI agent that answers
|
||||
user questions using the code interpreter tool.
|
||||
|
||||
The agent is then used to answer user questions that require code to be generated and
|
||||
executed. The responses are handled in a streaming manner.
|
||||
"""
|
||||
|
||||
# Define the YAML string for the sample
|
||||
spec = """
|
||||
type: foundry_agent
|
||||
name: CodeInterpreterAgent
|
||||
description: Agent with code interpreter tool.
|
||||
instructions: >
|
||||
Use the code interpreter tool to answer questions that require code to be generated
|
||||
and executed.
|
||||
model:
|
||||
id: ${AzureAI:ChatModelId}
|
||||
connection:
|
||||
connection_string: ${AzureAI:ConnectionString}
|
||||
tools:
|
||||
- type: code_interpreter
|
||||
options:
|
||||
file_ids:
|
||||
- ${AzureAI:FileId1}
|
||||
"""
|
||||
|
||||
settings = AzureAIAgentSettings() # ChatModelId & ConnectionString come from env vars
|
||||
|
||||
|
||||
async def main():
|
||||
async with (
|
||||
DefaultAzureCredential() as creds,
|
||||
AzureAIAgent.create_client(credential=creds) as client,
|
||||
):
|
||||
# Create the CSV file path for the sample
|
||||
csv_file_path = os.path.join(
|
||||
os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__)))),
|
||||
"resources",
|
||||
"agent_assistant_file_manipulation",
|
||||
"sales.csv",
|
||||
)
|
||||
|
||||
try:
|
||||
# Upload the CSV file to the agent service
|
||||
file = await client.agents.upload_file_and_poll(file_path=csv_file_path, purpose=FilePurpose.AGENTS)
|
||||
|
||||
# Create the AzureAI Agent from the YAML spec
|
||||
# Note: the extras can be provided in the short-format (shown below) or
|
||||
# in the long-format (as shown in the YAML spec, with the `AzureAI:` prefix).
|
||||
# The short-format is used here for brevity
|
||||
agent: AzureAIAgent = await AgentRegistry.create_from_yaml(
|
||||
yaml_str=spec,
|
||||
client=client,
|
||||
settings=settings,
|
||||
extras={"FileId1": file.id},
|
||||
)
|
||||
|
||||
# Define the task for the agent
|
||||
TASK = "Give me the code to calculate the total sales for all segments."
|
||||
|
||||
print(f"# User: '{TASK}'")
|
||||
|
||||
# Invoke the agent for the specified task
|
||||
is_code = False
|
||||
last_role = None
|
||||
async for response in agent.invoke_stream(
|
||||
messages=TASK,
|
||||
):
|
||||
current_is_code = response.metadata.get("code", False)
|
||||
|
||||
if current_is_code:
|
||||
if not is_code:
|
||||
print("\n\n```python")
|
||||
is_code = True
|
||||
print(response.content, end="", flush=True)
|
||||
else:
|
||||
if is_code:
|
||||
print("\n```")
|
||||
is_code = False
|
||||
last_role = None
|
||||
if hasattr(response, "role") and response.role is not None and last_role != response.role:
|
||||
print(f"\n# {response.role}: ", end="", flush=True)
|
||||
last_role = response.role
|
||||
print(response.content, end="", flush=True)
|
||||
if is_code:
|
||||
print("```\n")
|
||||
print()
|
||||
finally:
|
||||
# Cleanup: Delete the thread and agent
|
||||
await client.agents.delete_agent(agent.id)
|
||||
await client.agents.delete_file(file.id)
|
||||
|
||||
"""
|
||||
Sample output:
|
||||
|
||||
# User: 'Give me the code to calculate the total sales for all segments.'
|
||||
|
||||
# AuthorRole.ASSISTANT: Let me first examine the contents of the uploaded file to determine its structure. This
|
||||
will allow me to create the appropriate code for calculating the total sales for all segments. Hang tight!
|
||||
|
||||
```python
|
||||
import pandas as pd
|
||||
|
||||
# Load the uploaded file to examine its contents
|
||||
file_path = '/mnt/data/assistant-3nXizu2EX2EwXikUz71uNc'
|
||||
data = pd.read_csv(file_path)
|
||||
|
||||
# Display the first few rows and column names to understand the structure of the dataset
|
||||
data.head(), data.columns
|
||||
```
|
||||
|
||||
# AuthorRole.ASSISTANT: The dataset contains several columns, including `Segment`, `Sales`, and others such as
|
||||
`Country`, `Product`, and date-related information. To calculate the total sales for all segments, we will:
|
||||
|
||||
1. Group the data by the `Segment` column.
|
||||
2. Sum the `Sales` column for each segment.
|
||||
3. Calculate the grand total of all sales across all segments.
|
||||
|
||||
Here is the code snippet for this task:
|
||||
|
||||
```python
|
||||
# Group by 'Segment' and sum up 'Sales'
|
||||
segment_sales = data.groupby('Segment')['Sales'].sum()
|
||||
|
||||
# Calculate the total sales across all segments
|
||||
total_sales = segment_sales.sum()
|
||||
|
||||
print("Total Sales per Segment:")
|
||||
print(segment_sales)
|
||||
print(f"\nGrand Total Sales: {total_sales}")
|
||||
```
|
||||
|
||||
Would you like me to execute this directly for the uploaded data?
|
||||
"""
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
|
||||
from azure.ai.projects.models import OpenAIFile, VectorStore
|
||||
from azure.identity.aio import DefaultAzureCredential
|
||||
|
||||
from semantic_kernel.agents import AzureAIAgent, AzureAIAgentSettings
|
||||
from semantic_kernel.agents.agent import AgentRegistry
|
||||
|
||||
"""
|
||||
The following sample demonstrates how to create an Azure AI agent that answers
|
||||
user questions using the file search tool.
|
||||
|
||||
The agent is used to answer user questions that require file search to help ground
|
||||
answers from the model.
|
||||
"""
|
||||
|
||||
# Define the YAML string for the sample
|
||||
spec = """
|
||||
type: foundry_agent
|
||||
name: FileSearchAgent
|
||||
description: Agent with code interpreter tool.
|
||||
instructions: >
|
||||
Use the code interpreter tool to answer questions that require code to be generated
|
||||
and executed.
|
||||
model:
|
||||
id: ${AzureAI:ChatModelId}
|
||||
connection:
|
||||
connection_string: ${AzureAI:ConnectionString}
|
||||
tools:
|
||||
- type: file_search
|
||||
options:
|
||||
vector_store_ids:
|
||||
- ${AzureAI:VectorStoreId}
|
||||
"""
|
||||
|
||||
settings = AzureAIAgentSettings() # ChatModelId & ConnectionString come from .env/env vars
|
||||
|
||||
|
||||
async def main():
|
||||
async with (
|
||||
DefaultAzureCredential() as creds,
|
||||
AzureAIAgent.create_client(credential=creds) as client,
|
||||
):
|
||||
# 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.dirname(os.path.realpath(__file__)))),
|
||||
"resources",
|
||||
"file_search",
|
||||
"employees.pdf",
|
||||
)
|
||||
# Upload the pdf file to the agent service
|
||||
file: OpenAIFile = await client.agents.upload_file_and_poll(file_path=pdf_file_path, purpose="assistants")
|
||||
vector_store: VectorStore = await client.agents.create_vector_store_and_poll(
|
||||
file_ids=[file.id], name="my_vectorstore"
|
||||
)
|
||||
|
||||
try:
|
||||
# Create the AzureAI Agent from the YAML spec
|
||||
# Note: the extras can be provided in the short-format (shown below) or
|
||||
# in the long-format (as shown in the YAML spec, with the `AzureAI:` prefix).
|
||||
# The short-format is used here for brevity
|
||||
agent: AzureAIAgent = await AgentRegistry.create_from_yaml(
|
||||
yaml_str=spec,
|
||||
client=client,
|
||||
settings=settings,
|
||||
extras={"VectorStoreId": vector_store.id},
|
||||
)
|
||||
|
||||
# Define the task for the agent
|
||||
TASK = "Who can help me if I have a sales question?"
|
||||
|
||||
print(f"# User: '{TASK}'")
|
||||
|
||||
# Invoke the agent for the specified task
|
||||
async for response in agent.invoke(
|
||||
messages=TASK,
|
||||
):
|
||||
print(f"# {response.name}: {response}")
|
||||
finally:
|
||||
# Cleanup: Delete the agent, vector store, and file
|
||||
await client.agents.delete_agent(agent.id)
|
||||
await client.agents.delete_vector_store(vector_store.id)
|
||||
await client.agents.delete_file(file.id)
|
||||
|
||||
"""
|
||||
Sample output:
|
||||
|
||||
# User: 'Who can help me if I have a sales question?'
|
||||
# FileSearchAgent: If you have a sales question, you may contact the following individuals:
|
||||
|
||||
1. **Hicran Bea** - Sales Manager
|
||||
2. **Mariam Jaslyn** - Sales Representative
|
||||
3. **Angelino Embla** - Sales Representative
|
||||
|
||||
This information comes from the employee records【4:0†source】.
|
||||
"""
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
from typing import Annotated
|
||||
|
||||
from azure.identity.aio import DefaultAzureCredential
|
||||
|
||||
from semantic_kernel.agents import AgentRegistry, AzureAIAgent, AzureAIAgentSettings, AzureAIAgentThread
|
||||
from semantic_kernel.functions.kernel_function_decorator import kernel_function
|
||||
|
||||
"""
|
||||
The following sample demonstrates how to create an Azure AI agent that answers
|
||||
user questions. The sample shows how to load a declarative spec from a file.
|
||||
The plugins/functions must already exist in the kernel.
|
||||
They are not created declaratively via the spec.
|
||||
"""
|
||||
|
||||
|
||||
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"
|
||||
|
||||
|
||||
# Function spec
|
||||
|
||||
settings = AzureAIAgentSettings() # The Spec's ChatModelId & ConnectionString come from .env/env vars
|
||||
|
||||
|
||||
async def main():
|
||||
async with (
|
||||
DefaultAzureCredential() as creds,
|
||||
AzureAIAgent.create_client(credential=creds) as client,
|
||||
):
|
||||
try:
|
||||
# Define the YAML file path for the sample
|
||||
file_path = os.path.join(
|
||||
os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__)))),
|
||||
"resources",
|
||||
"declarative_spec",
|
||||
"spec.yaml",
|
||||
)
|
||||
|
||||
# Create the AzureAI Agent from the YAML spec
|
||||
agent: AzureAIAgent = await AgentRegistry.create_from_file(
|
||||
file_path,
|
||||
plugins=[MenuPlugin()],
|
||||
client=client,
|
||||
settings=settings,
|
||||
)
|
||||
|
||||
# Create the agent
|
||||
user_inputs = [
|
||||
"Hello",
|
||||
"What is the special soup?",
|
||||
"How much does that cost?",
|
||||
"Thank you",
|
||||
]
|
||||
|
||||
# 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
|
||||
|
||||
for user_input in user_inputs:
|
||||
print(f"# User: '{user_input}'")
|
||||
# Invoke the agent for the specified task
|
||||
async for response in agent.invoke(
|
||||
messages=user_input,
|
||||
thread=thread,
|
||||
):
|
||||
print(f"# {response.name}: {response}")
|
||||
# Store the thread for the next iteration
|
||||
thread = response.thread
|
||||
finally:
|
||||
# Cleanup: Delete the thread and agent
|
||||
await client.agents.delete_agent(agent.id) if agent else None
|
||||
await thread.delete() if thread else None
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,197 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
|
||||
from azure.identity.aio import DefaultAzureCredential
|
||||
|
||||
from semantic_kernel.agents import AzureAIAgent, AzureAIAgentSettings
|
||||
from semantic_kernel.agents.agent import AgentRegistry
|
||||
|
||||
"""
|
||||
The following sample demonstrates how to create an Azure AI agent that answers
|
||||
user questions using the OpenAPI tool. The agent is then used to answer user
|
||||
questions that leverage a free weather API.
|
||||
"""
|
||||
|
||||
# Toggle between a JSON or a YAML OpenAPI spec
|
||||
USE_JSON_OPENAPI_SPEC = True
|
||||
|
||||
json_openapi_spec = """
|
||||
type: foundry_agent
|
||||
name: WeatherAgent
|
||||
instructions: Answer questions about the weather. For all other questions politely decline to answer.
|
||||
description: This agent answers question about the weather.
|
||||
model:
|
||||
id: ${AzureAI:ChatModelId}
|
||||
connection:
|
||||
connection_string: ${AzureAI:ConnectionString}
|
||||
options:
|
||||
temperature: 0.4
|
||||
tools:
|
||||
- type: openapi
|
||||
id: GetCurrentWeather
|
||||
description: Retrieves current weather data for a location based on wttr.in.
|
||||
options:
|
||||
specification: |
|
||||
{
|
||||
"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": {}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
yaml_openapi_spec = """
|
||||
type: foundry_agent
|
||||
name: WeatherAgent
|
||||
instructions: Answer questions about the weather. For all other questions politely decline to answer.
|
||||
description: This agent answers question about the weather.
|
||||
model:
|
||||
id: ${AzureAI:ChatModelId}
|
||||
options:
|
||||
temperature: 0.4
|
||||
tools:
|
||||
- type: openapi
|
||||
id: GetCurrentWeather
|
||||
description: Retrieves current weather data for a location based on wttr.in.
|
||||
options:
|
||||
specification:
|
||||
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: {}
|
||||
"""
|
||||
|
||||
settings = AzureAIAgentSettings() # ChatModelId & ConnectionString come from .env/env vars
|
||||
|
||||
|
||||
async def main():
|
||||
async with (
|
||||
DefaultAzureCredential() as creds,
|
||||
AzureAIAgent.create_client(credential=creds) as client,
|
||||
):
|
||||
try:
|
||||
# Create the AzureAI Agent from the YAML spec
|
||||
agent: AzureAIAgent = await AgentRegistry.create_from_yaml(
|
||||
yaml_str=json_openapi_spec if USE_JSON_OPENAPI_SPEC else yaml_openapi_spec,
|
||||
client=client,
|
||||
settings=settings,
|
||||
)
|
||||
|
||||
# Define the task for the agent
|
||||
TASK = "What is the current weather in Seoul?"
|
||||
|
||||
print(f"# User: '{TASK}'")
|
||||
|
||||
# Invoke the agent for the specified task
|
||||
async for response in agent.invoke(
|
||||
messages=TASK,
|
||||
):
|
||||
print(f"# {response.name}: {response}")
|
||||
finally:
|
||||
# Cleanup: Delete the agent, vector store, and file
|
||||
await client.agents.delete_agent(agent.id)
|
||||
|
||||
"""
|
||||
Sample output:
|
||||
|
||||
# User: 'What is the current weather in Seoul?'
|
||||
# WeatherAgent: The current weather in Seoul is 14°C (57°F) with "light drizzle." It feels like 13°C (55°F).
|
||||
The humidity is at 81%, and there is heavy cloud cover (99%). The visibility is reduced to 2 km (1 mile),
|
||||
and the wind is coming from the east at 11 km/h (7 mph)
|
||||
"""
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
|
||||
from azure.identity.aio import DefaultAzureCredential
|
||||
|
||||
from semantic_kernel.agents import AzureAIAgent
|
||||
from semantic_kernel.agents.agent import AgentRegistry
|
||||
|
||||
"""
|
||||
The following sample demonstrates how to create an Azure AI agent based
|
||||
on an existing agent ID.
|
||||
"""
|
||||
|
||||
# Define the YAML string for the sample
|
||||
spec = """
|
||||
id: ${AzureAI:AgentId}
|
||||
type: foundry_agent
|
||||
instructions: You are helpful agent who always responds in French.
|
||||
"""
|
||||
|
||||
|
||||
async def main():
|
||||
async with (
|
||||
DefaultAzureCredential() as creds,
|
||||
AzureAIAgent.create_client(credential=creds) as client,
|
||||
):
|
||||
try:
|
||||
# Create the AzureAI Agent from the YAML spec
|
||||
# Note: the extras can be provided in the short-format (shown below) or
|
||||
# in the long-format (as shown in the YAML spec, with the `AzureAI:` prefix).
|
||||
# The short-format is used here for brevity
|
||||
agent: AzureAIAgent = await AgentRegistry.create_from_yaml(
|
||||
yaml_str=spec,
|
||||
client=client,
|
||||
extras={"AgentId": "<my-agent-id>"}, # Specify the existing agent ID
|
||||
)
|
||||
|
||||
# Define the task for the agent
|
||||
TASK = "Why is the sky blue?"
|
||||
|
||||
print(f"# User: '{TASK}'")
|
||||
|
||||
# Invoke the agent for the specified task
|
||||
async for response in agent.invoke(
|
||||
messages=TASK,
|
||||
):
|
||||
print(f"# {response.name}: {response}")
|
||||
finally:
|
||||
# Cleanup: Delete the thread and agent
|
||||
await client.agents.delete_agent(agent.id)
|
||||
|
||||
"""
|
||||
Sample output:
|
||||
|
||||
# User: 'Why is the sky blue?'
|
||||
# WeatherAgent: Le ciel est bleu à cause d'un phénomène appelé **diffusion de Rayleigh**. La lumière du
|
||||
Soleil est composée de toutes les couleurs du spectre visible, mais lorsqu'elle traverse l'atmosphère
|
||||
terrestre, elle entre en contact avec les molécules d'air et les particules présentes.
|
||||
|
||||
Les couleurs à courtes longueurs d'onde, comme le bleu et le violet, sont diffusées dans toutes les directions
|
||||
beaucoup plus efficacement que les couleurs à longues longueurs d'onde, comme le rouge et l'orange. Bien que le
|
||||
violet ait une longueur d'onde encore plus courte que le bleu, nos yeux sont moins sensibles à cette couleur,
|
||||
et une partie du violet est également absorbée par la haute atmosphère. Ainsi, le bleu domine, donnant au ciel
|
||||
sa couleur caractéristique.
|
||||
"""
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,15 @@
|
||||
type: foundry_agent
|
||||
name: FunctionCallingAgent
|
||||
description: This agent uses the provided functions to answer questions about the menu.
|
||||
instructions: Use the provided functions to answer questions about the menu.
|
||||
model:
|
||||
id: ${AzureAI:ChatModelId}
|
||||
connection:
|
||||
connection_string: ${AzureAI:ConnectionString}
|
||||
options:
|
||||
temperature: 0.4
|
||||
tools:
|
||||
- id: MenuPlugin.get_specials
|
||||
type: function
|
||||
- id: MenuPlugin.get_item_price
|
||||
type: function
|
||||
Binary file not shown.
@@ -22,16 +22,17 @@ The getting started with agents examples include:
|
||||
|
||||
Example|Description
|
||||
---|---
|
||||
[step1_chat_completion_agent_simple](../getting_started_with_agents/chat_completion/step1_chat_completion_agent_simple.py)|How to create and use a simple chat completion agent.
|
||||
[step2_chat_completion_agent_thread_management](../getting_started_with_agents/chat_completion/step2_chat_completion_agent_thread_management.py)|How to create and use a chat completion with a thread.
|
||||
[step3_chat_completion_agent_with_kernel](../getting_started_with_agents/chat_completion/step3_chat_completion_agent_with_kernel.py)|How to create and use a a chat completion agent with the AI service created on the kernel.
|
||||
[step4_chat_completion_agent_plugin_simple](../getting_started_with_agents/chat_completion/step4_chat_completion_agent_plugin_simple.py)|How to create a simple chat completion agent and specify plugins via the constructor with a kernel.
|
||||
[step5_chat_completion_agent_plugin_with_kernel](../getting_started_with_agents/chat_completion/step5_chat_completion_agent_plugin_with_kernel.py)|How to create and use a chat completion agent by registering plugins on the kernel.
|
||||
[step6_chat_completion_agent_group_chat](../getting_started_with_agents/chat_completion/step6_chat_completion_agent_group_chat.py)|How to create a conversation between agents.
|
||||
[step7_kernel_function_strategies](../getting_started_with_agents/chat_completion/step7_kernel_function_strategies.py)|How to utilize a `KernelFunction` as a chat strategy.
|
||||
[step8_chat_completion_agent_json_result](../getting_started_with_agents/chat_completion/step8_chat_completion_agent_json_result.py)|How to have an agent produce JSON.
|
||||
[step9_chat_completion_agent_logging](../getting_started_with_agents/chat_completion/step9_chat_completion_agent_logging.py)|How to enable logging for agents.
|
||||
[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
|
||||
|
||||
@@ -42,7 +43,9 @@ Example|Description
|
||||
[step3_azure_ai_agent_group_chat](../getting_started_with_agents/azure_ai_agent/step3_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/step4_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/step5_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/step6_azure_ai_agent_openapi.py)|How to use the Open API tool for an Azure AI agent.
|
||||
[step6_azure_ai_agent_openapi](../getting_started_with_agents/azure_ai_agent/step6_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/step7_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/step8_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)._
|
||||
|
||||
|
||||
@@ -28,15 +28,13 @@ USER_INPUTS = [
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
ai_agent_settings = AzureAIAgentSettings()
|
||||
|
||||
async with (
|
||||
DefaultAzureCredential() 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=ai_agent_settings.model_deployment_name,
|
||||
model=AzureAIAgentSettings().model_deployment_name,
|
||||
name="Assistant",
|
||||
instructions="Answer the user's questions.",
|
||||
)
|
||||
|
||||
+77
-31
@@ -1,8 +1,9 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
|
||||
from azure.ai.projects.models import CodeInterpreterTool
|
||||
from azure.ai.projects.models import CodeInterpreterTool, FilePurpose
|
||||
from azure.identity.aio import DefaultAzureCredential
|
||||
|
||||
from semantic_kernel.agents import AzureAIAgent, AzureAIAgentSettings, AzureAIAgentThread
|
||||
@@ -13,7 +14,7 @@ The following sample demonstrates how to create a simple, Azure AI agent that
|
||||
uses the code interpreter tool to answer a coding question.
|
||||
"""
|
||||
|
||||
TASK = "Use code to determine the values in the Fibonacci sequence that that are less then the value of 101."
|
||||
TASK = "What's the total sum of all sales for all segments using Python?"
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
@@ -22,64 +23,109 @@ async def main() -> None:
|
||||
AzureAIAgent.create_client(credential=creds) as client,
|
||||
):
|
||||
# 1. Create an agent with a code interpreter on the Azure AI agent service
|
||||
code_interpreter = CodeInterpreterTool()
|
||||
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.upload_file_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,
|
||||
)
|
||||
|
||||
# 2. Create a Semantic Kernel agent for the Azure AI agent
|
||||
# 4. Create a Semantic Kernel agent for the Azure AI agent
|
||||
agent = AzureAIAgent(
|
||||
client=client,
|
||||
definition=agent_definition,
|
||||
)
|
||||
|
||||
# 3. Create a thread for the agent
|
||||
# 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}'")
|
||||
# 4. Invoke the agent for the specified thread for response
|
||||
# 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:
|
||||
# 6. Cleanup: Delete the thread and agent
|
||||
# 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.delete_file(file.id)
|
||||
|
||||
"""
|
||||
Sample Output:
|
||||
# User: 'Use code to determine the values in the Fibonacci sequence that that are less then the value of 101.'
|
||||
# Agent: # Function to generate Fibonacci sequence values less than a given limit
|
||||
def fibonacci_less_than(limit):
|
||||
fib_sequence = []
|
||||
a, b = 0, 1
|
||||
while a < limit:
|
||||
fib_sequence.append(a)
|
||||
a, b = b, a + b
|
||||
a, b = 0, 1
|
||||
while a < limit:
|
||||
fib_sequence.append(a)
|
||||
a, b = 0, 1
|
||||
while a < limit:
|
||||
a, b = 0, 1
|
||||
a, b = 0, 1
|
||||
while a < limit:
|
||||
fib_sequence.append(a)
|
||||
a, b = b, a + b
|
||||
return fib_sequence
|
||||
# User: 'Give me the code to calculate the total sales for all segments.'
|
||||
|
||||
Generate Fibonacci sequence values less than 101
|
||||
fibonacci_values = fibonacci_less_than(101)
|
||||
fibonacci_values
|
||||
# Agent: The values in the Fibonacci sequence that are less than 101 are:
|
||||
# AuthorRole.ASSISTANT: Let me first load the uploaded file to understand its contents before providing
|
||||
tailored code.
|
||||
|
||||
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]
|
||||
```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!
|
||||
"""
|
||||
|
||||
|
||||
|
||||
+110
@@ -0,0 +1,110 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
from typing import Annotated
|
||||
|
||||
from azure.identity.aio import DefaultAzureCredential
|
||||
|
||||
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 (
|
||||
DefaultAzureCredential() 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())
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
from typing import Annotated
|
||||
|
||||
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 OpenAIChatCompletion
|
||||
from semantic_kernel.functions import 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.
|
||||
"""
|
||||
|
||||
|
||||
# 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 the agent from YAML + inject the AI service
|
||||
agent: ChatCompletionAgent = await AgentRegistry.create_from_yaml(
|
||||
AGENT_YAML, kernel=kernel, service=OpenAIChatCompletion()
|
||||
)
|
||||
|
||||
# 6. Create a thread to hold the conversation
|
||||
thread: ChatHistoryAgentThread | None = None
|
||||
|
||||
for user_input in USER_INPUTS:
|
||||
print(f"# User: {user_input}")
|
||||
# 7. Invoke the agent for a response
|
||||
response = await agent.get_response(messages=user_input, thread=thread)
|
||||
print(f"# {response.name}: {response}")
|
||||
thread = response.thread
|
||||
|
||||
# 8. Cleanup the thread
|
||||
await thread.delete() if thread else None
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -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
|
||||
|
@@ -4,6 +4,10 @@ import importlib
|
||||
|
||||
_AGENTS = {
|
||||
"Agent": ".agent",
|
||||
"AgentChat": ".group_chat.agent_chat",
|
||||
"AgentGroupChat": ".group_chat.agent_group_chat",
|
||||
"AgentSpec": ".agent",
|
||||
"AgentRegistry": ".agent",
|
||||
"AgentResponseItem": ".agent",
|
||||
"AgentThread": ".agent",
|
||||
"AutoGenConversableAgent": ".autogen.autogen_conversable_agent",
|
||||
@@ -11,6 +15,9 @@ _AGENTS = {
|
||||
"AzureAIAgent": ".azure_ai.azure_ai_agent",
|
||||
"AzureAIAgentSettings": ".azure_ai.azure_ai_agent_settings",
|
||||
"AzureAIAgentThread": ".azure_ai.azure_ai_agent",
|
||||
"AzureAssistantAgent": ".open_ai.azure_assistant_agent",
|
||||
"AssistantAgentThread": ".open_ai.open_ai_assistant_agent",
|
||||
"AzureResponsesAgent": ".open_ai.azure_responses_agent",
|
||||
"BedrockAgent": ".bedrock.bedrock_agent",
|
||||
"BedrockAgentThread": ".bedrock.bedrock_agent",
|
||||
"ChatCompletionAgent": ".chat_completion.chat_completion_agent",
|
||||
@@ -19,15 +26,15 @@ _AGENTS = {
|
||||
"CopilotStudioAgentAuthMode": ".copilot_studio.copilot_studio_agent_settings",
|
||||
"CopilotStudioAgentSettings": ".copilot_studio.copilot_studio_agent_settings",
|
||||
"CopilotStudioAgentThread": ".copilot_studio.copilot_studio_agent",
|
||||
"AgentChat": ".group_chat.agent_chat",
|
||||
"AgentGroupChat": ".group_chat.agent_group_chat",
|
||||
"AzureAssistantAgent": ".open_ai.azure_assistant_agent",
|
||||
"AssistantAgentThread": ".open_ai.open_ai_assistant_agent",
|
||||
"DeclarativeSpecMixin": ".agent",
|
||||
"OpenAIAssistantAgent": ".open_ai.open_ai_assistant_agent",
|
||||
"OpenAIResponsesAgent": ".open_ai.openai_responses_agent",
|
||||
"AzureResponsesAgent": ".open_ai.azure_responses_agent",
|
||||
"ModelConnection": ".agent",
|
||||
"ModelSpec": ".agent",
|
||||
"ResponsesAgentThread": ".open_ai.openai_responses_agent",
|
||||
"RunPollingOptions": ".open_ai.run_polling_options",
|
||||
"register_agent_type": ".agent",
|
||||
"ToolSpec": ".agent",
|
||||
"ConcurrentOrchestration": ".orchestration.concurrent",
|
||||
"SequentialOrchestration": ".orchestration.sequential",
|
||||
}
|
||||
|
||||
@@ -1,6 +1,17 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
from .agent import Agent, AgentResponseItem, AgentThread
|
||||
from .agent import (
|
||||
Agent,
|
||||
AgentRegistry,
|
||||
AgentResponseItem,
|
||||
AgentSpec,
|
||||
AgentThread,
|
||||
DeclarativeSpecMixin,
|
||||
ModelConnection,
|
||||
ModelSpec,
|
||||
ToolSpec,
|
||||
register_agent_type,
|
||||
)
|
||||
from .autogen.autogen_conversable_agent import AutoGenConversableAgent, AutoGenConversableAgentThread
|
||||
from .azure_ai.azure_ai_agent import AzureAIAgent, AzureAIAgentThread
|
||||
from .azure_ai.azure_ai_agent_settings import AzureAIAgentSettings
|
||||
@@ -22,7 +33,9 @@ __all__ = [
|
||||
"Agent",
|
||||
"AgentChat",
|
||||
"AgentGroupChat",
|
||||
"AgentRegistry",
|
||||
"AgentResponseItem",
|
||||
"AgentSpec",
|
||||
"AgentThread",
|
||||
"AssistantAgentThread",
|
||||
"AutoGenConversableAgent",
|
||||
@@ -41,9 +54,14 @@ __all__ = [
|
||||
"CopilotStudioAgentAuthMode",
|
||||
"CopilotStudioAgentSettings",
|
||||
"CopilotStudioAgentThread",
|
||||
"DeclarativeSpecMixin",
|
||||
"ModelConnection",
|
||||
"ModelSpec",
|
||||
"OpenAIAssistantAgent",
|
||||
"OpenAIResponsesAgent",
|
||||
"ResponsesAgentThread",
|
||||
"RunPollingOptions",
|
||||
"SequentialOrchestration",
|
||||
"ToolSpec",
|
||||
"register_agent_type",
|
||||
]
|
||||
|
||||
@@ -1,21 +1,25 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import importlib
|
||||
import logging
|
||||
import threading
|
||||
import uuid
|
||||
from abc import ABC, abstractmethod
|
||||
from collections.abc import AsyncIterable, Awaitable, Callable, Iterable, Sequence
|
||||
from contextlib import AbstractAsyncContextManager
|
||||
from typing import TYPE_CHECKING, Annotated, Any, ClassVar, Generic, TypeVar
|
||||
from typing import TYPE_CHECKING, Annotated, Any, ClassVar, Generic, Protocol, TypeVar, runtime_checkable
|
||||
|
||||
import yaml
|
||||
from pydantic import Field, model_validator
|
||||
|
||||
from semantic_kernel.agents.channels.agent_channel import AgentChannel
|
||||
from semantic_kernel.contents.chat_message_content import CMC_ITEM_TYPES, ChatMessageContent
|
||||
from semantic_kernel.contents.streaming_chat_message_content import StreamingChatMessageContent
|
||||
from semantic_kernel.contents.utils.author_role import AuthorRole
|
||||
from semantic_kernel.exceptions.agent_exceptions import AgentExecutionException
|
||||
from semantic_kernel.exceptions.agent_exceptions import AgentExecutionException, AgentInitializationException
|
||||
from semantic_kernel.functions import kernel_function
|
||||
from semantic_kernel.functions.kernel_arguments import KernelArguments
|
||||
from semantic_kernel.functions.kernel_function import TEMPLATE_FORMAT_MAP
|
||||
from semantic_kernel.functions.kernel_plugin import KernelPlugin
|
||||
from semantic_kernel.kernel import Kernel
|
||||
from semantic_kernel.kernel_pydantic import KernelBaseModel
|
||||
@@ -28,12 +32,65 @@ from semantic_kernel.utils.validation import AGENT_NAME_REGEX
|
||||
if TYPE_CHECKING:
|
||||
from mcp.server.lowlevel.server import LifespanResultT, Server
|
||||
|
||||
from semantic_kernel.kernel_pydantic import KernelBaseSettings
|
||||
|
||||
logger: logging.Logger = logging.getLogger(__name__)
|
||||
|
||||
_T = TypeVar("_T", bound="Agent")
|
||||
TMessage = TypeVar("TMessage", bound=ChatMessageContent)
|
||||
TThreadType = TypeVar("TThreadType", bound="AgentThread")
|
||||
|
||||
|
||||
# region Declarative Spec Definitions
|
||||
|
||||
|
||||
class ModelConnection(KernelBaseModel):
|
||||
"""Class representing a model connection."""
|
||||
|
||||
type: str | None = None
|
||||
service_id: str | None = None
|
||||
extras: dict[str, Any] = Field(default_factory=dict)
|
||||
|
||||
|
||||
class ModelSpec(KernelBaseModel):
|
||||
"""Class representing a model specification."""
|
||||
|
||||
id: str | None = None
|
||||
api: str = "chat"
|
||||
options: dict[str, Any] = Field(default_factory=dict)
|
||||
connection: ModelConnection | None = None
|
||||
|
||||
|
||||
class ToolSpec(KernelBaseModel):
|
||||
"""Class representing a tool specification."""
|
||||
|
||||
id: str | None = None
|
||||
type: str | None = None
|
||||
description: str | None = None
|
||||
options: dict[str, Any] = Field(default_factory=dict)
|
||||
extras: dict[str, Any] = Field(default_factory=dict)
|
||||
|
||||
|
||||
class AgentSpec(KernelBaseModel):
|
||||
"""Class representing an agent specification."""
|
||||
|
||||
type: str
|
||||
id: str | None = None
|
||||
name: str | None = None
|
||||
description: str | None = None
|
||||
instructions: str | None = None
|
||||
model: ModelSpec | None = None
|
||||
tools: list[ToolSpec] = Field(default_factory=list)
|
||||
template: dict[str, Any] | None = None
|
||||
extras: dict[str, Any] = Field(default_factory=dict)
|
||||
|
||||
|
||||
# endregion
|
||||
|
||||
|
||||
# region AgentThread
|
||||
|
||||
|
||||
class AgentThread(ABC):
|
||||
"""Base class for agent threads."""
|
||||
|
||||
@@ -109,6 +166,11 @@ class AgentThread(ABC):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
# endregion
|
||||
|
||||
# region AgentResponseItem
|
||||
|
||||
|
||||
class AgentResponseItem(KernelBaseModel, Generic[TMessage]):
|
||||
"""Class representing a response item from an agent.
|
||||
|
||||
@@ -158,6 +220,12 @@ class AgentResponseItem(KernelBaseModel, Generic[TMessage]):
|
||||
return hash((self.message, self.thread))
|
||||
|
||||
|
||||
# endregion
|
||||
|
||||
|
||||
# region Agent Base Class
|
||||
|
||||
|
||||
class Agent(KernelBaseModel, ABC):
|
||||
"""Base abstraction for all Semantic Kernel agents.
|
||||
|
||||
@@ -233,6 +301,8 @@ class Agent(KernelBaseModel, ABC):
|
||||
# it will fail validating the model.
|
||||
setattr(self, "_as_kernel_function", _as_kernel_function)
|
||||
|
||||
# region Invocation Methods
|
||||
|
||||
@abstractmethod
|
||||
def get_response(
|
||||
self,
|
||||
@@ -325,6 +395,10 @@ class Agent(KernelBaseModel, ABC):
|
||||
"""
|
||||
pass
|
||||
|
||||
# endregion
|
||||
|
||||
# region Channel Management
|
||||
|
||||
def get_channel_keys(self) -> Iterable[str]:
|
||||
"""Get the channel keys.
|
||||
|
||||
@@ -345,6 +419,10 @@ class Agent(KernelBaseModel, ABC):
|
||||
raise NotImplementedError("Unable to create channel. Channel type not configured.")
|
||||
return self.channel_type()
|
||||
|
||||
# endregion
|
||||
|
||||
# region Instructions Management
|
||||
|
||||
async def format_instructions(self, kernel: Kernel, arguments: KernelArguments | None = None) -> str | None:
|
||||
"""Format the instructions.
|
||||
|
||||
@@ -390,6 +468,10 @@ class Agent(KernelBaseModel, ABC):
|
||||
|
||||
return KernelArguments(settings=merged_execution_settings, **merged_params)
|
||||
|
||||
# endregion
|
||||
|
||||
# region Thread Management
|
||||
|
||||
async def _ensure_thread_exists_with_messages(
|
||||
self,
|
||||
*,
|
||||
@@ -432,6 +514,8 @@ class Agent(KernelBaseModel, ABC):
|
||||
"""Notify the thread of a new message."""
|
||||
await thread.on_new_message(new_message)
|
||||
|
||||
# endregion
|
||||
|
||||
def __eq__(self, other):
|
||||
"""Check if two agents are equal."""
|
||||
if isinstance(other, Agent):
|
||||
@@ -478,3 +562,450 @@ class Agent(KernelBaseModel, ABC):
|
||||
instructions=instructions,
|
||||
lifespan=lifespan,
|
||||
)
|
||||
|
||||
|
||||
# region Declarative Spec Handling
|
||||
|
||||
|
||||
@runtime_checkable
|
||||
class DeclarativeSpecProtocol(Protocol):
|
||||
"""Protocol for declarative spec mixin."""
|
||||
|
||||
@classmethod
|
||||
def resolve_placeholders(
|
||||
cls: type,
|
||||
yaml_str: str,
|
||||
settings: "KernelBaseSettings | None" = None,
|
||||
extras: dict[str, Any] | None = None,
|
||||
) -> str:
|
||||
"""Resolve placeholders in the YAML string."""
|
||||
...
|
||||
|
||||
@classmethod
|
||||
async def from_yaml(
|
||||
cls: type,
|
||||
yaml_str: str,
|
||||
*,
|
||||
kernel: Kernel | None = None,
|
||||
plugins: list[KernelPlugin | object] | dict[str, KernelPlugin | object] | None = None,
|
||||
settings: "KernelBaseSettings | None" = None,
|
||||
extras: dict[str, Any] | None = None,
|
||||
**kwargs,
|
||||
) -> "Agent":
|
||||
"""Create an agent instance from a YAML string."""
|
||||
...
|
||||
|
||||
@classmethod
|
||||
async def from_dict(
|
||||
cls: type,
|
||||
data: dict,
|
||||
*,
|
||||
kernel: Kernel | None = None,
|
||||
plugins: list[KernelPlugin | object] | dict[str, KernelPlugin | object] | None = None,
|
||||
settings: "KernelBaseSettings | None" = None,
|
||||
**kwargs,
|
||||
) -> "Agent":
|
||||
"""Create an agent from a dictionary."""
|
||||
...
|
||||
|
||||
|
||||
# region Agent Type Registry
|
||||
|
||||
|
||||
_TAgent = TypeVar("_TAgent", bound=Agent)
|
||||
|
||||
# Global agent type registry
|
||||
AGENT_TYPE_REGISTRY: dict[str, type[Agent]] = {}
|
||||
|
||||
|
||||
def register_agent_type(agent_type: str):
|
||||
"""Decorator to register an agent type with the registry.
|
||||
|
||||
Example usage:
|
||||
@register_agent_type("my_custom_agent")
|
||||
class MyCustomAgent(Agent):
|
||||
...
|
||||
"""
|
||||
|
||||
def decorator(cls: type[_TAgent]) -> type[_TAgent]:
|
||||
AGENT_TYPE_REGISTRY[agent_type.lower()] = cls
|
||||
return cls
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
_BUILTIN_AGENTS_LOADED = False
|
||||
_BUILTIN_AGENTS_LOCK = threading.Lock()
|
||||
|
||||
# List of import paths for all built-in agent modules
|
||||
# These modules must contain `@register_agent_type(...)` decorators.
|
||||
_BUILTIN_AGENT_MODULES = [
|
||||
"semantic_kernel.agents.chat_completion.chat_completion_agent",
|
||||
"semantic_kernel.agents.azure_ai.azure_ai_agent",
|
||||
]
|
||||
|
||||
|
||||
def _preload_builtin_agents() -> None:
|
||||
"""Make sure all built-in agent modules are imported at least once, so their decorators register agent types."""
|
||||
global _BUILTIN_AGENTS_LOADED
|
||||
|
||||
if _BUILTIN_AGENTS_LOADED:
|
||||
return
|
||||
|
||||
with _BUILTIN_AGENTS_LOCK:
|
||||
if _BUILTIN_AGENTS_LOADED:
|
||||
return # Double-checked locking
|
||||
|
||||
failed = []
|
||||
|
||||
for module_name in _BUILTIN_AGENT_MODULES:
|
||||
try:
|
||||
importlib.import_module(module_name)
|
||||
except Exception as ex:
|
||||
failed.append((module_name, ex))
|
||||
|
||||
if failed:
|
||||
error_msgs = "\n".join(f"- {mod}: {err}" for mod, err in failed)
|
||||
raise RuntimeError(f"Failed to preload the following built-in agent modules:\n{error_msgs}")
|
||||
|
||||
_BUILTIN_AGENTS_LOADED = True
|
||||
|
||||
|
||||
class AgentRegistry:
|
||||
"""Responsible for creating agents from YAML, dicts, or files."""
|
||||
|
||||
@staticmethod
|
||||
def register_type(agent_type: str, agent_cls: type[Agent]) -> None:
|
||||
"""Register a new agent type at runtime.
|
||||
|
||||
Args:
|
||||
agent_type: The string identifier representing the agent type (e.g., 'chat_completion_agent').
|
||||
agent_cls: The class implementing the agent, inheriting from `Agent`.
|
||||
|
||||
Example:
|
||||
AgentRegistry.register_type("my_custom_agent", MyCustomAgent)
|
||||
"""
|
||||
AGENT_TYPE_REGISTRY[agent_type.lower()] = agent_cls
|
||||
|
||||
@staticmethod
|
||||
async def create_from_yaml(
|
||||
yaml_str: str,
|
||||
*,
|
||||
kernel: Kernel | None = None,
|
||||
plugins: list[KernelPlugin | object] | dict[str, KernelPlugin | object] | None = None,
|
||||
settings: "KernelBaseSettings | None" = None,
|
||||
extras: dict[str, Any] | None = None,
|
||||
**kwargs,
|
||||
) -> _TAgent:
|
||||
"""Create a single agent instance from a YAML string.
|
||||
|
||||
Args:
|
||||
yaml_str: The YAML string defining the agent.
|
||||
kernel: The Kernel instance to use for tool resolution and agent initialization.
|
||||
plugins: The plugins to use for the agent.
|
||||
settings: The settings to use for the agent.
|
||||
extras: Additional parameters to resolve placeholders in the YAML.
|
||||
**kwargs: Additional parameters passed to the agent constructor if required.
|
||||
|
||||
Returns:
|
||||
An instance of the requested agent.
|
||||
|
||||
Raises:
|
||||
AgentInitializationException: If the YAML is invalid or the agent type is not supported.
|
||||
|
||||
Example:
|
||||
agent = await AgentRegistry.create_agent_from_yaml(
|
||||
yaml_str, kernel=kernel, service=AzureChatCompletion(),
|
||||
)
|
||||
"""
|
||||
_preload_builtin_agents()
|
||||
|
||||
data = yaml.safe_load(yaml_str)
|
||||
|
||||
agent_type = data.get("type", "").lower()
|
||||
if not agent_type:
|
||||
raise AgentInitializationException("Missing 'type' field in agent definition.")
|
||||
|
||||
if agent_type not in AGENT_TYPE_REGISTRY:
|
||||
raise AgentInitializationException(f"Agent type '{agent_type}' not registered.")
|
||||
|
||||
agent_cls = AGENT_TYPE_REGISTRY[agent_type]
|
||||
|
||||
if not isinstance(agent_cls, DeclarativeSpecProtocol):
|
||||
raise AgentInitializationException(
|
||||
f"Agent class '{agent_cls.__name__}' does not support declarative spec loading."
|
||||
)
|
||||
|
||||
yaml_str = agent_cls.resolve_placeholders(yaml_str, settings, extras)
|
||||
data = yaml.safe_load(yaml_str)
|
||||
|
||||
return await agent_cls.from_dict(
|
||||
data,
|
||||
kernel=kernel,
|
||||
plugins=plugins,
|
||||
settings=settings,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
async def create_from_dict(
|
||||
data: dict,
|
||||
*,
|
||||
kernel: Kernel | None = None,
|
||||
plugins: list[KernelPlugin | object] | dict[str, KernelPlugin | object] | None = None,
|
||||
settings: "KernelBaseSettings | None" = None,
|
||||
**kwargs,
|
||||
) -> _TAgent:
|
||||
"""Create a single agent instance from a dictionary.
|
||||
|
||||
Args:
|
||||
data: The dictionary defining the agent fields.
|
||||
kernel: The Kernel instance to use for tool resolution and agent initialization.
|
||||
plugins: The plugins to use for the agent.
|
||||
settings: The settings to use for the agent.
|
||||
**kwargs: Additional parameters passed to the agent constructor if required.
|
||||
|
||||
Returns:
|
||||
An instance of the requested agent.
|
||||
|
||||
Raises:
|
||||
AgentInitializationException: If the dictionary is missing a 'type' field or the agent type is unsupported.
|
||||
|
||||
Example:
|
||||
agent = await AgentRegistry.create_agent_from_dict(agent_data, kernel=kernel)
|
||||
"""
|
||||
_preload_builtin_agents()
|
||||
|
||||
agent_type = data.get("type", "").lower()
|
||||
|
||||
if not agent_type:
|
||||
raise AgentInitializationException("Missing 'type' field in agent definition.")
|
||||
|
||||
if agent_type not in AGENT_TYPE_REGISTRY:
|
||||
raise AgentInitializationException(f"Agent type '{agent_type}' is not supported.")
|
||||
|
||||
agent_cls = AGENT_TYPE_REGISTRY[agent_type]
|
||||
|
||||
if not isinstance(agent_cls, DeclarativeSpecProtocol):
|
||||
raise AgentInitializationException(
|
||||
f"Agent class '{agent_cls.__name__}' does not support declarative spec loading."
|
||||
)
|
||||
|
||||
return await agent_cls.from_dict(
|
||||
data,
|
||||
kernel=kernel,
|
||||
settings=settings,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
async def create_from_file(
|
||||
file_path: str,
|
||||
*,
|
||||
kernel: Kernel | None = None,
|
||||
plugins: list[KernelPlugin | object] | dict[str, KernelPlugin | object] | None = None,
|
||||
settings: "KernelBaseSettings | None" = None,
|
||||
extras: dict[str, Any] | None = None,
|
||||
encoding: str | None = None,
|
||||
**kwargs,
|
||||
) -> _TAgent:
|
||||
"""Create a single agent instance from a YAML file.
|
||||
|
||||
Args:
|
||||
file_path: Path to the YAML file defining the agent.
|
||||
kernel: The Kernel instance to use for tool resolution and agent initialization.
|
||||
plugins: The plugins to use for the agent.
|
||||
settings: The settings to use for the agent.
|
||||
extras: Additional parameters to resolve placeholders in the YAML.
|
||||
encoding: The encoding of the file (default is 'utf-8').
|
||||
**kwargs: Additional parameters passed to the agent constructor if required.
|
||||
|
||||
Returns:
|
||||
An instance of the requested agent.
|
||||
|
||||
Raises:
|
||||
AgentInitializationException: If the file is unreadable or the agent type is unsupported.
|
||||
"""
|
||||
_preload_builtin_agents()
|
||||
|
||||
try:
|
||||
if encoding is None:
|
||||
encoding = "utf-8"
|
||||
with open(file_path, encoding=encoding) as f:
|
||||
yaml_str = f.read()
|
||||
except Exception as e:
|
||||
raise AgentInitializationException(f"Failed to read agent spec file: {e}") from e
|
||||
|
||||
return await AgentRegistry.create_from_yaml(
|
||||
yaml_str,
|
||||
kernel=kernel,
|
||||
plugins=plugins,
|
||||
settings=settings,
|
||||
extras=extras,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
|
||||
# endregion
|
||||
|
||||
|
||||
# region DeclarativeSpecMixin
|
||||
|
||||
_D = TypeVar("_D", bound="DeclarativeSpecMixin")
|
||||
|
||||
|
||||
class DeclarativeSpecMixin(ABC):
|
||||
"""Mixin class for declarative agent methods."""
|
||||
|
||||
@classmethod
|
||||
async def from_yaml(
|
||||
cls: type[_D],
|
||||
yaml_str: str,
|
||||
*,
|
||||
kernel: Kernel | None = None,
|
||||
plugins: list[KernelPlugin | object] | dict[str, KernelPlugin | object] | None = None,
|
||||
prompt_template_config: PromptTemplateConfig | None = None,
|
||||
settings: "KernelBaseSettings | None" = None,
|
||||
extras: dict[str, Any] | None = None,
|
||||
**kwargs,
|
||||
) -> _D:
|
||||
"""Create an agent instance from a YAML string."""
|
||||
if settings:
|
||||
yaml_str = cls.resolve_placeholders(yaml_str, settings, extras=extras)
|
||||
|
||||
data = yaml.safe_load(yaml_str)
|
||||
return await cls.from_dict(
|
||||
data,
|
||||
kernel=kernel,
|
||||
plugins=plugins,
|
||||
prompt_template_config=prompt_template_config,
|
||||
settings=settings,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
async def from_dict(
|
||||
cls: type[_D],
|
||||
data: dict,
|
||||
*,
|
||||
kernel: Kernel | None = None,
|
||||
plugins: list[KernelPlugin | object] | dict[str, KernelPlugin | object] | None = None,
|
||||
prompt_template_config: PromptTemplateConfig | None = None,
|
||||
settings: "KernelBaseSettings | None" = None,
|
||||
**kwargs,
|
||||
) -> _D:
|
||||
"""Default implementation: call the protected _from_dict."""
|
||||
# Compose `data` and extracted common fields for the subclass
|
||||
extracted, kernel = cls._normalize_spec_fields(data, kernel=kernel, plugins=plugins, **kwargs)
|
||||
return await cls._from_dict(
|
||||
{**data, **extracted},
|
||||
kernel=kernel,
|
||||
prompt_template_config=prompt_template_config,
|
||||
settings=settings,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
@abstractmethod
|
||||
async def _from_dict(
|
||||
cls: type[_D],
|
||||
data: dict,
|
||||
*,
|
||||
kernel: Kernel,
|
||||
prompt_template_config: PromptTemplateConfig | None = None,
|
||||
**kwargs,
|
||||
) -> _D:
|
||||
"""Create an agent instance from a dictionary."""
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def resolve_placeholders(
|
||||
cls: type[_D],
|
||||
yaml_str: str,
|
||||
settings: "KernelBaseSettings | None" = None,
|
||||
extras: dict[str, Any] | None = None,
|
||||
) -> str:
|
||||
"""Resolve placeholders inside the YAML string using agent-specific settings.
|
||||
|
||||
Override in subclasses if necessary.
|
||||
"""
|
||||
return yaml_str
|
||||
|
||||
@classmethod
|
||||
def _normalize_spec_fields(
|
||||
cls: type[_D],
|
||||
data: dict,
|
||||
*,
|
||||
kernel: Kernel | None = None,
|
||||
plugins: list[KernelPlugin | object] | dict[str, KernelPlugin | object] | None = None,
|
||||
**kwargs,
|
||||
) -> tuple[dict[str, Any], Kernel]:
|
||||
"""Normalize the fields in the spec dictionary.
|
||||
|
||||
Returns:
|
||||
A tuple of:
|
||||
- Normalized constructor field dict
|
||||
- The effective Kernel instance (created or reused)
|
||||
"""
|
||||
if not kernel:
|
||||
kernel = Kernel()
|
||||
|
||||
# Plugins provided explicitly
|
||||
if plugins:
|
||||
for plugin in plugins:
|
||||
kernel.add_plugin(plugin)
|
||||
|
||||
# Validate tools declared in the spec exist in the kernel
|
||||
if "tools" in data:
|
||||
cls._validate_tools(data["tools"], kernel)
|
||||
|
||||
fields = {
|
||||
"name": data.get("name"),
|
||||
"description": data.get("description"),
|
||||
"instructions": data.get("instructions"),
|
||||
"arguments": KernelArguments(**(data.get("model", {}).get("options", {}))) if data.get("model") else None,
|
||||
}
|
||||
|
||||
# Handle prompt_template if available
|
||||
if "template" in data or "prompt_template" in data:
|
||||
template_data = data.get("prompt_template") or data.get("template")
|
||||
if isinstance(template_data, dict):
|
||||
prompt_template_config = PromptTemplateConfig(**template_data)
|
||||
fields["prompt_template"] = TEMPLATE_FORMAT_MAP[prompt_template_config.template_format](
|
||||
prompt_template_config=prompt_template_config
|
||||
)
|
||||
# Overwrite instructions from prompt template if explicitly provided
|
||||
if prompt_template_config.template is not None:
|
||||
fields["instructions"] = prompt_template_config.template
|
||||
|
||||
return fields, kernel
|
||||
|
||||
@classmethod
|
||||
def _validate_tools(cls: type[_D], tools_list: list[dict], kernel: Kernel) -> None:
|
||||
"""Validate tool references in the declarative spec against kernel's registered plugins.
|
||||
|
||||
This validates the declared tools in the YAML spec, and only checks whether those references resolve
|
||||
properly in the current kernel.
|
||||
"""
|
||||
if not kernel:
|
||||
raise AgentInitializationException("Kernel instance is required for tool resolution.")
|
||||
|
||||
for tool in tools_list:
|
||||
tool_id = tool.get("id")
|
||||
if not tool_id or tool.get("type") != "function":
|
||||
continue
|
||||
|
||||
if "." not in tool_id:
|
||||
raise AgentInitializationException(f"Tool id '{tool_id}' must be in format PluginName.FunctionName")
|
||||
|
||||
plugin_name, function_name = tool_id.split(".", 1)
|
||||
|
||||
plugin = kernel.plugins.get(plugin_name)
|
||||
if not plugin:
|
||||
raise AgentInitializationException(f"Plugin '{plugin_name}' not found in kernel.")
|
||||
|
||||
if function_name not in plugin.functions:
|
||||
raise AgentInitializationException(f"Function '{function_name}' not found in plugin '{plugin_name}'.")
|
||||
|
||||
|
||||
# endregion
|
||||
|
||||
@@ -16,6 +16,7 @@ from azure.ai.projects.models import (
|
||||
MessageTextUrlCitationAnnotation,
|
||||
RequiredFunctionToolCall,
|
||||
RunStep,
|
||||
RunStepAzureAISearchToolCall,
|
||||
RunStepBingGroundingToolCall,
|
||||
RunStepDeltaCodeInterpreterImageOutput,
|
||||
RunStepDeltaCodeInterpreterLogOutput,
|
||||
@@ -293,6 +294,31 @@ def generate_bing_grounding_content(
|
||||
return message_content
|
||||
|
||||
|
||||
@experimental
|
||||
def generate_azure_ai_search_content(
|
||||
agent_name: str, azure_ai_search_tool_call: "RunStepAzureAISearchToolCall"
|
||||
) -> ChatMessageContent:
|
||||
"""Generate function result content related to an Azure AI Search Tool."""
|
||||
message_content: ChatMessageContent = ChatMessageContent(role=AuthorRole.ASSISTANT, name=agent_name) # type: ignore
|
||||
# Azure AI Search tool call contains both tool call input and output
|
||||
message_content.items.append(
|
||||
FunctionCallContent(
|
||||
id=azure_ai_search_tool_call.id,
|
||||
name=azure_ai_search_tool_call.type,
|
||||
function_name=azure_ai_search_tool_call.type,
|
||||
arguments=azure_ai_search_tool_call.azure_ai_search.get("input"),
|
||||
)
|
||||
)
|
||||
message_content.items.append(
|
||||
FunctionResultContent(
|
||||
function_name=azure_ai_search_tool_call.type,
|
||||
id=azure_ai_search_tool_call.id,
|
||||
result=azure_ai_search_tool_call.azure_ai_search.get("output"),
|
||||
)
|
||||
)
|
||||
return message_content
|
||||
|
||||
|
||||
@experimental
|
||||
def generate_code_interpreter_content(agent_name: str, code: str) -> "ChatMessageContent":
|
||||
"""Generate code interpreter content.
|
||||
@@ -392,6 +418,45 @@ def generate_streaming_bing_grounding_content(
|
||||
) # type: ignore
|
||||
|
||||
|
||||
@experimental
|
||||
def generate_streaming_azure_ai_search_content(
|
||||
agent_name: str, step_details: "RunStepDeltaToolCallObject"
|
||||
) -> StreamingChatMessageContent | None:
|
||||
"""Generate function result content related to a Bing Grounding Tool."""
|
||||
if not step_details.tool_calls:
|
||||
return None
|
||||
|
||||
items: list[FunctionCallContent | FunctionResultContent] = []
|
||||
|
||||
for index, tool in enumerate(step_details.tool_calls):
|
||||
if tool.type == "azure_ai_search":
|
||||
azure_ai_search_tool = cast(RunStepAzureAISearchToolCall, tool)
|
||||
arguments = getattr(azure_ai_search_tool, "azure_ai_search", None)
|
||||
items.append(
|
||||
FunctionCallContent(
|
||||
id=azure_ai_search_tool.id,
|
||||
index=index,
|
||||
name=azure_ai_search_tool.type,
|
||||
function_name=azure_ai_search_tool.type,
|
||||
arguments=arguments,
|
||||
)
|
||||
)
|
||||
items.append(
|
||||
FunctionResultContent(
|
||||
function_name=azure_ai_search_tool.type,
|
||||
id=azure_ai_search_tool.id,
|
||||
result=azure_ai_search_tool.azure_ai_search.get("output"),
|
||||
)
|
||||
)
|
||||
|
||||
return StreamingChatMessageContent(
|
||||
role=AuthorRole.ASSISTANT,
|
||||
name=agent_name,
|
||||
choice_index=0,
|
||||
items=items, # type: ignore
|
||||
) # type: ignore
|
||||
|
||||
|
||||
@experimental
|
||||
def generate_streaming_code_interpreter_content(
|
||||
agent_name: str, step_details: "RunStepDeltaToolCallObject"
|
||||
|
||||
@@ -16,6 +16,7 @@ from azure.ai.projects.models import (
|
||||
OpenAIPageableListOfThreadMessage,
|
||||
ResponseFormatJsonSchemaType,
|
||||
RunStep,
|
||||
RunStepAzureAISearchToolCall,
|
||||
RunStepBingGroundingToolCall,
|
||||
RunStepCodeInterpreterToolCall,
|
||||
RunStepDeltaChunk,
|
||||
@@ -32,12 +33,14 @@ from azure.ai.projects.models import (
|
||||
from azure.ai.projects.models._enums import MessageRole
|
||||
|
||||
from semantic_kernel.agents.azure_ai.agent_content_generation import (
|
||||
generate_azure_ai_search_content,
|
||||
generate_bing_grounding_content,
|
||||
generate_code_interpreter_content,
|
||||
generate_function_call_content,
|
||||
generate_function_call_streaming_content,
|
||||
generate_function_result_content,
|
||||
generate_message_content,
|
||||
generate_streaming_azure_ai_search_content,
|
||||
generate_streaming_bing_grounding_content,
|
||||
generate_streaming_code_interpreter_content,
|
||||
generate_streaming_message_content,
|
||||
@@ -281,7 +284,7 @@ class AgentThreadActions:
|
||||
)
|
||||
case AgentsNamedToolChoiceType.BING_GROUNDING:
|
||||
logger.debug(
|
||||
f"Entering tool_calls (bing grounding) for run [{run.id}], agent "
|
||||
f"Entering tool_calls (bing_grounding) for run [{run.id}], agent "
|
||||
f" `{agent.name}` and thread `{thread_id}`"
|
||||
)
|
||||
bing_call: RunStepBingGroundingToolCall = cast(
|
||||
@@ -290,6 +293,17 @@ class AgentThreadActions:
|
||||
content = generate_bing_grounding_content(
|
||||
agent_name=agent.name, bing_tool_call=bing_call
|
||||
)
|
||||
case AgentsNamedToolChoiceType.AZURE_AI_SEARCH:
|
||||
logger.debug(
|
||||
f"Entering tool_calls (azure_ai_search) for run [{run.id}], agent "
|
||||
f" `{agent.name}` and thread `{thread_id}`"
|
||||
)
|
||||
azure_ai_search_call: RunStepAzureAISearchToolCall = cast(
|
||||
RunStepAzureAISearchToolCall, tool_call
|
||||
)
|
||||
content = generate_azure_ai_search_content(
|
||||
agent_name=agent.name, azure_ai_search_tool_call=azure_ai_search_call
|
||||
)
|
||||
|
||||
if content:
|
||||
message_count += 1
|
||||
@@ -487,6 +501,10 @@ class AgentThreadActions:
|
||||
content = generate_streaming_bing_grounding_content(
|
||||
agent_name=agent.name, step_details=details
|
||||
)
|
||||
case AgentsNamedToolChoiceType.AZURE_AI_SEARCH:
|
||||
content = generate_streaming_azure_ai_search_content(
|
||||
agent_name=agent.name, step_details=details
|
||||
)
|
||||
if content:
|
||||
if output_messages is not None:
|
||||
output_messages.append(content)
|
||||
@@ -791,13 +809,21 @@ class AgentThreadActions:
|
||||
tool["openapi"] = openapi_data
|
||||
return tool
|
||||
|
||||
@staticmethod
|
||||
def _deduplicate_tools(existing_tools: list[dict], new_tools: list[dict]) -> list[dict]:
|
||||
existing_names = {
|
||||
tool["function"]["name"] for tool in existing_tools if "function" in tool and "name" in tool["function"]
|
||||
}
|
||||
return [tool for tool in new_tools if tool.get("function", {}).get("name") not in existing_names]
|
||||
|
||||
@classmethod
|
||||
def _get_tools(cls: type[_T], agent: "AzureAIAgent", kernel: "Kernel") -> list[dict[str, Any] | ToolDefinition]:
|
||||
"""Get the tools for the agent."""
|
||||
tools: list[Any] = list(agent.definition.tools)
|
||||
funcs = kernel.get_full_list_of_function_metadata()
|
||||
dict_defs = [kernel_function_metadata_to_function_call_format(f) for f in funcs]
|
||||
tools.extend(dict_defs)
|
||||
deduped_defs = cls._deduplicate_tools(tools, dict_defs)
|
||||
tools.extend(deduped_defs)
|
||||
return [cls._prepare_tool_definition(tool) for tool in tools]
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -1,33 +1,48 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import inspect
|
||||
import json
|
||||
import logging
|
||||
import sys
|
||||
from collections.abc import AsyncIterable, Awaitable, Callable, Iterable
|
||||
from copy import deepcopy
|
||||
from typing import TYPE_CHECKING, Any, ClassVar, Literal, TypeVar
|
||||
|
||||
if sys.version_info >= (3, 12):
|
||||
from typing import override # pragma: no cover
|
||||
else:
|
||||
from typing_extensions import override # pragma: no cover
|
||||
|
||||
from azure.ai.projects.aio import AIProjectClient
|
||||
from azure.ai.projects.models import Agent as AzureAIAgentModel
|
||||
from azure.ai.projects.models import (
|
||||
AgentsApiResponseFormat,
|
||||
AgentsApiResponseFormatMode,
|
||||
AzureAISearchQueryType,
|
||||
AzureAISearchTool,
|
||||
BingGroundingTool,
|
||||
CodeInterpreterTool,
|
||||
FileSearchTool,
|
||||
OpenApiAnonymousAuthDetails,
|
||||
OpenApiTool,
|
||||
ResponseFormatJsonSchemaType,
|
||||
ThreadMessageOptions,
|
||||
ToolDefinition,
|
||||
ToolResources,
|
||||
TruncationObject,
|
||||
)
|
||||
from pydantic import Field
|
||||
from pydantic import Field, SecretStr
|
||||
|
||||
from semantic_kernel.agents.agent import Agent, AgentResponseItem, AgentThread
|
||||
from semantic_kernel.agents import (
|
||||
Agent,
|
||||
AgentResponseItem,
|
||||
AgentSpec,
|
||||
AgentThread,
|
||||
AzureAIAgentSettings,
|
||||
DeclarativeSpecMixin,
|
||||
ToolSpec,
|
||||
register_agent_type,
|
||||
)
|
||||
from semantic_kernel.agents.azure_ai.agent_thread_actions import AgentThreadActions
|
||||
from semantic_kernel.agents.azure_ai.azure_ai_agent_settings import AzureAIAgentSettings
|
||||
from semantic_kernel.agents.azure_ai.azure_ai_channel import AzureAIChannel
|
||||
from semantic_kernel.agents.channels.agent_channel import AgentChannel
|
||||
from semantic_kernel.agents.open_ai.run_polling_options import RunPollingOptions
|
||||
from semantic_kernel.connectors.ai.function_calling_utils import kernel_function_metadata_to_function_call_format
|
||||
from semantic_kernel.contents.chat_message_content import ChatMessageContent
|
||||
from semantic_kernel.contents.utils.author_role import AuthorRole
|
||||
from semantic_kernel.exceptions.agent_exceptions import (
|
||||
@@ -48,13 +63,19 @@ from semantic_kernel.utils.telemetry.agent_diagnostics.decorators import (
|
||||
)
|
||||
from semantic_kernel.utils.telemetry.user_agent import APP_INFO, SEMANTIC_KERNEL_USER_AGENT
|
||||
|
||||
logger: logging.Logger = logging.getLogger(__name__)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from azure.ai.projects.models import ToolResources
|
||||
from azure.identity.aio import DefaultAzureCredential
|
||||
|
||||
from semantic_kernel.contents.streaming_chat_message_content import StreamingChatMessageContent
|
||||
from semantic_kernel.kernel_pydantic import KernelBaseSettings
|
||||
|
||||
if sys.version_info >= (3, 12):
|
||||
from typing import override # pragma: no cover
|
||||
else:
|
||||
from typing_extensions import override # pragma: no cover
|
||||
|
||||
logger: logging.Logger = logging.getLogger(__name__)
|
||||
|
||||
AgentsApiResponseFormatOption = (
|
||||
str | AgentsApiResponseFormatMode | AgentsApiResponseFormat | ResponseFormatJsonSchemaType
|
||||
@@ -63,6 +84,170 @@ AgentsApiResponseFormatOption = (
|
||||
_T = TypeVar("_T", bound="AzureAIAgent")
|
||||
|
||||
|
||||
# region Declarative Spec
|
||||
|
||||
_TOOL_BUILDERS: dict[str, Callable[[ToolSpec, Kernel | None], ToolDefinition]] = {}
|
||||
|
||||
|
||||
def _register_tool(tool_type: str):
|
||||
def decorator(fn: Callable[[ToolSpec, Kernel | None], ToolDefinition]):
|
||||
_TOOL_BUILDERS[tool_type.lower()] = fn
|
||||
return fn
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
@_register_tool("azure_ai_search")
|
||||
def _azure_ai_search(spec: ToolSpec) -> AzureAISearchTool:
|
||||
opts = spec.options or {}
|
||||
|
||||
connections = opts.get("tool_connections")
|
||||
if not connections or not isinstance(connections, list) or not connections[0]:
|
||||
raise AgentInitializationException(f"Missing or malformed 'tool_connections' in: {spec}")
|
||||
conn_id = connections[0]
|
||||
|
||||
index_name = opts.get("index_name")
|
||||
if not index_name or not isinstance(index_name, str):
|
||||
raise AgentInitializationException(f"Missing or malformed 'index_name' in: {spec}")
|
||||
|
||||
raw_query_type = opts.get("query_type", AzureAISearchQueryType.SIMPLE)
|
||||
if type(raw_query_type) is str:
|
||||
try:
|
||||
query_type = AzureAISearchQueryType(raw_query_type.lower())
|
||||
except ValueError:
|
||||
raise AgentInitializationException(f"Invalid query_type '{raw_query_type}' in: {spec}")
|
||||
else:
|
||||
query_type = raw_query_type
|
||||
|
||||
filter_expr = opts.get("filter", "")
|
||||
|
||||
top_k = opts.get("top_k", 5)
|
||||
if not isinstance(top_k, int):
|
||||
raise AgentInitializationException(f"'top_k' must be an integer in: {spec}")
|
||||
|
||||
return AzureAISearchTool(
|
||||
index_connection_id=conn_id,
|
||||
index_name=index_name,
|
||||
query_type=query_type,
|
||||
filter=filter_expr,
|
||||
top_k=top_k,
|
||||
)
|
||||
|
||||
|
||||
@_register_tool("azure_function")
|
||||
def _azure_function(spec: ToolSpec) -> ToolDefinition:
|
||||
# TODO(evmattso): Implement Azure Function tool support
|
||||
raise NotImplementedError("Azure Function tools are not yet supported with the Azure AI Agent Declarative Spec.")
|
||||
|
||||
|
||||
@_register_tool("bing_grounding")
|
||||
def _bing_grounding(spec: ToolSpec) -> BingGroundingTool:
|
||||
connections = spec.options.get("tool_connections")
|
||||
if not connections or not isinstance(connections, list) or not connections[0]:
|
||||
raise AgentInitializationException(f"Missing or malformed 'tool_connections' in: {spec}")
|
||||
|
||||
conn_id = connections[0]
|
||||
return BingGroundingTool(connection_id=conn_id)
|
||||
|
||||
|
||||
@_register_tool("code_interpreter")
|
||||
def _code_interpreter(spec: ToolSpec) -> CodeInterpreterTool:
|
||||
file_ids = spec.options.get("file_ids")
|
||||
return CodeInterpreterTool(file_ids=file_ids) if file_ids else CodeInterpreterTool()
|
||||
|
||||
|
||||
@_register_tool("file_search")
|
||||
def _file_search(spec: ToolSpec) -> FileSearchTool:
|
||||
vector_store_ids = spec.options.get("vector_store_ids")
|
||||
if not vector_store_ids or not isinstance(vector_store_ids, list) or not vector_store_ids[0]:
|
||||
raise AgentInitializationException(f"Missing or malformed 'vector_store_ids' in: {spec}")
|
||||
return FileSearchTool(vector_store_ids=vector_store_ids)
|
||||
|
||||
|
||||
@_register_tool("function")
|
||||
def _function(spec: ToolSpec, kernel: "Kernel") -> ToolDefinition:
|
||||
def parse_fqn(fqn: str) -> tuple[str, str]:
|
||||
parts = fqn.split(".")
|
||||
if len(parts) != 2:
|
||||
raise AgentInitializationException(f"Function `{fqn}` must be in the form `pluginName.functionName`.")
|
||||
return parts[0], parts[1]
|
||||
|
||||
if not spec.id:
|
||||
raise AgentInitializationException("Function ID is required for function tools.")
|
||||
plugin_name, function_name = parse_fqn(spec.id)
|
||||
funcs = kernel.get_list_of_function_metadata_filters({"included_functions": f"{plugin_name}-{function_name}"})
|
||||
|
||||
match len(funcs):
|
||||
case 0:
|
||||
raise AgentInitializationException(f"Function `{spec.id}` not found in kernel.")
|
||||
case 1:
|
||||
return kernel_function_metadata_to_function_call_format(funcs[0]) # type: ignore[return-value]
|
||||
case _:
|
||||
raise AgentInitializationException(f"Multiple definitions found for `{spec.id}`. Please remove duplicates.")
|
||||
|
||||
|
||||
@_register_tool("openapi")
|
||||
def _openapi(spec: ToolSpec) -> OpenApiTool:
|
||||
opts = spec.options or {}
|
||||
|
||||
if not spec.id:
|
||||
raise AgentInitializationException("OpenAPI tool requires a non-empty 'id' (used as name).")
|
||||
if not spec.description:
|
||||
raise AgentInitializationException(f"OpenAPI tool '{spec.id}' requires a 'description'.")
|
||||
|
||||
raw_spec = opts.get("specification")
|
||||
if not raw_spec:
|
||||
raise AgentInitializationException(f"OpenAPI tool '{spec.id}' is missing required 'specification' field.")
|
||||
|
||||
try:
|
||||
parsed_spec = json.loads(raw_spec) if isinstance(raw_spec, str) else raw_spec
|
||||
except json.JSONDecodeError as e:
|
||||
raise AgentInitializationException(f"Invalid JSON in OpenAPI 'specification' field: {e}") from e
|
||||
|
||||
auth = opts.get("auth", OpenApiAnonymousAuthDetails())
|
||||
|
||||
return OpenApiTool(
|
||||
name=spec.id,
|
||||
description=spec.description,
|
||||
spec=parsed_spec,
|
||||
auth=auth,
|
||||
default_parameters=opts.get("default_parameters"),
|
||||
)
|
||||
|
||||
|
||||
def _build_tool(spec: ToolSpec, kernel: "Kernel") -> ToolDefinition:
|
||||
if not spec.type:
|
||||
raise AgentInitializationException("Tool spec must include a 'type' field.")
|
||||
|
||||
try:
|
||||
builder = _TOOL_BUILDERS[spec.type.lower()]
|
||||
except KeyError as exc:
|
||||
raise AgentInitializationException(f"Unsupported tool type: {spec.type}") from exc
|
||||
|
||||
sig = inspect.signature(builder)
|
||||
return builder(spec) if len(sig.parameters) == 1 else builder(spec, kernel) # type: ignore[call-arg]
|
||||
|
||||
|
||||
def _build_tool_resources(tool_defs: list[ToolDefinition]) -> ToolResources | None:
|
||||
"""Collects tool resources from known tool types with resource needs."""
|
||||
resources: dict[str, Any] = {}
|
||||
|
||||
for tool in tool_defs:
|
||||
if isinstance(tool, CodeInterpreterTool):
|
||||
resources["code_interpreter"] = tool.resources.code_interpreter
|
||||
elif isinstance(tool, AzureAISearchTool):
|
||||
resources["azure_ai_search"] = tool.resources.azure_ai_search
|
||||
elif isinstance(tool, FileSearchTool):
|
||||
resources["file_search"] = tool.resources.file_search
|
||||
|
||||
return ToolResources(**resources) if resources else None
|
||||
|
||||
|
||||
# endregion
|
||||
|
||||
# region Thread
|
||||
|
||||
|
||||
@experimental
|
||||
class AzureAIAgentThread(AgentThread):
|
||||
"""Azure AI Agent Thread class."""
|
||||
@@ -156,7 +341,8 @@ class AzureAIAgentThread(AgentThread):
|
||||
|
||||
|
||||
@experimental
|
||||
class AzureAIAgent(Agent):
|
||||
@register_agent_type("foundry_agent")
|
||||
class AzureAIAgent(DeclarativeSpecMixin, Agent):
|
||||
"""Azure AI Agent class."""
|
||||
|
||||
client: AIProjectClient
|
||||
@@ -264,6 +450,162 @@ class AzureAIAgent(Agent):
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
# region Declarative Spec
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
async def _from_dict(
|
||||
cls: type[_T],
|
||||
data: dict,
|
||||
*,
|
||||
kernel: Kernel,
|
||||
prompt_template_config: PromptTemplateConfig | None = None,
|
||||
**kwargs,
|
||||
) -> _T:
|
||||
"""Create an Azure AI Agent from the provided dictionary.
|
||||
|
||||
Args:
|
||||
data: The dictionary containing the agent data.
|
||||
kernel: The kernel to use for the agent.
|
||||
prompt_template_config: The prompt template configuration.
|
||||
kwargs: Additional keyword arguments. Note: unsupported keys may raise validation errors.
|
||||
|
||||
Returns:
|
||||
AzureAIAgent: The Azure AI Agent instance.
|
||||
"""
|
||||
client: AIProjectClient = kwargs.pop("client", None)
|
||||
if client is None:
|
||||
raise AgentInitializationException("Missing required 'client' in AzureAIAgent._from_dict()")
|
||||
|
||||
spec = AgentSpec.model_validate(data)
|
||||
|
||||
if "settings" in kwargs:
|
||||
kwargs.pop("settings")
|
||||
|
||||
if spec.id:
|
||||
existing_definition = await client.agents.get_agent(spec.id)
|
||||
|
||||
# Create a mutable clone
|
||||
definition = deepcopy(existing_definition)
|
||||
|
||||
# Selectively override attributes from spec
|
||||
if spec.name is not None:
|
||||
setattr(definition, "name", spec.name)
|
||||
if spec.description is not None:
|
||||
setattr(definition, "description", spec.description)
|
||||
if spec.instructions is not None:
|
||||
setattr(definition, "instructions", spec.instructions)
|
||||
if spec.extras:
|
||||
merged_metadata = dict(getattr(definition, "metadata", {}) or {})
|
||||
merged_metadata.update(spec.extras)
|
||||
setattr(definition, "metadata", merged_metadata)
|
||||
|
||||
return cls(
|
||||
definition=definition,
|
||||
client=client,
|
||||
kernel=kernel,
|
||||
prompt_template_config=prompt_template_config,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
if not (spec.model and spec.model.id):
|
||||
raise ValueError("model.id required when creating a new Azure AI agent")
|
||||
|
||||
# Build tool definitions & resources
|
||||
tool_objs = [_build_tool(t, kernel) for t in spec.tools if t.type != "function"]
|
||||
tool_defs = [d for tool in tool_objs for d in (tool.definitions if hasattr(tool, "definitions") else [tool])]
|
||||
tool_resources = _build_tool_resources(tool_objs)
|
||||
|
||||
try:
|
||||
agent_definition = await client.agents.create_agent(
|
||||
model=spec.model.id,
|
||||
name=spec.name,
|
||||
description=spec.description,
|
||||
instructions=spec.instructions,
|
||||
tools=tool_defs,
|
||||
tool_resources=tool_resources,
|
||||
metadata=spec.extras,
|
||||
**kwargs,
|
||||
)
|
||||
except Exception as ex:
|
||||
print(f"Error creating agent: {ex}")
|
||||
|
||||
return cls(
|
||||
definition=agent_definition,
|
||||
client=client,
|
||||
kernel=kernel,
|
||||
prompt_template_config=prompt_template_config,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _get_setting(cls: type[_T], value: Any) -> Any:
|
||||
"""Return raw value if `SecretStr`, otherwise pass through."""
|
||||
if isinstance(value, SecretStr):
|
||||
return value.get_secret_value()
|
||||
return value
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def resolve_placeholders(
|
||||
cls: type[_T],
|
||||
yaml_str: str,
|
||||
settings: "KernelBaseSettings | None" = None,
|
||||
extras: dict[str, Any] | None = None,
|
||||
) -> str:
|
||||
"""Substitute ${AzureAI:Key} placeholders with fields from AzureAIAgentSettings and extras."""
|
||||
import re
|
||||
|
||||
pattern = re.compile(r"\$\{([^}]+)\}")
|
||||
|
||||
# Build the mapping only if settings is provided and valid
|
||||
field_mapping: dict[str, Any] = {}
|
||||
|
||||
if settings is not None:
|
||||
if not isinstance(settings, AzureAIAgentSettings):
|
||||
raise AgentInitializationException(f"Expected AzureAIAgentSettings, got {type(settings).__name__}")
|
||||
|
||||
field_mapping.update({
|
||||
"ChatModelId": cls._get_setting(getattr(settings, "model_deployment_name", None)),
|
||||
"ConnectionString": cls._get_setting(getattr(settings, "project_connection_string", None)),
|
||||
"AgentId": cls._get_setting(getattr(settings, "agent_id", None)),
|
||||
"Endpoint": cls._get_setting(getattr(settings, "endpoint", None)),
|
||||
"SubscriptionId": cls._get_setting(getattr(settings, "subscription_id", None)),
|
||||
"ResourceGroup": cls._get_setting(getattr(settings, "resource_group_name", None)),
|
||||
"ProjectName": cls._get_setting(getattr(settings, "project_name", None)),
|
||||
"BingConnectionId": cls._get_setting(getattr(settings, "bing_connection_id", None)),
|
||||
"AzureAISearchConnectionId": cls._get_setting(getattr(settings, "azure_ai_search_connection_id", None)),
|
||||
"AzureAISearchIndexName": cls._get_setting(getattr(settings, "azure_ai_search_index_name", None)),
|
||||
})
|
||||
|
||||
if extras:
|
||||
field_mapping.update(extras)
|
||||
|
||||
def replacer(match: re.Match[str]) -> str:
|
||||
"""Replace the matched placeholder with the corresponding value from field_mapping."""
|
||||
full_key = match.group(1) # for example, AzureAI:AzureAISearchConnectionId
|
||||
section, _, key = full_key.partition(":")
|
||||
if section != "AzureAI":
|
||||
return match.group(0)
|
||||
|
||||
# Try short key first (AzureAISearchConnectionId), then full (AzureAI:AzureAISearchConnectionId)
|
||||
return str(field_mapping.get(key) or field_mapping.get(full_key) or match.group(0))
|
||||
|
||||
result = pattern.sub(replacer, yaml_str)
|
||||
|
||||
# Safety check for unresolved placeholders
|
||||
unresolved = pattern.findall(result)
|
||||
if unresolved:
|
||||
raise AgentInitializationException(
|
||||
f"Unresolved placeholders in spec: {', '.join(f'${{{key}}}' for key in unresolved)}"
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
# endregion
|
||||
|
||||
# region Invocation Methods
|
||||
|
||||
@trace_agent_get_response
|
||||
@override
|
||||
async def get_response(
|
||||
|
||||
@@ -20,6 +20,10 @@ class AzureAIAgentSettings(KernelBaseSettings):
|
||||
subscription_id: Azure AI Agent Subscription ID (Env var AZURE_AI_AGENT_SUBSCRIPTION_ID)
|
||||
resource_group_name: Azure AI Agent Resource Group Name (Env var AZURE_AI_AGENT_RESOURCE_GROUP_NAME)
|
||||
project_name: Azure AI Agent Project Name (Env var AZURE_AI_AGENT_PROJECT_NAME)
|
||||
bing_connection_id: Azure AI Agent Bing Connection ID (Env var AZURE_AI_AGENT_BING_CONNECTION_ID)
|
||||
azure_ai_search_connection_id: Azure AI Agent Azure AI Search Connection ID
|
||||
(Env var AZURE_AI_AGENT_AZURE_AI_SEARCH_CONNECTION_ID)
|
||||
azure_ai_search_index_name: Azure AI Agent Azure AI Search Index Name
|
||||
"""
|
||||
|
||||
env_prefix: ClassVar[str] = "AZURE_AI_AGENT_"
|
||||
@@ -30,3 +34,6 @@ class AzureAIAgentSettings(KernelBaseSettings):
|
||||
subscription_id: str | None = None
|
||||
resource_group_name: str | None = None
|
||||
project_name: str | None = None
|
||||
bing_connection_id: str | None = None
|
||||
azure_ai_search_connection_id: str | None = None
|
||||
azure_ai_search_index_name: str | None = None
|
||||
|
||||
@@ -15,7 +15,7 @@ if sys.version_info >= (3, 12):
|
||||
else:
|
||||
from typing_extensions import override # pragma: no cover
|
||||
|
||||
from semantic_kernel.agents.agent import AgentResponseItem, AgentThread
|
||||
from semantic_kernel.agents import AgentResponseItem, AgentThread
|
||||
from semantic_kernel.agents.bedrock.action_group_utils import (
|
||||
parse_function_result_contents,
|
||||
parse_return_control_payload,
|
||||
|
||||
@@ -6,15 +6,9 @@ import uuid
|
||||
from collections.abc import AsyncGenerator, AsyncIterable, Awaitable, Callable
|
||||
from typing import TYPE_CHECKING, Any, ClassVar
|
||||
|
||||
if sys.version_info >= (3, 12):
|
||||
from typing import override # pragma: no cover
|
||||
else:
|
||||
from typing_extensions import override # pragma: no cover
|
||||
|
||||
from pydantic import Field, model_validator
|
||||
|
||||
from semantic_kernel.agents import Agent
|
||||
from semantic_kernel.agents.agent import AgentResponseItem, AgentThread
|
||||
from semantic_kernel.agents import Agent, AgentResponseItem, AgentThread, DeclarativeSpecMixin, register_agent_type
|
||||
from semantic_kernel.agents.channels.agent_channel import AgentChannel
|
||||
from semantic_kernel.agents.channels.chat_history_channel import ChatHistoryChannel
|
||||
from semantic_kernel.connectors.ai.chat_completion_client_base import ChatCompletionClientBase
|
||||
@@ -45,6 +39,11 @@ from semantic_kernel.utils.telemetry.agent_diagnostics.decorators import (
|
||||
if TYPE_CHECKING:
|
||||
from semantic_kernel.kernel import Kernel
|
||||
|
||||
if sys.version_info >= (3, 12):
|
||||
from typing import override # pragma: no cover
|
||||
else:
|
||||
from typing_extensions import override # pragma: no cover
|
||||
|
||||
logger: logging.Logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -113,7 +112,8 @@ class ChatHistoryAgentThread(AgentThread):
|
||||
return await self._chat_history.reduce()
|
||||
|
||||
|
||||
class ChatCompletionAgent(Agent):
|
||||
@register_agent_type("chat_completion_agent")
|
||||
class ChatCompletionAgent(DeclarativeSpecMixin, Agent):
|
||||
"""A Chat Completion Agent based on ChatCompletionClientBase."""
|
||||
|
||||
function_choice_behavior: FunctionChoiceBehavior | None = Field(
|
||||
@@ -234,6 +234,33 @@ class ChatCompletionAgent(Agent):
|
||||
|
||||
return ChatHistoryChannel(messages=messages, thread=thread)
|
||||
|
||||
# region Declarative Spec
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
async def _from_dict(
|
||||
cls,
|
||||
data: dict,
|
||||
*,
|
||||
kernel: "Kernel | None" = None,
|
||||
plugins: list[KernelPlugin | object] | dict[str, KernelPlugin | object] | None = None,
|
||||
**kwargs,
|
||||
) -> "ChatCompletionAgent":
|
||||
# Returns the normalized spec fields and a kernel configured with plugins, if present.
|
||||
fields, kernel = cls._normalize_spec_fields(data, kernel=kernel, plugins=plugins, **kwargs)
|
||||
|
||||
if "service" in kwargs:
|
||||
fields["service"] = kwargs["service"]
|
||||
|
||||
if "function_choice_behavior" in kwargs:
|
||||
fields["function_choice_behavior"] = kwargs["function_choice_behavior"]
|
||||
|
||||
return cls(**fields, kernel=kernel)
|
||||
|
||||
# endregion
|
||||
|
||||
# region Invocation Methods
|
||||
|
||||
@trace_agent_get_response
|
||||
@override
|
||||
async def get_response(
|
||||
@@ -456,6 +483,10 @@ class ChatCompletionAgent(Agent):
|
||||
)
|
||||
)
|
||||
|
||||
# endregion
|
||||
|
||||
# region Helper Methods
|
||||
|
||||
async def _inner_invoke(
|
||||
self,
|
||||
thread: ChatHistoryAgentThread,
|
||||
|
||||
@@ -7,7 +7,7 @@ from typing import Any
|
||||
from openai import AsyncAzureOpenAI
|
||||
from pydantic import ValidationError
|
||||
|
||||
from semantic_kernel.agents.open_ai.open_ai_assistant_agent import OpenAIAssistantAgent
|
||||
from semantic_kernel.agents import OpenAIAssistantAgent
|
||||
from semantic_kernel.connectors.ai.open_ai.settings.azure_open_ai_settings import AzureOpenAISettings
|
||||
from semantic_kernel.exceptions.agent_exceptions import AgentInitializationException
|
||||
from semantic_kernel.utils.authentication.entra_id_authentication import get_entra_auth_token
|
||||
|
||||
@@ -8,7 +8,7 @@ from typing import Any
|
||||
from openai import AsyncAzureOpenAI
|
||||
from pydantic import ValidationError
|
||||
|
||||
from semantic_kernel.agents.open_ai.openai_responses_agent import OpenAIResponsesAgent
|
||||
from semantic_kernel.agents import OpenAIResponsesAgent
|
||||
from semantic_kernel.connectors.ai.open_ai.settings.azure_open_ai_settings import AzureOpenAISettings
|
||||
from semantic_kernel.exceptions.agent_exceptions import (
|
||||
AgentInitializationException,
|
||||
|
||||
@@ -6,8 +6,6 @@ from collections.abc import AsyncIterable, Awaitable, Callable, Iterable
|
||||
from copy import copy
|
||||
from typing import TYPE_CHECKING, Any, ClassVar, Literal
|
||||
|
||||
from semantic_kernel.utils.feature_stage_decorator import release_candidate
|
||||
|
||||
if sys.version_info >= (3, 12):
|
||||
from typing import override # pragma: no cover
|
||||
else:
|
||||
@@ -45,6 +43,7 @@ from semantic_kernel.functions import KernelArguments
|
||||
from semantic_kernel.functions.kernel_function import TEMPLATE_FORMAT_MAP
|
||||
from semantic_kernel.functions.kernel_plugin import KernelPlugin
|
||||
from semantic_kernel.schema.kernel_json_schema_builder import KernelJsonSchemaBuilder
|
||||
from semantic_kernel.utils.feature_stage_decorator import release_candidate
|
||||
from semantic_kernel.utils.naming import generate_random_ascii_name
|
||||
from semantic_kernel.utils.telemetry.agent_diagnostics.decorators import (
|
||||
trace_agent_get_response,
|
||||
|
||||
@@ -20,10 +20,8 @@ from openai.types.shared_params.compound_filter import CompoundFilter
|
||||
from openai.types.shared_params.response_format_json_object import ResponseFormatJSONObject
|
||||
from pydantic import BaseModel, Field, ValidationError
|
||||
|
||||
from semantic_kernel.agents import Agent
|
||||
from semantic_kernel.agents.agent import AgentResponseItem, AgentThread
|
||||
from semantic_kernel.agents import Agent, AgentResponseItem, AgentThread, RunPollingOptions
|
||||
from semantic_kernel.agents.open_ai.responses_agent_thread_actions import ResponsesAgentThreadActions
|
||||
from semantic_kernel.agents.open_ai.run_polling_options import RunPollingOptions
|
||||
from semantic_kernel.connectors.ai.function_choice_behavior import FunctionChoiceBehavior
|
||||
from semantic_kernel.connectors.ai.open_ai.settings.open_ai_settings import OpenAISettings
|
||||
from semantic_kernel.contents.chat_history import ChatHistory
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
type: chat_completion_agent
|
||||
name: FunctionCallingAgent
|
||||
description: This agent uses the provided functions to answer questions about the menu.
|
||||
instructions: Use the provided functions to answer questions about the menu.
|
||||
model:
|
||||
id: "gpt-4.1"
|
||||
options:
|
||||
temperature: 0.4
|
||||
@@ -12,6 +12,7 @@ import pandas as pd
|
||||
from pydantic import BaseModel
|
||||
from pytest import fixture
|
||||
|
||||
from semantic_kernel.agents import Agent, DeclarativeSpecMixin, register_agent_type
|
||||
from semantic_kernel.connectors.ai.open_ai import OpenAIEmbeddingPromptExecutionSettings
|
||||
from semantic_kernel.data.record_definition import (
|
||||
VectorStoreRecordDataField,
|
||||
@@ -455,3 +456,39 @@ def data_model_type_with_key_as_key_field(
|
||||
key: Annotated[str, VectorStoreRecordKeyField()]
|
||||
|
||||
return DataModelClass
|
||||
|
||||
|
||||
# region Declarative Spec
|
||||
|
||||
|
||||
@register_agent_type("test_agent")
|
||||
class TestAgent(DeclarativeSpecMixin, Agent):
|
||||
@classmethod
|
||||
def resolve_placeholders(cls, yaml_str, settings=None, extras=None):
|
||||
return yaml_str
|
||||
|
||||
@classmethod
|
||||
async def _from_dict(cls, data, **kwargs):
|
||||
return cls(
|
||||
name=data.get("name"),
|
||||
description=data.get("description"),
|
||||
instructions=data.get("instructions"),
|
||||
kernel=data.get("kernel"),
|
||||
)
|
||||
|
||||
async def get_response(self, messages, instructions_override=None):
|
||||
return "test response"
|
||||
|
||||
async def invoke(self, messages, **kwargs):
|
||||
return "invoke result"
|
||||
|
||||
async def invoke_stream(self, messages, **kwargs):
|
||||
yield "stream result"
|
||||
|
||||
|
||||
@fixture(scope="session")
|
||||
def test_agent_cls():
|
||||
return TestAgent
|
||||
|
||||
|
||||
# endregion
|
||||
|
||||
@@ -48,19 +48,19 @@ from samples.concepts.prompt_templates.template_language import main as template
|
||||
from samples.concepts.rag.rag_with_text_memory_plugin import main as rag_with_text_memory_plugin
|
||||
from samples.concepts.service_selector.custom_service_selector import main as custom_service_selector
|
||||
from samples.concepts.text_completion.text_completion import main as text_completion
|
||||
from samples.getting_started_with_agents.chat_completion.step1_chat_completion_agent_simple import (
|
||||
from samples.getting_started_with_agents.chat_completion.step01_chat_completion_agent_simple import (
|
||||
main as step1_chat_completion_agent_simple,
|
||||
)
|
||||
from samples.getting_started_with_agents.chat_completion.step3_chat_completion_agent_with_kernel import (
|
||||
from samples.getting_started_with_agents.chat_completion.step03_chat_completion_agent_with_kernel import (
|
||||
main as step2_chat_completion_agent_with_kernel,
|
||||
)
|
||||
from samples.getting_started_with_agents.chat_completion.step4_chat_completion_agent_plugin_simple import (
|
||||
from samples.getting_started_with_agents.chat_completion.step04_chat_completion_agent_plugin_simple import (
|
||||
main as step3_chat_completion_agent_plugin_simple,
|
||||
)
|
||||
from samples.getting_started_with_agents.chat_completion.step5_chat_completion_agent_plugin_with_kernel import (
|
||||
from samples.getting_started_with_agents.chat_completion.step05_chat_completion_agent_plugin_with_kernel import (
|
||||
main as step4_chat_completion_agent_plugin_with_kernel,
|
||||
)
|
||||
from samples.getting_started_with_agents.chat_completion.step6_chat_completion_agent_group_chat import (
|
||||
from samples.getting_started_with_agents.chat_completion.step06_chat_completion_agent_group_chat import (
|
||||
main as step5_chat_completion_agent_group_chat,
|
||||
)
|
||||
from samples.getting_started_with_agents.openai_assistant.step1_assistant import main as step1_openai_assistant
|
||||
|
||||
@@ -7,6 +7,10 @@ from unittest.mock import AsyncMock
|
||||
|
||||
import pytest
|
||||
|
||||
from semantic_kernel.agents.agent import AGENT_TYPE_REGISTRY, AgentRegistry, DeclarativeSpecMixin, register_agent_type
|
||||
from semantic_kernel.exceptions.agent_exceptions import AgentInitializationException
|
||||
from semantic_kernel.kernel import Kernel
|
||||
|
||||
if sys.version_info >= (3, 12):
|
||||
from typing import override # pragma: no cover
|
||||
else:
|
||||
@@ -171,3 +175,245 @@ def test_merge_arguments_both_not_none():
|
||||
|
||||
assert merged["param1"] == "baseVal", "Should retain base param from agent"
|
||||
assert merged["param2"] == "override_param", "Should include param from override"
|
||||
|
||||
|
||||
# region Declarative Spec tests
|
||||
|
||||
|
||||
class DummyPlugin:
|
||||
def __init__(self):
|
||||
self.functions = {"dummy_function": lambda: "result"}
|
||||
|
||||
def get(self, name):
|
||||
return self.functions.get(name)
|
||||
|
||||
|
||||
class DummyAgentSettings:
|
||||
azure_ai_search_connection_id = "test-conn-id"
|
||||
azure_ai_search_index_name = "test-index"
|
||||
|
||||
|
||||
class DummyKernel:
|
||||
def __init__(self):
|
||||
self.plugins = {}
|
||||
|
||||
def add_plugin(self, plugin):
|
||||
name = plugin.__class__.__name__
|
||||
self.plugins[name] = plugin
|
||||
|
||||
|
||||
async def test_resolve_placeholders_with_short_and_long_keys():
|
||||
class DummyDeclarativeSpec:
|
||||
@classmethod
|
||||
def resolve_placeholders(cls, yaml_str, settings=None, extras=None):
|
||||
import re
|
||||
|
||||
pattern = re.compile(r"\$\{([^}]+)\}")
|
||||
field_mapping = {
|
||||
"AzureAISearchConnectionId": "conn-123",
|
||||
"AzureAI:AzureAISearchConnectionId": "conn-123-override",
|
||||
}
|
||||
if extras:
|
||||
field_mapping.update(extras)
|
||||
|
||||
def replacer(match):
|
||||
full_key = match.group(1)
|
||||
section, _, key = full_key.partition(":")
|
||||
if section != "AzureAI":
|
||||
return match.group(0)
|
||||
return str(field_mapping.get(key) or field_mapping.get(full_key) or match.group(0))
|
||||
|
||||
return pattern.sub(replacer, yaml_str)
|
||||
|
||||
spec = "connection: ${AzureAI:AzureAISearchConnectionId}"
|
||||
resolved = DummyDeclarativeSpec.resolve_placeholders(spec)
|
||||
assert resolved == "connection: conn-123"
|
||||
|
||||
|
||||
async def test_validate_tools_succeeds_with_valid_plugin():
|
||||
class DummyDeclarativeSpec:
|
||||
@classmethod
|
||||
def _validate_tools(cls, tools_list, kernel):
|
||||
for tool in tools_list:
|
||||
tool_id = tool.get("id")
|
||||
if not tool_id or tool.get("type") != "function":
|
||||
continue
|
||||
plugin_name, function_name = tool_id.split(".")
|
||||
plugin = kernel.plugins.get(plugin_name)
|
||||
if not plugin:
|
||||
raise ValueError(f"Plugin {plugin_name} missing")
|
||||
if function_name not in plugin.functions:
|
||||
raise ValueError(f"Function {function_name} missing in plugin {plugin_name}")
|
||||
|
||||
kernel = DummyKernel()
|
||||
plugin = DummyPlugin()
|
||||
kernel.add_plugin(plugin)
|
||||
|
||||
DummyDeclarativeSpec._validate_tools([{"id": "DummyPlugin.dummy_function", "type": "function"}], kernel)
|
||||
|
||||
|
||||
async def test_validate_tools_raises_on_missing_plugin():
|
||||
class DummyDeclarativeSpec:
|
||||
@classmethod
|
||||
def _validate_tools(cls, tools_list, kernel):
|
||||
for tool in tools_list:
|
||||
tool_id = tool.get("id")
|
||||
if not tool_id or tool.get("type") != "function":
|
||||
continue
|
||||
plugin_name, function_name = tool_id.split(".")
|
||||
plugin = kernel.plugins.get(plugin_name)
|
||||
if not plugin:
|
||||
raise ValueError(f"Plugin {plugin_name} missing")
|
||||
if function_name not in plugin.functions:
|
||||
raise ValueError(f"Function {function_name} missing in plugin {plugin_name}")
|
||||
|
||||
kernel = DummyKernel()
|
||||
|
||||
with pytest.raises(ValueError, match="Plugin DummyPlugin missing"):
|
||||
DummyDeclarativeSpec._validate_tools([{"id": "DummyPlugin.dummy_function", "type": "function"}], kernel)
|
||||
|
||||
|
||||
def test_normalize_spec_fields_creates_kernel_and_extracts_fields():
|
||||
data = {
|
||||
"name": "TestAgent",
|
||||
"description": "An agent",
|
||||
"instructions": "Use this.",
|
||||
"model": {"options": {"temperature": 0.7}},
|
||||
}
|
||||
|
||||
fields, kernel = DeclarativeSpecMixin._normalize_spec_fields(data)
|
||||
assert isinstance(kernel, Kernel)
|
||||
assert fields["name"] == "TestAgent"
|
||||
assert isinstance(fields["arguments"], KernelArguments)
|
||||
|
||||
|
||||
def test_normalize_spec_fields_adds_plugins_to_kernel():
|
||||
plugin = DummyPlugin()
|
||||
data = {"name": "PluginAgent"}
|
||||
|
||||
_, kernel = DeclarativeSpecMixin._normalize_spec_fields(data, plugins=[plugin])
|
||||
assert "DummyPlugin" in kernel.plugins
|
||||
|
||||
|
||||
def test_normalize_spec_fields_parses_prompt_template_and_overwrites_instructions():
|
||||
data = {"name": "T", "prompt_template": {"template": "new instructions", "template_format": "semantic-kernel"}}
|
||||
|
||||
fields, _ = DeclarativeSpecMixin._normalize_spec_fields(data)
|
||||
assert fields["instructions"] == "new instructions"
|
||||
|
||||
|
||||
def test_validate_tools_success(custom_plugin_class):
|
||||
kernel = Kernel()
|
||||
kernel.add_plugin(custom_plugin_class())
|
||||
tools_list = [{"id": "CustomPlugin.getLightStatus", "type": "function"}]
|
||||
|
||||
DeclarativeSpecMixin._validate_tools(tools_list, kernel)
|
||||
|
||||
|
||||
def test_validate_tools_fails_on_invalid_format():
|
||||
kernel = Kernel()
|
||||
with pytest.raises(AgentInitializationException, match="format"):
|
||||
DeclarativeSpecMixin._validate_tools([{"id": "badformat", "type": "function"}], kernel)
|
||||
|
||||
|
||||
def test_validate_tools_fails_on_missing_plugin():
|
||||
kernel = Kernel()
|
||||
with pytest.raises(AgentInitializationException, match="not found in kernel"):
|
||||
DeclarativeSpecMixin._validate_tools([{"id": "MissingPlugin.foo", "type": "function"}], kernel)
|
||||
|
||||
|
||||
def test_validate_tools_fails_on_missing_function():
|
||||
plugin = DummyPlugin()
|
||||
kernel = Kernel()
|
||||
kernel.add_plugin(plugin)
|
||||
with pytest.raises(AgentInitializationException, match="not found in plugin"):
|
||||
DeclarativeSpecMixin._validate_tools([{"id": "DummyPlugin.bar", "type": "function"}], kernel)
|
||||
|
||||
|
||||
@register_agent_type("test_agent")
|
||||
class TestAgent(DeclarativeSpecMixin, Agent):
|
||||
@classmethod
|
||||
def resolve_placeholders(cls, yaml_str, settings=None, extras=None):
|
||||
return yaml_str
|
||||
|
||||
@classmethod
|
||||
async def _from_dict(cls, data, kernel, **kwargs):
|
||||
return cls(
|
||||
name=data.get("name"),
|
||||
description=data.get("description"),
|
||||
instructions=data.get("instructions"),
|
||||
kernel=kernel,
|
||||
)
|
||||
|
||||
async def get_response(self, messages, instructions_override=None):
|
||||
return "Test response"
|
||||
|
||||
async def invoke(self, messages, **kwargs):
|
||||
return "invoke result"
|
||||
|
||||
async def invoke_stream(self, messages, **kwargs):
|
||||
yield "streamed result"
|
||||
|
||||
|
||||
async def test_register_type_and_create_from_yaml_success():
|
||||
yaml_str = """
|
||||
type: test_agent
|
||||
name: TestAgent
|
||||
"""
|
||||
agent = await AgentRegistry.create_from_yaml(yaml_str)
|
||||
assert agent.__class__.__name__ == "TestAgent"
|
||||
|
||||
|
||||
async def test_create_from_yaml_missing_type():
|
||||
yaml_str = """
|
||||
name: InvalidAgent
|
||||
"""
|
||||
with pytest.raises(AgentInitializationException, match="Missing 'type'"):
|
||||
await AgentRegistry.create_from_yaml(yaml_str)
|
||||
|
||||
|
||||
async def test_create_from_yaml_unregistered_type():
|
||||
yaml_str = """
|
||||
type: nonexistent_agent
|
||||
"""
|
||||
# Ensure unregistered
|
||||
AGENT_TYPE_REGISTRY.pop("nonexistent_agent", None)
|
||||
with pytest.raises(AgentInitializationException, match="not registered"):
|
||||
await AgentRegistry.create_from_yaml(yaml_str)
|
||||
|
||||
|
||||
async def test_create_from_dict_success(test_agent_cls):
|
||||
data = {"type": "test_agent", "name": "FromDictAgent"}
|
||||
agent: TestAgent = await AgentRegistry.create_from_dict(data)
|
||||
assert agent.name == "FromDictAgent"
|
||||
assert type(agent).__name__ == "TestAgent"
|
||||
|
||||
|
||||
async def test_create_from_dict_missing_type():
|
||||
data = {"name": "NoType"}
|
||||
with pytest.raises(AgentInitializationException, match="Missing 'type'"):
|
||||
await AgentRegistry.create_from_dict(data)
|
||||
|
||||
|
||||
async def test_create_from_dict_type_not_supported():
|
||||
AGENT_TYPE_REGISTRY.pop("unknown", None)
|
||||
data = {"type": "unknown"}
|
||||
with pytest.raises(AgentInitializationException, match="not supported"):
|
||||
await AgentRegistry.create_from_dict(data)
|
||||
|
||||
|
||||
async def test_create_from_file_reads_and_creates(tmp_path, test_agent_cls):
|
||||
file_path = tmp_path / "spec.yaml"
|
||||
file_path.write_text("type: test_agent\nname: FileAgent\n", encoding="utf-8")
|
||||
|
||||
agent: TestAgent = await AgentRegistry.create_from_file(str(file_path))
|
||||
assert agent.name == "FileAgent"
|
||||
assert type(agent).__name__ == "TestAgent"
|
||||
|
||||
|
||||
async def test_create_from_file_raises_on_bad_path():
|
||||
with pytest.raises(AgentInitializationException, match="Failed to read agent spec file"):
|
||||
await AgentRegistry.create_from_file("/nonexistent/path/spec.yaml")
|
||||
|
||||
|
||||
# endregion
|
||||
|
||||
Generated
+1
-1
@@ -5646,7 +5646,7 @@ requires-dist = [
|
||||
{ name = "azure-cosmos", marker = "extra == 'azure'", specifier = "~=4.7" },
|
||||
{ name = "azure-identity", specifier = ">=1.13" },
|
||||
{ name = "azure-search-documents", marker = "extra == 'azure'", specifier = ">=11.6.0b4" },
|
||||
{ name = "boto3", marker = "extra == 'aws'", specifier = ">=1.36.4,<1.38.0" },
|
||||
{ name = "boto3", marker = "extra == 'aws'", specifier = ">=1.36.4,<1.39.0" },
|
||||
{ name = "chromadb", marker = "extra == 'chroma'", specifier = ">=0.5,<1.1" },
|
||||
{ name = "cloudevents", specifier = "~=1.0" },
|
||||
{ name = "dapr", marker = "extra == 'dapr'", specifier = ">=1.14.0" },
|
||||
|
||||
Reference in New Issue
Block a user