Skip to content

Commit 5ec77d6

Browse files
authored
Cache describe_interface and other endpoints (#1932)
* Cache describe_interface components * make style * Cache describe and schema * Address PR Comment & Fix Tests * Update inference/core/workflows/execution_engine/introspection/schema_parser.py
1 parent e7bc752 commit 5ec77d6

File tree

4 files changed

+145
-6
lines changed

4 files changed

+145
-6
lines changed

inference/core/workflows/execution_engine/introspection/blocks_loader.py

Lines changed: 90 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from collections import Counter
55
from copy import copy
66
from functools import lru_cache
7-
from typing import Any, Callable, Dict, List, Optional, Union
7+
from typing import Any, Callable, Dict, List, Optional, Tuple, Union
88

99
from packaging.specifiers import SpecifierSet
1010
from packaging.version import Version
@@ -50,10 +50,39 @@
5050
WORKFLOWS_CORE_PLUGIN_NAME = "workflows_core"
5151

5252

53+
def _get_env_configuration_state() -> Tuple[Tuple[str, ...], bool]:
54+
"""
55+
Returns current environment configuration state for cache keying.
56+
This ensures caches are invalidated when plugins or enterprise blocks change.
57+
"""
58+
plugins = tuple(get_plugin_modules())
59+
return (plugins, LOAD_ENTERPRISE_BLOCKS)
60+
61+
62+
def clear_caches() -> None:
63+
"""
64+
Clear all LRU caches in this module.
65+
Useful for testing or when environment configuration changes.
66+
"""
67+
_cached_describe_available_blocks.cache_clear()
68+
load_core_workflow_blocks.cache_clear()
69+
_cached_load_all_defined_kinds.cache_clear()
70+
_cached_model_json_schema.cache_clear()
71+
_cached_describe_outputs.cache_clear()
72+
73+
5374
def describe_available_blocks(
5475
dynamic_blocks: List[BlockSpecification],
5576
execution_engine_version: Optional[Union[str, Version]] = None,
5677
) -> BlocksDescription:
78+
# Fast path: cache for common case with no dynamic blocks
79+
if not dynamic_blocks:
80+
env_state = _get_env_configuration_state()
81+
return _cached_describe_available_blocks(
82+
execution_engine_version=execution_engine_version,
83+
env_state=env_state,
84+
)
85+
5786
blocks = (
5887
load_workflow_blocks(execution_engine_version=execution_engine_version)
5988
+ dynamic_blocks
@@ -87,7 +116,54 @@ def describe_available_blocks(
87116
)
88117
)
89118
_validate_loaded_blocks_manifest_type_identifiers(blocks=result)
90-
declared_kinds = load_all_defined_kinds()
119+
declared_kinds = _cached_load_all_defined_kinds(
120+
env_state=_get_env_configuration_state()
121+
)
122+
return BlocksDescription(blocks=result, declared_kinds=declared_kinds)
123+
124+
125+
@lru_cache(maxsize=8)
126+
def _cached_describe_available_blocks(
127+
execution_engine_version: Optional[Union[str, Version]] = None,
128+
env_state: Tuple[Tuple[str, ...], bool] = None,
129+
) -> BlocksDescription:
130+
"""Cached version for when there are no dynamic blocks (common case).
131+
132+
Args:
133+
execution_engine_version: Version filter for blocks
134+
env_state: Tuple of (plugins, enterprise_blocks_flag) for cache invalidation
135+
"""
136+
blocks = load_workflow_blocks(execution_engine_version=execution_engine_version)
137+
result = []
138+
for block in blocks:
139+
block_schema = _cached_model_json_schema(block.manifest_class)
140+
outputs_manifest = _cached_describe_outputs(block.manifest_class)
141+
manifest_type_identifiers = get_manifest_type_identifiers(
142+
block_schema=block_schema,
143+
block_source=block.block_source,
144+
block_identifier=block.identifier,
145+
)
146+
result.append(
147+
BlockDescription(
148+
manifest_class=block.manifest_class,
149+
block_class=block.block_class,
150+
block_schema=block_schema,
151+
outputs_manifest=outputs_manifest,
152+
block_source=block.block_source,
153+
fully_qualified_block_class_name=block.identifier,
154+
human_friendly_block_name=build_human_friendly_block_name(
155+
fully_qualified_name=block.identifier, block_schema=block_schema
156+
),
157+
manifest_type_identifier=manifest_type_identifiers[0],
158+
manifest_type_identifier_aliases=manifest_type_identifiers[1:],
159+
execution_engine_compatibility=block.manifest_class.get_execution_engine_compatibility(),
160+
input_dimensionality_offsets=block.manifest_class.get_input_dimensionality_offsets(),
161+
dimensionality_reference_property=block.manifest_class.get_dimensionality_reference_property(),
162+
output_dimensionality_offset=block.manifest_class.get_output_dimensionality_offset(),
163+
)
164+
)
165+
_validate_loaded_blocks_manifest_type_identifiers(blocks=result)
166+
declared_kinds = _cached_load_all_defined_kinds(env_state=env_state)
91167
return BlocksDescription(blocks=result, declared_kinds=declared_kinds)
92168

93169

@@ -350,6 +426,18 @@ def _validate_used_kinds_uniqueness(declared_kinds: List[Kind]) -> None:
350426
)
351427

