Extending PyGPT
Overview
PyGPT exposes extension points through pygpt_net.app.run(). A custom launcher can register additional:
plugins,
GUI tools,
LLM wrappers,
vector store providers,
data loaders,
audio input providers,
audio output providers,
web search providers,
agent providers.
The repository’s examples directory contains tutorial implementations for every extension type:
examples/custom_launcher.pyexamples/example_plugin.pyexamples/example_tool.pyexamples/example_agent.pyexamples/example_llm.pyexamples/example_vector_store.pyexamples/example_data_loader.pyexamples/example_audio_input.pyexamples/example_audio_output.pyexamples/example_web_search.py
The examples are intentionally small and are the recommended starting point for custom integrations.
Registration in a custom launcher
pygpt_net.app.run() first registers PyGPT’s built-in components and then appends custom instances
provided through keyword arguments.
A minimal launcher can register only one extension type:
from pygpt_net.app import run
from example_plugin import Plugin
run(
plugins=[
Plugin(),
],
)
A larger launcher can register all currently supported extension families:
from pygpt_net.app import run
from example_agent import ExampleAgent
from example_audio_input import ExampleAudioInput
from example_audio_output import ExampleAudioOutput
from example_data_loader import ExampleDataLoader
from example_llm import ExampleLlm
from example_plugin import Plugin
from example_tool import ExampleTool
from example_vector_store import ExampleVectorStore
from example_web_search import ExampleWebSearchEngine
def main():
run(
plugins=[Plugin()],
tools=[ExampleTool()],
llms=[ExampleLlm()],
vector_stores=[ExampleVectorStore()],
loaders=[ExampleDataLoader()],
audio_input=[ExampleAudioInput()],
audio_output=[ExampleAudioOutput()],
web=[ExampleWebSearchEngine()],
agents=[ExampleAgent()],
)
if __name__ == "__main__":
main()
run() owns the application lifecycle and should normally be called only once.
Every extension ID should be unique. Reusing a built-in ID may replace or shadow a built-in entry in the corresponding registry.
During application initialization PyGPT attaches the main window or owning plugin to registered
objects. Do not assume those references are already available inside your extension’s __init__()
unless the relevant base class explicitly provides them.
Plugins and GUI Tools are different
A plugin participates in application/model events and can expose commands callable by models.
A GUI Tool is an application component registered in the Tools subsystem. It can provide a Tools-menu entry, dialog, tabs, lifecycle hooks and event handling, but registering a GUI Tool does not automatically make it a callable model function.
Use a plugin when the model should call functionality as a tool/command. Use a GUI Tool when you want to extend the desktop application’s interface or provide a standalone utility.
Adding a custom model
Models can be created in Config -> Models -> Edit or added to %workdir%/models.json.
For a local OpenAI-compatible server a minimal model entry can look like this:
{
"id": "my-local-model",
"name": "My local model",
"mode": ["chat", "llama_index"],
"provider": "local_ai",
"input": ["text"],
"output": ["text"],
"ctx": 32768,
"tokens": 4096,
"tool_calls": true,
"custom_api_endpoint": "http://127.0.0.1:1234/v1",
"custom_api_key": "local",
"llama_index": {
"args": [
{
"name": "model",
"value": "my-local-model",
"type": "str"
}
],
"env": []
}
}
custom_api_endpoint and custom_api_key correspond to API base and API key in the
Models Editor’s Advanced section. When non-empty, the OpenAI-compatible Chat client uses these values for
that model. The local_ai LlamaIndex wrapper also uses them for its OpenAILike client.
Leave the custom API fields empty if the model should continue using the provider/global API settings.
For built-in providers, provider API keys configured in Config -> Settings -> API Keys are normally
used automatically if a LlamaIndex argument does not explicitly override them.
Adding a custom plugin
Custom plugins normally inherit from pygpt_net.plugin.base.plugin.BasePlugin. A plugin can:
define settings with
add_option(),expose model-callable commands with
add_cmd(),respond to application events in
handle(),return command results with
reply().
Minimal plugin example
The example below adds one option, exposes one model-callable command, modifies the final system prompt, and handles command execution:
from pygpt_net.core.events import Event
from pygpt_net.plugin.base.plugin import BasePlugin
class Plugin(BasePlugin):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.id = "example_plugin"
self.name = "Example Plugin"
self.description = "Small tutorial plugin."
self.prefix = "Example"
self.allowed_cmds = ["funny_cmd"]
self.add_option(
"append_footer",
type="bool",
value=False,
label="Append footer",
description="Append an instruction to the final system prompt.",
)
self.add_cmd(
"funny_cmd",
instruction="return an example response for the provided query",
params=[
{
"name": "query",
"type": "str",
"description": "Topic to include in the response.",
"required": True,
},
],
enabled=True,
description="Enable the example command.",
)
def handle(self, event: Event, *args, **kwargs):
if event.name == Event.POST_PROMPT_END:
if self.get_option_value("append_footer"):
event.data["value"] += "\n\nKeep the final answer concise."
elif event.name in (Event.CMD_SYNTAX, Event.CMD_SYNTAX_INLINE):
for cmd_name in self.allowed_cmds:
if self.has_cmd(cmd_name):
event.data["cmd"].append(self.get_cmd(cmd_name))
elif event.name in (Event.CMD_EXECUTE, Event.CMD_INLINE):
self.execute_commands(event)
def execute_commands(self, event: Event):
for item in event.data.get("commands", []):
if item.get("cmd") != "funny_cmd":
continue
params = item.get("params") or {}
query = str(params.get("query", "")).strip()
self.reply(
{
"request": {
"cmd": "funny_cmd",
"params": params,
},
"result": f"Example response for: {query}",
},
event.ctx,
)
return
add_cmd() is preferred over manually constructing the command schema. PyGPT can translate registered
commands into the appropriate native function/tool format when native API function calls are enabled.
The complete tutorial implementation is available in examples/example_plugin.py.
Handling events
Plugins can inspect and, for mutable events, modify data while a request moves through the application.
A basic handler:
from pygpt_net.core.events import Event
def handle(self, event: Event, *args, **kwargs):
if event.name == Event.USER_SEND:
# USER_SEND is mutable.
event.data["value"] = event.data["value"].strip()
elif event.name == Event.POST_PROMPT_END:
# Final chance to update the system prompt before the API request.
event.data["value"] += "\n\nAdditional instruction."
elif event.name == Event.MODELS_CHANGED:
# Refresh provider/plugin state after the models list changes.
self.refresh_models()
Most generic events expose three useful attributes:
event.name- event identifier.event.data- dictionary with event-specific data.event.ctx- currentCtxItemwhen the event is associated with a conversation item.
event.data is intentionally mutable for many pre-processing hooks. If the caller reads the changed
value after dispatch, modifying it changes the subsequent application flow.
Stopping propagation
A listener can stop the event from being passed to later listeners:
if event.name == Event.INPUT_BEFORE:
if should_handle_input_myself(event.data["value"]):
event.stop = True
event.stop stops further event propagation. It does not automatically cancel the operation that
created the event unless the relevant caller also checks a cancellation field.
For INPUT_BEFORE, the supported way to abort sending is to set data["stop"]:
if event.name == Event.INPUT_BEFORE:
if event.data["value"].startswith("/local-only"):
event.data["stop"] = True
event.data["silent"] = True
Event reference
Generic event constants are defined by pygpt_net.core.events.Event.
Some constants are retained as compatibility/reserved hooks even if the current core flow primarily uses a newer related event. This is noted below.
AI_NAME- emitted while the assistant name is being prepared.data["value"]contains the current assistant name and can be replaced.AGENT_PROMPT- emitted immediately before an input prompt is passed to an agent runner.data["value"]contains the prompt and can be modified.AUDIO_INPUT_RECORD_START- compatibility/reserved hook for an explicit microphone recording-start notification. The current UI primarily usesAUDIO_INPUT_RECORD_TOGGLEfor recording control.AUDIO_INPUT_RECORD_STOP- compatibility/reserved hook for an explicit microphone recording-stop notification. The current UI primarily usesAUDIO_INPUT_RECORD_TOGGLE.AUDIO_INPUT_RECORD_TOGGLE- requests that microphone recording be toggled. Internal callers may additionally providedata["state"]to request a specific state anddata["auto"]to mark an automatic/internal transition.AUDIO_INPUT_STOP- requests an immediate stop of audio-input processing. Some callers providedata["value"] = True.AUDIO_INPUT_TOGGLE- enables or disables speech/audio input.data["value"]is the requested boolean state.AUDIO_INPUT_TRANSCRIBE- asks the active Audio input provider to transcribe an existing audio file.data["path"]contains the file path.event.ctxmay be a temporary context when transcription is initiated from the Transcript tool.AUDIO_OUTPUT_STOP- requests that active TTS generation/playback be stopped.event.ctxmay identify the context whose playback is being stopped.AUDIO_OUTPUT_TOGGLE- compatibility/reserved generic hook for changing audio-output state. Current UI code normally enables/disables the Audio output plugin through the plugin controller.AUDIO_PLAYBACK- requests playback of an already generated audio file.data["audio_file"]contains the path andevent.ctxidentifies the related context when available.AUDIO_READ_TEXT- asks the Audio output provider to synthesize/read text.data["text"]contains the text anddata["cache_file"]may contain a preferred cache path.event.ctxidentifies the related context.BRIDGE_BEFORE- emitted before a prepared conversation request is handed to the bridge/provider layer. Typical fields aredata["mode"],data["context"]anddata["extra"].CMD_EXECUTE- dispatches normal plugin commands/tools for execution.data["commands"]is a list of parsed command dictionaries.data["silent"]may be present for internal calls.event.ctxidentifies the tool-call context.CMD_INLINE- inline-command equivalent ofCMD_EXECUTE. It is used by plugins that intentionally expose inline commands independently of the normalToolsswitch.CMD_SYNTAX- asks enabled plugins to publish normal command/tool definitions. Plugins append command definitions todata["cmd"]. Depending on the call site,datacan also containprompt,syntax,modeandis_expert.CMD_SYNTAX_INLINE- inline-plugin equivalent ofCMD_SYNTAX. Plugins append their definitions todata["cmd"].CTX_AFTER- emitted after model output has been attached to the current context item and before the response lifecycle fully finishes.event.ctxis the context item;data["mode"]is normally available for text responses.CTX_BEFORE- emitted after a context item has been prepared but before it is committed/sent through the normal processing path.event.ctxcontains that item anddata["mode"]is normally present.CTX_BEGIN- compatibility hook for the beginning of context processing. Some plugins still implement it, while the current input pipeline relies mainly onINPUT_BEGIN,CTX_BEFORE,CTX_AFTERandCTX_END.CTX_END- emitted when processing of a context item is complete.event.ctxcontains the finished item anddata["mode"]is normally present.CTX_SELECT- emitted when a conversation context is selected.data["value"]contains the context metadata ID.DISABLE- emitted when a plugin is disabled.data["value"]contains the plugin ID. Dispatch may be sent to all plugins so a plugin can react to another plugin being disabled.ENABLE- emitted when a plugin is enabled.data["value"]contains the plugin ID.FORCE_STOP- global request to stop active plugin/background operations, for example after the user presses Stop.data["value"]may beTrue; listeners should treat the event itself as the stop signal.INPUT_ACCEPT- emitted afterINPUT_BEFOREhas accepted the message and immediately before normal send processing continues. Typical fields aredata["value"],data["mode"]anddata["multimodal_ctx"].INPUT_BEFORE- main mutable input pre-processing hook. It is emitted after raw input reaches the chat send pipeline but before the request is sent. Fields includedata["mode"],data["value"],data["multimodal_ctx"],data["stop"]anddata["silent"]. Modifyvalueto rewrite input; setstoptoTrueto abort the send.INPUT_BEGIN- earliest generic hook in a manual send operation, before the textarea content is fully processed.data["mode"]anddata["force"]describe the request; settingdata["stop"]can stop the normal manual-send path.MODE_BEFORE- emitted before an inline/temporary mode choice is finalized for a request.data["value"]contains the mode,data["prompt"]contains the prompt, andevent.ctxis the current context. Replacingdata["value"]can redirect the inline mode.MODE_SELECT- emitted when the application’s active mode is selected.data["value"]contains the selected mode ID.MODEL_BEFORE- emitted before the model instance used by an inline request is finalized.data["mode"]contains the mode anddata["model"]contains the proposedModelItem. Replacingdata["model"]can override that choice.MODEL_SELECT- emitted when the active model changes.data["value"]contains the selected model ID.MODELS_CHANGED- emitted after the model registry is changed, for example by the Models Editor or importer. It has no required payload; listeners normally refresh cached model lists.PLUGIN_OPTION_GET- query hook for plugin-owned dynamic values.data["name"]identifies the requested value and the handling plugin writes the answer todata["value"].PLUGIN_SETTINGS_CHANGED- emitted after plugin settings are saved. There is no required payload; plugins use it to reload runtime configuration.POST_PROMPT- emitted after the main system prompt has been assembled and personalization has been applied, before command/tool syntax is appended. Typical fields aremode,reply,internal,valueandis_expert.event.ctxis set.data["value"]is mutable.POST_PROMPT_ASYNC- emitted in the bridge worker after processing has moved to the asynchronous request path. It is a late mutable system-prompt hook. Typical fields aremode,replyandvalue;event.ctxis set.POST_PROMPT_END- final mutable system-prompt hook immediately before the provider request is executed. Typical fields aremode,replyandvalue;event.ctxis set. This is usually the safest event for a plugin that must append a final instruction.PRE_PROMPT- early hook for the system prompt before the main prompt builder finishes processing it.data["mode"]anddata["value"]are provided; expert calls may additionally providedata["is_expert"].SETTINGS_CHANGED- emitted after the main Settings editor saves changes. It has no required payload. Plugins/providers can use it to refresh configuration-dependent state.SYSTEM_PROMPT- emitted while the base/final system prompt is being assembled.data["value"]is the mutable prompt text anddata["mode"]identifies the current mode. Depending on the call site,silentoris_expertmay also be present.TOOL_OUTPUT_RENDER- WebView rendering hook for custom tool output. Typical fields aredata["tool"](plugin/tool ID),data["content"](tool output data),data["html"](custom HTML result) anddata["multiple"](whether the renderer is handling one of multiple tool outputs). A plugin can populatehtmlto provide custom rendering.UI_ATTACHMENTS- asks listeners whether attachment UI should be visible for a mode.data["mode"]identifies the mode and mutabledata["value"]is the visibility boolean.UI_VISION- asks listeners whether the inline Vision availability indicator should be visible.data["mode"]identifies the mode and mutabledata["value"]is the visibility boolean.USER_NAME- emitted while the user display/name value is prepared.data["value"]contains the current name and can be replaced.USER_SEND- emitted for a manual user send after the text has been read from the input widget but before attachment handling and the kernel/bridge input event.data["mode"]contains the current mode and mutabledata["value"]contains the text.
Event ordering example
For a typical manual text message, a simplified sequence is approximately:
INPUT_BEGIN
|
v
USER_SEND
|
v
INPUT_BEFORE
|
v
INPUT_ACCEPT
|
v
CTX_BEFORE
|
v
PRE_PROMPT
|
v
SYSTEM_PROMPT
|
v
POST_PROMPT
|
v
CMD_SYNTAX / CMD_SYNTAX_INLINE
|
v
POST_PROMPT_ASYNC
|
v
POST_PROMPT_END
|
v
provider / model request
|
v
CTX_AFTER
|
v
CTX_END
Exact ordering can differ for image generation, Realtime, agents, internal calls and tool continuations.
Event debug logging
Event flow can be inspected by enabling:
Config -> Settings -> Debug -> Log events
Adding a custom GUI Tool
A custom application tool uses the Tools subsystem and is registered through tools=[...].
Minimal example:
from PySide6.QtGui import QAction
from pygpt_net.tools.base import BaseTool
class ExampleTool(BaseTool):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.id = "example_tool"
self.opened = False
def setup(self):
# self.window is available here.
pass
def setup_menu(self) -> dict:
action = QAction("Example Tool", self.window)
action.triggered.connect(self.toggle)
return {"example": action}
def toggle(self):
self.opened = not self.opened
self.window.ui.dialogs.alert(
f"Example tool opened: {self.opened}"
)
def on_reload(self):
# Called when profile/workdir is reloaded.
pass
def on_exit(self):
# Release external resources here.
pass
A real GUI Tool can register dialogs, tabs, menus, theme hooks and update hooks. See
examples/example_tool.py for a complete dialog-based tutorial.
Adding a custom LLM wrapper
LLM wrappers are used by the LlamaIndex integration and embeddings. They are not the same thing as the normal native Chat-provider path.
A wrapper derives from pygpt_net.provider.llms.base.BaseLLM and declares a unique id and supported
types.
A compact OpenAI-backed example:
from llama_index.llms.openai import OpenAI as LlamaOpenAI
from pygpt_net.core.types import MODE_LLAMA_INDEX
from pygpt_net.provider.llms.base import BaseLLM
class ExampleLlm(BaseLLM):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.id = "example_llm"
self.name = "Example LlamaIndex provider"
self.type = [MODE_LLAMA_INDEX, "embeddings"]
def llama(self, window, model, stream=False):
args = self.parse_args(model.llama_index, window)
args.setdefault("model", model.id)
# Global fallback used by this OpenAI-based tutorial.
args.setdefault(
"api_key",
window.core.config.get("api_key", ""),
)
# Per-model values have highest priority when configured.
if model.custom_api_key:
args["api_key"] = model.custom_api_key
if model.custom_api_endpoint:
args["api_base"] = model.custom_api_endpoint
args = self.inject_llamaindex_http_clients(
args,
window.core.config,
)
return LlamaOpenAI(**args)
For embeddings, implement get_embeddings_model() and return a LlamaIndex BaseEmbedding instance.
Depending on its purpose, a wrapper can implement:
llama()- return a LlamaIndex LLM for Chat with Files / LlamaIndex agents.get_embeddings_model()- return a LlamaIndex embedding model.get_openai_agent_provider()- optionally provide a model adapter for OpenAI Agents.get_models()- optionally expose provider-side model discovery.
Legacy chat() and completion() methods remain in the base interface for compatibility, but current
custom LlamaIndex integrations should normally follow the llama() path.
See examples/example_llm.py for a complete LLM + embeddings example.
Adding a custom vector store
Vector store providers derive from pygpt_net.provider.vector_stores.base.BaseStore and are registered
through vector_stores=[...].
The current index creation path passes the LLM and embedding model separately. Older
ServiceContext-based examples are no longer appropriate.
Example:
from llama_index.core import StorageContext, load_index_from_storage
from pygpt_net.provider.vector_stores.base import BaseStore
class ExampleVectorStore(BaseStore):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.id = "ExampleVectorStore"
self.prefix = "example_"
def create(self, id, embed_model=None):
if self.exists(id):
return
index = self.index_from_empty(embed_model)
self.store(id=id, index=index)
def get(self, id, llm=None, embed_model=None):
if not self.exists(id):
self.create(id, embed_model)
storage_context = StorageContext.from_defaults(
persist_dir=self.get_path(id),
)
index = load_index_from_storage(
storage_context,
llm=llm,
embed_model=embed_model,
)
self.indexes[id] = index
return index
def store(self, id, index=None):
index = index or self.indexes[id]
index.storage_context.persist(
persist_dir=self.get_path(id),
)
self.indexes[id] = index
BaseStore already provides common helpers such as exists(), remove(), truncate(),
remove_document(), attach() and get_path().
See examples/example_vector_store.py for the complete tutorial.
Adding a custom data loader
Data loaders are registered through loaders=[...] and normally derive from
pygpt_net.provider.loaders.base.BaseLoader.
The loader registers metadata/configuration and returns a LlamaIndex reader:
from llama_index.core.readers.base import BaseReader
from llama_index.core.schema import Document
from pygpt_net.provider.loaders.base import BaseLoader
class ExampleDataLoader(BaseLoader):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.id = "example_text"
self.name = "Example text loader"
self.extensions = ["exampletxt"]
self.type = ["file"]
self.init_args = {
"encoding": "utf-8",
}
self.init_args_types = {
"encoding": "str",
}
def get(self) -> BaseReader:
return ExampleReader(**self.get_args())
class ExampleReader(BaseReader):
def __init__(self, encoding="utf-8", **kwargs):
super().__init__(**kwargs)
self.encoding = encoding
def load_data(self, file, extra_info=None):
with open(file, "r", encoding=self.encoding) as handle:
text = handle.read()
return [
Document(
text=text,
metadata=extra_info or {},
)
]
Current LlamaIndex Document objects use the metadata field for metadata.
See examples/example_data_loader.py for a configurable CSV-like example.
Adding audio providers
Custom speech-to-text providers are registered with audio_input=[...]. Custom text-to-speech
providers are registered with audio_output=[...].
Audio input
A speech-to-text provider receives the path that PyGPT wants transcribed. Do not hard-code
input.wav; application-managed recordings can live under %workdir%/tmp.
from pygpt_net.provider.audio_input.base import BaseProvider
class ExampleAudioInput(BaseProvider):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.id = "example_audio_input"
self.name = "Example audio input"
def transcribe(self, path: str) -> str:
client = self.plugin.window.core.api.openai.get_client()
with open(path, "rb") as audio_file:
return client.audio.transcriptions.create(
model="whisper-1",
file=audio_file,
response_format="text",
)
def is_configured(self) -> bool:
return bool(
self.plugin.window.core.config.get("api_key")
)
See examples/example_audio_input.py for provider-specific settings and configuration messages.
Audio output
Use prepare_output_path() rather than a fixed filename. PyGPT stores generated TTS working files under
%workdir%/tmp/audio_output and uses unique names, which also avoids file-lock issues on Windows.
from pygpt_net.provider.audio_output.base import BaseProvider
class ExampleAudioOutput(BaseProvider):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.id = "example_audio_output"
self.name = "Example audio output"
def speech(self, text: str) -> str:
client = self.plugin.window.core.api.openai.get_client()
path = self.prepare_output_path(extension="mp3")
response = client.audio.speech.create(
model="tts-1",
voice="alloy",
input=text,
)
response.stream_to_file(path)
return str(path)
See examples/example_audio_output.py for settings, voices and configuration checks.
Adding a web search provider
Web search providers are registered with web=[...]. They provide a search-engine implementation used
by the Web search plugin.
A provider normally defines its own settings and implements search():
from pygpt_net.provider.web.base import BaseProvider
class ExampleWebSearchEngine(BaseProvider):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.id = "example_web_search"
self.name = "Example search engine"
self.type = ["search_engine"]
def search(self, query: str, limit: int = 10, offset: int = 0):
# Call the external search API here.
# Return result URLs as a list of strings.
return [
"https://example.com/result-1",
"https://example.com/result-2",
]
def is_configured(self, cmds):
return True
A production provider should define credentials/settings with plugin.add_option() and use
self.plugin.get_url() or another PyGPT/network helper appropriate for the integration.
See examples/example_web_search.py for a complete Google Custom Search tutorial.
Adding a custom agent
Agent providers are registered with agents=[...]. Built-in providers live under
pygpt_net.provider.agents and include LlamaIndex and OpenAI Agents workflows.
The simplest safe extension is often to subclass an existing workflow and customize one stage:
from pygpt_net.provider.agents.openai.agent import Agent as OpenAIAgentBase
class ExampleAgent(OpenAIAgentBase):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.id = "example_agent"
self.name = "Example custom agent"
def get_agent(self, window, kwargs):
kwargs = dict(kwargs or {})
current = str(
kwargs.get("system_prompt") or ""
).strip()
prefix = (
"You are running through the custom "
"ExampleAgent provider."
)
kwargs["system_prompt"] = (
f"{prefix}\n\n{current}"
if current
else prefix
)
return super().get_agent(window, kwargs)
The inherited run() keeps the existing OpenAI Agents execution lifecycle, streaming, tools, response
IDs and PyGPT bridge integration. Override run() only when a different execution engine/lifecycle is
actually required.
For a completely new runtime, derive from pygpt_net.provider.agents.base.BaseAgent and implement the
current provider contract.
See examples/example_agent.py.
Source code as API reference
The base interfaces are the source of truth for method signatures. Useful implementation directories are:
pygpt_net.pluginpygpt_net.toolspygpt_net.provider.llmspygpt_net.provider.vector_storespygpt_net.provider.loaderspygpt_net.provider.audio_inputpygpt_net.provider.audio_outputpygpt_net.provider.webpygpt_net.provider.agents
For complete runnable tutorial files, use the repository examples directory.