Custom Blocks
This guide will walk you through the steps of creating new blocks for the SmartSpace platform using the provided core block framework. Blocks are reusable components that perform specific tasks in workflows and can be configured with various inputs, outputs, and states.
Block Structure¶
Blocks are Python classes that inherit from the Block
base class and optionally from other generic types to support various input and output types. Each block should:
- Contain inputs and outputs as typed attributes.
- Implement logic in steps or callbacks.
- Optionally use states to store intermediate or persistent values.
- Provide metadata for categorisation and description.
Creating a Simple Block¶
To create a new block, define a class that extends Block
and optionally a generic type if needed. You can define the block’s inputs, outputs, and internal logic within the class.
Example:¶
from smartspace.core import Block, step
class Count(Block):
@step(output_name="output")
async def count(self, items: list[Any]) -> int:
return len(items)
In this example, the Count
block takes a list of items and outputs the count.
Adding Metadata¶
You can add metadata to a block to provide descriptive information such as the block’s category, description, or other attributes that are important for its usage in the SmartSpace system. Use the @metadata
decorator for this purpose.
Example:¶
from smartspace.core import Block, metadata, step
from smartspace.enums import BlockCategory
@metadata(
category=BlockCategory.FUNCTION,
description="Counts the number of items in a list."
)
class Count(Block):
@step(output_name="output")
async def count(self, items: list[Any]) -> int:
return len(items)
The @metadata
decorator adds useful information for developers and users of the block.
Working with Input and Output Pins¶
Blocks interact with other blocks via input and output pins. These pins can be single values, lists, or dictionaries. You can define inputs and outputs using Python type annotations.
Defining Inputs:¶
from smartspace.core import Block, step
class Count(Block):
@step(output_name="output")
async def count(self, items: list[Any]) -> int:
return len(items)
items
parameter is the input, which is a list of any type.
Defining Outputs:¶
Outputs are defined similarly to inputs but are typically sent using the send
method.
class Sum(Block):
total: Output[int]
@step()
async def sum(self, items: list[int]):
self.total.send(sum(items))
Working with States¶
States in SmartSpace blocks store values that persist across the execution of the block. You can define state variables using the State
annotation.
Example:¶
from typing import Annotated
from smartspace.core import Block, step, State
class Accumulator(Block):
total: Annotated[int, State(step_id="accumulate", input_ids=["items"])] = 0
@step()
async def accumulate(self, items: list[int]):
self.total += sum(items)
In this example, the total
variable persists across multiple calls to the accumulate
method.
Defining Block Functions¶
You can define block functions using the @step
and @callback
decorators. Steps are core functions of the block, while callbacks are executed in response to specific events.
Defining a Step:¶
class Multiply(Block):
@step(output_name="result")
async def multiply(self, x: int, y: int) -> int:
return x * y
Defining a Callback:¶
class MultiplyAndStore(Block):
@step()
async def multiply(self, x: int, y: int):
result = x * y
await self.store_result(result)
@callback()
async def store_result(self, result: int):
print(f"Result: {result}")
Handling Dynamic Inputs and Outputs¶
In some cases, blocks may need to dynamically handle inputs and outputs at runtime. For example, a block could process a variable number of inputs or outputs. You can define dynamic ports using lists or dictionaries of inputs and outputs.
Example:¶
class DynamicBlock(Block):
dynamic_inputs: list[Input[int]]
dynamic_outputs: dict[str, Output[int]]
@step()
async def process(self):
for input_value in self.dynamic_inputs:
output_value = input_value * 2
self.dynamic_outputs[str(input_value)].send(output_value)
Examples¶
Example: Map Block¶
The Map
block applies a tool to each item in a list and collects the results.
from smartspace.core import Block, Tool, Output, step, callback
class Map(Block):
class Operation(Tool):
def run(self, item: int) -> int:
return item * 2
results: Output[list[int]]
@step()
async def map(self, items: list[int]):
self.results.send([self.run.call(item) for item in items])
Example: Collect Block¶
The Collect
block collects data from a channel and outputs it once the channel closes.
from smartspace.core import Block, InputChannel, Output, step
class Collect(Block):
items: Output[list[int]]
@step()
async def collect(self, item: InputChannel[int]):
items = []
while not item.closed:
data = await item.receive()
items.append(data)
self.items.send(items)
Available Python Libraries¶
Currently (as of October 2024), SmartSpace supports the following Python libraries for use in blocks:
aiohappyeyeballs==2.4.3
aiohttp==3.10.8
aiosignal==1.3.1
annotated-types==0.5.0
antlr4-python3-runtime==4.9.3
anyio==3.7.1
asgiref==3.8.1
async-timeout==4.0.3
attrs==24.2.0
azure-common==1.1.28
azure-core==1.31.0
azure-cosmos==4.7.0
azure-identity==1.17.1
azure-monitor-opentelemetry-exporter==1.0.0b19
azure-search-documents==11.4.0a20230509004
azure-storage-blob==12.19.0
azure-storage-queue==12.9.0
backoff==2.2.1
beautifulsoup4==4.12.3
build==1.2.2
CacheControl==0.14.0
cachetools==5.5.0
certifi==2023.7.22
cffi==1.17.1
chardet==5.2.0
charset-normalizer==3.3.2
cleo==2.1.0
click==8.1.7
coloredlogs==15.0.1
contourpy==1.3.0
coverage==7.6.1
crashtest==0.4.1
cryptography==43.0.1
cycler==0.12.1
dataclasses-json==0.6.7
Deprecated==1.2.14
dirtyjson==1.0.8
distlib==0.3.8
distro==1.9.0
dnspython==2.4.2
docx2txt==0.8
dulwich==0.21.7
effdet==0.4.1
email-validator==2.0.0.post2
emoji==2.13.2
et-xmlfile==1.1.0
exceptiongroup==1.1.3
fastapi==0.101.1
fastapi-injector==0.5.1
fastjsonschema==2.20.0
filelock==3.16.1
filetype==1.2.0
fixedint==0.1.6
flatbuffers==24.3.25
fonttools==4.54.1
frozenlist==1.4.1
fsspec==2024.9.0
google-ai-generativelanguage==0.6.1
google-api-core==2.20.0
google-api-python-client==2.147.0
google-auth==2.35.0
google-auth-httplib2==0.2.0
google-generativeai==0.5.0
googleapis-common-protos==1.65.0
greenlet==3.1.1
grpcio==1.66.2
grpcio-status==1.62.3
grpcio-tools==1.62.3
h11==0.14.0
h2==4.1.0
hpack==4.0.0
httpcore==1.0.6
httplib2==0.22.0
httptools==0.6.0
httpx==0.27.2
httpx-sse==0.4.0
huggingface-hub==0.25.1
humanfriendly==10.0
hyperframe==6.0.1
idna==3.4
importlib-metadata==6.11.0
iniconfig==2.0.0
injector==0.21.0
installer==0.7.0
iopath==0.1.10
isodate==0.6.1
itsdangerous==2.1.2
jaraco.classes==3.4.0
jeepney==0.8.0
Jinja2==3.1.2
jiter==0.5.0
joblib==1.4.2
jsonpath-ng==1.6.1
jsonschema==4.23.0
jsonschema-specifications==2023.12.1
keyring==24.3.1
kiwisolver==1.4.7
langdetect==1.0.9
layoutparser==0.3.4
litellm==1.48.9
llama-cloud==0.1.0
llama-index==0.11.15
llama-index-agent-openai==0.3.4
llama-index-cli==0.3.1
llama-index-core==0.11.15
llama-index-embeddings-huggingface==0.3.1
llama-index-embeddings-openai==0.2.5
llama-index-indices-managed-llama-cloud==0.4.0
llama-index-legacy==0.9.48.post3
llama-index-llms-openai==0.2.10
llama-index-multi-modal-llms-openai==0.2.1
llama-index-program-openai==0.2.0
llama-index-question-gen-openai==0.2.0
llama-index-readers-file==0.2.2
llama-index-readers-llama-parse==0.3.0
llama-parse==0.5.6
lxml==5.3.0
markdown-it-py==3.0.0
MarkupSafe==2.1.3
marshmallow==3.22.0
matplotlib==3.9.2
mdurl==0.1.2
minijinja==2.2.0
more-itertools==10.5.0
motor==3.6.0
mpmath==1.3.0
msal==1.31.0
msal-extensions==1.2.0
msgpack==1.1.0
msrest==0.7.1
multidict==6.1.0
mypy==1.11.2
mypy-extensions==1.0.0
neo4j==5.25.0
nest-asyncio==1.6.0
networkx==3.3
nltk==3.9.1
numpy==1.26.4
oauthlib==3.2.2
omegaconf==2.3.0
onnx==1.17.0
onnxruntime==1.15.1
openai==1.51.0
opencv-python==4.10.0.84
openpyxl==3.1.2
opentelemetry-api==1.21.0
opentelemetry-instrumentation==0.42b0
opentelemetry-instrumentation-asgi==0.42b0
opentelemetry-instrumentation-fastapi==0.42b0
opentelemetry-instrumentation-logging==0.42b0
opentelemetry-instrumentation-requests==0.42b0
opentelemetry-instrumentation-system-metrics==0.42b0
opentelemetry-sdk==1.21.0
opentelemetry-semantic-conventions==0.42b0
opentelemetry-util-http==0.42b0
orjson==3.9.5
packaging==24.1
pandas==2.2.3
pandas-stubs==2.2.2.240909
pdf2image==1.17.0
pdfminer.six==20231228
pdfplumber==0.11.4
pexpect==4.9.0
pillow==10.4.0
pkginfo==1.11.1
platformdirs==4.3.6
pluggy==1.5.0
ply==3.11
poetry==1.8.2
poetry-core==1.9.0
poetry-plugin-export==1.8.0
portalocker==2.10.1
proto-plus==1.24.0
protobuf==4.25.5
psutil==5.9.8
ptyprocess==0.7.0
pyasn1==0.6.1
pyasn1_modules==0.4.1
pycocotools==2.0.8
pycparser==2.22
pydantic==2.7.0
pydantic-extra-types==2.0.0
pydantic-settings==2.0.3
pydantic_core==2.18.1
Pygments==2.18.0
PyJWT==2.9.0
pymongo==4.9.2
pypandoc==1.13
pyparsing==3.1.4
pypdf==4.3.1
PyPDF2==3.0.1
pypdfium2==4.30.0
pyproject_hooks==1.2.0
pysignalr==1.0.0
pytesseract==0.3.13
pytest==8.3.3
pytest-asyncio==0.23.8
pytest-cov==4.1.0
pytest-mock==3.14.0
python-dateutil==2.9.0.post0
python-docx==1.1.2
python-dotenv==1.0.1
python-iso639==2024.4.27
python-magic==0.4.27
python-multipart==0.0.6
pytz==2024.2
PyYAML==6.0.1
qdrant-client==1.11.3
RapidFuzz==3.10.0
referencing==0.35.1
regex==2024.9.11
requests==2.32.3
requests-oauthlib==2.0.0
requests-toolbelt==1.0.0
rich==13.9.1
rpds-py==0.20.0
rsa==4.9
ruff==0.3.7
safetensors==0.4.5
scikit-learn==1.5.2
scipy==1.14.1
SecretStorage==3.3.3
semantic-version==2.10.0
sentence-transformers==3.1.1
shellingham==1.5.4
six==1.16.0
sniffio==1.3.0
soupsieve==2.6
SQLAlchemy==2.0.35
sse-starlette==2.1.3
starlette==0.27.0
striprtf==0.0.26
sympy==1.13.3
tabulate==0.9.0
tenacity==8.2.3
threadpoolctl==3.5.0
tiktoken==0.7.0
timm==1.0.9
tokenizers==0.20.0
tomli==2.0.1
tomlkit==0.13.2
torch==2.4.1
torchvision==0.19.1
tqdm==4.66.5
transformers==4.45.1
trove-classifiers==2024.9.12
typer==0.12.5
types-jsonschema==4.23.0.20240813
types-openpyxl==3.1.5.20240918
types-pytz==2024.2.0.20240913
types-PyYAML==6.0.12.20240917
types-regex==2023.12.25.20240311
types-requests==2.32.0.20240914
typing-inspect==0.9.0
typing_extensions==4.12.2
tzdata==2024.2
ujson==5.8.0
unstructured==0.10.27
unstructured-inference==0.7.10
unstructured.pytesseract==0.3.13
uritemplate==4.1.1
urllib3==2.2.3
uvicorn==0.23.2
uvloop==0.17.0
virtualenv==20.26.6
watchdog==4.0.1
watchfiles==0.19.0
websockets==12.0
wrapt==1.16.0
yarl==1.13.1
zipp==3.20.2
In future versions, SmartSpace will enable the use of any Python library as the custom blocks will be running in a secure and isolated environment.
Conclusion¶
Blocks in SmartSpace are highly customizable and can be tailored to perform specific tasks within workflows. By following the examples above, you can create new blocks to expand the functionality of the SmartSpace platform. Remember to define inputs, outputs, and states clearly, and use the appropriate decorators (@step
, @callback
, @metadata
) to create powerful and reusable components.