Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
6.0.0
6.0.1
1 change: 0 additions & 1 deletion cypress/src/support/adminHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ export function expandAdminMenu () {
'Model Management',
'RAG Management',
'MCP Management',
'MCP Workbench',
]);
});
}
Expand Down
55 changes: 29 additions & 26 deletions lambda/models/lambda_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

import boto3
import botocore.session
from fastapi import FastAPI, Path, Request
from fastapi import FastAPI, HTTPException, Path, Request
from fastapi.encoders import jsonable_encoder
from fastapi.exceptions import RequestValidationError
from fastapi.middleware.cors import CORSMiddleware
Expand Down Expand Up @@ -61,6 +61,21 @@
stepfunctions = boto3.client("stepfunctions", region_name=os.environ["AWS_REGION"], config=retry_config)


def get_admin_status_and_groups(request: Request) -> tuple[bool, list[str]]:
admin_status = False
user_groups = []

if "aws.event" in request.scope:
event = request.scope["aws.event"]
try:
user_groups = get_groups(event)
admin_status = is_admin(event)
except Exception:
user_groups = []
admin_status = False
return admin_status, user_groups
Comment on lines +64 to +76
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The get_admin_status_and_groups() function silently catches all exceptions and returns default values. This could mask legitimate errors (e.g., network issues, malformed event data). Consider logging the exception or being more specific about which exceptions to catch.

Suggested change
def get_admin_status_and_groups(request: Request) -> tuple[bool, list[str]]:
admin_status = False
user_groups = []
if "aws.event" in request.scope:
event = request.scope["aws.event"]
try:
user_groups = get_groups(event)
admin_status = is_admin(event)
except Exception:
user_groups = []
admin_status = False
return admin_status, user_groups
def get_admin_status_and_groups(request: Request) -> tuple[bool, list[str]]:
admin_status = False
user_groups = []
if "aws.event" in request.scope:
event = request.scope["aws.event"]
try:
user_groups = get_groups(event)
admin_status = is_admin(event)
except (KeyError, ValueError, TypeError):
user_groups = []
admin_status = False
return admin_status, user_groups



