generated from NicolaiRuckel/python-template
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Start implementing dependency validator
- Loading branch information
Sebastian Simon
committed
Sep 23, 2024
1 parent
e7ad256
commit cd2aabb
Showing
5 changed files
with
139 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,42 @@ | ||
from dataclasses import dataclass | ||
from typing import Optional | ||
from cfgnet.linker.link import Link | ||
|
||
@dataclass | ||
class Dependency: | ||
project: str | ||
option_name: str | ||
option_file: str | ||
option_value: str | ||
option_type: str | ||
option_technology: str | ||
dependent_option_name: str | ||
dependent_option_value: str | ||
dependent_option_type: str | ||
dependent_option_file: str | ||
dependent_option_technology: str | ||
|
||
|
||
def is_test_file(abs_file_path) -> bool: | ||
"""Check if a given file is a test file.""" | ||
test_indicators = ["/tests", "test", "tests"] | ||
return any(indicator in abs_file_path for indicator in test_indicators) | ||
|
||
|
||
def transform(link: Link) -> Dependency: | ||
"""Transform a link into a dependency.""" | ||
dependency = Dependency( | ||
project=link.artifact_a.parent.name, | ||
option_name=link.node_a.get_options(), | ||
option_value=link.node_a.name, | ||
option_file=link.artifact_a.rel_file_path, | ||
option_type=link.node_a.config_type, | ||
option_technology=link.artifact_a.concept_name, | ||
dependent_option_name=link.node_b.get_options(), | ||
dependent_option_value=link.node_b.name, | ||
dependent_option_file=link.artifact_b.rel_file_path, | ||
dependent_option_type=link.node_b.config_type, | ||
dependent_option_technology=link.artifact_b.concept_name, | ||
) | ||
|
||
return dependency |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
from dataclasses import dataclass | ||
from llama_index.core import PromptTemplate | ||
|
||
@dataclass | ||
class Templates: | ||
system: PromptTemplate = PromptTemplate( | ||
"You are a full-stack expert in validating intra-technology and cross-technology configuration dependencies.\n" | ||
"You will be presented with configuration options found in the software project '{project}'.\n\n" | ||
"Your task is to determine whether the given configuration options actually depend on each other based on value-equality.\n\n" | ||
"{dependency_str}\n\n" | ||
"A value-equality dependency is present if two configuration options must have identical values in order to function correctly.\n" | ||
"Inconsistencies in these configuration values can lead to configuration errors.\n" | ||
"Importantly, configuration options may have equal values by accident, meaning that there is no actual dependency, but it just happens that they have equal values.\n" | ||
"If the values of configuration options are identical merely to ensure consistency within a software project, the options are not considered dependent." | ||
) | ||
task: PromptTemplate = PromptTemplate( | ||
"Carefully evaluate whether configuration option {nameA} of type {typeA} with value {valueA} in {fileA} of technology {technologyA} " | ||
"depends on configuration option {nameB} of type {typeB} with value {valueB} in {fileB} of technology {technologyB} or vice versa." | ||
) | ||
format: PromptTemplate = PromptTemplate( | ||
"Respond in a JSON format as shown below:\n" | ||
"{{\n" | ||
"\t“rationale”: string, // Provide a concise explanation of whether and why the configuration options depend on each other due to value-equality.\n" | ||
"\t“isDependency”: boolean // True if a dependency exists, or False otherwise.\n" | ||
"}}" | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
import os | ||
import backoff | ||
import logging | ||
import json | ||
from openai import OpenAI, RateLimitError, APIError, APIConnectionError, Timeout | ||
from typing import List | ||
from cfgnet.validator.prompts import Templates | ||
from cfgnet.conflicts.conflict import Conflict | ||
from cfgnet.utility.util import transform | ||
|
||
|
||
class Validator: | ||
def __init__(self) -> None: | ||
self.model_name= os.getenv("MODEL_NAME", default="gpt-4o-mini-2024-07-18") | ||
self.temperature = os.getenv("TEMPERATURE", default=0.4) | ||
self.max_tokens = os.getenv("TEMPERATURE", default=250) | ||
self.templates = Templates() | ||
|
||
@backoff.on_exception(backoff.expo, (RateLimitError, APIError, APIConnectionError, Timeout, Exception), max_tries=5) | ||
def generate(self, messages: List) -> str: | ||
client = OpenAI(api_key=os.getenv("OPENAI_KEY")) | ||
|
||
response = client.chat.completions.create( | ||
model=self.model_name, | ||
messages=messages, | ||
temperature=self.temperature, | ||
response_format={"type": "json_object"}, | ||
max_tokens=self.max_tokens | ||
) | ||
|
||
response_content = response.choices[0].message.content | ||
|
||
if not response or len(response_content.strip()) == 0: | ||
logging.eror("Response content was empty.") | ||
|
||
return response_content | ||
|
||
def validate(self, conflict: Conflict) -> bool: | ||
|
||
dependency = transform(link=conflict.link) | ||
|
||
system_prompt = self.templates.system.format(project=dependency.project) | ||
format_str = self.templates.format.format() | ||
task_prompt = self.templates.task.format( | ||
nameA=dependency.option_name, | ||
typeA=dependency.option_type, | ||
valueA=dependency.option_value, | ||
fileA=dependency.option_file, | ||
technologyA=dependency.option_technology, | ||
nameB=dependency.dependent_option_name, | ||
typeB=dependency.dependent_option_type, | ||
valueB=dependency.dependent_option_value, | ||
fileB=dependency.dependent_option_file, | ||
technologyB=dependency.dependent_option_technology, | ||
) | ||
|
||
user_prompt = f"{task_prompt}\n\n{format_str}" | ||
|
||
messages = [ | ||
{"role": "system", "content": system_prompt}, | ||
{"role": "user", "content": user_prompt} | ||
] | ||
|
||
# TODO: Add multi-aggregation | ||
response = self.generate(messages=messages) | ||
|
||
|
||
|
||
dependency | ||
|
||
|
||
|