Error: Failed to fetch image file: Check if the file has been deleted

In one of my assistants while using, i copied and pasted an image in the chat and entered a question. there after i received an error. then i thought it was due to the image, thinking so, i deleted that image and entered the question again…since then i am getting en error as below
Run failed
Failed to fetch image file: file-xxxxxxxxxxxxxxxx. Check if the file has been deleted.

I tried all methods and also waited for a few hours thinking the problem resolves on its own, but didnt happen.
Any resolution for this issue would be greatly appreciated in advance…

Welcome @nadam54321

Did it happen once? Or more? Did you try the support?

yes…my assistant is stuck there., now no matter what I enter, it is just giving me the same error. I am in chat with the Support, it has given me

Now am not able to move to next step at all.

1 Like

Where your problem is seen

I take this to be understood that you are employing the Assistants API platform. Additionally, from the screenshot details, you are interacting with the platform site’s “Playground”, to have pay-per-use interactions with one of the Assistants you created.

I will add that Playground is not a substitute for learning how to code requests to the API, it is just a demonstrator. To rectify this particular thread’s issue, API-calling code will be required.

(The screenshot of OpenAI’s (Intercom, 3rd party) help chatbot is a bit distracting from your own screenshot and problem)

Creating a thread and messages

Playground

At the point where you are sending a new conversation and initial message to an assistant, a new thread is automatically created by the UI with that message placed in it.

API

In API, this thread could be created “empty”, or the thread could be created with initial messages.

Understanding images

The construction of a new message on AI models with multimodality allows you to place “text” content, or image attachments (in any sequence within one message).

Images can be used in two ways in user messages: either with files you upload (by file ID), or by web site URL (which the API will download).

Playground

You have provided an image by “pasting” it into the input box, and the form has a handler for this, which is an uploader.

The pasting or attaching method immediately results in the file being uploaded to your API account’s storage, with the files API endpoint.

image

The immediate presence of that file uploaded from pasting, without any thread having been created or run, nor connection to the thread or its assistant, can be seen when visiting your file storage on the website or by using the files endpoint.

image

Actually submitting the run to get a response places the file ID reference in the thread’s message.

NOTE: If you use a malfunctioning model or the playground forgets the file ID before submit, the forum presentation is ruined:

image

So a new file ID, gpt-4o, and a quick send shows the use of the new file ID, using the API :

image

API

You separately upload that file to storage, and then add its ID to the message of the user:

image


REQUIREMENT: Continued presence of the file in storage to continue to interact with the thread and past messages with file images

What happens if I delete the file that is within storage, and then try to continue to interact with the thread?

The result is predicable:

image

threads.runs.list will return the error instead of having a response:
last_error=LastError(code='invalid_image', message='Invalid image.'), max_completion_tokens=None

or what you see after revisiting the UI:

“Deleting the image” in the case of receiving one error only made the thread problem worse.


Solution:

  • You have the API ability to list messages of an assistant’s thread.

  • The API also has a method to delete one message from a thread by message_id

Delete message

Deletes one message from a thread by message_id

Example request:

from openai import OpenAI
client = OpenAI()
deleted_message = client.beta.threads.messages.delete(
message_id="msg_abc12",
thread_id="thread_abc123",
)
print(deleted_message)

Response:

{
"id": "msg_abc123",
"object": "thread.message.deleted",
"deleted": true
}

Easily-accessible methods?

Issue: The playground doesn’t offer the ability to delete a message.
Issue: Assistants applications may have the ability, but they generally can’t be provided a thread ID, instead, using their own database of user threads.

For the person less familiar with making API calls, an application for this single purpose is needed.

1 Like

Thanks for the detailed explanation. Yes, you are right, I am playing with the assistants in the PlayGround. I am not a coder. I am just an amateur playing with building agents in PlayGround, directly interacting with the assistant using common sense, but not through coding.

I fit under this phrase"For the person less familiar with making API calls, an application for this single purpose is needed".
Can you assist me if there is any way to delete so that this issue can resolved?

