乐于分享
好东西不私藏

第四篇:【插件篇】插件系统深度解析:设计哲学与实现机制

第四篇:【插件篇】插件系统深度解析:设计哲学与实现机制

第四篇:【插件篇】插件系统深度解析:设计哲学与实现机制

4.1 章节概述

在前面的文章中,我们已经了解了QwenPaw的整体架构和Agent核心实现。本章将深入剖析QwenPaw的插件系统设计。QwenPaw的插件系统是其扩展性的核心所在,也是近年来GitHub上讨论最热烈的话题之一。本章将系统解析插件机制的设计哲学、整体架构、核心模块源码、当前支持的插件类型、从零开发实践,以及未来的扩展方向。

通过阅读本章,您将掌握:

插件系统的四大设计哲学及其背后的技术原理
插件的发现、加载、注册、执行完整流程
Provider、Hook、Command三大插件类型的实现细节
从零开发一个完整插件的实践指南
QwenPaw未来插件扩展的规划路线图

4.2 设计哲学:四大核心原则

QwenPaw的插件系统设计遵循四大核心原则:Duck Typing(鸭子类型)Convention over Configuration(约定优于配置)Isolation(隔离性)Safety First(安全优先)。这四大原则相互配合,共同构成了QwenPaw插件系统的设计基础。

4.2.1 Duck Typing:灵活的对象识别

在传统的面向对象设计中,通常需要通过继承某个基类或实现某个接口来表明身份。但QwenPaw采用了Duck Typing的设计哲学——不要求插件必须继承严格的PluginBase抽象基类。只要一个对象实现了特定的方法(对于QwenPaw来说是register(api)方法),它就可以被视为合法插件。

这种设计的优势在于:

# 传统模式:需要继承基类
class OldStylePlugin(PluginBase):
def register(self, api):
pass

# QwenPaw模式:只需实现所需方法
class MyPlugin:
def register(self, api: PluginApi):
# 在这里注册你的能力
pass

# 两者都可以被QwenPaw识别为有效插件
plugin = MyPlugin()

这种设计带来了极大的灵活性:

无需继承约束:插件可以使用任何类作为基类,不受插件框架的限制
简化迁移:已有的Python模块可以通过简单地添加register方法成为QwenPaw插件
类型无关:只要行为符合预期,具体类型并不重要

4.2.2 Convention over Configuration:约定优于配置

QwenPaw插件系统遵循"约定优于配置"的原则,通过目录结构和文件命名来自动识别和加载插件,而不是依赖复杂的配置文件:

// 插件目录结构
~/.qwenpaw/plugins/
├── my-provider/ # 插件目录
│ ├── plugin.json # ✅ 包含plugin.json即被识别为插件
│ └── plugin.py # ✅ 模块级导出的plugin对象即为入口
└── my-hook/
├── plugin.json # ✅ 发现入口
└── plugin.py # ✅ 注册点

核心约定规则

约定 说明 优先级说明
目录名即插件ID my-provider目录对应插件ID my-provider 无需配置
plugin.json识别 包含此文件即被扫描 目录是有效插件
plugin.py入口 模块级导出plugin对象 插件行为定义
优先级数字 数值越小越先执行 100为默认值

plugin.json示例

{
"id": "my-first-plugin",
"name": "My First Plugin",
"version": "0.1.0",
"description": "A minimal QwenPaw plugin",
"author": "Your Name",
"entry_point": "plugin.py",
"dependencies": [],
"min_version": "0.1.0"
}

4.2.3 Isolation:模块隔离性

每个QwenPaw插件都被作为独立的Python模块加载,底层使用importlib.util.spec_from_file_location实现动态导入。这种设计带来了以下保障:

# 插件隔离加载的实现示意
def load_plugin(plugin_path: Path) -> PluginRecord:
# 1. 创建模块规范
spec = importlib.util.spec_from_file_location(
f"qwenpaw_plugin_{plugin_path.name}",
plugin_path / "plugin.py"
)

# 2. 创建模块
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)

# 3. 设置包信息(支持相对导入)
module.__package__ = f"qwenpaw_plugins.{plugin_path.name}"
module.__path__ = [str(plugin_path)]

# 4. 获取插件入口
plugin = module.plugin

return PluginRecord(
manifest=manifest,
module=module,
instance=plugin
)

隔离性的核心价值

不污染全局sys.path:插件的文件操作不会影响其他插件或主程序
支持相对导入:插件内部可以使用相对导入组织代码
清晰的模块边界:每个插件的命名空间相互独立
便于卸载和更新:插件可以独立于其他插件进行热更新

4.2.4 Safety First:安全第一

插件的安装和卸载必须在QwenPaw离线状态下进行,这是出于安全考虑的强制性要求:

class PluginManager:
"""插件管理器 - 安全优先原则"""

async def install_plugin(self, plugin_path: Path) -> None:
# 1. 检查QwenPaw是否处于离线状态
if not self.is_qwenpaw_offline():
raise SafetyException(
"插件安装必须在QwenPaw离线状态下进行。"
"请先停止所有运行中的QwenPaw实例。"
)

# 2. 安全扫描
scanner = SecurityScanner()
if scanner.has_prompt_injection(plugin_path):
raise SafetyException("检测到提示词注入风险")
if scanner.has_command_injection(plugin_path):
raise SafetyException("检测到命令注入风险")
if scanner.has_hardcoded_secrets(plugin_path):
raise SafetyException("检测到硬编码密钥")

# 3. 验证依赖
manifest = self._load_manifest(plugin_path)
self._verify_dependencies(manifest.dependencies)

# 4. 执行安装
await self._do_install(plugin_path, manifest)

async def uninstall_plugin(self, plugin_id: str) -> None:
# 同样需要离线状态检查
if not self.is_qwenpaw_offline():
raise SafetyException(
"插件卸载必须在QwenPaw离线状态下进行。"
)
# 执行卸载逻辑...

