Skip to content

Commit 8f1fc23

Browse files
author
Jarno Ensio Hakulinen
committed
refactor config file operations
1 parent 3cd4a39 commit 8f1fc23

5 files changed

Lines changed: 90 additions & 86 deletions

File tree

gui/main_window.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,8 @@ def __init__(self):
5151
QTimer.singleShot(100, lambda: self.deferred_init())
5252

5353
def initialize_singletons(self):
54-
self.function_config_manager = FunctionConfigManager.get_instance()
55-
self.assistant_config_manager = AssistantConfigManager.get_instance()
54+
self.function_config_manager = FunctionConfigManager.get_instance('config')
55+
self.assistant_config_manager = AssistantConfigManager.get_instance('config')
5656
self.task_manager = TaskManager.get_instance(self)
5757
self.assistant_client_manager = AssistantClientManager.get_instance()
5858

@@ -229,6 +229,9 @@ def set_active_ai_client_type(self, ai_client_type : AIClientType):
229229
if self.conversation_thread_clients[self.active_ai_client_type] is not None:
230230
self.conversation_thread_clients[self.active_ai_client_type].save_conversation_threads()
231231

232+
# Save assistant configurations when switching AI client types
233+
self.assistant_config_manager.save_configs()
234+
232235
self.conversation_view.conversationView.clear()
233236
self.active_ai_client_type = ai_client_type
234237
client = None

sdk/azure-ai-assistant/azure/ai/assistant/_version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,4 @@
66
# Changes may cause incorrect behavior and will be lost if the code is regenerated.
77
# --------------------------------------------------------------------------
88

9-
VERSION = "0.3.2a1"
9+
VERSION = "0.3.3a1"

sdk/azure-ai-assistant/azure/ai/assistant/management/assistant_config_manager.py

Lines changed: 52 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -24,28 +24,37 @@ class AssistantConfigManager:
2424
"""
2525
A class to manage the creation, updating, deletion, and loading of assistant configurations from local files.
2626
27-
:param config_folder: The folder path for storing configuration files. Optional, defaults to 'config'.
27+
:param config_folder: The folder path for storing configuration files. Optional, defaults to config folder in the user's home directory.
2828
:type config_folder: str
2929
"""
3030
def __init__(
3131
self,
32-
config_folder : str ='config'
32+
config_folder : Optional[str] = None
3333
) -> None:
34-
self._config_folder = config_folder
34+
if config_folder is None:
35+
self._config_folder = self._default_config_path()
36+
else:
37+
self._config_folder = config_folder
3538
self._last_modified_assistant_name = None
3639
self._configs: dict[str, AssistantConfig] = {}
3740
# Load all assistant configurations under the config folder
3841
self.load_configs()
3942

