跳轉至

插件開發指南

使用自定義 Python 工具擴充 Boring 的功能。

解決風險: 功能膨脹 - 鼓勵外部插件而非核心功能擴展


🛠️ 概述

Boring 具備動態插件系統,允許您將自定義 Python 函數註冊為 AI 可呼叫的工具。插件可以是專案專用或全域通用的。

為什麼使用插件?

┌─────────────────────────────────────────────────────────────────────────────┐
│                         插件 vs 核心功能                                     │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│   ┌─────────────────────────────┐   ┌─────────────────────────────┐         │
│   │       🎯 核心功能            │   │       🔌 插件                │         │
│   ├─────────────────────────────┤   ├─────────────────────────────┤         │
│   │ • 所有用戶都需要             │   │ • 特定場景使用               │         │
│   │ • 穩定的 API                │   │ • 實驗性功能                 │         │
│   │ • 由核心團隊維護            │   │ • 社區或個人維護             │         │
│   │ • 嚴格的質量要求            │   │ • 快速迭代                   │         │
│   └─────────────────────────────┘   └─────────────────────────────┘         │
│                                                                              │
└─────────────────────────────────────────────────────────────────────────────┘

插件存放位置

  1. 專案本地{project_root}/.boring_plugins/
  2. 使用者全域~/.boring/plugins/

📝 建立插件

要建立插件,請定義一個 Python 函數並使用 @plugin 裝飾器。

基本範例:my_tool_plugin.py

from boring.plugins.loader import plugin

@plugin(
    name="my_custom_linter",
    description="針對特定業務邏輯的自定義 Lint 規則",
    version="1.0.0",
    author="Vibe Coder",
    tags=["lint", "custom"]
)
def my_custom_linter(file_path: str) -> dict:
    """
    分析檔案中是否存在自定義模式。
    """
    # 在這裡實作您的邏輯
    content = open(file_path).read().lower()
    if "todo" in content:
        return {"passed": False, "issues": ["發現尚未處理的 TODO"]}

    return {"passed": True, "issues": []}

🏗️ 進階插件架構

帶有配置的插件

from pathlib import Path
from typing import Optional
from boring.plugins.loader import plugin

# 插件可以有自己的配置
PLUGIN_CONFIG = {
    "max_line_length": 120,
    "ignored_patterns": ["*.test.py", "*_mock.py"],
}

@plugin(
    name="configurable_linter",
    description="可配置的代碼風格檢查器",
    version="1.0.0",
    author="Your Name",
    tags=["lint", "configurable"]
)
def configurable_linter(
    file_path: str,
    max_line_length: Optional[int] = None,
) -> dict:
    """
    根據配置檢查代碼風格。

    Args:
        file_path: 要檢查的檔案路徑
        max_line_length: 覆蓋默認行長限制

    Returns:
        包含檢查結果的字典
    """
    line_limit = max_line_length or PLUGIN_CONFIG["max_line_length"]

    issues = []
    with open(file_path, "r", encoding="utf-8") as f:
        for i, line in enumerate(f, 1):
            if len(line.rstrip()) > line_limit:
                issues.append(f"Line {i}: exceeds {line_limit} characters")

    return {
        "status": "PASS" if not issues else "FAIL",
        "file": file_path,
        "issues": issues,
        "config_used": {"max_line_length": line_limit},
    }

帶有外部依賴的插件

from boring.plugins.loader import plugin

# 安全地導入可選依賴
try:
    import pandas as pd
    PANDAS_AVAILABLE = True
except ImportError:
    PANDAS_AVAILABLE = False

@plugin(
    name="csv_analyzer",
    description="分析 CSV 檔案的統計資訊 (需要 pandas)",
    version="1.0.0",
    author="Data Team",
    tags=["data", "csv", "analysis"]
)
def csv_analyzer(file_path: str) -> dict:
    """
    分析 CSV 檔案並返回統計摘要。

    注意:此插件需要 pandas 庫。
    安裝:pip install pandas
    """
    if not PANDAS_AVAILABLE:
        return {
            "status": "ERROR",
            "error": "pandas not installed. Run: pip install pandas",
        }

    try:
        df = pd.read_csv(file_path)
        return {
            "status": "SUCCESS",
            "rows": len(df),
            "columns": list(df.columns),
            "dtypes": df.dtypes.to_dict(),
            "null_counts": df.isnull().sum().to_dict(),
        }
    except Exception as e:
        return {"status": "ERROR", "error": str(e)}

帶有 LLM 調用的插件

from boring.plugins.loader import plugin
from boring.config import settings

