Skip to content

Commit 762f764

Browse files
authored
Release v3.5.0 into Main
2 parents 090ebfe + 44fe888 commit 762f764

File tree

80 files changed

+4366
-7486
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

80 files changed

+4366
-7486
lines changed

CHANGELOG.md

+37
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,40 @@
1+
# v3.5.0
2+
## Key Features
3+
### User Interface Modernization
4+
- New year new me? We are rolling out an updated user interface (UI) in Q1. This release is the first stage of this effort.
5+
- **Document Summarization**
6+
- Building on existing non-RAG in context capabilities, we added a more comprehensive Document Summarization feature. This includes a dedicated modal interface where users:
7+
- Upload text-based documents
8+
- Select from approved summarization models
9+
- Select and customize summarization prompts
10+
- Choose between integrating summaries into existing chat sessions or initiating new ones
11+
- System administrators retain full control through configuration settings in the Admin Configuration page
12+
13+
## Other UI Enhancements
14+
- Refactored chatbot UI in advance of upcoming UI improvements and this launch
15+
- Consolidated existing chatbot features to streamline the UI
16+
- Added several components to improve user experience: copy button, response generation animation
17+
- Markdown formatting updated in LLM responses
18+
19+
## Other System Enhancements
20+
- Enhanced user data integration with RAG metadata infrastructure, enabling improved file management within vector stores
21+
- Optimized RAG metadata schema to accommodate expanded documentation requirements
22+
- Started updating sdk to be compliant with current APIs
23+
- Implementation of updated corporate brand guidelines
24+
25+
## Coming soon
26+
Our development roadmap includes several significant UI/UX enhancements:
27+
- Streamlined vector store file administration and access control
28+
- Integrated ingestion pipeline management
29+
- Enhanced Model Management user interface
30+
31+
## Acknowledgements
32+
* @bedanley
33+
* @estohlmann
34+
* @dustins
35+
36+
**Full Changelog**: https://github.com/awslabs/LISA/compare/v3.4.0...v3.5.0
37+
138
# v3.4.0
239
## Key Features
340
### Vector Store Support

Makefile

+1
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ createPythonEnvironment:
154154
installPythonRequirements:
155155
pip3 install pip --upgrade
156156
pip3 install -r requirements-dev.txt
157+
pip3 install -e lisa-sdk
157158

158159

159160
## Set up TypeScript interpreter environment

VERSION

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
3.4.0
1+
3.5.0

lambda/authorizer/lambda_functions.py

+33-11
Original file line numberDiff line numberDiff line change
@@ -54,26 +54,29 @@ def lambda_handler(event: Dict[str, Any], context) -> Dict[str, Any]: # type: i
5454
jwt_groups_property = os.environ.get("JWT_GROUPS_PROP", "")
5555

5656
deny_policy = generate_policy(effect="Deny", resource=event["methodArn"])
57-
57+
groups: str
5858
if id_token in get_management_tokens():
59-
allow_policy = generate_policy(effect="Allow", resource=event["methodArn"], username="lisa-management-token")
59+
username = "lisa-management-token"
60+
# Add management token to Admin groups
61+
groups = json.dumps([admin_group])
62+
allow_policy = generate_policy(effect="Allow", resource=event["methodArn"], username=username)
63+
allow_policy["context"] = {"username": username, "groups": groups}
6064
logger.debug(f"Generated policy: {allow_policy}")
6165
return allow_policy
6266

6367
if jwt_data := id_token_is_valid(id_token=id_token, client_id=client_id, authority=authority):
6468
is_admin_user = is_admin(jwt_data, admin_group, jwt_groups_property)
65-
groups = get_property_path(jwt_data, jwt_groups_property)
66-
allow_policy = generate_policy(effect="Allow", resource=event["methodArn"], username=jwt_data["sub"])
67-
allow_policy["context"] = {"username": jwt_data["sub"], "groups": json.dumps(groups or [])}
69+
groups = json.dumps(get_property_path(jwt_data, jwt_groups_property) or [])
70+
username = find_jwt_username(jwt_data)
71+
allow_policy = generate_policy(effect="Allow", resource=event["methodArn"], username=username)
72+
allow_policy["context"] = {"username": username, "groups": groups}
6873