离线操作的原因

避免运行时状态不一致
防止热更新导致的不完整状态
确保所有钩子都被正确清理
防止并发访问导致的竞争条件

4.3 整体架构:分层设计

4.3.1 架构图解

QwenPaw插件系统的架构可以表示为以下层次:

┌─────────────────────────────────────────────────────────────┐
│ QwenPaw Application │
│ ┌─────────────┐ ┌──────────────┐ ┌───────────────┐ │
│ │ FastAPI │ │ AgentRunner │ │ ProviderMgr │ │
│ │ (lifespan) │ │ │ │ │ │
│ └──────┬──────┘ └──────┬───────┘ └───────┬───────┘ │
│ │ │ │ │
│ ┌──────▼──────────────────▼───────────────────▼───────┐ │
│ │ Plugin System Layer │ │
│ │ │ │
│ │ PluginLoader ──► PluginRegistry ◄── PluginApi │ │
│ │ │ │ │ │ │ │
│ │ │ Providers │ Hooks Commands │ │
│ │ │ │ │ │ │ │
│ │ ┌────▼────┐ ┌───▼────▼────▼──┐ ┌───▼──────┐ │ │
│ │ │discover │ │RegistrationDB │ │PluginApi │ │ │
│ │ │& load │ │ (Singleton) │ │(per-plug)│ │ │
│ │ └─────────┘ └────────────────┘ └──────────┘ │ │
│ └───────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘

4.3.2 核心组件说明

组件 类型 职责
PluginLoader 负责插件的发现、扫描、加载
PluginRegistry 单例 中央注册表,存储所有已注册的扩展
PluginApi 接口 每个插件独享的API对象,用于注册能力
PluginManifest 数据类 插件清单,描述插件元数据
RegistrationDB 数据结构 存储Provider/Hook/Command注册信息

4.3.3 关键数据流

QwenPaw插件系统的数据流遵循以下模式:

1. 启动发现 (Startup Discovery)
PluginLoader.discover_plugins()
└─ scan ~/.qwenpaw/plugins/ 目录
└─ locate subfolders containing plugin.json
└─ parse them into PluginManifest

2. 动态加载 (Dynamic Loading)
PluginLoader.load_plugin(plugin_id)
├─ dynamically import module via importlib
├─ configure __package__ / __path__ for relative imports
├─ fetch module.plugin
├─ create PluginApi(plugin_id, config, manifest)
├─ invoke plugin.register(api) # sync or async
└─ return PluginRecord

3. 注册 (Registration)
PluginApi.register_provider(...)
PluginApi.register_startup_hook(...)
PluginApi.register_shutdown_hook(...)
PluginApi.register_control_command(...)
└─ all registrations stored in PluginRegistry

4. 集成 (Integration)
Application reads from PluginRegistry
└─ injects providers into ProviderManager
└─ registers hooks into lifecycle
└─ registers commands into CLI

5. 执行 (Execution)
Startup hooks execute in priority order (0 → 200)
└─ Environment setup
└─ SDK initialization
└─ Telemetry startup

4.4 核心模块源码解析

4.4.1 PluginManifest - 插件清单

from dataclasses import dataclass, field
from typing import List, Dict, Any

@dataclass
class PluginManifest:
"""插件清单数据类"""

id: str # 插件唯一标识
name: str # 插件显示名称
version: str # 插件版本号
description: str = "" # 插件描述
author: str = "" # 作者信息
entry_point: str = "plugin.py" # 入口文件(默认plugin.py)
dependencies: List[str] = field(default_factory=list) # 依赖列表
min_version: str = "0.1.0" # 最低QwenPaw版本要求
meta: Dict[str, Any] = field(default_factory=dict) # 元数据(扩展字段)

def to_json(self) -> str:
"""序列化为JSON"""
import json
return json.dumps(asdict(self), indent=2)

@classmethod
def from_json(cls, json_str: str) -> 'PluginManifest':
"""从JSON反序列化"""
import json
data = json.loads(json_str)
return cls(**data)

def validate(self) -> List[str]:
"""验证清单完整性,返回错误列表"""
errors = []
if not self.id:
errors.append("插件ID不能为空")
if not self.name:
errors.append("插件名称不能为空")
if not self.version:
errors.append("版本号不能为空")
if not self._is_valid_version(self.version):
errors.append(f"无效的版本号格式: {self.version}")
return errors

def _is_valid_version(self, version: str) -> bool:
"""验证版本号格式 (x.y.z)"""
import re
return bool(re.match(r'^\d+\.\d+\.\d+$', version))

4.4.2 PluginLoader - 发现与动态加载

class PluginLoader:
"""插件加载器 - 负责发现和动态加载插件"""

def __init__(self, plugins_dir: Path):
self.plugins_dir = plugins_dir
self._loaded_plugins: Dict[str, PluginRecord] = {}

def discover_plugins(self) -> List[PluginManifest]:
"""发现所有可用的插件"""
discovered = []

if not self.plugins_dir.exists():
logger.warning(f"插件目录不存在: {self.plugins_dir}")
return discovered

for entry in self.plugins_dir.iterdir():
if not entry.is_dir():
continue

plugin_json = entry / "plugin.json"
if not plugin_json.exists():
continue # 没有plugin.json,跳过

try:
manifest = self._parse_manifest(plugin_json)
# 检查版本兼容性
if self._is_version_compatible(manifest.min_version):
discovered.append(manifest)
logger.info(f"发现插件: {manifest.id} v{manifest.version}")
except Exception as e:
logger.error(f"解析插件清单失败 {plugin_json}: {e}")

return discovered

async def load_plugin(
self,
manifest: PluginManifest,
config: Dict[str, Any]
) -> PluginRecord:
"""加载单个插件"""

