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

Vivek Garudi

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

  • Download the content from GitHub repo

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.

0 comments

Discussion is closed.

Feedback usabilla icon