5
5
from fastapi import (
6
6
APIRouter , Request , UploadFile , File , HTTPException , Depends , Path , Form
7
7
)
8
- from fastapi .responses import HTMLResponse , StreamingResponse
8
+ from fastapi .responses import HTMLResponse , StreamingResponse , FileResponse
9
9
from fastapi .templating import Jinja2Templates
10
10
from openai import AsyncOpenAI
11
- from utils .files import get_or_create_vector_store , get_files_for_vector_store
11
+ from utils .files import get_or_create_vector_store , get_files_for_vector_store , store_file , retrieve_file , delete_local_file
12
12
from utils .streaming import stream_file_content
13
13
14
14
logger = logging .getLogger ("uvicorn.error" )
@@ -69,18 +69,38 @@ async def upload_file(
69
69
)
70
70
71
71
error_message = None
72
+ file_content = None
72
73
try :
73
- # Upload the file to OpenAI
74
+ # 1. Read the file content first
75
+ file_content = await file .read ()
76
+ if not file .filename :
77
+ raise ValueError ("File has no filename" )
78
+ if not file_content :
79
+ raise ValueError ("File content is empty" )
80
+
81
+ # 2. Upload the file content to OpenAI
74
82
openai_file = await client .files .create (
75
- file = (file .filename , file . file ),
83
+ file = (file .filename , file_content ),
76
84
purpose = purpose
77
85
)
78
86
79
- # Add the uploaded file to the vector store
87
+ # 3. Add the uploaded file to the vector store
80
88
await client .vector_stores .files .create (
81
89
vector_store_id = vector_store_id ,
82
90
file_id = openai_file .id
83
91
)
92
+ logger .info (f"File { file .filename } uploaded to OpenAI and added to vector store." )
93
+
94
+ # 4. Store the file locally using the same content
95
+ try :
96
+ store_file (file .filename , file_content )
97
+ except Exception as e :
98
+ logger .error (f"Error storing file { file .filename } locally: { e } " )
99
+ error_message = f"Error storing file locally"
100
+
101
+ except ValueError as ve :
102
+ logger .error (f"File validation error for assistant { assistant_id } : { ve } " )
103
+ error_message = str (ve )
84
104
except Exception as e :
85
105
logger .error (f"Error uploading file for assistant { assistant_id } : { e } " )
86
106
error_message = f"Error uploading file for assistant"
@@ -121,6 +141,26 @@ async def delete_file(
121
141
try :
122
142
vector_store_id = await get_or_create_vector_store (assistant_id , client )
123
143
144
+ # Retrieve filename before attempting deletions
145
+ file_to_delete_name = None
146
+ try :
147
+ retrieved_file = await client .files .retrieve (file_id )
148
+ if retrieved_file and retrieved_file .filename :
149
+ file_to_delete_name = retrieved_file .filename
150
+ logger .info (f"Retrieved filename '{ retrieved_file .filename } ' for deletion." )
151
+ else :
152
+ logger .warning (f"Could not retrieve filename for file_id { file_id } " )
153
+ except Exception as retrieve_err :
154
+ logger .error (f"Error retrieving file object { file_id } for filename: { retrieve_err } " )
155
+
156
+ # Attempt to delete stored file if filename was found
157
+ if file_to_delete_name :
158
+ try :
159
+ delete_local_file (file_to_delete_name )
160
+ except Exception as local_delete_err :
161
+ # Log error but continue with OpenAI/VS deletion
162
+ logger .error (f"Error deleting local file { file_to_delete_name } : { local_delete_err } " )
163
+
124
164
# 1. Delete the file association from the vector store
125
165
try :
126
166
deleted_vs_file = await client .vector_stores .files .delete (
@@ -175,9 +215,20 @@ async def delete_file(
175
215
176
216
# --- Streaming file content routes ---
177
217
178
- @router .get ("/{file_id }" )
218
+ @router .get ("/{file_name }" )
179
219
async def download_assistant_file (
180
- file_id : str = Path (..., description = "The ID of the file to retrieve" ),
220
+ assistant_id : str = Path (..., description = "The ID of the Assistant" ),
221
+ file_name : str = Path (..., description = "The name of the file to retrieve" )
222
+ ) -> FileResponse :
223
+ """Serves an assistant file stored locally in the uploads directory."""
224
+ return retrieve_file (file_name )
225
+
226
+
227
+ # This endpoint retrieves files uploaded TO openai (e.g., code interpreter output)
228
+ # Keep it separate for clarity
229
+ @router .get ("/{file_id}/openai_content" )
230
+ async def download_openai_file (
231
+ file_id : str = Path (..., description = "The ID of the file stored in OpenAI" ),
181
232
client : AsyncOpenAI = Depends (lambda : AsyncOpenAI ())
182
233
) -> StreamingResponse :
183
234
try :
@@ -187,12 +238,14 @@ async def download_assistant_file(
187
238
if not hasattr (file_content , 'content' ):
188
239
raise HTTPException (status_code = 500 , detail = "File content not available" )
189
240
241
+ # Use stream_file_content helper
190
242
return StreamingResponse (
191
- stream_file_content (file_content .content ),
192
- headers = {"Content-Disposition" : f'attachment; filename=\ "{ file .filename or "download" } \ " ' }
243
+ stream_file_content (file_content .content ), # Assuming stream_file_content handles bytes
244
+ headers = {"Content-Disposition" : f'attachment; filename="{ file .filename or file_id } "' } # Use file_id as fallback filename
193
245
)
194
246
except Exception as e :
195
- raise HTTPException (status_code = 500 , detail = str (e ))
247
+ logger .error (f"Error downloading file { file_id } from OpenAI: { e } " )
248
+ raise HTTPException (status_code = 500 , detail = f"Error downloading file from OpenAI: { str (e )} " )
196
249
197
250
198
251
@router .get ("/{file_id}/content" )
0 commit comments