Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PF Recording/Replaying part for sdk_cli #719

Closed
wants to merge 61 commits into from
Closed
Show file tree
Hide file tree
Changes from 43 commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
523c02e
TEMP PASS
crazygao Sep 28, 2023
aa959ee
Current Fix
crazygao Oct 11, 2023
37229ad
Fix
crazygao Oct 13, 2023
9973fa0
Remove ununsed items
crazygao Oct 13, 2023
d311941
Fix
crazygao Oct 13, 2023
e52fc0e
Merge branch 'main' into yigao/recording_draft
crazygao Oct 13, 2023
dc4d181
Fix
crazygao Oct 16, 2023
26b07a7
Fix
crazygao Oct 16, 2023
48e6d07
Fix Connection Override
crazygao Oct 16, 2023
632dd31
Commit
crazygao Oct 17, 2023
6d5c03f
Fix
crazygao Oct 17, 2023
d5d608a
Fix
crazygao Oct 17, 2023
9f0eeee
Fix
crazygao Oct 18, 2023
f3e6cd9
Merge branch 'main' into yigao/recording_draft
crazygao Oct 18, 2023
f74a03b
Fix
crazygao Oct 20, 2023
5131bd7
Merge branch 'main' into yigao/recording_draft
crazygao Oct 20, 2023
48b5355
Fix
crazygao Oct 20, 2023
57d10c4
Fix
crazygao Oct 20, 2023
bdf4981
Fix Names
crazygao Oct 20, 2023
6d49afc
Fix pipeline
crazygao Oct 20, 2023
215f57e
Fix Upload
crazygao Oct 20, 2023
ca5081b
Fix upload
crazygao Oct 20, 2023
930bcb8
Fix
crazygao Oct 20, 2023
f5ef438
get items
crazygao Oct 20, 2023
da1f5a0
Fix
crazygao Oct 20, 2023
13e53aa
Fix Mode
crazygao Oct 20, 2023
f2101fd
Fix
crazygao Oct 23, 2023
aaaadb9
Merge branch 'main' into yigao/recording_draft
crazygao Oct 23, 2023
79ff7b6
Fix
crazygao Oct 23, 2023
c8cf75a
Merge branch 'main' into yigao/recording_draft
crazygao Oct 23, 2023
6673310
Fix Comments
crazygao Oct 23, 2023
f38a89f
Fix nits
crazygao Oct 23, 2023
8a6b456
Fix tests
crazygao Oct 23, 2023
f118d7b
Merge branch 'main' into yigao/recording_draft
crazygao Oct 23, 2023
38a9206
Fix test
crazygao Oct 23, 2023
6f9c0a1
fix nits
crazygao Oct 23, 2023
fa10237
Fix
crazygao Oct 23, 2023
ad4a4e3
Fix
crazygao Oct 24, 2023
55761ba
Fix
crazygao Oct 24, 2023
b40ddc8
Fix
crazygao Oct 24, 2023
2b69b18
Fix
crazygao Oct 24, 2023
b6f75ec
Fix
crazygao Oct 24, 2023
d682da9
Fix
crazygao Oct 24, 2023
9e918e5
refactor
crazygao Oct 25, 2023
a92fd7c
Merge branch 'main' into yigao/recording_draft
crazygao Oct 25, 2023
9fff444
no changing production
crazygao Oct 25, 2023
b808a53
refactor
crazygao Oct 26, 2023
dab04b5
Merge branch 'main' into yigao/recording_draft
crazygao Oct 26, 2023
73fe306
Mark instable in replay mode
crazygao Oct 26, 2023
f0a3162
Mark Instable, 24 tests in total
crazygao Oct 26, 2023
45bd5b7
Fix
crazygao Oct 26, 2023
471a966
Fix
crazygao Oct 27, 2023
9fa75df
Merge branch 'main' into yigao/recording_draft
crazygao Oct 27, 2023
cb64c6c
Fix
crazygao Oct 27, 2023
14e4b95
Fix
crazygao Oct 27, 2023
3456ed6
Fix Comments
crazygao Oct 30, 2023
2086f19
Merge branch 'main' into yigao/recording_draft
crazygao Oct 30, 2023
5fb93e0
Fix error
crazygao Oct 30, 2023
4ff7d8d
Fix Comments
crazygao Oct 30, 2023
3a36c7e
Fix nit
crazygao Oct 30, 2023
d3b4c4a
Merge branch 'main' into yigao/recording_draft
crazygao Oct 30, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/actions/step_sdk_setup/action.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: step_sdk_setup_win
name: step_sdk_setup
inputs:
scriptPath:
required: false
Expand Down Expand Up @@ -30,7 +30,7 @@ runs:
if: inputs.setupType == 'promptflow_with_extra'
shell: pwsh
run: |
Set-PSDebug -Trace 2
Set-PSDebug -Trace 1
pip install -r ./dev_requirements.txt
echo "########### pip list (Before) ###########"
pip list
Expand All @@ -44,7 +44,7 @@ runs:
if: inputs.setupType == 'promptflow_dev'
shell: pwsh
run: |
Set-PSDebug -Trace 2
Set-PSDebug -Trace 1
pip install -r ./dev_requirements.txt
python ./setup.py bdist_wheel
$package = Get-ChildItem ./dist | ? { $_.Name.Contains('.whl')}
Expand Down
105 changes: 105 additions & 0 deletions .github/workflows/promptflow-replay-test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
name: promptflow-replay-test
on:
pull_request:
paths:
- src/promptflow/**
- scripts/**
workflow_dispatch:
env:
testWorkingDirectory: ${{ github.workspace }}/src/promptflow
PYTHONPATH: ${{ github.workspace }}/src/promptflow
IS_IN_CI_PIPELINE: "true"
PF_RECORDING_MODE: "replay"
crazygao marked this conversation as resolved.
Show resolved Hide resolved
jobs:
build:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest]
runs-on: ${{ matrix.os }}
steps:
- name: checkout
uses: actions/checkout@v3
- name: Display and Set Environment Variables
run: |
export pyVersion="3.9";
env | sort >> $GITHUB_OUTPUT
id: display_env
shell: bash -el {0}
- name: Python Setup - ${{ matrix.os }} - Python Version ${{ steps.display_env.outputs.pyVersion }}
uses: "./.github/actions/step_create_python_environment"
with:
pythonVersion: ${{ steps.display_env.outputs.pyVersion }}
- name: Build wheel
uses: "./.github/actions/step_sdk_setup"
with:
setupType: promptflow_with_extra
scriptPath: ${{ env.testWorkingDirectory }}
- name: Upload Wheel
if: always()
uses: actions/upload-artifact@v3
with:
name: wheel
path: |
${{ github.workspace }}/src/promptflow/dist/*.whl
${{ github.workspace }}/src/promptflow-tools/dist/*.whl
sdk_cli_tests:
needs: build
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest]
runs-on: ${{ matrix.os }}
steps:
- name: checkout
uses: actions/checkout@v3
- name: Display and Set Environment Variables
run: |
export pyVersion="3.9";
env | sort >> $GITHUB_OUTPUT
id: display_env
shell: bash -el {0}
- name: Python Setup - ${{ matrix.os }} - Python Version ${{ steps.display_env.outputs.pyVersion }}
uses: "./.github/actions/step_create_python_environment"
with:
pythonVersion: ${{ steps.display_env.outputs.pyVersion }}
- name: Download Artifacts
uses: actions/download-artifact@v3
with:
name: wheel
path: artifacts
- name: Install wheel
shell: pwsh
working-directory: artifacts
run: |
Set-PSDebug -Trace 1
python -m pip install --upgrade pip
crazygao marked this conversation as resolved.
Show resolved Hide resolved
python -m pip install --upgrade setuptools
python -m pip install --upgrade nox
python -m pip install --upgrade mock
python -m pip install --upgrade pytest-cov
python -m pip install --upgrade coverage==6.0.1
python -m pip install --upgrade pytest-forked
python -m pip install --upgrade pytest-xdist
python -m pip install --upgrade pytest-timeout
python -m pip install --upgrade pytest-mock
python -m pip install --upgrade pytest-nunit
python -m pip install --upgrade pytest
python -m pip install --upgrade pydash
python -m pip install --upgrade wheel
python -m pip install --upgrade keyrings.alt
python -m pip install --upgrade beautifulsoup4==4.12.2
gci ./promptflow -Recurse | % {if ($_.Name.Contains('.whl')) {python -m pip install "$($_.FullName)[azure]"}}
gci ./promptflow-tools -Recurse | % {if ($_.Name.Contains('.whl')) {python -m pip install $_.FullName}}
pip freeze
- name: Get number of CPU cores
uses: SimenB/github-actions-cpu-cores@v1
id: cpu-cores
- name: Run Test
shell: pwsh
working-directory: ${{ env.testWorkingDirectory }}
run: |
gci env:* | sort-object name
$env:PYTHONPATH=""
echo '{}' > ${{ github.workspace }}/src/promptflow/connections.json
pytest ./tests/sdk_cli_test/ -n auto --dist loadfile
4 changes: 1 addition & 3 deletions scripts/building/run_coverage_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
help="Pytest marker to identify the tests to run",
default="all",
)
parser.add_argument("-n", help="Pytest number of process to run the tests", default="15")
parser.add_argument("-n", help="Pytest number of process to run the tests", default="auto")
crazygao marked this conversation as resolved.
Show resolved Hide resolved
parser.add_argument(
"--model-name",
help="The model file name to run the tests",
Expand Down Expand Up @@ -71,8 +71,6 @@
pytest_command += [
"-n",
args.n,
"--dist",
crazygao marked this conversation as resolved.
Show resolved Hide resolved
"loadgroup",
"--log-level=info",
"--log-format=%(asctime)s %(levelname)s %(message)s",
"--log-date-format=[%Y-%m-%d %H:%M:%S]",
Expand Down
4 changes: 4 additions & 0 deletions src/promptflow/promptflow/_core/run_tracker.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
# ---------------------------------------------------------

import json
import os
from contextvars import ContextVar
from datetime import datetime
from types import GeneratorType
Expand Down Expand Up @@ -175,6 +176,9 @@ def _update_flow_run_info_with_node_runs(self, run_info):
child_run_infos = self.collect_child_node_runs(run_id)
run_info.system_metrics = run_info.system_metrics or {}
run_info.system_metrics.update(self.collect_metrics(child_run_infos, self.OPENAI_AGGREGATE_METRICS))
if os.environ.get("PF_RECORDING_MODE", None) == "replay":
crazygao marked this conversation as resolved.
Show resolved Hide resolved
# some tests require this metric to be set.
run_info.system_metrics["total_tokens"] = 0

def _node_run_postprocess(self, run_info: RunInfo, output, ex: Optional[Exception]):
run_id = run_info.run_id
Expand Down
36 changes: 36 additions & 0 deletions src/promptflow/promptflow/_core/tool_record.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# ---------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# ---------------------------------------------------------

import collections

from promptflow._internal import RecordStorage, ToolProvider, tool


class ToolRecord(ToolProvider):
crazygao marked this conversation as resolved.
Show resolved Hide resolved
"""
ToolRecord Record inputs and outputs of llm tool, in replay mode,
this tool will read the cached result from storage_record.json
"""

@tool
def completion(toolType: str, *args, **kwargs) -> str:
# "AzureOpenAI" = args[0], this is type indicator, there may be more than one indicators
prompt_tmpl = args[1]
prompt_tpl_inputs = args[2]
working_folder = args[3]

hashDict = {}
for keyword in prompt_tpl_inputs:
if keyword in kwargs:
hashDict[keyword] = kwargs[keyword]
hashDict["prompt"] = prompt_tmpl
hashDict = collections.OrderedDict(sorted(hashDict.items()))

real_item = RecordStorage.get_record(working_folder, hashDict)
return real_item


@tool
def just_return(toolType: str, *args, **kwargs) -> str:
return ToolRecord().completion(toolType, *args, **kwargs)
1 change: 1 addition & 0 deletions src/promptflow/promptflow/_internal/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@
)
from promptflow._utils.utils import (
AttrDict,
RecordStorage,
camel_to_snake,
count_and_log_progress,
load_json,
Expand Down
30 changes: 28 additions & 2 deletions src/promptflow/promptflow/_sdk/_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
USE_VARIANTS,
VARIANTS,
CommonYamlFields,
ConnectionType,
)
from promptflow._sdk._errors import (
ConnectionNotFoundError,
Expand All @@ -62,9 +63,30 @@
from promptflow._sdk._vendor import IgnoreFile, get_ignore_file, get_upload_files_from_folder
from promptflow._utils.context_utils import _change_working_dir, inject_sys_path
from promptflow._utils.dataclass_serializer import serialize
from promptflow._utils.tool_utils import get_inputs_for_prompt_template
from promptflow._utils.utils import RecordStorage
from promptflow.contracts.run_info import RunInfo as NodeRunInfo
from promptflow.contracts.tool import ToolType


def record_node_run(run_info: NodeRunInfo, flow_folder: Path) -> None:
"""Persist node run record to local storage."""
if os.environ.get("PF_RECORDING_MODE", None) == "record":
crazygao marked this conversation as resolved.
Show resolved Hide resolved
for api_call in run_info.api_calls:
hashDict = {}
if "name" in api_call and api_call["name"].startswith("AzureOpenAI"):
prompt_tpl = api_call["inputs"]["prompt"]
prompt_tpl_inputs = get_inputs_for_prompt_template(prompt_tpl)

for keyword in prompt_tpl_inputs:
if keyword in api_call["inputs"]:
hashDict[keyword] = api_call["inputs"][keyword]
hashDict["prompt"] = prompt_tpl
hashDict = collections.OrderedDict(sorted(hashDict.items()))
item = serialize(run_info)
RecordStorage.set_record(flow_folder, hashDict, str(item["output"]))


def snake_to_camel(name):
return re.sub(r"(?:^|_)([a-z])", lambda x: x.group(1).upper(), name)

Expand Down Expand Up @@ -639,8 +661,7 @@ def _gen_dynamic_list(function_config: Dict) -> List:
from promptflow._cli._utils import get_workspace_triad_from_local

workspace_triad = get_workspace_triad_from_local()
if (workspace_triad.subscription_id and workspace_triad.resource_group_name
and workspace_triad.workspace_name):
if workspace_triad.subscription_id and workspace_triad.resource_group_name and workspace_triad.workspace_name:
return gen_dynamic_list(func_path, func_kwargs, workspace_triad._asdict())
# if no workspace triple available, just skip.
else:
Expand Down Expand Up @@ -813,8 +834,13 @@ def get_local_connections_from_executable(executable, client):
for n in connection_names:
try:
conn = client.connections.get(name=n, with_secrets=True)
if conn is not None and conn.TYPE == ConnectionType.AZURE_OPEN_AI and conn.api_base == "dummy_base":
if os.environ.get("PF_RECORDING_MODE", None) == "replay":
return {}
result[n] = conn._to_execution_connection_dict()
except ConnectionNotFoundError:
if os.environ.get("PF_RECORDING_MODE", None) == "replay":
return result
# ignore when connection not found since it can be configured with env var.
raise Exception(f"Connection {n!r} required for flow {executable.name!r} is not found.")
return result
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
get_run_output_path,
)
from promptflow._sdk._errors import BulkRunException
from promptflow._sdk._utils import generate_flow_tools_json
from promptflow._sdk._utils import generate_flow_tools_json, record_node_run
from promptflow._sdk.entities import Run
from promptflow._sdk.entities._flow import Flow
from promptflow._utils.dataclass_serializer import serialize
Expand Down Expand Up @@ -204,6 +204,8 @@ def __init__(self, run: Run, stream=False, run_mode=RunMode.Test):
self._meta_path = self.path / LocalStorageFilenames.META
self._exception_path = self.path / LocalStorageFilenames.EXCEPTION

self._flow_path: Path = run.flow

self._dump_meta_file()

def _dump_meta_file(self) -> None:
Expand Down Expand Up @@ -366,6 +368,7 @@ def persist_node_run(self, run_info: NodeRunInfo) -> None:
line_number = 0 if node_run_record.line_number is None else node_run_record.line_number
filename = f"{str(line_number).zfill(self.LINE_NUMBER_WIDTH)}.jsonl"
node_run_record.dump(node_folder / filename, run_name=self._run.name)
record_node_run(run_info, self._flow_path)

def persist_flow_run(self, run_info: FlowRunInfo) -> None:
"""Persist line run record to local storage."""
Expand Down
4 changes: 3 additions & 1 deletion src/promptflow/promptflow/_sdk/operations/_test_submitter.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from typing import Any, Mapping

from promptflow._sdk._constants import LOGGER_NAME, PROMPT_FLOW_DIR_NAME
from promptflow._sdk._utils import dump_flow_result, parse_variant
from promptflow._sdk._utils import dump_flow_result, parse_variant, record_node_run
from promptflow._sdk.entities._flow import Flow
from promptflow._sdk.operations._local_storage_operations import LoggerOperations
from promptflow._sdk.operations._run_submitter import SubmitterHelper, variant_overwrite_context
Expand Down Expand Up @@ -177,6 +177,7 @@ def flow_test(
generator_outputs = self._get_generator_outputs(line_result.output)
if generator_outputs:
logger.info(f"Some streaming outputs in the result, {generator_outputs.keys()}")
record_node_run(line_result.run_info, self._origin_flow.code)
return line_result

def node_test(
Expand Down Expand Up @@ -204,6 +205,7 @@ def node_test(
working_dir=self.flow.code,
output_sub_dir=".promptflow/intermediate",
)
record_node_run(result, self._origin_flow.code)
return result

def _chat_flow(self, inputs, chat_history_name, environment_variables: dict = None, show_step_output=False):
Expand Down
Loading
Loading