promptflow
415 строк · 13.2 Кб
1import subprocess2from pathlib import Path3import hashlib4from jinja2 import Environment, FileSystemLoader, Template5from .telemetry_obj import Telemetry6
7
8class Step:9"""10StepType in workflow
11"""
12
13Environment = None14
15@staticmethod16def init_jinja_loader() -> Environment:17jinja_folder_path = (18Path(ReadmeStepsManage.git_base_dir())19/ "scripts"20/ "readme"21/ "ghactions_driver"22/ "workflow_steps"23)24Step.Environment = Environment(25loader=FileSystemLoader(jinja_folder_path.resolve())26)27
28def __init__(self, name: str) -> None:29self.workflow_name = name30
31def get_workflow_step(self) -> str:32# virtual method for override33return ""34
35@staticmethod36def get_workflow_template(step_file_name: str) -> Template:37# virtual method for override38if Step.Environment is None:39Step.init_jinja_loader()40template = Step.Environment.get_template(step_file_name)41return template42
43
44class AzureLoginStep(Step):45def __init__(self) -> None:46Step.__init__(self, "Azure Login")47
48def get_workflow_step(self) -> str:49template = Step.get_workflow_template("step_azure_login.yml.jinja2")50return template.render(51{52"step_name": self.workflow_name,53}54)55
56
57class LoginAgainStep(Step):58def __init__(self) -> None:59Step.__init__(self, "Azure Login Again")60
61def get_workflow_step(self) -> str:62template = Step.get_workflow_template("step_login_again.yml.jinja2")63return template.render(64{65"step_name": self.workflow_name,66}67)68
69
70class InstallDependenciesStep(Step):71def __init__(self) -> None:72Step.__init__(self, "Prepare requirements")73
74def get_workflow_step(self) -> str:75template = Step.get_workflow_template("step_install_deps.yml.jinja2")76return template.render(77{78"step_name": self.workflow_name,79"working_dir": ReadmeSteps.working_dir,80}81)82
83
84class InstallDevDependenciesStep(Step):85def __init__(self) -> None:86Step.__init__(self, "Prepare dev requirements")87
88def get_workflow_step(self) -> str:89template = Step.get_workflow_template("step_install_dev_deps.yml.jinja2")90return template.render(91{92"step_name": self.workflow_name,93"working_dir": ReadmeSteps.working_dir,94}95)96
97
98class CreateAoaiFromYaml(Step):99def __init__(self, yaml_name: str) -> None:100Step.__init__(self, "Create AOAI Connection from YAML")101self.yaml_name = yaml_name102
103def get_workflow_step(self) -> str:104template = Step.get_workflow_template("step_yml_create_aoai.yml.jinja2")105return template.render(106{107"step_name": self.workflow_name,108"yaml_name": self.yaml_name,109}110)111
112
113class ExtractStepsAndRun(Step):114def __init__(self) -> None:115Step.__init__(self, f"Extract Steps {ReadmeSteps.readme_name}")116
117def get_workflow_step(self) -> str:118template = Step.get_workflow_template("step_extract_steps_and_run.yml.jinja2")119return template.render(120{121"step_name": self.workflow_name,122"working_dir": ReadmeSteps.working_dir,123"readme_name": ReadmeSteps.readme_name,124}125)126
127
128class ExtractStepsAndRunGPTFour(Step):129def __init__(self) -> None:130Step.__init__(self, f"Extract Steps {ReadmeSteps.readme_name}")131
132def get_workflow_step(self) -> str:133template = Step.get_workflow_template(134"step_extract_steps_and_run_gpt4.yml.jinja2"135)136return template.render(137{138"step_name": self.workflow_name,139"working_dir": ReadmeSteps.working_dir,140"readme_name": ReadmeSteps.readme_name,141}142)143
144
145class ExecuteCommand(Step):146def __init__(self) -> None:147Step.__init__(self, "Execute Command")148
149def get_workflow_step(self) -> str:150template = Step.get_workflow_template(151"step_execute_command.yml.jinja2"152)153return template.render(154{155"step_name": self.workflow_name,156"working_dir": ReadmeSteps.working_dir,157}158)159
160
161class CreateEnv(Step):162def __init__(self) -> None:163Step.__init__(self, "Refine .env file")164
165def get_workflow_step(self) -> str:166template = Step.get_workflow_template("step_create_env.yml.jinja2")167content = template.render(168{"step_name": self.workflow_name, "working_dir": ReadmeSteps.working_dir}169)170return content171
172
173class CreateEnvGPTFour(Step):174def __init__(self) -> None:175Step.__init__(self, "Refine .env file")176
177def get_workflow_step(self) -> str:178template = Step.get_workflow_template("step_create_env_gpt4.yml.jinja2")179content = template.render(180{"step_name": self.workflow_name, "working_dir": ReadmeSteps.working_dir}181)182return content183
184
185class CreateAoaiFromEnv(Step):186def __init__(self, connection_name: str) -> None:187Step.__init__(self, "Create AOAI Connection from ENV file")188self.connection_name = connection_name189
190def get_workflow_step(self) -> str:191template = Step.get_workflow_template("step_env_create_aoai.yml.jinja2")192content = template.render(193{194"step_name": self.workflow_name,195"working_dir": ReadmeSteps.working_dir,196"connection_name": self.connection_name,197}198)199return content200
201
202class CreateRunYaml(Step):203def __init__(self) -> None:204Step.__init__(self, "Create run.yml")205
206def get_workflow_step(self) -> str:207template = Step.get_workflow_template("step_create_run_yml.yml.jinja2")208content = template.render(209{"step_name": self.workflow_name, "working_dir": ReadmeSteps.working_dir}210)211return content212
213
214class ReadmeSteps:215"""216Static class to record steps, to be filled in workflow templates and Readme
217"""
218
219step_array = [] # Record steps220readme_name = "" # Record readme name221working_dir = "" # the working directory of flow, relative to git_base_dir222template = "" # Select a base template under workflow_templates folder223workflow = "" # Target workflow name to be generated224
225@staticmethod226def remember_step(step: Step) -> Step:227ReadmeSteps.step_array.append(step)228return step229
230@staticmethod231def get_length() -> int:232return len(ReadmeSteps.step_array)233
234# region steps235@staticmethod236def create_env() -> Step:237return ReadmeSteps.remember_step(CreateEnv())238
239@staticmethod240def create_env_gpt4() -> Step:241return ReadmeSteps.remember_step(CreateEnvGPTFour())242
243@staticmethod244def yml_create_aoai(yaml_name: str) -> Step:245return ReadmeSteps.remember_step(CreateAoaiFromYaml(yaml_name=yaml_name))246
247@staticmethod248def env_create_aoai(connection_name: str) -> Step:249return ReadmeSteps.remember_step(250CreateAoaiFromEnv(connection_name=connection_name)251)252
253@staticmethod254def azure_login() -> Step:255return ReadmeSteps.remember_step(AzureLoginStep())256
257@staticmethod258def login_again() -> Step:259return ReadmeSteps.remember_step(LoginAgainStep())260
261@staticmethod262def install_dependencies() -> Step:263return ReadmeSteps.remember_step(InstallDependenciesStep())264
265@staticmethod266def install_dev_dependencies() -> Step:267return ReadmeSteps.remember_step(InstallDevDependenciesStep())268
269@staticmethod270def create_run_yaml() -> Step:271return ReadmeSteps.remember_step(CreateRunYaml())272
273@staticmethod274def extract_steps_and_run() -> Step:275return ReadmeSteps.remember_step(ExtractStepsAndRun())276
277@staticmethod278def extract_steps_and_run_gpt_four() -> Step:279return ReadmeSteps.remember_step(ExtractStepsAndRunGPTFour())280
281@staticmethod282def execute_command() -> Step:283return ReadmeSteps.remember_step(ExecuteCommand())284
285# endregion steps286
287@staticmethod288def setup_target(289working_dir: str, template: str, target: str, readme_name: str290) -> str:291"""292Used at the very head of jinja template to indicate basic information
293"""
294ReadmeSteps.working_dir = working_dir295ReadmeSteps.template = template296ReadmeSteps.workflow = target297ReadmeSteps.step_array = []298ReadmeSteps.readme_name = readme_name299return ""300
301@staticmethod302def cleanup() -> None:303ReadmeSteps.working_dir = ""304ReadmeSteps.template = ""305ReadmeSteps.workflow = ""306ReadmeSteps.step_array = []307
308
309class ReadmeStepsManage:310"""311# Static methods for manage all readme steps
312"""
313
314repo_base_dir = ""315
316@staticmethod317def git_base_dir() -> str:318"""319Get the base directory of the git repo
320"""
321if ReadmeStepsManage.repo_base_dir == "":322try:323ReadmeStepsManage.repo_base_dir = (324subprocess.check_output(["git", "rev-parse", "--show-toplevel"])325.decode("utf-8")326.strip()327)328raise Exception("Not in git repo")329except Exception:330ReadmeStepsManage.repo_base_dir = Path(__file__).parent.parent.parent.parent.resolve()331print(ReadmeStepsManage.repo_base_dir)332return ReadmeStepsManage.repo_base_dir333
334@staticmethod335def write_workflow(336workflow_name: str, pipeline_name: str, output_telemetry=Telemetry()337) -> None:338# Schedule notebooks at different times to reduce maximum quota usage.339name_hash = int(hashlib.sha512(workflow_name.encode()).hexdigest(), 16)340schedule_minute = name_hash % 60341schedule_hour = (name_hash // 60) % 4 + 19 # 19-22 UTC342
343if "tutorials" in workflow_name:344# markdown filename has some exceptions, special handle here345if "chat_with_pdf" in workflow_name:346readme_name = "chat-with-pdf.md"347elif (348"fine_tuning_evaluation_promptflow_quality_improvement" in workflow_name349):350readme_name = "promptflow-quality-improvement.md"351else:352readme_name = "README.md"353readme_path = (354Path(ReadmeStepsManage.git_base_dir())355/ ReadmeSteps.working_dir356/ readme_name357)358# local import to avoid circular import359from .resource_resolver import resolve_tutorial_resource360
361path_filter = resolve_tutorial_resource(362workflow_name, readme_path.resolve(), output_telemetry363)364else:365if (366"flow_with_additional_includes" in workflow_name367or "flow_with_symlinks" in workflow_name368):369# these two flows have dependencies on flow web-classification370# so corresponding workflows should also listen to changes in web-classification371path_filter = (372f"[ {ReadmeSteps.working_dir}/**, "373+ "examples/*requirements.txt, "374+ "examples/flows/standard/web-classification/**, "375+ f".github/workflows/{workflow_name}.yml ]"376)377else:378path_filter = (379f"[ {ReadmeSteps.working_dir}/**, "380+ "examples/*requirements.txt, "381+ f".github/workflows/{workflow_name}.yml ]"382)383replacements = {384"steps": ReadmeSteps.step_array,385"workflow_name": workflow_name,386"ci_name": pipeline_name,387"path_filter": path_filter,388"crontab": f"{schedule_minute} {schedule_hour} * * *",389"crontab_comment": f"Every day starting at {schedule_hour - 16}:{schedule_minute} BJT",390}391workflow_template_path = (392Path(ReadmeStepsManage.git_base_dir())393/ "scripts"394/ "readme"395/ "ghactions_driver"396/ "workflow_templates"397)398target_path = (399Path(ReadmeStepsManage.git_base_dir())400/ ".github"401/ "workflows"402/ f"{workflow_name}.yml"403)404template = Environment(405loader=FileSystemLoader(workflow_template_path.resolve())406).get_template(ReadmeSteps.template)407content = template.render(replacements)408with open(target_path.resolve(), "w", encoding="utf-8") as f:409f.write(content)410print(f"Write readme workflow: {target_path.resolve()}")411output_telemetry.workflow_name = workflow_name412output_telemetry.target_path = target_path413output_telemetry.readme_folder = ReadmeSteps.working_dir414output_telemetry.readme_name = ReadmeSteps.readme_name415output_telemetry.path_filter = path_filter416