352428

429+
@lru_cache(maxsize=8)
430+
def _cached_load_all_defined_kinds(
431+
env_state: Tuple[Tuple[str, ...], bool] = None,
432+
) -> List[Kind]:
433+
"""Cached version of load_all_defined_kinds.
434+
435+
Args:
436+
env_state: Tuple of (plugins, enterprise_blocks_flag) for cache invalidation
437+
"""
438+
return load_all_defined_kinds()
439+
440+
353441
def load_all_defined_kinds() -> List[Kind]:
354442
core_blocks_kinds = load_kinds()
355443
plugins_kinds = load_plugins_kinds()

inference/core/workflows/execution_engine/introspection/schema_parser.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import itertools
22
from collections import OrderedDict, defaultdict
33
from dataclasses import replace
4+
from functools import lru_cache
45
from typing import Dict, Optional, Set, Type
56

67
from inference.core.workflows.execution_engine.entities.types import (
@@ -52,10 +53,21 @@
5253
OBJECT_TYPE = "object"
5354

5455

56+
def clear_cache() -> None:
57+
"""Clear the parse_block_manifest cache."""
58+
parse_block_manifest.cache_clear()
59+
60+
61+
@lru_cache(maxsize=10000)
5562
def parse_block_manifest(
5663
manifest_type: Type[WorkflowBlockManifest],
5764
) -> BlockManifestMetadata:
58-
schema = manifest_type.model_json_schema()
65+
# Import here to avoid circular dependency
66+
from inference.core.workflows.execution_engine.introspection.blocks_loader import (
67+
_cached_model_json_schema,
68+
)
69+
70+
schema = _cached_model_json_schema(manifest_type)
5971
inputs_dimensionality_offsets = manifest_type.get_input_dimensionality_offsets()
6072
dimensionality_reference_property = (
6173
manifest_type.get_dimensionality_reference_property()

inference/core/workflows/execution_engine/v1/compiler/syntactic_parser.py

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
from typing import Dict, List, Optional, Type, Union
1+
from functools import lru_cache
2+
from typing import Dict, List, Optional, Tuple, Type, Union
23

34
import pydantic
45
from pydantic import BaseModel, Field, create_model
@@ -8,6 +9,7 @@
89
from inference.core.workflows.errors import WorkflowBlockError, WorkflowSyntaxError
910
from inference.core.workflows.execution_engine.entities.base import InputType, JsonField
1011
from inference.core.workflows.execution_engine.introspection.blocks_loader import (
12+
_get_env_configuration_state,
1113
load_workflow_blocks,
1214
)
1315
from inference.core.workflows.execution_engine.profiling.core import (
@@ -126,10 +128,28 @@ def build_workflow_definition_entity(
126128
return entity
127129

128130

129-
def get_workflow_schema_description() -> WorkflowsBlocksSchemaDescription:
131+
@lru_cache(maxsize=8)
132+
def _cached_workflow_schema(
133+
env_state: Tuple[Tuple[str, ...], bool] = None,
134+
) -> dict:
135+
"""Cached schema generation - called only when blocks don't change.
136+
137+
Args:
138+
env_state: Tuple of (plugins, enterprise_blocks_flag) for cache invalidation
139+
"""
130140
available_blocks = load_workflow_blocks()
131141
workflow_definition_class = build_workflow_definition_entity(
132142
available_blocks=available_blocks
133143
)
134-
schema = workflow_definition_class.model_json_schema()
144+
return workflow_definition_class.model_json_schema()
145+
146+
147+
def clear_cache() -> None:
148+
"""Clear the workflow schema cache."""
149+
_cached_workflow_schema.cache_clear()
150+
151+
152+
def get_workflow_schema_description() -> WorkflowsBlocksSchemaDescription:
153+
env_state = _get_env_configuration_state()
154+
schema = _cached_workflow_schema(env_state=env_state)
135155
return WorkflowsBlocksSchemaDescription(schema=schema)
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
"""Pytest configuration for introspection tests."""
2+
import pytest
3+
4+
from inference.core.workflows.execution_engine.introspection import blocks_loader
5+
from inference.core.workflows.execution_engine.introspection import schema_parser
6+
from inference.core.workflows.execution_engine.v1.compiler import syntactic_parser
7+
8+
9+
@pytest.fixture(autouse=True)
10+
def clear_all_caches():
11+
"""Clear all LRU caches before each test to ensure test isolation."""
12+
blocks_loader.clear_caches()
13+
schema_parser.clear_cache()
14+
syntactic_parser.clear_cache()
15+
yield
16+
# Also clear after test in case test modified state
17+
blocks_loader.clear_caches()
18+
schema_parser.clear_cache()
19+
syntactic_parser.clear_cache()

0 commit comments

Comments
 (0)