Skip to content

插件开发

AnythingLLM 提供了强大的插件系统,允许开发者扩展平台功能、集成第三方服务,以及创建自定义的工作流程。本指南将详细介绍如何开发、测试和部署 AnythingLLM 插件。

插件系统概述

插件架构

AnythingLLM 的插件系统基于事件驱动架构,支持以下类型的插件:

  • LLM 提供商插件: 集成新的大语言模型
  • 向量数据库插件: 支持新的向量存储后端
  • 文档处理插件: 扩展文档解析和处理能力
  • 认证插件: 集成第三方认证系统
  • 工作流插件: 创建自定义的自动化流程
  • UI 组件插件: 扩展前端界面功能

插件生命周期

  1. 初始化: 插件加载和配置
  2. 注册: 注册事件监听器和路由
  3. 执行: 响应系统事件和用户操作
  4. 清理: 插件卸载和资源释放

快速开始

创建插件项目

使用官方脚手架工具快速创建插件项目:

bash
# 安装插件开发工具
npm install -g @anythingllm/plugin-cli

# 创建新插件
anythingllm-plugin create my-awesome-plugin

# 进入插件目录
cd my-awesome-plugin

# 安装依赖
npm install

插件目录结构

my-awesome-plugin/
├── package.json          # 插件配置和依赖
├── plugin.json           # 插件元数据
├── src/
│   ├── index.js          # 插件入口文件
│   ├── handlers/         # 事件处理器
│   ├── routes/           # API 路由
│   ├── components/       # 前端组件
│   └── utils/            # 工具函数
├── tests/                # 测试文件
├── docs/                 # 插件文档
└── README.md

插件配置文件

plugin.json 定义了插件的基本信息:

json
{
  "name": "my-awesome-plugin",
  "version": "1.0.0",
  "description": "一个很棒的 AnythingLLM 插件",
  "author": "Your Name",
  "license": "MIT",
  "main": "src/index.js",
  "type": "workflow",
  "permissions": [
    "workspace.read",
    "workspace.write",
    "document.process"
  ],
  "dependencies": {
    "axios": "^1.0.0"
  },
  "config": {
    "apiKey": {
      "type": "string",
      "required": true,
      "description": "第三方服务 API 密钥"
    },
    "endpoint": {
      "type": "string",
      "default": "https://api.example.com",
      "description": "API 端点 URL"
    }
  }
}

插件开发

基础插件结构

每个插件都需要实现基础的插件类:

javascript
// src/index.js
const { BasePlugin } = require('@anythingllm/plugin-sdk');

class MyAwesomePlugin extends BasePlugin {
  constructor(config) {
    super(config);
    this.name = 'my-awesome-plugin';
    this.version = '1.0.0';
  }

  // 插件初始化
  async initialize() {
    console.log('插件初始化中...');
    
    // 注册事件监听器
    this.registerEventHandlers();
    
    // 注册 API 路由
    this.registerRoutes();
    
    // 注册前端组件
    this.registerComponents();
    
    console.log('插件初始化完成');
  }

  // 注册事件处理器
  registerEventHandlers() {
    // 文档上传事件
    this.on('document.uploaded', this.handleDocumentUploaded.bind(this));
    
    // 聊天消息事件
    this.on('chat.message', this.handleChatMessage.bind(this));
    
    // 工作空间创建事件
    this.on('workspace.created', this.handleWorkspaceCreated.bind(this));
  }

  // 注册 API 路由
  registerRoutes() {
    this.router.get('/status', this.getStatus.bind(this));
    this.router.post('/process', this.processData.bind(this));
  }

  // 注册前端组件
  registerComponents() {
    this.addComponent('sidebar', {
      name: 'MyPluginSidebar',
      component: require('./components/Sidebar')
    });
  }

  // 事件处理器
  async handleDocumentUploaded(event) {
    const { document, workspace } = event.data;
    console.log(`文档 ${document.filename} 已上传到工作空间 ${workspace.name}`);
    
    // 自定义处理逻辑
    await this.processDocument(document);
  }

  async handleChatMessage(event) {
    const { message, workspace, user } = event.data;
    
    // 检查是否需要插件处理
    if (message.includes('@my-plugin')) {
      const response = await this.generateResponse(message);
      return { response, handled: true };
    }
    
    return { handled: false };
  }

  // API 路由处理器
  async getStatus(req, res) {
    res.json({
      status: 'active',
      version: this.version,
      uptime: process.uptime()
    });
  }

