How the Plugin Hot Reload Feature Works in gpt_academic: Dynamic Module Reloading Explained

The gpt_academic plugin hot reload feature uses a @HotReload decorator in toolbox.py that forces importlib.reload() on a plugin's source module whenever the global PLUGIN_HOT_RELOAD flag is enabled, allowing code changes to take effect immediately without server restart.

The gpt_academic project implements a sophisticated plugin hot reload feature that enables rapid development cycles by dynamically reloading Python modules at runtime. This mechanism eliminates the need to restart the server when modifying plugin code, making it ideal for iterative debugging and feature development. The system centers on a decorator-based architecture that intercepts plugin calls and conditionally reloads the underlying source files.

Plugin Architecture and Module Resolution

In gpt_academic, plugins are identified using a specific string format that combines the module path and function name separated by ->. For example, crazy_functions.Markdown_Translate->Markdown翻译指定语言 specifies the module crazy_functions.Markdown_Translate and the function Markdown翻译指定语言.

When the UI initiates a plugin call, shared_utils.connect_void_terminal.get_plugin_handle resolves this string into an executable function:


# shared_utils/connect_void_terminal.py (lines 16-27)

module, fn_name = plugin_name.split("->")
f_hot_reload = getattr(importlib.import_module(module, fn_name), fn_name)
return f_hot_reload

This dynamic import system provides the foundation for hot reloading by ensuring plugins are always accessed through their module references rather than static imports.

The HotReload Decorator Mechanism

The core reload logic resides in toolbox.HotReload, a decorator that wraps plugin entry points and conditionally refreshes the underlying module before execution.

Configuration Flag Control

Hot reloading is gated by the PLUGIN_HOT_RELOAD boolean flag defined in config.py:


# config.py (lines 42-44)

PLUGIN_HOT_RELOAD = False          # default – no hot-reload

The decorator accesses this setting through shared_utils.config_loader.get_conf(). When False, the decorator returns the original function unchanged, ensuring zero runtime overhead in production environments.

Runtime Module Reloading

When PLUGIN_HOT_RELOAD is True, the decorator intercepts every function call to perform a live reload:


# toolbox.py (lines 56-74)

def HotReload(f):
    if get_conf("PLUGIN_HOT_RELOAD"):
        @wraps(f)
        def decorated(*args, **kwargs):
            fn_name = f.__name__
            # Reload the module that originally defined the function

            f_hot_reload = getattr(
                importlib.reload(inspect.getmodule(f)), 
                fn_name
            )
            yield from f_hot_reload(*args, **kwargs)
        return decorated
    else:
        return f

Key implementation details:

  • inspect.getmodule(f) retrieves the module object where the function was originally defined
  • importlib.reload() forces Python to re-parse the source file from disk, picking up any edits
  • yield from delegates execution to the freshly loaded function, preserving generator behavior for streaming responses

Execution Flow and Edge Cases

Understanding the complete execution path reveals when hot reload activates and when it bypasses.

Normal Plugin Invocation Path

During standard operation, ArgsGeneralWrapper receives the UI request and routes it through the plugin resolution system. The function object returned by get_plugin_handle passes through the decorator stack. If @HotReload is present (applied automatically by the framework), the reload check occurs on every invocation.

Locked Plugin Bypass

When a user locks a specific plugin via the lock_plugin cookie, the system bypasses the decorator entirely:


# Direct loading bypasses HotReload (toolbox.py lines 46-49)

module, fn_name = cookies['lock_plugin'].split('->')
f_hot_reload = getattr(
    importlib.import_module(module, fn_name), 
    fn_name
)

Locked plugins never trigger hot reload, even when PLUGIN_HOT_RELOAD is enabled. This ensures consistent behavior for pinned workflows while allowing iterative development on unlocked plugins.

Prompt Hot Reload

A similar pattern exists for prompt configuration in core_functional.py and shared_utils.fastapi_stream_server, where importlib.reload(core_functional) is called to refresh prompt templates without restart. This demonstrates the architectural consistency of the reload pattern across the codebase.

Enabling and Testing Hot Reload

To activate the feature, set the configuration flag in config.py or export the environment variable PLUGIN_HOT_RELOAD=True:


# config.py

PLUGIN_HOT_RELOAD = True

Practical Example

Create a simple plugin in crazy_functions/hello_world.py:

from toolbox import HotReload

@HotReload
def hello_world(txt: str):
    return f"Hello, {txt}!"

Invoke it through the system:

from shared_utils.connect_void_terminal import get_plugin_handle

plugin = get_plugin_handle("crazy_functions.hello_world->hello_world")

# First call executes current code

print(plugin("Alice"))   # Output: Hello, Alice!

# Edit hello_world.py to return: f"Hi there, {txt}! (updated)"

# Second call automatically picks up changes

print(plugin("Bob"))     # Output: Hi there, Bob! (updated)

Without hot reload enabled, the second call would still produce "Hello, Bob" until the process restarts.

Summary

  • The plugin hot reload feature depends on the PLUGIN_HOT_RELOAD flag in config.py and the HotReload decorator in toolbox.py.
  • Module resolution uses the module->function string format parsed by get_plugin_handle in shared_utils/connect_void_terminal.py.
  • Runtime reloading employs importlib.reload(inspect.getmodule(f)) to refresh the source module before each call when enabled.
  • Locked plugins bypass the reload mechanism entirely, loading functions directly via importlib.import_module().
  • The decorator uses yield from to properly handle generator-based plugins that stream responses.

Frequently Asked Questions

What is the performance impact of enabling PLUGIN_HOT_RELOAD?

Enabling hot reload introduces minimal overhead on the first call to a plugin, as importlib.reload() re-parses the source file from disk and re-executes the module-level code. Subsequent calls within the same request lifecycle share the reloaded module, but the file system check and module reconstruction occur on every invocation. Disable the flag in production environments to eliminate this I/O overhead.

Why does the locked plugin feature bypass hot reload?

The locked plugin mechanism in toolbox.py loads functions directly using importlib.import_module() without wrapping them in the HotReload decorator. This design ensures that pinned plugins maintain deterministic behavior across requests, preventing unexpected changes if a developer edits the source file while the plugin is locked for a specific conversation thread.

Can hot reload work with imported dependencies within a plugin?

Hot reload refreshes only the immediate module where the decorated function is defined. If your plugin imports helper modules from other files, those dependencies will not automatically reload unless the parent module re-imports them after the reload. For comprehensive updates, you must reload the specific submodule manually or restart the server when changing shared utility code.

How does gpt_academic handle generator functions during hot reload?

The HotReload decorator in toolbox.py uses yield from f_hot_reload(*args, **kwargs) to delegate execution to the reloaded function. This syntax preserves the generator protocol, allowing plugins that yield streaming response chunks to function correctly after reload. The decorator maintains the original function's signature and generator status through @wraps(f).

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →