Skip to content

Commit e4ea494

Browse files
committed
Add project idea generation and update certification processing
Introduce new dependencies and functionality to generate project ideas based on Azure certification skills and services. Update `GenerateProject` to use `ContentGenerationService` for generating project ideas. Modify `ProcessCertDataFile` to use certification code vectors. Extend `ContentGenerationService` with a method for generating project ideas using `ChatClient`. Add new model `CloudProjectIdea` to represent project ideas.
1 parent 5092fcc commit e4ea494

File tree

6 files changed

+141
-44
lines changed

6 files changed

+141
-44
lines changed
Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,34 @@
1+
using Azure;
12
using azure_project_generator.models;
3+
using azure_project_generator.services;
24
using Microsoft.AspNetCore.Http;
35
using Microsoft.AspNetCore.Mvc;
46
using Microsoft.Azure.Cosmos;
57
using Microsoft.Azure.Functions.Worker;
68
using Microsoft.Azure.Functions.Worker.Http;
79
using Microsoft.Extensions.Logging;
8-
using OpenAI.Embeddings;
9-
using System.ComponentModel;
10+
using Newtonsoft.Json;
11+
using System.Net;
1012

1113
namespace azure_project_generator
1214
{
1315
public class GenerateProject
1416
{
1517
private readonly ILogger<GenerateProject> _logger;
18+
private readonly ContentGenerationService _contentGenerationService;
1619

17-
public GenerateProject(ILogger<GenerateProject> logger)
20+
public GenerateProject(ILogger<GenerateProject> logger, ContentGenerationService contentGenerationService)
1821
{
1922
_logger = logger;
23+
_contentGenerationService = contentGenerationService;
2024
}
2125

2226
[Function("GenerateProject")]
23-
public IActionResult Run([HttpTrigger(AuthorizationLevel.Function, "get")] HttpRequest req, string certificationCode,
27+
public async Task<HttpResponseData> Run([HttpTrigger(AuthorizationLevel.Function, "get")] HttpRequestData req, string certificationCode,
2428

2529
[CosmosDBInput(Connection = "CosmosDBConnection")] CosmosClient client)
2630
{
31+
var response = req.CreateResponse(HttpStatusCode.OK);
2732
_logger.LogInformation("C# HTTP trigger function processed a request.");
2833

2934
var query = new QueryDefinition("SELECT * FROM c WHERE c.certificationCode = @certificationCode")
@@ -36,25 +41,35 @@ public IActionResult Run([HttpTrigger(AuthorizationLevel.Function, "get")] HttpR
3641
CertificationProjectPromptDocument certificationProjectPromptDocument = iterator.ReadNextAsync().Result.FirstOrDefault();
3742

3843
float[] projectPromptVector = certificationProjectPromptDocument.ProjectPromptVector;
39-
4044

45+
var queryDef = new QueryDefinition
46+
(query: $"SELECT c.serviceName, c.skillName, c.topicName, VectorDistance(c.contextVector,@embedding) " +
47+
$"AS SimilarityScore FROM c ORDER BY VectorDistance(c.contextVector,@embedding)"
48+
).WithParameter("@embedding", projectPromptVector);
4149

42-
var queryDef = new QueryDefinition(
43-
query: $"SELECT c.serviceName, VectorDistance(c.contextVector,@embedding) AS SimilarityScore FROM c ORDER BY VectorDistance(c.contextVector,@embedding)"
44-
).WithParameter("@embedding", projectPromptVector);
50+
using FeedIterator<CertificationService> resultSetIterator =
51+
client.GetContainer("AzureCertDB", "certvectors").GetItemQueryIterator<CertificationService>(queryDef);
52+
53+
string projectServices = "";
54+
string projectSkills = "";
4555

46-
using FeedIterator<CertificationServiceDocument> resultSetIterator = client.GetContainer("AzureCertDB", "certvectors").GetItemQueryIterator<CertificationServiceDocument>(queryDef);
47-
4856
while (resultSetIterator.HasMoreResults)
4957
{
50-
FeedResponse<CertificationServiceDocument> response = resultSetIterator.ReadNextAsync().Result;
51-
foreach (var item in response)
58+
FeedResponse<CertificationService> feedResponse = resultSetIterator.ReadNextAsync().Result;
59+
foreach (var item in feedResponse)
5260
{
53-
_logger.LogInformation(item.ServiceName);
61+
projectServices += item.ServiceName + " ";
62+
projectSkills += item.SkillName + " ";
63+
5464
}
5565
}
5666

57-
return new OkObjectResult("Welcome to Azure Functions!");
67+
string cloudProjectIdea = await _contentGenerationService.GenerateProjectIdeaAsync(projectSkills, projectServices);
68+
69+
response.Headers.Add("Content-Type", "application/json");
70+
await response.WriteStringAsync(cloudProjectIdea);
71+
72+
return response;
5873
}
5974
}
6075
}

azure-project-generator/ProcessCertDataFile.cs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -79,10 +79,9 @@ public async Task<CertificationProjectPromptOutput> Run([BlobTrigger("certdata/{
7979

8080

8181

82-
string contextSentence = _contentGenerationService.GenerateCertDataContextSentence(certification);
83-
float[] contentVector = await _contentGenerationService.GenerateEmbeddingsAsync(contextSentence);
82+
float[] cerificationCodeVector = await _contentGenerationService.GenerateEmbeddingsAsync(certification.CertificationCode);
8483

85-
var certificationDocument = CreateCertificationProjectPromptDocument(certification, contextSentence, contentVector);
84+
var certificationDocument = CreateCertificationProjectPromptDocument(certification, cerificationCodeVector);
8685

8786
_logger.LogInformation("Document created successfully.");
8887
_logger.LogInformation($"Archiving blob: {name}");
@@ -95,13 +94,12 @@ public async Task<CertificationProjectPromptOutput> Run([BlobTrigger("certdata/{
9594
};
9695
}
9796

98-
private CertificationProjectPromptDocument CreateCertificationProjectPromptDocument(Certification data, string contextSentence, float[] contentVector) =>
97+
private CertificationProjectPromptDocument CreateCertificationProjectPromptDocument(Certification data, float[] contentVector) =>
9998
new CertificationProjectPromptDocument
10099
{
101100
Id = Guid.NewGuid().ToString(),
102101
CertificationCode = data.CertificationCode,
103102
CertificationName = data.CertificationName,
104-
ProjectPrompt = contextSentence,
105103
ProjectPromptVector = contentVector
106104
};
107105
}

azure-project-generator/Program.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
string endpointFromEnvironment = config["AZURE_OPENAI_API_ENDPOINT"];
2222
string embeddingsDeployment = config["EMBEDDINGS_DEPLOYMENT"];
2323
string azureWebJobsStorage = config["AzureWebJobsStorage"];
24+
string completionsDeployment = config["COMPLETIONS_DEPLOYMENT"];
2425

2526
if (string.IsNullOrEmpty(keyFromEnvironment) || string.IsNullOrEmpty(endpointFromEnvironment) || string.IsNullOrEmpty(embeddingsDeployment))
2627
{
@@ -37,6 +38,9 @@
3738
// Register EmbeddingClient as a singleton
3839
services.AddSingleton(azureClient.GetEmbeddingClient(embeddingsDeployment));
3940

41+
// Register ChatClient as a singleton
42+
services.AddSingleton(azureClient.GetChatClient(completionsDeployment));
43+
4044
// Register JsonValidationService
4145
services.AddSingleton<JsonValidationService>();
4246

azure-project-generator/models/CertificationProjectPromptDocument.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,7 @@ public class CertificationProjectPromptDocument
1515
public string CertificationCode { get; set; }
1616
[JsonPropertyName("certificationName")]
1717
public string CertificationName { get; set; }
18-
[JsonPropertyName("skillName")]
19-
public string ProjectPrompt { get; set; }
20-
[JsonPropertyName("projectPrompt")]
18+
[JsonPropertyName("projectPromptVector")]
2119
public float[] ProjectPromptVector { get; set; }
2220
}
2321
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using System.Threading.Tasks;
6+
7+
namespace azure_project_generator.models
8+
{
9+
public class CloudProjectIdea
10+
{
11+
12+
public string Title { get; set; }
13+
public string Description { get; set; }
14+
public List<string> Steps { get; set; }
15+
16+
}
17+
}

azure-project-generator/services/ContentGenerationService.cs

Lines changed: 87 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,43 +2,30 @@
22
using azure_project_generator.models;
33
using Microsoft.Extensions.Logging;
44
using OpenAI.Embeddings;
5+
using OpenAI.Chat;
6+
using System.Text.Json;
7+
using Microsoft.AspNetCore.Mvc;
8+
using Newtonsoft.Json;
9+
using Newtonsoft.Json.Linq;
510

611
namespace azure_project_generator.services
712
{
813
public class ContentGenerationService
914
{
1015
private readonly ILogger<ContentGenerationService> _logger;
1116
private readonly EmbeddingClient _embeddingClient;
12-
13-
public ContentGenerationService(ILogger<ContentGenerationService> logger, EmbeddingClient embeddingClient)
17+
private readonly ChatClient _completionsClient;
18+
public ContentGenerationService(ILogger<ContentGenerationService> logger, EmbeddingClient embeddingClient, ChatClient completionsClient)
1419
{
1520
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
1621
_embeddingClient = embeddingClient ?? throw new ArgumentNullException(nameof(embeddingClient));
22+
_completionsClient = completionsClient ?? throw new ArgumentNullException(nameof(completionsClient));
23+
1724
}
1825

1926
public string GenerateCertServiceContextSentence(CertificationService data) =>
2027
$"The {data.CertificationCode} {data.CertificationName} certification includes the skill of {data.SkillName}. Within this skill, there is a focus on the topic of {data.TopicName}, particularly through the use of the service {data.ServiceName}.";
2128

22-
public string GenerateCertDataContextSentence(Certification certificationDocument)
23-
{
24-
var certificationName = certificationDocument.CertificationName;
25-
var certificationCode = certificationDocument.CertificationCode;
26-
27-
var skills = certificationDocument.SkillsMeasured.Select(s => s.Name).ToList();
28-
var topicsWithServices = certificationDocument.SkillsMeasured
29-
.SelectMany(s => s.Topics)
30-
.Select(t => $"{t.TopicName} (using {string.Join(", ", t.Services)})").ToList();
31-
32-
string skillNames = string.Join(", ", skills);
33-
string topicDetails = string.Join("; ", topicsWithServices);
34-
35-
string prompt = $"Generate a project idea that aligns with the {certificationCode} certification, " +
36-
$"which covers skills such as {skillNames}. The project should focus on topics like {topicDetails}, " +
37-
$"addressing real-world scenarios related to {certificationName}.";
38-
39-
return prompt;
40-
41-
}
4229

4330
public async Task<float[]> GenerateEmbeddingsAsync(string content)
4431
{
@@ -60,5 +47,83 @@ public async Task<float[]> GenerateEmbeddingsAsync(string content)
6047
throw;
6148
}
6249
}
50+
51+
public async Task<string> GenerateProjectIdeaAsync(string skills, string services)
52+
{
53+
string userPrompt = $@"You are an expert cloud architect.
54+
Please generate a detailed project idea for a beginner-friendly weekend cloud solution
55+
based on the following Azure certification skills: {skills}.
56+
The project should utilize the following services: {services}.
57+
The project should be small in scale, achievable over a weekend, and have a fun, creative name. Suitable for beginners. Cheap to run.
58+
The response must be formatted as valid JSON and include only the following fields:
59+
{{
60+
""projectName"": ""A fun and creative project name"",
61+
""description"": ""A brief, engaging description of the project, highlighting its purpose and main features."",
62+
""learningGoals"": [""Goal 1"", ""Goal 2"", ""Goal 3""],
63+
""steps"": [
64+
""Step 1: Description of the first step"",
65+
""Step 2: Description of the second step"",
66+
""Step 3: Description of the third step"",
67+
""Step 4: Description of the fourth step"",
68+
""Step 5: Description of the fifth step""
69+
]
70+
}}
71+
Ensure that the project idea is practical, aligned with beginner-level skills, and leverages best practices in Azure architecture.";
72+
73+
try
74+
{
75+
_logger.LogInformation("Generating project idea...");
76+
ChatCompletion completion = await _completionsClient.CompleteChatAsync(new ChatMessage[]
77+
{
78+
new SystemChatMessage("You are a technical assistant specialized in generating beginner-friendly cloud project ideas. Provide the response in JSON format only, without any additional text."),
79+
new UserChatMessage(userPrompt)
80+
});
81+
82+
string projectIdeaContent = completion.Content[0].Text;
83+
84+
if (string.IsNullOrWhiteSpace(projectIdeaContent))
85+
{
86+
_logger.LogWarning("The response from the AI model was empty or null.");
87+
throw new Exception("Failed to generate project idea.");
88+
}
89+
90+
string cleanedJsonContent = projectIdeaContent
91+
.Replace("```json", string.Empty)
92+
.Replace("```", string.Empty)
93+
.Replace("json\n", string.Empty)
94+
.Trim();
95+
96+
JObject jsonObject = JObject.Parse(cleanedJsonContent);
97+
98+
// Validate that all required fields are present
99+
string[] requiredFields = { "projectName", "description", "learningGoals", "steps"};
100+
foreach (var field in requiredFields)
101+
{
102+
if (!jsonObject.ContainsKey(field))
103+
{
104+
_logger.LogWarning($"Generated JSON is missing required field: {field}");
105+
throw new System.Text.Json.JsonException($"Generated project idea is missing required field: {field}");
106+
107+
}
108+
}
109+
110+
return jsonObject.ToString();
111+
}
112+
catch (System.Text.Json.JsonException ex)
113+
{
114+
_logger.LogError(ex, "Error parsing JSON response");
115+
throw;
116+
}
117+
catch (RequestFailedException ex)
118+
{
119+
_logger.LogError(ex, "Azure OpenAI API request failed");
120+
throw;
121+
}
122+
catch (Exception ex)
123+
{
124+
_logger.LogError(ex, "Error generating project idea");
125+
throw;
126+
}
127+
}
63128
}
64129
}

0 commit comments

Comments
 (0)