plugin_path = self.plugins_dir / manifest.id
plugin_py = plugin_path / manifest.entry_point

if not plugin_py.exists():
raise PluginLoadError(
f"插件入口文件不存在: {plugin_py}"
)

# 动态导入模块
spec = importlib.util.spec_from_file_location(
f"qwenpaw_plugins.{manifest.id}",
plugin_py
)

module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)

# 设置包信息以支持相对导入
module.__package__ = f"qwenpaw_plugins.{manifest.id}"
module.__path__ = [str(plugin_path)]
module.__file__ = str(plugin_py)

# 获取插件入口
if not hasattr(module, 'plugin'):
raise PluginLoadError(
f"插件模块缺少 'plugin' 属性: {manifest.id}"
)

plugin_instance = module.plugin

# 创建插件API
api = PluginApi(
plugin_id=manifest.id,
config=config.get(manifest.id, {}),
manifest=manifest
)

# 调用注册方法(支持异步)
if asyncio.iscoroutinefunction(plugin_instance.register):
await plugin_instance.register(api)
else:
plugin_instance.register(api)

# 创建并存储插件记录
record = PluginRecord(
manifest=manifest,
module=module,
instance=plugin_instance,
api=api,
loaded_at=datetime.now()
)

self._loaded_plugins[manifest.id] = record
logger.info(f"插件加载成功: {manifest.id}")

return record

def _parse_manifest(self, json_path: Path) -> PluginManifest:
"""解析插件清单JSON"""
with open(json_path, 'r', encoding='utf-8') as f:
data = json.load(f)
return PluginManifest(**data)

4.4.3 PluginRegistry - 中央注册表

PluginRegistry采用单例模式实现,是整个插件系统的中央协调器:

from threading import Lock

class PluginRegistry:
"""插件注册表 - 单例模式"""

_instance = None
_lock = Lock()

def __new__(cls):
if cls._instance is None:
with cls._lock:
if cls._instance is None:
cls._instance = super().__new__(cls)
cls._instance._initialized = False
return cls._instance

def __init__(self):
if self._initialized:
return

# 存储四类注册信息
self._providers: Dict[str, ProviderRegistration] = {}
self._startup_hooks: List[HookRegistration] = []
self._shutdown_hooks: List[HookRegistration] = []
self._control_commands: List[ControlCommandRegistration] = []

self._initialized = True

# ============ Provider 注册 ============

def register_provider(
self,
provider_id: str,
provider_class: Type[ProviderBase],
label: str,
base_url: str,
require_api_key: bool = True,
config_schema: Optional[Dict] = None
) -> None:
"""注册模型提供商"""
registration = ProviderRegistration(
id=provider_id,
provider_class=provider_class,
label=label,
base_url=base_url,
require_api_key=require_api_key,
config_schema=config_schema or {}
)
self._providers[provider_id] = registration
logger.info(f"提供商注册: {provider_id}")

def get_provider(self, provider_id: str) -> Optional[ProviderRegistration]:
"""获取提供商注册信息"""
return self._providers.get(provider_id)

def list_providers(self) -> List[ProviderRegistration]:
"""列出所有已注册的提供商"""
return list(self._providers.values())

# ============ Hook 注册 ============

def register_startup_hook(
self,
hook_name: str,
callback: Callable,
priority: int = 100,
description: str = ""
) -> None:
"""注册启动钩子"""
registration = HookRegistration(
name=hook_name,
callback=callback,
priority=priority,
description=description,
hook_type="startup"
)
self._startup_hooks.append(registration)
# 按优先级排序(数字越小越先执行)
self._startup_hooks.sort(key=lambda h: h.priority)

def register_shutdown_hook(
self,
hook_name: str,
callback: Callable,
priority: int = 100,
description: str = ""
) -> None:
"""注册关闭钩子"""
registration = HookRegistration(
name=hook_name,
callback=callback,
priority=priority,
description=description,
hook_type="shutdown"
)
self._shutdown_hooks.append(registration)
self._shutdown_hooks.sort(key=lambda h: h.priority)

async def execute_startup_hooks(self) -> None:
"""执行所有启动钩子"""
for hook in self._startup_hooks:
try:
logger.info(f"执行启动钩子: {hook.name}")
if asyncio.iscoroutinefunction(hook.callback):
await hook.callback()
else:
hook.callback()
except Exception as e:
logger.error(f"启动钩子执行失败 {hook.name}: {e}")

async def execute_shutdown_hooks(self) -> None:
"""执行所有关闭钩子"""
for hook in self._shutdown_hooks:
try:
logger.info(f"执行关闭钩子: {hook.name}")
if asyncio.iscoroutinefunction(hook.callback):
await hook.callback()
else:
hook.callback()
except Exception as e:
logger.error(f"关闭钩子执行失败 {hook.name}: {e}")

# ============ Command 注册 ============

def register_control_command(
self,
command_name: str,
handler: Type[BaseControlCommandHandler],
description: str = "",
aliases: List[str] = None
) -> None:
"""注册控制命令"""
registration = ControlCommandRegistration(
name=command_name,
handler=handler,
description=description,
aliases=aliases or []
)
self._control_commands.append(registration)

def get_command_handler(self, command_name: str) -> Optional[Type]:
"""获取命令处理器"""
for cmd in self._control_commands:
if cmd.name == command_name or command_name in cmd.aliases:
return cmd.handler
return None

4.4.4 PluginApi - 开发者接口

PluginApi是插件开发者与QwenPaw交互的主要接口,每个插件在注册时都会获得一个独享的PluginApi实例:

@dataclass
class PluginApi:
"""插件API - 插件开发者的接口"""

plugin_id: str
config: Dict[str, Any]
manifest: PluginManifest

