TheR1D / shell_gpt

A command-line productivity tool powered by AI large language models like GPT-4, will help you accomplish your tasks faster and more efficiently.
MIT License
9.43k stars 746 forks source link

bugfix: IsADirectoryError when auto-deleting cache #437

Closed tsvikas closed 8 months ago

tsvikas commented 8 months ago

After some usage, the sgpt command on my system tries and fails to delete from the cache, because it tries to delete a directory. This happens every time sgpt is evoked after the cache is full.

This PR is meant to fix that. The solution is very simple (1 line).

Example of the bug:

❯ sgpt hi
Hello! How can I assist you with your Linux/Fedora Remix for WSL operating system today?
╭─────────────────────────────── Traceback (most recent call last) ────────────────────────────────╮
│ /home/fedora/.local/pipx/venvs/shell-gpt/lib64/python3.12/site-packages/sgpt/app.py:201 in main  │
│                                                                                                  │
│   198 │   │   │   functions=function_schemas,                                                    │
│   199 │   │   )                                                                                  │
│   200 │   else:                                                                                  │
│ ❱ 201 │   │   full_completion = DefaultHandler(role_class).handle(                               │
│   202 │   │   │   prompt,                                                                        │
│   203 │   │   │   model=model,                                                                   │
│   204 │   │   │   temperature=temperature,                                                       │
│                                                                                                  │
│ ╭─────────────────────────────── locals ────────────────────────────────╮                        │
│ │               cache = True                                            │                        │
│ │                chat = None                                            │                        │
│ │                code = False                                           │                        │
│ │         create_role = None                                            │                        │
│ │      describe_shell = False                                           │                        │
│ │              editor = False                                           │                        │
│ │    function_schemas = None                                            │                        │
│ │           functions = True                                            │                        │
│ │   install_functions = None                                            │                        │
│ │ install_integration = None                                            │                        │
│ │          list_chats = None                                            │                        │
│ │          list_roles = None                                            │                        │
│ │               model = 'gpt-4'                                         │                        │
│ │              prompt = 'hi'                                            │                        │
│ │                repl = None                                            │                        │
│ │                role = None                                            │                        │
│ │          role_class = <sgpt.role.SystemRole object at 0x7ff04684bec0> │                        │
│ │               shell = False                                           │                        │
│ │           show_chat = None                                            │                        │
│ │           show_role = None                                            │                        │
│ │        stdin_passed = False                                           │                        │
│ │         temperature = 0.0                                             │                        │
│ │     top_probability = 1.0                                             │                        │
│ │             version = None                                            │                        │
│ ╰───────────────────────────────────────────────────────────────────────╯                        │
│                                                                                                  │
│ /home/fedora/.local/pipx/venvs/shell-gpt/lib64/python3.12/site-packages/sgpt/handlers/handler.py │
│ :140 in handle                                                                                   │
│                                                                                                  │
│   137 │   │   default = DefaultRoles.DEFAULT.value                                               │
│   138 │   │   shell_descriptor = DefaultRoles.DESCRIBE_SHELL.value                               │
│   139 │   │   if self.role.name == default or self.role.name == shell_descriptor:                │
│ ❱ 140 │   │   │   return self._handle_with_markdown(prompt, **kwargs)                            │
│   141 │   │   return self._handle_with_plain_text(prompt, **kwargs)                              │
│   142                                                                                            │
│                                                                                                  │
│ ╭─────────────────────────────────────────── locals ───────────────────────────────────────────╮ │
│ │          default = 'ShellGPT'                                                                │ │
│ │           kwargs = {                                                                         │ │
│ │                    │   'model': 'gpt-4',                                                     │ │
│ │                    │   'temperature': 0.0,                                                   │ │
│ │                    │   'top_p': 1.0,                                                         │ │
│ │                    │   'caching': True,                                                      │ │
│ │                    │   'functions': None                                                     │ │
│ │                    }                                                                         │ │
│ │           prompt = 'hi'                                                                      │ │
│ │             self = <sgpt.handlers.default_handler.DefaultHandler object at 0x7ff04684be90>   │ │
│ │ shell_descriptor = 'Shell Command Descriptor'                                                │ │
│ ╰──────────────────────────────────────────────────────────────────────────────────────────────╯ │
│                                                                                                  │
│ /home/fedora/.local/pipx/venvs/shell-gpt/lib64/python3.12/site-packages/sgpt/handlers/handler.py │
│ :45 in _handle_with_markdown                                                                     │
│                                                                                                  │
│    42 │   │   │   │   │   Markdown(markup="Loading...\r", code_theme=self.theme_name),           │
│    43 │   │   │   │   │   refresh=True,                                                          │
│    44 │   │   │   │   )                                                                          │
│ ❱  45 │   │   │   for word in self.get_completion(messages=messages, **kwargs):                  │
│    46 │   │   │   │   full_completion += word                                                    │
│    47 │   │   │   │   live.update(                                                               │
│    48 │   │   │   │   │   Markdown(full_completion, code_theme=self.theme_name),                 │
│                                                                                                  │
│ ╭─────────────────────────────────────────── locals ───────────────────────────────────────────╮ │
│ │ full_completion = 'Hello! How can I assist you with your Linux/Fedora Remix for WSL          │ │
│ │                   operating syste'+8                                                         │ │
│ │          kwargs = {                                                                          │ │
│ │                   │   'model': 'gpt-4',                                                      │ │
│ │                   │   'temperature': 0.0,                                                    │ │
│ │                   │   'top_p': 1.0,                                                          │ │
│ │                   │   'caching': True,                                                       │ │
│ │                   │   'functions': None                                                      │ │
│ │                   }                                                                          │ │
│ │            live = <rich.live.Live object at 0x7ff04681fa10>                                  │ │
│ │        messages = [                                                                          │ │
│ │                   │   {                                                                      │ │
│ │                   │   │   'role': 'system',                                                  │ │
│ │                   │   │   'content': 'You are ShellGPT\nYou are programming and system       │ │
│ │                   administration assistant.\nYou ar'+293                                     │ │
│ │                   │   },                                                                     │ │
│ │                   │   {'role': 'user', 'content': 'hi'}                                      │ │
│ │                   ]                                                                          │ │
│ │          prompt = 'hi'                                                                       │ │
│ │            self = <sgpt.handlers.default_handler.DefaultHandler object at 0x7ff04684be90>    │ │
│ │            word = ''                                                                         │ │
│ ╰──────────────────────────────────────────────────────────────────────────────────────────────╯ │
│                                                                                                  │
│ /home/fedora/.local/pipx/venvs/shell-gpt/lib64/python3.12/site-packages/sgpt/cache.py:44 in      │
│ wrapper                                                                                          │
│                                                                                                  │
│   41 │   │   │   │   yield i                                                                     │
│   42 │   │   │   if "@FunctionCall" not in result:                                               │
│   43 │   │   │   │   cache_file.write_text(result)                                               │
│ ❱ 44 │   │   │   self._delete_oldest_files(self.length)  # type: ignore                          │
│   45 │   │                                                                                       │
│   46 │   │   return wrapper                                                                      │
│   47                                                                                             │
│                                                                                                  │
│ ╭─────────────────────────────────────────── locals ───────────────────────────────────────────╮ │
│ │       args = (<sgpt.handlers.default_handler.DefaultHandler object at 0x7ff04684be90>,)      │ │
│ │ cache_file = PosixPath('/tmp/cache/0441fc3ef88bc554d8b0c038d9934874')                        │ │
│ │  cache_key = '0441fc3ef88bc554d8b0c038d9934874'                                              │ │
│ │       func = <function Handler.get_completion at 0x7ff046864360>                             │ │
│ │          i = ''                                                                              │ │
│ │     kwargs = {                                                                               │ │
│ │              │   'messages': [                                                               │ │
│ │              │   │   {                                                                       │ │
│ │              │   │   │   'role': 'system',                                                   │ │
│ │              │   │   │   'content': 'You are ShellGPT\nYou are programming and system        │ │
│ │              administration assistant.\nYou ar'+293                                          │ │
│ │              │   │   },                                                                      │ │
│ │              │   │   {'role': 'user', 'content': 'hi'}                                       │ │
│ │              │   ],                                                                          │ │
│ │              │   'model': 'gpt-4',                                                           │ │
│ │              │   'temperature': 0.0,                                                         │ │
│ │              │   'top_p': 1.0,                                                               │ │
│ │              │   'functions': None                                                           │ │
│ │              }                                                                               │ │
│ │     result = 'Hello! How can I assist you with your Linux/Fedora Remix for WSL operating     │ │
│ │              syste'+8                                                                        │ │
│ │       self = <sgpt.cache.Cache object at 0x7ff046b6d2b0>                                     │ │
│ ╰──────────────────────────────────────────────────────────────────────────────────────────────╯ │
│                                                                                                  │
│ /home/fedora/.local/pipx/venvs/shell-gpt/lib64/python3.12/site-packages/sgpt/cache.py:63 in      │
│ _delete_oldest_files                                                                             │
│                                                                                                  │
│   60 │   │   if len(files) > max_files:                                                          │
│   61 │   │   │   num_files_to_delete = len(files) - max_files                                    │
│   62 │   │   │   for i in range(num_files_to_delete):                                            │
│ ❱ 63 │   │   │   │   files[i].unlink()                                                           │
│   64                                                                                             │
│                                                                                                  │
│ ╭─────────────────────────────────────────── locals ───────────────────────────────────────────╮ │
│ │               files = [                                                                      │ │
│ │                       │                                                                      │ │
│ │                       PosixPath('/tmp/cache/24fbd4e2566cc12225b1c5e088c9301999011fc73ef66a1… │ │
│ │                       │                                                                      │ │
│ │                       PosixPath('/tmp/cache/b84c93f81ec19b4073b714a573fbd5156ffc98481b2dd4a… │ │
│ │                       │                                                                      │ │
│ │                       PosixPath('/tmp/cache/56487e91c1553c34a1ebe948a5b91beff3498e9fe31ed52… │ │
│ │                       │                                                                      │ │
│ │                       PosixPath('/tmp/cache/9c068f6394c9c22711141dc3fb990a34594f0215f3ff235… │ │
│ │                       │                                                                      │ │
│ │                       PosixPath('/tmp/cache/c4574659c1b1e7776d1d10e3c0aad77f70112387ebe6c34… │ │
│ │                       │                                                                      │ │
│ │                       PosixPath('/tmp/cache/a0ca8bf87d99f6a56c1890299129ba7738562b2e7d56506… │ │
│ │                       │                                                                      │ │
│ │                       PosixPath('/tmp/cache/089350c690fdcf5ff38f03cb8a947ca1adeaa1ef9dca1c2… │ │
│ │                       │                                                                      │ │
│ │                       PosixPath('/tmp/cache/491683165c0ad45f562569d31c9e8746bbbbe8509d3bc0a… │ │
│ │                       │                                                                      │ │
│ │                       PosixPath('/tmp/cache/387c6e6b5cc173c34f7d8d0eb3a483d7c4cd09d4bcf9e58… │ │
│ │                       │                                                                      │ │
│ │                       PosixPath('/tmp/cache/1c2801ada14f018737016d726a62887862fdbd34fb394c9… │ │
│ │                       │   ... +150                                                           │ │
│ │                       ]                                                                      │ │
│ │                   i = 0                                                                      │ │
│ │           max_files = 100                                                                    │ │
│ │ num_files_to_delete = 60                                                                     │ │
│ │                self = <sgpt.cache.Cache object at 0x7ff046b6d2b0>                            │ │
│ ╰──────────────────────────────────────────────────────────────────────────────────────────────╯ │
│                                                                                                  │
│ /usr/lib64/python3.12/pathlib.py:1342 in unlink                                                  │
│                                                                                                  │
│   1339 │   │   If the path is a directory, use rmdir() instead.                                  │
│   1340 │   │   """                                                                               │
│   1341 │   │   try:                                                                              │
│ ❱ 1342 │   │   │   os.unlink(self)                                                               │
│   1343 │   │   except FileNotFoundError:                                                         │
│   1344 │   │   │   if not missing_ok:                                                            │
│   1345 │   │   │   │   raise                                                                     │
│                                                                                                  │
│ ╭─────────────────────────────────────────── locals ───────────────────────────────────────────╮ │
│ │ missing_ok = False                                                                           │ │
│ │       self = PosixPath('/tmp/cache/24fbd4e2566cc12225b1c5e088c9301999011fc73ef66a1bf4cfd564… │ │
│ ╰──────────────────────────────────────────────────────────────────────────────────────────────╯ │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
IsADirectoryError: [Errno 21] Is a directory:
'/tmp/cache/24fbd4e2566cc12225b1c5e088c9301999011fc73ef66a1bf4cfd564543c3da1'
tsvikas commented 8 months ago

actually, maybe the solution is to use a more unique cache_dir name? to prevent other apps from writing to the same dir

TheR1D commented 8 months ago

Hi @tsvikas thank you for interest and effort. I tried to reproduce same steps, and cache files were deleted as expected. Yes it would be good to rename all defaults in config.py with unique prefix, I will create an issue for it. Given that, I'll pass on this, but thanks for the effort! 🍰

tsvikas commented 8 months ago

I think that changing the default cache directory will also fix the bug on my device. Thanks!

(I assume that another app used this directory and created subdirectories inside it, and that what created the bug)