Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,942 changes: 1,942 additions & 0 deletions dotnet-install.sh

Large diffs are not rendered by default.

22 changes: 22 additions & 0 deletions src/ChatApp.AppHost/BicepTemplates/openAi_bingSearch.module.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@ param keyVaultName string

param aiProjectName string = take('aiProject-${uniqueString(resourceGroup().id)}', 64)

param searchServiceName string = ''

// Reference to existing search service if provided, otherwise create a new one
resource searchService 'Microsoft.Search/searchServices@2023-11-01' existing = if(!empty(searchServiceName)) {
name: searchServiceName
}

resource bingSearchService 'Microsoft.Bing/accounts@2020-06-10' = {
name: 'bing-grounding-${uniqueString(resourceGroup().id)}'
location: 'global'
Expand Down Expand Up @@ -145,6 +152,21 @@ resource aiHub 'Microsoft.MachineLearningServices/workspaces@2024-10-01' = {
}
}
}

resource azureSearchConnection 'connections@2024-01-01-preview' = {
name: 'azureAISearch'
properties: {
category: 'AzureSearch'
target: 'https://${searchService.name}.search.windows.net'
authType: 'AAD'
isSharedToAll: true
metadata: {
ApiType: 'Azure'
ResourceId: searchService.id
}
}
}
}
}

//for constructing project connection string
Expand Down
16 changes: 11 additions & 5 deletions src/ChatApp.AppHost/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,8 @@

var vectorStoreCollectionName = Environment.GetEnvironmentVariable("VectorStoreCollectionName") ?? "products";

var agentModelBingDeployment = builder.AddBicepTemplate("aoiabing", "./BicepTemplates/openAi_bingSearch.module.bicep")
.WithParameter(AzureBicepResource.KnownParameters.KeyVaultName)
.WithParameter(AzureBicepResource.KnownParameters.PrincipalId)
.WithParameter(AzureBicepResource.KnownParameters.PrincipalType);

var exisitingVectorSearch = !builder.Configuration.GetSection("ConnectionStrings")["vectorSearch"].IsNullOrEmpty();

var vectorSearch = !builder.ExecutionContext.IsPublishMode && exisitingVectorSearch
? builder.AddConnectionString("vectorSearch")
: builder.AddAzureSearch("vectorSearch")
Expand All @@ -31,12 +27,22 @@
};
});

// Simply provide an empty string as searchServiceName when not in publish mode
string searchServiceName = "";

var agentModelBingDeployment = builder.AddBicepTemplate("aoiabing", "./BicepTemplates/openAi_bingSearch.module.bicep")
.WithParameter(AzureBicepResource.KnownParameters.KeyVaultName)
.WithParameter(AzureBicepResource.KnownParameters.PrincipalId)
.WithParameter(AzureBicepResource.KnownParameters.PrincipalType)
.WithParameter("searchServiceName", searchServiceName);

var backend = builder.AddProject<Projects.ChatApp_WebApi>("backend")
.WithReference(vectorSearch)
.WithEnvironment("AzureDeployment", azureDeployment)
.WithEnvironment("EmbeddingModelDeployment", embeddingModelDeployment)
.WithEnvironment("AzureEndpoint", azureEndpoint)
.WithEnvironment("VectorStoreCollectionName", vectorStoreCollectionName)
.WithEnvironment("UseAzureAIAgents", builder.Configuration["UseAzureAIAgents"] ?? "false")
.WithEnvironment("ConnectionStrings__openAi", agentModelBingDeployment.GetOutput("connectionString"))
.WithEnvironment("ModelDeployment", agentModelBingDeployment.GetOutput("modelDeployment"))
.WithEnvironment("AIProjectConnectionString", agentModelBingDeployment.GetOutput("aiProjectConnectionString"))
Expand Down
35 changes: 35 additions & 0 deletions src/ChatApp.WebApi/Agents/AzureAISearchToolDefinition.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using Microsoft.SemanticKernel.Agents.AzureAI;
using Azure.AI.Projects;
using System.Text.Json.Serialization;

namespace ChatApp.WebApi.Agents;

/// <summary>
/// Tool definition for Azure AI Search capability
/// </summary>
public class AzureAISearchToolDefinition : ToolDefinition
{
/// <summary>
/// Gets the type of the tool.
/// </summary>
[JsonPropertyName("type")]
public string Type { get; } = "azure_ai_search";

/// <summary>
/// Creates a new instance of the <see cref="AzureAISearchToolDefinition"/> class.
/// </summary>
/// <param name="connectionList">Tool connection list</param>
public AzureAISearchToolDefinition(ToolConnectionList connectionList)
{
this.ConnectionList = connectionList;
}

/// <summary>
/// Gets or sets the connection list.
/// </summary>
[JsonPropertyName("connectionList")]
public ToolConnectionList ConnectionList { get; set; }
}
189 changes: 141 additions & 48 deletions src/ChatApp.WebApi/Agents/CreativeWriterApp.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,13 @@ public class CreativeWriterApp
private AgentsClient _agentsClient;
private Kernel defaultKernel;
private IConfiguration configuration;
private bool _useAzureAIAgents;

