219232de74
以讲义内容为骨架迁移到标准目录格式: - officefile/ 主内容(12章 + 附录 + CC4SI补充) - dofile/ 代码示例(11个Python脚本) - data/ 图片资源 - output/ 生成输出(忽略) - Archive/ 归档旧目录(忽略) - .claude/skills/ 保留markdown-to-docx工具链 - .pandoc/ 保留CSL和本地化配置 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1062 lines
33 KiB
Markdown
1062 lines
33 KiB
Markdown
# 03.3 技能组合与复用
|
|
|
|
## 核心问题
|
|
|
|
> 如何将复杂功能分解为可复用的技能单元?
|
|
> 如何设计技能接口以支持动态组合?
|
|
> 如何发现和加载新技能而不修改核心代码?
|
|
|
|
---
|
|
|
|
## 概念讲解
|
|
|
|
### 技能抽象的概念
|
|
|
|
**技能 (Skill)** 是Agent可执行的**独立功能单元**,具有明确的输入输出接口:
|
|
|
|
```
|
|
技能 = 功能定义 + 接口契约 + 元数据
|
|
|
|
┌─────────────────────────────────────────────────────────────┐
|
|
│ 技能的基本结构 │
|
|
├─────────────────────────────────────────────────────────────┤
|
|
│ │
|
|
│ ┌─────────────────────────────────────────────────────┐ │
|
|
│ │ Skill Interface │ │
|
|
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │
|
|
│ │ │ 名称 │ │ 描述 │ │ 参数 │ │ │
|
|
│ │ │ name │ │ description │ │ parameters │ │ │
|
|
│ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │
|
|
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │
|
|
│ │ │ 输入 │ │ 输出 │ │ 副作用 │ │ │
|
|
│ │ │ input │ │ output │ │ side_effects│ │ │
|
|
│ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │
|
|
│ └─────────────────────────────────────────────────────┘ │
|
|
│ │ │
|
|
│ ↓ │
|
|
│ ┌─────────────────────────────────────────────────────┐ │
|
|
│ │ Implementation │ │
|
|
│ │ │
|
|
│ │ def execute(self, **kwargs) -> SkillResult: │
|
|
│ │ # 技能实现 │
|
|
│ │ pass │ │
|
|
│ └─────────────────────────────────────────────────────┘ │
|
|
│ │
|
|
└─────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
### 技能组合模式
|
|
|
|
```
|
|
技能组合模式层次
|
|
|
|
1. 顺序组合 (Sequential)
|
|
│
|
|
├──→ 技能A输出 → 技能B输入 → 技能C输入
|
|
│ 适用:数据流水线处理
|
|
│
|
|
2. 并行组合 (Parallel)
|
|
│
|
|
├──→ 技能A ──┐
|
|
│ 技能B ──┼──→ 合并结果
|
|
│ 技能C ──┘
|
|
│ 适用:独立任务并行执行
|
|
│
|
|
3. 条件组合 (Conditional)
|
|
│
|
|
├──→ 条件判断 → 技能A或技能B
|
|
│ 适用:分支处理逻辑
|
|
│
|
|
4. 迭代组合 (Iterative)
|
|
│
|
|
└──→ 技能A输出 → [循环] → 技能B
|
|
适用:增量式处理
|
|
```
|
|
|
|
---
|
|
|
|
## 设计原理
|
|
|
|
### 技能接口设计
|
|
|
|
良好的技能接口是实现组合的基础:
|
|
|
|
```python
|
|
from abc import ABC, abstractmethod
|
|
from typing import Dict, Any, List, Optional, Type, Callable
|
|
from dataclasses import dataclass, field
|
|
from enum import Enum
|
|
|
|
|
|
class SkillCategory(Enum):
|
|
"""技能分类"""
|
|
DATA_PROCESSING = "data_processing"
|
|
SPATIAL_ANALYSIS = "spatial_analysis"
|
|
VISUALIZATION = "visualization"
|
|
FILE_OPERATIONS = "file_operations"
|
|
MODEL_EXECUTION = "model_execution"
|
|
|
|
|
|
@dataclass
|
|
class ParameterSpec:
|
|
"""参数规格"""
|
|
name: str
|
|
type: Type
|
|
description: str
|
|
required: bool = True
|
|
default: Any = None
|
|
constraints: Dict[str, Any] = field(default_factory=dict)
|
|
|
|
|
|
@dataclass
|
|
class SkillResult:
|
|
"""技能执行结果"""
|
|
success: bool
|
|
data: Any = None
|
|
error: Optional[str] = None
|
|
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
|
|
|
|
class Skill(ABC):
|
|
"""技能基类"""
|
|
|
|
# 技能元数据
|
|
name: str = ""
|
|
description: str = ""
|
|
category: SkillCategory = SkillCategory.DATA_PROCESSING
|
|
version: str = "1.0.0"
|
|
parameters: List[ParameterSpec] = field(default_factory=list)
|
|
|
|
@abstractmethod
|
|
def execute(self, **kwargs) -> SkillResult:
|
|
"""执行技能"""
|
|
pass
|
|
|
|
def validate_input(self, **kwargs) -> tuple[bool, Optional[str]]:
|
|
"""验证输入参数"""
|
|
for param in self.parameters:
|
|
if param.required and param.name not in kwargs:
|
|
return False, f"Missing required parameter: {param.name}"
|
|
|
|
if param.name in kwargs:
|
|
value = kwargs[param.name]
|
|
if not isinstance(value, param.type):
|
|
try:
|
|
kwargs[param.name] = param.type(value)
|
|
except (ValueError, TypeError):
|
|
return False, f"Invalid type for {param.name}"
|
|
|
|
return True, None
|
|
|
|
def get_spec(self) -> Dict[str, Any]:
|
|
"""获取技能规格"""
|
|
return {
|
|
'name': self.name,
|
|
'description': self.description,
|
|
'category': self.category.value,
|
|
'version': self.version,
|
|
'parameters': [
|
|
{
|
|
'name': p.name,
|
|
'type': p.type.__name__,
|
|
'description': p.description,
|
|
'required': p.required,
|
|
'default': p.default
|
|
}
|
|
for p in self.parameters
|
|
]
|
|
}
|
|
```
|
|
|
|
### 技能组合器
|
|
|
|
```python
|
|
class SkillComposer:
|
|
"""
|
|
技能组合器:将多个技能组合成复合技能
|
|
"""
|
|
|
|
def __init__(self):
|
|
self.skills: Dict[str, Skill] = {}
|
|
|
|
def register(self, skill: Skill) -> 'SkillComposer':
|
|
"""注册技能"""
|
|
self.skills[skill.name] = skill
|
|
return self
|
|
|
|
def sequential(self, *skill_names: str) -> 'CompositeSkill':
|
|
"""
|
|
顺序组合:前一个技能的输出传递给下一个
|
|
|
|
数据流: input → skill1 → skill2 → skill3 → output
|
|
"""
|
|
skills = [self.skills[name] for name in skill_names]
|
|
return CompositeSkill(
|
|
name=f"sequential_{'_'.join(skill_names)}",
|
|
skills=skills,
|
|
mode='sequential'
|
|
)
|
|
|
|
def parallel(self, *skill_names: str, merge_func: Callable = None) -> 'CompositeSkill':
|
|
"""
|
|
并行组合:所有技能并行执行,结果合并
|
|
|
|
数据流:
|
|
input ──→ skill1 ──┐
|
|
─→ skill2 ──┼──→ merge → output
|
|
─→ skill3 ──┘
|
|
"""
|
|
skills = [self.skills[name] for name in skill_names]
|
|
return CompositeSkill(
|
|
name=f"parallel_{'_'.join(skill_names)}",
|
|
skills=skills,
|
|
mode='parallel',
|
|
merge_func=merge_func
|
|
)
|
|
|
|
def conditional(self, condition: Callable,
|
|
true_skill: str, false_skill: str = None) -> 'CompositeSkill':
|
|
"""
|
|
条件组合:根据条件选择技能执行
|
|
|
|
数据流: input → condition → skill_if_true / skill_if_false → output
|
|
"""
|
|
skills = [self.skills[true_skill]]
|
|
if false_skill:
|
|
skills.append(self.skills[false_skill])
|
|
|
|
return CompositeSkill(
|
|
name=f"conditional_{true_skill}_{false_skill}",
|
|
skills=skills,
|
|
mode='conditional',
|
|
condition=condition
|
|
)
|
|
|
|
def loop(self, skill_name: str,
|
|
until: Callable = None,
|
|
max_iterations: int = 10) -> 'CompositeSkill':
|
|
"""
|
|
迭代组合:循环执行技能直到满足条件
|
|
|
|
数据流: input → [skill → check] → output
|
|
"""
|
|
skill = self.skills[skill_name]
|
|
return CompositeSkill(
|
|
name=f"loop_{skill_name}",
|
|
skills=[skill],
|
|
mode='loop',
|
|
until_condition=until,
|
|
max_iterations=max_iterations
|
|
)
|
|
|
|
|
|
class CompositeSkill(Skill):
|
|
"""复合技能:由多个子技能组合而成"""
|
|
|
|
def __init__(self, name: str, skills: List[Skill],
|
|
mode: str, **kwargs):
|
|
self.name = name
|
|
self.skills = skills
|
|
self.mode = mode # sequential, parallel, conditional, loop
|
|
self.condition = kwargs.get('condition')
|
|
self.merge_func = kwargs.get('merge_func')
|
|
self.until_condition = kwargs.get('until_condition')
|
|
self.max_iterations = kwargs.get('max_iterations', 10)
|
|
|
|
def execute(self, initial_input: Any = None, **kwargs) -> SkillResult:
|
|
"""执行复合技能"""
|
|
if self.mode == 'sequential':
|
|
return self._execute_sequential(initial_input, **kwargs)
|
|
elif self.mode == 'parallel':
|
|
return self._execute_parallel(initial_input, **kwargs)
|
|
elif self.mode == 'conditional':
|
|
return self._execute_conditional(initial_input, **kwargs)
|
|
elif self.mode == 'loop':
|
|
return self._execute_loop(initial_input, **kwargs)
|
|
else:
|
|
return SkillResult(success=False, error=f"Unknown mode: {self.mode}")
|
|
|
|
def _execute_sequential(self, initial_input, **kwargs):
|
|
"""顺序执行"""
|
|
current_input = initial_input
|
|
results = []
|
|
|
|
for skill in self.skills:
|
|
if isinstance(current_input, dict):
|
|
result = skill.execute(**current_input)
|
|
else:
|
|
result = skill.execute(input=current_input)
|
|
|
|
if not result.success:
|
|
return SkillResult(
|
|
success=False,
|
|
error=f"Skill {skill.name} failed: {result.error}",
|
|
metadata={'failed_at': skill.name}
|
|
)
|
|
|
|
results.append(result)
|
|
current_input = result.data
|
|
|
|
return SkillResult(
|
|
success=True,
|
|
data=current_input,
|
|
metadata={'sub_results': results}
|
|
)
|
|
|
|
def _execute_parallel(self, initial_input, **kwargs):
|
|
"""并行执行"""
|
|
import concurrent.futures
|
|
|
|
results = []
|
|
with concurrent.futures.ThreadPoolExecutor() as executor:
|
|
futures = {
|
|
executor.submit(skill.execute, input=initial_input): skill
|
|
for skill in self.skills
|
|
}
|
|
|
|
for future in concurrent.futures.as_completed(futures):
|
|
skill = futures[future]
|
|
try:
|
|
result = future.result()
|
|
results.append(result)
|
|
except Exception as e:
|
|
results.append(SkillResult(
|
|
success=False,
|
|
error=str(e),
|
|
metadata={'skill': skill.name}
|
|
))
|
|
|
|
# 合并结果
|
|
if self.merge_func:
|
|
merged_data = self.merge_func(results)
|
|
else:
|
|
# 默认合并:收集所有成功的数据
|
|
merged_data = [r.data for r in results if r.success]
|
|
|
|
return SkillResult(
|
|
success=all(r.success for r in results),
|
|
data=merged_data,
|
|
metadata={'sub_results': results}
|
|
)
|
|
|
|
def _execute_conditional(self, initial_input, **kwargs):
|
|
"""条件执行"""
|
|
if self.condition and self.condition(initial_input):
|
|
result = self.skills[0].execute(input=initial_input)
|
|
elif len(self.skills) > 1:
|
|
result = self.skills[1].execute(input=initial_input)
|
|
else:
|
|
result = SkillResult(success=True, data=initial_input)
|
|
|
|
return result
|
|
|
|
def _execute_loop(self, initial_input, **kwargs):
|
|
"""循环执行"""
|
|
current_input = initial_input
|
|
results = []
|
|
|
|
for i in range(self.max_iterations):
|
|
result = self.skills[0].execute(input=current_input)
|
|
|
|
if not result.success:
|
|
return SkillResult(
|
|
success=False,
|
|
error=f"Iteration {i} failed: {result.error}"
|
|
)
|
|
|
|
results.append(result)
|
|
current_input = result.data
|
|
|
|
# 检查终止条件
|
|
if self.until_condition and self.until_condition(result.data):
|
|
break
|
|
|
|
return SkillResult(
|
|
success=True,
|
|
data=current_input,
|
|
metadata={'iterations': len(results), 'sub_results': results}
|
|
)
|
|
```
|
|
|
|
### 动态技能发现与加载
|
|
|
|
```python
|
|
import importlib
|
|
import importlib.util
|
|
import inspect
|
|
from pathlib import Path
|
|
|
|
|
|
class SkillRegistry:
|
|
"""
|
|
技能注册表:管理技能的发现、加载和注册
|
|
"""
|
|
|
|
def __init__(self):
|
|
self._skills: Dict[str, Type[Skill]] = {}
|
|
self._categories: Dict[SkillCategory, List[str]] = {
|
|
category: [] for category in SkillCategory
|
|
}
|
|
|
|
def register_class(self, skill_class: Type[Skill]) -> None:
|
|
"""注册技能类"""
|
|
if not issubclass(skill_class, Skill):
|
|
raise TypeError(f"{skill_class} must be a subclass of Skill")
|
|
|
|
# 创建实例获取元数据
|
|
instance = skill_class()
|
|
self._skills[instance.name] = skill_class
|
|
self._categories[instance.category].append(instance.name)
|
|
|
|
def get_skill(self, name: str) -> Optional[Skill]:
|
|
"""获取技能实例"""
|
|
if name in self._skills:
|
|
return self._skills[name]()
|
|
return None
|
|
|
|
def list_skills(self, category: SkillCategory = None) -> List[str]:
|
|
"""列出技能"""
|
|
if category:
|
|
return self._categories.get(category, [])
|
|
return list(self._skills.keys())
|
|
|
|
def discover_from_directory(self, directory: Path) -> int:
|
|
"""
|
|
从目录发现并加载技能
|
|
|
|
约定:技能文件以 _skill.py 结尾,包含继承自 Skill 的类
|
|
"""
|
|
count = 0
|
|
|
|
for file_path in directory.rglob("*_skill.py"):
|
|
try:
|
|
# 动态导入模块
|
|
module_name = file_path.stem
|
|
spec = importlib.util.spec_from_file_location(module_name, file_path)
|
|
if spec and spec.loader:
|
|
module = importlib.util.module_from_spec(spec)
|
|
spec.loader.exec_module(module)
|
|
|
|
# 查找 Skill 子类
|
|
for name, obj in inspect.getmembers(module, inspect.isclass):
|
|
if (issubclass(obj, Skill) and
|
|
obj != Skill and
|
|
not obj.__module__.startswith('_')):
|
|
self.register_class(obj)
|
|
count += 1
|
|
|
|
except Exception as e:
|
|
print(f"Failed to load {file_path}: {e}")
|
|
|
|
return count
|
|
|
|
def get_skill_spec(self, name: str) -> Optional[Dict]:
|
|
"""获取技能规格"""
|
|
skill = self.get_skill(name)
|
|
if skill:
|
|
return skill.get_spec()
|
|
return None
|
|
|
|
|
|
# 全局技能注册表
|
|
registry = SkillRegistry()
|
|
|
|
|
|
def register_skill(skill_class: Type[Skill]) -> Type[Skill]:
|
|
"""技能注册装饰器"""
|
|
registry.register_class(skill_class)
|
|
return skill_class
|
|
```
|
|
|
|
---
|
|
|
|
## 代码示例
|
|
|
|
### QGIS技能集成管理器
|
|
|
|
```python
|
|
"""
|
|
QGIS技能集成管理器
|
|
|
|
演示如何为空间分析工具创建可组合的技能系统
|
|
"""
|
|
import json
|
|
from typing import Dict, Any, List, Optional
|
|
from pathlib import Path
|
|
|
|
|
|
# ==================== 基础空间分析技能 ====================
|
|
|
|
@register_skill
|
|
class LoadVectorLayerSkill(Skill):
|
|
"""加载矢量图层的技能"""
|
|
name = "load_vector_layer"
|
|
description = "从文件加载矢量图层"
|
|
category = SkillCategory.FILE_OPERATIONS
|
|
version = "1.0.0"
|
|
parameters = [
|
|
ParameterSpec("path", str, "文件路径", required=True),
|
|
ParameterSpec("layer_name", str, "图层名称", required=False, default="layer"),
|
|
]
|
|
|
|
def execute(self, **kwargs) -> SkillResult:
|
|
valid, error = self.validate_input(**kwargs)
|
|
if not valid:
|
|
return SkillResult(success=False, error=error)
|
|
|
|
path = kwargs['path']
|
|
layer_name = kwargs.get('layer_name', 'layer')
|
|
|
|
# 模拟加载(实际会调用QGIS API)
|
|
return SkillResult(
|
|
success=True,
|
|
data={
|
|
'layer': layer_name,
|
|
'path': path,
|
|
'type': 'vector',
|
|
'feature_count': 1250,
|
|
'crs': 'EPSG:4326'
|
|
},
|
|
metadata={'loaded_at': '2024-01-01T00:00:00'}
|
|
)
|
|
|
|
|
|
@register_skill
|
|
class BufferAnalysisSkill(Skill):
|
|
"""缓冲区分析技能"""
|
|
name = "buffer_analysis"
|
|
description = "对几何图形创建缓冲区"
|
|
category = SkillCategory.SPATIAL_ANALYSIS
|
|
version = "1.0.0"
|
|
parameters = [
|
|
ParameterSpec("layer", str, "输入图层", required=True),
|
|
ParameterSpec("distance", float, "缓冲距离", required=True),
|
|
ParameterSpec("segments", int, "分段数", required=False, default=8),
|
|
]
|
|
|
|
def execute(self, **kwargs) -> SkillResult:
|
|
valid, error = self.validate_input(**kwargs)
|
|
if not valid:
|
|
return SkillResult(success=False, error=error)
|
|
|
|
layer = kwargs['layer']
|
|
distance = kwargs['distance']
|
|
|
|
# 模拟缓冲区分析
|
|
return SkillResult(
|
|
success=True,
|
|
data={
|
|
'output_layer': f"{layer}_buffer_{distance}m",
|
|
'input_layer': layer,
|
|
'distance': distance,
|
|
'area_ha': 542.3
|
|
}
|
|
)
|
|
|
|
|
|
@register_skill
|
|
class CalculateAreaSkill(Skill):
|
|
"""计算面积技能"""
|
|
name = "calculate_area"
|
|
description = "计算要素面积"
|
|
category = SkillCategory.SPATIAL_ANALYSIS
|
|
version = "1.0.0"
|
|
parameters = [
|
|
ParameterSpec("layer", str, "输入图层", required=True),
|
|
ParameterSpec("unit", str, "单位", required=False, default="ha"),
|
|
]
|
|
|
|
def execute(self, **kwargs) -> SkillResult:
|
|
valid, error = self.validate_input(**kwargs)
|
|
if not valid:
|
|
return SkillResult(success=False, error=error)
|
|
|
|
return SkillResult(
|
|
success=True,
|
|
data={
|
|
'layer': kwargs['layer'],
|
|
'unit': kwargs.get('unit', 'ha'),
|
|
'total_area': 1250.5,
|
|
'mean_area': 0.42,
|
|
'areas': [1.2, 0.8, 1.5, 0.3, 2.1]
|
|
}
|
|
)
|
|
|
|
|
|
@register_skill
|
|
class ExportToGeoJSONSkill(Skill):
|
|
"""导出GeoJSON技能"""
|
|
name = "export_geojson"
|
|
description = "导出图层为GeoJSON格式"
|
|
category = SkillCategory.FILE_OPERATIONS
|
|
version = "1.0.0"
|
|
parameters = [
|
|
ParameterSpec("layer", str, "输入图层", required=True),
|
|
ParameterSpec("output_path", str, "输出路径", required=True),
|
|
]
|
|
|
|
def execute(self, **kwargs) -> SkillResult:
|
|
valid, error = self.validate_input(**kwargs)
|
|
if not valid:
|
|
return SkillResult(success=False, error=error)
|
|
|
|
# 模拟导出
|
|
return SkillResult(
|
|
success=True,
|
|
data={
|
|
'output_path': kwargs['output_path'],
|
|
'layer': kwargs['layer'],
|
|
'format': 'GeoJSON',
|
|
'size_kb': 245
|
|
}
|
|
)
|
|
|
|
|
|
@register_skill
|
|
class CreateHeatmapSkill(Skill):
|
|
"""创建热力图技能"""
|
|
name = "create_heatmap"
|
|
description = "创建点数据热力图"
|
|
category = SkillCategory.VISUALIZATION
|
|
version = "1.0.0"
|
|
parameters = [
|
|
ParameterSpec("layer", str, "输入图层", required=True),
|
|
ParameterSpec("radius", int, "影响半径", required=False, default=100),
|
|
ParameterSpec("color_ramp", str, "颜色渐变", required=False, default="hot"),
|
|
]
|
|
|
|
def execute(self, **kwargs) -> SkillResult:
|
|
valid, error = self.validate_input(**kwargs)
|
|
if not valid:
|
|
return SkillResult(success=False, error=error)
|
|
|
|
return SkillResult(
|
|
success=True,
|
|
data={
|
|
'output_layer': f"{kwargs['layer']}_heatmap",
|
|
'radius': kwargs.get('radius', 100),
|
|
'color_ramp': kwargs.get('color_ramp', 'hot'),
|
|
'render_time_ms': 234
|
|
}
|
|
)
|
|
|
|
|
|
@register_skill
|
|
class CalculateStatisticsSkill(Skill):
|
|
"""统计计算技能"""
|
|
name = "calculate_statistics"
|
|
description = "计算字段统计值"
|
|
category = SkillCategory.DATA_PROCESSING
|
|
version = "1.0.0"
|
|
parameters = [
|
|
ParameterSpec("layer", str, "输入图层", required=True),
|
|
ParameterSpec("field", str, "统计字段", required=True),
|
|
]
|
|
|
|
def execute(self, **kwargs) -> SkillResult:
|
|
valid, error = self.validate_input(**kwargs)
|
|
if not valid:
|
|
return SkillResult(success=False, error=error)
|
|
|
|
# 模拟统计计算
|
|
import random
|
|
values = [random.uniform(0, 100) for _ in range(100)]
|
|
|
|
return SkillResult(
|
|
success=True,
|
|
data={
|
|
'field': kwargs['field'],
|
|
'count': len(values),
|
|
'mean': sum(values) / len(values),
|
|
'min': min(values),
|
|
'max': max(values),
|
|
'std': (sum((x - sum(values)/len(values))**2 for x in values) / len(values)) ** 0.5
|
|
}
|
|
)
|
|
|
|
|
|
# ==================== 技能管理器 ====================
|
|
|
|
class QGISSkillManager:
|
|
"""QGIS技能管理器"""
|
|
|
|
def __init__(self):
|
|
self.composer = SkillComposer()
|
|
self.registry = registry
|
|
|
|
# 注册基础技能
|
|
for skill_name in self.registry.list_skills():
|
|
skill = self.registry.get_skill(skill_name)
|
|
if skill:
|
|
self.composer.register(skill)
|
|
|
|
def list_available_skills(self) -> Dict[str, List[str]]:
|
|
"""列出所有可用技能"""
|
|
skills_by_category = {}
|
|
for category in SkillCategory:
|
|
skills_by_category[category.value] = self.registry.list_skills(category)
|
|
return skills_by_category
|
|
|
|
def get_skill_info(self, skill_name: str) -> Optional[Dict]:
|
|
"""获取技能详细信息"""
|
|
return self.registry.get_skill_spec(skill_name)
|
|
|
|
def create_workflow(self, workflow_name: str, skill_names: List[str]) -> CompositeSkill:
|
|
"""创建工作流(顺序组合技能)"""
|
|
return self.composer.sequential(*skill_names)
|
|
|
|
def execute_skill(self, skill_name: str, **kwargs) -> SkillResult:
|
|
"""执行单个技能"""
|
|
skill = self.registry.get_skill(skill_name)
|
|
if skill:
|
|
return skill.execute(**kwargs)
|
|
return SkillResult(success=False, error=f"Skill not found: {skill_name}")
|
|
|
|
def create_parallel_analysis(self, skill_names: List[str]) -> CompositeSkill:
|
|
"""创建并行分析工作流"""
|
|
def default_merge(results):
|
|
merged = {}
|
|
for r in results:
|
|
if r.success and isinstance(r.data, dict):
|
|
merged.update(r.data)
|
|
return merged
|
|
|
|
return self.composer.parallel(*skill_names, merge_func=default_merge)
|
|
|
|
|
|
# ==================== 预定义工作流 ====================
|
|
|
|
class CommonWorkflows:
|
|
"""常用分析工作流"""
|
|
|
|
@staticmethod
|
|
def impact_analysis(manager: QGISSkillManager) -> CompositeSkill:
|
|
"""
|
|
影响范围分析工作流
|
|
|
|
流程:加载数据 → 缓冲区分析 → 导出结果
|
|
"""
|
|
return manager.create_workflow(
|
|
"impact_analysis",
|
|
["load_vector_layer", "buffer_analysis", "export_geojson"]
|
|
)
|
|
|
|
@staticmethod
|
|
def site_analysis(manager: QGISSkillManager) -> CompositeSkill:
|
|
"""
|
|
场地分析工作流
|
|
|
|
流程:加载 → 计算面积 → 统计 → 可视化
|
|
"""
|
|
return manager.create_workflow(
|
|
"site_analysis",
|
|
["load_vector_layer", "calculate_area", "calculate_statistics"]
|
|
)
|
|
|
|
@staticmethod
|
|
def comprehensive_analysis(manager: QGISSkillManager) -> CompositeSkill:
|
|
"""
|
|
综合分析工作流(并行+顺序)
|
|
|
|
流程:加载 → [缓冲区分析 | 统计计算] → 导出
|
|
"""
|
|
load = manager.registry.get_skill("load_vector_layer")
|
|
|
|
# 先加载数据
|
|
load_result = load.execute(path="sites.shp", layer_name="sites")
|
|
|
|
# 然后并行执行多个分析
|
|
parallel_analysis = manager.create_parallel_analysis([
|
|
"buffer_analysis", "calculate_area", "calculate_statistics"
|
|
])
|
|
|
|
# 最后导出
|
|
return manager.composer.sequential(
|
|
"buffer_analysis",
|
|
"calculate_area",
|
|
"export_geojson"
|
|
)
|
|
|
|
|
|
# ==================== 演示程序 ====================
|
|
|
|
def demonstrate_skill_system():
|
|
"""演示技能系统"""
|
|
print("=" * 70)
|
|
print("QGIS技能集成管理器演示")
|
|
print("=" * 70)
|
|
|
|
# 创建管理器
|
|
manager = QGISSkillManager()
|
|
|
|
# 1. 列出所有可用技能
|
|
print("\n1. 可用技能列表:")
|
|
print("-" * 70)
|
|
skills_by_category = manager.list_available_skills()
|
|
for category, skills in skills_by_category.items():
|
|
if skills:
|
|
print(f"\n{category.upper()}:")
|
|
for skill in skills:
|
|
info = manager.get_skill_info(skill)
|
|
print(f" - {skill}: {info['description']}" if info else f" - {skill}")
|
|
|
|
# 2. 执行单个技能
|
|
print("\n2. 执行单个技能(加载矢量图层):")
|
|
print("-" * 70)
|
|
result = manager.execute_skill(
|
|
"load_vector_layer",
|
|
path="data/sites.shp",
|
|
layer_name="ecological_sites"
|
|
)
|
|
print(f"成功: {result.success}")
|
|
print(f"数据: {json.dumps(result.data, indent=2)}")
|
|
|
|
# 3. 执行复合技能(顺序组合)
|
|
print("\n3. 执行顺序复合技能(影响范围分析):")
|
|
print("-" * 70)
|
|
workflow = manager.create_workflow(
|
|
"impact_analysis",
|
|
["load_vector_layer", "buffer_analysis"]
|
|
)
|
|
result = workflow.execute(
|
|
path="data/sources.shp",
|
|
layer_name="sources",
|
|
distance=500
|
|
)
|
|
print(f"成功: {result.success}")
|
|
print(f"最终数据: {json.dumps(result.data, indent=2)}")
|
|
|
|
# 4. 执行并行技能
|
|
print("\n4. 执行并行复合技能(多个分析同时进行):")
|
|
print("-" * 70)
|
|
parallel_workflow = manager.create_parallel_analysis([
|
|
"calculate_area",
|
|
"calculate_statistics"
|
|
])
|
|
result = parallel_workflow.execute(
|
|
layer="test_layer",
|
|
field="area_ha"
|
|
)
|
|
print(f"成功: {result.success}")
|
|
print(f"合并数据: {json.dumps(result.data, indent=2)}")
|
|
|
|
# 5. 创建自定义工作流
|
|
print("\n5. 创建自定义工作流(场地分析):")
|
|
print("-" * 70)
|
|
custom_workflow = CommonWorkflows.site_analysis(manager)
|
|
print(f"工作流名称: {custom_workflow.name}")
|
|
print(f"包含技能: {[s.name for s in custom_workflow.skills]}")
|
|
|
|
# 6. 条件组合示例
|
|
print("\n6. 条件组合示例(根据文件大小选择处理方式):")
|
|
print("-" * 70)
|
|
|
|
def is_large_file(input_data):
|
|
return input_data.get('feature_count', 0) > 1000
|
|
|
|
conditional_workflow = manager.composer.conditional(
|
|
condition=is_large_file,
|
|
true_skill="buffer_analysis", # 大文件用简单缓冲
|
|
# false_skill 可选
|
|
)
|
|
|
|
result = conditional_workflow.execute(
|
|
layer="large_dataset",
|
|
distance=100
|
|
)
|
|
print(f"条件结果: {result.success}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
demonstrate_skill_system()
|
|
```
|
|
|
|
---
|
|
|
|
## 案例分析
|
|
|
|
### Claude Code的技能系统
|
|
|
|
Claude Code使用**Frontmatter-based技能定义**,支持热加载和动态发现:
|
|
|
|
```python
|
|
"""
|
|
Claude Code风格的技能系统
|
|
|
|
技能定义在 .md 文件中,包含 YAML frontmatter
|
|
"""
|
|
|
|
# 示例:commit_skill.md
|
|
"""
|
|
---
|
|
name: commit
|
|
description: Create git commits with staged changes
|
|
category: git
|
|
parameters:
|
|
- name: message
|
|
type: string
|
|
description: Commit message
|
|
required: true
|
|
---
|
|
|
|
This skill creates git commits following best practices:
|
|
1. Runs git status and git diff first
|
|
2. Analyzes changes to draft commit message
|
|
3. Stages specific files (not all)
|
|
4. Creates commit with co-author tag
|
|
"""
|
|
|
|
class ClaudeCodeStyleSkill:
|
|
"""Claude Code风格的技能加载"""
|
|
|
|
def __init__(self, skills_dir: Path):
|
|
self.skills_dir = skills_dir
|
|
self.skills = {}
|
|
|
|
def load_skills(self):
|
|
"""从目录加载所有技能"""
|
|
for md_file in self.skills_dir.glob("*.md"):
|
|
skill = self._parse_skill_file(md_file)
|
|
if skill:
|
|
self.skills[skill['name']] = skill
|
|
|
|
def _parse_skill_file(self, file_path: Path) -> Optional[Dict]:
|
|
"""解析技能文件"""
|
|
content = file_path.read_text()
|
|
|
|
# 解析 frontmatter
|
|
if content.startswith('---'):
|
|
parts = content.split('---', 2)
|
|
if len(parts) >= 3:
|
|
import yaml
|
|
frontmatter = yaml.safe_load(parts[1])
|
|
body = parts[2]
|
|
return {
|
|
'name': frontmatter.get('name'),
|
|
'description': frontmatter.get('description'),
|
|
'parameters': frontmatter.get('parameters', []),
|
|
'instructions': body.strip()
|
|
}
|
|
return None
|
|
|
|
def get_skill_prompt(self, skill_name: str) -> str:
|
|
"""获取技能的执行提示"""
|
|
if skill_name in self.skills:
|
|
skill = self.skills[skill_name]
|
|
return f"""
|
|
Skill: {skill['name']}
|
|
Description: {skill['description']}
|
|
|
|
Parameters:
|
|
{self._format_parameters(skill['parameters'])}
|
|
|
|
Instructions:
|
|
{skill['instructions']}
|
|
"""
|
|
return ""
|
|
|
|
def _format_parameters(self, params: list) -> str:
|
|
return "\n".join(
|
|
f" - {p['name']}: {p['description']}"
|
|
for p in params
|
|
)
|
|
```
|
|
|
|
### ENAgent的技能组合
|
|
|
|
ENAgent将生态网络分析分解为可组合技能:
|
|
|
|
```python
|
|
class ENAgentSkills:
|
|
"""
|
|
ENAgent技能集
|
|
|
|
将六阶段分析流程分解为可复用技能
|
|
"""
|
|
|
|
# 数据处理技能
|
|
skills_data = [
|
|
"load_landcover",
|
|
"load_elevation",
|
|
"normalize_raster",
|
|
"reclassify_landcover",
|
|
]
|
|
|
|
# 空间分析技能
|
|
skills_spatial = [
|
|
"identify_core_areas",
|
|
"calculate_resistance",
|
|
"compute_mcr",
|
|
"extract_corridors",
|
|
]
|
|
|
|
# 可视化技能
|
|
skills_viz = [
|
|
"map_sources",
|
|
"map_resistance_surface",
|
|
"map_corridors",
|
|
"export_report",
|
|
]
|
|
|
|
def build_analysis_workflow(self, requirements: Dict) -> CompositeSkill:
|
|
"""
|
|
根据需求构建分析工作流
|
|
|
|
Args:
|
|
requirements: 包含分析需求的字典
|
|
"""
|
|
selected_skills = []
|
|
|
|
# 数据准备阶段
|
|
if requirements.get('data_sources'):
|
|
selected_skills.extend(self.skills_data)
|
|
|
|
# 分析阶段
|
|
if requirements.get('identify_sources'):
|
|
selected_skills.append("identify_core_areas")
|
|
if requirements.get('build_resistance'):
|
|
selected_skills.extend([
|
|
"calculate_resistance",
|
|
"compute_mcr"
|
|
])
|
|
if requirements.get('extract_corridors'):
|
|
selected_skills.append("extract_corridors")
|
|
|
|
# 输出阶段
|
|
if requirements.get('visualize'):
|
|
selected_skills.extend(self.skills_viz)
|
|
|
|
return self.composer.sequential(*selected_skills)
|
|
```
|
|
|
|
---
|
|
|
|
## 反思与延伸
|
|
|
|
### 思考问题
|
|
|
|
1. **技能粒度**:技能应该多细粒度?太细会有什么问题?太粗会有什么问题?
|
|
|
|
2. **接口设计**:如何设计技能接口以支持不同的数据格式?
|
|
|
|
3. **版本兼容性**:技能升级时如何保持向后兼容?
|
|
|
|
4. **技能发现**:如何让Agent自动发现和组合有用的技能?
|
|
|
|
### 延伸阅读
|
|
|
|
- **"Design Patterns: Elements of Reusable Object-Oriented Software"** - Composite Pattern
|
|
- **"Microservices Patterns"** (Richards) - 服务组合模式
|
|
- LangChain文档 - Tool/Agent composition
|
|
|
|
---
|
|
|
|
## 关键要点
|
|
|
|
1. **技能是Agent的功能单元**,具有明确定义的接口
|
|
2. **组合模式**包括顺序、并行、条件和迭代四种基本类型
|
|
3. **技能注册表**支持动态发现和加载新技能
|
|
4. **复合技能**可以像原子技能一样被使用和组合
|
|
5. **前后端分离**的设计使技能可以在不同上下文中复用
|
|
6. **元数据描述**使技能可被自动发现和组合
|