Skip to content

Commit

Permalink
.Net Agents - Support Prompt Template (#8631)
Browse files Browse the repository at this point in the history
### Motivation and Context
<!-- 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.
-->

Support ability to paramaterize agent instructions (à la
`IKernelTemplate`).

This also includes the ability to initialize an agent using a _yaml_
based prompt-template.

### Description

**Core:**
- Update `ChatCompletionAgent` and `OpenAIAssistantAgent` to support
initialization via `PromptTemplateConfig`.
- Also allow both agent types to support _semantic-kernel_ template
format for string based `Instructions`
- Formalize templating contracts on `KernelAgent` (base class)
- Added _GettingStartedWithAgents_ samples using _yaml_ template:
_Step01_ & _Step08_
- Added templating samples under `Concepts/Agents` to explore full range
of templating patterns

**Structural:**
- Split off `OpenAIAssistantCapabilities` from
`OpenAIAssistantDefinition` to clarify creation via
`PromptTemplateConfig`
- Re-ordered method parameters for `OpenAIAssistant.CreateAsync` and
`RetrieveAsync` to rationalize functional grouping and optionality.
- Externalized internal `AssistantCreationOptionsFactory` (from private
`OpenAIAssistant` method) for clarity and ease of testing
- Persisting _template-format_ as part of assistant metadata for
retrieval case (in the event of any ambiguity)

**Additionally:**
- Updated/added comments where appropriate
- Updated sample conventions (argument labels, explicit types)
- Updated all call sites for creating `OpenAIAssistantAgent` (due to
parameter ordering)
- Added test coverage where able


### 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 😄

---------

Co-authored-by: Evan Mattson <35585003+moonbox3@users.noreply.github.com>
Co-authored-by: Dmytro Struk <13853051+dmytrostruk@users.noreply.github.com>
  • Loading branch information
3 people authored Sep 23, 2024
1 parent 8924bdc commit a5570b1
Show file tree
Hide file tree
Showing 37 changed files with 1,949 additions and 883 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ namespace Agents;

/// <summary>
/// Demonstrate service selection for <see cref="ChatCompletionAgent"/> through setting service-id
/// on <see cref="ChatHistoryKernelAgent.Arguments"/> and also providing override <see cref="KernelArguments"/>
/// on <see cref="KernelAgent.Arguments"/> and also providing override <see cref="KernelArguments"/>
/// when calling <see cref="ChatCompletionAgent.InvokeAsync"/>
/// </summary>
public class ChatCompletion_ServiceSelection(ITestOutputHelper output) : BaseTest(output)
Expand Down
132 changes: 132 additions & 0 deletions dotnet/samples/Concepts/Agents/ChatCompletion_Templating.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.ChatCompletion;
using Microsoft.SemanticKernel.PromptTemplates.Handlebars;
using Microsoft.SemanticKernel.PromptTemplates.Liquid;

namespace Agents;

/// <summary>
/// Demonstrate parameterized template instruction for <see cref="ChatCompletionAgent"/>.
/// </summary>
public class ChatCompletion_Templating(ITestOutputHelper output) : BaseAgentsTest(output)
{
private readonly static (string Input, string? Style)[] s_inputs =
[
(Input: "Home cooking is great.", Style: null),
(Input: "Talk about world peace.", Style: "iambic pentameter"),
(Input: "Say something about doing your best.", Style: "e. e. cummings"),
(Input: "What do you think about having fun?", Style: "old school rap")
];

[Fact]
public async Task InvokeAgentWithInstructionsTemplateAsync()
{
// Instruction based template always processed by KernelPromptTemplateFactory
ChatCompletionAgent agent =
new()
{
Kernel = this.CreateKernelWithChatCompletion(),
Instructions =
"""
Write a one verse poem on the requested topic in the style of {{$style}}.
Always state the requested style of the poem.
""",
Arguments = new KernelArguments()
{
{"style", "haiku"}
}
};

await InvokeChatCompletionAgentWithTemplateAsync(agent);
}

[Fact]
public async Task InvokeAgentWithKernelTemplateAsync()
{
// Default factory is KernelPromptTemplateFactory
await InvokeChatCompletionAgentWithTemplateAsync(
"""
Write a one verse poem on the requested topic in the style of {{$style}}.
Always state the requested style of the poem.
""");
}

[Fact]
public async Task InvokeAgentWithHandlebarsTemplateAsync()
{
await InvokeChatCompletionAgentWithTemplateAsync(
"""
Write a one verse poem on the requested topic in the style of {{style}}.
Always state the requested style of the poem.
""",
HandlebarsPromptTemplateFactory.HandlebarsTemplateFormat,
new HandlebarsPromptTemplateFactory());
}

[Fact]
public async Task InvokeAgentWithLiquidTemplateAsync()
{
await InvokeChatCompletionAgentWithTemplateAsync(
"""
Write a one verse poem on the requested topic in the style of {{style}}.
Always state the requested style of the poem.
""",
LiquidPromptTemplateFactory.LiquidTemplateFormat,
new LiquidPromptTemplateFactory());
}

private async Task InvokeChatCompletionAgentWithTemplateAsync(
string instructionTemplate,
string? templateFormat = null,
IPromptTemplateFactory? templateFactory = null)
{
// Define the agent
PromptTemplateConfig templateConfig =
new()
{
Template = instructionTemplate,
TemplateFormat = templateFormat,
};
ChatCompletionAgent agent =
new(templateConfig, templateFactory)
{
Kernel = this.CreateKernelWithChatCompletion(),
Arguments = new KernelArguments()
{
{"style", "haiku"}
}
};

await InvokeChatCompletionAgentWithTemplateAsync(agent);
}

private async Task InvokeChatCompletionAgentWithTemplateAsync(ChatCompletionAgent agent)
{
ChatHistory chat = [];

foreach ((string input, string? style) in s_inputs)
{
// Add input to chat
ChatMessageContent request = new(AuthorRole.User, input);
chat.Add(request);
this.WriteAgentChatMessage(request);

KernelArguments? arguments = null;

if (!string.IsNullOrWhiteSpace(style))
{
// Override style template parameter
arguments = new() { { "style", style } };
}

// Process agent response
await foreach (ChatMessageContent message in agent.InvokeAsync(chat, arguments))
{
chat.Add(message);
this.WriteAgentChatMessage(message);
}
}
}
}
6 changes: 3 additions & 3 deletions dotnet/samples/Concepts/Agents/MixedChat_Agents.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,14 @@ public async Task ChatWithOpenAIAssistantAgentAndChatCompletionAgentAsync()

OpenAIAssistantAgent agentWriter =
await OpenAIAssistantAgent.CreateAsync(
kernel: new(),
clientProvider: this.GetClientProvider(),
definition: new(this.Model)
definition: new OpenAIAssistantDefinition(this.Model)
{
Instructions = CopyWriterInstructions,
Name = CopyWriterName,
Metadata = AssistantSampleMetadata,
});
},
kernel: new Kernel());