public CreativeWriterApp(Kernel defaultKernel, IConfiguration configuration)
{
this.defaultKernel = defaultKernel;
this.configuration = configuration;
_useAzureAIAgents = configuration.GetValue<bool>("UseAzureAIAgents");
var clientOptions = new AIProjectClientOptions();
_aIProjectClient = new AIProjectClient(configuration.GetValue<string>("AIProjectConnectionString")!, new DefaultAzureCredential(new DefaultAzureCredentialOptions { ExcludeVisualStudioCredential = true }), clientOptions);
_agentsClient = _aIProjectClient.GetAgentsClient();
Expand All @@ -54,61 +56,102 @@ internal async Task<CreativeWriterSession> CreateSessionAsync()
_vectorSearchKernel = defaultKernel.Clone();
await ConfigureVectorSearchKernel(_vectorSearchKernel);

var bingConnection = await _aIProjectClient.GetConnectionsClient().GetConnectionAsync("bingGrounding");
var connectionId = bingConnection.Value.Id;
// Get Azure AI Search tool connection if available
var azureAISearchToolConnection = await TryGetAzureAISearchConnectionAsync();

ToolConnectionList connectionList = new ToolConnectionList
// Create agent instances based on configuration
if (_useAzureAIAgents)
{
ConnectionList = { new ToolConnection(connectionId) }
};
BingGroundingToolDefinition bingGroundingTool = new BingGroundingToolDefinition(connectionList);
var researcherTemplate = ReadFileForPromptTemplateConfig("./Agents/Prompts/researcher.yaml");

// for the ease of the demo, we are creating an Agent in Azure AI Agent Service for every session and deleting it after the session finished
// for production, you can want to create an agent once and reuse them
var rAgent = await _agentsClient.CreateAgentAsync(
model: configuration.GetValue<string>("ModelDeployment")!,
name: researcherTemplate.Name,
description: researcherTemplate.Description,
instructions: researcherTemplate.Template,
tools: new List<ToolDefinition> { bingGroundingTool }
);

AzureAIAgent researcherAgent = new(rAgent,
_agentsClient,
templateFactory: new KernelPromptTemplateFactory(),
templateFormat: PromptTemplateConfig.SemanticKernelTemplateFormat)
// Use Azure AI Agents for all roles
var researcherAgent = await CreateAzureAIAgentAsync(
ResearcherName,
"./Agents/Prompts/researcher.yaml",
new List<ToolDefinition> { new BingGroundingToolDefinition(await GetBingConnectionListAsync()) }
);

var marketingAgent = await CreateAzureAIAgentAsync(
MarketingName,
"./Agents/Prompts/marketing.yaml",
azureAISearchToolConnection != null
? new List<ToolDefinition> {
new AzureAISearchToolDefinition(new ToolConnectionList { ConnectionList = { azureAISearchToolConnection } })
}
: new List<ToolDefinition>()
);

var writerAgent = await CreateAzureAIAgentAsync(
WriterName,
"./Agents/Prompts/writer.yaml",
new List<ToolDefinition>()
);

var editorAgent = await CreateAzureAIAgentAsync(
EditorName,
"./Agents/Prompts/editor.yaml",
new List<ToolDefinition>()
);

return new CreativeWriterSession(defaultKernel, _agentsClient, researcherAgent, marketingAgent, writerAgent, editorAgent);
}
else
{
Name = ResearcherName,
Kernel = defaultKernel,
Arguments = CreateFunctionChoiceAutoBehavior(),
LoggerFactory = defaultKernel.LoggerFactory,
};
// Default implementation - only Researcher as Azure AI Agent, others as ChatCompletionAgent
var bingConnection = await _aIProjectClient.GetConnectionsClient().GetConnectionAsync("bingGrounding");
var connectionId = bingConnection.Value.Id;

ChatCompletionAgent marketingAgent = new(ReadFileForPromptTemplateConfig("./Agents/Prompts/marketing.yaml"), templateFactory: new KernelPromptTemplateFactory())
{
Name = MarketingName,
Kernel = _vectorSearchKernel,
Arguments = CreateFunctionChoiceAutoBehavior(),
LoggerFactory = _vectorSearchKernel.LoggerFactory
};
ToolConnectionList connectionList = new ToolConnectionList
{
ConnectionList = { new ToolConnection(connectionId) }
};
BingGroundingToolDefinition bingGroundingTool = new BingGroundingToolDefinition(connectionList);
var researcherTemplate = ReadFileForPromptTemplateConfig("./Agents/Prompts/researcher.yaml");