6974
if requested_resource.startswith("/models") and not is_admin_user:
7075
# non-admin users can still list models
7176
if event["path"].rstrip("/") != "/models":
72-
username = jwt_data.get("sub", "user")
7377
logger.info(f"Deny access to {username} due to non-admin accessing /models api.")
7478
return deny_policy
7579
if requested_resource.startswith("/configuration") and request_method == "PUT" and not is_admin_user:
76-
username = jwt_data.get("sub", "user")
7780
logger.info(f"Deny access to {username} due to non-admin trying to update configuration.")
7881
return deny_policy
7982
logger.debug(f"Generated policy: {allow_policy}")
@@ -160,6 +163,22 @@ def get_property_path(data: dict[str, Any], property_path: str) -> Optional[Any]
160163
return current_node
161164

162165

166+
def find_jwt_username(jwt_data: dict[str, str]) -> str:
167+
"""Find the username in the JWT. If the key 'username' doesn't exist, return 'sub', which will be a UUID"""
168+
username = None
169+
if "username" in jwt_data:
170+
username = jwt_data.get("username")
171+
if "cognito:username" in jwt_data:
172+
username = jwt_data.get("cognito:username")
173+
else:
174+
username = jwt_data.get("sub")
175+
176+
if not username:
177+
raise ValueError("No username found in JWT")
178+
179+
return username
180+
181+
163182
@cache
164183
def get_management_tokens() -> list[str]:
165184
"""Return secret management tokens if they exist."""
@@ -170,10 +189,13 @@ def get_management_tokens() -> list[str]:
170189
secret_tokens.append(
171190
secrets_manager.get_secret_value(SecretId=secret_id, VersionStage="AWSCURRENT")["SecretString"]
172191
)
173-
secret_tokens.append(
174-
secrets_manager.get_secret_value(SecretId=secret_id, VersionStage="AWSPREVIOUS")["SecretString"]
175-
)
192+
try:
193+
secret_tokens.append(
194+
secrets_manager.get_secret_value(SecretId=secret_id, VersionStage="AWSPREVIOUS")["SecretString"]
195+
)
196+
except Exception:
197+
logger.info("No previous management token version found")
176198
except ClientError as e:
177-
logger.warn(f"Unable to fetch {secret_id}. {e.response['Error']['Code']}: {e.response['Error']['Message']}")
199+
logger.warning(f"Unable to fetch {secret_id}. {e.response['Error']['Code']}: {e.response['Error']['Message']}")
178200

179201
return secret_tokens

lambda/models/domain_objects.py

+71-3
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,19 @@
1414

1515
"""Domain objects for interacting with the model endpoints."""
1616

17+
import logging
1718
import time
1819
import uuid
1920
from enum import Enum
20-
from typing import Annotated, Any, Dict, List, Optional, Union
21+
from typing import Annotated, Any, Dict, Generator, List, Optional, TypeAlias, Union
2122

2223
from pydantic import BaseModel, ConfigDict, Field, NonNegativeInt, PositiveInt
2324
from pydantic.functional_validators import AfterValidator, field_validator, model_validator
2425
from typing_extensions import Self
2526
from utilities.validators import validate_all_fields_defined, validate_any_fields_defined, validate_instance_type
2627

28+
logger = logging.getLogger(__name__)
29+
2730

2831
class InferenceContainer(str, Enum):
2932
"""Enum representing the interface container type."""
@@ -167,6 +170,15 @@ def validate_environment(cls, environment: Dict[str, str]) -> Dict[str, str]:
167170
return environment
168171

169172

173+
class ModelFeature(BaseModel):
174+
__exceptions: List[Any] = []
175+
name: str
176+
overview: str
177+
178+
def __init__(self, **kwargs: Any) -> None:
179+
super().__init__(**kwargs)
180+
181+
170182
class LISAModel(BaseModel):
171183
"""Core model definition fields."""
172184