  async processData(req, res) {
    try {
      const result = await this.processUserData(req.body);
      res.json({ success: true, data: result });
    } catch (error) {
      res.status(500).json({ success: false, error: error.message });
    }
  }

  // 插件清理
  async cleanup() {
    console.log('插件清理中...');
    // 清理资源、关闭连接等
  }
}

module.exports = MyAwesomePlugin;

LLM 提供商插件

创建自定义 LLM 提供商插件:

javascript
// src/providers/CustomLLMProvider.js
const { BaseLLMProvider } = require('@anythingllm/plugin-sdk');

class CustomLLMProvider extends BaseLLMProvider {
  constructor(config) {
    super(config);
    this.name = 'custom-llm';
    this.apiKey = config.apiKey;
    this.endpoint = config.endpoint;
  }

  // 实现聊天接口
  async chat(messages, options = {}) {
    try {
      const response = await this.makeRequest('/chat/completions', {
        messages: this.formatMessages(messages),
        model: options.model || 'default',
        temperature: options.temperature || 0.7,
        max_tokens: options.maxTokens || 2048
      });

      return {
        content: response.choices[0].message.content,
        usage: response.usage,
        model: response.model
      };
    } catch (error) {
      throw new Error(`CustomLLM Error: ${error.message}`);
    }
  }

  // 实现流式聊天接口
  async streamChat(messages, options = {}) {
    const stream = await this.makeStreamRequest('/chat/completions', {
      messages: this.formatMessages(messages),
      model: options.model || 'default',
      temperature: options.temperature || 0.7,
      max_tokens: options.maxTokens || 2048,
      stream: true
    });

    return stream;
  }

  // 格式化消息
  formatMessages(messages) {
    return messages.map(msg => ({
      role: msg.role,
      content: msg.content
    }));
  }

