Skip to content

Commit

Permalink
Merge pull request #644 from roboflow/describe-workflow-output
Browse files Browse the repository at this point in the history
Describe workflow intefrace
  • Loading branch information
PawelPeczek-Roboflow authored Sep 18, 2024
2 parents ae8d48e + d8519af commit 13332f8
Show file tree
Hide file tree
Showing 27 changed files with 1,892 additions and 110 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jobs:
if: ${{ !github.event.act }}
runs-on:
group: group8core
timeout-minutes: 30
timeout-minutes: 35

steps:
- name: Set up QEMU
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
build:
runs-on:
group: group8core
timeout-minutes: 20
timeout-minutes: 30
strategy:
matrix:
python-version: ["3.8", "3.9", "3.10", "3.11"]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
build:
runs-on:
group: group8core
timeout-minutes: 30
timeout-minutes: 35
strategy:
matrix:
python-version: ["3.8", "3.9", "3.10", "3.11"]
Expand Down
10 changes: 10 additions & 0 deletions inference/core/entities/requests/workflows.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,13 @@ class DescribeBlocksRequest(BaseModel):
"contain blocks suitable for requested EE version, otherwise - descriptions for "
"all available blocks will be delivered.",
)


class DescribeInterfaceRequest(BaseModel):
api_key: str = Field(
description="Roboflow API Key that will be passed to the model during initialization for artifact retrieval",
)


class WorkflowSpecificationDescribeInterfaceRequest(DescribeInterfaceRequest):
specification: dict
18 changes: 17 additions & 1 deletion inference/core/entities/responses/workflows.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Any, Dict, List, Optional
from typing import Any, Dict, List, Optional, Union

from pydantic import BaseModel, Field

Expand Down Expand Up @@ -157,3 +157,19 @@ class ExecutionEngineVersions(BaseModel):

class WorkflowsBlocksSchemaDescription(BaseModel):
schema: dict = Field(description="Schema for validating block definitions")


class DescribeInterfaceResponse(BaseModel):
inputs: Dict[str, List[str]] = Field(
description="Dictionary mapping Workflow inputs to their kinds"
)
outputs: Dict[str, Union[List[str], Dict[str, List[str]]]] = Field(
description="Dictionary mapping Workflow outputs to their kinds"
)
typing_hints: Dict[str, str] = Field(
description="Dictionary mapping name of the kind with Python typing hint for underlying serialised object",
)
kinds_schemas: Dict[str, Union[dict, List[dict]]] = Field(
description="Dictionary mapping name of the kind with OpenAPI 3.0 definitions of underlying objects. "
"If list is given, entity should be treated as union of types."
)
59 changes: 57 additions & 2 deletions inference/core/interfaces/http/handlers/workflows.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
# TODO - for everyone: start migrating other handlers to bring relief to http_api.py
from typing import List, Optional
from typing import Dict, List, Optional, Set, Union

from packaging.version import Version
from packaging.specifiers import SpecifierSet

from inference.core.entities.responses.workflows import (
DescribeInterfaceResponse,
ExternalBlockPropertyPrimitiveDefinition,
ExternalWorkflowsBlockSelectorDefinition,
UniversalQueryLanguageDescription,
Expand All @@ -13,6 +14,10 @@
prepare_operations_descriptions,
prepare_operators_descriptions,
)
from inference.core.workflows.errors import WorkflowExecutionEngineVersionError
from inference.core.workflows.execution_engine.core import (
retrieve_requested_execution_engine_version,
)
from inference.core.workflows.execution_engine.introspection.blocks_loader import (
describe_available_blocks,
)
Expand All @@ -25,6 +30,16 @@
from inference.core.workflows.execution_engine.v1.dynamic_blocks.entities import (
DynamicBlockDefinition,
)
from inference.core.workflows.execution_engine.v1.introspection.inputs_discovery import (
describe_workflow_inputs,
)
from inference.core.workflows.execution_engine.v1.introspection.outputs_discovery import (
describe_workflow_outputs,
)
from inference.core.workflows.execution_engine.v1.introspection.types_discovery import (
discover_kinds_schemas,
discover_kinds_typing_hints,
)


