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 6a672cd refactor(llm): enhance the multi configs for LLM (#212) 6a672cd is described below commit 6a672cdd3f2dc1a2d624ead0a92d32d452a9656b Author: HLOVI <126333723+cg...@users.noreply.github.com> AuthorDate: Fri May 9 17:50:41 2025 +0800 refactor(llm): enhance the multi configs for LLM (#212) This PR aims to enhance configuration handling by integrating environment variable management and improved logging. Key changes include: - Introducing os and dotenv imports to load environment variables in the demo configuration block. - Adding logic to read API keys from a .env file and conditionally applying configuration updates based on their presence. - Updating the base configuration class to verify and update the .env file with added error handling and logging. --------- Co-authored-by: imbajin <j...@apache.org> --- .gitignore | 1 + .../src/hugegraph_llm/config/models/base_config.py | 78 +++++++++++++++++----- .../hugegraph_llm/demo/rag_demo/configs_block.py | 17 ++++- 3 files changed, 79 insertions(+), 17 deletions(-) diff --git a/.gitignore b/.gitignore index afe736e..1eb9be9 100644 --- a/.gitignore +++ b/.gitignore @@ -130,6 +130,7 @@ config_prompt.yaml # Environments .env +.env.bak .venv .vscode .cursor diff --git a/hugegraph-llm/src/hugegraph_llm/config/models/base_config.py b/hugegraph-llm/src/hugegraph_llm/config/models/base_config.py index 7ad0fda..69af55c 100644 --- a/hugegraph-llm/src/hugegraph_llm/config/models/base_config.py +++ b/hugegraph-llm/src/hugegraph_llm/config/models/base_config.py @@ -31,7 +31,7 @@ class BaseConfig(BaseSettings): class Config: env_file = env_path case_sensitive = False - extra = 'ignore' # ignore extra fields to avoid ValidationError + extra = 'ignore' # ignore extra fields to avoid ValidationError env_ignore_empty = True def generate_env(self): @@ -69,19 +69,67 @@ class BaseConfig(BaseSettings): set_key(env_path, k, v if v else "", quote_mode="never") def check_env(self): - config_dict = self.model_dump() - config_dict = {k.upper(): v for k, v in config_dict.items()} - 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 if v else "", quote_mode="never") + """Synchronize configs between .env file and object. + + This method performs two steps: + 1. Updates object attributes from .env file values when they differ + 2. Adds missing configuration items to the .env file + """ + try: + # Read the.env file and prepare object config + env_config = dotenv_values(env_path) + config_dict = {k.upper(): v for k, v in self.model_dump().items()} + + # Step 1: Update the object from .env when values differ + self._sync_env_to_object(env_config, config_dict) + # Step 2: Add missing config items to .env + self._sync_object_to_env(env_config, config_dict) + except Exception as e: + log.error("An error occurred when checking the .env variable file: %s", str(e)) + raise + + def _sync_env_to_object(self, env_config, config_dict): + """Update object attributes from .env file values when they differ.""" + for env_key, env_value in env_config.items(): + if env_key in config_dict: + obj_value = config_dict[env_key] + obj_value_str = str(obj_value) if obj_value is not None else "" + + if env_value != obj_value_str: + log.info("Update configuration from the file: %s=%s (Original value: %s)", + env_key, env_value, obj_value_str) + # Update the object attribute (using lowercase key) + setattr(self, env_key.lower(), env_value) + + def _sync_object_to_env(self, env_config, config_dict): + """Add missing configuration items to the .env file.""" + for obj_key, obj_value in config_dict.items(): + if obj_key not in env_config: + obj_value_str = str(obj_value) if obj_value is not None else "" + log.info("Add configuration items to the environment variable file: %s=%s", + obj_key, obj_value) + # Add to .env + set_key(env_path, obj_key, obj_value_str, quote_mode="never") def __init__(self, **data): - super().__init__(**data) - if not os.path.exists(env_path): - self.generate_env() - else: - self.check_env() - log.info("Loading %s successfully for %s!", env_path, self.__class__.__name__) + try: + file_exists = os.path.exists(env_path) + # Step 1: Load environment variables if file exists + if file_exists: + env_config = dotenv_values(env_path) + for k, v in env_config.items(): + os.environ[k] = v + + # Step 2: Init the parent class with loaded environment variables + super().__init__(**data) + # Step 3: Handle environment file operations after initialization + if not file_exists: + self.generate_env() + else: + # Synchronize configurations between the object and .env file + self.check_env() + + log.info("The %s file was loaded. Class: %s", env_path, self.__class__.__name__) + except Exception as e: + log.error("An error occurred when initializing the configuration object: %s", str(e)) + raise diff --git a/hugegraph-llm/src/hugegraph_llm/demo/rag_demo/configs_block.py b/hugegraph-llm/src/hugegraph_llm/demo/rag_demo/configs_block.py index 73d0600..7903474 100644 --- a/hugegraph-llm/src/hugegraph_llm/demo/rag_demo/configs_block.py +++ b/hugegraph-llm/src/hugegraph_llm/demo/rag_demo/configs_block.py @@ -16,11 +16,13 @@ # under the License. import json +import os from functools import partial from typing import Optional import gradio as gr import requests +from dotenv import dotenv_values from requests.auth import HTTPBasicAuth from hugegraph_llm.config import huge_settings, llm_settings @@ -261,7 +263,8 @@ def create_configs_block() -> list: # TODO : use OOP to refactor the following code with gr.Accordion("2. Set up the LLM.", open=False): - gr.Markdown("> Tips: the openai option also support openai style api from other providers.") + gr.Markdown("> Tips: The OpenAI option also support openai style api from other providers. " + "**Refresh the page** to load the **latest configs** in __UI__.") with gr.Tab(label='chat'): chat_llm_dropdown = gr.Dropdown(choices=["openai", "litellm", "qianfan_wenxin", "ollama/local"], value=getattr(llm_settings, "chat_llm_type"), label="type") @@ -308,7 +311,17 @@ def create_configs_block() -> list: llm_config_input = [gr.Textbox(value="", visible=False) for _ in range(4)] llm_config_button = gr.Button("Apply configuration") llm_config_button.click(apply_llm_config_with_chat_op, inputs=llm_config_input) - + # Determine whether there are Settings in the.env file + dir_name = os.path.dirname + package_path = dir_name(dir_name(dir_name(dir_name(dir_name(os.path.abspath(__file__)))))) + env_path = os.path.join(package_path, ".env") + env_vars = dotenv_values(env_path) + api_extract_key = env_vars.get("OPENAI_EXTRACT_API_KEY") + api_text2sql_key = env_vars.get("OPENAI_TEXT2GQL_API_KEY") + if not api_extract_key: + llm_config_button.click(apply_llm_config_with_text2gql_op, inputs=llm_config_input) + if not api_text2sql_key: + llm_config_button.click(apply_llm_config_with_extract_op, inputs=llm_config_input) with gr.Tab(label='mini_tasks'): extract_llm_dropdown = gr.Dropdown(choices=["openai", "litellm", "qianfan_wenxin", "ollama/local"], value=getattr(llm_settings, "extract_llm_type"), label="type")