  // 发送 HTTP 请求
  async makeRequest(endpoint, data) {
    const response = await fetch(`${this.endpoint}${endpoint}`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${this.apiKey}`
      },
      body: JSON.stringify(data)
    });

    if (!response.ok) {
      throw new Error(`HTTP ${response.status}: ${response.statusText}`);
    }

    return response.json();
  }

  // 获取可用模型
  async getModels() {
    const response = await this.makeRequest('/models', {});
    return response.data.map(model => ({
      id: model.id,
      name: model.name,
      maxTokens: model.max_tokens
    }));
  }
}

module.exports = CustomLLMProvider;

文档处理插件

创建自定义文档处理器:

javascript
// src/processors/CustomDocumentProcessor.js
const { BaseDocumentProcessor } = require('@anythingllm/plugin-sdk');

class CustomDocumentProcessor extends BaseDocumentProcessor {
  constructor(config) {
    super(config);
    this.supportedTypes = ['.custom', '.special'];
  }

  // 检查是否支持该文件类型
  canProcess(filename) {
    const ext = path.extname(filename).toLowerCase();
    return this.supportedTypes.includes(ext);
  }

  // 处理文档
  async process(filePath, options = {}) {
    try {
      const content = await this.extractContent(filePath);
      const chunks = await this.chunkContent(content, options);
      
      return {
        content,
        chunks,
        metadata: {
          processor: this.constructor.name,
          processedAt: new Date().toISOString(),
          chunkCount: chunks.length
        }
      };
    } catch (error) {
      throw new Error(`文档处理失败: ${error.message}`);
    }
  }

  // 提取文档内容
  async extractContent(filePath) {
    // 实现自定义文档解析逻辑
    const buffer = await fs.readFile(filePath);
    
    // 根据文件类型进行解析
    if (filePath.endsWith('.custom')) {
      return this.parseCustomFormat(buffer);
    } else if (filePath.endsWith('.special')) {
      return this.parseSpecialFormat(buffer);
    }
    
    throw new Error('不支持的文件格式');
  }

  // 分块处理
  async chunkContent(content, options) {
    const chunkSize = options.chunkSize || 1000;
    const overlap = options.overlap || 200;
    
    const chunks = [];
    let start = 0;
    
    while (start < content.length) {
      const end = Math.min(start + chunkSize, content.length);
      const chunk = content.slice(start, end);
      
      chunks.push({
        content: chunk,
        start,
        end,
        length: chunk.length
      });
      
      start = end - overlap;
    }
    
    return chunks;
  }

  // 解析自定义格式
  parseCustomFormat(buffer) {
    // 实现自定义解析逻辑
    return buffer.toString('utf-8');
  }

  // 解析特殊格式
  parseSpecialFormat(buffer) {
    // 实现特殊格式解析逻辑
    return buffer.toString('utf-8');
  }
}

module.exports = CustomDocumentProcessor;

前端组件插件

创建自定义前端组件:

jsx
// src/components/CustomSidebar.jsx
import React, { useState, useEffect } from 'react';
import { usePlugin } from '@anythingllm/plugin-sdk/react';

const CustomSidebar = () => {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const plugin = usePlugin();

  useEffect(() => {
    loadData();
  }, []);

  const loadData = async () => {
    try {
      setLoading(true);
      const response = await plugin.api.get('/status');
      setData(response.data);
    } catch (error) {
      console.error('加载数据失败:', error);
    } finally {
      setLoading(false);
    }
  };

  const handleAction = async () => {
    try {
      await plugin.api.post('/process', { action: 'custom' });
      plugin.notify('操作执行成功', 'success');
      await loadData();
    } catch (error) {
      plugin.notify('操作执行失败', 'error');
    }
  };

  if (loading) {
    return <div className="loading">加载中...</div>;
  }

  return (
    <div className="custom-sidebar">
      <h3>自定义插件</h3>
      
      <div className="status-section">
        <h4>状态信息</h4>
        <p>版本: {data?.version}</p>
        <p>状态: {data?.status}</p>
        <p>运行时间: {Math.floor(data?.uptime / 60)} 分钟</p>
      </div>

      <div className="actions-section">
        <h4>操作</h4>
        <button onClick={handleAction} className="btn-primary">
          执行自定义操作
        </button>
      </div>

      <div className="settings-section">
        <h4>设置</h4>
        <PluginSettings />
      </div>
    </div>
  );
};

// 插件设置组件
const PluginSettings = () => {
  const plugin = usePlugin();
  const [settings, setSettings] = useState({});

  useEffect(() => {
    loadSettings();
  }, []);

  const loadSettings = async () => {
    const config = await plugin.getConfig();
    setSettings(config);
  };

  const saveSettings = async () => {
    try {
      await plugin.updateConfig(settings);
      plugin.notify('设置保存成功', 'success');
    } catch (error) {
      plugin.notify('设置保存失败', 'error');
    }
  };

  return (
    <div className="plugin-settings">
      <div className="form-group">
        <label>API 密钥:</label>
        <input
          type="password"
          value={settings.apiKey || ''}
          onChange={(e) => setSettings({...settings, apiKey: e.target.value})}
        />
      </div>
      
      <div className="form-group">
        <label>端点 URL:</label>
        <input
          type="url"
          value={settings.endpoint || ''}
          onChange={(e) => setSettings({...settings, endpoint: e.target.value})}
        />
      </div>
      
      <button onClick={saveSettings} className="btn-secondary">
        保存设置
      </button>
    </div>
  );
};

export default CustomSidebar;

插件测试

单元测试

javascript
// tests/unit/plugin.test.js
const MyAwesomePlugin = require('../src/index');

describe('MyAwesomePlugin', () => {
  let plugin;

  beforeEach(() => {
    plugin = new MyAwesomePlugin({
      apiKey: 'test-key',
      endpoint: 'https://test.example.com'
    });
  });

  test('应该正确初始化插件', () => {
    expect(plugin.name).toBe('my-awesome-plugin');
    expect(plugin.version).toBe('1.0.0');
  });

  test('应该处理文档上传事件', async () => {
    const event = {
      data: {
        document: { filename: 'test.pdf' },
        workspace: { name: 'test-workspace' }
      }
    };

    const spy = jest.spyOn(plugin, 'processDocument');
    await plugin.handleDocumentUploaded(event);
    
    expect(spy).toHaveBeenCalledWith(event.data.document);
  });

  test('应该正确处理聊天消息', async () => {
    const event = {
      data: {
        message: '@my-plugin 帮我处理这个',
        workspace: { id: 1 },
        user: { id: 1 }
      }
    };

    const result = await plugin.handleChatMessage(event);
    expect(result.handled).toBe(true);
    expect(result.response).toBeDefined();
  });
});

集成测试

javascript
// tests/integration/api.test.js
const request = require('supertest');
const { createTestApp } = require('@anythingllm/test-utils');

describe('插件 API 集成测试', () => {
  let app;
  let plugin;

  beforeAll(async () => {
    app = await createTestApp();
    plugin = new MyAwesomePlugin({ apiKey: 'test-key' });
    await app.loadPlugin(plugin);
  });

  test('GET /plugins/my-awesome-plugin/status', async () => {
    const response = await request(app)
      .get('/plugins/my-awesome-plugin/status')
      .expect(200);

    expect(response.body.status).toBe('active');
    expect(response.body.version).toBe('1.0.0');
  });

  test('POST /plugins/my-awesome-plugin/process', async () => {
    const response = await request(app)
      .post('/plugins/my-awesome-plugin/process')
      .send({ data: 'test data' })
      .expect(200);

    expect(response.body.success).toBe(true);
    expect(response.body.data).toBeDefined();
  });
});

插件部署

本地开发

bash
# 启动开发模式
npm run dev

# 运行测试
npm test

# 构建插件
npm run build

插件打包

bash
# 打包插件
anythingllm-plugin build

# 生成插件包
anythingllm-plugin package

插件安装

bash
# 从本地文件安装
anythingllm-plugin install ./my-awesome-plugin.zip

# 从 npm 安装
anythingllm-plugin install my-awesome-plugin

# 从 GitHub 安装
anythingllm-plugin install github:username/my-awesome-plugin

插件管理

bash
# 列出已安装的插件
anythingllm-plugin list

# 启用插件
anythingllm-plugin enable my-awesome-plugin

# 禁用插件
anythingllm-plugin disable my-awesome-plugin

# 卸载插件
anythingllm-plugin uninstall my-awesome-plugin

最佳实践

性能优化

  1. 异步处理: 使用异步操作避免阻塞主线程
  2. 缓存机制: 实现适当的缓存策略
  3. 资源管理: 及时释放不需要的资源
  4. 批量处理: 对大量数据进行批量处理

安全考虑

  1. 输入验证: 验证所有用户输入
  2. 权限检查: 实施适当的权限控制
  3. 数据加密: 加密敏感数据
  4. 错误处理: 避免泄露敏感信息

代码质量

  1. 代码规范: 遵循 ESLint 和 Prettier 规则
  2. 测试覆盖: 保持高测试覆盖率
  3. 文档完整: 编写完整的 API 文档
  4. 版本管理: 使用语义化版本控制

插件示例

天气查询插件

javascript
class WeatherPlugin extends BasePlugin {
  constructor(config) {
    super(config);
    this.name = 'weather-plugin';
    this.apiKey = config.weatherApiKey;
  }

  registerEventHandlers() {
    this.on('chat.message', this.handleWeatherQuery.bind(this));
  }

  async handleWeatherQuery(event) {
    const { message } = event.data;
    
    if (message.includes('天气')) {
      const location = this.extractLocation(message);
      const weather = await this.getWeather(location);
      
      return {
        response: `${location}的天气: ${weather.description},温度 ${weather.temperature}°C`,
        handled: true
      };
    }
    
    return { handled: false };
  }

  async getWeather(location) {
    const response = await fetch(
      `https://api.openweathermap.org/data/2.5/weather?q=${location}&appid=${this.apiKey}&units=metric&lang=zh`
    );
    const data = await response.json();
    
    return {
      description: data.weather[0].description,
      temperature: Math.round(data.main.temp)
    };
  }

