Added a SWOT analysis assistant (#1122)

Co-authored-by: hart_s3 <sabrina.hartmann@dlr.de>
This commit is contained in:
Thorsten Sommerandhart_s3 authored and GitHub committed 2026-10-28 08:51:30 +01:00
1 parent 6cb20fd75a
commit 060a2a5734
87 files changed
+4601 -45

No files matched your search

@@ -0,0 +1,181 @@
using System.Text.Json;
using AIStudio.Assistants.SWOTAnalysis;
namespace AIStudio.Tests.Assistants.SWOTAnalysis;
[TestFixture]
public sealed class SwotCategoryAnalysisResponseTests
{
[Test]
public void TheFindingsOfACategoryAreReadAndTrimmed()
{
var result = CreateResult();
result.Findings[0].Summary = " Experienced team ";
var parsed = SwotCategoryAnalysisResponseParser.TryParse(JsonSerializer.Serialize(result), out var parsedResult, out var issue);
Assert.Multiple(() =>
{
Assert.That(parsed, Is.True);
Assert.That(issue, Is.Empty);
Assert.That(parsedResult.EmptyMessage, Is.Empty);
Assert.That(parsedResult.Findings.Single().Summary, Is.EqualTo("Experienced team"));
});
}
[Test]
public void AnAnswerWithoutJsonNamesTheProblemForTheModel()
{
var parsed = SwotCategoryAnalysisResponseParser.TryParse("I cannot analyze this category.", out _, out var issue);
Assert.Multiple(() =>
{
Assert.That(parsed, Is.False);
Assert.That(issue, Does.Contain("no complete JSON object"));
});
}
[Test]
public void BrokenJsonNamesTheProblemForTheModel()
{
var parsed = SwotCategoryAnalysisResponseParser.TryParse("""{"findings": [{"summary": "Experienced team",}""", out _, out var issue);
Assert.Multiple(() =>
{
Assert.That(parsed, Is.False);
Assert.That(issue, Is.Not.Empty);
});
}
[Test]
public void ANullFieldIsRejectedRatherThanReadAsEmpty()
{
var parsed = SwotCategoryAnalysisResponseParser.TryParse("""{"empty_message": null, "findings": []}""", out _, out var issue);
Assert.Multiple(() =>
{
Assert.That(parsed, Is.False);
Assert.That(issue, Does.StartWith("The JSON object cannot be read:"));
});
}
[Test]
public void ANullFindingIsLeftOut()
{
var parsed = SwotCategoryAnalysisResponseParser.TryParse("""{"empty_message": "", "findings": [null, {"summary": "Experienced team", "explanation": "Long tenure."}]}""", out var result, out _);
Assert.Multiple(() =>
{
Assert.That(parsed, Is.True);
Assert.That(result.Findings.Single().Summary, Is.EqualTo("Experienced team"));
});
}
[Test]
public void AnUnsupportedCategoryRequiresAnExplanation()
{
var validEmptyResponse = JsonSerializer.Serialize(new SwotCategoryAnalysisResult
{
EmptyMessage = "The supplied material contains no evidence for this category.",
});
var invalidEmptyResponse = JsonSerializer.Serialize(new SwotCategoryAnalysisResult());
Assert.Multiple(() =>
{
Assert.That(SwotCategoryAnalysisResponseParser.TryParse(validEmptyResponse, out var result, out _), Is.True);
Assert.That(result.Findings, Is.Empty);
Assert.That(SwotCategoryAnalysisResponseParser.TryParse(invalidEmptyResponse, out _, out var issue), Is.False);
Assert.That(issue, Does.Contain("empty_message"));
});
}
[Test]
public void FindingsRejectAnEmptyMessageOrMissingEvidenceExplanation()
{
var result = CreateResult();
result.EmptyMessage = "No evidence.";
var responseWithContradictoryEmptyMessage = JsonSerializer.Serialize(result);
result.EmptyMessage = string.Empty;
result.Findings[0].Explanation = string.Empty;
var responseWithoutExplanation = JsonSerializer.Serialize(result);
Assert.Multiple(() =>
{
Assert.That(SwotCategoryAnalysisResponseParser.TryParse(responseWithContradictoryEmptyMessage, out _, out var contradictionIssue), Is.False);
Assert.That(contradictionIssue, Does.Contain("Leave empty_message empty"));
Assert.That(SwotCategoryAnalysisResponseParser.TryParse(responseWithoutExplanation, out _, out var explanationIssue), Is.False);
Assert.That(explanationIssue, Is.EqualTo("Finding 1 has no explanation."));
});
}
[TestCase("STRENGTHS", "strengths: internal positive factors")]
[TestCase("WEAKNESSES", "weaknesses: internal negative factors")]
[TestCase("OPPORTUNITIES", "opportunities: external positive factors")]
[TestCase("THREATS", "threats: external negative factors")]
public void TheSystemPromptAssignsExactlyOneCategory(string categoryName, string expectedDescription)
{
var category = Enum.Parse<SwotAnalysisCategory>(categoryName);
var prompt = SwotCategoryAnalysis.BuildSystemPrompt(category, "Answer in German.");
Assert.Multiple(() =>
{
Assert.That(prompt, Does.Contain($"Analyze only {expectedDescription}."));
Assert.That(prompt, Does.Contain("Do not return findings for another SWOT category."));
Assert.That(prompt, Does.Contain("shared-research contains additional evidence"));
Assert.That(prompt, Does.Contain("analysis-subject, contextual-knowledge, and\nshared-research"));
Assert.That(prompt, Does.Contain("untrusted data"));
Assert.That(prompt, Does.EndWith("Answer in German."));
});
}
[Test]
public void TheCategoryRequestKeepsEveryInputInItsAssignedSection()
{
var request = SwotCategoryAnalysis.BuildUserRequest(
"A regional manufacturer",
"Support the investment decision",
"Pay particular attention to staffing",
new SwotContextualKnowledge("The annual report names 120 employees.", []),
sharedResearch: "A market report documents increasing regional demand.");
Assert.Multiple(() =>
{
Assert.That(request, Does.Match("(?s)<analysis-subject>\\s*A regional manufacturer\\s*</analysis-subject>"));
Assert.That(request, Does.Match("(?s)<analysis-goal>\\s*Support the investment decision\\s*</analysis-goal>"));
Assert.That(request, Does.Match("(?s)<analysis-focus>\\s*Pay particular attention to staffing\\s*</analysis-focus>"));
Assert.That(request, Does.Match("(?s)<contextual-knowledge>\\s*The annual report names 120 employees\\.\\s*</contextual-knowledge>"));
Assert.That(request, Does.Match("(?s)<shared-research>\\s*A market report documents increasing regional demand\\.\\s*</shared-research>"));
});
}
[Test]
public void TheCategoryRequestOmitsEmptySharedResearch()
{
var request = SwotCategoryAnalysis.BuildUserRequest(
"A regional manufacturer",
"Support the investment decision",
string.Empty,
SwotContextualKnowledge.NONE);
Assert.Multiple(() =>
{
Assert.That(request, Does.Not.Contain("<shared-research>"));
Assert.That(request, Does.Contain("No additional analysis focus was provided."));
Assert.That(request, Does.Contain("No additional contextual knowledge was provided."));
});
}
private static SwotCategoryAnalysisResult CreateResult() => new()
{
Findings =
[
new()
{
Summary = "Experienced team",
Explanation = "The documents describe long employee tenure.",
},
],
};
}