Start work on supporting multiple AI providers via plugins

This commit is contained in:
Kovid Goyal 2025-08-29 11:38:21 +05:30
parent 21aa23eba4
commit adb85e2569
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
5 changed files with 68 additions and 4 deletions

View File

@ -0,0 +1,13 @@
#!/usr/bin/env python
# License: GPLv3 Copyright: 2025, Kovid Goyal <kovid at kovidgoyal.net>
from calibre.customize import AIProviderPlugin
class OpenRouterAI(AIProviderPlugin):
name = 'OpenRouter'
version = (1, 0, 0)
description = _('AI services from OpenRouter.ai. Allows choosing from hundreds of different AI models to query.')
author = 'Kovid Goyal'
builtin_live_module_name = 'calibre.ai.open_router.backend'

View File

@ -0,0 +1,5 @@
#!/usr/bin/env python
# License: GPLv3 Copyright: 2025, Kovid Goyal <kovid at kovidgoyal.net>
module_version = 1 # needed for live updates

View File

@ -817,3 +817,32 @@ class LibraryClosedPlugin(Plugin): # {{{
raise NotImplementedError('LibraryClosedPlugin '
'run method must be overridden in subclass')
# }}}
class AIProviderPlugin(Plugin): # {{{
'''
AIProvider plugins abstract AI services that can be used by the rest of calibre.
'''
type = _('AI provider')
# minimum version when support for this plugin type was added
minimum_calibre_version = (8, 10, 0)
# Used by builtin AI Provider plugins to live load the backend code
builtin_live_module_name = ''
@property
def builtin_live_module(self):
if not self.builtin_live_module_name:
return None
if (ans := self._builtin_live_module) is None:
from calibre.live import Strategy, load_module
ans = self._builtin_live_module = load_module(self.builtin_live_module_name, strategy=Strategy.fast)
return ans
def initialize(self):
self._builtin_live_module = None
def customization_help(self):
return ''
# }}}

View File

@ -4,6 +4,7 @@ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
import glob
import os
from calibre.ai.open_router import OpenRouterAI
from calibre.constants import numeric_version
from calibre.customize import FileTypePlugin, InterfaceActionBase, MetadataReaderPlugin, MetadataWriterPlugin, PreferencesPlugin, StoreBase
from calibre.ebooks.html.to_zip import HTML2ZIP
@ -434,9 +435,9 @@ plugins += [x for x in list(locals().values()) if isinstance(x, type) and
# }}}
# Metadata writer plugins {{{
class EPUBMetadataWriter(MetadataWriterPlugin):
name = 'Set EPUB metadata'
@ -851,9 +852,9 @@ plugins += [GoogleBooks, GoogleImages, Amazon, Edelweiss, OpenLibrary, BigBookSe
# }}}
# Interface Actions {{{
class ActionAdd(InterfaceActionBase):
name = 'Add Books'
actual_plugin = 'calibre.gui2.actions.add:AddAction'
@ -1191,9 +1192,9 @@ plugins += [ActionAdd, ActionAllActions, ActionColumnTooltip, ActionFetchAnnotat
# }}}
# Preferences Plugins {{{
class LookAndFeel(PreferencesPlugin):
name = 'Look & Feel'
icon = 'lookfeel.png'
@ -1468,9 +1469,9 @@ plugins += [LookAndFeel, Behavior, Columns, Toolbar, Search, InputOptions,
# }}}
# Store plugins {{{
class StoreAmazonKindleStore(StoreBase):
name = 'Amazon Kindle'
description = 'Kindle books from Amazon.'
@ -1976,6 +1977,8 @@ plugins += [
# }}}
plugins.extend((OpenRouterAI,))
if __name__ == '__main__':
# Test load speed
import subprocess

View File

@ -7,10 +7,12 @@ import shutil
import sys
import traceback
from collections import defaultdict
from collections.abc import Iterator
from itertools import chain, repeat
from calibre.constants import DEBUG, ismacos, numeric_version, system_plugins_loc
from calibre.customize import (
AIProviderPlugin,
CatalogPlugin,
EditBookToolPlugin,
FileTypePlugin,
@ -357,6 +359,18 @@ def has_library_closed_plugins():
# }}}
# AI Provider Plugins {{{
def available_ai_provider_plugins() -> Iterator[AIProviderPlugin]:
customization = config['plugin_customization']
for plugin in _initialized_plugins:
if isinstance(plugin, AIProviderPlugin):
if not is_disabled(plugin):
plugin.site_customization = customization.get(plugin.name, '')
yield plugin
# }}}
# Store Plugins # {{{
def store_plugins():