chore: import upstream snapshot with attribution
tools_continuous_delivery / Private PyPI non-main branch release (push) Has been skipped
tools_continuous_delivery / Private PyPI main branch release (push) Failing after 2m42s
Publish Promptflow Doc / Build (push) Has been cancelled
Publish Promptflow Doc / Deploy (push) Has been cancelled
Flake8 Lint / flake8 (push) Has been cancelled
Spell check CI / Spell_Check (push) Has been cancelled
tools_continuous_delivery / Private PyPI non-main branch release (push) Has been skipped
tools_continuous_delivery / Private PyPI main branch release (push) Failing after 2m42s
Publish Promptflow Doc / Build (push) Has been cancelled
Publish Promptflow Doc / Deploy (push) Has been cancelled
Flake8 Lint / flake8 (push) Has been cancelled
Spell check CI / Spell_Check (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
# Readme Workflow Generator
|
||||
|
||||
These tools is used to generate workflows from README.md and python notebook files in the [examples](../../examples/) folder.
|
||||
* Generated workflows will be placed in [.github/workflows/samples_*](../../.github/workflows/) folder.
|
||||
* The script will also generate a new explanation [README.md](../../examples/README.md) for all the examples.
|
||||
|
||||
## 1. Install dependencies
|
||||
|
||||
```bash
|
||||
pip install -r ../../examples/requirements.txt
|
||||
pip install -r ../../examples/dev_requirements.txt
|
||||
```
|
||||
|
||||
## 2. Generate workflows
|
||||
|
||||
### (Option 1) One Step Generation
|
||||
|
||||
At the **root** of the repository, run the following command:
|
||||
|
||||
```bash
|
||||
python scripts/readme/readme.py
|
||||
```
|
||||
|
||||
### (Option 2) Step by Step Generation
|
||||
|
||||
At the **root** of the repository, run the following command:
|
||||
|
||||
```bash
|
||||
# Generate workflow from README.md inside examples folder
|
||||
python scripts/readme/readme_generator.py -g "examples/**/*.ipynb"
|
||||
|
||||
# Generate workflow from python notebook inside examples folder
|
||||
python scripts/readme/workflow_generator.py -g "examples/flows/**/README.md"
|
||||
```
|
||||
|
||||
Multiple inputs are supported.
|
||||
|
||||
## 3. Options to control generations of examples [README.md](../../examples/README.md)
|
||||
|
||||
### 3.1 Notebook Workflow Generation
|
||||
|
||||
* Each workflow contains metadata area, set `.metadata.description` area will display this message in the corresponding cell in [README.md](../../examples/README.md) file.
|
||||
* When set `.metadata.no_readme_generation` to value `true`, the script will stop generating for this notebook.
|
||||
|
||||
### 3.2 README.md Workflow Generation
|
||||
|
||||
* For README.md files, only `bash` cells will be collected and converted to workflow. No cells will produce no workflow.
|
||||
* Readme descriptions are simply collected from the first sentence in the README.md file just below the title. The script will collect words before the first **.** of the fist paragraph. Multi-line sentence is also supported
|
||||
* A supported description sentence: `This is a sample workflow for testing.`
|
||||
* A not supported description sentence: `Please check www.microsoft.com for more details.`
|
||||
@@ -0,0 +1,60 @@
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
from jinja2 import Environment, FileSystemLoader
|
||||
|
||||
from ghactions_driver.readme_parse import readme_parser
|
||||
from ghactions_driver.readme_step import ReadmeStepsManage
|
||||
|
||||
|
||||
def comment_pytest(full_text: str):
|
||||
# separate full_text into lines, group them by start with pytest or not
|
||||
full_text_lines = full_text.split("\n")
|
||||
full_text_pytest = []
|
||||
full_text_no_pytest = []
|
||||
for line in full_text_lines:
|
||||
if line.strip().startswith("python -m unittest"):
|
||||
full_text_pytest.append(line)
|
||||
else:
|
||||
full_text_no_pytest.append(line)
|
||||
return "\n".join(full_text_no_pytest), "\n".join(full_text_pytest)
|
||||
|
||||
|
||||
def write_readme_shell(readme_path: str, output_folder: str):
|
||||
full_text = readme_parser(readme_path)
|
||||
full_text, full_text_pytest = comment_pytest(full_text)
|
||||
Path(ReadmeStepsManage.git_base_dir())
|
||||
bash_script_path = (
|
||||
Path(ReadmeStepsManage.git_base_dir()) / output_folder / "bash_script.sh"
|
||||
)
|
||||
bash_script_backup = (
|
||||
Path(ReadmeStepsManage.git_base_dir()) / output_folder / "bash_script_pytest.sh"
|
||||
)
|
||||
template_env = Environment(
|
||||
loader=FileSystemLoader(
|
||||
Path(ReadmeStepsManage.git_base_dir())
|
||||
/ "scripts/readme/ghactions_driver/bash_script"
|
||||
)
|
||||
)
|
||||
bash_script_template = template_env.get_template("bash_script.sh.jinja2")
|
||||
with open(bash_script_path, "w") as f:
|
||||
f.write(bash_script_template.render({"command": full_text}))
|
||||
if full_text_pytest != "":
|
||||
with open(bash_script_backup, "w") as f:
|
||||
f.write(bash_script_template.render({"command": full_text_pytest}))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# setup argparse
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument(
|
||||
"-f",
|
||||
"--readme-file",
|
||||
help="Input README.md example 'examples/flows/standard/basic/README.md'",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-o",
|
||||
"--output-folder",
|
||||
help="Output folder for bash_script.sh example 'examples/flows/standard/basic/'",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
write_readme_shell(args.readme_file, args.output_folder)
|
||||
@@ -0,0 +1,3 @@
|
||||
#!/usr/bin/env bash
|
||||
set -xe
|
||||
{{ command }}
|
||||
@@ -0,0 +1,47 @@
|
||||
import io
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
import panflute
|
||||
import pypandoc
|
||||
|
||||
from .readme_step import ReadmeStepsManage
|
||||
|
||||
|
||||
def strip_comments(code):
|
||||
code = str(code)
|
||||
code = re.sub(r"(?m)^ *#.*\n?", "", code) # remove comments
|
||||
splits = [ll.rstrip() for ll in code.splitlines() if ll.strip()] # remove empty
|
||||
splits_no_interactive = [
|
||||
split
|
||||
for split in splits
|
||||
if "interactive" not in split
|
||||
and "pf flow serve" not in split
|
||||
and "pf connection delete" not in split
|
||||
] # remove --interactive and pf flow serve and pf export docker
|
||||
text = "\n".join([ll.rstrip() for ll in splits_no_interactive])
|
||||
# replacements
|
||||
text = text.replace("<your_api_key>", "$aoai_api_key")
|
||||
text = text.replace("<your_api_base>", "$aoai_api_endpoint")
|
||||
text = text.replace("<your_subscription_id>", "$test_workspace_sub_id")
|
||||
text = text.replace("<your_resource_group_name>", "$test_workspace_rg")
|
||||
text = text.replace("<your_workspace_name>", "$test_workspace_name")
|
||||
return text
|
||||
|
||||
|
||||
def prepare(doc):
|
||||
doc.full_text = ""
|
||||
|
||||
|
||||
def action(elem, doc):
|
||||
if isinstance(elem, panflute.CodeBlock) and "bash" in elem.classes:
|
||||
doc.full_text = "\n".join([doc.full_text, strip_comments(elem.text)])
|
||||
|
||||
|
||||
def readme_parser(filename: str):
|
||||
real_filename = Path(ReadmeStepsManage.git_base_dir()) / filename
|
||||
data = pypandoc.convert_file(str(real_filename), "json")
|
||||
f = io.StringIO(data)
|
||||
doc = panflute.load(f)
|
||||
panflute.run_filter(action, prepare, doc=doc)
|
||||
return doc.full_text
|
||||
@@ -0,0 +1,415 @@
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
import hashlib
|
||||
from jinja2 import Environment, FileSystemLoader, Template
|
||||
from .telemetry_obj import Telemetry
|
||||
|
||||
|
||||
class Step:
|
||||
"""
|
||||
StepType in workflow
|
||||
"""
|
||||
|
||||
Environment = None
|
||||
|
||||
@staticmethod
|
||||
def init_jinja_loader() -> Environment:
|
||||
jinja_folder_path = (
|
||||
Path(ReadmeStepsManage.git_base_dir())
|
||||
/ "scripts"
|
||||
/ "readme"
|
||||
/ "ghactions_driver"
|
||||
/ "workflow_steps"
|
||||
)
|
||||
Step.Environment = Environment(
|
||||
loader=FileSystemLoader(jinja_folder_path.resolve())
|
||||
)
|
||||
|
||||
def __init__(self, name: str) -> None:
|
||||
self.workflow_name = name
|
||||
|
||||
def get_workflow_step(self) -> str:
|
||||
# virtual method for override
|
||||
return ""
|
||||
|
||||
@staticmethod
|
||||
def get_workflow_template(step_file_name: str) -> Template:
|
||||
# virtual method for override
|
||||
if Step.Environment is None:
|
||||
Step.init_jinja_loader()
|
||||
template = Step.Environment.get_template(step_file_name)
|
||||
return template
|
||||
|
||||
|
||||
class AzureLoginStep(Step):
|
||||
def __init__(self) -> None:
|
||||
Step.__init__(self, "Azure Login")
|
||||
|
||||
def get_workflow_step(self) -> str:
|
||||
template = Step.get_workflow_template("step_azure_login.yml.jinja2")
|
||||
return template.render(
|
||||
{
|
||||
"step_name": self.workflow_name,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class LoginAgainStep(Step):
|
||||
def __init__(self) -> None:
|
||||
Step.__init__(self, "Azure Login Again")
|
||||
|
||||
def get_workflow_step(self) -> str:
|
||||
template = Step.get_workflow_template("step_login_again.yml.jinja2")
|
||||
return template.render(
|
||||
{
|
||||
"step_name": self.workflow_name,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class InstallDependenciesStep(Step):
|
||||
def __init__(self) -> None:
|
||||
Step.__init__(self, "Prepare requirements")
|
||||
|
||||
def get_workflow_step(self) -> str:
|
||||
template = Step.get_workflow_template("step_install_deps.yml.jinja2")
|
||||
return template.render(
|
||||
{
|
||||
"step_name": self.workflow_name,
|
||||
"working_dir": ReadmeSteps.working_dir,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class InstallDevDependenciesStep(Step):
|
||||
def __init__(self) -> None:
|
||||
Step.__init__(self, "Prepare dev requirements")
|
||||
|
||||
def get_workflow_step(self) -> str:
|
||||
template = Step.get_workflow_template("step_install_dev_deps.yml.jinja2")
|
||||
return template.render(
|
||||
{
|
||||
"step_name": self.workflow_name,
|
||||
"working_dir": ReadmeSteps.working_dir,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class CreateAoaiFromYaml(Step):
|
||||
def __init__(self, yaml_name: str) -> None:
|
||||
Step.__init__(self, "Create AOAI Connection from YAML")
|
||||
self.yaml_name = yaml_name
|
||||
|
||||
def get_workflow_step(self) -> str:
|
||||
template = Step.get_workflow_template("step_yml_create_aoai.yml.jinja2")
|
||||
return template.render(
|
||||
{
|
||||
"step_name": self.workflow_name,
|
||||
"yaml_name": self.yaml_name,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class ExtractStepsAndRun(Step):
|
||||
def __init__(self) -> None:
|
||||
Step.__init__(self, f"Extract Steps {ReadmeSteps.readme_name}")
|
||||
|
||||
def get_workflow_step(self) -> str:
|
||||
template = Step.get_workflow_template("step_extract_steps_and_run.yml.jinja2")
|
||||
return template.render(
|
||||
{
|
||||
"step_name": self.workflow_name,
|
||||
"working_dir": ReadmeSteps.working_dir,
|
||||
"readme_name": ReadmeSteps.readme_name,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class ExtractStepsAndRunGPTFour(Step):
|
||||
def __init__(self) -> None:
|
||||
Step.__init__(self, f"Extract Steps {ReadmeSteps.readme_name}")
|
||||
|
||||
def get_workflow_step(self) -> str:
|
||||
template = Step.get_workflow_template(
|
||||
"step_extract_steps_and_run_gpt4.yml.jinja2"
|
||||
)
|
||||
return template.render(
|
||||
{
|
||||
"step_name": self.workflow_name,
|
||||
"working_dir": ReadmeSteps.working_dir,
|
||||
"readme_name": ReadmeSteps.readme_name,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class ExecuteCommand(Step):
|
||||
def __init__(self) -> None:
|
||||
Step.__init__(self, "Execute Command")
|
||||
|
||||
def get_workflow_step(self) -> str:
|
||||
template = Step.get_workflow_template(
|
||||
"step_execute_command.yml.jinja2"
|
||||
)
|
||||
return template.render(
|
||||
{
|
||||
"step_name": self.workflow_name,
|
||||
"working_dir": ReadmeSteps.working_dir,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class CreateEnv(Step):
|
||||
def __init__(self) -> None:
|
||||
Step.__init__(self, "Refine .env file")
|
||||
|
||||
def get_workflow_step(self) -> str:
|
||||
template = Step.get_workflow_template("step_create_env.yml.jinja2")
|
||||
content = template.render(
|
||||
{"step_name": self.workflow_name, "working_dir": ReadmeSteps.working_dir}
|
||||
)
|
||||
return content
|
||||
|
||||
|
||||
class CreateEnvGPTFour(Step):
|
||||
def __init__(self) -> None:
|
||||
Step.__init__(self, "Refine .env file")
|
||||
|
||||
def get_workflow_step(self) -> str:
|
||||
template = Step.get_workflow_template("step_create_env_gpt4.yml.jinja2")
|
||||
content = template.render(
|
||||
{"step_name": self.workflow_name, "working_dir": ReadmeSteps.working_dir}
|
||||
)
|
||||
return content
|
||||
|
||||
|
||||
class CreateAoaiFromEnv(Step):
|
||||
def __init__(self, connection_name: str) -> None:
|
||||
Step.__init__(self, "Create AOAI Connection from ENV file")
|
||||
self.connection_name = connection_name
|
||||
|
||||
def get_workflow_step(self) -> str:
|
||||
template = Step.get_workflow_template("step_env_create_aoai.yml.jinja2")
|
||||
content = template.render(
|
||||
{
|
||||
"step_name": self.workflow_name,
|
||||
"working_dir": ReadmeSteps.working_dir,
|
||||
"connection_name": self.connection_name,
|
||||
}
|
||||
)
|
||||
return content
|
||||
|
||||
|
||||
class CreateRunYaml(Step):
|
||||
def __init__(self) -> None:
|
||||
Step.__init__(self, "Create run.yml")
|
||||
|
||||
def get_workflow_step(self) -> str:
|
||||
template = Step.get_workflow_template("step_create_run_yml.yml.jinja2")
|
||||
content = template.render(
|
||||
{"step_name": self.workflow_name, "working_dir": ReadmeSteps.working_dir}
|
||||
)
|
||||
return content
|
||||
|
||||
|
||||
class ReadmeSteps:
|
||||
"""
|
||||
Static class to record steps, to be filled in workflow templates and Readme
|
||||
"""
|
||||
|
||||
step_array = [] # Record steps
|
||||
readme_name = "" # Record readme name
|
||||
working_dir = "" # the working directory of flow, relative to git_base_dir
|
||||
template = "" # Select a base template under workflow_templates folder
|
||||
workflow = "" # Target workflow name to be generated
|
||||
|
||||
@staticmethod
|
||||
def remember_step(step: Step) -> Step:
|
||||
ReadmeSteps.step_array.append(step)
|
||||
return step
|
||||
|
||||
@staticmethod
|
||||
def get_length() -> int:
|
||||
return len(ReadmeSteps.step_array)
|
||||
|
||||
# region steps
|
||||
@staticmethod
|
||||
def create_env() -> Step:
|
||||
return ReadmeSteps.remember_step(CreateEnv())
|
||||
|
||||
@staticmethod
|
||||
def create_env_gpt4() -> Step:
|
||||
return ReadmeSteps.remember_step(CreateEnvGPTFour())
|
||||
|
||||
@staticmethod
|
||||
def yml_create_aoai(yaml_name: str) -> Step:
|
||||
return ReadmeSteps.remember_step(CreateAoaiFromYaml(yaml_name=yaml_name))
|
||||
|
||||
@staticmethod
|
||||
def env_create_aoai(connection_name: str) -> Step:
|
||||
return ReadmeSteps.remember_step(
|
||||
CreateAoaiFromEnv(connection_name=connection_name)
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def azure_login() -> Step:
|
||||
return ReadmeSteps.remember_step(AzureLoginStep())
|
||||
|
||||
@staticmethod
|
||||
def login_again() -> Step:
|
||||
return ReadmeSteps.remember_step(LoginAgainStep())
|
||||
|
||||
@staticmethod
|
||||
def install_dependencies() -> Step:
|
||||
return ReadmeSteps.remember_step(InstallDependenciesStep())
|
||||
|
||||
@staticmethod
|
||||
def install_dev_dependencies() -> Step:
|
||||
return ReadmeSteps.remember_step(InstallDevDependenciesStep())
|
||||
|
||||
@staticmethod
|
||||
def create_run_yaml() -> Step:
|
||||
return ReadmeSteps.remember_step(CreateRunYaml())
|
||||
|
||||
@staticmethod
|
||||
def extract_steps_and_run() -> Step:
|
||||
return ReadmeSteps.remember_step(ExtractStepsAndRun())
|
||||
|
||||
@staticmethod
|
||||
def extract_steps_and_run_gpt_four() -> Step:
|
||||
return ReadmeSteps.remember_step(ExtractStepsAndRunGPTFour())
|
||||
|
||||
@staticmethod
|
||||
def execute_command() -> Step:
|
||||
return ReadmeSteps.remember_step(ExecuteCommand())
|
||||
|
||||
# endregion steps
|
||||
|
||||
@staticmethod
|
||||
def setup_target(
|
||||
working_dir: str, template: str, target: str, readme_name: str
|
||||
) -> str:
|
||||
"""
|
||||
Used at the very head of jinja template to indicate basic information
|
||||
"""
|
||||
ReadmeSteps.working_dir = working_dir
|
||||
ReadmeSteps.template = template
|
||||
ReadmeSteps.workflow = target
|
||||
ReadmeSteps.step_array = []
|
||||
ReadmeSteps.readme_name = readme_name
|
||||
return ""
|
||||
|
||||
@staticmethod
|
||||
def cleanup() -> None:
|
||||
ReadmeSteps.working_dir = ""
|
||||
ReadmeSteps.template = ""
|
||||
ReadmeSteps.workflow = ""
|
||||
ReadmeSteps.step_array = []
|
||||
|
||||
|
||||
class ReadmeStepsManage:
|
||||
"""
|
||||
# Static methods for manage all readme steps
|
||||
"""
|
||||
|
||||
repo_base_dir = ""
|
||||
|
||||
@staticmethod
|
||||
def git_base_dir() -> str:
|
||||
"""
|
||||
Get the base directory of the git repo
|
||||
"""
|
||||
if ReadmeStepsManage.repo_base_dir == "":
|
||||
try:
|
||||
ReadmeStepsManage.repo_base_dir = (
|
||||
subprocess.check_output(["git", "rev-parse", "--show-toplevel"])
|
||||
.decode("utf-8")
|
||||
.strip()
|
||||
)
|
||||
raise Exception("Not in git repo")
|
||||
except Exception:
|
||||
ReadmeStepsManage.repo_base_dir = Path(__file__).parent.parent.parent.parent.resolve()
|
||||
print(ReadmeStepsManage.repo_base_dir)
|
||||
return ReadmeStepsManage.repo_base_dir
|
||||
|
||||
@staticmethod
|
||||
def write_workflow(
|
||||
workflow_name: str, pipeline_name: str, output_telemetry=Telemetry()
|
||||
) -> None:
|
||||
# Schedule notebooks at different times to reduce maximum quota usage.
|
||||
name_hash = int(hashlib.sha512(workflow_name.encode()).hexdigest(), 16)
|
||||
schedule_minute = name_hash % 60
|
||||
schedule_hour = (name_hash // 60) % 4 + 19 # 19-22 UTC
|
||||
|
||||
if "tutorials" in workflow_name:
|
||||
# markdown filename has some exceptions, special handle here
|
||||
if "chat_with_pdf" in workflow_name:
|
||||
readme_name = "chat-with-pdf.md"
|
||||
elif (
|
||||
"fine_tuning_evaluation_promptflow_quality_improvement" in workflow_name
|
||||
):
|
||||
readme_name = "promptflow-quality-improvement.md"
|
||||
else:
|
||||
readme_name = "README.md"
|
||||
readme_path = (
|
||||
Path(ReadmeStepsManage.git_base_dir())
|
||||
/ ReadmeSteps.working_dir
|
||||
/ readme_name
|
||||
)
|
||||
# local import to avoid circular import
|
||||
from .resource_resolver import resolve_tutorial_resource
|
||||
|
||||
path_filter = resolve_tutorial_resource(
|
||||
workflow_name, readme_path.resolve(), output_telemetry
|
||||
)
|
||||
else:
|
||||
if (
|
||||
"flow_with_additional_includes" in workflow_name
|
||||
or "flow_with_symlinks" in workflow_name
|
||||
):
|
||||
# these two flows have dependencies on flow web-classification
|
||||
# so corresponding workflows should also listen to changes in web-classification
|
||||
path_filter = (
|
||||
f"[ {ReadmeSteps.working_dir}/**, "
|
||||
+ "examples/*requirements.txt, "
|
||||
+ "examples/flows/standard/web-classification/**, "
|
||||
+ f".github/workflows/{workflow_name}.yml ]"
|
||||
)
|
||||
else:
|
||||
path_filter = (
|
||||
f"[ {ReadmeSteps.working_dir}/**, "
|
||||
+ "examples/*requirements.txt, "
|
||||
+ f".github/workflows/{workflow_name}.yml ]"
|
||||
)
|
||||
replacements = {
|
||||
"steps": ReadmeSteps.step_array,
|
||||
"workflow_name": workflow_name,
|
||||
"ci_name": pipeline_name,
|
||||
"path_filter": path_filter,
|
||||
"crontab": f"{schedule_minute} {schedule_hour} * * *",
|
||||
"crontab_comment": f"Every day starting at {schedule_hour - 16}:{schedule_minute} BJT",
|
||||
}
|
||||
workflow_template_path = (
|
||||
Path(ReadmeStepsManage.git_base_dir())
|
||||
/ "scripts"
|
||||
/ "readme"
|
||||
/ "ghactions_driver"
|
||||
/ "workflow_templates"
|
||||
)
|
||||
target_path = (
|
||||
Path(ReadmeStepsManage.git_base_dir())
|
||||
/ ".github"
|
||||
/ "workflows"
|
||||
/ f"{workflow_name}.yml"
|
||||
)
|
||||
template = Environment(
|
||||
loader=FileSystemLoader(workflow_template_path.resolve())
|
||||
).get_template(ReadmeSteps.template)
|
||||
content = template.render(replacements)
|
||||
with open(target_path.resolve(), "w", encoding="utf-8") as f:
|
||||
f.write(content)
|
||||
print(f"Write readme workflow: {target_path.resolve()}")
|
||||
output_telemetry.workflow_name = workflow_name
|
||||
output_telemetry.target_path = target_path
|
||||
output_telemetry.readme_folder = ReadmeSteps.working_dir
|
||||
output_telemetry.readme_name = ReadmeSteps.readme_name
|
||||
output_telemetry.path_filter = path_filter
|
||||
@@ -0,0 +1,116 @@
|
||||
# Promptflow examples
|
||||
|
||||
[](https://github.com/psf/black)
|
||||
[](../LICENSE)
|
||||
|
||||
## Get started
|
||||
|
||||
**Install dependencies**
|
||||
|
||||
- Bootstrap your python environment.
|
||||
- e.g: create a new [conda](https://conda.io/projects/conda/en/latest/user-guide/getting-started.html) environment. `conda create -n pf-examples python=3.9`.
|
||||
- install required packages in python environment : `pip install -r requirements.txt`
|
||||
- show installed sdk: `pip show promptflow`
|
||||
|
||||
**Quick start**
|
||||
|
||||
| path | status | description |
|
||||
------|--------|-------------
|
||||
{% for quickstart in quickstarts.notebooks %}| [{{ quickstart.name }}]({{ quickstart.path }}) | [](https://github.com/microsoft/promptflow/actions/workflows/{{quickstart.yaml_name}}) | {{ quickstart.description }} |
|
||||
{% endfor %}
|
||||
|
||||
## CLI examples
|
||||
|
||||
### Tutorials ([tutorials](tutorials))
|
||||
|
||||
| path | status | description |
|
||||
------|--------|-------------
|
||||
{% for tutorial in tutorials.readmes %}| [{{ tutorial.name }}]({{ tutorial.path }}) | [](https://github.com/microsoft/promptflow/actions/workflows/{{tutorial.yaml_name}}) | {{ tutorial.description }} |
|
||||
{% endfor %}
|
||||
|
||||
### Prompty ([prompty](prompty))
|
||||
|
||||
| path | status | description |
|
||||
------|--------|-------------
|
||||
{% for flow in prompty.readmes %}| [{{ flow.name }}]({{ flow.path }}) | [](https://github.com/microsoft/promptflow/actions/workflows/{{flow.yaml_name}}) | {{ flow.description }} |
|
||||
{% endfor %}
|
||||
|
||||
### Flex Flows ([flex-flows](flex-flows))
|
||||
|
||||
| path | status | description |
|
||||
------|--------|-------------
|
||||
{% for flow in flex_flows.readmes %}| [{{ flow.name }}]({{ flow.path }}) | [](https://github.com/microsoft/promptflow/actions/workflows/{{flow.yaml_name}}) | {{ flow.description }} |
|
||||
{% endfor %}
|
||||
|
||||
### Flows ([flows](flows))
|
||||
|
||||
#### [Standard flows](flows/standard/)
|
||||
|
||||
| path | status | description |
|
||||
------|--------|-------------
|
||||
{% for flow in flows.readmes %}| [{{ flow.name }}]({{ flow.path }}) | [](https://github.com/microsoft/promptflow/actions/workflows/{{flow.yaml_name}}) | {{ flow.description }} |
|
||||
{% endfor %}
|
||||
|
||||
#### [Evaluation flows](flows/evaluation/)
|
||||
|
||||
| path | status | description |
|
||||
------|--------|-------------
|
||||
{% for evaluation in evaluations.readmes %}| [{{ evaluation.name }}]({{ evaluation.path }}) | [](https://github.com/microsoft/promptflow/actions/workflows/{{evaluation.yaml_name}}) | {{ evaluation.description }} |
|
||||
{% endfor %}
|
||||
|
||||
#### [Chat flows](flows/chat/)
|
||||
|
||||
| path | status | description |
|
||||
------|--------|-------------
|
||||
{% for chat in chats.readmes %}| [{{ chat.name }}]({{ chat.path }}) | [](https://github.com/microsoft/promptflow/actions/workflows/{{chat.yaml_name}}) | {{ chat.description }} |
|
||||
{% endfor %}
|
||||
|
||||
### Tool Use Cases ([Tool Use Cases](tools/use-cases))
|
||||
|
||||
| path | status | description |
|
||||
------|--------|-------------
|
||||
{% for toolusecase in toolusecases.readmes %}| [{{ toolusecase.name }}]({{ toolusecase.path }}) | [](https://github.com/microsoft/promptflow/actions/workflows/{{toolusecase.yaml_name}}) | {{ toolusecase.description }} |
|
||||
{% endfor %}
|
||||
|
||||
### Connections ([connections](connections))
|
||||
|
||||
| path | status | description |
|
||||
------|--------|-------------
|
||||
{% for connection in connections.readmes %}| [{{ connection.name }}]({{ connection.path }}) | [](https://github.com/microsoft/promptflow/actions/workflows/{{connection.yaml_name}}) | {{ connection.description }} |
|
||||
{% endfor %}
|
||||
|
||||
|
||||
## SDK examples
|
||||
|
||||
| path | status | description |
|
||||
------|--------|-------------
|
||||
{% for quickstart in quickstarts.notebooks %}| [{{ quickstart.name }}]({{ quickstart.path }}) | [](https://github.com/microsoft/promptflow/actions/workflows/{{quickstart.yaml_name}}) | {{ quickstart.description }} |
|
||||
{% endfor %}
|
||||
{%- for tutorial in tutorials.notebooks -%}| [{{ tutorial.name }}]({{ tutorial.path }}) | [](https://github.com/microsoft/promptflow/actions/workflows/{{tutorial.yaml_name}}) | {{ tutorial.description }} |
|
||||
{% endfor %}
|
||||
{%- if connections.notebooks|length > 0 -%}{% for connection in connections.notebooks %}| [{{ connection.name }}]({{ connection.path }}) | [](https://github.com/microsoft/promptflow/actions/workflows/{{connection.yaml_name}}) | {{ connection.description }} |
|
||||
{% endfor %}{% endif %}
|
||||
{%- if flex_flows.notebooks|length > 0 -%}{% for flow in flex_flows.notebooks %}| [{{ flow.name }}]({{ flow.path }}) | [](https://github.com/microsoft/promptflow/actions/workflows/{{flow.yaml_name}}) | {{ flow.description }} |
|
||||
{% endfor %}{% endif %}
|
||||
{%- if prompty.notebooks|length > 0 -%}{% for flow in prompty.notebooks %}| [{{ flow.name }}]({{ flow.path }}) | [](https://github.com/microsoft/promptflow/actions/workflows/{{flow.yaml_name}}) | {{ flow.description }} |
|
||||
{% endfor %}{% endif %}
|
||||
{%- if chats.notebooks|length > 0 -%}{% for chat in chats.notebooks %}| [{{ chat.name }}]({{ chat.path }}) | [](https://github.com/microsoft/promptflow/actions/workflows/{{chat.yaml_name}}) | {{ chat.description }} |
|
||||
{% endfor %}{% endif %}
|
||||
{%- if evaluations.notebooks|length > 0 -%}{% for evaluation in evaluations.notebooks %}| [{{ evaluation.name }}]({{ evaluation.path }}) | [](https://github.com/microsoft/promptflow/actions/workflows/{{evaluation.yaml_name}}) | {{ evaluation.description }} |
|
||||
{% endfor %}{% endif %}
|
||||
{%- if flows.notebooks|length > 0 -%}{% for flow in flows.notebooks %}| [{{ flow.name }}]({{ flow.path }}) | [](https://github.com/microsoft/promptflow/actions/workflows/{{flow.yaml_name}}) | {{ flow.description }} |
|
||||
{% endfor %}{% endif %}
|
||||
|
||||
|
||||
## Contributing
|
||||
|
||||
We welcome contributions and suggestions! Please see the [contributing guidelines](../CONTRIBUTING.md) for details.
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). Please see the [code of conduct](../CODE_OF_CONDUCT.md) for details.
|
||||
|
||||
## Reference
|
||||
|
||||
* [Promptflow documentation](https://microsoft.github.io/promptflow/)
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
# Tutorials
|
||||
|
||||
This section contains a collection of flow samples and step-by-step tutorials.
|
||||
|
||||
|Category|<div style="width:250px">Sample</div>|Description|
|
||||
|--|--|--|
|
||||
{% for item in items %}|{{ item.category }}|[{{ item.title }}]({{ item.url }})|{{ item.description }}|
|
||||
{% endfor %}
|
||||
|
||||
Learn more: [Try out more promptflow examples.](https://github.com/microsoft/promptflow/tree/main/examples)
|
||||
@@ -0,0 +1,68 @@
|
||||
from pathlib import Path
|
||||
|
||||
from .readme_step import ReadmeStepsManage, ReadmeSteps
|
||||
from ghactions_driver.telemetry_obj import Telemetry
|
||||
|
||||
|
||||
def write_readme_workflow(readme_path, output_telemetry=Telemetry()):
|
||||
relative_path = Path(readme_path).relative_to(
|
||||
Path(ReadmeStepsManage.git_base_dir())
|
||||
)
|
||||
workflow_path = relative_path.parent.as_posix()
|
||||
relative_name_path = Path(readme_path).relative_to(
|
||||
Path(ReadmeStepsManage.git_base_dir()) / "examples"
|
||||
)
|
||||
workflow_name = (
|
||||
relative_name_path.as_posix()
|
||||
.replace(".md", "")
|
||||
.replace("/README", "")
|
||||
.replace("/", "_")
|
||||
.replace("-", "_")
|
||||
)
|
||||
workflow_name = "samples_" + workflow_name
|
||||
|
||||
ReadmeSteps.setup_target(
|
||||
working_dir=workflow_path,
|
||||
template="basic_workflow_replace_config_json.yml.jinja2"
|
||||
if "e2e_development_chat_with_pdf" in workflow_name
|
||||
else "basic_workflow_replace.yml.jinja2",
|
||||
target=f"{workflow_name}.yml",
|
||||
readme_name=relative_path.as_posix(),
|
||||
)
|
||||
ReadmeSteps.install_dependencies()
|
||||
ReadmeSteps.install_dev_dependencies()
|
||||
if (
|
||||
workflow_name.endswith("flows_chat_chat_with_image")
|
||||
or workflow_name.endswith("flows_standard_describe_image")
|
||||
):
|
||||
ReadmeSteps.create_env_gpt4()
|
||||
ReadmeSteps.env_create_aoai("aoai_gpt4v_connection")
|
||||
else:
|
||||
ReadmeSteps.create_env()
|
||||
if workflow_name.endswith("pdf"):
|
||||
ReadmeSteps.env_create_aoai("chat_with_pdf_custom_connection")
|
||||
ReadmeSteps.create_run_yaml()
|
||||
if (
|
||||
workflow_name.endswith("flows_standard_basic_with_builtin_llm")
|
||||
or workflow_name.endswith("flows_standard_flow_with_symlinks")
|
||||
or workflow_name.endswith("flows_standard_flow_with_additional_includes")
|
||||
or workflow_name.endswith("flows_standard_basic_with_connection")
|
||||
):
|
||||
ReadmeSteps.yml_create_aoai("examples/connections/azure_openai.yml")
|
||||
ReadmeSteps.azure_login()
|
||||
if (
|
||||
workflow_name.endswith("flows_chat_chat_with_image")
|
||||
or workflow_name.endswith("flows_standard_describe_image")
|
||||
):
|
||||
ReadmeSteps.extract_steps_and_run_gpt_four()
|
||||
else:
|
||||
ReadmeSteps.extract_steps_and_run()
|
||||
|
||||
if workflow_name.endswith("e2e_development_chat_with_pdf"):
|
||||
ReadmeSteps.login_again()
|
||||
ReadmeSteps.execute_command()
|
||||
|
||||
ReadmeStepsManage.write_workflow(
|
||||
workflow_name, "samples_readme_ci", output_telemetry
|
||||
)
|
||||
ReadmeSteps.cleanup()
|
||||
@@ -0,0 +1,120 @@
|
||||
from pathlib import Path
|
||||
|
||||
import markdown
|
||||
import nbformat
|
||||
|
||||
from .readme_step import ReadmeStepsManage
|
||||
|
||||
RESOURCES_KEY_NAME = "resources"
|
||||
RESOURCES_KEY_ERROR_MESSAGE = (
|
||||
"Please follow examples contributing guide to declare tutorial resources: "
|
||||
"https://github.com/microsoft/promptflow/blob/main/examples/CONTRIBUTING.md"
|
||||
)
|
||||
TITLE_KEY_NAME = "title"
|
||||
CLOUD_KEY_NAME = "cloud"
|
||||
CATEGORY_KEY_NAME = "category"
|
||||
WEIGHT_KEY_NAME = "weight"
|
||||
|
||||
|
||||
def _parse_resources_string_from_notebook(path: Path, output_telemetry):
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
nb = nbformat.read(f, as_version=4)
|
||||
obj = {}
|
||||
if nb.metadata.get('build_doc', False):
|
||||
if nb.metadata['build_doc'].get(CLOUD_KEY_NAME, None):
|
||||
output_telemetry.cloud = nb.metadata['build_doc']['category']
|
||||
if nb.metadata['build_doc'].get(CATEGORY_KEY_NAME, None):
|
||||
output_telemetry.category = nb.metadata['build_doc']['section']
|
||||
if nb.metadata['build_doc'].get(WEIGHT_KEY_NAME, None):
|
||||
output_telemetry.weight = int(nb.metadata['build_doc']['weight'])
|
||||
cell = nb['cells'][0]
|
||||
for cell in nb['cells']:
|
||||
if (cell['cell_type'] == 'markdown'):
|
||||
break
|
||||
if (cell['cell_type'] == 'markdown'):
|
||||
lines = cell.source.split('\n')
|
||||
for line in lines:
|
||||
if '#' in line:
|
||||
output_telemetry.title = line.replace('#', '').strip()
|
||||
break
|
||||
if RESOURCES_KEY_NAME not in nb.metadata:
|
||||
raise Exception(RESOURCES_KEY_ERROR_MESSAGE + f" . Error in {path}")
|
||||
obj[RESOURCES_KEY_NAME] = nb.metadata[RESOURCES_KEY_NAME]
|
||||
return obj
|
||||
|
||||
|
||||
def _parse_resources_string_from_markdown(path: Path, output_telemetry):
|
||||
markdown_content = path.read_text(encoding="utf-8")
|
||||
md = markdown.Markdown(extensions=["meta"])
|
||||
md.convert(markdown_content)
|
||||
obj = {}
|
||||
for line in md.lines:
|
||||
if '#' in line:
|
||||
output_telemetry.title = line.replace('#', '').strip()
|
||||
break
|
||||
if CLOUD_KEY_NAME in md.Meta:
|
||||
output_telemetry.cloud = md.Meta[CLOUD_KEY_NAME][0]
|
||||
if CATEGORY_KEY_NAME in md.Meta:
|
||||
output_telemetry.category = md.Meta[CATEGORY_KEY_NAME][0]
|
||||
if WEIGHT_KEY_NAME in md.Meta:
|
||||
output_telemetry.weight = int(md.Meta[WEIGHT_KEY_NAME][0])
|
||||
if RESOURCES_KEY_NAME not in md.Meta:
|
||||
raise Exception(RESOURCES_KEY_ERROR_MESSAGE + f" . Error in {path}")
|
||||
obj[RESOURCES_KEY_NAME] = md.Meta[RESOURCES_KEY_NAME][0]
|
||||
return obj
|
||||
|
||||
|
||||
def _parse_resources(path: Path, output_telemetry):
|
||||
metadata = {}
|
||||
if path.suffix == ".ipynb":
|
||||
metadata = _parse_resources_string_from_notebook(path, output_telemetry)
|
||||
resources_string = metadata["resources"]
|
||||
elif path.suffix == ".md":
|
||||
metadata = _parse_resources_string_from_markdown(path, output_telemetry)
|
||||
resources_string = metadata["resources"]
|
||||
else:
|
||||
raise Exception(f"Unknown file type: {path.suffix!r}")
|
||||
return [resource.strip() for resource in resources_string.split(",")]
|
||||
|
||||
|
||||
def resolve_tutorial_resource(workflow_name: str, resource_path: Path, output_telemetry):
|
||||
"""Resolve tutorial resources, so that workflow can be triggered more precisely.
|
||||
|
||||
A tutorial workflow should listen to changes of:
|
||||
1. working directory
|
||||
2. resources declared in notebook/markdown metadata
|
||||
3. workflow file
|
||||
4. examples/requirements.txt (for release verification)
|
||||
5. examples/connections/azure_openai.yml (fall back as it is the most basic and common connection)
|
||||
"""
|
||||
# working directory
|
||||
git_base_dir = Path(ReadmeStepsManage.git_base_dir())
|
||||
working_dir = resource_path.parent.relative_to(git_base_dir).as_posix()
|
||||
path_filter_list = [f"{working_dir}/**"]
|
||||
# resources declared in text file
|
||||
resources = _parse_resources(resource_path, output_telemetry)
|
||||
for resource in resources:
|
||||
# skip empty line
|
||||
if len(resource) == 0:
|
||||
continue
|
||||
# validate resource path exists
|
||||
resource_path = (git_base_dir / resource).resolve()
|
||||
if not resource_path.exists():
|
||||
raise FileNotFoundError(
|
||||
f"Please declare tutorial resources path {resource_path} whose base is the git repo root."
|
||||
)
|
||||
elif resource_path.is_file():
|
||||
path_filter_list.append(resource)
|
||||
else:
|
||||
path_filter_list.append(f"{resource}/**")
|
||||
# workflow file
|
||||
path_filter_list.append(f".github/workflows/{workflow_name}.yml")
|
||||
# manually add examples/requirements.txt if not exists
|
||||
examples_req = "examples/requirements.txt"
|
||||
if examples_req not in path_filter_list:
|
||||
path_filter_list.append(examples_req)
|
||||
# manually add examples/connections/azure_openai.yml if not exists
|
||||
aoai_conn = "examples/connections/azure_openai.yml"
|
||||
if aoai_conn not in path_filter_list:
|
||||
path_filter_list.append(aoai_conn)
|
||||
return "[ " + ", ".join(path_filter_list) + " ]"
|
||||
@@ -0,0 +1,2 @@
|
||||
class Telemetry(object):
|
||||
pass
|
||||
@@ -0,0 +1,11 @@
|
||||
- name: Random Wait
|
||||
uses: AliSajid/random-wait-action@main
|
||||
with:
|
||||
minimum: 0
|
||||
maximum: 99
|
||||
- name: {{ step_name }}
|
||||
uses: azure/login@v1
|
||||
with:
|
||||
subscription-id: ${{ '{{' }}secrets.AZURE_SUBSCRIPTION_ID}}
|
||||
tenant-id: ${{ '{{' }}secrets.AZURE_TENANT_ID}}
|
||||
client-id: ${{ '{{' }}secrets.AZURE_CLIENT_ID}}
|
||||
@@ -0,0 +1,16 @@
|
||||
- name: {{ step_name }}
|
||||
working-directory: {{ working_dir }}
|
||||
run: |
|
||||
AOAI_API_KEY=${{ '{{' }} secrets.AOAI_API_KEY_TEST }}
|
||||
AOAI_API_ENDPOINT=${{ '{{' }} secrets.AOAI_API_ENDPOINT_TEST }}
|
||||
AOAI_API_ENDPOINT=$(echo ${AOAI_API_ENDPOINT//\//\\/})
|
||||
if [[ -e .env.example ]]; then
|
||||
echo "env replacement"
|
||||
sed -i -e "s/<your_AOAI_key>/$AOAI_API_KEY/g" -e "s/<your_AOAI_endpoint>/$AOAI_API_ENDPOINT/g" .env.example
|
||||
mv .env.example .env
|
||||
fi
|
||||
if [[ -e ../.env.example ]]; then
|
||||
echo "env replacement"
|
||||
sed -i -e "s/<your_AOAI_key>/$AOAI_API_KEY/g" -e "s/<your_AOAI_endpoint>/$AOAI_API_ENDPOINT/g" ../.env.example
|
||||
mv ../.env.example ../.env
|
||||
fi
|
||||
@@ -0,0 +1,8 @@
|
||||
- name: {{ step_name }}
|
||||
working-directory: {{ working_dir }}
|
||||
run: |
|
||||
AOAI_API_KEY=${{ '{{' }} secrets.AOAI_GPT_4V_KEY }}
|
||||
AOAI_API_ENDPOINT=${{ '{{' }} secrets.AOAI_GPT_4V_ENDPOINT }}
|
||||
AOAI_API_ENDPOINT=$(echo ${AOAI_API_ENDPOINT//\//\\/})
|
||||
cp ../../../connections/azure_openai.yml ./azure_openai.yml
|
||||
sed -i -e "s/<user-input>/$AOAI_API_KEY/g" -e "s/aoai-api-endpoint/$AOAI_API_ENDPOINT/g" azure_openai.yml
|
||||
@@ -0,0 +1,8 @@
|
||||
- name: {{ step_name }}
|
||||
working-directory: {{ working_dir }}
|
||||
run: |
|
||||
gpt_base=${{ '{{' }} secrets.AOAI_API_ENDPOINT_TEST }}
|
||||
gpt_base=$(echo ${gpt_base//\//\\/})
|
||||
if [[ -e run.yml ]]; then
|
||||
sed -i -e "s/\${azure_open_ai_connection.api_key}/${{ '{{' }} secrets.AOAI_API_KEY_TEST }}/g" -e "s/\${azure_open_ai_connection.api_base}/$gpt_base/g" run.yml
|
||||
fi
|
||||
@@ -0,0 +1,10 @@
|
||||
- name: {{ step_name }}
|
||||
working-directory: {{ working_dir }}
|
||||
run: |
|
||||
if [[ -e .env ]]; then
|
||||
pf connection create --file .env --name {{ connection_name }}
|
||||
fi
|
||||
if [[ -e azure_openai.yml ]]; then
|
||||
pf connection create --file azure_openai.yml --name {{ connection_name }}
|
||||
fi
|
||||
pf connection list
|
||||
@@ -0,0 +1,20 @@
|
||||
- name: Run backup scripts against canary workspace (scheduled runs only)
|
||||
if: github.event_name == 'schedule'
|
||||
working-directory: {{ working_dir }}
|
||||
run: |
|
||||
export aoai_api_key=${{ '{{' }}secrets.AOAI_GPT_4V_KEY }}
|
||||
export aoai_api_endpoint=${{ '{{' }} secrets.AOAI_GPT_4V_ENDPOINT }}
|
||||
export test_workspace_sub_id=${{ '{{' }} secrets.TEST_WORKSPACE_SUB_ID }}
|
||||
export test_workspace_rg=${{ '{{' }} secrets.TEST_WORKSPACE_RG }}
|
||||
export test_workspace_name=${{ '{{' }} secrets.TEST_WORKSPACE_NAME_CANARY }}
|
||||
bash bash_script_pytest.sh
|
||||
- name: Run backup scripts against production workspace
|
||||
if: github.event_name != 'schedule'
|
||||
working-directory: {{ working_dir }}
|
||||
run: |
|
||||
export aoai_api_key=${{ '{{' }}secrets.AOAI_GPT_4V_KEY }}
|
||||
export aoai_api_endpoint=${{ '{{' }} secrets.AOAI_GPT_4V_ENDPOINT }}
|
||||
export test_workspace_sub_id=${{ '{{' }} secrets.TEST_WORKSPACE_SUB_ID }}
|
||||
export test_workspace_rg=${{ '{{' }} secrets.TEST_WORKSPACE_RG }}
|
||||
export test_workspace_name=${{ '{{' }} secrets.TEST_WORKSPACE_NAME_PROD }}
|
||||
bash bash_script_pytest.sh
|
||||
@@ -0,0 +1,43 @@
|
||||
- name: {{ step_name }}
|
||||
working-directory: ${{ '{{' }} github.workspace }}
|
||||
run: |
|
||||
python scripts/readme/extract_steps_from_readme.py -f {{ readme_name }} -o {{ working_dir }}
|
||||
- name: Cat script
|
||||
working-directory: {{ working_dir }}
|
||||
run: |
|
||||
cat bash_script.sh
|
||||
- name: Run scripts against canary workspace (scheduled runs only)
|
||||
if: github.event_name == 'schedule'
|
||||
working-directory: {{ working_dir }}
|
||||
run: |
|
||||
export aoai_api_key=${{ '{{' }}secrets.AOAI_API_KEY_TEST }}
|
||||
export aoai_api_endpoint=${{ '{{' }} secrets.AOAI_API_ENDPOINT_TEST }}
|
||||
export AZURE_OPENAI_API_KEY=${{ '{{' }}secrets.AOAI_API_KEY_TEST }}
|
||||
export AZURE_OPENAI_ENDPOINT=${{ '{{' }} secrets.AOAI_API_ENDPOINT_TEST }}
|
||||
export test_workspace_sub_id=${{ '{{' }} secrets.TEST_WORKSPACE_SUB_ID }}
|
||||
export test_workspace_rg=${{ '{{' }} secrets.TEST_WORKSPACE_RG }}
|
||||
export test_workspace_name=${{ '{{' }} secrets.TEST_WORKSPACE_NAME_CANARY }}
|
||||
bash bash_script.sh
|
||||
- name: Run scripts against production workspace
|
||||
if: github.event_name != 'schedule'
|
||||
working-directory: {{ working_dir }}
|
||||
run: |
|
||||
export aoai_api_key=${{ '{{' }}secrets.AOAI_API_KEY_TEST }}
|
||||
export aoai_api_endpoint=${{ '{{' }} secrets.AOAI_API_ENDPOINT_TEST }}
|
||||
export AZURE_OPENAI_API_KEY=${{ '{{' }}secrets.AOAI_API_KEY_TEST }}
|
||||
export AZURE_OPENAI_ENDPOINT=${{ '{{' }} secrets.AOAI_API_ENDPOINT_TEST }}
|
||||
export test_workspace_sub_id=${{ '{{' }} secrets.TEST_WORKSPACE_SUB_ID }}
|
||||
export test_workspace_rg=${{ '{{' }} secrets.TEST_WORKSPACE_RG }}
|
||||
export test_workspace_name=${{ '{{' }} secrets.TEST_WORKSPACE_NAME_PROD }}
|
||||
bash bash_script.sh
|
||||
- name: Pip List for Debug
|
||||
if : ${{ '{{' }} always() }}
|
||||
working-directory: {{ working_dir }}
|
||||
run: |
|
||||
pip list
|
||||
- name: Upload artifact
|
||||
if: ${{ '{{' }} always() }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: artifact
|
||||
path: {{ working_dir }}/bash_script.sh
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
- name: {{ step_name }}
|
||||
working-directory: ${{ '{{' }} github.workspace }}
|
||||
run: |
|
||||
python scripts/readme/extract_steps_from_readme.py -f {{ readme_name }} -o {{ working_dir }}
|
||||
- name: Cat script
|
||||
working-directory: {{ working_dir }}
|
||||
run: |
|
||||
cat bash_script.sh
|
||||
- name: Run scripts against canary workspace (scheduled runs only)
|
||||
if: github.event_name == 'schedule'
|
||||
working-directory: {{ working_dir }}
|
||||
run: |
|
||||
export aoai_api_key=${{ '{{' }}secrets.AOAI_GPT_4V_KEY }}
|
||||
export aoai_api_endpoint=${{ '{{' }} secrets.AOAI_GPT_4V_ENDPOINT }}
|
||||
export test_workspace_sub_id=${{ '{{' }} secrets.TEST_WORKSPACE_SUB_ID }}
|
||||
export test_workspace_rg=${{ '{{' }} secrets.TEST_WORKSPACE_RG }}
|
||||
export test_workspace_name=${{ '{{' }} secrets.TEST_WORKSPACE_NAME_CANARY }}
|
||||
bash bash_script.sh
|
||||
- name: Run scripts against production workspace
|
||||
if: github.event_name != 'schedule'
|
||||
working-directory: {{ working_dir }}
|
||||
run: |
|
||||
export aoai_api_key=${{ '{{' }}secrets.AOAI_GPT_4V_KEY }}
|
||||
export aoai_api_endpoint=${{ '{{' }} secrets.AOAI_GPT_4V_ENDPOINT }}
|
||||
export test_workspace_sub_id=${{ '{{' }} secrets.TEST_WORKSPACE_SUB_ID }}
|
||||
export test_workspace_rg=${{ '{{' }} secrets.TEST_WORKSPACE_RG }}
|
||||
export test_workspace_name=${{ '{{' }} secrets.TEST_WORKSPACE_NAME_PROD }}
|
||||
bash bash_script.sh
|
||||
- name: Pip List for Debug
|
||||
if : ${{ '{{' }} always() }}
|
||||
working-directory: {{ working_dir }}
|
||||
run: |
|
||||
pip list
|
||||
- name: Upload artifact
|
||||
if: ${{ '{{' }} always() }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: artifact
|
||||
path: {{ working_dir }}/bash_script.sh
|
||||
@@ -0,0 +1,7 @@
|
||||
- name: {{ step_name }}
|
||||
working-directory: examples
|
||||
run: |
|
||||
if [[ -e requirements.txt ]]; then
|
||||
python -m pip install --upgrade pip
|
||||
pip install -r requirements.txt
|
||||
fi
|
||||
@@ -0,0 +1,5 @@
|
||||
- name: {{ step_name }}
|
||||
working-directory: examples
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install -r dev_requirements.txt
|
||||
@@ -0,0 +1,12 @@
|
||||
- name: {{ step_name }}
|
||||
uses: azure/powershell@v1
|
||||
with:
|
||||
azPSVersion: "latest"
|
||||
inlineScript: |
|
||||
Disconnect-AzAccount
|
||||
- name: {{ step_name }}_login
|
||||
uses: azure/login@v1
|
||||
with:
|
||||
subscription-id: ${{ '{{' }}secrets.AZURE_SUBSCRIPTION_ID}}
|
||||
tenant-id: ${{ '{{' }}secrets.AZURE_TENANT_ID}}
|
||||
client-id: ${{ '{{' }}secrets.AZURE_CLIENT_ID}}
|
||||
@@ -0,0 +1,3 @@
|
||||
- name: {{ step_name }}
|
||||
working-directory: ${{ '{{' }} github.workspace }}
|
||||
run: pf connection create --file {{ yaml_name }} --set api_key=${{ '{{' }} secrets.AOAI_API_KEY_TEST }} api_base=${{ '{{' }} secrets.AOAI_API_ENDPOINT_TEST }}
|
||||
@@ -0,0 +1,73 @@
|
||||
{% extends "workflow_skeleton.yml.jinja2" %}
|
||||
{% block steps %}
|
||||
runs-on: ubuntu-latest
|
||||
environment:
|
||||
internal
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
- name: Setup Python 3.9 environment
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: "3.9"
|
||||
- name: Prepare requirements
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install -r ${{ '{{' }} github.workspace }}/examples/requirements.txt
|
||||
pip install -r ${{ '{{' }} github.workspace }}/examples/dev_requirements.txt
|
||||
- name: setup .env file
|
||||
working-directory: {{ gh_working_dir }}
|
||||
run: |
|
||||
AOAI_API_KEY=${{ '{{' }} secrets.AOAI_API_KEY_TEST }}
|
||||
AOAI_API_ENDPOINT=${{ '{{' }} secrets.AOAI_API_ENDPOINT_TEST }}
|
||||
AOAI_API_ENDPOINT=$(echo ${AOAI_API_ENDPOINT//\//\\/})
|
||||
if [[ -e .env.example ]]; then
|
||||
echo "env replacement"
|
||||
sed -i -e "s/<your_AOAI_key>/$AOAI_API_KEY/g" -e "s/<your_AOAI_endpoint>/$AOAI_API_ENDPOINT/g" .env.example
|
||||
mv .env.example .env
|
||||
fi
|
||||
if [[ -e ../.env.example ]]; then
|
||||
echo "env replacement"
|
||||
sed -i -e "s/<your_AOAI_key>/$AOAI_API_KEY/g" -e "s/<your_AOAI_endpoint>/$AOAI_API_ENDPOINT/g" ../.env.example
|
||||
mv ../.env.example ../.env
|
||||
fi
|
||||
if [[ -e OAI_CONFIG_LIST.json.example ]]; then
|
||||
echo "OAI_CONFIG_LIST replacement"
|
||||
sed -i -e "s/<your_AOAI_key>/$AOAI_API_KEY/g" -e "s/<your_AOAI_endpoint>/$AOAI_API_ENDPOINT/g" OAI_CONFIG_LIST.json.example
|
||||
mv OAI_CONFIG_LIST.json.example OAI_CONFIG_LIST.json
|
||||
fi
|
||||
- name: Create Aoai Connection
|
||||
run: pf connection create -f ${{ '{{' }} github.workspace }}/examples/connections/azure_openai.yml --set api_key="${{ '{{' }} secrets.AOAI_API_KEY_TEST }}" api_base="${{ '{{' }} secrets.AOAI_API_ENDPOINT_TEST }}"
|
||||
- name: Random Wait
|
||||
uses: AliSajid/random-wait-action@main
|
||||
with:
|
||||
minimum: 1
|
||||
maximum: 99
|
||||
- name: Azure Login
|
||||
uses: azure/login@v2
|
||||
with:
|
||||
subscription-id: ${{ '{{' }}secrets.AZURE_SUBSCRIPTION_ID}}
|
||||
tenant-id: ${{ '{{' }}secrets.AZURE_TENANT_ID}}
|
||||
client-id: ${{ '{{' }}secrets.AZURE_CLIENT_ID}}
|
||||
- name: Fetch OID token every 4 mins
|
||||
shell: bash
|
||||
run: |
|
||||
while true; do
|
||||
token_request=$ACTIONS_ID_TOKEN_REQUEST_TOKEN
|
||||
token_uri=$ACTIONS_ID_TOKEN_REQUEST_URL
|
||||
token=$(curl -H "Authorization: bearer $token_request" "${token_uri}&audience=api://AzureADTokenExchange" | jq .value -r)
|
||||
az login --service-principal -u ${{ '{{' }}secrets.AZURE_CLIENT_ID}} -t ${{ '{{' }}secrets.AZURE_TENANT_ID}} --federated-token $token --output none
|
||||
# Sleep for 4 minutes
|
||||
sleep 240
|
||||
done &
|
||||
- name: Test Notebook
|
||||
working-directory: {{ gh_working_dir }}
|
||||
run: |
|
||||
papermill -k python {{ name }}.ipynb {{ name }}.output.ipynb
|
||||
- name: Upload artifact
|
||||
if: ${{ '{{' }} always() }}
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: artifact
|
||||
path: {{ gh_working_dir }}
|
||||
{% endblock steps %}
|
||||
@@ -0,0 +1,68 @@
|
||||
{% extends "workflow_skeleton.yml.jinja2" %}
|
||||
{% block steps %}
|
||||
runs-on: ubuntu-latest
|
||||
environment:
|
||||
internal
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
- name: Setup Python 3.9 environment
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: "3.9"
|
||||
- name: Prepare requirements
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install -r ${{ '{{' }} github.workspace }}/examples/requirements.txt
|
||||
pip install -r ${{ '{{' }} github.workspace }}/examples/dev_requirements.txt
|
||||
- name: setup .env file
|
||||
working-directory: {{ gh_working_dir }}
|
||||
run: |
|
||||
AOAI_API_KEY=${{ '{{' }} secrets.AOAI_API_KEY_TEST }}
|
||||
AOAI_API_ENDPOINT=${{ '{{' }} secrets.AOAI_API_ENDPOINT_TEST }}
|
||||
AOAI_API_ENDPOINT=$(echo ${AOAI_API_ENDPOINT//\//\\/})
|
||||
if [[ -e .env.example ]]; then
|
||||
echo "env replacement"
|
||||
sed -i -e "s/<your_AOAI_key>/$AOAI_API_KEY/g" -e "s/<your_AOAI_endpoint>/$AOAI_API_ENDPOINT/g" .env.example
|
||||
mv .env.example .env
|
||||
fi
|
||||
if [[ -e ../.env.example ]]; then
|
||||
echo "env replacement"
|
||||
sed -i -e "s/<your_AOAI_key>/$AOAI_API_KEY/g" -e "s/<your_AOAI_endpoint>/$AOAI_API_ENDPOINT/g" ../.env.example
|
||||
mv ../.env.example ../.env
|
||||
fi
|
||||
- name: Create Aoai Connection
|
||||
run: pf connection create -f ${{ '{{' }} github.workspace }}/examples/connections/azure_openai.yml --set api_key="${{ '{{' }} secrets.AOAI_API_KEY_TEST }}" api_base="${{ '{{' }} secrets.AOAI_API_ENDPOINT_TEST }}"
|
||||
- name: Random Wait
|
||||
uses: AliSajid/random-wait-action@main
|
||||
with:
|
||||
minimum: 1
|
||||
maximum: 99
|
||||
- name: Azure Login
|
||||
uses: azure/login@v2
|
||||
with:
|
||||
subscription-id: ${{ '{{' }}secrets.AZURE_SUBSCRIPTION_ID}}
|
||||
tenant-id: ${{ '{{' }}secrets.AZURE_TENANT_ID}}
|
||||
client-id: ${{ '{{' }}secrets.AZURE_CLIENT_ID}}
|
||||
- name: Fetch OID token every 4 mins
|
||||
shell: bash
|
||||
run: |
|
||||
while true; do
|
||||
token_request=$ACTIONS_ID_TOKEN_REQUEST_TOKEN
|
||||
token_uri=$ACTIONS_ID_TOKEN_REQUEST_URL
|
||||
token=$(curl -H "Authorization: bearer $token_request" "${token_uri}&audience=api://AzureADTokenExchange" | jq .value -r)
|
||||
az login --service-principal -u ${{ '{{' }}secrets.AZURE_CLIENT_ID}} -t ${{ '{{' }}secrets.AZURE_TENANT_ID}} --federated-token $token --output none
|
||||
# Sleep for 4 minutes
|
||||
sleep 240
|
||||
done &
|
||||
- name: Test Notebook
|
||||
working-directory: {{ gh_working_dir }}
|
||||
run: |
|
||||
papermill -k python {{ name }}.ipynb {{ name }}.output.ipynb
|
||||
- name: Upload artifact
|
||||
if: ${{ '{{' }} always() }}
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: artifact
|
||||
path: {{ gh_working_dir }}
|
||||
{% endblock steps %}
|
||||
@@ -0,0 +1,17 @@
|
||||
{% extends "workflow_skeleton.yml.jinja2" %}
|
||||
{% block steps %}
|
||||
runs-on: ubuntu-latest
|
||||
environment:
|
||||
internal
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
- name: Setup Python 3.9 environment
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: "3.9"
|
||||
{%- filter indent(width=2) -%}
|
||||
{% for step in steps %}
|
||||
{{ step.get_workflow_step() }}{% endfor %}
|
||||
{%- endfilter -%}
|
||||
{% endblock steps %}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
{% extends "workflow_skeleton.yml.jinja2" %}
|
||||
{% block steps %}
|
||||
runs-on: ubuntu-latest
|
||||
environment:
|
||||
internal
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
- name: Setup Python 3.9 environment
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: "3.9"
|
||||
- name: Generate config.json for canary workspace (scheduled runs only)
|
||||
if: github.event_name == 'schedule'
|
||||
run: echo '${{ '{{' }} secrets.TEST_WORKSPACE_CONFIG_JSON_CANARY }}' > ${{ '{{' }} github.workspace }}/examples/config.json
|
||||
- name: Generate config.json for production workspace
|
||||
if: github.event_name != 'schedule'
|
||||
run: echo '${{ '{{' }} secrets.EXAMPLE_WORKSPACE_CONFIG_JSON_PROD }}' > ${{ '{{' }} github.workspace }}/examples/config.json
|
||||
{%- filter indent(width=2) -%}
|
||||
{% for step in steps %}
|
||||
{{ step.get_workflow_step() }}{% endfor %}
|
||||
{%- endfilter -%}
|
||||
{% endblock steps %}
|
||||
@@ -0,0 +1,54 @@
|
||||
{% extends "workflow_skeleton.yml.jinja2" %}
|
||||
{% block steps %}
|
||||
runs-on: ubuntu-latest
|
||||
environment:
|
||||
internal
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
- name: Setup Python 3.9 environment
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: "3.9"
|
||||
- name: Prepare requirements
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install -r ${{ '{{' }} github.workspace }}/examples/requirements.txt
|
||||
pip install -r ${{ '{{' }} github.workspace }}/examples/dev_requirements.txt
|
||||
- name: Create Aoai Connection
|
||||
run: pf connection create -f ${{ '{{' }} github.workspace }}/examples/connections/azure_openai.yml --set api_key="${{ '{{' }} secrets.AOAI_API_KEY_TEST }}" api_base="${{ '{{' }} secrets.AOAI_API_ENDPOINT_TEST }}"
|
||||
- name: Create new Aoai Connection
|
||||
run: pf connection create -f ${{ '{{' }} github.workspace }}/examples/connections/azure_openai.yml --set api_key="${{ '{{' }} secrets.AOAI_API_KEY_TEST }}" api_base="${{ '{{' }} secrets.AOAI_API_ENDPOINT_TEST }}" name=new_ai_connection
|
||||
- name: Random Wait
|
||||
uses: AliSajid/random-wait-action@main
|
||||
with:
|
||||
minimum: 1
|
||||
maximum: 99
|
||||
- name: Azure Login
|
||||
uses: azure/login@v2
|
||||
with:
|
||||
subscription-id: ${{ '{{' }}secrets.AZURE_SUBSCRIPTION_ID}}
|
||||
tenant-id: ${{ '{{' }}secrets.AZURE_TENANT_ID}}
|
||||
client-id: ${{ '{{' }}secrets.AZURE_CLIENT_ID}}
|
||||
- name: Fetch OID token every 4 mins
|
||||
shell: bash
|
||||
run: |
|
||||
while true; do
|
||||
token_request=$ACTIONS_ID_TOKEN_REQUEST_TOKEN
|
||||
token_uri=$ACTIONS_ID_TOKEN_REQUEST_URL
|
||||
token=$(curl -H "Authorization: bearer $token_request" "${token_uri}&audience=api://AzureADTokenExchange" | jq .value -r)
|
||||
az login --service-principal -u ${{ '{{' }}secrets.AZURE_CLIENT_ID}} -t ${{ '{{' }}secrets.AZURE_TENANT_ID}} --federated-token $token --output none
|
||||
# Sleep for 4 minutes
|
||||
sleep 240
|
||||
done &
|
||||
- name: Test Notebook
|
||||
working-directory: {{ gh_working_dir }}
|
||||
run: |
|
||||
papermill -k python {{ name }}.ipynb {{ name }}.output.ipynb -p api_key ${{ '{{' }} secrets.AOAI_API_KEY_TEST }} -p api_base ${{ '{{' }} secrets.AOAI_API_ENDPOINT_TEST }} -p api_version 2023-07-01-preview
|
||||
- name: Upload artifact
|
||||
if: ${{ '{{' }} always() }}
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: artifact
|
||||
path: {{ gh_working_dir }}
|
||||
{% endblock steps %}
|
||||
@@ -0,0 +1,83 @@
|
||||
{% extends "workflow_skeleton.yml.jinja2" %}
|
||||
{% block steps %}
|
||||
runs-on: ubuntu-latest
|
||||
environment:
|
||||
internal
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
- name: Generate config.json for canary workspace (scheduled runs only)
|
||||
if: github.event_name == 'schedule'
|
||||
run: echo '${{ '{{' }} secrets.TEST_WORKSPACE_CONFIG_JSON_CANARY }}' > ${{ '{{' }} github.workspace }}/examples/config.json
|
||||
- name: Generate config.json for production workspace
|
||||
if: github.event_name != 'schedule'
|
||||
run: echo '${{ '{{' }} secrets.EXAMPLE_WORKSPACE_CONFIG_JSON_PROD }}' > ${{ '{{' }} github.workspace }}/examples/config.json
|
||||
- name: Setup Python 3.9 environment
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: "3.9"
|
||||
- name: Prepare requirements
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install -r ${{ '{{' }} github.workspace }}/examples/requirements.txt
|
||||
pip install -r ${{ '{{' }} github.workspace }}/examples/dev_requirements.txt
|
||||
pip freeze
|
||||
pf --version
|
||||
shell: bash -el {0}
|
||||
- name: Prepare sample requirements
|
||||
working-directory: {{ gh_working_dir }}
|
||||
run: |
|
||||
pip install -r requirements.txt
|
||||
- name: Create Chat With PDF Custom Connection
|
||||
working-directory: {{ gh_working_dir }}
|
||||
run: |
|
||||
AOAI_API_KEY=${{ '{{' }} secrets.AOAI_API_KEY_TEST }}
|
||||
AOAI_API_ENDPOINT=${{ '{{' }} secrets.AOAI_API_ENDPOINT_TEST }}
|
||||
AOAI_API_ENDPOINT=$(echo ${AOAI_API_ENDPOINT//\//\\/})
|
||||
if [[ -e .env.example ]]; then
|
||||
echo "env replacement"
|
||||
sed -i -e "s/<your_AOAI_key>/$AOAI_API_KEY/g" -e "s/<your_AOAI_endpoint>/$AOAI_API_ENDPOINT/g" .env.example
|
||||
mv .env.example .env
|
||||
pf connection create --file .env --name chat_with_pdf_custom_connection
|
||||
fi
|
||||
- name: Create AOAI Connection
|
||||
working-directory: examples/connections
|
||||
run: |
|
||||
AOAI_API_KEY=${{ '{{' }} secrets.AOAI_API_KEY_TEST }}
|
||||
AOAI_API_ENDPOINT=${{ '{{' }} secrets.AOAI_API_ENDPOINT_TEST }}
|
||||
if [[ -e azure_openai.yml ]]; then
|
||||
pf connection create --file azure_openai.yml --set api_key=$AOAI_API_KEY api_base=$AOAI_API_ENDPOINT
|
||||
fi
|
||||
- name: Random Wait
|
||||
uses: AliSajid/random-wait-action@main
|
||||
with:
|
||||
minimum: 1
|
||||
maximum: 99
|
||||
- name: Azure Login
|
||||
uses: azure/login@v2
|
||||
with:
|
||||
subscription-id: ${{ '{{' }}secrets.AZURE_SUBSCRIPTION_ID}}
|
||||
tenant-id: ${{ '{{' }}secrets.AZURE_TENANT_ID}}
|
||||
client-id: ${{ '{{' }}secrets.AZURE_CLIENT_ID}}
|
||||
- name: Fetch OID token every 4 mins
|
||||
shell: bash
|
||||
run: |
|
||||
while true; do
|
||||
token_request=$ACTIONS_ID_TOKEN_REQUEST_TOKEN
|
||||
token_uri=$ACTIONS_ID_TOKEN_REQUEST_URL
|
||||
token=$(curl -H "Authorization: bearer $token_request" "${token_uri}&audience=api://AzureADTokenExchange" | jq .value -r)
|
||||
az login --service-principal -u ${{ '{{' }}secrets.AZURE_CLIENT_ID}} -t ${{ '{{' }}secrets.AZURE_TENANT_ID}} --federated-token $token --output none
|
||||
# Sleep for 4 minutes
|
||||
sleep 240
|
||||
done &
|
||||
- name: Test Notebook
|
||||
working-directory: {{ gh_working_dir }}
|
||||
run: |
|
||||
papermill -k python {{ name }}.ipynb {{ name }}.output.ipynb
|
||||
- name: Upload artifact
|
||||
if: ${{ '{{' }} always() }}
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: artifact
|
||||
path: {{ gh_working_dir }}
|
||||
{% endblock steps %}
|
||||
@@ -0,0 +1,58 @@
|
||||
{% extends "workflow_skeleton.yml.jinja2" %}
|
||||
{% block steps %}
|
||||
runs-on: ubuntu-latest
|
||||
environment:
|
||||
internal
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
- name: Generate config.json for canary workspace (scheduled runs only)
|
||||
if: github.event_name == 'schedule'
|
||||
run: echo '${{ '{{' }} secrets.TEST_WORKSPACE_CONFIG_JSON_CANARY }}' > ${{ '{{' }} github.workspace }}/examples/config.json
|
||||
- name: Generate config.json for production workspace
|
||||
if: github.event_name != 'schedule'
|
||||
run: echo '${{ '{{' }} secrets.EXAMPLE_WORKSPACE_CONFIG_JSON_PROD }}' > ${{ '{{' }} github.workspace }}/examples/config.json
|
||||
- name: Setup Python 3.9 environment
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: "3.9"
|
||||
- name: Prepare requirements
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install -r ${{ '{{' }} github.workspace }}/examples/requirements.txt
|
||||
pip install -r ${{ '{{' }} github.workspace }}/examples/dev_requirements.txt
|
||||
- name: Create Aoai Connection
|
||||
run: pf connection create -f ${{ '{{' }} github.workspace }}/examples/connections/azure_openai.yml --set api_key="${{ '{{' }} secrets.AOAI_API_KEY_TEST }}" api_base="${{ '{{' }} secrets.AOAI_API_ENDPOINT_TEST }}"
|
||||
- name: Random Wait
|
||||
uses: AliSajid/random-wait-action@main
|
||||
with:
|
||||
minimum: 1
|
||||
maximum: 99
|
||||
- name: Azure Login
|
||||
uses: azure/login@v2
|
||||
with:
|
||||
subscription-id: ${{ '{{' }}secrets.AZURE_SUBSCRIPTION_ID}}
|
||||
tenant-id: ${{ '{{' }}secrets.AZURE_TENANT_ID}}
|
||||
client-id: ${{ '{{' }}secrets.AZURE_CLIENT_ID}}
|
||||
- name: Fetch OID token every 4 mins
|
||||
shell: bash
|
||||
run: |
|
||||
while true; do
|
||||
token_request=$ACTIONS_ID_TOKEN_REQUEST_TOKEN
|
||||
token_uri=$ACTIONS_ID_TOKEN_REQUEST_URL
|
||||
token=$(curl -H "Authorization: bearer $token_request" "${token_uri}&audience=api://AzureADTokenExchange" | jq .value -r)
|
||||
az login --service-principal -u ${{ '{{' }}secrets.AZURE_CLIENT_ID}} -t ${{ '{{' }}secrets.AZURE_TENANT_ID}} --federated-token $token --output none
|
||||
# Sleep for 4 minutes
|
||||
sleep 240
|
||||
done &
|
||||
- name: Test Notebook
|
||||
working-directory: {{ gh_working_dir }}
|
||||
run: |
|
||||
papermill -k python {{ name }}.ipynb {{ name }}.output.ipynb
|
||||
- name: Upload artifact
|
||||
if: ${{ '{{' }} always() }}
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: artifact
|
||||
path: {{ gh_working_dir }}
|
||||
{% endblock steps %}
|
||||
@@ -0,0 +1,27 @@
|
||||
# This code is autogenerated.
|
||||
# Code is generated by running custom script: python3 readme.py
|
||||
# Any manual changes to this file may cause incorrect behavior.
|
||||
# Any manual changes will be overwritten if the code is regenerated.
|
||||
|
||||
name: {{ workflow_name }}
|
||||
on:
|
||||
schedule:
|
||||
- cron: "{{ crontab }}" # {{ crontab_comment }}
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
paths: {{ path_filter }}
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
IS_IN_CI_PIPELINE: "true"
|
||||
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
{{ workflow_name }}:
|
||||
{%- filter indent(width=4) -%}
|
||||
{% block steps %}
|
||||
{% endblock steps %}
|
||||
{%- endfilter -%}
|
||||
@@ -0,0 +1,445 @@
|
||||
# Generate Readme file for the examples folder
|
||||
import json
|
||||
from pathlib import Path
|
||||
import workflow_generator
|
||||
import readme_generator
|
||||
from jinja2 import Environment, FileSystemLoader
|
||||
from ghactions_driver.readme_step import ReadmeStepsManage
|
||||
from operator import itemgetter
|
||||
import argparse
|
||||
import sys
|
||||
import os
|
||||
import re
|
||||
|
||||
BRANCH = "main"
|
||||
|
||||
|
||||
def get_notebook_readme_description(notebook) -> str:
|
||||
"""
|
||||
Set each ipynb metadata description at .metadata.description
|
||||
"""
|
||||
try:
|
||||
# read in notebook
|
||||
with open(notebook, "r", encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
return data["metadata"]["description"]
|
||||
except Exception:
|
||||
print(f"{notebook} metadata description not set")
|
||||
return ""
|
||||
|
||||
|
||||
def get_notebook_buildDoc_description(notebook) -> str:
|
||||
"""
|
||||
Set each ipynb metadata description at .metadata.description
|
||||
"""
|
||||
try:
|
||||
# read in notebook
|
||||
with open(notebook, "r", encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
return data["metadata"]["build_doc"]
|
||||
except Exception:
|
||||
print(f"{notebook} metadata build_doc not set")
|
||||
return {}
|
||||
|
||||
|
||||
def get_readme_description_first_sentence(readme) -> str:
|
||||
"""
|
||||
Get each readme first sentence of first paragraph
|
||||
"""
|
||||
try:
|
||||
with open(readme, "r", encoding="utf-8") as f:
|
||||
# read first line
|
||||
line = f.readline()
|
||||
sentence = ""
|
||||
while True:
|
||||
line = f.readline()
|
||||
if line.startswith("#"):
|
||||
line = ""
|
||||
# skip metadata section
|
||||
if (
|
||||
line.startswith("---")
|
||||
or line.startswith("resources:")
|
||||
or line.startswith("title:")
|
||||
or line.startswith("cloud:")
|
||||
or line.startswith("category:")
|
||||
or line.startswith("weight:")
|
||||
):
|
||||
line = ""
|
||||
if line.strip() == "" and sentence != "":
|
||||
break
|
||||
elif "." in line:
|
||||
sentence += " " + line.split(".")[0].strip()
|
||||
break
|
||||
else:
|
||||
if sentence == "":
|
||||
sentence += line.strip()
|
||||
elif line.strip() != "":
|
||||
sentence += " " + line.strip()
|
||||
return sentence
|
||||
except Exception:
|
||||
print(f"Error during reading {readme}")
|
||||
return ""
|
||||
|
||||
|
||||
def write_readme(workflow_telemetries, readme_telemetries):
|
||||
global BRANCH
|
||||
|
||||
ReadmeStepsManage.git_base_dir()
|
||||
readme_file = Path(ReadmeStepsManage.git_base_dir()) / "examples/README.md"
|
||||
|
||||
quickstarts = {
|
||||
"readmes": [],
|
||||
"notebooks": [],
|
||||
}
|
||||
tutorials = {
|
||||
"readmes": [],
|
||||
"notebooks": [],
|
||||
}
|
||||
flex_flows = {
|
||||
"readmes": [],
|
||||
"notebooks": [],
|
||||
}
|
||||
prompty = {
|
||||
"readmes": [],
|
||||
"notebooks": [],
|
||||
}
|
||||
flows = {
|
||||
"readmes": [],
|
||||
"notebooks": [],
|
||||
}
|
||||
evaluations = {
|
||||
"readmes": [],
|
||||
"notebooks": [],
|
||||
}
|
||||
chats = {
|
||||
"readmes": [],
|
||||
"notebooks": [],
|
||||
}
|
||||
toolusecases = {
|
||||
"readmes": [],
|
||||
"notebooks": [],
|
||||
}
|
||||
connections = {
|
||||
"readmes": [],
|
||||
"notebooks": [],
|
||||
}
|
||||
|
||||
for workflow_telemetry in workflow_telemetries:
|
||||
notebook_name = f"{workflow_telemetry.name}.ipynb"
|
||||
gh_working_dir = workflow_telemetry.gh_working_dir
|
||||
pipeline_name = workflow_telemetry.workflow_name
|
||||
yaml_name = f"{pipeline_name}.yml"
|
||||
|
||||
# For workflows, open ipynb as raw json and
|
||||
# setup description at .metadata.description
|
||||
description = get_notebook_readme_description(workflow_telemetry.notebook)
|
||||
build_doc = get_notebook_buildDoc_description(workflow_telemetry.notebook)
|
||||
notebook_path = gh_working_dir.replace("examples/", "") + f"/{notebook_name}"
|
||||
default_workflow_item = {
|
||||
"name": notebook_name,
|
||||
"path": notebook_path,
|
||||
"pipeline_name": pipeline_name,
|
||||
"yaml_name": yaml_name,
|
||||
"description": description,
|
||||
"build_doc": build_doc,
|
||||
"title": workflow_telemetry.title.capitalize()
|
||||
if hasattr(workflow_telemetry, "title")
|
||||
else "Empty title",
|
||||
"cloud": workflow_telemetry.cloud.capitalize()
|
||||
if hasattr(workflow_telemetry, "cloud")
|
||||
else "NOT DEFINED",
|
||||
"category": workflow_telemetry.category.capitalize()
|
||||
if hasattr(workflow_telemetry, "category")
|
||||
else "General",
|
||||
"weight": workflow_telemetry.weight
|
||||
if hasattr(workflow_telemetry, "weight")
|
||||
else 0,
|
||||
}
|
||||
if gh_working_dir.startswith("examples/flows/standard"):
|
||||
flows["notebooks"].append(default_workflow_item)
|
||||
elif gh_working_dir.startswith("examples/connections"):
|
||||
connections["notebooks"].append(default_workflow_item)
|
||||
elif gh_working_dir.startswith("examples/flows/evaluation"):
|
||||
evaluations["notebooks"].append(default_workflow_item)
|
||||
elif gh_working_dir.startswith("examples/tutorials"):
|
||||
if "quickstart" in notebook_name:
|
||||
quickstarts["notebooks"].append(default_workflow_item)
|
||||
else:
|
||||
tutorials["notebooks"].append(default_workflow_item)
|
||||
elif gh_working_dir.startswith("examples/flows/chat"):
|
||||
chats["notebooks"].append(default_workflow_item)
|
||||
elif gh_working_dir.startswith("examples/flex-flows"):
|
||||
flex_flows["notebooks"].append(default_workflow_item)
|
||||
elif gh_working_dir.startswith("examples/prompty"):
|
||||
prompty["notebooks"].append(default_workflow_item)
|
||||
elif gh_working_dir.startswith("examples/tools/use-cases"):
|
||||
toolusecases["notebooks"].append(default_workflow_item)
|
||||
else:
|
||||
print(f"Unknown workflow type: {gh_working_dir}")
|
||||
|
||||
# Adjust tutorial names:
|
||||
|
||||
no_workflow_readmes = []
|
||||
|
||||
for readme_telemetry in readme_telemetries:
|
||||
if readme_telemetry.readme_name.endswith("README.md"):
|
||||
notebook_name = readme_telemetry.readme_folder.split("/")[-1]
|
||||
else:
|
||||
notebook_name = readme_telemetry.readme_name.split("/")[-1].replace(
|
||||
".md", ""
|
||||
)
|
||||
notebook_path = readme_telemetry.readme_name.replace("examples/", "")
|
||||
if not hasattr(readme_telemetry, "workflow_name"):
|
||||
no_workflow_readme_item = {
|
||||
"name": notebook_name,
|
||||
"path": notebook_path,
|
||||
"description": get_readme_description_first_sentence(
|
||||
readme_telemetry.readme_name
|
||||
),
|
||||
"title": readme_telemetry.title.capitalize()
|
||||
if hasattr(readme_telemetry, "title")
|
||||
else "Empty title",
|
||||
"cloud": readme_telemetry.cloud.capitalize()
|
||||
if hasattr(readme_telemetry, "cloud")
|
||||
else "NOT DEFINED",
|
||||
"category": readme_telemetry.category.capitalize()
|
||||
if hasattr(readme_telemetry, "category")
|
||||
else "General",
|
||||
"weight": readme_telemetry.weight
|
||||
if hasattr(readme_telemetry, "weight")
|
||||
else 0,
|
||||
}
|
||||
no_workflow_readmes.append(no_workflow_readme_item)
|
||||
continue
|
||||
|
||||
pipeline_name = readme_telemetry.workflow_name
|
||||
yaml_name = f"{readme_telemetry.workflow_name}.yml"
|
||||
description = get_readme_description_first_sentence(
|
||||
readme_telemetry.readme_name
|
||||
)
|
||||
readme_folder = readme_telemetry.readme_folder
|
||||
|
||||
default_readme_item = {
|
||||
"name": notebook_name,
|
||||
"path": notebook_path,
|
||||
"pipeline_name": pipeline_name,
|
||||
"yaml_name": yaml_name,
|
||||
"description": description,
|
||||
"title": readme_telemetry.title.capitalize()
|
||||
if hasattr(readme_telemetry, "title")
|
||||
else "Empty title",
|
||||
"cloud": readme_telemetry.cloud.capitalize()
|
||||
if hasattr(readme_telemetry, "cloud")
|
||||
else "NOT DEFINED",
|
||||
"category": readme_telemetry.category.capitalize()
|
||||
if hasattr(readme_telemetry, "category")
|
||||
else "General",
|
||||
"weight": readme_telemetry.weight
|
||||
if hasattr(readme_telemetry, "weight")
|
||||
else 0,
|
||||
}
|
||||
if readme_folder.startswith("examples/flows/standard"):
|
||||
flows["readmes"].append(default_readme_item)
|
||||
elif readme_folder.startswith("examples/connections"):
|
||||
connections["readmes"].append(default_readme_item)
|
||||
elif readme_folder.startswith("examples/flows/evaluation"):
|
||||
evaluations["readmes"].append(default_readme_item)
|
||||
elif readme_folder.startswith("examples/tutorials"):
|
||||
if "quickstart" in notebook_name:
|
||||
quickstarts["readmes"].append(default_readme_item)
|
||||
else:
|
||||
tutorials["readmes"].append(default_readme_item)
|
||||
elif readme_folder.startswith("examples/flows/chat"):
|
||||
chats["readmes"].append(default_readme_item)
|
||||
elif readme_folder.startswith("examples/flex-flows"):
|
||||
flex_flows["readmes"].append(default_readme_item)
|
||||
elif readme_folder.startswith("examples/prompty"):
|
||||
prompty["readmes"].append(default_readme_item)
|
||||
elif readme_folder.startswith("examples/tools/use-cases"):
|
||||
toolusecases["readmes"].append(default_readme_item)
|
||||
else:
|
||||
print(f"Unknown workflow type: {readme_folder}")
|
||||
|
||||
quickstarts["notebooks"] = sorted(
|
||||
quickstarts["notebooks"],
|
||||
key=itemgetter("name"),
|
||||
reverse=True,
|
||||
)
|
||||
|
||||
# Debug this replacement to check if generated correctly
|
||||
replacement = {
|
||||
"branch": BRANCH,
|
||||
"tutorials": tutorials,
|
||||
"flex_flows": flex_flows,
|
||||
"prompty": prompty,
|
||||
"flows": flows,
|
||||
"evaluations": evaluations,
|
||||
"chats": chats,
|
||||
"toolusecases": toolusecases,
|
||||
"connections": connections,
|
||||
"quickstarts": quickstarts,
|
||||
}
|
||||
|
||||
print("writing README.md...")
|
||||
env = Environment(
|
||||
loader=FileSystemLoader(
|
||||
Path(ReadmeStepsManage.git_base_dir())
|
||||
/ "scripts/readme/ghactions_driver/readme_templates"
|
||||
)
|
||||
)
|
||||
template = env.get_template("README.md.jinja2")
|
||||
with open(readme_file, "w") as f:
|
||||
f.write(template.render(replacement))
|
||||
print(f"finished writing {str(readme_file)}")
|
||||
|
||||
# Build a table out of replacement
|
||||
# |Area|Cloud|Category|Sample|Description|
|
||||
new_items = []
|
||||
for row in replacement.keys():
|
||||
if row == "branch":
|
||||
continue
|
||||
for item in replacement[row]["notebooks"]:
|
||||
item[
|
||||
"url"
|
||||
] = f"https://github.com/microsoft/promptflow/blob/main/examples/{item['path']}"
|
||||
item["area"] = "SDK"
|
||||
if "azure" in item["name"].lower():
|
||||
item["weight"] += 1000
|
||||
new_items.append(item)
|
||||
for item in replacement[row]["readmes"]:
|
||||
if item.get("category", "General") == "General":
|
||||
print(
|
||||
f"Tutorial Index: Skipping {item['path']} for not having a category"
|
||||
)
|
||||
continue
|
||||
item[
|
||||
"url"
|
||||
] = f"https://github.com/microsoft/promptflow/blob/main/examples/{item['path']}"
|
||||
item["area"] = "CLI"
|
||||
new_items.append(item)
|
||||
for item in no_workflow_readmes:
|
||||
if not item["path"].startswith("tutorials"):
|
||||
print(f"Tutorial Index: Skipping {item['path']} for not being in tutorials")
|
||||
continue
|
||||
if item.get("category", "General") == "General":
|
||||
print(f"Tutorial Index: Skipping {item['path']} for not having a category")
|
||||
continue
|
||||
item[
|
||||
"url"
|
||||
] = f"https://github.com/microsoft/promptflow/blob/main/examples/{item['path']}"
|
||||
item["area"] = "CLI"
|
||||
new_items.append(item)
|
||||
|
||||
# sort new_items by category
|
||||
tracing_category = sorted(
|
||||
[item for item in new_items if item["category"] == "Tracing"],
|
||||
key=lambda x: x["weight"],
|
||||
)
|
||||
prompty_category = sorted(
|
||||
[item for item in new_items if item["category"] == "Prompty"],
|
||||
key=lambda x: x["weight"],
|
||||
)
|
||||
flow_category = sorted(
|
||||
[item for item in new_items if item["category"] == "Flow"],
|
||||
key=lambda x: x["weight"],
|
||||
)
|
||||
deployment_category = sorted(
|
||||
[item for item in new_items if item["category"] == "Deployment"],
|
||||
key=lambda x: x["weight"],
|
||||
)
|
||||
rag_category = sorted(
|
||||
[item for item in new_items if item["category"] == "Rag"],
|
||||
key=lambda x: x["weight"],
|
||||
)
|
||||
|
||||
real_new_items = [
|
||||
*tracing_category,
|
||||
*prompty_category,
|
||||
*flow_category,
|
||||
*deployment_category,
|
||||
*rag_category,
|
||||
]
|
||||
tutorial_items = {"items": real_new_items}
|
||||
tutorial_index_file = (
|
||||
Path(ReadmeStepsManage.git_base_dir()) / "docs/tutorials/index.md"
|
||||
)
|
||||
template_tutorial = env.get_template("tutorial_index.md.jinja2")
|
||||
with open(tutorial_index_file, "w") as f:
|
||||
f.write(template_tutorial.render(tutorial_items))
|
||||
print(f"Tutorial Index: finished writing {str(tutorial_index_file)}")
|
||||
|
||||
|
||||
def main(check):
|
||||
if check:
|
||||
# Disable print
|
||||
sys.stdout = open(os.devnull, "w")
|
||||
|
||||
input_glob = ["examples/**/*.ipynb"]
|
||||
workflow_telemetry = []
|
||||
workflow_generator.main(input_glob, workflow_telemetry, check=check)
|
||||
|
||||
input_glob_readme = [
|
||||
"examples/flows/**/README.md",
|
||||
"examples/flex-flows/**/README.md",
|
||||
"examples/prompty/**/README.md",
|
||||
"examples/connections/**/README.md",
|
||||
"examples/tutorials/**/*.md",
|
||||
"examples/tools/use-cases/**/README.md",
|
||||
]
|
||||
# exclude the readme since this is 3p integration folder, pipeline generation is not included
|
||||
input_glob_readme_exclude = ["examples/flows/integrations/**/README.md"]
|
||||
readme_telemetry = []
|
||||
readme_generator.main(
|
||||
input_glob_readme, input_glob_readme_exclude, readme_telemetry
|
||||
)
|
||||
|
||||
write_readme(workflow_telemetry, readme_telemetry)
|
||||
|
||||
if check:
|
||||
output_object = {}
|
||||
for workflow in workflow_telemetry:
|
||||
workflow_items = re.split(r"\[|,| |\]", workflow.path_filter)
|
||||
workflow_items = list(filter(None, workflow_items))
|
||||
output_object[workflow.workflow_name] = []
|
||||
for item in workflow_items:
|
||||
if item == "examples/*requirements.txt":
|
||||
output_object[workflow.workflow_name].append(
|
||||
"examples/requirements.txt"
|
||||
)
|
||||
output_object[workflow.workflow_name].append(
|
||||
"examples/dev_requirements.txt"
|
||||
)
|
||||
continue
|
||||
output_object[workflow.workflow_name].append(item)
|
||||
for readme in readme_telemetry:
|
||||
if not hasattr(readme, "workflow_name"):
|
||||
continue
|
||||
output_object[readme.workflow_name] = []
|
||||
readme_items = re.split(r"\[|,| |\]", readme.path_filter)
|
||||
readme_items = list(filter(None, readme_items))
|
||||
for item in readme_items:
|
||||
if item == "examples/*requirements.txt":
|
||||
output_object[readme.workflow_name].append(
|
||||
"examples/requirements.txt"
|
||||
)
|
||||
output_object[readme.workflow_name].append(
|
||||
"examples/dev_requirements.txt"
|
||||
)
|
||||
continue
|
||||
output_object[readme.workflow_name].append(item)
|
||||
# enable output
|
||||
sys.stdout = sys.__stdout__
|
||||
return output_object
|
||||
else:
|
||||
return ""
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# setup argparse
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument(
|
||||
"-c", "--check", action="store_true", help="Check what file is affected"
|
||||
)
|
||||
args = parser.parse_args()
|
||||
output = main(args.check)
|
||||
print(json.dumps(output))
|
||||
@@ -0,0 +1,90 @@
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
from functools import reduce
|
||||
|
||||
from ghactions_driver.readme_workflow_generate import write_readme_workflow
|
||||
from ghactions_driver.readme_step import ReadmeStepsManage, ReadmeSteps
|
||||
from ghactions_driver.readme_parse import readme_parser
|
||||
|
||||
from ghactions_driver.telemetry_obj import Telemetry
|
||||
|
||||
|
||||
def local_filter(callback, array: [Path]):
|
||||
results = []
|
||||
backups = []
|
||||
for index, item in enumerate(array):
|
||||
result = callback(item, index, array)
|
||||
# if returned true, append item to results
|
||||
if result:
|
||||
results.append(item)
|
||||
else:
|
||||
backups.append(item)
|
||||
return results, backups
|
||||
|
||||
|
||||
def no_readme_generation_filter(item: Path, index, array) -> bool:
|
||||
"""
|
||||
If there is no steps in the readme, then no generation
|
||||
"""
|
||||
try:
|
||||
if 'build' in str(item): # skip build folder
|
||||
return False
|
||||
|
||||
full_text = readme_parser(item.relative_to(ReadmeStepsManage.git_base_dir()))
|
||||
if full_text == "":
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
except Exception as error:
|
||||
print(error)
|
||||
return False # generate readme
|
||||
|
||||
|
||||
def main(input_glob, exclude_glob=[], output_files=[]):
|
||||
def set_add(p, q):
|
||||
return p | q
|
||||
|
||||
def set_difference(p, q):
|
||||
return p - q
|
||||
|
||||
globs = reduce(set_add, [set(Path(ReadmeStepsManage.git_base_dir()).glob(p)) for p in input_glob], set())
|
||||
globs_exclude = reduce(set_difference,
|
||||
[set(Path(ReadmeStepsManage.git_base_dir()).glob(p)) for p in exclude_glob],
|
||||
globs)
|
||||
readme_items = sorted([i for i in globs_exclude])
|
||||
|
||||
readme_items, no_generation_files = local_filter(no_readme_generation_filter, readme_items)
|
||||
for readme in readme_items:
|
||||
readme_telemetry = Telemetry()
|
||||
workflow_name = readme.relative_to(ReadmeStepsManage.git_base_dir())
|
||||
# Deal with readme
|
||||
write_readme_workflow(workflow_name.resolve(), readme_telemetry)
|
||||
ReadmeSteps.cleanup()
|
||||
output_files.append(readme_telemetry)
|
||||
for readme in no_generation_files:
|
||||
readme_telemetry = Telemetry()
|
||||
from ghactions_driver.resource_resolver import resolve_tutorial_resource
|
||||
try:
|
||||
resolve_tutorial_resource(
|
||||
"TEMP", readme.resolve(), readme_telemetry
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
readme_telemetry.readme_name = str(readme.relative_to(ReadmeStepsManage.git_base_dir()))
|
||||
readme_telemetry.readme_folder = str(readme.relative_to(ReadmeStepsManage.git_base_dir()).parent)
|
||||
output_files.append(readme_telemetry)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# setup argparse
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument(
|
||||
"-g",
|
||||
"--input-glob",
|
||||
nargs="+",
|
||||
help="Input Readme.md glob example 'examples/flows/**/Readme.md'",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
# call main
|
||||
main(args.input_glob)
|
||||
@@ -0,0 +1,80 @@
|
||||
from promptflow._sdk._load_functions import load_yaml
|
||||
from promptflow._sdk._pf_client import PFClient
|
||||
from ghactions_driver.readme_step import ReadmeStepsManage
|
||||
from pathlib import Path
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
|
||||
def install(filename):
|
||||
subprocess.check_call([sys.executable, "-m", "pip", "install", "-r", filename])
|
||||
|
||||
|
||||
def main(input_glob_flow_dag):
|
||||
# check if flow.dag.yaml contains schema field.
|
||||
|
||||
error = False
|
||||
globs = set()
|
||||
pf_client = PFClient()
|
||||
|
||||
for p in input_glob_flow_dag:
|
||||
globs = globs | set(Path(ReadmeStepsManage.git_base_dir()).glob(p))
|
||||
flow_dag_items = sorted([i for i in globs])
|
||||
|
||||
for file in flow_dag_items:
|
||||
data = load_yaml(file)
|
||||
if "$schema" not in data.keys():
|
||||
print(f"{file} does not contain $schema field.")
|
||||
error = True
|
||||
if error is False:
|
||||
new_links = []
|
||||
if (Path(file).parent / "requirements.txt").exists():
|
||||
# remove all promptflow lines in requirements.txt
|
||||
# and save time, or else it will check all dependencies of promptflow time by time
|
||||
with open(Path(file).parent / "requirements.txt", "r") as f:
|
||||
lines = f.readlines()
|
||||
with open(Path(file).parent / "requirements.txt", "w") as f:
|
||||
for line in lines:
|
||||
if "promptflow" not in line:
|
||||
f.write(line)
|
||||
|
||||
install(Path(file).parent / "requirements.txt")
|
||||
|
||||
if "flow-with-symlinks" in str(file):
|
||||
saved_path = os.getcwd()
|
||||
os.chdir(str(file.parent))
|
||||
source_folder = Path("../web-classification")
|
||||
for file_name in os.listdir(source_folder):
|
||||
if not Path(file_name).exists():
|
||||
os.symlink(
|
||||
source_folder / file_name,
|
||||
file_name
|
||||
)
|
||||
new_links.append(file_name)
|
||||
validation_result = pf_client.flows.validate(
|
||||
flow=file,
|
||||
)
|
||||
if "flow-with-symlinks" in str(file):
|
||||
for link in new_links:
|
||||
os.remove(link)
|
||||
os.chdir(saved_path)
|
||||
print(f"VALIDATE {file}: \n" + repr(validation_result))
|
||||
if not validation_result.passed:
|
||||
print(f"{file} is not valid.")
|
||||
error = True
|
||||
if len(validation_result._warnings) > 0:
|
||||
print(f"{file} has warnings.")
|
||||
error = True
|
||||
|
||||
if error:
|
||||
raise Exception("Some flow.dag.yaml validation failed.")
|
||||
else:
|
||||
print("All flow.dag.yaml validation completed.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
input_glob_flow_dag = [
|
||||
"examples/**/flow.dag.yaml",
|
||||
]
|
||||
main(input_glob_flow_dag)
|
||||
@@ -0,0 +1,192 @@
|
||||
import os
|
||||
import glob
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
import ntpath
|
||||
import re
|
||||
import hashlib
|
||||
import json
|
||||
from jinja2 import Environment, FileSystemLoader
|
||||
from ghactions_driver.readme_step import ReadmeStepsManage
|
||||
from ghactions_driver.resource_resolver import resolve_tutorial_resource
|
||||
from ghactions_driver.telemetry_obj import Telemetry
|
||||
|
||||
|
||||
def format_ipynb(notebooks):
|
||||
# run code formatter on .ipynb files
|
||||
for notebook in notebooks:
|
||||
os.system(f"black-nb --clear-output {notebook}")
|
||||
|
||||
|
||||
def _get_paths(paths_list):
|
||||
"""
|
||||
Convert the path list to unix format.
|
||||
:param paths_list: The input path list.
|
||||
:returns: The same list with unix-like paths.
|
||||
"""
|
||||
paths_list.sort()
|
||||
if ntpath.sep == os.path.sep:
|
||||
return [pth.replace(ntpath.sep, "/") for pth in paths_list]
|
||||
return paths_list
|
||||
|
||||
|
||||
def write_notebook_workflow(notebook, name, output_telemetry=Telemetry()):
|
||||
temp_name_list = re.split(r"/|\.", notebook)
|
||||
temp_name_list = [
|
||||
x
|
||||
for x in temp_name_list
|
||||
if x != "tutorials" and x != "examples" and x != "ipynb"
|
||||
]
|
||||
temp_name_list = [x.replace("-", "") for x in temp_name_list]
|
||||
workflow_name = "_".join(["samples"] + temp_name_list)
|
||||
|
||||
place_to_write = (
|
||||
Path(ReadmeStepsManage.git_base_dir())
|
||||
/ ".github"
|
||||
/ "workflows"
|
||||
/ f"{workflow_name}.yml"
|
||||
)
|
||||
|
||||
gh_working_dir = "/".join(notebook.split("/")[:-1])
|
||||
env = Environment(
|
||||
loader=FileSystemLoader("./scripts/readme/ghactions_driver/workflow_templates")
|
||||
)
|
||||
template = env.get_template("basic_workflow.yml.jinja2")
|
||||
|
||||
# Schedule notebooks at different times to reduce maximum quota usage.
|
||||
name_hash = int(hashlib.sha512(workflow_name.encode()).hexdigest(), 16)
|
||||
schedule_minute = name_hash % 60
|
||||
schedule_hour = (name_hash // 60) % 4 + 19 # 19-22 UTC
|
||||
|
||||
notebook_path = Path(ReadmeStepsManage.git_base_dir()) / str(notebook)
|
||||
try:
|
||||
# resolve tutorial resources
|
||||
path_filter = resolve_tutorial_resource(workflow_name, notebook_path.resolve(), output_telemetry)
|
||||
except Exception:
|
||||
if "examples/tutorials" in gh_working_dir:
|
||||
raise
|
||||
else:
|
||||
pass
|
||||
if "samples_configuration" in workflow_name:
|
||||
# exception, samples configuration is very simple and not related to other prompt flow examples
|
||||
path_filter = (
|
||||
"[ examples/configuration.ipynb, .github/workflows/samples_configuration.yml ]"
|
||||
)
|
||||
else:
|
||||
path_filter = f"[ {gh_working_dir}/**, examples/*requirements.txt, .github/workflows/{workflow_name}.yml ]"
|
||||
|
||||
# these workflows require config.json to init PF/ML client
|
||||
workflows_require_config_json = [
|
||||
"configuration",
|
||||
"runflowwithpipeline",
|
||||
"quickstartazure",
|
||||
"cloudrunmanagement",
|
||||
"chatwithclassbasedflowazure",
|
||||
]
|
||||
if any(keyword in workflow_name for keyword in workflows_require_config_json):
|
||||
template = env.get_template("workflow_config_json.yml.jinja2")
|
||||
elif "chatwithpdf" in workflow_name:
|
||||
template = env.get_template("pdf_workflow.yml.jinja2")
|
||||
elif "flowasfunction" in workflow_name:
|
||||
template = env.get_template("flow_as_function.yml.jinja2")
|
||||
elif "traceautogengroupchat" in workflow_name:
|
||||
template = env.get_template("autogen_workflow.yml.jinja2")
|
||||
|
||||
content = template.render(
|
||||
{
|
||||
"workflow_name": workflow_name,
|
||||
"ci_name": "samples_notebook_ci",
|
||||
"name": name,
|
||||
"gh_working_dir": gh_working_dir,
|
||||
"path_filter": path_filter,
|
||||
"crontab": f"{schedule_minute} {schedule_hour} * * *",
|
||||
"crontab_comment": f"Every day starting at {schedule_hour - 16}:{schedule_minute} BJT",
|
||||
}
|
||||
)
|
||||
|
||||
# To customize workflow, add new steps in steps.py
|
||||
# make another function for special cases.
|
||||
with open(place_to_write.resolve(), "w") as f:
|
||||
f.write(content)
|
||||
print(f"Write workflow: {place_to_write.resolve()}")
|
||||
output_telemetry.workflow_name = workflow_name
|
||||
output_telemetry.name = name
|
||||
output_telemetry.gh_working_dir = gh_working_dir
|
||||
output_telemetry.path_filter = path_filter
|
||||
|
||||
|
||||
def write_workflows(notebooks, output_telemetries=[]):
|
||||
# process notebooks
|
||||
for notebook in notebooks:
|
||||
# get notebook name
|
||||
output_telemetry = Telemetry()
|
||||
nb_path = Path(notebook)
|
||||
name, _ = os.path.splitext(nb_path.parts[-1])
|
||||
|
||||
# write workflow file
|
||||
write_notebook_workflow(notebook, name, output_telemetry)
|
||||
output_telemetry.notebook = nb_path
|
||||
output_telemetries.append(output_telemetry)
|
||||
|
||||
|
||||
def local_filter(callback, array):
|
||||
results = []
|
||||
for index, item in enumerate(array):
|
||||
result = callback(item, index, array)
|
||||
# if returned true, append item to results
|
||||
if result:
|
||||
results.append(item)
|
||||
return results
|
||||
|
||||
|
||||
def no_readme_generation_filter(item, index, array) -> bool:
|
||||
"""
|
||||
Set each ipynb metadata no_readme_generation to "true" to skip readme generation
|
||||
"""
|
||||
try:
|
||||
if item.endswith("test.ipynb"):
|
||||
return False
|
||||
if "examples/flows/integrations/" in item:
|
||||
return False
|
||||
# read in notebook
|
||||
with open(item, "r", encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
try:
|
||||
if data["metadata"]["no_readme_generation"] is not None:
|
||||
# no_readme_generate == "true", then no generation
|
||||
return data["metadata"]["no_readme_generation"] != "true"
|
||||
except Exception:
|
||||
return True # generate readme
|
||||
except Exception:
|
||||
return False # not generate readme
|
||||
|
||||
|
||||
def main(input_glob, output_files=[], check=False):
|
||||
# get list of workflows
|
||||
|
||||
notebooks = _get_paths(
|
||||
[j for i in [glob.glob(p, recursive=True) for p in input_glob] for j in i]
|
||||
)
|
||||
|
||||
# check each workflow, get metadata.
|
||||
notebooks = local_filter(no_readme_generation_filter, notebooks)
|
||||
|
||||
# format code
|
||||
if not check:
|
||||
format_ipynb(notebooks)
|
||||
|
||||
# write workflows
|
||||
write_workflows(notebooks, output_files)
|
||||
|
||||
|
||||
# run functions
|
||||
if __name__ == "__main__":
|
||||
# setup argparse
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument(
|
||||
"-g", "--input-glob", nargs="+", help="Input glob example 'examples/**/*.ipynb'"
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
# call main
|
||||
main(input_glob=args.input_glob)
|
||||
Reference in New Issue
Block a user