GeezerVison is showing github.com — feat(cli): Add recommendation template for feast init by doc-johnson · Pull Request #6078 · feast-dev/feast
feat(cli): Add recommendation template for feast init
#6078
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and
privacy statement . We’ll occasionally send you account related emails.
Already on GitHub?
Sign in
to your account
Uh oh!
There was an error while loading. Please reload this page.
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
14 commits
Select commit
Hold shift + click to select a range
7478002
feat(cli): Add recommendation template for feast init (#5478)
doc-johnson Mar 8, 2026
34996fa
Merge branch 'master' into feat/recommendation-template
franciscojavierarceo Jul 8, 2026
9e52058
Merge remote-tracking branch 'upstream/master' into feat/recommendati…
doc-johnson Jul 14, 2026
7335fac
test: Add recommendation template artifacts test
doc-johnson Jul 14, 2026
edbff11
Merge remote-tracking branch 'upstream/master' into feat/recommendati…
doc-johnson Jul 15, 2026
8be3517
Merge branch 'master' into feat/recommendation-template
doc-johnson Jul 21, 2026
dce09f2
Merge branch 'master' into feat/recommendation-template
doc-johnson Aug 12, 2026
bdd4089
Merge branch 'master' into feat/recommendation-template
doc-johnson Aug 14, 2026
8462228
Merge remote-tracking branch 'upstream/master' into feat/recommendati…
doc-johnson Aug 17, 2026
4911874
fix(cli): Make recommendation template initialization self-contained
doc-johnson Aug 18, 2026
2ff60bc
Merge remote-tracking branch 'upstream/master' into feat/recommendati…
doc-johnson Aug 18, 2026
ea79119
Merge branch 'master' into feat/recommendation-template
doc-johnson Aug 18, 2026
db99b69
Merge branch 'master' into feat/recommendation-template
doc-johnson Aug 19, 2026
82e4bcb
fix: Refresh secret scan baseline
doc-johnson Sep 11, 2026
File filter
Filter by extension
.baseline
(1)
.py
(7)
.yaml
(1)
All 3 file types selected
Viewed files
Conversations
Failed to load comments.
Loading
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Jump to file
Failed to load files.
Loading
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Hide whitespace
Diff view
Hide whitespace
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
Oops, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
Original file line number
Diff line number
Diff line change
Expand Up
@@ -481,6 +481,7 @@ def materialize_incremental_command(
"ray_rag",
"rag",
"pytorch_nlp",
"recommendation",
],
case_sensitive=False,
),
Expand Down
Original file line number
Diff line number
Diff line change
@@ -0,0 +1,149 @@
from feast.file_utils import replace_str_in_file
def bootstrap() -> None:
import pathlib
from datetime import datetime, timedelta
import numpy as np
import pandas as pd
repo_path = pathlib.Path(__file__).parent.absolute() / "feature_repo"
project_name = pathlib.Path(__file__).parent.absolute().name
data_path = repo_path / "data"
data_path.mkdir(exist_ok=True)
products = [
(
"P001",
"Wireless Noise-Cancelling Headphones",
"Premium over-ear headphones with active noise cancellation and 30-hour battery life.",
"Electronics",
299.99,
4.7,
),
(
"P002",
"Bluetooth Portable Speaker",
"Waterproof portable speaker with deep bass and 12-hour playtime.",
"Electronics",
79.99,
4.5,
),
(
"P003",
"Mechanical Gaming Keyboard",
"RGB mechanical keyboard with Cherry MX switches and programmable keys.",
"Electronics",
149.99,
4.6,
),
(
"P004",
"Ergonomic Wireless Mouse",
"Vertical ergonomic mouse designed to reduce wrist strain.",
"Electronics",
49.99,
4.3,
),
(
"P005",
"Python Machine Learning Cookbook",
"Practical recipes for building ML models with scikit-learn and TensorFlow.",
"Books",
39.99,
4.4,
),
(
"P006",
"Data Engineering Fundamentals",
"Comprehensive guide to building modern data pipelines and architectures.",
"Books",
44.99,
4.6,
),
(
"P007",
"Introduction to Deep Learning",
"Beginner-friendly deep learning textbook with hands-on PyTorch examples.",
"Books",
54.99,
4.7,
),
(
"P008",
"Trail Running Shoes",
"Lightweight trail running shoes with superior grip and cushioning.",
"Sports",
129.99,
4.6,
),
(
"P009",
"Premium Yoga Mat",
"Non-slip extra-thick yoga mat with carrying strap.",
"Sports",
34.99,
4.4,
),
(
"P010",
"Resistance Bands Set",
"Set of 5 resistance bands with varying tension levels for home workouts.",
"Sports",
24.99,
4.3,
),
(
"P011",
"Robot Vacuum Cleaner",
"Self-navigating robot vacuum with app control and auto-charging.",
"Home",
349.99,
4.5,
),
(
"P012",
"Air Purifier with HEPA Filter",
"Room air purifier with true HEPA filter and air quality sensor.",
"Home",
199.99,
4.6,
),
]
embedding_dim = 384
embeddings = np.zeros((len(products), embedding_dim), dtype=np.float32)
embeddings[np.arange(len(products)), np.arange(len(products))] = 1.0
end_date = datetime.now().replace(microsecond=0, second=0, minute=0)
start_date = end_date - timedelta(days=15)
timestamps = [start_date + timedelta(hours=i) for i in range(len(products))]
columns = [
"product_id",
"product_name",
"description",
"category",
"price",
"rating",
]
df = pd.DataFrame(products, columns=columns)
df["price"] = df["price"].astype(np.float32)
df["rating"] = df["rating"].astype(np.float32)
df["embedding"] = [embedding.tolist() for embedding in embeddings]
df["event_timestamp"] = timestamps
df["created"] = end_date
products_path = data_path / "products.parquet"
df.to_parquet(path=str(products_path), allow_truncated_timestamps=True)
example_py_file = repo_path / "feature_definitions.py"
replace_str_in_file(example_py_file, "%PROJECT_NAME%", str(project_name))
replace_str_in_file(
example_py_file, "%PARQUET_PATH%", str(products_path.relative_to(repo_path))
)
if __name__ == "__main__":
bootstrap()
Original file line number
Diff line number
Diff line change
@@ -0,0 +1,43 @@
from datetime import timedelta
from feast import Entity, FeatureService, FeatureView, Field, FileSource, Project
from feast.types import Array, Float32, String
project = Project(
name="%PROJECT_NAME%",
description="A project for product recommendations using vector similarity search",
)
product = Entity(name="product", join_keys=["product_id"])
products_source = FileSource(
name="products_source",
path="%PARQUET_PATH%",
timestamp_field="event_timestamp",
created_timestamp_column="created",
)
# test_workflow.py writes model embeddings before materialization.
product_embeddings = FeatureView(
name="product_embeddings",
entities=[product],
ttl=timedelta(days=365),
schema=[
Field(
name="embedding", dtype=Array(Float32), vector_index=True, vector_length=384
),
Field(name="product_name", dtype=String),
Field(name="description", dtype=String),
Field(name="category", dtype=String),
Field(name="price", dtype=Float32),
Field(name="rating", dtype=Float32),
],
online=True,
source=products_source,
tags={"team": "recommendations"},
)
recommendation_service = FeatureService(
name="recommendation_service",
features=[product_embeddings],
)
Original file line number
Diff line number
Diff line change
@@ -0,0 +1,13 @@
project: my_project
# By default, the registry is a file (but can be turned into a more scalable SQL-backed registry)
registry: data/registry.db
# The provider primarily specifies default offline / online stores & storing the registry in a given cloud
provider: local
online_store:
type: sqlite
path: data/online_store.db
vector_enabled: true
entity_key_serialization_version: 3
# By default, no_auth for authentication and authorization, other possible values kubernetes and oidc. Refer the documentation for more details.
auth:
type: no_auth
Original file line number
Diff line number
Diff line change
@@ -0,0 +1,70 @@
import subprocess
import sys
from datetime import datetime
from pathlib import Path
import numpy as np
import pandas as pd
from feast import FeatureStore
def run_demo() -> None:
try:
from sentence_transformers import SentenceTransformer
except ImportError:
print("sentence-transformers is required: pip install sentence-transformers")
sys.exit(1)
model = SentenceTransformer("all-MiniLM-L6-v2")
products_path = Path("data/products.parquet")
products = pd.read_parquet(products_path)
descriptions = (products["product_name"] + ". " + products["description"]).tolist()
embeddings = model.encode(descriptions, normalize_embeddings=True)
products["embedding"] = [
embedding.astype(np.float32).tolist() for embedding in embeddings
]
products.to_parquet(products_path, allow_truncated_timestamps=True)
print("\nApplying feature definitions")
subprocess.run(["feast", "apply"], check=True)
print("\nLoading features into the online store")
store = FeatureStore(repo_path=".")
store.materialize_incremental(end_date=datetime.now())
query = "gaming laptop accessories"
print(f"\nSearching for: {query}")
query_embedding = model.encode([query], normalize_embeddings=True)[0].tolist()
results = store.retrieve_online_documents_v2(
features=[
"product_embeddings:embedding",
"product_embeddings:product_id",
"product_embeddings:product_name",
"product_embeddings:category",
"product_embeddings:price",
"product_embeddings:rating",
],
query=query_embedding,
top_k=5,
).to_dict()
if results and len(results.get("product_id", [])) > 0:
num_results = len(results["product_id"])
print(f" Top {num_results} recommendations:")
for i in range(num_results):
name = results["product_name"][i]
category = results["category"][i]
price = results["price"][i]
rating = results["rating"][i]
print(f" {i + 1}. {name} [{category}] - ${price:.2f} (rating: {rating})")
else:
print(" No results found.")
print("\nTearing down the feature store")
subprocess.run(["feast", "teardown"], check=True)
if __name__ == "__main__":
run_demo()
Original file line number
Diff line number
Diff line change
Expand Up
@@ -3,6 +3,9 @@
from pathlib import Path
from textwrap import dedent
import pandas as pd
from feast.repo_operations import init_repo
from feast.utils import _utc_now
from tests.utils.cli_repo_creator import CliRunner
Expand Down
Expand Up
@@ -81,3 +84,32 @@ def test_postgres_template_registry_path_is_parameterized() -> None:
contents = template_fs_yaml.read_text(encoding="utf-8")
expected = "path: postgresql://DB_USERNAME:DB_PASSWORD@DB_HOST:DB_PORT/DB_NAME"
assert expected in contents
def test_recommendation_template_init(tmp_path: Path) -> None:
repo_path = tmp_path / "recommendation_project"
init_repo(
"recommendation_project",
template="recommendation",
repo_path=str(repo_path),
)
feature_repo = repo_path / "feature_repo"
config = (feature_repo / "feature_store.yaml").read_text(encoding="utf-8")
assert "project: recommendation_project" in config
assert "type: sqlite" in config
assert "vector_enabled: true" in config
definitions = (feature_repo / "feature_definitions.py").read_text(encoding="utf-8")
assert "vector_index=True" in definitions
assert "vector_length=384" in definitions
assert 'name="recommendation_project"' in definitions
assert 'path="data/products.parquet"' in definitions
products_path = feature_repo / "data" / "products.parquet"
assert products_path.is_file()
products = pd.read_parquet(products_path)
assert len(products) == 12
assert all(len(embedding) == 384 for embedding in products["embedding"])
assert not (repo_path / "bootstrap.py").exists()
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.