@app.exception_handler(ModelNotFoundError)
async def model_not_found_handler(request: Request, exc: ModelNotFoundError) -> JSONResponse:
"""Handle exception when model cannot be found and translate to a 404 error."""
Expand All @@ -87,8 +102,11 @@ async def user_error_handler(

@app.post(path="", include_in_schema=False)
@app.post(path="/")
async def create_model(create_request: CreateModelRequest) -> CreateModelResponse:
async def create_model(create_request: CreateModelRequest, request: Request) -> CreateModelResponse:
"""Endpoint to create a model."""
admin_status, _ = get_admin_status_and_groups(request)
if not admin_status:
raise HTTPException(status_code=403, detail="User does not have permission to create models.")
Comment on lines +107 to +109
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The authorization check uses a generic error message. Consider including more context about what permissions are required or suggesting next steps for the user.

create_handler = CreateModelHandler(
autoscaling_client=autoscaling,
stepfunctions_client=stepfunctions,
Expand All @@ -109,18 +127,7 @@ async def list_models(request: Request) -> ListModelsResponse:
guardrails_table_resource=guardrails_table,
)

user_groups = []
admin_status = False

if "aws.event" in request.scope:
event = request.scope["aws.event"]
try:
user_groups = get_groups(event)
admin_status = is_admin(event)
except Exception:
user_groups = []
admin_status = False

admin_status, user_groups = get_admin_status_and_groups(request)
return list_handler(user_groups=user_groups, is_admin=admin_status)


Expand All @@ -136,27 +143,20 @@ async def get_model(
guardrails_table_resource=guardrails_table,
)

user_groups = []
admin_status = False

if "aws.event" in request.scope:
event = request.scope["aws.event"]
try:
user_groups = get_groups(event)
admin_status = is_admin(event)
except Exception:
user_groups = []
admin_status = False

admin_status, user_groups = get_admin_status_and_groups(request)
return get_handler(model_id=model_id, user_groups=user_groups, is_admin=admin_status)


@app.put(path="/{model_id}")
async def update_model(
model_id: Annotated[str, Path(title="The unique model ID of the model to update")],
update_request: UpdateModelRequest,
request: Request,
) -> UpdateModelResponse:
"""Endpoint to update a model."""
admin_status, _ = get_admin_status_and_groups(request)
if not admin_status:
raise HTTPException(status_code=403, detail="User does not have permission to update models.")
update_handler = UpdateModelHandler(
autoscaling_client=autoscaling,
stepfunctions_client=stepfunctions,
Expand All @@ -171,6 +171,9 @@ async def delete_model(
model_id: Annotated[str, Path(title="The unique model ID of the model to delete")], request: Request
) -> DeleteModelResponse:
"""Endpoint to delete a model."""
admin_status, _ = get_admin_status_and_groups(request)
if not admin_status:
raise HTTPException(status_code=403, detail="User does not have permission to delete models.")
delete_handler = DeleteModelHandler(
autoscaling_client=autoscaling,
stepfunctions_client=stepfunctions,
Expand Down
4 changes: 2 additions & 2 deletions lib/api-base/fastApiContainer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,8 @@ export class FastApiContainer extends Construct {
};

// Add build config overrides if provided
if (config.restApiConfig.buildConfig?.NODEENV_CACHE_DIR) {
buildArgs.NODEENV_CACHE_DIR = config.restApiConfig.buildConfig.NODEENV_CACHE_DIR;
if (config.restApiConfig.buildConfig?.PRISMA_CACHE_DIR) {
buildArgs.PRISMA_CACHE_DIR = config.restApiConfig.buildConfig.PRISMA_CACHE_DIR;
}

// Add MCP Workbench build config overrides if provided
Expand Down
31 changes: 26 additions & 5 deletions lib/docs/admin/deploy.md
Original file line number Diff line number Diff line change
Expand Up @@ -294,10 +294,10 @@ npmConfig:
# Use ADC-accessible base images for LISA-Serve and Batch Ingestion
baseImage: <adc-registry>/python:3.11

# Configure offline build dependencies for REST API (nodeenv for prisma-client-py)
# Configure offline build dependencies for REST API (prisma-client-py dependencies)
restApiConfig:
buildConfig:
NODEENV_CACHE_DIR: "./nodeenv-cache" # Path relative to lib/serve/rest-api/
PRISMA_CACHE_DIR: "./PRISMA_CACHE" # Path relative to lib/serve/rest-api/

# Configure offline build dependencies for MCP Workbench (S6 Overlay and rclone)
mcpWorkbenchBuildConfig:
Expand All @@ -311,12 +311,33 @@ You'll also want any model hosting base containers available, e.g. vllm/vllm-ope

For environments without internet access during Docker builds, you can pre-cache required dependencies:

**REST API nodeenv cache** (required by prisma-client-py):
**REST API Prisma cache** (required by prisma-client-py):

The `prisma-client-py` package requires platform-specific binaries and a Node.js environment to function. When Prisma runs for the first time, it downloads these dependencies to `~/.cache/prisma/` and `~/.cache/prisma-python/`. For offline deployments, you need to pre-populate this cache.
Comment thread
dustins marked this conversation as resolved.

Below is an example workflow using an Amazon Linux 2023 instance with Python 3.12:

```bash
# Create the cache directory in the REST API build context
python -m nodeenv lib/serve/rest-api/nodeenv-cache
# Ensure Pip is up-to-date
pip3 install --upgrade pip

# Install Prisma Python package
pip3 install prisma

# Trigger Prisma to download all required binaries and create its Node.js environment
# This populates ~/.cache/prisma/ and ~/.cache/prisma-python/
prisma version

# Copy the complete Prisma cache to your build context
# The wildcard captures both 'prisma' and 'prisma-python' directories
cp -r ~/.cache/prisma* lib/serve/rest-api/PRISMA_CACHE/
```

**Important Notes:**
- The cache is platform-specific. Generate it on a system matching your Docker base image (e.g., for `python:3.13-slim` which is Debian-based, so you may want to use a Debian-based system)
Comment thread
dustins marked this conversation as resolved.
- The `prisma version` command downloads binaries for your current platform
- Both `prisma/` and `prisma-python/` directories are required for offline operation

**MCP Workbench dependencies** (S6 Overlay and rclone):
```bash
# Download S6 Overlay files
Expand Down
2 changes: 1 addition & 1 deletion lib/schema/configSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -711,7 +711,7 @@ const FastApiContainerConfigSchema = z.object({
sslCertIamArn: z.string().nullish().default(null).describe('ARN of the self-signed cert to be used throughout the system'),
imageConfig: ImageAssetSchema.optional().describe('Override image configuration for ECS FastAPI Containers'),
buildConfig: z.object({
NODEENV_CACHE_DIR: z.string().optional().describe('Override with a path relative to the build directory for a pre-cached nodeenv directory. Defaults to NODEENV_CACHE. For offline environments, populate using: python -m nodeenv PATH')
PRISMA_CACHE_DIR: z.string().optional().describe('Override with a path relative to the build directory for a pre-cached prisma directory. Defaults to PRISMA_CACHE.')
Comment thread
dustins marked this conversation as resolved.
}).default({}),
rdsConfig: RdsInstanceConfig
.default({
Expand Down
20 changes: 14 additions & 6 deletions lib/serve/rest-api/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
ARG BASE_IMAGE=python:3.11
FROM ${BASE_IMAGE}

ARG NODEENV_CACHE_DIR=NODEENV_CACHE
ENV NODEENV_CACHE_DIR=$NODEENV_CACHE_DIR
ARG PRISMA_CACHE_DIR=PRISMA_CACHE
ENV PRISMA_CACHE_DIR=$PRISMA_CACHE_DIR

# Install build dependencies for madoka package
RUN apt-get update && apt-get install -y \
Expand All @@ -28,13 +28,21 @@ WORKDIR /app
COPY src/requirements.txt .
RUN pip install --no-cache-dir --upgrade -r requirements.txt

# Copy nodeenv cache directory (always exists, may be empty or populated)
COPY ${NODEENV_CACHE_DIR} /tmp/nodeenv-cache/
# Copy prisma cache directory (always exists, may be empty or populated)
COPY ${PRISMA_CACHE_DIR} /tmp/prisma-cache/

# Pre-cache nodeenv for prisma-client-py
# Pre-cache prisma for prisma-client-py
# If the copied directory has content, use it (for offline environments)
# Otherwise, download it during build (requires internet)
RUN python -m prisma version
RUN mkdir -p /root/.cache && \
if [ -d "/tmp/prisma-cache" ] && [ -n "$(ls /tmp/prisma-cache 2>/dev/null)" ]; then \
Comment thread
dustins marked this conversation as resolved.
echo "Using pre-cached Prisma dependencies from host" && \
cp -r /tmp/prisma-cache/prisma* /root/.cache && \
Comment thread
dustins marked this conversation as resolved.
rm -rf /tmp/prisma-cache; \
else \
echo "Fetching Prisma Dependencies (requires internet)" && \
prisma version; \
fi

# Copy the source code into the container
COPY src/ ./src
Expand Down
2 changes: 0 additions & 2 deletions lib/serve/rest-api/NODEENV_CACHE/.gitkeep

This file was deleted.

5 changes: 5 additions & 0 deletions lib/serve/rest-api/PRISMA_CACHE/.gitkeep
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Placeholder to ensure PRISMA_CACHE directory exists in build context
# For offline builds, populate this directory by copying the complete Prisma cache:
# 1. Install prisma: pip3 install prisma
# 2. Generate cache: prisma version
# 3. Copy cache: cp -r ~/.cache/prisma* lib/serve/rest-api/PRISMA_CACHE/
2 changes: 1 addition & 1 deletion lib/user-interface/react/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "lisa-web",
"private": true,
"version": "6.0.0",
"version": "6.0.1",
"type": "module",
"scripts": {
"dev": "vite",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ export const metricConfigSchema = z.object({
});

export const loadBalancerHealthCheckConfigSchema = z.object({
path: z.string().default('/status'),
path: z.string().default('/health'),
interval: z.number().default(60),
timeout: z.number().default(30),
healthyThresholdCount: z.number().default(2),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export const configurationApi = createApi({
baseQuery: lisaBaseQuery(),
tagTypes: ['configuration'],
refetchOnFocus: true,
refetchOnReconnect: true,
refetchOnMountOrArgChange: true,
endpoints: (builder) => ({
getConfiguration: builder.query<IConfiguration[], string>({
query: (configScope) => ({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export const mcpServerApi = createApi({
baseQuery: lisaBaseQuery(),
tagTypes: ['mcpServers'],
refetchOnFocus: true,
refetchOnReconnect: true,
refetchOnMountOrArgChange: true,
endpoints: (builder) => ({
createMcpServer: builder.mutation<McpServer, NewMcpServer>({
query: (mcpServer) => ({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export const mcpToolsApi = createApi({
baseQuery: lisaBaseQuery(),
tagTypes: ['mcpTools'],
refetchOnFocus: true,
refetchOnReconnect: true,
refetchOnMountOrArgChange: true,
endpoints: (builder) => ({
listMcpTools: builder.query<IMcpTool[], void>({
query: () => ({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export const modelManagementApi = createApi({
baseQuery: lisaBaseQuery(),
tagTypes: ['models'],
refetchOnFocus: true,
refetchOnReconnect: true,
refetchOnMountOrArgChange: true,
endpoints: (builder) => ({
getAllModels: builder.query<IModelListResponse['models'], void>({
query: () => ({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export const promptTemplateApi = createApi({
baseQuery: lisaBaseQuery(),
tagTypes: ['promptTemplates'],
refetchOnFocus: true,
refetchOnReconnect: true,
refetchOnMountOrArgChange: true,
endpoints: (builder) => ({
createPromptTemplate: builder.mutation<PromptTemplate, NewPromptTemplate>({
query: (promptTemplate) => ({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ export const ragApi = createApi({
baseQuery: lisaBaseQuery(),
tagTypes: ['repositories', 'docs', 'repository-status', 'jobs', 'collections'],
refetchOnFocus: true,
refetchOnReconnect: true,
refetchOnMountOrArgChange: true,
endpoints: (builder) => ({
listRagRepositories: builder.query<RagRepositoryConfig[], void>({
query: () => ({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export const sessionApi = createApi({
baseQuery: lisaBaseQuery(),
tagTypes: ['sessions'],
refetchOnFocus: true,
refetchOnReconnect: true,
refetchOnMountOrArgChange: true,
endpoints: (builder) => ({
getSessionById: builder.query<LisaChatSession, string>({
query: (sessionId: string) => ({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export const userPreferencesApi = createApi({
baseQuery: lisaBaseQuery(),
tagTypes: ['user-preferences'],
refetchOnFocus: true,
refetchOnReconnect: true,
refetchOnMountOrArgChange: true,
endpoints: (builder) => ({
updateUserPreferences: builder.mutation<UserPreferences, UserPreferences>({
query: (userPreferences) => ({
Expand Down
4 changes: 2 additions & 2 deletions lisa-sdk/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "lisapy"
version = "6.0.0"
version = "6.0.1"
description = "A simple SDK to help you interact with LISA. LISA is an LLM hosting solution for AWS dedicated clouds or ADCs."
readme = "README.md"
requires-python = ">=3.11"
Expand All @@ -15,7 +15,7 @@ dependencies = [

[tool.poetry]
name = "lisapy"
version = "6.0.0"
version = "6.0.1"
description = "A simple SDK to help you interact with LISA. LISA is an LLM hosting solution for AWS dedicated clouds or ADCs."
authors = ["Steve Goley <sgoley@amazon.com>"]
readme = "README.md"
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@awslabs/lisa",
"version": "6.0.0",
"version": "6.0.1",
"description": "A scalable infrastructure-as-code solution for self-hosting and orchestrating LLM inference with RAG capabilities, providing low-latency access to generative AI and embedding models across multiple providers.",
"homepage": "https://awslabs.github.io/LISA/",
"license": "Apache-2.0",
Expand Down
Loading
Loading