// Create a chat for agent interaction.
AgentGroupChat chat =
Expand Down
6 changes: 3 additions & 3 deletions dotnet/samples/Concepts/Agents/MixedChat_Files.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,14 @@ await fileClient.UploadFileAsync(
// Define the agents
OpenAIAssistantAgent analystAgent =
await OpenAIAssistantAgent.CreateAsync(
kernel: new(),
provider,
new(this.Model)
definition: new OpenAIAssistantDefinition(this.Model)
{
EnableCodeInterpreter = true,
CodeInterpreterFileIds = [uploadFile.Id], // Associate uploaded file with assistant code-interpreter
Metadata = AssistantSampleMetadata,
});
},
kernel: new Kernel());

ChatCompletionAgent summaryAgent =
new()
Expand Down
6 changes: 3 additions & 3 deletions dotnet/samples/Concepts/Agents/MixedChat_Images.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,15 @@ public async Task AnalyzeDataAndGenerateChartAsync()
// Define the agents
OpenAIAssistantAgent analystAgent =
await OpenAIAssistantAgent.CreateAsync(
kernel: new(),
provider,
new(this.Model)
definition: new OpenAIAssistantDefinition(this.Model)
{
Instructions = AnalystInstructions,
Name = AnalystName,
EnableCodeInterpreter = true,
Metadata = AssistantSampleMetadata,
});
},
kernel: new Kernel());