// for the ease of the demo, we are creating an Agent in Azure AI Agent Service for every session and deleting it after the session finished
// for production, you can want to create an agent once and reuse them
var rAgent = await _agentsClient.CreateAgentAsync(
model: configuration.GetValue<string>("ModelDeployment")!,
name: researcherTemplate.Name,
description: researcherTemplate.Description,
instructions: researcherTemplate.Template,
tools: new List<ToolDefinition> { bingGroundingTool }
);

AzureAIAgent researcherAgent = new(rAgent,
_agentsClient,
templateFactory: new KernelPromptTemplateFactory(),
templateFormat: PromptTemplateConfig.SemanticKernelTemplateFormat)
{
Name = ResearcherName,
Kernel = defaultKernel,
Arguments = CreateFunctionChoiceAutoBehavior(),
LoggerFactory = defaultKernel.LoggerFactory,
};

ChatCompletionAgent writerAgent = new(ReadFileForPromptTemplateConfig("./Agents/Prompts/writer.yaml"), templateFactory: new KernelPromptTemplateFactory())
{
Name = WriterName,
Kernel = defaultKernel,
Arguments = [],
LoggerFactory = defaultKernel.LoggerFactory
};
ChatCompletionAgent marketingAgent = new(ReadFileForPromptTemplateConfig("./Agents/Prompts/marketing.yaml"), templateFactory: new KernelPromptTemplateFactory())
{
Name = MarketingName,
Kernel = _vectorSearchKernel,
Arguments = CreateFunctionChoiceAutoBehavior(),
LoggerFactory = _vectorSearchKernel.LoggerFactory
};

ChatCompletionAgent editorAgent = new(ReadFileForPromptTemplateConfig("./Agents/Prompts/editor.yaml"), templateFactory: new KernelPromptTemplateFactory())
{
Name = EditorName,
Kernel = defaultKernel,
LoggerFactory = defaultKernel.LoggerFactory
};
ChatCompletionAgent writerAgent = new(ReadFileForPromptTemplateConfig("./Agents/Prompts/writer.yaml"), templateFactory: new KernelPromptTemplateFactory())
{
Name = WriterName,
Kernel = defaultKernel,
Arguments = [],
LoggerFactory = defaultKernel.LoggerFactory
};

ChatCompletionAgent editorAgent = new(ReadFileForPromptTemplateConfig("./Agents/Prompts/editor.yaml"), templateFactory: new KernelPromptTemplateFactory())
{
Name = EditorName,
Kernel = defaultKernel,
LoggerFactory = defaultKernel.LoggerFactory
};

return new CreativeWriterSession(defaultKernel, _agentsClient, researcherAgent, marketingAgent, writerAgent, editorAgent);
return new CreativeWriterSession(defaultKernel, _agentsClient, researcherAgent, marketingAgent, writerAgent, editorAgent);
}
}

private async Task ConfigureVectorSearchKernel(Kernel vectorSearchKernel)
Expand Down Expand Up @@ -159,4 +202,54 @@ private static KernelArguments CreateFunctionChoiceAutoBehavior()
{
return new KernelArguments(new AzureOpenAIPromptExecutionSettings() { FunctionChoiceBehavior = FunctionChoiceBehavior.Required() });
}

private async Task<ToolConnectionList> GetBingConnectionListAsync()
{
var bingConnection = await _aIProjectClient.GetConnectionsClient().GetConnectionAsync("bingGrounding");
var connectionId = bingConnection.Value.Id;

return new ToolConnectionList
{
ConnectionList = { new ToolConnection(connectionId) }
};
}

private async Task<ToolConnection?> TryGetAzureAISearchConnectionAsync()
{
try
{
var connectionsClient = _aIProjectClient.GetConnectionsClient();
var searchConnection = await connectionsClient.GetConnectionAsync("azureAISearch");
return new ToolConnection(searchConnection.Value.Id);
}
catch
{
// If connection doesn't exist, return null
return null;
}
}

private async Task<AzureAIAgent> CreateAzureAIAgentAsync(string name, string promptPath, List<ToolDefinition> tools)
{
var templateConfig = ReadFileForPromptTemplateConfig(promptPath);

var agent = await _agentsClient.CreateAgentAsync(
model: configuration.GetValue<string>("ModelDeployment")!,
name: templateConfig.Name,
description: templateConfig.Description,
instructions: templateConfig.Template,
tools: tools
);

return new AzureAIAgent(agent,
_agentsClient,
templateFactory: new KernelPromptTemplateFactory(),
templateFormat: PromptTemplateConfig.SemanticKernelTemplateFormat)
{
Name = name,
Kernel = name == MarketingName ? _vectorSearchKernel! : defaultKernel,
Arguments = name == WriterName ? new KernelArguments() : CreateFunctionChoiceAutoBehavior(),
LoggerFactory = defaultKernel.LoggerFactory,
};
}
}
Loading