September 24th, 2023

Revolutionizing Requirement Gathering: Azure DevOps Meets Azure OpenAI using Semantic kernel

Vivek Garudi
SR PROGRAM MGR

This blog is a deep dive into the future of requirement gathering. This blog explores how Azure DevOps and Azure OpenAI are joining forces to transform the way we manage project requirements. From automated requirement generation to intelligent analysis, learn how these powerful tools are reshaping the landscape of project management. Stay tuned for an enlightening journey into the world of AI-powered requirement gathering!

Setting up environment

Pre-requisite

  • Visual studio code

    Please install below extension

    – Jupyter (Publisher- Microsoft)

    – Python (Publisher- Microsoft)

    – Pylance (Publisher- Microsoft)

    – Semantic Kernel Tools (Publisher- Microsoft)

  • Python

  Please install below python packages

    – PIP

    – Semantic-kernel

Define the Semantic Function to generate feature description-

Now that you have below mentioned folder structure.

Image image1

Create semantic function for generating Feature description.

The first step is to define a semantic function that can interpret the input string and map it to a specific action. In our case, the action is to generate feature description from title. The function could look something like this:

  1. Create folder structure    

    • Create /plugins folder    
    • Create folder for semantic plugins inside Plugins folder, in this case its “AzureDevops”. (For more details on plugins)    

    • Create Folder for semantic function inside the skills folder ie ‘/plugin/AzureDevops’, in this case “FeatureDescription” (For more details on functions)

  2. Define semantic function    

    • Once we have folder structure in place lets define the function by having

        ‘config.json’ with below JSON content for more details on content refer here.

{ "schema": 1, "description": "get standard feature title and description", "type": "completion", "completion": { "max_tokens": 500, "temperature": 0.0, "top_p": 0.0, "presence_penalty": 0.0, "frequency_penalty": 0.0 }, "input": { "parameters": [ { "name": "input", "description": "The feature name.", "defaultValue": "" } ] } } 

In above file, we are defining semantic function which accept ‘input’ parameter to perform “get standard feature title and description” as mentioned in Description section.

    – Now, let’s put the single shot prompt for our semantic function in ‘skprompt.txt’. where ‘{{input}}’ where our input ask would be replaced.

 Create feature title and description for {{$input}} in below format Feature Title:"[Prodive a short title for the feature]" Description: "[Provide a more detailed description of the feature's purpose, the problem it addresses, and its significance to the product or project.] User Needs- [Outline the specific user needs or pain points that this feature aims to address.] Functional Requirements:- - [Requirement 1] - [Requirement 2] - [Requirement 3] - ... Non-Functional Requirements:- - [Requirement 1] - [Requirement 2] - [Requirement 3] - ... Feature Scope: [Indicates the minimum capabilities that feature should address. Agreed upon between Engineering Leads and Product Mangers] " 

Execute above semantic function in action.

  • Rename “.env.example’ as ‘.env’ and update the parameters with actual values

  • Open notebook “Create-Azure-Devops-feature-from-requirement-text” in visual studio code and follow the steps mentioned to test

Step 1 Install all python libraries

!python -m pip install semantic-kernel==0.3.10.dev0 !python -m pip install azure-devops 

Step 2 Import Packages required to prepare a semantic kernel instance first.

import os from dotenv import dotenv_values import semantic_kernel as sk from semantic_kernel import ContextVariables, Kernel # Context to store variables and Kernel to interact with the kernel from semantic_kernel.connectors.ai.open_ai import AzureChatCompletion, OpenAIChatCompletion # AI services from semantic_kernel.planning.sequential_planner import SequentialPlanner # Planner kernel = sk.Kernel() # Create a kernel instance kernel1 = sk.Kernel() #create another kernel instance for not having semanitc function in the same kernel useAzureOpenAI = True # Configure AI service used by the kernel if useAzureOpenAI: deployment, api_key, endpoint = sk.azure_openai_settings_from_dot_env() kernel.add_chat_service("chat_completion", AzureChatCompletion(deployment, endpoint, api_key)) kernel1.add_chat_service("chat_completion", AzureChatCompletion(deployment, endpoint, api_key)) else: api_key, org_id = sk.openai_settings_from_dot_env() kernel.add_chat_service("chat-gpt", OpenAIChatCompletion("gpt-3.5-turbo", api_key, org_id)) 

Step 3 Importing skills and function from folder

# note: using skills from the samples folder plugins_directory = "./plugins" # Import the semantic functions DevFunctions=kernel1.import_semantic_skill_from_directory(plugins_directory, "AzureDevOps") FDesFunction = DevFunctions["FeatureDescription"] 

Step 4 calling the semantic function with feature title to generate feature description based on predefined template

resultFD = FDesFunction("Azure Resource Group Configuration Export and Infrastructure as Code (IAC) Generation") print(resultFD) 