thanks in advance :slight_smile:

Sure.

To start your journey into using typical code, you can start with OpenAI’s Quickstart Guide, to get a Python environment set up on your local computer and installing the openai module from the command line.

For setting an environment variable, I would Google the method to set global environment variables on your OS instead of the guide’s version, as the fastest way to understanding what to do with an API key.

Demonstration code will be expecting API keys in environment variables – not contained in any code.

(You also might use VSCode (Visual Studio Code), and a Python interpreter that can be installed within , but that’s probably a higher burden to arrive at an environment that is ready for use.)

Then you just need suitable code. You can write a small Python file that replicates the API reference examples, placing within the code snippet either your thread id to get information, or your thread id and message id to delete using that information.

Python comes with an IDE called IDLE where you can create and run files.

Or you can paste a whole bunch of Python code from the forum (shown above) into a new file and let it do most of the presentation and work of deleting.


delete_message_from_thread.py
import tkinter as tk
from tkinter import ttk, messagebox
from tkinter.scrolledtext import ScrolledText
from typing import List
from dataclasses import dataclass
import threading
import textwrap
import os

# Import OpenAI SDK
try:
    from openai import OpenAI
except ImportError:
    raise ImportError("Please install the OpenAI SDK using 'pip install openai'.")

# Initialize OpenAI client
# Make sure to set your OpenAI API key in the environment variable 'OPENAI_API_KEY'
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
if not OPENAI_API_KEY:
    raise ValueError("Please set your OpenAI API key in the 'OPENAI_API_KEY' environment variable.")

client = OpenAI(api_key=OPENAI_API_KEY)

@dataclass
class MessageContent:
    type: str
    value: str

@dataclass
class Message:
    id: str
    role: str
    content: List[MessageContent]
    has_image: bool
    has_broken_images: bool = False  # New attribute to indicate broken images