43+
@staticmethod
44+
def _default_config_path() -> str:
45+
home = os.path.expanduser("~")
46+
return os.path.join(home, ".config", 'azure-ai-assistant')
47+
4048
@classmethod
4149
def get_instance(
4250
cls,
43-
config_folder : str ='config'
51+
config_folder : Optional[str] = None
52+
4453
) -> 'AssistantConfigManager':
4554
"""
4655
Gets the singleton instance of the AssistantConfigManager object.
4756
48-
:param config_folder: The folder path for storing configuration files. Optional, defaults to 'config'.
57+
:param config_folder: The folder path for storing configuration files. Optional, defaults to config folder in the user's home directory.
4958
:type config_folder: str
5059
5160
:return: The singleton instance of the AssistantConfigManager object.
@@ -61,8 +70,8 @@ def update_config(
6170
config_json : str
6271
) -> str:
6372
"""
64-
Updates an existing assistant local configuration.
65-
73+
Updates an existing assistant local configuration in memory.
74+
6675
:param name: The name of the configuration to update.
6776
:type name: str
6877
:param config_json: The JSON string containing the updated configuration data.
@@ -75,8 +84,11 @@ def update_config(
7584
logger.info(f"Updating assistant configuration for '{name}' with data: {config_json}")
7685
new_config_data = json.loads(config_json)
7786
self._validate_config(new_config_data)
78-
name = self._save_config(name, new_config_data)
87+
88+
# Update the configuration in memory without saving to a file
89+
self._configs[name] = AssistantConfig(new_config_data)
7990
self._last_modified_assistant_name = name
91+
8092
return name
8193
except json.JSONDecodeError as e:
8294
raise InvalidJSONError(f"Invalid JSON format: {e}")
@@ -136,9 +148,6 @@ def get_config(
136148
logger.warning(f"No configuration found for '{name}'")
137149
return None
138150

139-
# ensure the configurations are up-to-date
140-
self._load_config(name)
141-
142151
# Return the AssistantConfig object for the given name
143152
return self._configs.get(name, None)
144153

@@ -209,13 +218,19 @@ def _set_last_modified_assistant(self):
209218

210219
self._last_modified_assistant_name = latest_assistant_name
211220

212-
def save_configs(self) -> None:
221+
def save_configs(
222+
self,
223+
config_folder: Optional[str] = None
224+
) -> None:
213225
"""
214226
Saves all assistant local configurations to json files.
227+
228+
:param config_folder: The folder path where the configuration files should be saved. Optional, defaults to the config folder.
229+
:type config_folder: str
215230
"""
216231
# Save all assistant configurations to files
217232
for assistant_name, assistant_config in self._configs.items():
218-
self._save_config(assistant_name, assistant_config._get_config_data())
233+
self.save_config(assistant_name, config_folder or self._config_folder)
219234

220235
def get_last_modified_assistant(self) -> str:
221236
"""
@@ -296,71 +311,39 @@ def _validate_config(self, config_data):
296311
if 'tool_resources' in config_data and config_data.get('tool_resources') is not None and not isinstance(config_data['tool_resources'], dict):
297312
raise ConfigError("Assistant 'tool_resources' must be a dictionary in the configuration")
298313

299-
def _save_config(self, assistant_name, config_data):
300-
# Check if the assistant name and configuration data are provided
301-
if not assistant_name:
302-
raise ConfigError("Assistant name is required")
303-
304-
if not config_data:
305-
raise ConfigError("Assistant configuration data is required")
306-
307-
logger.info(f"Checking for updates in assistant configuration for '{assistant_name}'")
308-
309-
# Handle possible change in assistant name within the configuration data
310-
if 'name' in config_data and config_data['name'] != assistant_name:
311-
logger.info(f"Assistant name changed from '{assistant_name}' to \"{config_data['name']}\"")
312-
313-
# Construct path for potentially existing old configuration files
314-
old_json_config_path = os.path.join(self._config_folder, f"{assistant_name}_assistant_config.json")
315-
old_yaml_config_path = os.path.join(self._config_folder, f"{assistant_name}_assistant_config.yaml")
316-
old_yml_config_path = os.path.join(self._config_folder, f"{assistant_name}_assistant_config.yml")
317-
318-
# Attempt to delete old configuration files if they exist
319-
for old_path in [old_json_config_path, old_yaml_config_path, old_yml_config_path]:
320-
if os.path.exists(old_path):
321-
try:
322-
os.remove(old_path)
323-
logger.info(f"Removed outdated configuration file: {old_path}")
324-
except Exception as e:
325-
logger.error(f"Error deleting outdated file: {e}")
326-
327-
# Update the assistant name to the new name from the configuration data
328-
assistant_name = config_data['name']
314+
def save_config(
315+
self,
316+
name: str,
317+
folder_path : Optional[str] = None
318+
) -> None:
319+
"""
320+
Saves the specified assistant configuration to a file in the given directory.
329321
330-
# Define the new YAML file path for saving the configuration
331-
config_filename = f"{assistant_name}_assistant_config.yaml"
332-
config_path = os.path.join(self._config_folder, config_filename)
322+
:param name: The name of the assistant configuration to save.
323+
:type name: str
324+
:param folder_path: The directory path where the configuration file should be saved. Optional, defaults to the config folder.
325+
:type folder_path: str
326+
"""
327+
if name not in self._configs:
328+
raise ConfigError(f"No configuration found for '{name}'")
333329

334-
# Update in-memory configuration
335-
self._configs[assistant_name] = AssistantConfig(config_data)
336-
337-
logger.info(f"Saving updated configuration for '{assistant_name}' in YAML format")
330+
config_data = self._configs[name]._get_config_data() # assuming AssistantConfig has a method to get its data
331+
config_filename = f"{name}_assistant_config.yaml"
332+
folder_path = folder_path or self._config_folder
333+
config_path = os.path.join(folder_path, config_filename)
338334

339-
# Ensure the configuration directory exists
340-
if not os.path.exists(self._config_folder):
341-
try:
342-
os.makedirs(self._config_folder)
343-
except Exception as e:
344-
logger.error(f"Error creating config directory: {e}")
345-
raise ConfigError(f"Error creating config directory: {e}")
335+
# Ensure the directory exists
336+
if not os.path.exists(folder_path):
337+
os.makedirs(folder_path)
346338

347339
# Save the configuration data in YAML format
348340
try:
349341
with open(config_path, 'w') as file:
350342
yaml.dump(config_data, file, sort_keys=False)
343+
logger.info(f"Configuration for '{name}' saved successfully at '{config_path}'")
351344
except Exception as e:
352-
logger.error(f"Error writing to YAML file: {e}")
353-
raise ConfigError(f"Error writing to YAML file: {e}")
354-
355-
# Delete the corresponding JSON file if it exists
356-
json_config_path = config_path.replace('.yaml', '.json')
357-
if os.path.exists(json_config_path):
358-
try:
359-
os.remove(json_config_path)
360-
logger.info(f"Removed outdated JSON configuration for '{assistant_name}'")
361-
except Exception as e:
362-
logger.error(f"Error deleting outdated JSON file: {e}")
363-
return assistant_name
345+
logger.error(f"Error saving configuration file at '{config_path}': {e}")
346+
raise ConfigError(f"Error saving configuration file: {e}")
364347

365348
@property
366349
def configs(self) -> dict:

sdk/azure-ai-assistant/azure/ai/assistant/management/base_assistant_client.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,14 @@ def _replace_file_references_with_content(self, assistant_config: AssistantConfi
233233
file_references = assistant_config.file_references
234234

235235
try:
236+
# Log the current working directory
237+
cwd = os.getcwd()
238+
logger.info(f"Current working directory: {cwd}")
239+
240+
# Optionally, list files in the current directory
241+
files_in_cwd = os.listdir(cwd)
242+
logger.debug(f"Files in the current directory: {files_in_cwd}")
243+
236244
# Regular expression to find all placeholders in the format {file_reference:X}
237245
pattern = re.compile(r'\{file_reference:(\d+)\}')
238246

sdk/azure-ai-assistant/azure/ai/assistant/management/function_config_manager.py

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
# Copyright (c) Microsoft. All rights reserved.
22
# Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
33

4-
import json
5-
import os, ast, re, sys
6-
from pathlib import Path
74
from azure.ai.assistant.management.function_config import FunctionConfig
85
from azure.ai.assistant.management.exceptions import EngineError
96
from azure.ai.assistant.management.logger_module import logger
107

8+
import json
9+
import os, ast, re, sys
10+
from pathlib import Path
11+
from typing import Optional
12+
1113
# Template for a function spec
1214
function_spec_template = {
1315
"type": "function",
@@ -39,16 +41,24 @@ class FunctionConfigManager:
3941
"""
4042
def __init__(
4143
self,
42-
config_directory : str = 'config'
44+
config_folder : Optional[str] = None
4345
) -> None:
44-
self._config_directory = config_directory
46+
if config_folder is None:
47+
self._config_folder = self._default_config_path()
48+
else:
49+
self._config_folder = config_folder
4550
self.load_function_configs()
4651
self.load_function_error_specs()
4752

53+
@staticmethod
54+
def _default_config_path() -> str:
55+
home = os.path.expanduser("~")
56+
return os.path.join(home, ".config", 'azure-ai-assistant')
57+
4858
@classmethod
4959
def get_instance(
5060
cls,
51-
config_directory : str = 'config'
61+
config_directory : Optional[str] = None
5262
) -> 'FunctionConfigManager':
5363
"""
5464
Get the singleton instance of FunctionConfigManager.
@@ -67,13 +77,13 @@ def load_function_configs(self) -> None:
6777
"""
6878
Loads function specifications from the config directory.
6979
"""
70-
logger.info(f"Loading function specifications from {self._config_directory}")
80+
logger.info(f"Loading function specifications from {self._config_folder}")
7181

7282
# Clear the existing configs
7383
self._function_configs = {}
7484

7585
# Scan the directory for JSON files
76-
for file in Path(self._config_directory).glob("*_function_specs.json"):
86+
for file in Path(self._config_folder).glob("*_function_specs.json"):
7787
self._load_function_spec(file)
7888

7989
def _load_function_spec(self, file_path):
@@ -98,13 +108,13 @@ def load_function_error_specs(self) -> None:
98108
"""
99109
Loads function error specifications from the config directory.
100110
"""
101-
logger.info(f"Loading function error specifications from {self._config_directory}")
111+
logger.info(f"Loading function error specifications from {self._config_folder}")
102112

103113
# Clear the existing configs
104114
self._function_error_specs = {}
105115

106116
# Load the error specs from function_error_specs.json
107-
file_path = Path(self._config_directory) / "function_error_specs.json"
117+
file_path = Path(self._config_folder) / "function_error_specs.json"
108118
logger.info(f"Loading function error specs from {file_path}")
109119
try:
110120
with open(file_path, 'r') as file:
@@ -154,7 +164,7 @@ def save_function_error_specs(self, function_error_specs : dict) -> bool:
154164
"""
155165
try:
156166
# Define path for error specs
157-
file_path = Path(self._config_directory) / "function_error_specs.json"
167+
file_path = Path(self._config_folder) / "function_error_specs.json"
158168

159169
# Write the error specs to the file
160170
with open(file_path, 'w') as file:
@@ -280,8 +290,8 @@ def save_function_spec(
280290
"""
281291
try:
282292
# Define paths for system and user specs
283-
system_file_path = Path(self._config_directory) / "system_function_specs.json"
284-
user_file_path = Path(self._config_directory) / "user_function_specs.json"
293+
system_file_path = Path(self._config_folder) / "system_function_specs.json"
294+
user_file_path = Path(self._config_folder) / "user_function_specs.json"
285295

286296
new_spec_dict = json.loads(new_spec)
287297

@@ -324,7 +334,7 @@ def delete_user_function(self, function_name : str) -> bool:
324334
"""
325335
try:
326336
# Define path for user specs
327-
user_file_path = Path(self._config_directory) / "user_function_specs.json"
337+
user_file_path = Path(self._config_folder) / "user_function_specs.json"
328338

329339
# Delete the function from user specs
330340
if not self._delete_function_spec(function_name, user_file_path):

0 commit comments

Comments
 (0)