Create native function to create features in Azure DevOps

 – Create file “native_function.py” under “AzureDevOps” or download the file from repo.

 – Copy the code base and update Azure Devops parameter. you can access this as context parameter but for simplicity of this exercise, we kept it as hardcoded. Please find below code flow

        – Importing python packages

        – Defining class ‘feature’ and native function as “create” under “@sk_function”.

        – Call semantic function to generate feature description.

        – Use this description to create Azure DevOps feature.

from semantic_kernel.skill_definition import ( sk_function, sk_function_context_parameter, ) from semantic_kernel.orchestration.sk_context import SKContext from azure.devops.v7_1.py_pi_api import JsonPatchOperation from azure.devops.connection import Connection from msrest.authentication import BasicAuthentication import base64 from semantic_kernel import ContextVariables, Kernel import re class feature: def __init__(self, kernel: Kernel): self._kernel = kernel _function( description="create a Azure DevOps feature with description", name="create", ) _function_context_parameter( name="title", description="the tile of the feature", ) _function_context_parameter( name="description", description="Description of the feature", ) async def create_feature(self, context: SKContext) -> str: feature_title = context["title"] get_feature = self._kernel.skills.get_function("AzureDevOps", "FeatureDescription") fdetails = get_feature(feature_title) # Define a regular expression pattern to match the feature title pattern = r"Feature Title:\s+(.+)" # Search for the pattern in the input string match = re.search(pattern, str(fdetails)) # Check if a match was found if match: feature_title = match.group(1) # Define a regular expression pattern to match the feature description # Split the string into lines lines = str(fdetails).split('\n') lines = [line for index, line in enumerate(lines) if index not in [0]] description = '\n'.join(lines) jsonPatchList = [] #description=context["description"] targetOrganizationName= "XXX" targetProjectName= "test" targetOrganizationPAT = "XXXXXX" workItemCsvFile= "abc" teamName = "test Team" areaName = teamName iterationName ="Sprint 1" targetOrganizationUri='https://dev.azure.com/'+targetOrganizationName credentials = BasicAuthentication('', targetOrganizationPAT) connection = Connection(base_url=targetOrganizationUri, creds=credentials) userToken = "" + ":" + targetOrganizationPAT base64UserToken = base64.b64encode(userToken.encode()).decode() headers = {'Authorization': 'Basic' + base64UserToken} core_client = connection.clients.get_core_client() targetProjectId = core_client.get_project(targetProjectName).id workItemObjects = [ { 'op': 'add', 'path': '/fields/System.WorkItemType', 'value': "Feature" }, { 'op': 'add', 'path': '/fields/System.Title', 'value': feature_title }, { 'op': 'add', 'path': '/fields/System.State', 'value': "New" }, { 'op': 'add', 'path': '/fields/System.Description', 'value': description }, { 'op': 'add', 'path': '/fields/Microsoft.VSTS.Common.AcceptanceCriteria', 'value': "acceptance criteria" }, { 'op': 'add', 'path': '/fields/System.IterationPath', 'value': targetProjectName+"\\"+iterationName } ] jsonPatchList = JsonPatchOperation(workItemObjects) work_client = connection.clients.get_work_item_tracking_client() try: WorkItemCreation = work_client.create_work_item(jsonPatchList.from_, targetProjectName, "Feature") except Exception as e: return feature_title+"Feature created unsuccessfully" return feature_title+" Feature created successfully" 

 

Let’s execute native function

Let’s go back to notebook.

Step 5 Importing native function

from plugins.AzureDevops.native_function import feature math_plugin = kernel.import_skill(feature(kernel1), skill_name="AzureDevOps") variables = ContextVariables() 

  Step 6 Executing native function by putting natural language queries in title field

variables["title"] = "creating a nice pipelines" variables["description"] = "test" result = await kernel.run_async( math_plugin["create"], input_vars=variables ) print(result) 

Use of Sequential planner to dynamical create N number of features.

Step 7 Initiate sequential planner with semantic kernel

from plugins.AzureDevops.native_function import feature planner = SequentialPlanner(kernel) # Import the native functions AzDevplugin = kernel.import_skill(feature(kernel1), skill_name="AzureDevOps") ask = "create two Azure DevOps features for one with title creating user and one with creating work items with standard feature title and description" plan = await planner.create_plan_async(goal=ask) for step in plan._steps: print(step.description, ":", step._state.__dict__) 

This would generate a plan to meet the goal which is above case is “create two Azure DevOps features for one with title creating user and one with creating work items with standard feature title and description” using available function in kernel.

Step 8 once the plan is created, we can use this plan and execute it to create multiple features.

<br />print("Plan results:") result = await plan.invoke_async(ask) for step in plan._steps: print(step.description, ":", step._state.__dict__) 

This will create two features one for user and one for work item. Using this block, you can create a semantic function-based solution that can interpret natural language requirement document or transcript of reequipment call and use it to create features in azure DevOps. You can increase the accuracy of this solution by brining multi-shot prompt and historical data using collections.

Category
DevOps

Author

Vivek Garudi
SR PROGRAM MGR

0 comments

Discussion is closed.