文章目录
-
- 📌 技术名片
-
- 💡 一个简单比喻
- 一、为什么 AI 特别需要扩展点?
- 二、架构规范
-
- 1. Interface → Plugin → Registry
-
- Interface:接口
- Registry:注册表
- Loader:加载器
- 2. 进一步配置化:让系统动态加载插件
- 3. 什么功能适合设计成扩展点?
- 三、 插件化有什么好处?
- 四、提示词落地:明确告诉 AI 如何扩展
- 五、正面产出:[miniagent](https://github.com/liupras/miniagent) 的领域插件机制
-
- 1. `DomainPlugin` 定义插件契约
- 2. `DomainRegistry` 统一管理插件
- 3. 从数据库动态加载具体实现
- 4. 新增一个领域时会发生什么?
- 总结
- 开源代码
AI 写代码很快,但也很容易出现一种典型问题:
每增加一个功能,就继续往核心代码里加 if/elif。
例如:
if domain == "legal":
...
elif domain == "finance":
...
elif domain == "medical":
...
功能少时没有问题,但随着类型不断增加,核心模块会越来越臃肿,每次新增功能都可能影响已有逻辑。
更合理的做法是提前定义:
Plugin:插件 Extension Point:扩展点
让 AI 遵循一个原则:
新增能力优先增加模块,而不是修改核心流程。
📌 技术名片
Plugin Architecture:插件化架构
把容易变化的功能拆成独立模块,通过统一接口接入系统。核心系统依赖接口,而不是依赖某个具体实现。
其中:
Extension Point:扩展点
是系统提前预留的功能接入口。
例如:
#mermaid-svg-z5CopurtwCIzF1pB{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-z5CopurtwCIzF1pB .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-z5CopurtwCIzF1pB .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-z5CopurtwCIzF1pB .error-icon{fill:#552222;}#mermaid-svg-z5CopurtwCIzF1pB .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-z5CopurtwCIzF1pB .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-z5CopurtwCIzF1pB .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-z5CopurtwCIzF1pB .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-z5CopurtwCIzF1pB .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-z5CopurtwCIzF1pB .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-z5CopurtwCIzF1pB .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-z5CopurtwCIzF1pB .marker{fill:#333333;stroke:#333333;}#mermaid-svg-z5CopurtwCIzF1pB .marker.cross{stroke:#333333;}#mermaid-svg-z5CopurtwCIzF1pB svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-z5CopurtwCIzF1pB p{margin:0;}#mermaid-svg-z5CopurtwCIzF1pB .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-z5CopurtwCIzF1pB .cluster-label text{fill:#333;}#mermaid-svg-z5CopurtwCIzF1pB .cluster-label span{color:#333;}#mermaid-svg-z5CopurtwCIzF1pB .cluster-label span p{background-color:transparent;}#mermaid-svg-z5CopurtwCIzF1pB .label text,#mermaid-svg-z5CopurtwCIzF1pB span{fill:#333;color:#333;}#mermaid-svg-z5CopurtwCIzF1pB .node rect,#mermaid-svg-z5CopurtwCIzF1pB .node circle,#mermaid-svg-z5CopurtwCIzF1pB .node ellipse,#mermaid-svg-z5CopurtwCIzF1pB .node polygon,#mermaid-svg-z5CopurtwCIzF1pB .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-z5CopurtwCIzF1pB .rough-node .label text,#mermaid-svg-z5CopurtwCIzF1pB .node .label text,#mermaid-svg-z5CopurtwCIzF1pB .image-shape .label,#mermaid-svg-z5CopurtwCIzF1pB .icon-shape .label{text-anchor:middle;}#mermaid-svg-z5CopurtwCIzF1pB .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-z5CopurtwCIzF1pB .rough-node .label,#mermaid-svg-z5CopurtwCIzF1pB .node .label,#mermaid-svg-z5CopurtwCIzF1pB .image-shape .label,#mermaid-svg-z5CopurtwCIzF1pB .icon-shape .label{text-align:center;}#mermaid-svg-z5CopurtwCIzF1pB .node.clickable{cursor:pointer;}#mermaid-svg-z5CopurtwCIzF1pB .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-z5CopurtwCIzF1pB .arrowheadPath{fill:#333333;}#mermaid-svg-z5CopurtwCIzF1pB .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-z5CopurtwCIzF1pB .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-z5CopurtwCIzF1pB .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-z5CopurtwCIzF1pB .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-z5CopurtwCIzF1pB .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-z5CopurtwCIzF1pB .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-z5CopurtwCIzF1pB .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-z5CopurtwCIzF1pB .cluster text{fill:#333;}#mermaid-svg-z5CopurtwCIzF1pB .cluster span{color:#333;}#mermaid-svg-z5CopurtwCIzF1pB div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-z5CopurtwCIzF1pB .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-z5CopurtwCIzF1pB rect.text{fill:none;stroke-width:0;}#mermaid-svg-z5CopurtwCIzF1pB .icon-shape,#mermaid-svg-z5CopurtwCIzF1pB .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-z5CopurtwCIzF1pB .icon-shape p,#mermaid-svg-z5CopurtwCIzF1pB .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-z5CopurtwCIzF1pB .icon-shape .label rect,#mermaid-svg-z5CopurtwCIzF1pB .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-z5CopurtwCIzF1pB .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-z5CopurtwCIzF1pB .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-z5CopurtwCIzF1pB :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
Core System
Extension Point
LegalPlugin
FinancePlugin
MedicalPlugin
核心系统不需要知道每个插件内部怎么实现,只需要知道:
它们都遵守同一套接口。
💡 一个简单比喻
插件化很像电脑的 USB 接口。
电脑不需要为了鼠标、键盘、U 盘分别设计不同的主机,只需要定义统一的 USB 接口。
软件架构也是一样:
核心系统定义“怎么接”,插件决定“接进来以后怎么做”。
一、为什么 AI 特别需要扩展点?
假设知识库现在支持:
general
legal
让 AI 增加一个金融领域,它最容易写成:
if domain == "general":
return process_general(document)
elif domain == "legal":
return process_legal(document)
elif domain == "finance":
return process_finance(document)
下一次增加医学领域,再继续增加一个分支。
最终:
Core Service
↓
大量 if / elif
↓
所有领域逻辑耦合在一起
更好的思路是:
Core
↓
Plugin Interface
↓
Registry
↓
Concrete Plugin
这符合:
Open-Closed Principle:开闭原则
简单来说就是:
对扩展开放,对修改关闭。
新增功能尽量增加新的实现,而不是反复修改已经稳定的核心代码。
二、架构规范
1. Interface → Plugin → Registry
插件化可以归纳成三个核心角色。
Interface:接口
先规定插件必须提供什么能力:
from abc import ABC, abstractmethod
class DomainPlugin(ABC):
@abstractmethod
def parse_metadata(self, raw: dict) –> dict:
pass
这里的:
ABC(Abstract Base Class,抽象基类)
相当于插件契约。
具体插件只需要实现它:
#mermaid-svg-cXuX9SUVgXjXU959{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-cXuX9SUVgXjXU959 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-cXuX9SUVgXjXU959 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-cXuX9SUVgXjXU959 .error-icon{fill:#552222;}#mermaid-svg-cXuX9SUVgXjXU959 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-cXuX9SUVgXjXU959 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-cXuX9SUVgXjXU959 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-cXuX9SUVgXjXU959 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-cXuX9SUVgXjXU959 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-cXuX9SUVgXjXU959 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-cXuX9SUVgXjXU959 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-cXuX9SUVgXjXU959 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-cXuX9SUVgXjXU959 .marker.cross{stroke:#333333;}#mermaid-svg-cXuX9SUVgXjXU959 svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-cXuX9SUVgXjXU959 p{margin:0;}#mermaid-svg-cXuX9SUVgXjXU959 .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-cXuX9SUVgXjXU959 .cluster-label text{fill:#333;}#mermaid-svg-cXuX9SUVgXjXU959 .cluster-label span{color:#333;}#mermaid-svg-cXuX9SUVgXjXU959 .cluster-label span p{background-color:transparent;}#mermaid-svg-cXuX9SUVgXjXU959 .label text,#mermaid-svg-cXuX9SUVgXjXU959 span{fill:#333;color:#333;}#mermaid-svg-cXuX9SUVgXjXU959 .node rect,#mermaid-svg-cXuX9SUVgXjXU959 .node circle,#mermaid-svg-cXuX9SUVgXjXU959 .node ellipse,#mermaid-svg-cXuX9SUVgXjXU959 .node polygon,#mermaid-svg-cXuX9SUVgXjXU959 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-cXuX9SUVgXjXU959 .rough-node .label text,#mermaid-svg-cXuX9SUVgXjXU959 .node .label text,#mermaid-svg-cXuX9SUVgXjXU959 .image-shape .label,#mermaid-svg-cXuX9SUVgXjXU959 .icon-shape .label{text-anchor:middle;}#mermaid-svg-cXuX9SUVgXjXU959 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-cXuX9SUVgXjXU959 .rough-node .label,#mermaid-svg-cXuX9SUVgXjXU959 .node .label,#mermaid-svg-cXuX9SUVgXjXU959 .image-shape .label,#mermaid-svg-cXuX9SUVgXjXU959 .icon-shape .label{text-align:center;}#mermaid-svg-cXuX9SUVgXjXU959 .node.clickable{cursor:pointer;}#mermaid-svg-cXuX9SUVgXjXU959 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-cXuX9SUVgXjXU959 .arrowheadPath{fill:#333333;}#mermaid-svg-cXuX9SUVgXjXU959 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-cXuX9SUVgXjXU959 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-cXuX9SUVgXjXU959 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-cXuX9SUVgXjXU959 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-cXuX9SUVgXjXU959 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-cXuX9SUVgXjXU959 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-cXuX9SUVgXjXU959 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-cXuX9SUVgXjXU959 .cluster text{fill:#333;}#mermaid-svg-cXuX9SUVgXjXU959 .cluster span{color:#333;}#mermaid-svg-cXuX9SUVgXjXU959 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-cXuX9SUVgXjXU959 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-cXuX9SUVgXjXU959 rect.text{fill:none;stroke-width:0;}#mermaid-svg-cXuX9SUVgXjXU959 .icon-shape,#mermaid-svg-cXuX9SUVgXjXU959 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-cXuX9SUVgXjXU959 .icon-shape p,#mermaid-svg-cXuX9SUVgXjXU959 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-cXuX9SUVgXjXU959 .icon-shape .label rect,#mermaid-svg-cXuX9SUVgXjXU959 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-cXuX9SUVgXjXU959 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-cXuX9SUVgXjXU959 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-cXuX9SUVgXjXU959 :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
DomainPlugin
LegalPlugin
FinancePlugin
MedicalPlugin
核心系统只依赖 DomainPlugin,而不依赖某个具体领域。
Registry:注册表
系统还需要知道:
某个领域应该使用哪个插件?
可以通过:
Registry Pattern:注册表模式
实现:
class PluginRegistry:
def __init__(self):
self._plugins = {}
def register(self, name, plugin):
self._plugins[name] = plugin
def get(self, name):
return self._plugins.get(name)
于是运行时只需要:
plugin = registry.get(domain)
而不需要:
if domain == ...
Loader:加载器
插件的创建最好也不要散落在业务代码中。
推荐:
Application Startup
↓
Plugin Loader
↓
Plugin Registry
运行时:
Request
↓
Registry.get()
↓
Plugin
即:
启动阶段负责发现和注册,运行阶段负责查找和使用。
2. 进一步配置化:让系统动态加载插件
如果插件未来会频繁增加,可以把具体实现配置化。
例如配置中保存:
app.plugins.legal.LegalPlugin
系统通过:
module = importlib.import_module(module_path)
plugin_cls = getattr(module, class_name)
动态加载。
这样核心系统不需要写:
from app.plugins.legal import LegalPlugin
from app.plugins.finance import FinancePlugin
from app.plugins.medical import MedicalPlugin
增加一个插件可以变成:
实现 Plugin
+
增加配置
↓
系统自动加载
这就是:
Plug-and-Play:即插即用。
3. 什么功能适合设计成扩展点?
不是所有代码都需要插件化。
真正适合成为扩展点的,通常是:
稳定流程中存在多个实现,而且未来还会继续变化的部分。
Agent 系统中常见的扩展点包括:
Core Runtime
│
├── Tool Plugin
├── Domain Plugin
├── Retriever
├── Reranker
├── LLM Provider
├── Vector Store
└── Document Processor
其中:
- Retriever:检索器
- Reranker:重排序器
- LLM(Large Language Model,大语言模型)Provider:模型提供方
- Vector Store:向量存储
都可能存在多个实现。
一个很实用的判断方法是:
如果某个地方开始出现越来越多的 if type == "A"、elif type == "B",就应该检查这里是不是缺少一个扩展点。
但对于简单且稳定的逻辑,没有必要强行设计 Plugin、Registry、Factory,否则就会变成:
Overengineering:过度设计。
三、 插件化有什么好处?
最直接的变化是:
传统方式
新增功能
↓
修改 Core
↓
重新验证核心流程
变成:
插件方式
新增功能
↓
实现 Plugin
↓
Register
由此带来几个实际好处:
- 降低耦合:领域逻辑不会全部堆进核心 Service;
- 降低回归风险:新增插件较少影响已有插件;
- 方便测试:每个插件可以独立测试;
- 方便替换:实现可以按配置切换;
- 方便 AI 开发:AI 知道新功能应该放在哪里。
最后一点尤其重要。
没有扩展点时,AI 的开发方式很容易是:
找到旧代码
↓
插入新判断
↓
修改多个 Service
有扩展点之后则变成:
找到 Extension Point
↓
实现 Plugin
↓
Register
插件化实际上是在给 AI 划定:
功能增长的正确方向。
四、提示词落地:明确告诉 AI 如何扩展
可以把以下规则放进:
Project Rules:项目级规则
## 插件和扩展规则
1. 优先使用扩展点,而不是向核心服务添加基于类型的 if/elif 分支。
2. 核心模块必须依赖于抽象,而不是具体的插件实现。
3. 可扩展功能应遵循以下流程:接口→ 插件实现→ 注册表→ 运行时查找
4. 不要在业务服务内部实例化具体的插件。
5. 插件的发现和注册应在应用程序启动期间进行。
6. 特定领域的行为必须保留在插件内部,并且不得泄露到通用服务中。
7. 当实现可以独立更改时,优先使用配置驱动的注册。
8. 添加插件理想情况下应该只需要:
– 插件实现
– 注册/配置
– 插件测试
9. 当不太可能出现多个实现时,不要引入插件抽象。
10. 在添加基于类型的 if/elif 分支之前,检查该位置是否应该是一个扩展点。
核心目的不是要求 AI:
“多写几个 Plugin 类。”
而是要求:
稳定能力留在 Core,可变化能力进入 Extension Point。
五、正面产出:miniagent 的领域插件机制
miniagent 的知识库系统中,就存在一个典型扩展点:
Domain Plugin:领域插件
不同领域可以拥有自己的:
- 文档元数据处理;
- Small-to-Big 上下文扩展;
- Citation(引用)合并规则;
而通用知识库流程不需要知道具体领域细节。
1. DomainPlugin 定义插件契约
miniagent 定义了:
class DomainPlugin(ABC):
@property
@abstractmethod
def processor(self) –> SmallToBigProcessor:
...
@abstractmethod
def parse_metadata(self, raw: dict) –> dict:
...
@property
def citation_merger(self) –> CitationMerger:
return CitationMerger()
它定义了几个领域扩展点:
DomainPlugin
│
├── processor
├── parse_metadata()
└── citation_merger
新的领域只需要实现这份契约,而不需要把领域判断继续塞进通用流程。
2. DomainRegistry 统一管理插件
miniagent 使用:
class DomainRegistry:
def __init__(self):
self._plugins: dict[str, DomainPlugin] = {}
def register(
self,
domain: str,
plugin: DomainPlugin
) –> None:
self._plugins[domain] = plugin
def get(self, domain: str) –> DomainPlugin:
return self._plugins.get(domain)
运行时的关系因此变成:
domain
↓
DomainRegistry
↓
DomainPlugin
而不是越来越长的 if/elif。
3. 从数据库动态加载具体实现
更进一步,miniagent 在 ServiceContainer 启动时读取领域配置:
domains = await self.domain_db.get_all_domains()
然后根据配置中的:
processor_class
plugin_class
动态加载:
processor_cls = import_class(
domain_orm.processor_class
)
processor_instance = processor_cls()
plugin_cls = import_class(
domain_orm.plugin_class
)
plugin_instance = plugin_cls(
processor=processor_instance
)
最后注册:
self.domain_registry.register(
domain=domain_orm.name,
plugin=plugin_instance
)
其中动态导入的核心实现是:
def import_class(class_path: str):
module_path, class_name = class_path.rsplit(".", 1)
module = importlib.import_module(module_path)
return getattr(module, class_name)
因此整个插件加载过程可以概括为:
Database Configuration
↓
processor_class / plugin_class
↓
Dynamic Import
↓
Create Plugin
↓
DomainRegistry.register()
↓
Runtime Lookup
下图详细的描述了 miniagent 的领域插件实现机制:
#mermaid-svg-33WKisAUs0jN6MXl{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-33WKisAUs0jN6MXl .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-33WKisAUs0jN6MXl .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-33WKisAUs0jN6MXl .error-icon{fill:#552222;}#mermaid-svg-33WKisAUs0jN6MXl .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-33WKisAUs0jN6MXl .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-33WKisAUs0jN6MXl .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-33WKisAUs0jN6MXl .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-33WKisAUs0jN6MXl .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-33WKisAUs0jN6MXl .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-33WKisAUs0jN6MXl .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-33WKisAUs0jN6MXl .marker{fill:#333333;stroke:#333333;}#mermaid-svg-33WKisAUs0jN6MXl .marker.cross{stroke:#333333;}#mermaid-svg-33WKisAUs0jN6MXl svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-33WKisAUs0jN6MXl p{margin:0;}#mermaid-svg-33WKisAUs0jN6MXl .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-33WKisAUs0jN6MXl .cluster-label text{fill:#333;}#mermaid-svg-33WKisAUs0jN6MXl .cluster-label span{color:#333;}#mermaid-svg-33WKisAUs0jN6MXl .cluster-label span p{background-color:transparent;}#mermaid-svg-33WKisAUs0jN6MXl .label text,#mermaid-svg-33WKisAUs0jN6MXl span{fill:#333;color:#333;}#mermaid-svg-33WKisAUs0jN6MXl .node rect,#mermaid-svg-33WKisAUs0jN6MXl .node circle,#mermaid-svg-33WKisAUs0jN6MXl .node ellipse,#mermaid-svg-33WKisAUs0jN6MXl .node polygon,#mermaid-svg-33WKisAUs0jN6MXl .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-33WKisAUs0jN6MXl .rough-node .label text,#mermaid-svg-33WKisAUs0jN6MXl .node .label text,#mermaid-svg-33WKisAUs0jN6MXl .image-shape .label,#mermaid-svg-33WKisAUs0jN6MXl .icon-shape .label{text-anchor:middle;}#mermaid-svg-33WKisAUs0jN6MXl .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-33WKisAUs0jN6MXl .rough-node .label,#mermaid-svg-33WKisAUs0jN6MXl .node .label,#mermaid-svg-33WKisAUs0jN6MXl .image-shape .label,#mermaid-svg-33WKisAUs0jN6MXl .icon-shape .label{text-align:center;}#mermaid-svg-33WKisAUs0jN6MXl .node.clickable{cursor:pointer;}#mermaid-svg-33WKisAUs0jN6MXl .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-33WKisAUs0jN6MXl .arrowheadPath{fill:#333333;}#mermaid-svg-33WKisAUs0jN6MXl .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-33WKisAUs0jN6MXl .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-33WKisAUs0jN6MXl .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-33WKisAUs0jN6MXl .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-33WKisAUs0jN6MXl .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-33WKisAUs0jN6MXl .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-33WKisAUs0jN6MXl .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-33WKisAUs0jN6MXl .cluster text{fill:#333;}#mermaid-svg-33WKisAUs0jN6MXl .cluster span{color:#333;}#mermaid-svg-33WKisAUs0jN6MXl div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-33WKisAUs0jN6MXl .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-33WKisAUs0jN6MXl rect.text{fill:none;stroke-width:0;}#mermaid-svg-33WKisAUs0jN6MXl .icon-shape,#mermaid-svg-33WKisAUs0jN6MXl .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-33WKisAUs0jN6MXl .icon-shape p,#mermaid-svg-33WKisAUs0jN6MXl .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-33WKisAUs0jN6MXl .icon-shape .label rect,#mermaid-svg-33WKisAUs0jN6MXl .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-33WKisAUs0jN6MXl .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-33WKisAUs0jN6MXl .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-33WKisAUs0jN6MXl :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
Application Startup
Load Domain Config
processor_class / plugin_class
Dynamic Import
Create DomainPlugin
DomainRegistry.register()
Runtime Request
domain
DomainRegistry.get(domain)
Domain-specific Behavior
这里最重要的是两个阶段:
启动阶段
发现 → 创建 → 注册插件
运行阶段
查询 → 使用插件
插件管理和业务执行因此被分离开来。
4. 新增一个领域时会发生什么?
假设未来 miniagent 增加金融领域。
理想路径是:
Finance Processor
↓
Finance DomainPlugin
↓
增加 Domain 配置
↓
系统启动自动加载
↓
DomainRegistry
而不是:
修改 Retrieval Service
修改 Document Service
增加 finance if/elif
修改核心路由
这就是插件化真正希望达到的效果:
通过增加实现扩展系统,而不是通过修改核心流程扩展系统。
总结
AI 很擅长往已有代码中继续增加逻辑,但长期迭代的软件更需要:
Stable Core
↓
Extension Point
↓
Plugin
可以把插件化原则压缩成四句话:
稳定能力 → 留在 Core
变化能力 → 放进 Plugin
插件管理 → 交给 Registry
具体实现 → 通过配置接入
miniagent 的 DomainPlugin + DomainRegistry + 动态导入 + 数据库配置 就体现了这套思路:接口定义扩展边界,Registry 管理具体实现,ServiceContainer 在启动阶段动态发现和注册插件。
对于 AI 编程,最值得写进项目规则的一句话是:
新增能力时先寻找扩展点;已有扩展点,就新增实现,不要修改核心流程。
这样 AI 才不是不断往系统里“堆代码”,而是在既有架构中插入一个:
可替换、可测试、可独立迭代的模块。
开源代码
- github
- gitee
🪐祝您好运🪐