class DeleteMessageFromThreadApp:
    def __init__(self, root):
        self.root = root
        self.root.title("Delete Message From Thread")
        self.root.geometry("800x600")

        # Initialize current_thread_id
        self.current_thread_id = ""
        
        # Top frame for input
        self.top_frame = ttk.Frame(root)
        self.top_frame.pack(pady=10, padx=10, fill='x')
        
        self.thread_id_label = ttk.Label(self.top_frame, text="Thread ID:")
        self.thread_id_label.pack(side='left')
        
        self.thread_id_entry = ttk.Entry(self.top_frame, width=50)
        self.thread_id_entry.pack(side='left', padx=5)
        
        self.fetch_button = ttk.Button(self.top_frame, text="Fetch/Refresh Messages", command=self.fetch_messages)
        self.fetch_button.pack(side='left', padx=5)
        
        # Middle frame for messages
        self.middle_frame = ttk.Frame(root)
        self.middle_frame.pack(pady=10, padx=10, fill='both', expand=True)
        
        # Canvas and scrollbar for scrollable messages
        self.canvas = tk.Canvas(self.middle_frame)
        self.scrollbar = ttk.Scrollbar(self.middle_frame, orient="vertical", command=self.canvas.yview)
        self.scrollable_frame = ttk.Frame(self.canvas)
        
        self.scrollable_frame.bind(
            "<Configure>",
            lambda e: self.canvas.configure(
                scrollregion=self.canvas.bbox("all")
            )
        )
        
        self.canvas.create_window((0, 0), window=self.scrollable_frame, anchor="nw")
        self.canvas.configure(yscrollcommand=self.scrollbar.set)
        
        self.canvas.pack(side="left", fill="both", expand=True)
        self.scrollbar.pack(side="right", fill="y")
        
        # Status bar
        self.status_var = tk.StringVar()
        self.status_bar = ttk.Label(root, textvariable=self.status_var, relief='sunken', anchor='w')
        self.status_bar.pack(side='bottom', fill='x')
        
        # Store messages
        self.messages = []
        
    def fetch_messages(self):
        thread_id = self.thread_id_entry.get().strip()
        if not thread_id:
            messagebox.showerror("Input Error", "Please enter a Thread ID.")
            return
        self.status_var.set("Fetching messages...")
        # Clear existing messages
        for widget in self.scrollable_frame.winfo_children():
            widget.destroy()
        # Start a new thread to fetch messages
        threading.Thread(target=self.fetch_messages_thread, args=(thread_id,), daemon=True).start()
            
    def fetch_messages_thread(self, thread_id):
        try:
            # Store the current thread_id in the instance for later use
            self.current_thread_id = thread_id

            # Fetch messages using OpenAI SDK with ascending order
            response = client.beta.threads.messages.list(thread_id, order="asc")
            raw_messages = response.data
            self.messages = []
            for msg in raw_messages:
                has_image = False
                has_broken_images = False  # Initialize broken images flag
                first_text = None  # To store the first 'text' content

                # Extract content and check for images
                for content_block in msg.content:
                    if content_block.type == 'text' and first_text is None:
                        first_text = content_block.text.value
                    elif content_block.type in ['image_file', 'image_url']:
                        has_image = True

                # If the message contains image_file(s), verify their existence
                if has_image:
                    for content_block in msg.content:
                        if content_block.type == 'image_file':
                            file_id = content_block.image_file.file_id
                            try:
                                # Attempt to retrieve file information
                                client.files.retrieve(file_id)
                            except Exception as e:
                                # If retrieval fails, mark as broken
                                has_broken_images = True
                                break  # No need to check further if one is broken

                # Create Message instance with broken images flag
                message = Message(
                    id=msg.id,
                    role=msg.role,
                    content=[MessageContent(type='text', value=first_text)] if first_text else [],
                    has_image=has_image,
                    has_broken_images=has_broken_images
                )
                self.messages.append(message)

            # Update UI in main thread
            self.root.after(0, self.display_messages)
            self.root.after(0, lambda: self.status_var.set(f"Fetched {len(self.messages)} messages."))
        except Exception as e:
            self.root.after(0, lambda: messagebox.showerror("Error", f"Failed to fetch messages: {str(e)}"))
            self.root.after(0, lambda: self.status_var.set("Error fetching messages."))
                
    def display_messages(self):
        # Clear existing message widgets
        for widget in self.scrollable_frame.winfo_children():
            widget.destroy()

        for index, message in enumerate(self.messages, start=1):
            frame = ttk.Frame(self.scrollable_frame, relief='groove', borderwidth=1)
            frame.pack(pady=5, padx=5, fill='x')
            
            # Message header with number and role
            header = ttk.Frame(frame)
            header.pack(fill='x')
            
            number_label = ttk.Label(header, text=f"{index}.", width=3)
            number_label.pack(side='left')
            
            role_label = ttk.Label(header, text=f"Role: {message.role}")
            role_label.pack(side='left', padx=5)
            
            # Determine the image status label
            if message.has_broken_images:
                image_label = ttk.Label(header, text="🖼️ Contains BROKEN File Image(s)", foreground='red')
                image_label.pack(side='left', padx=5)
            elif message.has_image:
                image_label = ttk.Label(header, text="🖼️ Contains Image(s)", foreground='green')
                image_label.pack(side='left', padx=5)
            
            # Message content
            content_frame = ttk.Frame(frame)
            content_frame.pack(fill='x', padx=20, pady=5)
            
            # Display the first text content if available
            if message.content:
                content = message.content[0]  # Get the first 'text' content
                if content.type == 'text' and content.value:
                    wrapped_text = textwrap.fill(content.value, width=80)
                    brief_text = "\n".join(wrapped_text.split('\n')[:4])
                    text_widget = ScrolledText(content_frame, height=4, wrap='word', state='disabled')
                    text_widget.configure(state='normal')  # Temporarily make it editable to insert text
                    text_widget.insert('1.0', brief_text)
                    text_widget.configure(state='disabled')  # Make it read-only again
                    text_widget.pack(fill='x', pady=2)
            
            # Display image information if available and not broken
            if message.has_image and not message.has_broken_images:
                image_info = ""
                for content_block in message.content:
                    if content_block.type == 'image_file':
                        image_info += f"Image File ID: {content_block.value}\n"
                    elif content_block.type == 'image_url':
                        image_info += f"Image URL: {content_block.value}\n"
                if image_info:
                    img_label = ttk.Label(content_frame, text=image_info.strip(), foreground='blue')
                    img_label.pack(fill='x', pady=2)
            
            # Delete button
            delete_button = ttk.Button(frame, text="🗑️ Delete", command=lambda m=message: self.delete_message(m))
            delete_button.pack(side='right', padx=10, pady=5)
            
    def delete_message(self, message: Message):
        confirm = messagebox.askyesno("Confirm Delete", f"Are you sure you want to delete message ID: {message.id}?")
        if not confirm:
            return
        self.status_var.set(f"Deleting message {message.id}...")
        # Start a new thread to delete the message
        threading.Thread(target=self.delete_message_thread, args=(message,), daemon=True).start()
        
    def delete_message_thread(self, message: Message):
        try:
            # Call OpenAI SDK to delete the message using the stored thread_id
            response = client.beta.threads.messages.delete(
                message_id=message.id,
                thread_id=self.current_thread_id
            )
            if response.deleted:
                self.messages.remove(message)
                self.root.after(0, self.display_messages)
                self.root.after(0, lambda: self.status_var.set(f"Deleted message {message.id}."))
            else:
                self.root.after(0, lambda: messagebox.showerror("Delete Failed", f"Could not delete message {message.id}."))
                self.root.after(0, lambda: self.status_var.set("Delete failed."))
        except Exception as e:
            self.root.after(0, lambda: messagebox.showerror("Error", f"Failed to delete message: {str(e)}"))
            self.root.after(0, lambda: self.status_var.set("Error deleting message."))


