Penemuan dan Pendaftaran Plugin
Registri alat, berkas manifes, dan pemuatan alat dinamis saat runtime.
Penemuan dan Pendaftaran Plugin adalah pelajaran AI Agents gratis di CoddyKit. Ini adalah pelajaran 2 dari 4. Kamu bisa membaca pelajaran lengkapnya di bawah secara gratis — lalu praktikkan langsung di browser dengan editor kode bawaan dan tutor AI 24/7. Ini adalah bagian dari jalur belajar AI Agents, dan progresmu tersinkronisasi di web dan aplikasi CoddyKit. Kursus AI Agents mencakup 4 pelajaran total.
Sistem Pengaya untuk Agen
Sistem pengaya memungkinkan agen menemukan dan memuat alat baru saat berjalan tanpa memodifikasi kode inti agen. Agen membaca direktori pengaya, memuat manifes setiap pengaya, memvalidasinya, dan menambahkan alatnya ke registri alat aktif.
Hal ini memungkinkan arsitektur agen yang modular dan dapat diperluas.
Format Manifes Pengaya
Setiap pengaya menyertakan berkas manifes plugin.json. Manifes ini adalah kartu identitas pengaya: menjelaskan apa pengaya tersebut, versinya, alat yang disediakannya, serta hal-hal yang dibutuhkannya (paket Python, variabel lingkungan).
# plugin.json — stored in the plugin's root directory
EXAMPLE_MANIFEST = {
'name': 'weather-tools',
'display_name': 'Weather Tools',
'version': '2.1.0',
'description': 'Real-time weather and forecast tools',
'author': 'Jane Developer <jane@example.com>',
'license': 'MIT',
'entry_point': 'weather_tools.plugin', # Python module path
'tool_definitions': [
'get_current_weather',
'get_5day_forecast',
'get_weather_alerts'
],
'requires': {
'python_packages': ['requests>=2.28'],
'env_vars': ['WEATHER_API_KEY']
},
'tags': ['weather', 'forecast', 'iot'],
'min_framework_version': '1.0.0'
}
import json
print(json.dumps(EXAMPLE_MANIFEST, indent=2)[:300])Pemuat Direktori Pengaya
Pindai direktori untuk mencari subdirektori pengaya. Setiap subdirektori harus berisi manifes plugin.json. Pemuat membaca setiap manifes, memvalidasinya, dan mendaftarkan pengaya ke registri pengaya.
import os
import json
PLUGIN_DIR = 'plugins'
def discover_plugins(plugin_dir: str = PLUGIN_DIR) -> list:
discovered = []
if not os.path.isdir(plugin_dir):
print(f'Plugin directory not found: {plugin_dir}')
return []
for entry in os.scandir(plugin_dir):
if not entry.is_dir():
continue
manifest_path = os.path.join(entry.path, 'plugin.json')
if not os.path.exists(manifest_path):
print(f'Skipping {entry.name}: no plugin.json')
continue
try:
with open(manifest_path) as f:
manifest = json.load(f)
manifest['_path'] = entry.path
manifest['_name'] = entry.name
discovered.append(manifest)
print(f'Discovered: {manifest["name"]} v{manifest["version"]}')
except (json.JSONDecodeError, KeyError) as e:
print(f'Invalid manifest in {entry.name}: {e}')
return discovered
plugins = discover_plugins()
print(f'Total plugins discovered: {len(plugins)}')Validasi Manifes
Sebelum memuat pengaya, validasikan manifesnya terhadap skema untuk menemukan kolom wajib yang hilang, versi yang tidak valid, atau variabel lingkungan yang tidak ada. Tolak pengaya yang tidak valid dan catat alasannya.
import os
REQUIRED_MANIFEST_FIELDS = {'name', 'version', 'entry_point', 'tool_definitions'}
def validate_manifest(manifest: dict) -> tuple:
"""
Returns (is_valid: bool, errors: list)
"""
errors = []
# Required fields
missing = REQUIRED_MANIFEST_FIELDS - set(manifest.keys())
if missing:
errors.append(f'Missing required fields: {missing}')
# Version format
version = manifest.get('version', '')
parts = version.split('.')
if len(parts) != 3 or not all(p.isdigit() for p in parts):
errors.append(f'Invalid version format: {version}')
# Check required env vars exist
env_vars = manifest.get('requires', {}).get('env_vars', [])
for var in env_vars:
if not os.environ.get(var):
errors.append(f'Missing required env var: {var}')
# Tool definitions must be a non-empty list
tools = manifest.get('tool_definitions', [])
if not isinstance(tools, list) or len(tools) == 0:
errors.append('tool_definitions must be a non-empty list')
return len(errors) == 0, errors
if __name__ == '__main__':
good_manifest = {
'name': 'weather-tools', 'version': '1.2.0',
'entry_point': 'weather_tools.plugin', 'tool_definitions': [{'name': 'get_weather'}]
}
bad_manifest = {'name': 'broken-plugin', 'version': 'v1'}
print('Good manifest:', validate_manifest(good_manifest))
print('Bad manifest: ', validate_manifest(bad_manifest))
Pemuatan Alat Dinamis
Setelah manifes pengaya divalidasi, impor modul Python pengaya secara dinamis dan panggil get_tools() untuk mengambil definisi alat serta fungsi eksekusinya. importlib milik Python membuat proses ini menjadi sederhana.
import importlib
import importlib.util
import sys
def load_plugin_module(manifest: dict):
"""
Import the plugin's Python module and return it.
Adds the plugin directory to sys.path if needed.
"""
plugin_path = manifest['_path']
entry_point = manifest['entry_point'] # e.g. 'weather_tools.plugin'
# Add plugin directory to path so relative imports work
if plugin_path not in sys.path:
sys.path.insert(0, plugin_path)
try:
module = importlib.import_module(entry_point)
return module
except ImportError as e:
print(f'Failed to import {entry_point}: {e}')
return None
def extract_tools_from_module(module, tool_names: list) -> dict:
"""
Returns {tool_name: {'schema': dict, 'execute': callable}}
"""
tools = {}
for name in tool_names:
schema_fn = getattr(module, f'get_{name}_schema', None)
execute_fn = getattr(module, f'execute_{name}', None)
if schema_fn and execute_fn:
tools[name] = {'schema': schema_fn(), 'execute': execute_fn}
return tools
if __name__ == '__main__':
class FakeModule:
def get_weather_schema(self):
return {'name': 'get_weather'}
def execute_weather(self, params):
return {'temp': 72}
tools = extract_tools_from_module(FakeModule(), ['weather'])
print('Extracted tools:', list(tools.keys()))
print('Weather schema:', tools['weather']['schema'])
Registri Alat
Registri alat adalah satu-satunya sumber kebenaran untuk semua alat yang tersedia. Registri ini memetakan nama alat ke skema dan fungsi eksekusinya. Agen meminta data dari registri saat menyusun daftar alat untuk setiap panggilan LLM.
class ToolRegistry:
def __init__(self):
self._tools: dict = {} # name -> {'schema', 'execute', 'plugin'}
self._plugins: dict = {} # plugin_name -> manifest
def register_tool(
self, name: str, schema: dict,
execute_fn, plugin_name: str
):
if name in self._tools:
print(f'WARNING: Tool {name} already registered, overwriting')
self._tools[name] = {
'schema': schema,
'execute': execute_fn,
'plugin': plugin_name
}
def register_plugin(self, manifest: dict, tools: dict):
self._plugins[manifest['name']] = manifest
for name, tool in tools.items():
self.register_tool(name, tool['schema'],
tool['execute'], manifest['name'])
print(f'Registered plugin: {manifest["name"]} '
f'({len(tools)} tools)')
def get_all_schemas(self) -> list:
return [t['schema'] for t in self._tools.values()]
def execute(self, tool_name: str, params: dict):
if tool_name not in self._tools:
raise KeyError(f'Unknown tool: {tool_name}')
return self._tools[tool_name]['execute'](params)
registry = ToolRegistry()
if __name__ == '__main__':
def get_weather(params):
return {'temp': 72, 'city': params.get('city')}
manifest = {'name': 'weather-tools'}
tools = {'get_weather': {'schema': {'name': 'get_weather'}, 'execute': get_weather}}
registry.register_plugin(manifest, tools)
print('Registered schemas:', registry.get_all_schemas())
print('Execution result:', registry.execute('get_weather', {'city': 'Paris'}))
Pencarian berdasarkan Kemampuan
Ketika agen memiliki banyak pengaya, agen harus dapat mencari registri berdasarkan tag kemampuan, bukan memuat semua alat ke dalam setiap panggilan LLM. Terlalu banyak alat dalam konteks menurunkan akurasi pemilihan alat oleh LLM.
class SearchableToolRegistry(ToolRegistry):
def search(self, query: str) -> list:
"""
Search tools by name, description, or tags.
Returns list of matching tool schemas.
"""
query_lower = query.lower()
matches = []
for name, tool in self._tools.items():
schema = tool['schema']
plugin = self._plugins.get(tool['plugin'], {})
plugin_tags = plugin.get('tags', [])
if (
query_lower in name.lower() or
query_lower in schema.get('description', '').lower() or
any(query_lower in tag.lower() for tag in plugin_tags)
):
matches.append(schema)
return matches
# Usage:
registry = SearchableToolRegistry()
# After loading plugins...
weather_tools = registry.search('weather')
print(f'Weather tools: {[t["name"] for t in weather_tools]}')Muat Ulang saat Direktori Berubah
Dalam pengembangan, memuat ulang pengaya saat berkas berubah tanpa memulai ulang agen sangatlah berguna. Gunakan pustaka watchdog untuk mengawasi direktori pengaya dan memicu pemuatan ulang ketika berkas plugin.json berubah.
# pip install watchdog
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler
class PluginReloadHandler(FileSystemEventHandler):
def __init__(self, registry, plugin_loader_fn):
self.registry = registry
self.loader = plugin_loader_fn
def on_modified(self, event):
if 'plugin.json' in event.src_path:
print(f'Plugin manifest changed: {event.src_path}')
self.reload_plugin(event.src_path)
def reload_plugin(self, manifest_path: str):
import json, os
plugin_dir = os.path.dirname(manifest_path)
try:
with open(manifest_path) as f:
manifest = json.load(f)
manifest['_path'] = plugin_dir
self.loader(manifest, self.registry)
print(f'Reloaded: {manifest["name"]}')
except Exception as e:
print(f'Reload failed: {e}')
def start_plugin_watcher(plugin_dir: str, registry):
handler = PluginReloadHandler(registry, lambda m, r: None)
observer = Observer()
observer.schedule(handler, plugin_dir, recursive=True)
observer.start()
return observerPemeriksaan Dependensi dan Instalasi Otomatis
Saat memuat pengaya, periksa apakah paket Python yang diperlukan sudah terpasang. Anda dapat memilih untuk memasang paket yang hilang secara otomatis menggunakan pip. Catat kesalahan yang jelas jika dependensi tidak dapat dipenuhi.
import subprocess
import sys
import importlib
def check_and_install_deps(manifest: dict, auto_install: bool = False) -> bool:
packages = manifest.get('requires', {}).get('python_packages', [])
missing = []
for pkg_spec in packages:
pkg_name = pkg_spec.split('>=')[0].split('==')[0].strip()
try:
importlib.import_module(pkg_name.replace('-', '_'))
except ImportError:
missing.append(pkg_spec)
if not missing:
return True
print(f'Missing packages for {manifest["name"]}: {missing}')
if auto_install:
for pkg in missing:
print(f'Installing {pkg}...')
result = subprocess.run(
[sys.executable, '-m', 'pip', 'install', pkg],
capture_output=True, text=True
)
if result.returncode != 0:
print(f'Install failed: {result.stderr[:200]}')
return False
return True
return False
if __name__ == '__main__':
manifest = {'name': 'demo-plugin', 'requires': {'python_packages': ['totally_fake_package_xyz']}}
ok = check_and_install_deps(manifest, auto_install=False)
print('All dependencies satisfied:', ok)
Inisialisasi Pengaya Lengkap
Satukan penemuan, validasi, pemeriksaan dependensi, pemuatan modul, dan pendaftaran ke registri dalam satu fungsi inisialisasi yang dipanggil saat agen dimulai.
def bootstrap_plugins(
plugin_dir: str,
registry: ToolRegistry,
auto_install_deps: bool = False
) -> dict:
results = {'loaded': [], 'failed': []}
manifests = discover_plugins(plugin_dir)
for manifest in manifests:
name = manifest.get('name', '?')
is_valid, errors = validate_manifest(manifest)
if not is_valid:
print(f'INVALID {name}: {errors}')
results['failed'].append({'name': name, 'reason': errors})
continue
deps_ok = check_and_install_deps(manifest, auto_install_deps)
if not deps_ok:
results['failed'].append({'name': name, 'reason': 'missing_deps'})
continue
module = load_plugin_module(manifest)
if not module:
results['failed'].append({'name': name, 'reason': 'import_error'})
continue
tools = extract_tools_from_module(module, manifest['tool_definitions'])
registry.register_plugin(manifest, tools)
results['loaded'].append(name)
print(f'Plugins loaded: {results["loaded"]}')
print(f'Plugins failed: {results["failed"]}')
return resultsMencatat Peristiwa Siklus Hidup Pengaya
Catat setiap peristiwa penting dalam siklus hidup pengaya: penemuan, kegagalan validasi, pemuatan yang berhasil, pemuatan ulang langsung, dan pembongkaran. Catatan ini penting untuk penelusuran kesalahan dan audit sistem pengaya di lingkungan produksi.
import logging
from datetime import datetime
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s [%(levelname)s] %(name)s: %(message)s'
)
plugin_logger = logging.getLogger('plugin_system')
def log_plugin_event(event: str, plugin_name: str, details: dict = None):
entry = {
'event': event,
'plugin': plugin_name,
'timestamp': datetime.utcnow().isoformat(),
'details': details or {}
}
if event in ('VALIDATION_FAILED', 'LOAD_FAILED', 'SIGNATURE_INVALID'):
plugin_logger.warning('Plugin event: %s', entry)
else:
plugin_logger.info('Plugin event: %s', entry)
# Usage:
log_plugin_event('DISCOVERED', 'weather-tools', {'path': 'plugins/weather-tools'})
log_plugin_event('LOADED', 'weather-tools', {'tools': ['get_current_weather']})
log_plugin_event('VALIDATION_FAILED', 'bad-plugin', {'errors': ['Missing entry_point']})
if __name__ == '__main__':
import sys
plugin_logger.addHandler(logging.StreamHandler(sys.stdout))
log_plugin_event('LOADED', 'demo-plugin', {'tools': ['get_weather']})
Uji Pengetahuan
Apa tujuan kolom entry_point dalam manifes pengaya?
Rangkuman: Penemuan dan Pendaftaran Pengaya
Sangat baik! Yang telah dipelajari:
- Manifes pengaya: plugin.json dengan nama, versi, entry_point, tool_definitions, requires
- Penemuan: memindai direktori untuk mencari subdirektori yang berisi plugin.json
- Validasi: memeriksa kolom wajib, format versi, variabel lingkungan, dan alat yang tidak kosong
- Pemuatan dinamis: importlib.import_module() menggunakan jalur entry_point
- Registri alat: pemetaan pusat dari nama alat ke skema dan fungsi execute
- Pemuatan ulang langsung: watchdog mengawasi direktori pengaya dan memuat ulang saat manifes berubah
Berikutnya: pembuatan versi alat dan pengelolaan kompatibilitas.
Pertanyaan yang Sering Diajukan
Apakah pelajaran “Penemuan dan Pendaftaran Plugin” gratis?
Ya — teks lengkap “Penemuan dan Pendaftaran Plugin” gratis dibaca di sini di web. Untuk praktiknya secara interaktif (editor kode bawaan dan tutor AI 24/7) dan buka sisa kursus AI Agents, upgrade ke CoddyKit PRO. Kursus AI Agents mencakup 4 pelajaran total.
Apa yang akan aku pelajari di “Penemuan dan Pendaftaran Plugin”?
Registri alat, berkas manifes, dan pemuatan alat dinamis saat runtime. Kamu berlatih AI Agents dengan kode praktik yang langsung kamu jalankan di browser, dan tutor AI 24/7 menjawab pertanyaanmu saat kamu mengerjakan pelajaran ini.
Apakah aku perlu pengalaman untuk memulai AI Agents?
Tidak diperlukan pengalaman sebelumnya. AI Agents di CoddyKit dirancang untuk pemula hingga pelajar tingkat lanjut, jadi kamu bisa memulai di sini atau dari awal dan belajar sesuai kecepatan kamu sendiri. Ini adalah pelajaran 2 dari 4.
Berapa lama pelajaran “Penemuan dan Pendaftaran Plugin” memakan waktu?
Sebagian besar pelajaran CoddyKit memakan waktu sekitar 5–10 menit. Setiap pelajaran ringkas dan interaktif, jadi kamu membuat kemajuan stabil dan melanjutkan dari tempat kamu tinggalkan di web dan aplikasi.
Bisakah aku menulis dan menjalankan kode dalam pelajaran AI Agents ini?
Ya. Setiap pelajaran AI Agents menyertakan editor kode bawaan, jadi kamu menulis dan menjalankan kode nyata langsung di browser dan mendapatkan umpan balik AI instan — tidak diperlukan penyiapan lokal.
Semua pelajaran dalam kursus ini
- Merancang Alat Agen yang Dapat Dibagikan
- Penemuan dan Pendaftaran Plugin
- Pemberian Versi dan Kompatibilitas Alat
- Membangun Marketplace Plugin Agen