1
+ from typing import Callable
2
+
1
3
import yaml
2
4
3
5
from litellm ._logging import verbose_proxy_logger
4
6
5
7
6
- def get_file_contents_from_s3 (bucket_name , object_key ):
8
+ async def get_file_contents_from_s3 (bucket_name , object_key ):
7
9
try :
8
10
# v0 rely on boto3 for authentication - allowing boto3 to handle IAM credentials etc
9
11
import tempfile
@@ -20,26 +22,33 @@ def get_file_contents_from_s3(bucket_name, object_key):
20
22
aws_secret_access_key = credentials .secret_key ,
21
23
aws_session_token = credentials .token , # Optional, if using temporary credentials
22
24
)
23
- verbose_proxy_logger .debug (
25
+ verbose_proxy_logger .error (
24
26
f"Retrieving { object_key } from S3 bucket: { bucket_name } "
25
27
)
26
28
response = s3_client .get_object (Bucket = bucket_name , Key = object_key )
27
- verbose_proxy_logger .debug (f"Response: { response } " )
29
+ verbose_proxy_logger .error (f"Response: { response } " )
28
30
29
31
# Read the file contents
30
32
file_contents = response ["Body" ].read ().decode ("utf-8" )
31
- verbose_proxy_logger .debug ("File contents retrieved from S3" )
33
+ verbose_proxy_logger .error ("File contents retrieved from S3" )
32
34
33
35
# Create a temporary file with YAML extension
34
36
with tempfile .NamedTemporaryFile (delete = False , suffix = ".yaml" ) as temp_file :
35
37
temp_file .write (file_contents .encode ("utf-8" ))
36
38
temp_file_path = temp_file .name
37
- verbose_proxy_logger .debug (f"File stored temporarily at: { temp_file_path } " )
39
+ verbose_proxy_logger .error (f"File stored temporarily at: { temp_file_path } " )
38
40
39
41
# Load the YAML file content
40
42
with open (temp_file_path , "r" ) as yaml_file :
41
43
config = yaml .safe_load (yaml_file )
42
44
45
+ # include file config
46
+ config = await process_includes_from_bucket (
47
+ config = config ,
48
+ get_file_method = get_file_contents_from_s3 ,
49
+ bucket_name = bucket_name ,
50
+ )
51
+
43
52
return config
44
53
except ImportError as e :
45
54
# this is most likely if a user is not using the litellm docker container
@@ -64,13 +73,57 @@ async def get_config_file_contents_from_gcs(bucket_name, object_key):
64
73
file_contents = file_contents .decode ("utf-8" )
65
74
# convert to yaml
66
75
config = yaml .safe_load (file_contents )
76
+ # include file config
77
+ config = await process_includes_from_bucket (
78
+ config = config ,
79
+ get_file_method = get_config_file_contents_from_gcs ,
80
+ bucket_name = bucket_name ,
81
+ )
67
82
return config
68
83
69
84
except Exception as e :
70
85
verbose_proxy_logger .error (f"Error retrieving file contents: { str (e )} " )
71
86
return None
72
87
73
88
89
+ async def process_includes_from_bucket (
90
+ config : dict , get_file_method : Callable , bucket_name : str
91
+ ) -> dict :
92
+ """
93
+ Process includes by appending their contents to the main config
94
+
95
+ Handles nested config.yamls with `include` section
96
+
97
+ Example config: This will get the contents from files in `include` and append it
98
+ ```yaml
99
+ include:
100
+ - /path/to/key/model_config.yaml
101
+
102
+ litellm_settings:
103
+ callbacks: ["prometheus"]
104
+ ```
105
+ """
106
+ if "include" not in config :
107
+ return config
108
+
109
+ if not isinstance (config ["include" ], list ):
110
+ raise ValueError ("'include' must be a list of file paths" )
111
+
112
+ # Load and append all included files
113
+ for include_file in config ["include" ]:
114
+ included_config = await get_file_method (bucket_name , include_file )
115
+ # Simply update/extend the main config with included config
116
+ for key , value in included_config .items ():
117
+ if isinstance (value , list ) and key in config :
118
+ config [key ].extend (value )
119
+ else :
120
+ config [key ] = value
121
+
122
+ # Remove the include directive
123
+ del config ["include" ]
124
+ return config
125
+
126
+
74
127
# # Example usage
75
128
# bucket_name = 'litellm-proxy'
76
129
# object_key = 'litellm_proxy_config.yaml'
0 commit comments