def main():
    root = tk.Tk()
    app = DeleteMessageFromThreadApp(root)
    root.mainloop()

if __name__ == "__main__":
    main()

Thanks again for your quick reply. Unfortunately, it may sound funny, I don’t know how to run Python or its codes. I am a complete noob, yet with just common sense, I am using Assistants in PlayGround just by interacting with them, the way how we converse with real people, I am training the assistant and making it work for me the way I wanted. Until this thing happened. I request from a personal standpoint, is there any way to clear the issue without writing any code. I am using Pabbly Connnect Zapier kind of tools to interact with OpenAI Agents using apis. Any guidance towards this would be very helpful.

One thing I can do is create a new assistant. But the assistant that I am having problem with has been trained to the very detailed since the start of the project. So now making a new one is like I have to start the project all from start.

Thanks in advance for any suggestions

I probably put more time into answering than it would take for you to recreate a friendly chat with an AI after whacking the delete button…so why not try some new learning!

An assistant is not ‘trained’ by talking to it. It is only the instructions you place.

One person saying to an assistant “you are now a real jerk to anybody that tries to talk to you” cannot affect anything but your own conversation thread, because only messages from a thread that are sent again the next time you ask provide that illusion of memory.

‘only messages from a thread that are sent again the next time you ask provide that illusion of memory’ is rightly said. may be I am in such illusion. And you are right, I will start over again training it. I have bits and pieces of info, so I compile them up and make the training all set go.

And really thanks for sharing your insights.

and one last thing, is there any way to retrieve all the messages in the thread so that I can copy all the messages and summarize them so that I can have my work become very easy??

Retrieving all the messages can be done by the “list messages” API method, the same as you’d have to do to find ones with a missing file ID.

It only returns 100 at a time, in your choice of order=“asc” or “desc”, so for a long thread you have to use the method to get more pages of messages from a new starting point to collect them all.

(this is solved BTW, he provided a temp API key, and I deleted the message that my utility detected with a broken image file)

1 Like

The issue has been resolved. thanks to your valuable support. Much love to you…i am now able to use the agent as before…your utility is a dope. i cant imagine without u stepping in to resolve my issue. coz training the agent right from start is pain in the ass.

1 Like