def handle_describe_workflows_blocks_request(
Expand Down Expand Up @@ -82,3 +97,43 @@ def handle_describe_workflows_blocks_request(
universal_query_language_description=universal_query_language_description,
dynamic_block_definition_schema=DynamicBlockDefinition.schema(),
)


def handle_describe_workflows_interface(
definition: dict,
) -> DescribeInterfaceResponse:
requested_execution_engine_version = retrieve_requested_execution_engine_version(
workflow_definition=definition
)
if not SpecifierSet(f">=1.0.0,<2.0.0").contains(requested_execution_engine_version):
raise WorkflowExecutionEngineVersionError(
public_message="Describing workflow outputs is only supported for Execution Engine v1.",
context="describing_workflow_outputs",
)
inputs = describe_workflow_inputs(definition=definition)
outputs = describe_workflow_outputs(definition=definition)
unique_kinds = get_unique_kinds(inputs=inputs, outputs=outputs)
typing_hints = discover_kinds_typing_hints(kinds_names=unique_kinds)
kinds_schemas = discover_kinds_schemas(kinds_names=unique_kinds)
return DescribeInterfaceResponse(
inputs=inputs,
outputs=outputs,
typing_hints=typing_hints,
kinds_schemas=kinds_schemas,
)


def get_unique_kinds(
inputs: Dict[str, List[str]],
outputs: Dict[str, Union[List[str], Dict[str, List[str]]]],
) -> Set[str]:
all_kinds = set()
for input_element_kinds in inputs.values():
all_kinds.update(input_element_kinds)
for output_definition in outputs.values():
if isinstance(output_definition, list):
all_kinds.update(output_definition)
if isinstance(output_definition, dict):
for output_field_kinds in output_definition.values():
all_kinds.update(output_field_kinds)
return all_kinds
47 changes: 43 additions & 4 deletions inference/core/interfaces/http/http_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,9 @@
from inference.core.entities.requests.trocr import TrOCRInferenceRequest
from inference.core.entities.requests.workflows import (
DescribeBlocksRequest,
DescribeInterfaceRequest,
WorkflowInferenceRequest,
WorkflowSpecificationDescribeInterfaceRequest,
WorkflowSpecificationInferenceRequest,
)
from inference.core.entities.requests.yolo_world import YOLOWorldInferenceRequest
Expand Down Expand Up @@ -85,6 +87,7 @@
ServerVersionInfo,
)
from inference.core.entities.responses.workflows import (
DescribeInterfaceResponse,
ExecutionEngineVersions,
WorkflowInferenceResponse,
WorkflowsBlocksDescription,
Expand Down Expand Up @@ -145,6 +148,7 @@
from inference.core.interfaces.base import BaseInterface
from inference.core.interfaces.http.handlers.workflows import (
handle_describe_workflows_blocks_request,
handle_describe_workflows_interface,
)
from inference.core.interfaces.http.orjson_utils import (
orjson_response,
Expand Down Expand Up @@ -972,6 +976,41 @@ async def infer_lmm(

if not DISABLE_WORKFLOW_ENDPOINTS:

@app.post(
"/{workspace_name}/workflows/{workflow_id}/describe_interface",
response_model=DescribeInterfaceResponse,
summary="Endpoint to describe interface of predefined workflow",
description="Checks Roboflow API for workflow definition, once acquired - describes workflow inputs and outputs",
)
@with_route_exceptions
async def describe_predefined_workflow_interface(
workspace_name: str,
workflow_id: str,
workflow_request: DescribeInterfaceRequest,
) -> DescribeInterfaceResponse:
workflow_specification = get_workflow_specification(
api_key=workflow_request.api_key,
workspace_id=workspace_name,
workflow_id=workflow_id,
)
return handle_describe_workflows_interface(
definition=workflow_specification,
)

@app.post(
"/workflows/describe_interface",
response_model=DescribeInterfaceResponse,
summary="Endpoint to describe interface of workflow given in request",
description="Parses workflow definition and retrieves describes inputs and outputs",
)
@with_route_exceptions
async def describe_workflow_interface(
workflow_request: WorkflowSpecificationDescribeInterfaceRequest,
) -> DescribeInterfaceResponse:
return handle_describe_workflows_interface(
definition=workflow_request.specification,
)

@app.post(
"/{workspace_name}/workflows/{workflow_id}",
response_model=WorkflowInferenceResponse,
Expand Down Expand Up @@ -1486,7 +1525,7 @@ async def sam_segment_image(
"/sam2/embed_image",
response_model=Sam2EmbeddingResponse,
summary="SAM2 Image Embeddings",
description="Run the Meta AI Segmant Anything 2 Model to embed image data.",
description="Run the Meta AI Segment Anything 2 Model to embed image data.",
)
@with_route_exceptions
async def sam2_embed_image(
Expand All @@ -1498,7 +1537,7 @@ async def sam2_embed_image(
),
):
"""
Embeds image data using the Meta AI Segmant Anything Model (SAM).
Embeds image data using the Meta AI Segment Anything Model (SAM).
Args:
inference_request (SamEmbeddingRequest): The request containing the image to be embedded.
Expand All @@ -1519,7 +1558,7 @@ async def sam2_embed_image(
"/sam2/segment_image",
response_model=Sam2SegmentationResponse,
summary="SAM2 Image Segmentation",
description="Run the Meta AI Segmant Anything 2 Model to generate segmenations for image data.",
description="Run the Meta AI Segment Anything 2 Model to generate segmenations for image data.",
)
@with_route_exceptions
async def sam2_segment_image(
Expand All @@ -1531,7 +1570,7 @@ async def sam2_segment_image(
),
):
"""
Generates segmentations for image data using the Meta AI Segmant Anything Model (SAM).
Generates segmentations for image data using the Meta AI Segment Anything Model (SAM).
Args:
inference_request (Sam2SegmentationRequest): The request containing the image to be segmented.
Expand Down
2 changes: 1 addition & 1 deletion inference/core/version.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
__version__ = "0.18.1"
__version__ = "0.19.0"


if __name__ == "__main__":
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
class LineCounterManifest(WorkflowBlockManifest):
model_config = ConfigDict(
json_schema_extra={
"name": "Time in zone",
"name": "Line Counter",
"version": "v1",
"short_description": SHORT_DESCRIPTION,
"long_description": LONG_DESCRIPTION,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
)
from inference.core.workflows.prototypes.block import BlockResult, WorkflowBlockManifest

TYPE: str = "roboflow_core/line_counter_zone_visualization@v1"
TYPE: str = "roboflow_core/line_counter_visualization@v1"
SHORT_DESCRIPTION = "Paints a mask over line zone in an image."
LONG_DESCRIPTION = """
The `LineCounterZoneVisualization` block draws line
Expand All @@ -38,7 +38,7 @@ class LineCounterZoneVisualizationManifest(VisualizationManifest):
type: Literal[f"{TYPE}"]
model_config = ConfigDict(
json_schema_extra={
"name": "Polygon Zone Visualization",
"name": "Line Counter Visualization",
"version": "v1",
"short_description": SHORT_DESCRIPTION,
"long_description": LONG_DESCRIPTION,
Expand Down
4 changes: 2 additions & 2 deletions inference/core/workflows/execution_engine/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ def init(
prevent_local_images_loading: bool = False,
workflow_id: Optional[str] = None,
) -> "ExecutionEngine":
requested_engine_version = _retrieve_requested_execution_engine_version(
requested_engine_version = retrieve_requested_execution_engine_version(
workflow_definition=workflow_definition,
)
engine_type = _select_execution_engine(
Expand Down Expand Up @@ -70,7 +70,7 @@ def run(
)


def _retrieve_requested_execution_engine_version(workflow_definition: dict) -> Version:
def retrieve_requested_execution_engine_version(workflow_definition: dict) -> Version:
raw_version = workflow_definition.get("version")
if raw_version:
try:
Expand Down
Loading

0 comments on commit 13332f8

Please sign in to comment.