ChatCompletionAgent summaryAgent =
new()
Expand Down
6 changes: 3 additions & 3 deletions dotnet/samples/Concepts/Agents/MixedChat_Reset.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,13 @@ public async Task ResetChatAsync()
// Define the agents
OpenAIAssistantAgent assistantAgent =
await OpenAIAssistantAgent.CreateAsync(
kernel: new(),
provider,
new(this.Model)
definition: new OpenAIAssistantDefinition(this.Model)
{
Name = nameof(OpenAIAssistantAgent),
Instructions = AgentInstructions,
});
},
kernel: new Kernel());

ChatCompletionAgent chatAgent =
new()
Expand Down
6 changes: 3 additions & 3 deletions dotnet/samples/Concepts/Agents/MixedChat_Streaming.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,14 +47,14 @@ public async Task UseStreamingAgentChatAsync()

OpenAIAssistantAgent agentWriter =
await OpenAIAssistantAgent.CreateAsync(
kernel: new(),
clientProvider: this.GetClientProvider(),
definition: new(this.Model)
definition: new OpenAIAssistantDefinition(this.Model)
{
Instructions = CopyWriterInstructions,
Name = CopyWriterName,
Metadata = AssistantSampleMetadata,
});
},
kernel: new Kernel());

// Create a chat for agent interaction.
AgentGroupChat chat =
Expand Down
6 changes: 3 additions & 3 deletions dotnet/samples/Concepts/Agents/OpenAIAssistant_ChartMaker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,15 @@ public async Task GenerateChartWithOpenAIAssistantAgentAsync()
// Define the agent
OpenAIAssistantAgent agent =
await OpenAIAssistantAgent.CreateAsync(
kernel: new(),
provider,
new(this.Model)
definition: new OpenAIAssistantDefinition(this.Model)
{
Instructions = AgentInstructions,
Name = AgentName,
EnableCodeInterpreter = true,
Metadata = AssistantSampleMetadata,
});
},
kernel: new());

// Create a chat for agent interaction.
AgentGroupChat chat = new();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,14 @@ await fileClient.UploadFileAsync(
// Define the agent
OpenAIAssistantAgent agent =
await OpenAIAssistantAgent.CreateAsync(
kernel: new(),
provider,
new(this.Model)
definition: new OpenAIAssistantDefinition(this.Model)
{
EnableCodeInterpreter = true,
CodeInterpreterFileIds = [uploadFile.Id],
Metadata = AssistantSampleMetadata,
});
},
kernel: new Kernel());

// Create a chat for agent interaction.
AgentGroupChat chat = new();
Expand Down
8 changes: 4 additions & 4 deletions dotnet/samples/Concepts/Agents/OpenAIAssistant_Streaming.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,14 @@ public class OpenAIAssistant_Streaming(ITestOutputHelper output) : BaseAgentsTes
private const string ParrotInstructions = "Repeat the user message in the voice of a pirate and then end with a parrot sound.";

[Fact]
public async Task UseStreamingChatCompletionAgentAsync()
public async Task UseStreamingAssistantAgentAsync()
{
// Define the agent
OpenAIAssistantAgent agent =
await OpenAIAssistantAgent.CreateAsync(
kernel: new(),
clientProvider: this.GetClientProvider(),
new(this.Model)
definition: new OpenAIAssistantDefinition(this.Model)
{
Instructions = ParrotInstructions,
Name = ParrotName,
Expand All @@ -42,7 +42,7 @@ await OpenAIAssistantAgent.CreateAsync(
}

[Fact]
public async Task UseStreamingChatCompletionAgentWithPluginAsync()
public async Task UseStreamingAssistantAgentWithPluginAsync()
{
const string MenuInstructions = "Answer questions about the menu.";

Expand All @@ -51,7 +51,7 @@ public async Task UseStreamingChatCompletionAgentWithPluginAsync()
await OpenAIAssistantAgent.CreateAsync(
kernel: new(),
clientProvider: this.GetClientProvider(),
new(this.Model)
definition: new OpenAIAssistantDefinition(this.Model)
{
Instructions = MenuInstructions,
Name = "Host",
Expand Down
Loading

0 comments on commit a5570b1

Please sign in to comment.