Skip to content

Commit 6de305a

Browse files
authored
Merge pull request #23 from learntocloud/definitionendpoint
Refactor project generation and enhance ContentService
2 parents a35c12b + dde1c79 commit 6de305a

File tree

3 files changed

+127
-47
lines changed

3 files changed

+127
-47
lines changed

azure-project-generator/GenerateProject.cs renamed to azure-project-generator/GenerateProjectFromCertification.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,18 @@
88

99
namespace azure_project_generator
1010
{
11-
public class GenerateProject
11+
public class GenerateProjectFromCertification
1212
{
13-
private readonly ILogger<GenerateProject> _logger;
13+
private readonly ILogger<GenerateProjectFromCertification> _logger;
1414
private readonly ContentGenerationService _contentGenerationService;
1515

16-
public GenerateProject(ILogger<GenerateProject> logger, ContentGenerationService contentGenerationService)
16+
public GenerateProjectFromCertification(ILogger<GenerateProjectFromCertification> logger, ContentGenerationService contentGenerationService)
1717
{
1818
_logger = logger;
1919
_contentGenerationService = contentGenerationService;
2020
}
2121

22-
[Function("GenerateProject")]
22+
[Function("GenerateProjectFromCertification")]
2323
public async Task<HttpResponseData> Run([HttpTrigger(AuthorizationLevel.Function, "get")] HttpRequestData req, string certificationCode, string skillName, string topic,
2424
[CosmosDBInput(Connection = "CosmosDBConnection")] CosmosClient client)
2525
{
@@ -49,7 +49,7 @@ public async Task<HttpResponseData> Run([HttpTrigger(AuthorizationLevel.Function
4949
}
5050
}
5151

52-
string cloudProjectIdea = await _contentGenerationService.GenerateProjectIdeaAsync(projectServices, skillName, topic);
52+
string cloudProjectIdea = await _contentGenerationService.GenerateProjectIdeaFromCertAsync(projectServices, skillName, topic);
5353

5454

5555
response.Headers.Add("Content-Type", "application/json");
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
using azure_project_generator.models;
2+
using azure_project_generator.services;
3+
using Microsoft.AspNetCore.Http;
4+
using Microsoft.AspNetCore.Mvc;
5+
using Microsoft.Azure.Cosmos;
6+
using Microsoft.Azure.Functions.Worker;
7+
using Microsoft.Azure.Functions.Worker.Http;
8+
using Microsoft.Extensions.Logging;
9+
using System.Net;
10+
11+
namespace azure_project_generator
12+
{
13+
public class GenerateProjectFromConcept
14+
{
15+
private readonly ILogger<GenerateProjectFromConcept> _logger;
16+
private ContentGenerationService _contentGenerationService;
17+
18+
public GenerateProjectFromConcept(ILogger<GenerateProjectFromConcept> logger, ContentGenerationService contentGenerationService)
19+
{
20+
_logger = logger;
21+
_contentGenerationService = contentGenerationService;
22+
}
23+
24+
[Function("GenerateProjectFromConcept")]
25+
public async Task<HttpResponseData> Run([HttpTrigger(AuthorizationLevel.Admin, "get", "post")] HttpRequestData req, string concept,
26+
[CosmosDBInput(Connection = "CosmosDBConnection")]
27+
CosmosClient client)
28+
{
29+
var response = req.CreateResponse(HttpStatusCode.OK);
30+
_logger.LogInformation("C# HTTP trigger function processed a request.");
31+
32+
string projectPrompt = $"I need a project idea for the cloud engineering concept exam {concept}";
33+
34+
float[] projectPromptVector = _contentGenerationService.GenerateEmbeddingsAsync(projectPrompt).Result;
35+
36+
var queryDef = new QueryDefinition(
37+
query: $"SELECT TOP 5 c.serviceName, c.skillName, c.topicName, VectorDistance(c.contextVector, @embedding) " +
38+
$"AS SimilarityScore FROM c ORDER BY VectorDistance(c.contextVector, @embedding)"
39+
).WithParameter("@embedding", projectPromptVector);
40+
41+
using FeedIterator<CertificationService> resultSetIterator =
42+
client.GetContainer("AzureCertDB", "certvectors").GetItemQueryIterator<CertificationService>(queryDef);
43+
44+
List<string> projectServices = new List<string>();
45+
46+
while (resultSetIterator.HasMoreResults)
47+
{
48+
FeedResponse<CertificationService> feedResponse = resultSetIterator.ReadNextAsync().Result;
49+
foreach (var item in feedResponse)
50+
{
51+
projectServices.Add(item.ServiceName);
52+
}
53+
}
54+
string cloudProjectIdea = await _contentGenerationService.GenerateProjectIdeaFromConceptAsync(projectServices, concept);
55+
56+
57+
response.Headers.Add("Content-Type", "application/json");
58+
await response.WriteStringAsync(cloudProjectIdea);
59+
60+
return response;
61+
}
62+
}
63+
}
Lines changed: 59 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1+
12
using Azure;
23
using azure_project_generator.models;
34
using Microsoft.Extensions.Logging;
45
using OpenAI.Embeddings;
56
using OpenAI.Chat;
67
using Newtonsoft.Json;
8+
using System.Text.RegularExpressions;
79

810
namespace azure_project_generator.services
911
{
@@ -12,6 +14,19 @@ public class ContentGenerationService
1214
private readonly ILogger<ContentGenerationService> _logger;
1315
private readonly EmbeddingClient _embeddingClient;
1416
private readonly ChatClient _completionsClient;
17+
18+
private const string JSON_FORMAT = @"{{
19+
""title"": ""A concise, descriptive project name"",
20+
""description"": ""A factual description of the project, highlighting its technical purpose and main features."",
21+
""steps"": [
22+
""Step 1: Description of the first technical step"",
23+
""Step 2: Description of the second technical step"",
24+
""Step 3: Description of the third technical step"",
25+
""Step 4: Description of the fourth technical step"",
26+
""Step 5: Description of the fifth technical step""
27+
]
28+
}}";
29+
1530
public ContentGenerationService(ILogger<ContentGenerationService> logger, EmbeddingClient embeddingClient, ChatClient completionsClient)
1631
{
1732
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
@@ -24,6 +39,7 @@ public string GenerateCertServiceContextSentence(CertificationService data) =>
2439

2540
public string GenerateCertDataContextSentence(Certification data) =>
2641
$"The {data.CertificationCode} {data.CertificationName} certification includes the following skills: {string.Join(", ", data.SkillsMeasured.Select(s => s.Name))}.";
42+
2743
public async Task<float[]> GenerateEmbeddingsAsync(string content)
2844
{
2945
try
@@ -45,34 +61,41 @@ public async Task<float[]> GenerateEmbeddingsAsync(string content)
4561
}
4662
}
4763

48-
public async Task<string> GenerateProjectIdeaAsync(List<string> services, string skill, string topic)
64+
public async Task<string> GenerateProjectIdeaFromCertAsync(List<string> services, string skill, string topic)
4965
{
5066
string userPrompt = $@"You are a cloud architect specializing in Azure architecture.
51-
Please generate a detailed project idea for a small, practical cloud solution based on the following Azure certification skill: {skill} and topic: {topic}.
52-
The project should utilize ONLY the following services: {services}.
53-
The project should focus on key technical steps without any subjective descriptions or recommendations.
54-
The response must be formatted as valid JSON and include only the following fields:
55-
{{
56-
""title"": ""A concise, descriptive project name"",
57-
""description"": ""A factual description of the project, highlighting its technical purpose and main features."",
58-
""steps"": [
59-
""Step 1: Description of the first technical step"",
60-
""Step 2: Description of the second technical step"",
61-
""Step 3: Description of the third technical step"",
62-
""Step 4: Description of the fourth technical step"",
63-
""Step 5: Description of the fifth technical step""
64-
]
65-
}}
66-
Ensure that the project idea is focused purely on technical details, aligned with best practices in Azure architecture, and small in scope.";
67+
Please generate a detailed project idea for a small, practical cloud solution based on the following Azure certification skill: {skill} and topic: {topic}.
68+
The project should utilize ONLY the following services: {string.Join(", ", services)}.
69+
The project should focus on key technical steps without any subjective descriptions or recommendations.
70+
The response must be formatted as valid JSON and include only the following fields:
71+
{JSON_FORMAT}
72+
Ensure that the project idea is focused purely on technical details, aligned with best practices in Azure architecture, and small in scope.";
73+
74+
return await GenerateProjectIdeaAsync(userPrompt, services, topic);
75+
}
6776

77+
public async Task<string> GenerateProjectIdeaFromConceptAsync(List<string> services, string topic)
78+
{
79+
string userPrompt = $@"You are a cloud architect.
80+
Please generate a detailed project idea for a small, practical cloud solution based on the following cloud engineering concept certification: {topic}.
81+
The project should utilize ONLY the following services: {string.Join(", ", services)}.
82+
The project should focus on key technical steps without any subjective descriptions or recommendations.
83+
The response must be formatted as valid JSON and include only the following fields:
84+
{JSON_FORMAT}
85+
Ensure that the project idea is focused purely on technical details, aligned with best practices in cloud architecture, and small in scope.";
86+
87+
return await GenerateProjectIdeaAsync(userPrompt, services, topic);
88+
}
89+
90+
private async Task<string> GenerateProjectIdeaAsync(string userPrompt, List<string> services, string topic)
91+
{
6892
try
6993
{
7094
_logger.LogInformation("Generating project idea...");
7195
ChatCompletion completion = await _completionsClient.CompleteChatAsync(new ChatMessage[]
7296
{
73-
new SystemChatMessage("You are a cloud engineer and mentor specialized in generating " +
74-
"beginner-friendly cloud project ideas. Provide the response in JSON format only, without any additional text."),
75-
new UserChatMessage(userPrompt)
97+
new SystemChatMessage("You are a cloud engineer and mentor specialized in generating beginner-friendly cloud project ideas. Provide the response in JSON format only, without any additional text."),
98+
new UserChatMessage(userPrompt)
7699
});
77100

78101
string projectIdeaContent = completion.Content[0].Text;
@@ -83,37 +106,19 @@ The project should focus on key technical steps without any subjective descripti
83106
throw new Exception("Failed to generate project idea.");
84107
}
85108

86-
string cleanedJsonContent = projectIdeaContent
87-
.Replace("```json", string.Empty)
88-
.Replace("```", string.Empty)
89-
.Replace("json\n", string.Empty)
90-
.Trim();
91-
92-
// deserialize to CloudProjectIdea object
109+
string cleanedJsonContent = CleanJsonContent(projectIdeaContent);
93110

94111
CloudProjectIdea? cloudProjectIdea = JsonConvert.DeserializeObject<CloudProjectIdea>(cleanedJsonContent);
95112
if (cloudProjectIdea == null)
96113
{
97114
_logger.LogError("Deserialization returned null for project idea.");
98115
throw new Exception("Failed to deserialize project idea.");
99116
}
100-
// Ensure Steps is initialized if it's null
101-
cloudProjectIdea.Steps ??= new List<string>();
102-
103-
foreach (var service in services)
104-
{
105-
// Properly encode the skill, topic, and service
106-
string encodedTopic = System.Net.WebUtility.UrlEncode(topic);
107-
string encodedService = System.Net.WebUtility.UrlEncode(service);
108117

109-
// Construct the URL using the encoded values
110-
cloudProjectIdea.Resources.Add($"https://learn.microsoft.com/search/?terms={encodedTopic}%20{encodedService}&category=Training");
111-
}
112-
113-
114-
string jsonString = JsonConvert.SerializeObject(cloudProjectIdea);
118+
cloudProjectIdea.Steps ??= new List<string>();
119+
cloudProjectIdea.Resources = GenerateResources(services, topic);
115120

116-
return jsonString;
121+
return JsonConvert.SerializeObject(cloudProjectIdea);
117122
}
118123
catch (System.Text.Json.JsonException ex)
119124
{
@@ -131,5 +136,17 @@ The project should focus on key technical steps without any subjective descripti
131136
throw;
132137
}
133138
}
139+
140+
private string CleanJsonContent(string content)
141+
{
142+
return Regex.Replace(content, @"^```json\s*|\s*```$", "", RegexOptions.Multiline).Trim();
143+
}
144+
145+
private List<string> GenerateResources(List<string> services, string topic)
146+
{
147+
return services.Select(service =>
148+
$"https://learn.microsoft.com/search/?terms={System.Net.WebUtility.UrlEncode(topic)}%20{System.Net.WebUtility.UrlEncode(service)}&category=Training"
149+
).ToList();
150+
}
134151
}
135152
}

0 commit comments

Comments
 (0)