From ecec138cb679dd7399dbcebe41471ec578c879c0 Mon Sep 17 00:00:00 2001
From: "Huu Le (Lee)" <39040748+leehuwuj@users.noreply.github.com>
Date: Sat, 3 Feb 2024 01:04:58 +0700
Subject: [PATCH] feat: Add create-llama option to RagCLI (#10405)

---
 docs/use_cases/q_and_a/rag_cli.md | 32 ++++++++++++-
 llama_index/command_line/rag.py   | 74 +++++++++++++++++++++++++++++++
 2 files changed, 105 insertions(+), 1 deletion(-)

diff --git a/docs/use_cases/q_and_a/rag_cli.md b/docs/use_cases/q_and_a/rag_cli.md
index 3d17e7676..9a0a0e840 100644
--- a/docs/use_cases/q_and_a/rag_cli.md
+++ b/docs/use_cases/q_and_a/rag_cli.md
@@ -22,7 +22,7 @@ After that, you can start using the tool:
 
 ```shell
 $ llamaindex-cli rag -h
-usage: llamaindex-cli rag [-h] [-q QUESTION] [-f FILES] [-c] [-v] [--clear]
+usage: llamaindex-cli rag [-h] [-q QUESTION] [-f FILES] [-c] [-v] [--clear] [--create-llama]
 
 options:
   -h, --help            show this help message and exit
@@ -33,6 +33,7 @@ options:
   -c, --chat            If flag is present, opens a chat REPL.
   -v, --verbose         Whether to print out verbose information during execution.
   --clear               Clears out all currently embedded data.
+  --create-llama        Create a LlamaIndex application based on the selected files.
 ```
 
 ## Usage
@@ -58,6 +59,35 @@ Here are some high level steps to get you started:
    ```
 1. **Open a Chat REPL**: You can even open a chat interface within your terminal! Just run `$ llamaindex-cli rag --chat` and start asking questions about the files you've ingested.
 
+### Create a LlamaIndex chat application
+
+You can also create a full-stack chat application with a FastAPI backend and NextJS frontend based on the files that you have selected.
+
+To bootstrap the application, make sure you have NodeJS and npx installed on your machine. If not, please refer to the [LlamaIndex.TS](https://ts.llamaindex.ai/getting_started/installation) documentation for instructions.
+
+Once you have everything set up, creating a new application is easy. Simply run the following command:
+
+`$ llamaindex-cli rag --create-llama`
+
+It will call our `create-llama` tool, so you will need to provide several pieces of information to create the app. You can find more information about the `create-llama` on [npmjs - create-llama](https://www.npmjs.com/package/create-llama#example)
+
+```shell
+❯ llamaindex-cli rag --create-llama
+
+Calling create-llama using data from /tmp/rag-data/...
+
+✔ What is your project named? … my-app
+✔ Which model would you like to use? › gpt-3.5-turbo
+✔ Please provide your OpenAI API key (leave blank to skip): …
+? How would you like to proceed? › - Use arrow-keys. Return to submit.
+   Just generate code (~1 sec)
+   Generate code and install dependencies (~2 min)
+❯  Generate code, install dependencies, and run the app (~2 min)
+...
+```
+
+If you choose the option `Generate code, install dependencies, and run the app (~2 min)`, all dependencies will be installed and the app will run automatically. You can then access the application by going to this address: http://localhost:3000.
+
 ### Supported File Types
 
 Internally, the `rag` CLI tool uses the [SimpleDirectoryReader](/api/llama_index.readers.SimpleDirectoryReader.rst) to parse the raw files in your local filesystem into strings.
diff --git a/llama_index/command_line/rag.py b/llama_index/command_line/rag.py
index f57c880d0..0ea3b26f4 100644
--- a/llama_index/command_line/rag.py
+++ b/llama_index/command_line/rag.py
@@ -1,5 +1,6 @@
 import asyncio
 import os
+import shutil
 from argparse import ArgumentParser
 from glob import iglob
 from pathlib import Path
@@ -24,6 +25,8 @@ from llama_index.readers.base import BaseReader
 from llama_index.response_synthesizers import CompactAndRefine
 from llama_index.utils import get_cache_dir
 
+RAG_HISTORY_FILE_NAME = "files_history.txt"
+
 
 def default_ragcli_persist_dir() -> str:
     return str(Path(get_cache_dir()) / "rag_cli")
@@ -164,6 +167,7 @@ class RagCLI(BaseModel):
         chat: bool = False,
         verbose: bool = False,
         clear: bool = False,
+        create_llama: bool = False,
         **kwargs: Dict[str, Any],
     ) -> None:
         """
@@ -208,6 +212,70 @@ class RagCLI(BaseModel):
             await ingestion_pipeline.arun(show_progress=verbose, documents=documents)
             ingestion_pipeline.persist(persist_dir=self.persist_dir)
 
+            # Append the `--files` argument to the history file
+            with open(f"{self.persist_dir}/{RAG_HISTORY_FILE_NAME}", "a") as f:
+                f.write(files + "\n")
+
+        if create_llama:
+            if shutil.which("npx") is None:
+                print(
+                    "`npx` is not installed. Please install it by calling `npm install -g npx`"
+                )
+            else:
+                history_file_path = Path(f"{self.persist_dir}/{RAG_HISTORY_FILE_NAME}")
+                if not history_file_path.exists():
+                    print(
+                        "No data has been ingested, "
+                        "please specify `--files` to create llama dataset."
+                    )
+                else:
+                    with open(history_file_path) as f:
+                        stored_paths = {line.strip() for line in f if line.strip()}
+                    if len(stored_paths) == 0:
+                        print(
+                            "No data has been ingested, "
+                            "please specify `--files` to create llama dataset."
+                        )
+                    elif len(stored_paths) > 1:
+                        print(
+                            "Multiple files or folders were ingested, which is not supported by create-llama. "
+                            "Please call `llamaindex-cli rag --clear` to clear the cache first, "
+                            "then call `llamaindex-cli rag --files` again with a single folder or file"
+                        )
+                    else:
+                        path = stored_paths.pop()
+                        if "*" in path:
+                            print(
+                                "Glob pattern is not supported by create-llama. "
+                                "Please call `llamaindex-cli rag --clear` to clear the cache first, "
+                                "then call `llamaindex-cli rag --files` again with a single folder or file."
+                            )
+                        elif not os.path.exists(path):
+                            print(
+                                f"The path {path} does not exist. "
+                                "Please call `llamaindex-cli rag --clear` to clear the cache first, "
+                                "then call `llamaindex-cli rag --files` again with a single folder or file."
+                            )
+                        else:
+                            print(f"Calling create-llama using data from {path} ...")
+                            command_args = [
+                                "npx",
+                                "create-llama@latest",
+                                "--frontend",
+                                "--template",
+                                "streaming",
+                                "--framework",
+                                "fastapi",
+                                "--ui",
+                                "shadcn",
+                                "--vector-db",
+                                "none",
+                                "--engine",
+                                "context",
+                                f"--files {path}",
+                            ]
+                            os.system(" ".join(command_args))
+
         if question is not None:
             await self.handle_question(question)
         if chat:
@@ -276,6 +344,12 @@ class RagCLI(BaseModel):
             help="Clears out all currently embedded data.",
             action="store_true",
         )
+        parser.add_argument(
+            "--create-llama",
+            help="Create a LlamaIndex application with your embedded data.",
+            required=False,
+            action="store_true",
+        )
         parser.set_defaults(
             func=lambda args: asyncio.run(instance_generator().handle_cli(**vars(args)))
         )
-- 
GitLab