This is an automated email from the ASF dual-hosted git repository.

jin pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-hugegraph-ai.git


The following commit(s) were added to refs/heads/main by this push:
     new 41412a6  feat(llm): support log_block in RAG platform & rest-api (#110)
41412a6 is described below

commit 41412a6320860a05d5e9d0b1826a6460664c1844
Author: Dylan <[email protected]>
AuthorDate: Sat Nov 16 14:51:28 2024 +0800

    feat(llm): support log_block in RAG platform & rest-api (#110)
    
    curl command for rolling logs in cmd:
    ```bash
    curl -X POST "http://127.0.0.1:8001/logs"; -H "Content-Type: 
application/json" \
    -H "Authorization: Bearer 123456" \
    -d "{\"log_token\": \"000000\", \"log_file\": \"llm-server.log\"}"
    ```
    
    ---------
    
    Co-authored-by: imbajin <[email protected]>
---
 hugegraph-llm/src/hugegraph_llm/api/admin_api.py   |  36 +++++
 .../src/hugegraph_llm/api/models/rag_requests.py   |   5 +
 hugegraph-llm/src/hugegraph_llm/api/rag_api.py     |   4 +
 hugegraph-llm/src/hugegraph_llm/config/config.py   |  25 +++-
 .../src/hugegraph_llm/config/config_data.py        |   6 +-
 hugegraph-llm/src/hugegraph_llm/config/generate.py |   2 +-
 .../src/hugegraph_llm/demo/rag_demo/admin_block.py | 153 +++++++++++++++++++++
 .../src/hugegraph_llm/demo/rag_demo/app.py         |  15 +-
 .../src/hugegraph_llm/demo/rag_demo/other_block.py |   2 +-
 .../src/hugegraph_llm/demo/rag_demo/rag_block.py   |  28 ++--
 .../demo/rag_demo/vector_graph_block.py            |   2 +-
 11 files changed, 251 insertions(+), 27 deletions(-)

diff --git a/hugegraph-llm/src/hugegraph_llm/api/admin_api.py 
b/hugegraph-llm/src/hugegraph_llm/api/admin_api.py
new file mode 100644
index 0000000..f933b71
--- /dev/null
+++ b/hugegraph-llm/src/hugegraph_llm/api/admin_api.py
@@ -0,0 +1,36 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+import os
+
+from fastapi import status, APIRouter
+from fastapi.responses import StreamingResponse
+
+from hugegraph_llm.api.exceptions.rag_exceptions import generate_response
+from hugegraph_llm.api.models.rag_requests import LogStreamRequest
+from hugegraph_llm.api.models.rag_response import RAGResponse
+
+
+def admin_http_api(router: APIRouter, log_stream):
+    @router.post("/logs", status_code=status.HTTP_200_OK)
+    async def log_stream_api(req: LogStreamRequest):
+        if os.getenv('ADMIN_TOKEN') != req.admin_token:
+            raise 
generate_response(RAGResponse(status_code=status.HTTP_403_FORBIDDEN, 
detail="Invalid admin_token"))
+        else:
+            log_path = os.path.join("logs", req.log_file)
+
+            # Create a StreamingResponse that reads from the log stream 
generator
+            return StreamingResponse(log_stream(log_path), 
media_type="text/plain")
diff --git a/hugegraph-llm/src/hugegraph_llm/api/models/rag_requests.py 
b/hugegraph-llm/src/hugegraph_llm/api/models/rag_requests.py
index 0e7c666..5ee5c05 100644
--- a/hugegraph-llm/src/hugegraph_llm/api/models/rag_requests.py
+++ b/hugegraph-llm/src/hugegraph_llm/api/models/rag_requests.py
@@ -70,3 +70,8 @@ class RerankerConfigRequest(BaseModel):
     reranker_type: str
     api_key: str
     cohere_base_url: Optional[str] = None
+
+class LogStreamRequest(BaseModel):
+    admin_token: Optional[str] = None
+    log_file: Optional[str] = 'llm-server.log'
+    
\ No newline at end of file
diff --git a/hugegraph-llm/src/hugegraph_llm/api/rag_api.py 
b/hugegraph-llm/src/hugegraph_llm/api/rag_api.py
index 8379550..cf57095 100644
--- a/hugegraph-llm/src/hugegraph_llm/api/rag_api.py
+++ b/hugegraph-llm/src/hugegraph_llm/api/rag_api.py
@@ -14,10 +14,12 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
+import os
 import json
 from typing import Literal
 
 from fastapi import status, APIRouter, HTTPException
+from fastapi.responses import StreamingResponse
 
 from hugegraph_llm.api.exceptions.rag_exceptions import generate_response
 from hugegraph_llm.api.models.rag_requests import (
@@ -25,6 +27,7 @@ from hugegraph_llm.api.models.rag_requests import (
     GraphConfigRequest,
     LLMConfigRequest,
     RerankerConfigRequest, GraphRAGRequest,
+    LogStreamRequest,
 )
 from hugegraph_llm.api.models.rag_response import RAGResponse
 from hugegraph_llm.config import settings
@@ -126,3 +129,4 @@ def rag_http_api(
         else:
             res = status.HTTP_501_NOT_IMPLEMENTED
         return generate_response(RAGResponse(status_code=res, message="Missing 
Value"))
+    
\ No newline at end of file
diff --git a/hugegraph-llm/src/hugegraph_llm/config/config.py 
b/hugegraph-llm/src/hugegraph_llm/config/config.py
index c6e06e7..499d6a0 100644
--- a/hugegraph-llm/src/hugegraph_llm/config/config.py
+++ b/hugegraph-llm/src/hugegraph_llm/config/config.py
@@ -39,7 +39,7 @@ def read_dotenv() -> dict[str, Optional[str]]:
     log.info("Loading %s successfully!", env_path)
     for key, value in env_config.items():
         if key not in os.environ:
-            os.environ[key] = value or ""
+            os.environ[key] = value or "" # upper
     return env_config
 
 
@@ -50,10 +50,10 @@ class Config(ConfigData):
         if os.path.exists(env_path):
             env_config = read_dotenv()
             for key, value in env_config.items():
-                if key in self.__annotations__ and value:
-                    if self.__annotations__[key] in [int, Optional[int]]:
+                if key.lower() in self.__annotations__ and value:
+                    if self.__annotations__[key.lower()] in [int, 
Optional[int]]:
                         value = int(value)
-                    setattr(self, key, value)
+                    setattr(self, key.lower(), value)
         else:
             self.generate_env()
 
@@ -67,7 +67,7 @@ class Config(ConfigData):
         else:
             config_dict = {}
             for k, v in self.__dict__.items():
-                config_dict[k] = v
+                config_dict[k.upper()] = v
             with open(env_path, "w", encoding="utf-8") as f:
                 for k, v in config_dict.items():
                     if v is None:
@@ -76,10 +76,23 @@ class Config(ConfigData):
                         f.write(f"{k}={v}\n")
             log.info("Generate %s successfully!", env_path)
 
+
+    def check_env(self):
+        config_dict = {}
+        for k, v in self.__dict__.items():
+            config_dict[k.upper()] = str(v) if v else ""
+        env_config = dotenv_values(f"{env_path}")
+        for k, v in config_dict.items():
+            if k in env_config:
+                continue
+            log.info("Update %s: %s=%s", env_path, k, v)
+            set_key(env_path, k, v, quote_mode="never")
+
+
     def update_env(self):
         config_dict = {}
         for k, v in self.__dict__.items():
-            config_dict[k] = str(v) if v else ""
+            config_dict[k.upper()] = str(v) if v else ""
         env_config = dotenv_values(f"{env_path}")
         for k, v in config_dict.items():
             if k in env_config and env_config[k] == v:
diff --git a/hugegraph-llm/src/hugegraph_llm/config/config_data.py 
b/hugegraph-llm/src/hugegraph_llm/config/config_data.py
index 3610aa2..004a29e 100644
--- a/hugegraph-llm/src/hugegraph_llm/config/config_data.py
+++ b/hugegraph-llm/src/hugegraph_llm/config/config_data.py
@@ -71,10 +71,14 @@ class ConfigData:
     graph_pwd: Optional[str] = "xxx"
     graph_space: Optional[str] = None
 
+    """Admin settings"""
+    enable_login: Optional[str] = "False"
+    user_token: Optional[str] = "4321"
+    admin_token: Optional[str] = "xxxx"
+
 
 # Additional static content like PromptConfig
 class PromptData:
-
     # Data is detached from llm_op/answer_synthesize.py
     answer_prompt = """You are an expert in knowledge graphs and natural 
language processing.
 Your task is to provide a precise and accurate answer based on the given 
context.
diff --git a/hugegraph-llm/src/hugegraph_llm/config/generate.py 
b/hugegraph-llm/src/hugegraph_llm/config/generate.py
index 0961b39..1d85feb 100644
--- a/hugegraph-llm/src/hugegraph_llm/config/generate.py
+++ b/hugegraph-llm/src/hugegraph_llm/config/generate.py
@@ -22,7 +22,7 @@ from hugegraph_llm.config import Config, PromptConfig
 
 if __name__ == "__main__":
     parser = argparse.ArgumentParser(description="Generate hugegraph-llm 
config file")
-    parser.add_argument("-U", "--update", action="store_true", help="Update 
the config file")
+    parser.add_argument("-U", "--update", default=True, action="store_true", 
help="Update the config file")
     args = parser.parse_args()
     if args.update:
         Config().generate_env()
diff --git a/hugegraph-llm/src/hugegraph_llm/demo/rag_demo/admin_block.py 
b/hugegraph-llm/src/hugegraph_llm/demo/rag_demo/admin_block.py
new file mode 100644
index 0000000..db34b7e
--- /dev/null
+++ b/hugegraph-llm/src/hugegraph_llm/demo/rag_demo/admin_block.py
@@ -0,0 +1,153 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+import asyncio
+import os
+
+import gradio as gr
+from gradio import Request
+
+from hugegraph_llm.utils.log import log
+
+
+async def log_stream(log_path: str):
+    """
+    Stream the content of a log file like `tail -f`.
+    """
+    try:
+        with open(log_path, 'r') as file:
+            while True:
+                line = file.readline()
+                if line:
+                    yield line
+                else:
+                    await asyncio.sleep(0.1)  # Non-blocking sleep
+    except FileNotFoundError:
+        raise Exception(f"Log file not found: {log_path}")
+    except Exception as e:
+        raise Exception(f"An error occurred while reading the log: {str(e)}")
+
+
+# Functions to read each log file
+def read_llm_server_log():
+    try:
+        with open("logs/llm-server.log", "r") as f:
+            # TODO: avoid read the whole file (read latest N lines instead)
+            return f.read()
+    except FileNotFoundError:
+        return "LLM Server log file not found."
+
+
+# Functions to clear each log file
+def clear_llm_server_log():
+    with open("logs/llm-server.log", "w") as f:
+        f.truncate(0)  # Clear the contents of the file
+    return "LLM Server log cleared."
+
+
+# Function to validate password and control access to logs
+def check_password(password, request: Request = None):
+    client_ip = request.client.host if request else "Unknown IP"
+
+    if password == os.getenv('ADMIN_TOKEN'):
+        # Return logs and update visibility
+        llm_log = read_llm_server_log()
+        # Log the successful access with the IP address
+        log.info(f"Logs accessed successfully from IP: {client_ip}")
+        return (
+            llm_log, 
+            gr.update(visible=True), 
+            gr.update(visible=True), 
+            gr.update(visible=True), 
+            gr.update(visible=False))
+    else:
+        # Log the failed attempt with IP address
+        log.error(f"Incorrect password attempt from IP: {client_ip}")
+        return (
+            "",
+            gr.update(visible=False),
+            gr.update(visible=False),
+            gr.update(visible=False),
+            gr.update(value="Incorrect password. Access denied.", visible=True)
+        )
+
+
+def create_admin_block():
+    with gr.Blocks():
+        gr.Markdown("## Admin Info - Password Protected")
+
+        # Password input
+        password_input = gr.Textbox(
+            label="Enter Password",
+            type="password",
+            placeholder="Enter password to access admin information",
+        )
+
+        # Error message box, initially hidden
+        error_message = gr.Textbox(
+            label="",
+            visible=False,
+            interactive=False,
+            elem_classes="error-message"
+        )
+
+        # Button to submit password
+        submit_button = gr.Button("Submit")
+
+        with gr.Row(visible=False) as hidden_row:
+            with gr.Column():
+                # LLM Server log display, refreshes every 1 second
+                gr.Markdown("### LLM Server Log")
+                llm_server_log_output = gr.Textbox(
+                    label="LLM Server Log (llm-server.log)",
+                    lines=20,
+                    value=read_llm_server_log,  # Initial value using the 
function
+                    show_copy_button=True,
+                    elem_classes="log-container",
+                    every=60,  # Refresh every 60 second
+                    autoscroll=True  # Enable auto-scroll
+                )
+                with gr.Row():
+                    with gr.Column():
+                        # Button to clear LLM Server log, initially hidden
+                        clear_llm_server_button = gr.Button("Clear LLM Server 
Log", visible=False)
+                    with gr.Column():
+                        # Button to refresh LLM Server log manually
+                        refresh_llm_server_button = gr.Button("Refresh LLM 
Server Log", visible=False,
+                                                              
variant="primary")
+
+        # Define what happens when the password is submitted
+        submit_button.click(
+            fn=check_password,
+            inputs=[password_input],
+            outputs=[llm_server_log_output, hidden_row, 
clear_llm_server_button,
+                     refresh_llm_server_button, error_message],
+        )
+
+        # Define what happens when the Clear LLM Server Log button is clicked
+        clear_llm_server_button.click(
+            fn=clear_llm_server_log,
+            inputs=[],
+            outputs=[llm_server_log_output],
+        )
+
+        # Define what happens when the Refresh LLM Server Log button is clicked
+        refresh_llm_server_button.click(
+            fn=read_llm_server_log,
+            inputs=[],
+            outputs=[llm_server_log_output],
+        )
diff --git a/hugegraph-llm/src/hugegraph_llm/demo/rag_demo/app.py 
b/hugegraph-llm/src/hugegraph_llm/demo/rag_demo/app.py
index 89696b9..16cabf4 100644
--- a/hugegraph-llm/src/hugegraph_llm/demo/rag_demo/app.py
+++ b/hugegraph-llm/src/hugegraph_llm/demo/rag_demo/app.py
@@ -25,6 +25,7 @@ from fastapi import FastAPI, Depends, APIRouter
 from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
 
 from hugegraph_llm.api.rag_api import rag_http_api
+from hugegraph_llm.api.admin_api import admin_http_api
 from hugegraph_llm.config import settings, prompt
 from hugegraph_llm.demo.rag_demo.configs_block import (
     create_configs_block,
@@ -36,6 +37,7 @@ from hugegraph_llm.demo.rag_demo.configs_block import (
 from hugegraph_llm.demo.rag_demo.other_block import create_other_block
 from hugegraph_llm.demo.rag_demo.rag_block import create_rag_block, rag_answer
 from hugegraph_llm.demo.rag_demo.vector_graph_block import 
create_vector_graph_block
+from hugegraph_llm.demo.rag_demo.admin_block import create_admin_block, 
log_stream
 from hugegraph_llm.resources.demo.css import CSS
 from hugegraph_llm.utils.log import log
 
@@ -43,7 +45,7 @@ sec = HTTPBearer()
 
 
 def authenticate(credentials: HTTPAuthorizationCredentials = Depends(sec)):
-    correct_token = os.getenv("TOKEN")
+    correct_token = os.getenv("USER_TOKEN")
     if credentials.credentials != correct_token:
         from fastapi import HTTPException
 
@@ -91,8 +93,11 @@ def init_rag_ui() -> gr.Interface:
             textbox_input_schema, textbox_info_extract_template = 
create_vector_graph_block()
         with gr.Tab(label="2. (Graph)RAG & User Functions 📖"):
             textbox_inp, textbox_answer_prompt_input = create_rag_block()
-        with gr.Tab(label="3. Others Tools 🚧"):
+        with gr.Tab(label="3. Graph Tools 🚧"):
             create_other_block()
+        with gr.Tab(label="4. Admin Tools ⚙️"):
+            create_admin_block()
+        
 
         def refresh_ui_config_prompt() -> tuple:
             settings.from_env()
@@ -128,11 +133,15 @@ if __name__ == "__main__":
     args = parser.parse_args()
     app = FastAPI()
     api_auth = APIRouter(dependencies=[Depends(authenticate)])
+    
+    settings.check_env()
+    prompt.update_yaml_file()
 
     hugegraph_llm = init_rag_ui()
     rag_http_api(api_auth, rag_answer, apply_graph_config, apply_llm_config, 
apply_embedding_config,
                  apply_reranker_config)
-
+    admin_http_api(api_auth, log_stream)
+    
     app.include_router(api_auth)
     auth_enabled = os.getenv("ENABLE_LOGIN", "False").lower() == "true"
     log.info("(Status) Authentication is %s now.", "enabled" if auth_enabled 
else "disabled")
diff --git a/hugegraph-llm/src/hugegraph_llm/demo/rag_demo/other_block.py 
b/hugegraph-llm/src/hugegraph_llm/demo/rag_demo/other_block.py
index 5297f8a..a156442 100644
--- a/hugegraph-llm/src/hugegraph_llm/demo/rag_demo/other_block.py
+++ b/hugegraph-llm/src/hugegraph_llm/demo/rag_demo/other_block.py
@@ -21,7 +21,7 @@ from hugegraph_llm.utils.hugegraph_utils import 
init_hg_test_data, run_gremlin_q
 
 
 def create_other_block():
-    gr.Markdown("""## 4. Other Tools """)
+    gr.Markdown("""## Other Tools """)
     with gr.Row():
         inp = gr.Textbox(value="g.V().limit(10)", label="Gremlin query", 
show_copy_button=True, lines=8)
         out = gr.Code(label="Output", language="json", 
elem_classes="code-container-show")
diff --git a/hugegraph-llm/src/hugegraph_llm/demo/rag_demo/rag_block.py 
b/hugegraph-llm/src/hugegraph_llm/demo/rag_demo/rag_block.py
index 3dd9eec..c4263f4 100644
--- a/hugegraph-llm/src/hugegraph_llm/demo/rag_demo/rag_block.py
+++ b/hugegraph-llm/src/hugegraph_llm/demo/rag_demo/rag_block.py
@@ -91,7 +91,7 @@ def rag_answer(
 
 def create_rag_block():
     # pylint: disable=R0915 (too-many-statements)
-    gr.Markdown("""## 2. RAG with HugeGraph""")
+    gr.Markdown("""## 1. HugeGraph RAG Query""")
     with gr.Row():
         with gr.Column(scale=2):
             inp = gr.Textbox(value=prompt.default_question, label="Question", 
show_copy_button=True, lines=2)
@@ -122,7 +122,7 @@ def create_rag_block():
                         value="reranker" if online_rerank else "bleu",
                         label="Rerank method",
                     )
-                    graph_ratio = gr.Slider(0, 1, 0.5, label="Graph Ratio", 
step=0.1, interactive=False)
+                    graph_ratio = gr.Slider(0, 1, 0.6, label="Graph Ratio", 
step=0.1, interactive=False)
 
                 graph_vector_radio.change(
                     toggle_slider, inputs=graph_vector_radio, 
outputs=graph_ratio
@@ -155,7 +155,7 @@ def create_rag_block():
         outputs=[raw_out, vector_only_out, graph_only_out, graph_vector_out],
     )
 
-    gr.Markdown("""## 3. User Functions (Back-testing)
+    gr.Markdown("""## 2. (Batch) Back-testing )
     > 1. Download the template file & fill in the questions you want to test.
     > 2. Upload the file & click the button to generate answers. (Preview 
shows the first 40 lines)
     > 3. The answer options are the same as the above RAG/Q&A frame 
@@ -200,17 +200,17 @@ def create_rag_block():
         return df
 
     def several_rag_answer(
-        is_raw_answer: bool,
-        is_vector_only_answer: bool,
-        is_graph_only_answer: bool,
-        is_graph_vector_answer: bool,
-        graph_ratio: float,
-        rerank_method: Literal["bleu", "reranker"],
-        near_neighbor_first: bool,
-        custom_related_information: str,
-        answer_prompt: str,
-        progress=gr.Progress(track_tqdm=True),
-        answer_max_line_count: int = 1,
+            is_raw_answer: bool,
+            is_vector_only_answer: bool,
+            is_graph_only_answer: bool,
+            is_graph_vector_answer: bool,
+            graph_ratio: float,
+            rerank_method: Literal["bleu", "reranker"],
+            near_neighbor_first: bool,
+            custom_related_information: str,
+            answer_prompt: str,
+            progress=gr.Progress(track_tqdm=True),
+            answer_max_line_count: int = 1,
     ):
         df = pd.read_excel(questions_path, dtype=str)
         total_rows = len(df)
diff --git 
a/hugegraph-llm/src/hugegraph_llm/demo/rag_demo/vector_graph_block.py 
b/hugegraph-llm/src/hugegraph_llm/demo/rag_demo/vector_graph_block.py
index 1af8a51..b8e087c 100644
--- a/hugegraph-llm/src/hugegraph_llm/demo/rag_demo/vector_graph_block.py
+++ b/hugegraph-llm/src/hugegraph_llm/demo/rag_demo/vector_graph_block.py
@@ -43,7 +43,7 @@ def store_prompt(schema, example_prompt):
 def create_vector_graph_block():
     # pylint: disable=no-member
     gr.Markdown(
-        """## 1. Build Vector/Graph Index & Extract Knowledge Graph
+        """## Build Vector/Graph Index & Extract Knowledge Graph
 - Docs:
   - text: Build rag index from plain text
   - file: Upload file(s) which should be <u>TXT</u> or <u>.docx</u> (Multiple 
files can be selected together)

Reply via email to