Skip to content

Commit

Permalink
feat: import resolve_flow_language in _internal
Browse files Browse the repository at this point in the history
  • Loading branch information
elliotzh committed Apr 26, 2024
1 parent 05d5558 commit ac0027c
Show file tree
Hide file tree
Showing 4 changed files with 101 additions and 132 deletions.
21 changes: 0 additions & 21 deletions src/promptflow-core/promptflow/_utils/flow_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,8 @@
FLOW_DAG_YAML,
FLOW_FILE_SUFFIX,
FLOW_FLEX_YAML,
LANGUAGE_KEY,
PROMPT_FLOW_DIR_NAME,
PROMPTY_EXTENSION,
FlowLanguage,
)
from promptflow._core._errors import MetaFileNotFound, MetaFileReadError
from promptflow._utils.logger_utils import LoggerFactory
Expand Down Expand Up @@ -187,25 +185,6 @@ def is_flex_flow(
return isinstance(yaml_dict, dict) and "entry" in yaml_dict


def resolve_flow_language(
*,
flow_path: Union[str, Path, PathLike, None] = None,
yaml_dict: Optional[dict] = None,
working_dir: Union[str, Path, PathLike, None] = None,
) -> str:
"""Get language of a flow. Will return 'python' for Prompty."""
if flow_path is None and yaml_dict is None:
raise UserErrorException("Either file_path or yaml_dict should be provided.")
if flow_path is not None and yaml_dict is not None:
raise UserErrorException("Only one of file_path and yaml_dict should be provided.")
if flow_path is not None:
flow_path, flow_file = resolve_flow_path(flow_path, base_path=working_dir, check_flow_exist=False)
file_path = flow_path / flow_file
if file_path.is_file() and file_path.suffix.lower() in (".yaml", ".yml"):
yaml_dict = load_yaml(file_path)
return yaml_dict.get(LANGUAGE_KEY, FlowLanguage.Python)


def is_prompty_flow(file_path: Union[str, Path], raise_error: bool = False):
"""Check if the flow is a prompty flow by extension of the flow file is .prompty."""
if not file_path or not Path(file_path).exists():
Expand Down
1 change: 1 addition & 0 deletions src/promptflow-devkit/promptflow/_internal/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
from promptflow._sdk._constants import LOCAL_MGMT_DB_PATH, CreatedByFieldName
from promptflow._sdk._service.apis.collector import trace_collector
from promptflow._sdk._tracing import process_otlp_trace_request
from promptflow._sdk._utils.general_utils import resolve_flow_language
from promptflow._sdk._version import VERSION
from promptflow._utils.context_utils import _change_working_dir, inject_sys_path
from promptflow._utils.credential_scrubber import CredentialScrubber
Expand Down
21 changes: 20 additions & 1 deletion src/promptflow-devkit/promptflow/_sdk/_utils/general_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
from keyring.errors import NoKeyringError
from marshmallow import ValidationError

from promptflow._constants import ENABLE_MULTI_CONTAINER_KEY, EXTENSION_UA, FLOW_FLEX_YAML, FlowLanguage
from promptflow._constants import ENABLE_MULTI_CONTAINER_KEY, EXTENSION_UA, FLOW_FLEX_YAML, LANGUAGE_KEY, FlowLanguage
from promptflow._core.entry_meta_generator import generate_flow_meta as _generate_flow_meta
from promptflow._sdk._constants import (
AZURE_WORKSPACE_REGEX_FORMAT,
Expand Down Expand Up @@ -1103,3 +1103,22 @@ def load_input_data(data_path):
return json.load(f)
else:
raise ValueError("Only support jsonl or json file as input.")


def resolve_flow_language(
*,
flow_path: Union[str, Path, PathLike, None] = None,
yaml_dict: Optional[dict] = None,
working_dir: Union[str, Path, PathLike, None] = None,
) -> str:
"""Get language of a flow. Will return 'python' for Prompty."""
if flow_path is None and yaml_dict is None:
raise UserErrorException("Either file_path or yaml_dict should be provided.")
if flow_path is not None and yaml_dict is not None:
raise UserErrorException("Only one of file_path and yaml_dict should be provided.")
if flow_path is not None:
flow_path, flow_file = resolve_flow_path(flow_path, base_path=working_dir, check_flow_exist=False)
file_path = flow_path / flow_file
if file_path.is_file() and file_path.suffix.lower() in (".yaml", ".yml"):
yaml_dict = load_yaml(file_path)
return yaml_dict.get(LANGUAGE_KEY, FlowLanguage.Python)
190 changes: 80 additions & 110 deletions src/promptflow-devkit/promptflow/_sdk/_utils/serve_utils.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import abc
import contextlib
import json
import logging
Expand All @@ -8,13 +7,15 @@
import subprocess
import sys
import tempfile
import uuid
import webbrowser
from pathlib import Path
from typing import Any, Dict
from typing import Any, Dict, Generator

from promptflow._constants import FlowLanguage
from promptflow._constants import PROMPT_FLOW_DIR_NAME, FlowLanguage
from promptflow._proxy._csharp_inspector_proxy import EXECUTOR_SERVICE_DLL
from promptflow._utils.flow_utils import resolve_flow_language, resolve_flow_path
from promptflow._sdk._utils.general_utils import resolve_flow_language
from promptflow._utils.flow_utils import resolve_flow_path

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -68,7 +69,7 @@ def start_flow_service(

os.environ["PROMPTFLOW_PROJECT_PATH"] = flow_dir.absolute().as_posix()
if language == FlowLanguage.Python:
helper = PythonFlowServiceHelper(
serve_python_flow(
flow_file_name=flow_file_name,
flow_dir=flow_dir,
init=init or {},
Expand All @@ -80,120 +81,89 @@ def start_flow_service(
skip_open_browser=skip_open_browser,
)
else:
helper = CSharpFlowServiceHelper(
serve_csharp_flow(
flow_file_name=flow_file_name,
flow_dir=flow_dir,
init=init or {},
port=port,
)
helper.run()


class BaseFlowServiceHelper:
def __init__(self):
pass

@abc.abstractmethod
def run(self):
pass
def serve_python_flow(
*,
flow_file_name,
flow_dir,
port,
host,
static_folder,
config,
environment_variables,
init,
skip_open_browser: bool,
):
from promptflow._sdk._configuration import Configuration
from promptflow.core._serving.app import create_app

flow_dir = _resolve_python_flow_additional_includes(flow_dir / flow_file_name)

class PythonFlowServiceHelper(BaseFlowServiceHelper):
def __init__(
self,
*,
pf_config = Configuration(overrides=config)
logger.info(f"Promptflow config: {pf_config}")
connection_provider = pf_config.get_connection_provider()
os.environ["PROMPTFLOW_PROJECT_PATH"] = flow_dir.absolute().as_posix()
logger.info(f"Change working directory to model dir {flow_dir}")
os.chdir(flow_dir)
app = create_app(
static_folder=Path(static_folder).absolute().as_posix() if static_folder else None,
environment_variables=environment_variables,
connection_provider=connection_provider,
init=init,
)
if not skip_open_browser:
target = f"http://{host}:{port}"
logger.info(f"Opening browser {target}...")
webbrowser.open(target)
# Debug is not supported for now as debug will rerun command, and we changed working directory.
app.run(port=port, host=host)


@contextlib.contextmanager
def construct_csharp_service_start_up_command(
*, port: int, flow_file_name: str, flow_dir: Path, init: Dict[str, Any] = None
) -> Generator[str, None, None]:
cmd = [
"dotnet",
EXECUTOR_SERVICE_DLL,
"--port",
str(port),
"--yaml_path",
flow_file_name,
flow_dir,
port,
host,
static_folder,
config,
environment_variables,
init,
skip_open_browser: bool,
):
self._static_folder = static_folder
self.flow_file_name = flow_file_name
self.flow_dir = flow_dir
self.host = host
self.port = port
self.config = config
self.environment_variables = environment_variables
self.init = init
self.skip_open_browser = skip_open_browser
super().__init__()

@property
def static_folder(self):
if self._static_folder is None:
return None
return Path(self._static_folder).absolute().as_posix()

def run(self):
from promptflow._sdk._configuration import Configuration
from promptflow.core._serving.app import create_app

flow_dir = _resolve_python_flow_additional_includes(self.flow_dir / self.flow_file_name)

pf_config = Configuration(overrides=self.config)
logger.info(f"Promptflow config: {pf_config}")
connection_provider = pf_config.get_connection_provider()
os.environ["PROMPTFLOW_PROJECT_PATH"] = flow_dir.absolute().as_posix()
logger.info(f"Change working directory to model dir {flow_dir}")
os.chdir(flow_dir)
app = create_app(
static_folder=self.static_folder,
environment_variables=self.environment_variables,
connection_provider=connection_provider,
init=self.init,
)
if not self.skip_open_browser:
target = f"http://{self.host}:{self.port}"
logger.info(f"Opening browser {target}...")
webbrowser.open(target)
# Debug is not supported for now as debug will rerun command, and we changed working directory.
app.run(port=self.port, host=self.host)


class CSharpFlowServiceHelper(BaseFlowServiceHelper):
def __init__(self, *, flow_file_name, flow_dir, init, port):
self.port = port
self._init = init
self.flow_dir, self.flow_file_name = flow_dir, flow_file_name
super().__init__()

@contextlib.contextmanager
def _construct_command(self):
cmd = [
"dotnet",
EXECUTOR_SERVICE_DLL,
"--port",
str(self.port),
"--yaml_path",
self.flow_file_name,
"--assembly_folder",
".",
"--connection_provider_url",
"",
"--log_path",
"",
"--serving",
]
if self._init:
init_json_path = Path(tempfile.mktemp()).with_suffix(".json")
with open(init_json_path, "w") as f:
json.dump(self._init, f)
cmd.extend(["--init", init_json_path.as_posix()])
try:
yield cmd
finally:
os.remove(init_json_path)
else:
"--assembly_folder",
".",
"--connection_provider_url",
"",
"--log_path",
"",
"--serving",
]
if init:
init_json_path = flow_dir / PROMPT_FLOW_DIR_NAME / f"init-{uuid.uuid4()}.json"
init_json_path.parent.mkdir(parents=True, exist_ok=True)
with open(init_json_path, "w") as f:
json.dump(init, f)
cmd.extend(["--init", init_json_path.as_posix()])
try:
yield cmd
finally:
os.remove(init_json_path)
else:
yield cmd

def run(self):
try:
with self._construct_command() as command:
subprocess.run(command, cwd=self.flow_dir, stdout=sys.stdout, stderr=sys.stderr)
except KeyboardInterrupt:
pass

def serve_csharp_flow(flow_dir: Path, port: int, flow_file_name: str, init: Dict[str, Any] = None):
try:
with construct_csharp_service_start_up_command(
port=port, flow_file_name=flow_file_name, flow_dir=flow_dir, init=init
) as command:
subprocess.run(command, cwd=flow_dir, stdout=sys.stdout, stderr=sys.stderr)
except KeyboardInterrupt:
pass

0 comments on commit ac0027c

Please sign in to comment.