Skip to content

Commit

Permalink
Csharp flow test in ui mode (#1221)
Browse files Browse the repository at this point in the history
# Description
- support csharp flow test --ui
- change jinja template to python
- using main.py as streamlit script for chat/standard flow in ui mode

![image](https://github.com/microsoft/promptflow/assets/26239730/3306d22b-50ee-47c4-9052-00076a93bf95)

![image](https://github.com/microsoft/promptflow/assets/26239730/6cde6fb8-53e9-4565-9aed-e57a6952f561)

Please add an informative description that covers that changes made by
the pull request and link all relevant issues.

# All Promptflow Contribution checklist:
- [ ] **The pull request does not introduce [breaking changes].**
- [ ] **CHANGELOG is updated for new features, bug fixes or other
significant changes.**
- [ ] **I have read the [contribution guidelines](../CONTRIBUTING.md).**
- [ ] **Create an issue and link to the pull request to get dedicated
review from promptflow team. Learn more: [suggested
workflow](../CONTRIBUTING.md#suggested-workflow).**

## General Guidelines and Best Practices
- [ ] Title of the pull request is clear and informative.
- [ ] There are a small number of commits, each of which have an
informative message. This means that previously merged commits do not
appear in the history of the PR. For more information on cleaning up the
commits in your PR, [see this
page](https://github.com/Azure/azure-powershell/blob/master/documentation/development-docs/cleaning-up-commits.md).

### Testing Guidelines
- [ ] Pull request includes test coverage for the included changes.

---------

Co-authored-by: Ying Chen <[email protected]>
Co-authored-by: zhangxingzhi <[email protected]>
  • Loading branch information
3 people authored Nov 25, 2023
1 parent bcfdd23 commit 8fc1184
Show file tree
Hide file tree
Showing 8 changed files with 256 additions and 163 deletions.
2 changes: 1 addition & 1 deletion scripts/installer/windows/scripts/promptflow.spec
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ from PyInstaller.utils.hooks import collect_data_files
from PyInstaller.utils.hooks import copy_metadata

datas = [('../resources/CLI_LICENSE.rtf', '.'), ('../../../../src/promptflow/NOTICE.txt', '.'),
('../../../../src/promptflow/promptflow/_sdk/data/executable/utils.py', './promptflow/_sdk/data/executable/')]
('../../../../src/promptflow/promptflow/_sdk/data/executable/', './promptflow/_sdk/data/executable/')]

datas += collect_data_files('streamlit')
datas += copy_metadata('streamlit')
Expand Down
7 changes: 3 additions & 4 deletions src/promptflow/promptflow/_cli/_pf/_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
ChatFlowDAGGenerator,
FlowDAGGenerator,
OpenAIConnectionGenerator,
StreamlitFileGenerator,
StreamlitFileReplicator,
ToolMetaGenerator,
ToolPyGenerator,
copy_extra_files,
Expand Down Expand Up @@ -389,10 +389,9 @@ def test_flow(args):
os.path.join(temp_dir, "logo.png"),
]
for script in script_path:
StreamlitFileGenerator(
flow_name=flow.name,
StreamlitFileReplicator(
flow_name=flow.display_name if flow.display_name else flow.name,
flow_dag_path=flow.flow_dag_path,
connection_provider=pf_client._ensure_connection_provider(),
).generate_to_file(script)
main_script_path = os.path.join(temp_dir, "main.py")
pf_client.flows._chat_with_ui(script=main_script_path)
Expand Down
63 changes: 29 additions & 34 deletions src/promptflow/promptflow/_cli/_pf/_init_entry_generators.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
# ---------------------------------------------------------

import inspect
import json
import logging
import shutil
from abc import ABC, abstractmethod
Expand Down Expand Up @@ -223,45 +224,39 @@ def entry_template_keys(self):
return ["flow_name"]


class StreamlitFileGenerator(BaseGenerator):
def __init__(self, flow_name, flow_dag_path, connection_provider):
class StreamlitFileReplicator:
def __init__(self, flow_name, flow_dag_path):
self.flow_name = flow_name
self.flow_dag_path = Path(flow_dag_path)
self.connection_provider = connection_provider
self.executable = ExecutableFlow.from_yaml(
flow_file=Path(self.flow_dag_path.name), working_dir=self.flow_dag_path.parent
)
self.is_chat_flow, self.chat_history_input_name, error_msg = FlowOperations._is_chat_flow(self.executable)
if not self.is_chat_flow:
raise UserErrorException(f"Only support chat flow in ui mode, {error_msg}.")
self._chat_input_name = next(
(flow_input for flow_input, value in self.executable.inputs.items() if value.is_chat_input), None
)
self._chat_input = self.executable.inputs[self._chat_input_name]
if self._chat_input.type not in [ValueType.STRING.value, ValueType.LIST.value]:
raise UserErrorException(
f"Only support string or list type for chat input, but got {self._chat_input.type}."
)

@property
def chat_input_default_value(self):
return self._chat_input.default

@property
def chat_input_value_type(self):
return self._chat_input.type

@property
def chat_input_name(self):
return self._chat_input_name
def flow_inputs(self):
if self.is_chat_flow:
results = {}
for flow_input, value in self.executable.inputs.items():
if value.is_chat_input:
if value.type.value not in [ValueType.STRING.value, ValueType.LIST.value]:
raise UserErrorException(
f"Only support string or list type for chat input, but got {value.type.value}."
)
results.update({flow_input: (value.default, value.type.value)})
else:
results = {
flow_input: (value.default, value.type.value) for flow_input, value in self.executable.inputs.items()
}
return results

@property
def flow_inputs_params(self):
return f"{self.chat_input_name}={self.chat_input_name}"
def label(self):
return "Chat" if self.is_chat_flow else "Run"

@property
def tpl_file(self):
return SERVE_TEMPLATE_PATH / "flow_test_main.py.jinja2"
def py_file(self):
return SERVE_TEMPLATE_PATH / "main.py"

@property
def flow_path(self):
Expand All @@ -271,20 +266,20 @@ def flow_path(self):
def entry_template_keys(self):
return [
"flow_name",
"chat_input_name",
"flow_inputs_params",
"flow_path",
"is_chat_flow",
"chat_history_input_name",
"connection_provider",
"chat_input_default_value",
"chat_input_value_type",
"chat_input_name",
"flow_inputs",
"label",
]

def generate_to_file(self, target):
if Path(target).name == "main.py":
super().generate_to_file(target=target)
target = Path(target).resolve()
shutil.copy(self.py_file, target)
config_content = {key: getattr(self, key) for key in self.entry_template_keys}
with open(target.parent / "config.json", "w") as file:
json.dump(config_content, file, indent=4)
else:
shutil.copy(SERVE_TEMPLATE_PATH / Path(target).name, target)

Expand Down
30 changes: 29 additions & 1 deletion src/promptflow/promptflow/_sdk/_submitter/test_submitter.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
from promptflow.contracts.run_info import Status
from promptflow.exceptions import UserErrorException
from promptflow.storage._run_storage import DefaultRunStorage
from promptflow.batch._csharp_executor_proxy import CSharpExecutorProxy

from .utils import SubmitterHelper, variant_overwrite_context

Expand Down Expand Up @@ -409,7 +410,6 @@ def flow_test(
):

from promptflow._constants import LINE_NUMBER_KEY
from promptflow.batch import CSharpExecutorProxy

if not connections:
connections = SubmitterHelper.resolve_connection_names_from_tool_meta(
Expand Down Expand Up @@ -463,3 +463,31 @@ def flow_test(
return line_result
finally:
flow_executor.destroy()

def exec_with_inputs(self, inputs):
from promptflow._constants import LINE_NUMBER_KEY

try:
connections = SubmitterHelper.resolve_connection_names_from_tool_meta(
tools_meta=CSharpExecutorProxy.generate_tool_metadata(
flow_dag=self.flow.dag,
working_dir=self.flow.code,
)
)
storage = DefaultRunStorage(base_dir=self.flow.code, sub_dir=Path(".promptflow/intermediate"))
flow_executor = CSharpExecutorProxy.create(
flow_file=self.flow.path,
working_dir=self.flow.code,
connections=connections,
storage=storage,
)
# validate inputs
flow_inputs, _ = self.resolve_data(inputs=inputs, dataplane_flow=self.dataplane_flow)
line_result = asyncio.run(flow_executor.exec_line_async(inputs, index=0))
# line_result = flow_executor.exec_line(inputs, index=0)
if isinstance(line_result.output, dict):
# Remove line_number from output
line_result.output.pop(LINE_NUMBER_KEY, None)
return line_result
finally:
flow_executor.destroy()

This file was deleted.

Loading

0 comments on commit 8fc1184

Please sign in to comment.