Skip to content

Commit 5dea23d

Browse files
authored
Merge pull request #10 from learntocloud/searchfunction
added project generation and querying
2 parents c1b6747 + e4ea494 commit 5dea23d

File tree

9 files changed

+192
-61
lines changed

9 files changed

+192
-61
lines changed

azure-project-generator/Function1.cs

Lines changed: 0 additions & 30 deletions
This file was deleted.
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
using Azure;
2+
using azure_project_generator.models;
3+
using azure_project_generator.services;
4+
using Microsoft.AspNetCore.Http;
5+
using Microsoft.AspNetCore.Mvc;
6+
using Microsoft.Azure.Cosmos;
7+
using Microsoft.Azure.Functions.Worker;
8+
using Microsoft.Azure.Functions.Worker.Http;
9+
using Microsoft.Extensions.Logging;
10+
using Newtonsoft.Json;
11+
using System.Net;
12+
13+
namespace azure_project_generator
14+
{
15+
public class GenerateProject
16+
{
17+
private readonly ILogger<GenerateProject> _logger;
18+
private readonly ContentGenerationService _contentGenerationService;
19+
20+
public GenerateProject(ILogger<GenerateProject> logger, ContentGenerationService contentGenerationService)
21+
{
22+
_logger = logger;
23+
_contentGenerationService = contentGenerationService;
24+
}
25+
26+
[Function("GenerateProject")]
27+
public async Task<HttpResponseData> Run([HttpTrigger(AuthorizationLevel.Function, "get")] HttpRequestData req, string certificationCode,
28+
29+
[CosmosDBInput(Connection = "CosmosDBConnection")] CosmosClient client)
30+
{
31+
var response = req.CreateResponse(HttpStatusCode.OK);
32+
_logger.LogInformation("C# HTTP trigger function processed a request.");
33+
34+
var query = new QueryDefinition("SELECT * FROM c WHERE c.certificationCode = @certificationCode")
35+
.WithParameter("@certificationCode", certificationCode);
36+
37+
var iterator = client.GetContainer("AzureCertDB", "projectpromptvectors")
38+
.GetItemQueryIterator<CertificationProjectPromptDocument>(query);
39+
40+
41+
CertificationProjectPromptDocument certificationProjectPromptDocument = iterator.ReadNextAsync().Result.FirstOrDefault();
42+
43+
float[] projectPromptVector = certificationProjectPromptDocument.ProjectPromptVector;
44+
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);
49+
50+
using FeedIterator<CertificationService> resultSetIterator =
51+
client.GetContainer("AzureCertDB", "certvectors").GetItemQueryIterator<CertificationService>(queryDef);
52+
53+
string projectServices = "";
54+
string projectSkills = "";
55+
56+
while (resultSetIterator.HasMoreResults)
57+
{
58+
FeedResponse<CertificationService> feedResponse = resultSetIterator.ReadNextAsync().Result;
59+
foreach (var item in feedResponse)
60+
{
61+
projectServices += item.ServiceName + " ";
62+
projectSkills += item.SkillName + " ";
63+
64+
}
65+
}
66+
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;
73+
}
74+
}
75+
}

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/app.http

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,5 @@
11
# For more info on HTTP files go to https://aka.ms/vs/httpfile
2-
GET http://localhost:7204/api/Function1
2+
GET http://localhost:7204/api/Function1
3+
4+
###
5+
GET http://localhost:7204/api/GenerateProject?certificationCode=AZ-104

azure-project-generator/azure-project-generator.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
<PackageReference Include="Microsoft.Azure.Functions.Worker" Version="1.23.0" />
2222
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.CosmosDB" Version="4.10.0" />
2323
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Http" Version="3.2.0" />
24+
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore" Version="1.2.1" />
2425
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Storage.Blobs" Version="6.6.0" />
2526
<PackageReference Include="Microsoft.Azure.Functions.Worker.Sdk" Version="1.17.4" />
2627
<PackageReference Include="Microsoft.ApplicationInsights.WorkerService" Version="2.22.0" />

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)