  extractLocation(message) {
    // 简单的位置提取逻辑
    const match = message.match(/(.+?)的天气/);
    return match ? match[1] : '北京';
  }
}

文档摘要插件

javascript
class DocumentSummaryPlugin extends BasePlugin {
  constructor(config) {
    super(config);
    this.name = 'document-summary-plugin';
  }

  registerEventHandlers() {
    this.on('document.processed', this.generateSummary.bind(this));
  }

  async generateSummary(event) {
    const { document } = event.data;
    
    try {
      const content = await this.getDocumentContent(document.id);
      const summary = await this.createSummary(content);
      
      await this.saveDocumentSummary(document.id, summary);
      
      this.emit('summary.generated', {
        documentId: document.id,
        summary
      });
    } catch (error) {
      console.error('生成摘要失败:', error);
    }
  }

  async createSummary(content) {
    const llm = this.getLLMProvider();
    
    const response = await llm.chat([
      {
        role: 'system',
        content: '请为以下文档生成一个简洁的摘要,突出主要内容和关键点。'
      },
      {
        role: 'user',
        content: content.slice(0, 4000) // 限制输入长度
      }
    ]);
    
    return response.content;
  }
}

社区和支持

插件市场

  • 浏览官方插件市场
  • 分享您的插件
  • 发现社区贡献的插件

开发者资源

获取帮助


开始创建您的第一个 AnythingLLM 插件,为平台添加独特的功能!

AnythingLLM 是一个功能强大的开源 AI 知识管理平台,支持多种 LLM 模型,让您轻松构建智能对话系统和知识库。