15. Deploy and Serve a Feature List
Deploy and Serve a Feature List for Model Predictions¶
Once you have a feature list ready, it's essential to serve these features effectively to make real-time or batch predictions using your machine learning model.
In this section, we'll take the feature list we previously crafted and explore two primary ways to serve its values:
REST API: Ideal for real-time predictions where you need instantaneous results. For instance, in applications where user interactions require immediate feedback based on model predictions.
Batch Processing: Best suited for scenarios where you have a bulk of data and don't require instant results.
Important Note for FeatureByte Enterprise Users¶
In Catalogs with Approval Flow enabled, moving features to production-ready status involves a comprehensive approval process.
This includes several evaluations, such as checking the feature's compliance with default cleaning operations and the feature job setting of its source tables. It also involves confirming the status of these tables and backtesting the feature job setting to prevent future training-serving inconsistencies. Additionally, essential details of the feature, particularly its feature definition file, are shared and subjected to a thorough review.
import featurebyte as fb
from datetime import datetime
14:13:36 | INFO | SDK version: 3.0.1.dev45 INFO :featurebyte:SDK version: 3.0.1.dev45 14:13:36 | INFO | No catalog activated. INFO :featurebyte:No catalog activated.
Activate Catalog¶
# Set your profile to the tutorial environment
fb.use_profile("tutorial")
catalog_name = "Loan Applications Dataset SDK Tutorial"
catalog = fb.Catalog.activate(catalog_name)
14:02:26 | INFO | Using profile: tutorial INFO :featurebyte:Using profile: tutorial 14:02:27 | INFO | Using configuration file at: /Users/gxav/.featurebyte/config.yaml INFO :featurebyte:Using configuration file at: /Users/gxav/.featurebyte/config.yaml 14:02:27 | INFO | Active profile: tutorial (https://tutorials.featurebyte.com/api/v1) INFO :featurebyte:Active profile: tutorial (https://tutorials.featurebyte.com/api/v1) 14:02:27 | INFO | SDK version: 3.0.1.dev45 INFO :featurebyte:SDK version: 3.0.1.dev45 14:02:27 | INFO | No catalog activated. INFO :featurebyte:No catalog activated. 14:02:27 | INFO | Catalog activated: Loan Applications Dataset SDK Tutorial INFO :featurebyte.api.catalog:Catalog activated: Loan Applications Dataset SDK Tutorial 16:13:11 | WARNING | Remote SDK version (1.1.0.dev7) is different from local (1.1.0.dev1). Update local SDK to avoid unexpected behavior. 16:13:11 | INFO | No catalog activated. 16:13:11 | INFO | Catalog activated: Grocery Dataset Tutorial
List feature lists in Catalog¶
catalog.list_feature_lists()
id | name | num_feature | status | deployed | readiness_frac | online_frac | tables | entities | primary_entity | created_at | |
---|---|---|---|---|---|---|---|---|---|---|---|
0 | 683d3c6ea3c52cb2862eea26 | 40 features for Loan Applications | 40 | DRAFT | False | 0.0 | 0.0 | [NEW_APPLICATION, CLIENT_PROFILE, BUREAU, INST... | [New Application, Client] | [New Application] | 2025-06-02T05:53:57.964000 |
Get a feature list from Catalog¶
feature_list_name = "40 features for Loan Applications"
simple_feature_list = catalog.get_feature_list(feature_list_name)
Loading Feature(s) |████████████████████████████████████████| 40/40 [100%] in 0.
Deploy feature list¶
deployment_name = "Loan Application: simple feature list with 40 features"
# Create a deployment
deployment = simple_feature_list.deploy(
deployment_name=deployment_name,
make_production_ready=True,
use_case_name="Loan Default by client",
)
Done! |████████████████████████████████████████| 100% in 6.1s (0.17%/s) Done! |████████████████████████████████████████| 100% in 6.1s (0.17%/s) Done! |████████████████████████████████████████| 100% in 6.1s (0.17%/s) Done! |████████████████████████████████████████| 100% in 6.1s (0.17%/s)
# Enable deployment
deployment.enable()
Done! |████████████████████████████████████████| 100% in 5:38.7 (0.00%/s) Done! |████████████████████████████████████████| 100% in 1:49.2 (0.01%/s)
# Check that the deployment is enabled
catalog.list_deployments()
id | name | feature_list_name | feature_list_version | num_feature | enabled | |
---|---|---|---|---|---|---|
0 | 683d3e7a52b8c1c6652ed315 | Loan Application: simple feature list with 40 ... | 40 features for Loan Applications | V250602 | 40 | True |
# Check status of the feature list
print("simple_feature_list.status:", simple_feature_list.status)
simple_feature_list.status: DEPLOYED
Get template for online serving¶
# Get a python template for consuming the feature serving API
deployment.get_online_serving_code(language="python")
from typing import Any, Dict
import pandas as pd
import requests
def request_features(entity_serving_names: Dict[str, Any]) -> pd.DataFrame:
"""
Send POST request to online serving endpoint
Parameters
----------
entity_serving_names: Dict[str, Any]
Entity serving name values to used for serving request
Returns
-------
pd.DataFrame
"""
response = requests.post(
url="https://tutorials.featurebyte.com/api/v1/deployment/683d3e7a52b8c1c6652ed315/online_features",
headers={"Content-Type": "application/json", "active-catalog-id": "683d3958cd5508b7e42fde58", "Authorization": "Bearer alZJY8HvLxoi_emU_eukghMpSMzxL0eQ4ufUSWxvsLk"},
json={"entity_serving_names": entity_serving_names},
)
assert response.status_code == 200, response.json()
return pd.DataFrame.from_dict(response.json()["features"])
request_features([{"SK_ID_CURR": 367827}])
# Get shell script
deployment.get_online_serving_code(language="sh")
#!/bin/sh
curl -X POST \
-H 'Content-Type: application/json' \
-H 'active-catalog-id: 683d3958cd5508b7e42fde58' \
-H 'Authorization: Bearer alZJY8HvLxoi_emU_eukghMpSMzxL0eQ4ufUSWxvsLk' \
-d '{"entity_serving_names": [{"SK_ID_CURR": 367827}]}' \
https://tutorials.featurebyte.com/api/v1/deployment/683d3e7a52b8c1c6652ed315/online_features
Create batch request table¶
Let's run a batch request for the last 24 hours applications.
# Get view of new applications
application_view = catalog.get_view("NEW_APPLICATION")
from datetime import datetime, timedelta, timezone
import pandas as pd
date_time_now_utc = datetime.now(timezone.utc)
now_less_24h = date_time_now_utc - timedelta(hours=24)
cond = application_view.application_time > pd.Timestamp(now_less_24h)
recent_application_view = application_view[cond]
recent_application_view.preview()
SK_ID_CURR | ClientID | CONTRACT_TYPE | AMT_INCOME_TOTAL | AMT_CREDIT | AMT_ANNUITY | AMT_GOODS_PRICE | TYPE_SUITE | REGION_POPULATION_RELATIVE | application_time | ... | FLAG_DOCUMENT_18 | FLAG_DOCUMENT_19 | FLAG_DOCUMENT_20 | FLAG_DOCUMENT_21 | AMT_REQ_CREDIT_BUREAU_HOUR | AMT_REQ_CREDIT_BUREAU_DAY | AMT_REQ_CREDIT_BUREAU_WEEK | AMT_REQ_CREDIT_BUREAU_MON | AMT_REQ_CREDIT_BUREAU_QRT | AMT_REQ_CREDIT_BUREAU_YEAR | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 370675 | 370675 | Cash loans | 225000.0 | 248760.0 | 28134.0 | 225000.0 | Unaccompanied | 0.025164 | 2025-06-02 03:21:48 | ... | 0 | 0 | 0 | 0 | NaN | NaN | NaN | NaN | NaN | NaN |
1 | 370878 | 370878 | Cash loans | 90000.0 | 485190.0 | 24903.0 | 405000.0 | Unaccompanied | 0.005144 | 2025-06-02 00:59:30 | ... | 0 | 0 | 0 | 0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 2.0 |
2 | 373328 | 373328 | Cash loans | 112500.0 | 900000.0 | 38133.0 | 900000.0 | Unaccompanied | 0.018801 | 2025-06-01 16:03:25 | ... | 0 | 0 | 0 | 0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 4.0 |
3 | 374568 | 374568 | Cash loans | 292500.0 | 690660.0 | 54697.5 | 589500.0 | Unaccompanied | 0.046220 | 2025-06-02 03:16:18 | ... | 0 | 0 | 0 | 0 | 0.0 | 0.0 | 0.0 | 0.0 | 1.0 | 3.0 |
4 | 376354 | 376354 | Cash loans | 157500.0 | 254799.0 | 20259.0 | 193500.0 | Unaccompanied | 0.010032 | 2025-06-01 20:14:39 | ... | 0 | 0 | 0 | 0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 3.0 |
5 | 376494 | 376494 | Cash loans | 270000.0 | 202500.0 | 16128.0 | 202500.0 | Unaccompanied | 0.026392 | 2025-06-02 02:04:27 | ... | 0 | 0 | 0 | 0 | 0.0 | 0.0 | 0.0 | 0.0 | 1.0 | 1.0 |
6 | 381735 | 381735 | Cash loans | 225000.0 | 450000.0 | 47254.5 | 450000.0 | Unaccompanied | 0.010006 | 2025-06-01 17:31:43 | ... | 0 | 0 | 0 | 0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 4.0 |
7 | 382140 | 382140 | Cash loans | 72000.0 | 450000.0 | 22977.0 | 450000.0 | Unaccompanied | 0.008068 | 2025-06-01 13:43:16 | ... | 0 | 0 | 0 | 0 | NaN | NaN | NaN | NaN | NaN | NaN |
8 | 384065 | 384065 | Revolving loans | 270000.0 | 180000.0 | 9000.0 | 180000.0 | Unaccompanied | 0.009334 | 2025-06-01 19:56:58 | ... | 0 | 0 | 0 | 0 | 0.0 | 0.0 | 0.0 | 0.0 | 1.0 | 2.0 |
9 | 388505 | 388505 | Cash loans | 216000.0 | 817560.0 | 30820.5 | 675000.0 | Unaccompanied | 0.030755 | 2025-06-01 18:30:07 | ... | 0 | 0 | 0 | 0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 |
10 rows × 111 columns
# Check primary entity of the deployed feature list to obtain its serving name
simple_feature_list.primary_entity
[<featurebyte.api.entity.Entity at 0x302c7da80> { 'name': 'New Application', 'created_at': '2025-06-02T05:47:02.488000', 'updated_at': '2025-06-02T05:47:03.887000', 'description': None, 'serving_names': [ 'SK_ID_CURR' ], 'catalog_name': 'Loan Applications Dataset SDK Tutorial' }]
# Create batch request table from the view
batch_request_table = recent_application_view.create_batch_request_table(
name="Latest Applications the latest 24 hours at " + date_time_now_utc.strftime("%Y%m%d:%H%M"),
columns=["SK_ID_CURR"],
columns_rename_mapping={
"SK_ID_CURR": "SK_ID_CURR",
}
)
Done! |████████████████████████████████████████| 100% in 9.2s (0.11%/s) Done! |████████████████████████████████████████| 100% in 12.2s (0.08%/s)
# Get name of the batch request table
batch_request_table.name
'Latest Applications the latest 24 hours at 20250602:0608'
# List batch request tables in catalog
catalog.list_batch_request_tables()
id | name | type | shape | feature_store_name | created_at | |
---|---|---|---|---|---|---|
0 | 683d3fdd52b8c1c6652ed316 | Latest Applications the latest 24 hours at 202... | view | [132, 1] | playground | 2025-06-02T06:08:33.231000 |
Compute batch feature table¶
# Get deployment and batch request table
deployment = catalog.get_deployment(deployment_name)
batch_request_table = catalog.get_batch_request_table(batch_request_table.name)
# Compute batch features
batch_features = deployment.compute_batch_feature_table(
batch_request_table=batch_request_table,
batch_feature_table_name =
f"{feature_list_name} with {batch_request_table.name}"
)
Done! |████████████████████████████████████████| 100% in 58.2s (0.02%/s) Done! |████████████████████████████████████████| 100% in 12.1s (0.08%/s)
# List observation tables
catalog.list_batch_feature_tables()
id | name | feature_store_name | batch_request_table_name | shape | created_at | |
---|---|---|---|---|---|---|
0 | 683d3fe752b8c1c6652ed317 | 40 features for Loan Applications with Latest ... | playground | Latest Applications the latest 24 hours at 202... | [132, 41] | 2025-06-02T06:09:32.045000 |
# Convert to pandas
batch_features.to_pandas()
Downloading table |████████████████████████████████████████| 132/132 [100%] in 0
SK_ID_CURR | NEW_APPLICATION_EXT_SOURCE_2 | NEW_APPLICATION_EXT_SOURCE_3 | NEW_APPLICATION_EXT_SOURCE_1 | NEW_APPLICATION_Credit-Goods_Difference | NEW_APPLICATION_AMT_ANNUITY_To_AMT_CREDIT | NEW_APPLICATION_DAYS_EMPLOYED | CLIENT_GENDER | CLIENT_Max_of_Active_Cr_active_BureauReportedCredits_AMT_CREDIT_SUM_DEBT_To_AMT_CREDIT_SUMs_104w | CLIENT_EDUCATION_TYPE | ... | CLIENT_Installments_AMT_PAYMENTs_by_PriorApplication_PAYMENT_TYPE_24cMo | CLIENT_Time_To_Latest_Approved_Contract_status_PriorApplication_last_due_1st_version_timestamp_104w | NEW_APPLICATION_FLAG_DOCUMENT_3 | NEW_APPLICATION_FLOORSMAX_MODE | CLIENT_Max_of_Installments_Days_Difference_Actual_vs_Scheduleds_24cMo | CLIENT_Max_of_Installments_PriorApplication_AMT_ANNUITY_To_AMT_CREDITs_6cMo | CLIENT_Std_of_Credit_card_monthly_balance_records_CNT_DRAWINGS_ATM_CURRENTs_24cMo | CLIENT_PriorApplications_AMT_CREDITs_by_PriorApplication_NFLAG_INSURED_ON_APPROVAL_52w | CLIENT_Std_of_BureauReportedCredits_Available_Credits_26w | CLIENT_Max_of_Consumer_credit_Cr_type_BureauReportedCredits_AMT_CREDIT_SUM_DEBT_To_AMT_CREDIT_SUMs_52w | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 370675 | 0.688276 | NaN | 0.653914 | 23760.0 | 0.113097 | -1828.0 | M | NaN | Secondary / secondary special | ... | None | -29.883851 | 1 | NaN | NaN | NaN | NaN | {\n "0": 1.345500000000000e+05\n} | NaN | NaN |
1 | 373328 | 0.340961 | 0.622922 | NaN | 0.0 | 0.042370 | -3056.0 | F | 0.180025 | Secondary / secondary special | ... | None | NaN | 1 | NaN | NaN | NaN | NaN | None | 0.000000 | NaN |
2 | 374568 | 0.566644 | 0.236611 | 0.367467 | 101160.0 | 0.079196 | -2452.0 | M | 0.823229 | Higher education | ... | None | -128.880031 | 1 | NaN | NaN | NaN | NaN | {\n "0": 1.955970000000000e+05\n} | 120068.584626 | NaN |
3 | 390276 | 0.596397 | NaN | 0.637626 | 0.0 | 0.064453 | NaN | F | 0.956667 | Secondary / secondary special | ... | None | -22.864047 | 1 | 0.4583 | NaN | NaN | NaN | {\n "1": 3.575700000000000e+04\n} | 8780.969124 | NaN |
4 | 398384 | 0.766377 | 0.362277 | 0.635817 | 0.0 | 0.079007 | -517.0 | F | 1.000000 | Higher education | ... | None | -78.316686 | 1 | 0.0417 | NaN | NaN | NaN | {\n "0": 4.784400000000000e+04,\n "__MISSING... | 89383.302862 | 1.000000 |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
127 | 355417 | 0.772387 | 0.794629 | 0.783045 | 143271.0 | 0.035918 | -6312.0 | F | 0.916667 | Secondary / secondary special | ... | None | NaN | 1 | 0.1667 | NaN | NaN | NaN | None | 0.000000 | 0.916667 |
128 | 356857 | 0.510737 | 0.360613 | NaN | 95427.0 | 0.042536 | NaN | F | 0.884400 | Secondary / secondary special | ... | None | -216.530089 | 0 | NaN | NaN | NaN | NaN | {\n "1": 1.935720000000000e+05,\n "__MISSING... | 13372.114158 | 0.525827 |
129 | 357464 | 0.663915 | 0.144648 | 0.643336 | 0.0 | 0.063900 | -1805.0 | F | 0.921416 | Secondary / secondary special | ... | None | NaN | 1 | 0.1667 | NaN | NaN | NaN | {\n "__MISSING__": 2.636865000000000e+05\n} | 51613.575403 | 0.689758 |
130 | 358444 | 0.530524 | 0.483050 | NaN | 79596.0 | 0.079350 | -1596.0 | M | 0.968565 | Secondary / secondary special | ... | None | -521.448075 | 1 | NaN | NaN | NaN | NaN | {\n "__MISSING__": 0.000000000000000e+00\n} | 15923.250000 | 0.968565 |
131 | 364068 | 0.074342 | 0.475850 | 0.843238 | 209088.0 | 0.040288 | NaN | F | 0.961179 | Higher education | ... | None | -88.804128 | 1 | NaN | NaN | NaN | NaN | {\n "0": 1.514835000000000e+05,\n "__MISSING... | 329985.617751 | 0.961179 |
132 rows × 41 columns
Downloading table |████████████████████████████████████████| 500/500 [100%] in 0
GROCERYCUSTOMERGUID | PRODUCTGROUP | CUSTOMER_Age_band | CUSTOMER_Latest_invoice_Amount | CUSTOMER_Count_of_invoice_14d | CUSTOMER_Avg_of_invoice_Amount_14d | CUSTOMER_Std_of_invoice_Amount_14d | CUSTOMER_Latest_invoice_Amount_Z_Score_to_invoice_Amount_28d | CUSTOMER_vs_OVERALL_item_TotalCost_across_product_ProductGroups_26w | CUSTOMER_x_PRODUCTGROUP_Sum_of_item_TotalCost_14d | CUSTOMER_x_PRODUCTGROUP_Time_Since_Latest_Timestamp | |
---|---|---|---|---|---|---|---|---|---|---|---|
0 | 14f94723-4cae-4041-a11a-a044df1a5a5f | Fromages | 60-64 | 14.28 | NaN | NaN | NaN | 1.000000 | 0.373275 | NaN | 1527.861052 |
1 | d4559f7d-eb28-42c6-b47d-847de24952c2 | Fromages | 65-69 | 20.04 | NaN | NaN | NaN | NaN | 0.370545 | NaN | 2177.017718 |
2 | 1d363f8c-88f4-49eb-a107-4f1e2fbe6722 | Fromages | 25-29 | 73.16 | NaN | NaN | NaN | NaN | 0.781391 | NaN | 1118.766885 |
3 | 768e57df-7513-412b-a466-effd270ad6a6 | Fromages | 65-69 | 7.16 | 3.0 | 8.246667 | 4.395121 | -0.644055 | 0.650126 | NaN | 1357.819107 |
4 | 48f6e550-ce63-457e-ad14-d511ee934661 | Fromages | 40-44 | 24.62 | 1.0 | 24.620000 | 0.000000 | -0.219845 | 0.753619 | NaN | 475.681329 |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
495 | c1b3224a-5dbb-427d-87d3-3882c6bb1278 | Fromages | 50-54 | 6.69 | NaN | NaN | NaN | NaN | 0.104953 | NaN | NaN |
496 | 261d2979-0568-4544-8be0-32355049e565 | Fromages | 65-69 | 2.00 | NaN | NaN | NaN | NaN | 0.317982 | NaN | NaN |
497 | 825ee709-2f85-49e2-bec7-27bea06efe3a | Fromages | 45-49 | 3.00 | 2.0 | 5.995000 | 2.995000 | -0.990561 | 0.375129 | NaN | NaN |
498 | ea68ce6b-b3c3-4ba5-a471-f11b1c5bd892 | Fromages | 45-49 | 4.50 | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
499 | b8b00613-2e48-4eed-8971-61718990a994 | Fromages | 45-49 | 10.00 | 2.0 | 6.000000 | 4.000000 | 1.264961 | 0.579119 | NaN | NaN |
500 rows × 11 columns
# download parquet file
batch_features.download()
Downloading table |████████████████████████████████████████| 132/132 [100%] in 0
PosixPath('BATCH_FEATURE_TABLE_683d3fe72b2121b4644f2571.parquet')
Downloading table |████████████████████████████████████████| 500/500 [100%] in 0
PosixPath('BATCH_FEATURE_TABLE_666959284c98806ac301afa0.parquet')
# delete if not needed any more
batch_features.delete()
batch_request_table.delete()
Done! |████████████████████████████████████████| 100% in 9.2s (0.11%/s) Done! |████████████████████████████████████████| 100% in 6.1s (0.17%/s) Done! |████████████████████████████████████████| 100% in 6.1s (0.17%/s) Done! |████████████████████████████████████████| 100% in 6.1s (0.17%/s)
Manage Deployment¶
deployment = catalog.get_deployment(deployment_name)
# get feature jobs status (this will produce meaningful results only once multiple jobs have been run)
# deployment.get_feature_jobs_status(job_history_window=12)
# Disable deployment
deployment.disable()
Done! |████████████████████████████████████████| 100% in 18.6s (0.05%/s) Done! |████████████████████████████████████████| 100% in 12.1s (0.08%/s)
# The deployment is still part of the catalog but disabled
catalog.list_deployments()
id | name | feature_list_name | feature_list_version | num_feature | enabled | |
---|---|---|---|---|---|---|
0 | 683d3e7a52b8c1c6652ed315 | Loan Application: simple feature list with 40 ... | 40 features for Loan Applications | V250602 | 40 | False |