def register_provider(
self,
provider_id: str,
provider_class: Type[ProviderBase],
label: str,
base_url: str,
require_api_key: bool = True,
config_schema: Optional[Dict] = None
) -> None:
"""注册自定义模型提供商

适用场景:
- 企业内部模型服务
- 私有化部署模型
- 自研推理服务

Example:
api.register_provider(
provider_id="my-llm",
provider_class=MyLLMProvider,
label="My LLM Service",
base_url="https://api.internal.company.com/v1",
require_api_key=True,
)
"""

registry = PluginRegistry()
registry.register_provider(
provider_id=provider_id,
provider_class=provider_class,
label=label,
base_url=base_url,
require_api_key=require_api_key,
config_schema=config_schema
)

def register_startup_hook(
self,
hook_name: str,
callback: Callable,
priority: int = 100,
description: str = ""
) -> None:
"""注册启动钩子

适用场景:
- 监控初始化
- 环境检查
- 第三方SDK启动
- 日志与上报集成

优先级说明:
- priority=0 → 最早执行
- priority=100 → 默认值
- priority=200 → 最晚执行
"""

registry = PluginRegistry()
registry.register_startup_hook(
hook_name=hook_name,
callback=callback,
priority=priority,
description=description
)

def register_shutdown_hook(
self,
hook_name: str,
callback: Callable,
priority: int = 100,
description: str = ""
) -> None:
"""注册关闭钩子"""
registry = PluginRegistry()
registry.register_shutdown_hook(
hook_name=hook_name,
callback=callback,
priority=priority,
description=description
)

def register_control_command(
self,
command_name: str,
handler: Type[BaseControlCommandHandler],
description: str = "",
aliases: Optional[List[str]] = None
) -> None:
"""注册斜杠命令

两种实现路径:
- Path A: BaseControlCommandHandler(推荐)
- Path B: Monkey-patch查询重写
"""

registry = PluginRegistry()
registry.register_control_command(
command_name=command_name,
handler=handler,
description=description,
aliases=aliases
)

@property
def runtime(self) -> 'RuntimeHelpers':
"""访问运行时辅助能力"""
return RuntimeHelpers(plugin_id=self.plugin_id)


@dataclass
class RuntimeHelpers:
"""运行时辅助能力"""

plugin_id: str

def get_provider(self, provider_id: str) -> Optional[ProviderBase]:
"""获取某个Provider实例"""
registry = PluginRegistry()
reg = registry.get_provider(provider_id)
if reg:
return reg.provider_class()
return None

def list_providers(self) -> List[str]:
"""列出所有可用Provider ID"""
registry = PluginRegistry()
return [p.id for p in registry.list_providers()]

def log_info(self, message: str) -> None:
"""统一日志接口 - INFO级别"""
logger.info(f"[{self.plugin_id}] {message}")

def log_error(self, message: str, exc: Optional[Exception] = None) -> None:
"""统一日志接口 - ERROR级别"""
if exc:
logger.error(f"[{self.plugin_id}] {message}", exc_info=exc)
else:
logger.error(f"[{self.plugin_id}] {message}")

def log_debug(self, message: str) -> None:
"""统一日志接口 - DEBUG级别"""
logger.debug(f"[{self.plugin_id}] {message}")

4.5 当前三大插件类型

4.5.1 Provider Plugins - 提供商插件

Provider插件用于注册自定义的大语言模型提供商:

# my-provider/plugin.py

from qwenpaw.plugins.api import PluginApi
from qwenpaw.providers.base import ProviderBase
from my_provider.chat_model import MyChatModel

class MyLLMProvider(ProviderBase):
"""自定义LLM提供商"""

def __init__(self, config: Dict):
self.api_key = config.get('api_key')
self.base_url = config.get('base_url', 'https://api.example.com')

async def create_chat_model(self, model_config: ModelConfig) -> ChatModelBase:
return MyChatModel(
api_key=self.api_key,
base_url=self.base_url,
model_name=model_config.model_name,
temperature=model_config.temperature,
)

def get_available_models(self) -> List[str]:
return ["my-model-v1", "my-model-v2", "my-model-gpt"]

def register(api: PluginApi):
api.register_provider(
provider_id="my-llm",
provider_class=MyLLMProvider,
label="My LLM Service",
base_url="https://api.internal.company.com/v1",
require_api_key=True,
config_schema={
"api_key": {"type": "string", "required": True},
"base_url": {"type": "string", "required": False},
}
)

# 模块级入口
plugin = type('Plugin', (), {'register': register})()

4.5.2 Hook Plugins - 钩子插件

Hook插件用于在应用启动或关闭时执行自定义逻辑:

# my-hook/plugin.py

from qwenpaw.plugins.api import PluginApi
import logging

logger = logging.getLogger(__name__)

class MyHookPlugin:
def register(self, api: PluginApi):
logger.info(f"注册插件: {api.plugin_id}")

# 注册启动钩子
api.register_startup_hook(
hook_name="my_init",
callback=self._on_startup,
priority=100,
description="初始化自定义服务"
)

# 注册关闭钩子
api.register_shutdown_hook(
hook_name="my_cleanup",
callback=self._on_shutdown,
priority=100,
description="清理自定义资源"
)

async def _on_startup(self) -> None:
"""启动时的初始化逻辑"""
logger.info("执行启动初始化...")

# 初始化自定义服务
await self._init_my_service()

# 连接外部系统
await self._connect_external_systems()

logger.info("启动初始化完成")

async def _on_shutdown(self) -> None:
"""关闭时的清理逻辑"""
logger.info("执行关闭清理...")

# 断开外部连接
await self._disconnect_external_systems()

# 保存状态
await self._save_state()

logger.info("关闭清理完成")

async def _init_my_service(self) -> None:
pass # 实现自定义初始化逻辑

async def _connect_external_systems(self) -> None:
pass # 实现外部系统连接

