Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
6 changes: 5 additions & 1 deletion src/Elzik.Breef.Application/BreefGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@ public class BreefGenerator(
public async Task<PublishedBreef> GenerateBreefAsync(string url)
{
var extract = await contentExtractor.ExtractAsync(url);
var summary = await contentSummariser.SummariseAsync(extract.Content);

var instructionsPath = Path.Combine(AppContext.BaseDirectory, "SummarisationInstructions", "HtmlContent.md");
var instructions = await File.ReadAllTextAsync(instructionsPath);

var summary = await contentSummariser.SummariseAsync(extract.Content, instructions);

var breef = new Domain.Breef(url, extract.Title ,summary, extract.PreviewImageUrl);

Expand Down
2 changes: 1 addition & 1 deletion src/Elzik.Breef.Domain/IContentSummariser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@
{
public interface IContentSummariser
{
Task<string> SummariseAsync(string content);
Task<string> SummariseAsync(string content, string instructions);
}
}
18 changes: 4 additions & 14 deletions src/Elzik.Breef.Infrastructure/AI/AiContentSummariser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,15 @@ namespace Elzik.Breef.Infrastructure.AI;

public class AiContentSummariser(
ILogger<AiContentSummariser> logger,
IChatCompletionService Chat,
IOptions<AiContentSummariserOptions> summariserOptions) : IContentSummariser
IChatCompletionService Chat) : IContentSummariser
{
public async Task<string> SummariseAsync(string content)
public async Task<string> SummariseAsync(string content, string instructions)
{
var systemPrompt = @$"
You are an expert summarizer. Your task is to summarize the provided text:
- Summarise text, including HTML entities.
- Limit summaries to {summariserOptions.Value.TargetSummaryLengthPercentage}% of the original length but never more then {summariserOptions.Value.TargetSummaryMaxWordCount} words.
- Ensure accurate attribution of information to the correct entities.
- Do not include a link to the original articles.
- Do not include the title in the response.
- Do not include any metadata in the response.
- Do not include a code block in the response.";

ArgumentNullException.ThrowIfNullOrWhiteSpace(instructions);
var formattingInstructions = "Summarise this content in an HTML format using paragraphs and " +
"bullet points to enhance readability\n:";

var chatHistory = new ChatHistory(systemPrompt);
var chatHistory = new ChatHistory(instructions);
chatHistory.AddMessage(AuthorRole.User, $"{formattingInstructions}{content}");

var result = await Chat.GetChatMessageContentAsync(chatHistory);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,11 @@
<ProjectReference Include="..\Elzik.Breef.Domain\Elzik.Breef.Domain.csproj" />
</ItemGroup>

<ItemGroup>
<None Include="..\Elzik.Breef.Infrastructure\SummarisationInstructions\**\*.md"
Link="SummarisationInstructions\%(RecursiveDir)%(Filename)%(Extension)">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
You are an expert summarizer. Your task is to summarize the provided text:
- Summarise text, including HTML entities.
- Limit summaries to 10% of the original length but never more then200 words.
- Ensure accurate attribution of information to the correct entities.
- Do not include a link to the original articles.
- Do not include the title in the response.
- Do not include any metadata in the response.
- Do not include a code block in the response.
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,11 @@
<Using Include="Xunit" />
</ItemGroup>

<ItemGroup>
<None Include="..\..\src\Elzik.Breef.Infrastructure\SummarisationInstructions\**\*.md"
Link="SummarisationInstructions\%(RecursiveDir)%(Filename)%(Extension)">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,14 @@
using Microsoft.SemanticKernel.ChatCompletion;
using Shouldly;
using Xunit.Abstractions;
using System.IO;

namespace Elzik.Breef.Infrastructure.Tests.Integration;

public class AiContentSummariserTests(ITestOutputHelper testOutputHelper)
{
private readonly ITestOutputHelper _testOutputHelper = testOutputHelper
?? throw new ArgumentNullException(nameof(testOutputHelper));
?? throw new ArgumentNullException(nameof(testOutputHelper));
private readonly FakeLogger<AiContentSummariser> _fakeLogger = new();

[Theory]
Expand All @@ -24,36 +25,32 @@ public async Task Summarise_WithValidContent_ReturnsSummary(string testExtracted
{
// Arrange
var configuration = new ConfigurationBuilder()
.AddEnvironmentVariables("breef_")
.Build();
.AddEnvironmentVariables("breef_")
.Build();
var aiServiceOptions = configuration.GetRequiredSection("AiService").Get<AiServiceOptions>()
?? throw new InvalidOperationException("AI service options not found in AiService configuration section.");
?? throw new InvalidOperationException("AI service options not found in AiService configuration section.");

var testContent = await File.ReadAllTextAsync(Path.Join("../../../../TestData", testExtractedContentFile));

var builder = Kernel.CreateBuilder().AddAzureOpenAIChatCompletion(
aiServiceOptions.ModelId, aiServiceOptions.EndpointUrl, aiServiceOptions.ApiKey);
builder.Services.AddLogging(loggingBuilder =>
{
var builder = Kernel.CreateBuilder().AddAzureOpenAIChatCompletion(
aiServiceOptions.ModelId, aiServiceOptions.EndpointUrl, aiServiceOptions.ApiKey);
builder.Services.AddLogging(loggingBuilder =>
{
loggingBuilder.AddProvider(new TestOutputLoggerProvider(_testOutputHelper));
loggingBuilder.SetMinimumLevel(LogLevel.Information);
loggingBuilder.SetMinimumLevel(LogLevel.Information);
});

var kernel = builder.Build();
var chatCompletionService = kernel.GetRequiredService<IChatCompletionService>();
var chatCompletionService = kernel.GetRequiredService<IChatCompletionService>();

var summariserOptions = Options.Create(new AiContentSummariserOptions
{
TargetSummaryLengthPercentage = 10,
TargetSummaryMaxWordCount = 200
});
var systemPrompt = await File.ReadAllTextAsync(Path.Combine(AppContext.BaseDirectory, "SummarisationInstructions", "HtmlContent.md"));

// Act
var contentSummariser = new AiContentSummariser(_fakeLogger, chatCompletionService, summariserOptions);
var summary = await contentSummariser.SummariseAsync(testContent);
var contentSummariser = new AiContentSummariser(_fakeLogger, chatCompletionService);
var summary = await contentSummariser.SummariseAsync(testContent, systemPrompt);

// Assert
_testOutputHelper.WriteLine(summary);
summary.Length.ShouldBeGreaterThan(100, "because this is the minimum acceptable summary length");
_testOutputHelper.WriteLine(summary);
summary.Length.ShouldBeGreaterThan(100, "because this is the minimum acceptable summary length");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,10 @@
<Using Include="Xunit" />
</ItemGroup>

<ItemGroup>
<None Include="..\..\src\Elzik.Breef.Infrastructure\SummarisationInstructions\**\*.md" Link="SummarisationInstructions\%(RecursiveDir)%(Filename)%(Extension)">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,7 @@ public ContentSummariserTests()
{
_logger = new FakeLogger<AiContentSummariser>();
_chatCompletionService = Substitute.For<IChatCompletionService>();
var summariserOptions = Options.Create(new AiContentSummariserOptions
{
TargetSummaryLengthPercentage = 10,
TargetSummaryMaxWordCount = 200
});
_contentSummariser = new AiContentSummariser(_logger, _chatCompletionService, summariserOptions);
_contentSummariser = new AiContentSummariser(_logger, _chatCompletionService);

_testContent = "This is a test content.";
_testSummary = "Test summary.";
Expand All @@ -52,7 +47,7 @@ public ContentSummariserTests()
public async Task SummariseAsync_ValidContent_ReturnsSummary()
{
// Act
var result = await _contentSummariser.SummariseAsync(_testContent);
var result = await _contentSummariser.SummariseAsync(_testContent, "Test instructions");

// Assert
result.ShouldBe("Test summary.");
Expand All @@ -61,22 +56,16 @@ public async Task SummariseAsync_ValidContent_ReturnsSummary()
[Fact]
public async Task SummariseAsync_ValidContent_ProvidesModelInstructions()
{
// Arrange
var instructions = "Test instructions";

// Act
_ = await _contentSummariser.SummariseAsync(_testContent);
_ = await _contentSummariser.SummariseAsync(_testContent, instructions);

// Assert
var systemPrompt = @$"
You are an expert summarizer. Your task is to summarize the provided text:
- Summarise text, including HTML entities.
- Limit summaries to 10% of the original length but never more then 200 words.
- Ensure accurate attribution of information to the correct entities.
- Do not include a link to the original articles.
- Do not include the title in the response.
- Do not include any metadata in the response.
- Do not include a code block in the response.";
await _chatCompletionService.Received(1).GetChatMessageContentsAsync(
Arg.Is<ChatHistory>(ch => ch.Any(m =>
m.Content == systemPrompt && m.Role == AuthorRole.System)),
m.Content == instructions && m.Role == AuthorRole.System)),
Arg.Any<PromptExecutionSettings>(),
Arg.Any<Kernel>(),
Arg.Any<CancellationToken>());
Expand All @@ -86,7 +75,7 @@ await _chatCompletionService.Received(1).GetChatMessageContentsAsync(
public async Task SummariseAsync_ValidContent_Logs()
{
// Act
await _contentSummariser.SummariseAsync(_testContent);
await _contentSummariser.SummariseAsync(_testContent, "Test instructions");

// Assert
_logger.Collector.Count.ShouldBe(1);
Expand Down
Loading