@@ -181,6 +193,7 @@ class LISAModel(BaseModel):
181193
modelUrl: Optional[str] = None
182194
status: ModelStatus
183195
streaming: bool
196+
features: Optional[List[ModelFeature]] = None
184197

185198

186199
class ApiResponseBase(BaseModel):
@@ -202,6 +215,7 @@ class CreateModelRequest(BaseModel):
202215
modelType: ModelType
203216
modelUrl: Optional[str] = None
204217
streaming: Optional[bool] = False
218+
features: Optional[List[ModelFeature]] = None
205219

206220
@model_validator(mode="after")
207221
def validate_create_model_request(self) -> Self:
@@ -301,6 +315,28 @@ class IngestionType(Enum):
301315
MANUAL = "manual"
302316

303317

318+
RagDocumentDict: TypeAlias = Dict[str, Any]
319+
320+
321+
class ChunkStrategyType(Enum):
322+
"""Enum for different types of chunking strategies."""
323+
324+
FIXED = "fixed"
325+
326+
327+
class RagSubDocument(BaseModel):
328+
"""Rag Sub-Document Entity for storing in DynamoDB."""
329+
330+
document_id: str
331+
subdocs: list[str] = Field(default_factory=lambda: [])
332+
index: int = Field(exclude=True)
333+
sk: Optional[str] = None
334+
335+
def __init__(self, **data: Any) -> None:
336+
super().__init__(**data)
337+
self.sk = f"subdoc#{self.document_id}#{self.index}"
338+
339+
304340
class RagDocument(BaseModel):
305341
"""Rag Document Entity for storing in DynamoDB."""
306342

@@ -310,16 +346,48 @@ class RagDocument(BaseModel):
310346
collection_id: str
311347
document_name: str
312348
source: str
313-
sub_docs: List[str] = Field(default_factory=lambda: [])
349+
username: str
350+
subdocs: List[str] = Field(default_factory=lambda: [], exclude=True)
351+
chunk_strategy: dict[str, str] = {}
314352
ingestion_type: IngestionType = Field(default_factory=lambda: IngestionType.MANUAL)
315353
upload_date: int = Field(default_factory=lambda: int(time.time()))
316-
354+
chunks: Optional[int] = 0
317355
model_config = ConfigDict(use_enum_values=True, validate_default=True)
318356

319357
def __init__(self, **data: Any) -> None:
320358
super().__init__(**data)
321359
self.pk = self.createPartitionKey(self.repository_id, self.collection_id)
360+
self.chunks = len(self.subdocs)
322361

323362
@staticmethod
324363
def createPartitionKey(repository_id: str, collection_id: str) -> str:
325364
return f"{repository_id}#{collection_id}"
365+
366+
def chunk_doc(self, chunk_size: int = 1000) -> Generator[RagSubDocument, None, None]:
367+
"""Chunk the document into smaller sub-documents."""
368+
total_subdocs = len(self.subdocs)
369+
for start_index in range(0, total_subdocs, chunk_size):
370+
end_index = min(start_index + chunk_size, total_subdocs)
371+
yield RagSubDocument(
372+
document_id=self.document_id, subdocs=self.subdocs[start_index:end_index], index=start_index
373+
)
374+
375+
@staticmethod
376+
def join_docs(documents: List[RagDocumentDict]) -> List[RagDocumentDict]:
377+
"""Join the multiple sub-documents into a single document."""
378+
# Group documents by document_id
379+
grouped_docs: dict[str, List[RagDocumentDict]] = {}
380+
for doc in documents:
381+
doc_id = doc.get("document_id", "")
382+
if doc_id not in grouped_docs:
383+
grouped_docs[doc_id] = []
384+
grouped_docs[doc_id].append(doc)
385+
386+
# Join same document_id into single RagDocument
387+
joined_docs: List[RagDocumentDict] = []
388+
for docs in grouped_docs.values():
389+
joined_doc = docs[0]
390+
joined_doc["subdocs"] = [sub_doc for doc in docs for sub_doc in (doc.get("subdocs", []) or [])]
391+
joined_docs.append(joined_doc)
392+
393+
return joined_docs

0 commit comments

Comments
 (0)