async def _disconnect_external_systems(self) -> None:
pass # 实现外部系统断开

async def _save_state(self) -> None:
pass # 实现状态保存

plugin = MyHookPlugin()

4.5.3 Command Plugins - 命令插件

Command插件用于注册斜杠命令(slash commands):

# my-command/plugin.py

from qwenpaw.plugins.api import PluginApi
from qwenpaw.agents.command_handler import BaseControlCommandHandler

class MyCommandHandler(BaseControlCommandHandler):
"""自定义命令处理器"""

command_name = "mycommand"
description = "执行自定义命令"

async def handle(self, args: str, context: CommandContext) -> str:
"""处理命令"""
# 解析参数
params = self._parse_args(args)

#执行业务逻辑
result = await self._do_something(params)

return result

def _parse_args(self, args: str) -> Dict:
"""解析命令参数"""
parts = args.split()
return {
'action': parts[0] if parts else None,
'params': parts[1:]
}

async def _do_something(self, params: Dict) -> str:
"""执行业务逻辑"""
return f"执行完成: {params}"

def register(api: PluginApi):
api.register_control_command(
command_name="mycommand",
handler=MyCommandHandler,
description="我的自定义命令",
aliases=["mc", "my"]
)

plugin = type('Plugin', (), {'register': register})()

4.6 动手实践:从零开发一个插件

4.6.1 创建最小可行插件

让我们创建一个最简单的"Hello World"插件:

目录结构

~/.qwenpaw/plugins/
└── my-first-plugin/
├── plugin.json
└── plugin.py

plugin.json

{
"id": "my-first-plugin",
"name": "My First Plugin",
"version": "0.1.0",
"description": "A minimal QwenPaw plugin demonstrating the basics",
"author": "Your Name",
"entry_point": "plugin.py",
"dependencies": [],
"min_version": "1.0.0"
}

plugin.py

"""
QwenPaw插件示例:Hello World

这个插件演示了QwenPaw插件的基本结构。
"""


from qwenpaw.plugins.api import PluginApi
import logging

logger = logging.getLogger(__name__)

class MyFirstPlugin:
"""我的第一个QwenPaw插件"""

def register(self, api: PluginApi) -> None:
"""插件注册方法

当QwenPaw加载此插件时会调用此方法。
在这里注册插件的各种能力。
"""

logger.info(f"Hello from {api.plugin_id}!")

# 注册一个启动钩子
api.register_startup_hook(
hook_name="hello_world",
callback=self._on_startup,
priority=100,
description="打印启动消息"
)

# 注册一个关闭钩子
api.register_shutdown_hook(
hook_name="goodbye_world",
callback=self._on_shutdown,
priority=100,
description="打印关闭消息"
)

# 访问配置
custom_setting = api.config.get('custom_setting', 'default_value')
logger.info(f"Custom setting: {custom_setting}")

def _on_startup(self) -> None:
"""启动时的回调"""
logger.info("🎉 QwenPaw已启动!我的插件正在运行。")

def _on_shutdown(self) -> None:
"""关闭时的回调"""
logger.info("👋 QwenPaw正在关闭。我的插件完成工作。")


# 模块级入口对象 - QwenPaw会查找这个
plugin = MyFirstPlugin()

4.6.2 配置并使用插件

~/.qwenpaw/config.json中配置插件:

{
"plugins": {
"my-first-plugin": {
"custom_setting": "custom_value"
}
}
}

4.6.3 异步注册支持

QwenPaw的插件支持异步注册,这对于需要执行异步初始化操作的插件非常有用:

class AsyncPlugin:
"""异步注册示例"""

async def register(self, api: PluginApi) -> None:
"""异步注册方法

如果插件的注册逻辑需要执行异步操作,
可以将register方法定义为async。
"""

# 异步获取远程配置
remote_config = await self._fetch_remote_config()

# 根据远程配置注册提供商
api.register_provider(
provider_id="my-async-provider",
provider_class=MyAsyncProvider,
label="Async Provider",
base_url=remote_config['base_url'],
)

# 注册启动钩子
api.register_startup_hook(
hook_name="async_init",
callback=self._async_startup,
priority=50, # 较高优先级,提前执行
)

async def _fetch_remote_config(self) -> Dict:
"""模拟从远程获取配置"""
import asyncio
await asyncio.sleep(0.1) # 模拟网络延迟
return {
'base_url': 'https://api.async-provider.example.com'
}

async def _async_startup(self) -> None:
"""异步启动逻辑"""
import asyncio
await asyncio.sleep(0.1) # 模拟初始化
logging.getLogger(__name__).info("异步启动完成")

plugin = AsyncPlugin()

4.6.4 完整的Provider插件示例

以下是创建一个完整的自定义模型提供商的完整示例:

# custom-provider/plugin.py

import logging
from typing import List, Dict, Any
from qwenpaw.plugins.api import PluginApi
from qwenpaw.providers.base import ProviderBase
from qwenpaw.agents.model import ChatModelBase

logger = logging.getLogger(__name__)

# ============ 自定义模型类 ============

class CustomChatModel(ChatModelBase):
"""自定义聊天模型实现"""

def __init__(
self,
api_key: str,
base_url: str,
model_name: str,
temperature: float = 0.7
):
self.api_key = api_key
self.base_url = base_url
self.model_name = model_name
self.temperature = temperature

async def chat(
self,
messages: List[Dict],
**kwargs
) -> Dict:
"""调用自定义API"""
import aiohttp

headers = {
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json"
}

payload = {
"model": self.model_name,
"messages": messages,
"temperature": kwargs.get('temperature', self.temperature)
}

async with aiohttp.ClientSession() as session:
async with session.post(
f"{self.base_url}/chat",
json=payload,
headers=headers
) as response:
if response.status != 200:
error = await response.text()
raise Exception(f"API调用失败: {error}")

