Blocks

Blocks are the units of work that comprise a worklet.

Blocks are the units of work that comprise a worklet. To create a block, click "Add block" in a worklet graph and select the type of block that you want to add.

Code

Baseten allows you to write your own Python code as part of your worklet. Simply add a Code block to your worklet and select the desired function from a file.

Code blocks require a function with the following signature, selected from the appropriate file.

def func(block_input, env, context):

The three arguments are all optional, but they are positional, so you can't have env without block_input or context without both block_input and env. Because the arguments are positional, you can call them whatever you want, but we recommend sticking with the standard parameter names. Valid function signatures:

def func():
def func(block_input):
def func(block_input, env):
def func(block_input, env, context):
def func(predictions, env, context): # Meaningful name for block_input
def func(apples, oranges, grapefruit): # Valid, but not recommended

Let's examine the arguments individually.

block_input

This is the input to the block. If this block is the first in the worklet, then block_input is what the worklet was called with. Otherwise block_input is the return value of the previous block in the worklet.

env

Exclusively relying on block input/output for the execution of the worklet would cause headaches. What if the final block needs to access the first block's input? Would you have to ensure that the input is passed along all the way to the end? No.

That's where env comes in. It's an empty dictionary to which you can add key/value pairs (again, JSON-serializable values only). env is persisted through the worklet execution, which means that after a block modifies it, all subsequent blocks have access to that data.

context

context provides runtime information as well as variety of ways of invoking many Baseten functionalities:

1. Access to external data connections (e.g. Snowflake, Redis, and PostgreSQL):

Any data connections can be queried with a SQL string via the context object.

Example:

def my_function(block_input, env, context):
    context.run_query('db_connection_name', 'select * from my_table')

2. ORM access to user-defined data tables:

Baseten Postgres tables are similarly available to the context object.

Example:

def my_function(block_input, env, context):
    # All database table ORM classes are available under context.classes
    my_table = context.classes.MyTableName
    # And the SQLAlchemy session object is here:
    session = context.session
    # Get the first item from the table
    session.query(my_table).first()

3. Access to secrets:

Key-value pairs from Baseten's secret store are available via context for authenticating with third-party services and APIs.

Example:

def my_function(block_input, env, context):
    my_secret = context.get_secret('secret_name')

4. Access to user information like user ID and username:

The context object contains information on the current user. For workspaces with multiple users, the "current" user is the user who invoked the worklet that runs the code block. If the worklet was invoked via an API endpoint, the "current" user is the account that issued the API key used to invoke the worklet.

Example:

def my_function(block_input, env, context):
    user = context.user
    user_id = context.user.id
    user_email = context.user.email
    user_name = context.user.username

5. Ability to invoke a model:

You can invoke a model from a code block by model ID or model version ID.

Example:

def my_function(block_input, env, context):
    model_id = 'gpqvbql' # Replace with your model ID
    model_version_id = 'k9p2lbe' # Replace with your model version ID
    # call model
    predictions = context.invoke_model(model_id, block_input)
    # or call a specific version
    predictions = context.invoke_model_version(model_version_id, block_input)
    return predictions

Return value

It's worth repeating that Code blocks must explicitly return a JSON-serializable value to use as input for the next block, unless the Code block in question is the last block in a worklet. Even in this case, we recommend returning JSON in case the worklet is ever called from another worklet.

If your function is only called for its side effects, simply end with return block_input to pass along the block's input to the next block in the worklet.

Decision

Decision blocks are very similar to Code blocks in that you write custom Python code with access to the same block_input, env, context args. However, Decision blocks' output must be a boolean, which will determine which of its two child blocks should be called next.

If the decision block returns True (or a truthy value), the "then" path will be executed. If it returns False (or equivalent), the "else" path will be executed.

Since a block's output becomes the subsequent block's input, you can imagine that the boolean result of the Decision block will become the input to its subsequent block. That would be somewhat useless, so we made an exception: The input to the Decision block becomes the input to the subsequent block.

While block_inputand env are available arguments for a Decision block's function, they are considered read-only. Decision blocks should solely return a boolean value, not have any side effects. Any changes made to the input or environment will not be available in subsequent blocks.

Model

A Model block is the heart of any ML-powered worklet. This block invokes a selected model and returns its output.

While the model doesn't have access to env or context, the output of the previous block becomes the model's input. As such, make sure to pass along an input of the correct type. For example, if the model expects a list or numpy array, the previous block should return this type.

In your model blocks, you can set a model's version. Like all changes to worklets, changing a model version is environment-specific for users in workspaces with the draft environment feature. Setting the model version to primary will keep your worklet up-to-date as you publish new model versions, but specifying a version lets you roll out new model versions in sync with application changes that support the new version.

Query

A Query block lets you access queries directly in worklets.

The query block, like all other blocks, takes a JSON dictionary as input. For queries that do not require input, this can be an empty dictionary. But in some cases, we need to pass inputs into a query. For example:

SELECT 
  id, 
  image_url, 
  Round(friendly_score, 2) AS "Friendly score", 
  Round(dangerous_score, 2) AS "Dangerous score", 
  Round(not_feline_score, 2) AS "Not feline score" 
FROM 
  classified_feline 
WHERE 
  friendly_score < {{upper_limit}}
  AND friendly_score > {{lower_limit}}
  AND friendly_score != 1 
  AND dangerous_score != 1 
  OR (
    not_feline_score > friendly_score 
    AND not_feline_score > dangerous_score
  )

In this case, invoking the query block would require passing in:

{
    "upper_limit": 0.6,
    "lower_limit": 0.4
}

The query block outputs a dictionary with the key results and a list of query results. For example, a query that returns the following tabular output:

question        | answer
"What is 1 + 1" | 2
"What is 7 - 2" | 5

Would return the following JSON when run as a worklet block:

{
    "results": [
        {
            "question": "what is 1 + 1",
            "answer": 2
        },
        {
            "question": "what is 7 - 2",
            "answer": 5
        }
    ]
}

Worklet

Worklets, like functions, are composable. With a worklet block, you can call another worklet from your current worklet. This does not result in a new API request or worklet invocation, and env persists in the called worklet. This block returns the result of the called worklet, so be sure to format it as a valid input for the next block.

If you don't enjoy recursion, you can skip this paragraph and still be a fully capable Baseten user. A worklet can call itself, opening the possibility of recursive worklets. Also, worklet A can call worklet B, which in turn calls worklet A. Such complexities are rarely the best way to solve problems, but we have left the capability in the platform to maximize developer freedom and flexibility. The runtime will error out at an unsustainable recursion depth (think hundreds of recursive calls), so don't go too wild.

Slack

To send a Slack message from a worklet:

  1. In Slack, create a webhook.

  2. Paste the webhook into the appropriate field.

  3. Write your message! To include the value of block_input in your message, use a single empty pair of curly braces {}.

When you run the worklet, the Slack message will be sent!

The output of the Slack block will be {'message_sent': 'Your message here'}, so if you have any blocks set to run after the Slack block make sure that any necessary variables are stored in env.

Slack is our first of many planned integrations. If there is an API that you would like a similar integration for, please let us know.

Last updated