@plugin(
    name="smart_summarizer",
    description="使用 LLM 智能摘要文件",
    version="1.0.0",
    author="AI Team",
    tags=["ai", "summarization"]
)
def smart_summarizer(file_path: str, max_words: int = 100) -> dict:
    """
    使用 AI 生成文件摘要。

    Args:
        file_path: 要摘要的檔案
        max_words: 摘要最大字數
    """
    from boring.llm import get_llm_provider

    try:
        with open(file_path, "r", encoding="utf-8") as f:
            content = f.read()

        provider = get_llm_provider()
        if not provider.is_available:
            return {"status": "ERROR", "error": "No LLM provider available"}

        prompt = f"請用不超過 {max_words} 字摘要以下內容:\n\n{content[:5000]}"
        summary, success = provider.generate(prompt)

        if success:
            return {"status": "SUCCESS", "summary": summary}
        else:
            return {"status": "ERROR", "error": summary}

    except Exception as e:
        return {"status": "ERROR", "error": str(e)}

🚀 載入與熱重載 (Hot-Reload)

  • 自動發現:位於搜尋路徑中的任何 .py 或以 _plugin.py 結尾的檔案都會被自動發現。
  • 熱重載:Boring 會監控插件檔案。如果您在循環執行期間修改插件,系統會在下一次迭代時自動載入新邏輯。

🔍 使用插件

載入後,您的插件會像內建工具一樣提供給 AI 代理使用。代理會透過語義搜尋或工具列表發現它。

CLI 命令

# 列出目前已載入的插件
boring list-plugins

# 重新載入所有插件
boring reload-plugins

# 執行特定插件
boring run-plugin my_custom_linter --file-path src/main.py

MCP 工具

boring_list_plugins    # 列出插件
boring_reload_plugins  # 重新載入
boring_run_plugin      # 執行插件

🧪 測試插件

單元測試範例

# tests/plugins/test_my_plugin.py
import pytest
from pathlib import Path

# 直接導入插件函數
from my_tool_plugin import my_custom_linter

class TestMyCustomLinter:
    def test_pass_no_todo(self, tmp_path):
        """測試沒有 TODO 時通過"""
        test_file = tmp_path / "clean.py"
        test_file.write_text("def hello(): pass")

        result = my_custom_linter(str(test_file))

        assert result["passed"] is True
        assert result["issues"] == []

    def test_fail_with_todo(self, tmp_path):
        """測試有 TODO 時失敗"""
        test_file = tmp_path / "dirty.py"
        test_file.write_text("# TODO: fix this")

        result = my_custom_linter(str(test_file))

        assert result["passed"] is False
        assert len(result["issues"]) > 0

🛡️ 安全與防護 (Security & Safety)

插件(特別是透過 boring_synth_tool 生成的工具)受到嚴格的安全約束:

  1. AST 沙箱驗證:所有即時合成的工具都會通過 SynthesizedToolValidator 進行語法樹掃描。
  2. 禁用操作:為了防止破壞性行為,沙箱會封鎖:
    • 禁止導入os, sys, subprocess, shutil, socket
    • 禁止函數exec(), eval(), open(), compile()
  3. 影子模式綁定:所有插件執行都受 影子模式 (Shadow Mode) 監控,為檔案操作提供第二層防線。

📦 發布插件

作為 Python 包發布

# setup.py 或 pyproject.toml
[project]
name = "boring-plugin-mytools"
version = "1.0.0"
dependencies = ["boring-aicoding>=11.0.0"]

[project.entry-points."boring.plugins"]
my_tool = "boring_plugin_mytools:my_tool"

社區插件倉庫

我們歡迎社區貢獻插件!請: 1. 創建獨立的 Git 倉庫 2. 遵循命名約定:boring-plugin-{name} 3. 提交 Issue 將您的插件添加到官方列表


💡 最佳實踐

  1. 型別提示 (Type Hints):務必為參數提供型別提示,這能幫助 LLM 理解輸入要求。
  2. 文檔字串 (Docstrings):提供清晰、具描述性的文檔字串,因為 AI 依靠這些資訊決定何時呼叫該工具。
  3. 回傳格式:回傳結構化數據(dict)而非純字串,以便代理進行後續處理。
  4. 錯誤處理:總是返回 {"status": "ERROR", "error": "..."} 而不是拋出異常。
  5. 可選依賴:使用 try/except 導入可選依賴,提供友好的錯誤訊息。
  6. 版本管理:使用語義化版本號標記您的插件。

🔗 相關文檔