Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
f96c664
Bump Mui X Charts Packages (#244)
ewilliams-cloudera Jun 12, 2025
ce534dd
Remove crew, replace with custom tool-calling (#243)
jkwatson Jun 12, 2025
3d78fbb
Update release version to 1.23.0-beta
actions-user Jun 12, 2025
eb87355
Fix Python logging alignment (#246)
mliu-cloudera Jun 13, 2025
a59662d
Bump brace-expansion in /ui in the npm_and_yarn group across 1 direct…
dependabot[bot] Jun 13, 2025
70c9830
Fix docker build/compose issues (#240)
jkwatson Jun 13, 2025
5ad0c04
Streaming chat cleanup & in-process docling (#247)
jkwatson Jun 18, 2025
ea23106
Update release version to 1.23.0
actions-user Jun 18, 2025
42aca02
Show loader for doc summaries and open response links in new tab (#249)
ewilliams-cloudera Jun 18, 2025
626ebcb
Save artifacts in the repository, rather than using the release artif…
jkwatson Jun 23, 2025
193a3cf
Update release version to 1.23.0-beta
actions-user Jun 23, 2025
ba1bc97
Implement fake-streaming for non-streaming tool models (#251)
jkwatson Jun 24, 2025
c0503ed
Update release version to 1.23.0-beta
actions-user Jun 24, 2025
45d705c
Enable tool calling by default for a subset of models (#252)
jkwatson Jun 24, 2025
f8cede5
Update release version to 1.23.0-beta
actions-user Jun 24, 2025
97be352
Misc. changes (#253)
jkwatson Jun 26, 2025
c7c9987
New Tools Manager (#255)
ewilliams-cloudera Jun 26, 2025
37e0ee5
Update release version to 1.23.0-beta
actions-user Jun 26, 2025
b2162f8
Update release version to 1.23.0
actions-user Jul 8, 2025
d04759d
Get docling fixes onto main (#258)
jkwatson Jul 8, 2025
405ce6e
Merge branch 'release/1' into main
jkwatson Jul 8, 2025
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
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# prebuilt_artifacts/* filter=lfs diff=lfs merge=lfs -text
20 changes: 15 additions & 5 deletions .github/workflows/publish_release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ on:
- customer-hotfix
jobs:
build:

runs-on: ubuntu-latest

steps:
Expand All @@ -29,6 +28,13 @@ jobs:
with:
ref: ${{ github.event.inputs.BRANCH }}
ssh-key: ${{ secrets.DEPLOY_KEY }}
lfs: true

- name: Install Git LFS
run: |
sudo apt-get update && sudo apt-get install git-lfs
- name: Initialize Git LFS
run: git lfs install

- name: Set up JDK 21
uses: actions/setup-java@v4
Expand Down Expand Up @@ -102,14 +108,18 @@ jobs:
run: |
git config --local user.name actions-user
git config --local user.email "actions@github.com"
echo "export RELEASE_TAG=${{ github.event.inputs.VERSION }}" > release_version.txt
git add release_version.txt
echo "export RELEASE_TAG=${{ github.event.inputs.VERSION }}" > scripts/release_version.txt
mkdir -p prebuilt_artifacts
cp backend/build/libs/rag-api.jar prebuilt_artifacts/
cp ui/fe-dist.tar.gz prebuilt_artifacts/
cp ui/express/node-dist.tar.gz prebuilt_artifacts/
git add prebuilt_artifacts
git add scripts/release_version.txt
if ! git diff --cached --quiet; then
git commit -m "Update release version to ${{ github.event.inputs.VERSION }}"
git push
else
echo "No changes to commit"
fi
working-directory: scripts
env:
GITHUB_TOKEN: ${{ github.token }}
GITHUB_TOKEN: ${{ github.token }}
41 changes: 41 additions & 0 deletions .github/workflows/publish_runtime.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
name: publish_runtime.yml
on:
workflow_dispatch:
inputs:
VERSION:
description: 'Version of runtime to release'
required: true
BRANCH:
description: 'Branch to use for runtime build'
required: true
default: 'main'
type: choice
options:
- main
- mob/main
- release/1
- customer-hotfix
jobs:
runtime-build:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
ref: ${{ github.event.inputs.BRANCH }}
ssh-key: ${{ secrets.DEPLOY_KEY }}
lfs: true

- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Build Docker image
run: |
docker build -t ghcr.io/cloudera/rag-studio-runtime:${{ github.event.inputs.VERSION }} -t ghcr.io/cloudera/rag-studio-runtime:latest -f runtime.Dockerfile .
docker push ghcr.io/cloudera/rag-studio-runtime:${{ github.event.inputs.VERSION }}
docker push ghcr.io/cloudera/rag-studio-runtime:latest
working-directory: llm-service
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@ databases/
**/.DS_Store
.history
addresses/
tools/
tools/mcp.json
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,18 @@ the Node service locally, you can do so by following these steps:
docker run -p 6333:6333 -p 6334:6334 -v $(pwd)/databases/qdrant_storage:/qdrant/storage:z qdrant/qdrant
```

#### Modifying UI in CML

* This is an unsupported workflow, but it is possible to modify the UI code in CML.

- Start a CML Session from a CML Project that has the RAG Studio AMP installed.
- Open the terminal in the CML Session and navigate to the `ui` directory.
- Run `source ~/.bashrc` to ensure the Node environment variables are loaded.
- Install PNPM using `npm install -g pnpm`. Docs on PNPM can be found here: https://pnpm.io/installation#using-npm
- Run `pnpm install` to install the dependencies.
- Make your changes to the UI code in the `ui` directory.
- Run `pnpm build` to build the new UI bundle.

## The Fine Print

IMPORTANT: Please read the following before proceeding. This AMP includes or otherwise depends on certain third party software packages. Information about such third party software packages are made available in the notice file associated with this AMP. By configuring and launching this AMP, you will cause such third party software packages to be downloaded and installed into your environment, in some instances, from third parties' websites. For each third party software package, please see the notice file and the applicable websites for more information, including the applicable license terms. If you do not wish to download and install the third party software packages, do not configure, launch or otherwise use this AMP. By configuring, launching or otherwise using the AMP, you acknowledge the foregoing statement and agree that Cloudera is not responsible or liable in any way for the third party software packages.
Original file line number Diff line number Diff line change
Expand Up @@ -123,11 +123,11 @@ public List<Project> getProjects(String username) {
SELECT *
FROM project
WHERE created_by_id = :createdById
OR default_project = true
OR default_project = :default
""";
handle.registerRowMapper(ConstructorMapper.factory(Project.class));
try (Query query = handle.createQuery(sql)) {
query.bind("createdById", username);
query.bind("createdById", username).bind("default", true);
return query.mapTo(Project.class).list();
}
});
Expand All @@ -145,11 +145,12 @@ public Project getDefaultProject() {
"""
SELECT *
FROM project
WHERE default_project = true
WHERE default_project = :default
""";
handle.registerRowMapper(ConstructorMapper.factory(Project.class));
try (Query query = handle.createQuery(sql)) {
return query
.bind("default", true)
.mapTo(Project.class)
.findOne()
.orElseThrow(() -> new NotFound("Default project not found"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,13 @@ BEGIN;

CREATE TABLE project
(
id BIGINT auto_increment NOT NULL,
id SERIAL PRIMARY KEY,
name VARCHAR(1024) NOT NULL,
default_project BOOLEAN NOT NULL DEFAULT FALSE,
time_created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
time_updated TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
created_by_id VARCHAR(255) NOT NULL,
updated_by_id VARCHAR(255) NOT NULL,
CONSTRAINT PK_project PRIMARY KEY (id)
updated_by_id VARCHAR(255) NOT NULL
);

COMMIT;
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,6 @@

BEGIN;

INSERT INTO project (name, created_by_id, updated_by_id) VALUES ('Default', 'admin', 'admin');
INSERT INTO project (name, DEFAULT_PROJECT, created_by_id, updated_by_id) VALUES ('Default', true,'admin', 'admin');

COMMIT;
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,9 @@ BEGIN;

CREATE TABLE project_data_source
(
id BIGINT auto_increment NOT NULL,
id SERIAL PRIMARY KEY,
project_id BIGINT NOT NULL,
data_source_id BIGINT NOT NULL,
CONSTRAINT PK_project_ds PRIMARY KEY (id)
data_source_id BIGINT NOT NULL
);

COMMIT;
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,6 @@
* DATA.
*/

SET MODE MYSQL;

BEGIN;

DELETE from project_data_source where project_id = (select id from project where default_project = true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,6 @@
* DATA.
*/

SET MODE MYSQL;

BEGIN;

INSERT INTO project_data_source (project_id, data_source_id)
Expand Down
1 change: 1 addition & 0 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ services:
- S3_RAG_DOCUMENT_BUCKET=cloudera-ai-rag-dev-us-west-2
- QDRANT_HOST=qdrant
- API_URL=http://api:8080
- MLFLOW_RECONCILER_DATA_PATH=/tmp
depends_on:
- qdrant
db:
Expand Down
6 changes: 1 addition & 5 deletions docs/allow_list.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,9 @@ NVM:
https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh

Node 22:
https://nodejs.org/dist/v22.15.0/node-v22.15.0-darwin-arm64.tar.xz
https://nodejs.org/dist/v22.15.0/node-v22.15.0-linux-x64.tar.gz

RAG Studio artifacts:
# note: these first 3 redirect to the specific release url (eg. releases/download/1.16.0/...)
https://github.com/cloudera/CML_AMP_RAG_Studio/releases/latest/download/rag-api.jar
https://github.com/cloudera/CML_AMP_RAG_Studio/releases/latest/download/fe-dist.tar.gz
https://github.com/cloudera/CML_AMP_RAG_Studio/releases/latest/download/node-dist.tar.gz
https://github.com/cloudera/CML_AMP_RAG_Studio/releases/download/model_download/craft_mlt_25k.pth
https://github.com/cloudera/CML_AMP_RAG_Studio/releases/download/model_download/latin_g2.pth

Expand Down
32 changes: 22 additions & 10 deletions llm-service/app/ai/indexing/base.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import logging
import os
from abc import abstractmethod
from dataclasses import dataclass
Expand All @@ -6,13 +7,17 @@

from .readers.base_reader import BaseReader, ReaderConfig
from .readers.csv import CSVReader
from .readers.docling_reader import DoclingReader
from .readers.docx import DocxReader
from .readers.images import ImagesReader
from .readers.json import JSONReader
from .readers.markdown import MdReader
from .readers.pdf import PDFReader
from .readers.pptx import PptxReader
from .readers.simple_file import SimpleFileReader
from ...config import settings

logger = logging.getLogger(__name__)

READERS: Dict[str, Type[BaseReader]] = {
".pdf": PDFReader,
Expand All @@ -29,6 +34,11 @@
".png": ImagesReader,
}

DOCLING_READERS: Dict[str, Type[BaseReader]] = {
".pdf": DoclingReader,
".html": DoclingReader,
}


@dataclass
class NotSupportedFileExtensionError(Exception):
Expand All @@ -50,17 +60,19 @@ def index_file(self, file_path: Path, doc_id: str) -> None:

def _get_reader_class(self, file_path: Path) -> Type[BaseReader]:
file_extension = os.path.splitext(file_path)[1]
reader_cls = READERS.get(file_extension)
reader_cls: Optional[Type[BaseReader]] = None
if settings.advanced_pdf_parsing and DOCLING_READERS.get(file_extension):
try:
reader_cls = DoclingReader
except Exception as e:
logger.error(
"Error initializing DoclingReader, falling back to default readers",
e,
)
reader_cls = READERS.get(file_extension)
else:
reader_cls = READERS.get(file_extension)
if not reader_cls:
raise NotSupportedFileExtensionError(file_extension)

return reader_cls


def get_reader_class(file_path: Path) -> Type[BaseReader]:
file_extension = os.path.splitext(file_path)[1]
reader_cls = READERS.get(file_extension)
if not reader_cls:
raise NotSupportedFileExtensionError(file_extension)

return reader_cls
89 changes: 89 additions & 0 deletions llm-service/app/ai/indexing/readers/docling_reader.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
#
# CLOUDERA APPLIED MACHINE LEARNING PROTOTYPE (AMP)
# (C) Cloudera, Inc. 2025
# All rights reserved.
#
# Applicable Open Source License: Apache 2.0
#
# NOTE: Cloudera open source products are modular software products
# made up of hundreds of individual components, each of which was
# individually copyrighted. Each Cloudera open source product is a
# collective work under U.S. Copyright Law. Your license to use the
# collective work is as provided in your written agreement with
# Cloudera. Used apart from the collective work, this file is
# licensed for your use pursuant to the open source license
# identified above.
#
# This code is provided to you pursuant a written agreement with
# (i) Cloudera, Inc. or (ii) a third-party authorized to distribute
# this code. If you do not have a written agreement with Cloudera nor
# with an authorized and properly licensed third party, you do not
# have any rights to access nor to use this code.
#
# Absent a written agreement with Cloudera, Inc. ("Cloudera") to the
# contrary, A) CLOUDERA PROVIDES THIS CODE TO YOU WITHOUT WARRANTIES OF ANY
# KIND; (B) CLOUDERA DISCLAIMS ANY AND ALL EXPRESS AND IMPLIED
# WARRANTIES WITH RESPECT TO THIS CODE, INCLUDING BUT NOT LIMITED TO
# IMPLIED WARRANTIES OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY AND
# FITNESS FOR A PARTICULAR PURPOSE; (C) CLOUDERA IS NOT LIABLE TO YOU,
# AND WILL NOT DEFEND, INDEMNIFY, NOR HOLD YOU HARMLESS FOR ANY CLAIMS
# ARISING FROM OR RELATED TO THE CODE; AND (D)WITH RESPECT TO YOUR EXERCISE
# OF ANY RIGHTS GRANTED TO YOU FOR THE CODE, CLOUDERA IS NOT LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, PUNITIVE OR
# CONSEQUENTIAL DAMAGES INCLUDING, BUT NOT LIMITED TO, DAMAGES
# RELATED TO LOST REVENUE, LOST PROFITS, LOSS OF INCOME, LOSS OF
# BUSINESS ADVANTAGE OR UNAVAILABILITY, OR LOSS OR CORRUPTION OF
# DATA.
#

import logging
from pathlib import Path
from typing import List, Any

from docling.datamodel.document import ConversionResult
from docling.document_converter import DocumentConverter
from docling_core.transforms.chunker.base import BaseChunk
from docling_core.transforms.chunker.hybrid_chunker import HybridChunker
from llama_index.core.schema import Document, TextNode, NodeRelationship

from .base_reader import BaseReader
from .base_reader import ChunksResult
from .pdf import MarkdownSerializerProvider

logger = logging.getLogger(__name__)

class DoclingReader(BaseReader):
def __init__(self, *args: Any, **kwargs: Any) -> None:
super().__init__(*args, **kwargs)

def load_chunks(self, file_path: Path) -> ChunksResult:
document = Document()
document.id_ = self.document_id
self._add_document_metadata(document, file_path)
parent = document.as_related_node_info()

converted_chunks: List[TextNode] = []
logger.debug(f"{file_path=}")
docling_doc: ConversionResult = DocumentConverter().convert(file_path)
chunky_chunks = HybridChunker(serializer_provider=MarkdownSerializerProvider()).chunk(docling_doc.document)
chunky_chunk: BaseChunk
for i, chunky_chunk in enumerate(chunky_chunks):
page_number: int = 0
if not hasattr(chunky_chunk.meta, "doc_items"):
logger.warning(f"Chunk {i} is empty, skipping")
continue
for item in chunky_chunk.meta.doc_items:
page_number= item.prov[0].page_no if item.prov else None
node = TextNode(text=chunky_chunk.text)
if page_number:
node.metadata["page_number"] = page_number
node.metadata["file_name"] = document.metadata["file_name"]
node.metadata["document_id"] = document.metadata["document_id"]
node.metadata["data_source_id"] = document.metadata["data_source_id"]
node.metadata["chunk_number"] = i
node.metadata["chunk_format"] = "markdown"
node.relationships.update(
{NodeRelationship.SOURCE: parent}
)
converted_chunks.append(node)
return ChunksResult(converted_chunks)
Loading
Loading