Files
pengxiao a90f7adfa1 refactor(officefile): 按 md/latex/word 三层结构重组文档目录
将 Markdown 源文件移入 md/,LaTeX 工作目录保留在 latex/,
Word 导出移入 word/;删除临时脚本、调试截图和空 stub。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 14:25:21 +08:00

33 KiB

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
              适用:增量式处理

设计原理

技能接口设计

良好的技能接口是实现组合的基础:

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
            ]
        }

技能组合器

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}
        )

动态技能发现与加载

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技能集成管理器

"""
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技能定义,支持热加载和动态发现:

"""
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将生态网络分析分解为可组合技能:

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. 元数据描述使技能可被自动发现和组合