return await response.json()

# ============ 自定义提供商类 ============

class CustomLLMProvider(ProviderBase):
"""自定义LLM提供商"""

def __init__(self, config: Dict[str, Any]):
self.api_key = config.get('api_key')
self.base_url = config.get('base_url', 'https://api.example.com/v1')

async def create_chat_model(
self,
model_config: 'ModelConfig'
) -> ChatModelBase:
"""创建聊天模型实例"""
return CustomChatModel(
api_key=self.api_key,
base_url=self.base_url,
model_name=model_config.model_name,
temperature=model_config.temperature
)

def get_available_models(self) -> List[str]:
"""获取可用模型列表"""
return [
"custom-model-v1",
"custom-model-v2",
"custom-model-instruct"
]

# ============ 插件入口 ============

class CustomProviderPlugin:
"""自定义提供商插件"""

def register(self, api: PluginApi) -> None:
"""注册插件"""
logger.info("注册自定义模型提供商...")

# 注册提供商
api.register_provider(
provider_id="custom-llm",
provider_class=CustomLLMProvider,
label="Custom LLM",
base_url="https://api.example.com/v1",
require_api_key=True,
config_schema={
"api_key": {
"type": "string",
"required": True,
"description": "API密钥"
},
"base_url": {
"type": "string",
"required": False,
"description": "API基础URL"
}
}
)

# 注册启动钩子(用于日志记录)
api.register_startup_hook(
hook_name="log_provider_init",
callback=self._log_init,
priority=100
)

def _log_init(self) -> None:
"""记录初始化"""
logger.info("Custom LLM Provider已初始化")


plugin = CustomProviderPlugin()

4.7 未来插件点规划

QwenPaw的插件系统仍在不断演进。根据社区讨论,未来将支持更多扩展点:

4.7.1 路线图概览

扩展点 优先级 原因
Tool Plugins 🔴 P0 企业工具接入缺口最大,社区需求最强
Middleware Hooks 🟡 P1 解耦monkey patch,提升安全审计能力
Event Bus 🟢 P2 当前可通过config.json共享替代
UI Extensions 🔵 P3 前端改动大,且Console/企业微信使用更多

4.7.2 Tool Plugins - 工具插件

允许插件直接向Agent注册工具,打通插件系统与Agent工具生态之间的壁垒:

# 未来API设计(预览)
class FuturePlugin:
def register(self, api: PluginApi) -> None:
# 注册自定义工具
api.register_tool(
tool_id="my_custom_tool",
tool_class=MyCustomTool,
description="执行自定义操作",
parameters=[
{"name": "input", "type": "string", "required": True}
]
)

4.7.3 Middleware / Pipeline Hooks - 中间件与处理链

提供请求/响应处理链的钩子:

# 未来API设计(预览)
class MiddlewarePlugin:
def register(self, api: PluginApi) -> None:
# 注册前置钩子
api.register_middleware_hook(
stage="pre_query",
callback=self._preprocess_query,
priority=100
)

# 注册后置钩子
api.register_middleware_hook(
stage="post_response",
callback=self._postprocess_response,
priority=100
)

# 钩子阶段说明
MIDDLEWARE_STAGES = {
"pre_query": "用户输入到达Agent之前",
"post_query": "Agent生成输出之后",
"pre_tool_call": "工具执行之前",
"post_tool_call": "工具执行之后",
"on_error": "发生错误时",
"on_memory_update": "记忆/上下文更新时"
}

4.7.4 Event Bus - 事件总线

统一的事件发布/订阅机制,让插件之间产生更自然的协作:

# 未来API设计(预览)
class EventBusPlugin:
def register(self, api: PluginApi) -> None:
# 订阅事件
api.subscribe_event(
event_type="tool_executed",
handler=self._on_tool_executed
)

# 发布事件
api.publish_event(
event_type="custom_event",
data={"key": "value"}
)

4.7.5 UI Extensions - UI扩展点

允许插件向Web UI注入扩展能力:

# 未来API设计(预览)
class UIExtensionPlugin:
def register(self, api: PluginApi) -> None:
api.register_ui_extension(
location="sidebar",
component=MySidebarComponent,
title="My Extension"
)

4.8 本章小结

本章深入分析了QwenPaw的插件系统设计,主要涵盖以下内容:

核心要点回顾

设计哲学 技术实现 核心价值
Duck Typing 仅需实现register方法 极大灵活性,降低门槛
约定优于配置 plugin.json + 目录结构 零配置扩展
模块隔离 importlib动态加载 安全可靠,可热更新
安全优先 离线状态强制检查 防止状态不一致

三大插件类型

类型 用途 注册方法
Provider 注册自定义模型 register_provider()
Hook 生命周期管理 register_startup_hook() / register_shutdown_hook()
Command 斜杠命令 register_control_command()

开发实践要点

1.
最小插件:只需实现register方法并导出plugin对象
2.
配置访问:通过api.config获取用户配置
3.
异步支持:注册方法可以是同步或异步
4.
运行时辅助:通过api.runtime访问运行时能力

源码位置索引

模块 文件路径 功能
PluginManifest src/qwenpaw/plugins/manifest.py 插件清单定义
PluginLoader src/qwenpaw/plugins/loader.py 插件发现与加载
PluginRegistry src/qwenpaw/plugins/registry.py 中央注册表
PluginApi src/qwenpaw/plugins/api.py 开发者接口

预告:下一篇文章将进入【安全篇】,我们将详细分析QwenPaw的多层安全防护体系,包括工具守卫、文件访问控制、注入防护等核心机制。敬请期待!


往期回顾

【开篇】QwenPaw:一款值得深入研究的开源AI Agent框架
【架构篇】QwenPaw整体架构设计解析
【Agent篇】ReAct代理核心实现与模型工厂

