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,44 @@
|
||||
---
|
||||
resources: examples/tutorials/flow-deploy/create-service-with-flow
|
||||
category: deployment
|
||||
weight: 10
|
||||
---
|
||||
|
||||
# Create service with flow
|
||||
|
||||
This example shows how to create a simple service with flow.
|
||||
|
||||
You can create your own service by utilize `flow-as-function`.
|
||||
|
||||
This folder contains a example on how to build a service with a flow.
|
||||
Reference [here](./simple_score.py) for a minimal service example.
|
||||
The output of score.py will be a json serialized dictionary.
|
||||
You can use json parser to parse the output.
|
||||
|
||||
## 1. Start the service and put in background
|
||||
|
||||
```bash
|
||||
nohup python simple_score.py &
|
||||
# Note: added this to run in our CI pipeline, not needed for user.
|
||||
sleep 10
|
||||
```
|
||||
|
||||
## 2. Test the service with request
|
||||
|
||||
Executing the following command to send a request to execute a flow.
|
||||
|
||||
```bash
|
||||
curl -X POST http://127.0.0.1:5000/score --header "Content-Type: application/json" --data '{"flow_input": "some_flow_input", "node_input": "some_node_input"}'
|
||||
```
|
||||
|
||||
Sample output of the request:
|
||||
|
||||
```json
|
||||
{
|
||||
"output": {
|
||||
"value": "some_flow_input"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Reference [here](./simple_score.py) for more.
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
from promptflow.core import tool
|
||||
from promptflow.connections import AzureOpenAIConnection
|
||||
|
||||
|
||||
@tool
|
||||
def echo_connection(flow_input: str, node_input: str, connection: AzureOpenAIConnection):
|
||||
print(f"Flow input: {flow_input}")
|
||||
print(f"Node input: {node_input}")
|
||||
print(f"Flow connection: {connection._to_dict()}")
|
||||
# get from env var
|
||||
return {"value": flow_input}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
$schema: https://azuremlschemas.azureedge.net/promptflow/latest/Flow.schema.json
|
||||
inputs:
|
||||
flow_input:
|
||||
type: string
|
||||
outputs:
|
||||
output:
|
||||
type: object
|
||||
reference: ${echo_connection.output}
|
||||
nodes:
|
||||
- name: echo_connection
|
||||
type: python
|
||||
source:
|
||||
type: code
|
||||
path: echo_connection.py
|
||||
inputs:
|
||||
flow_input: ${inputs.flow_input}
|
||||
node_input: "dummy_node_input"
|
||||
connection: open_ai_connection
|
||||
@@ -0,0 +1,85 @@
|
||||
import json
|
||||
import logging
|
||||
|
||||
from flask import Flask, jsonify, request
|
||||
|
||||
from promptflow.client import load_flow
|
||||
from promptflow.connections import AzureOpenAIConnection
|
||||
from promptflow.entities import FlowContext
|
||||
from promptflow.exceptions import SystemErrorException, UserErrorException
|
||||
|
||||
|
||||
class SimpleScoreApp(Flask):
|
||||
pass
|
||||
|
||||
|
||||
app = SimpleScoreApp(__name__)
|
||||
logging.basicConfig(format="%(threadName)s:%(message)s")
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.setLevel(logging.INFO)
|
||||
# load flow as a function, the function object can be shared accross threads.
|
||||
f = load_flow("./echo_connection_flow/")
|
||||
|
||||
|
||||
@app.errorhandler(Exception)
|
||||
def handle_error(e):
|
||||
if isinstance(e, UserErrorException):
|
||||
return jsonify({"message": e.message, "additional_info": e.additional_info}), 400
|
||||
elif isinstance(e, SystemErrorException):
|
||||
return jsonify({"message": e.message, "additional_info": e.additional_info}), 500
|
||||
else:
|
||||
from promptflow._internal import ErrorResponse, ExceptionPresenter
|
||||
|
||||
# handle other unexpected errors, can use internal class to format them
|
||||
# but interface may change in the future
|
||||
presenter = ExceptionPresenter.create(e)
|
||||
trace_back = presenter.formatted_traceback
|
||||
|
||||
resp = ErrorResponse(presenter.to_dict(include_debug_info=False))
|
||||
response_code = resp.response_code
|
||||
result = resp.to_simplified_dict()
|
||||
result.update({"trace_back": trace_back})
|
||||
return jsonify(result), response_code
|
||||
|
||||
|
||||
@app.route("/health", methods=["GET"])
|
||||
def health():
|
||||
"""Check if the runtime is alive."""
|
||||
return {"status": "Healthy"}
|
||||
|
||||
|
||||
@app.route("/score", methods=["POST"])
|
||||
def score():
|
||||
"""process a flow request in the runtime."""
|
||||
raw_data = request.get_data()
|
||||
logger.info(f"Start loading request data '{raw_data}'.")
|
||||
data = json.loads(raw_data)
|
||||
|
||||
# create a dummy connection object
|
||||
# the connection object will only exist in memory and won't store in local db.
|
||||
llm_connection = AzureOpenAIConnection(
|
||||
name="llm_connection", api_key="[determined by request]", api_base="[determined by request]"
|
||||
)
|
||||
|
||||
# configure flow contexts, create a new context object for each request to make sure they are thread safe.
|
||||
f.context = FlowContext(
|
||||
# override flow connections with connection object created above
|
||||
connections={"echo_connection": {"connection": llm_connection}},
|
||||
# override the flow nodes' inputs or other flow configs, the overrides may come from the request
|
||||
# **Note**: after this change, node "echo_connection" will take input node_input from request
|
||||
overrides={"nodes.echo_connection.inputs.node_input": data["node_input"]} if "node_input" in data else {},
|
||||
)
|
||||
# data in request will be passed to flow as kwargs
|
||||
result_dict = f(**data)
|
||||
# Note: if specified streaming=True in the flow context, the result will be a generator
|
||||
# reference promptflow.core._serving.response_creator.ResponseCreator on how to handle it in app.
|
||||
return jsonify(result_dict)
|
||||
|
||||
|
||||
def create_app(**kwargs):
|
||||
return app
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# test this with curl -X POST http://127.0.0.1:5000/score --header "Content-Type: application/json" --data '{"flow_input": "some_flow_input", "node_input": "some_node_input"}' # noqa: E501
|
||||
create_app().run(debug=True)
|
||||
Reference in New Issue
Block a user