参考文献

QwenPaw插件系统讨论:https://github.com/agentscope-ai/QwenPaw/discussions/3384
QwenPaw源码:https://github.com/agentscope-ai/QwenPaw
基本 文件 流程 错误 SQL 调试
  1. 请求信息 : 2026-05-12 22:45:32 HTTP/1.1 GET : https://www.yeyulingfeng.com/a/614902.html
  2. 运行时间 : 0.220970s [ 吞吐率:4.53req/s ] 内存消耗:4,875.63kb 文件加载:145
  3. 缓存信息 : 0 reads,0 writes
  4. 会话信息 : SESSION_ID=678b94576af4c85b4c6d0bb4201a8791
  1. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/public/index.php ( 0.79 KB )
  2. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/autoload.php ( 0.17 KB )
  3. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/composer/autoload_real.php ( 2.49 KB )
  4. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/composer/platform_check.php ( 0.90 KB )
  5. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/composer/ClassLoader.php ( 14.03 KB )
  6. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/composer/autoload_static.php ( 6.05 KB )
  7. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-helper/src/helper.php ( 8.34 KB )
  8. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-validate/src/helper.php ( 2.19 KB )
  9. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/ralouphie/getallheaders/src/getallheaders.php ( 1.60 KB )
  10. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/helper.php ( 1.47 KB )
  11. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/stubs/load_stubs.php ( 0.16 KB )
  12. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Exception.php ( 1.69 KB )
  13. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-container/src/Facade.php ( 2.71 KB )
  14. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/symfony/deprecation-contracts/function.php ( 0.99 KB )
  15. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/symfony/polyfill-mbstring/bootstrap.php ( 8.26 KB )
  16. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/symfony/polyfill-mbstring/bootstrap80.php ( 9.78 KB )
  17. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/symfony/var-dumper/Resources/functions/dump.php ( 1.49 KB )
  18. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-dumper/src/helper.php ( 0.18 KB )
  19. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/symfony/var-dumper/VarDumper.php ( 4.30 KB )
  20. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/guzzlehttp/guzzle/src/functions_include.php ( 0.16 KB )
  21. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/guzzlehttp/guzzle/src/functions.php ( 5.54 KB )
  22. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/App.php ( 15.30 KB )
  23. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-container/src/Container.php ( 15.76 KB )
  24. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/psr/container/src/ContainerInterface.php ( 1.02 KB )
  25. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/provider.php ( 0.19 KB )
  26. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Http.php ( 6.04 KB )
  27. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-helper/src/helper/Str.php ( 7.29 KB )
  28. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Env.php ( 4.68 KB )
  29. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/common.php ( 0.03 KB )
  30. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/helper.php ( 18.78 KB )
  31. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Config.php ( 5.54 KB )
  32. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/alipay.php ( 3.59 KB )
  33. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/facade/Env.php ( 1.67 KB )
  34. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/app.php ( 0.95 KB )
  35. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/cache.php ( 0.78 KB )
  36. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/console.php ( 0.23 KB )
  37. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/cookie.php ( 0.56 KB )
  38. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/database.php ( 2.48 KB )
  39. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/filesystem.php ( 0.61 KB )
  40. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/lang.php ( 0.91 KB )
  41. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/log.php ( 1.35 KB )
  42. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/middleware.php ( 0.19 KB )
  43. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/route.php ( 1.89 KB )
  44. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/session.php ( 0.57 KB )
  45. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/trace.php ( 0.34 KB )
  46. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/view.php ( 0.82 KB )
  47. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/event.php ( 0.25 KB )
  48. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Event.php ( 7.67 KB )
  49. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/service.php ( 0.13 KB )
  50. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/AppService.php ( 0.26 KB )
  51. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Service.php ( 1.64 KB )
  52. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Lang.php ( 7.35 KB )
  53. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/lang/zh-cn.php ( 13.70 KB )
  54. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/initializer/Error.php ( 3.31 KB )
  55. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/initializer/RegisterService.php ( 1.33 KB )
  56. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/services.php ( 0.14 KB )
  57. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/service/PaginatorService.php ( 1.52 KB )
  58. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/service/ValidateService.php ( 0.99 KB )
  59. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/service/ModelService.php ( 2.04 KB )
  60. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-trace/src/Service.php ( 0.77 KB )
  61. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Middleware.php ( 6.72 KB )
  62. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/initializer/BootService.php ( 0.77 KB )
  63. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/Paginator.php ( 11.86 KB )
  64. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-validate/src/Validate.php ( 63.20 KB )
  65. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/Model.php ( 23.55 KB )
  66. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/model/concern/Attribute.php ( 21.05 KB )
  67. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/model/concern/AutoWriteData.php ( 4.21 KB )
  68. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/model/concern/Conversion.php ( 6.44 KB )
  69. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/model/concern/DbConnect.php ( 5.16 KB )
  70. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/model/concern/ModelEvent.php ( 2.33 KB )
  71. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/model/concern/RelationShip.php ( 28.29 KB )
  72. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-helper/src/contract/Arrayable.php ( 0.09 KB )
  73. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-helper/src/contract/Jsonable.php ( 0.13 KB )
  74. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/model/contract/Modelable.php ( 0.09 KB )
  75. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Db.php ( 2.88 KB )
  76. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/DbManager.php ( 8.52 KB )
  77. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Log.php ( 6.28 KB )
  78. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Manager.php ( 3.92 KB )
  79. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/psr/log/src/LoggerTrait.php ( 2.69 KB )
  80. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/psr/log/src/LoggerInterface.php ( 2.71 KB )
  81. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Cache.php ( 4.92 KB )
  82. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/psr/simple-cache/src/CacheInterface.php ( 4.71 KB )
  83. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-helper/src/helper/Arr.php ( 16.63 KB )
  84. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/cache/driver/File.php ( 7.84 KB )
  85. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/cache/Driver.php ( 9.03 KB )
  86. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/contract/CacheHandlerInterface.php ( 1.99 KB )
  87. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/Request.php ( 0.09 KB )
  88. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Request.php ( 55.78 KB )
  89. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/middleware.php ( 0.25 KB )
  90. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Pipeline.php ( 2.61 KB )
  91. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-trace/src/TraceDebug.php ( 3.40 KB )
  92. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/middleware/SessionInit.php ( 1.94 KB )
  93. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Session.php ( 1.80 KB )
  94. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/session/driver/File.php ( 6.27 KB )
  95. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/contract/SessionHandlerInterface.php ( 0.87 KB )
  96. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/session/Store.php ( 7.12 KB )
  97. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Route.php ( 23.73 KB )
  98. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/route/RuleName.php ( 5.75 KB )
  99. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/route/Domain.php ( 2.53 KB )
  100. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/route/RuleGroup.php ( 22.43 KB )
  101. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/route/Rule.php ( 26.95 KB )
  102. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/route/RuleItem.php ( 9.78 KB )
  103. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/route/app.php ( 3.94 KB )
  104. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/facade/Route.php ( 4.70 KB )
  105. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/route/dispatch/Controller.php ( 4.74 KB )
  106. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/route/Dispatch.php ( 10.44 KB )
  107. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/controller/Index.php ( 9.87 KB )
  108. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/BaseController.php ( 2.05 KB )
  109. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/facade/Db.php ( 0.93 KB )
  110. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/connector/Mysql.php ( 5.44 KB )
  111. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/PDOConnection.php ( 52.47 KB )
  112. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/Connection.php ( 8.39 KB )
  113. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/ConnectionInterface.php ( 4.57 KB )
  114. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/builder/Mysql.php ( 16.58 KB )
  115. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/Builder.php ( 24.06 KB )
  116. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/BaseBuilder.php ( 27.50 KB )
  117. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/Query.php ( 15.71 KB )
  118. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/BaseQuery.php ( 45.13 KB )
  119. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/concern/TimeFieldQuery.php ( 7.43 KB )
  120. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/concern/AggregateQuery.php ( 3.26 KB )
  121. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/concern/ModelRelationQuery.php ( 20.07 KB )
  122. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/concern/ParamsBind.php ( 3.66 KB )
  123. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/concern/ResultOperation.php ( 7.01 KB )
  124. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/concern/WhereQuery.php ( 19.37 KB )
  125. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/concern/JoinAndViewQuery.php ( 7.11 KB )
  126. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/concern/TableFieldInfo.php ( 2.63 KB )
  127. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/concern/Transaction.php ( 2.77 KB )
  128. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/log/driver/File.php ( 5.96 KB )
  129. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/contract/LogHandlerInterface.php ( 0.86 KB )
  130. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/log/Channel.php ( 3.89 KB )
  131. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/event/LogRecord.php ( 1.02 KB )
  132. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-helper/src/Collection.php ( 16.47 KB )
  133. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/facade/View.php ( 1.70 KB )
  134. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/View.php ( 4.39 KB )
  135. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/controller/Es.php ( 3.30 KB )
  136. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Response.php ( 8.81 KB )
  137. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/response/View.php ( 3.29 KB )
  138. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Cookie.php ( 6.06 KB )
  139. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-view/src/Think.php ( 8.38 KB )
  140. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/contract/TemplateHandlerInterface.php ( 1.60 KB )
  141. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-template/src/Template.php ( 46.61 KB )
  142. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-template/src/template/driver/File.php ( 2.41 KB )
  143. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-template/src/template/contract/DriverInterface.php ( 0.86 KB )
  144. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/runtime/temp/c935550e3e8a3a4c27dd94e439343fdf.php ( 31.50 KB )
  145. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-trace/src/Html.php ( 4.42 KB )
  1. CONNECT:[ UseTime:0.000750s ] mysql:host=127.0.0.1;port=3306;dbname=wenku;charset=utf8mb4
  2. SHOW FULL COLUMNS FROM `fenlei` [ RunTime:0.001493s ]
  3. SELECT * FROM `fenlei` WHERE `fid` = 0 [ RunTime:0.000760s ]
  4. SELECT * FROM `fenlei` WHERE `fid` = 63 [ RunTime:0.000601s ]
  5. SHOW FULL COLUMNS FROM `set` [ RunTime:0.001326s ]
  6. SELECT * FROM `set` [ RunTime:0.000548s ]
  7. SHOW FULL COLUMNS FROM `article` [ RunTime:0.001345s ]
  8. SELECT * FROM `article` WHERE `id` = 614902 LIMIT 1 [ RunTime:0.001197s ]
  9. UPDATE `article` SET `lasttime` = 1778597133 WHERE `id` = 614902 [ RunTime:0.008836s ]
  10. SELECT * FROM `fenlei` WHERE `id` = 64 LIMIT 1 [ RunTime:0.000651s ]
  11. SELECT * FROM `article` WHERE `id` < 614902 ORDER BY `id` DESC LIMIT 1 [ RunTime:0.001095s ]
  12. SELECT * FROM `article` WHERE `id` > 614902 ORDER BY `id` ASC LIMIT 1 [ RunTime:0.000913s ]
  13. SELECT * FROM `article` WHERE `id` < 614902 ORDER BY `id` DESC LIMIT 10 [ RunTime:0.005958s ]
  14. SELECT * FROM `article` WHERE `id` < 614902 ORDER BY `id` DESC LIMIT 10,10 [ RunTime:0.001328s ]
  15. SELECT * FROM `article` WHERE `id` < 614902 ORDER BY `id` DESC LIMIT 20,10 [ RunTime:0.001583s ]
0.226221s