Composition et orchestration des compétences

Une seule compétence résout un seul problème, tandis que les flux de travail complexes nécessitent souvent plusieurs compétences pour travailler ensemble.

Cet article présente comment concevoir des dépendances entre les compétences et comment orchestrer des flux de travail multi-compétences dans SKILL.md.


Modèles de composition : deux approches

ModèleDescriptionScénario applicable
Orchestration séquentielleLa sortie de la compétence A est utilisée comme entrée de la compétence BPipeline de traitement de données
Invocation parallèleUtiliser plusieurs Skills simultanément et fusionner les résultatsAgrégation de données multi-sources

Claude peut effectuer des appels inter-Skills dans une seule conversation, mais ne peut « lire activement » le corps que d'un seul Skill à la fois. Les workflows complexes multi-Skills sont généralement coordonnés par un « Skill d'orchestration ».


Exemple d'orchestration séquentielle

avec« Téléverser un PDF → Extraire le texte → Générer un rapport de synthèse Word »Prenons ceci comme exemple : trois Skills collaborent en séquence.

---
name: pdf-to-word-report
description: >
  将 PDF 文档转换为 Word 格式的摘要报告,包含文字提取、
  内容分析和格式化输出三个阶段。当用户需要从 PDF 生成报告、
  提取 PDF 内容并整理为 Word 文档时触发。
---

# PDF 转 Word 摘要报告

## 工作流程

本 Skill 协调三个处理阶段,最终生成 .docx 报告。

### 阶段一:提取 PDF 文字
使用 pdf-reading Skill(位于 /mnt/skills/public/pdf-reading/)的处理逻辑:

```bash
python /mnt/skills/public/pdf-reading/scripts/extract.py \
  --input  <PDF路径> \
  --output /home/claude/extracted_text.txt
```

完成后告知用户:已提取 {N} 页内容,共 {字数} 字。

### 阶段二:生成摘要内容
对提取的文字进行内容分析,生成:
- 文档主题(1句话)
- 关键章节摘要(每章 2-3 句)
- 重要数据与结论

将摘要内容写入 /home/claude/summary.json

### 阶段三:生成 Word 报告
使用 docx Skill 的规范,调用:

```bash
python /mnt/skills/public/docx/scripts/generate.py \
  --template /mnt/skills/public/docx/assets/report_template.docx \
  --data     /home/claude/summary.json \
  --output   /mnt/user-data/outputs/report.docx
```

完成后调用 present_files 展示下载链接。

Implémentation de la composition de Skills via des scripts

Lorsque plusieurs Skills partagent des scripts, le script principal peut directement appeler les scripts d'autres Skills pour former un pipeline de traitement.

Exemple

# Chemin de fichier : scripts/pipeline_orchestrator.py
# Orchestre le pipeline de traitement de plusieurs scripts de Skill

import subprocess
import sys
import os
import json

SKILLS_BASE = "/mnt/skills/public"

def run_script(script_path: str, args: list) -> dict:
    """Exécute le script au chemin spécifié et renvoie le résultat JSON"""
    cmd = [sys.executable, script_path] + args
    result = subprocess.run(cmd, capture_output=True, text=True, timeout=120)
    if result.returncode != 0:
        return {"status": "error", "message": result.stderr}
    try:
        return json.loads(result.stdout)
    except json.JSONDecodeError:
        return {"status": "success", "raw_output": result.stdout}

def run_pipeline(pdf_path: str, output_dir: str) -> dict:
    """
Exécuter le pipeline complet de génération de rapport PDF → Word

Étape 1 : extraction de texte PDF
Étape 2 : génération du résumé de contenu (gérée par Claude, ignorée ici)
Étape 3 : Génération de document Word
    """

    os.makedirs(output_dir, exist_ok=True)
    extracted_txt = os.path.join(output_dir, "extracted.txt")
    final_docx    = os.path.join(output_dir, "report.docx")

    # Étape 1 : Extraire le texte PDF
    print("Étape 1 : Extraction du texte PDF...")
    step1 = run_script(
        f"{SKILLS_BASE}/pdf-reading/scripts/extract.py",
        ["--input", pdf_path, "--output", extracted_txt]
    )
    if step1.get("status") == "error":
        return {"status": "error", "stage": 1, "message": step1["message"]}
    print(f" {step1.get('pages', '?')} pages extraites")

    # Étape 3 : Générer un rapport Word (le résumé est généré et transmis par Claude avant que ce script soit appelé)
    summary_json = os.path.join(output_dir, "summary.json")
    if not os.path.exists(summary_json):
        return {"status": "error", "stage": 3,
                "message": "summary.json manquant, veuillez d'abord terminer l'étape 2"}

    print("Étape 3 : Génération du rapport Word...")
    step3 = run_script(
        f"{SKILLS_BASE}/docx/scripts/generate.py",
        ["--data", summary_json, "--output", final_docx]
    )
    if step3.get("status") == "error":
        return {"status": "error", "stage": 3, "message": step3["message"]}

    return {"status": "success", "output": final_docx}

if __name__ == "__main__":
    pdf_path   = sys.argv[1] if len(sys.argv) > 1 else ""
    output_dir = sys.argv[2] if len(sys.argv) > 2 else "/home/claude/pipeline_output"
    result = run_pipeline(pdf_path, output_dir)
    print(json.dumps(result, ensure_ascii=False, indent=2))

Éviter les dépendances circulaires

Lorsque la compétence A dépend de la compétence B et que la compétence B dépend de la compétence A, une dépendance circulaire se forme, rendant l’exécution impossible.

Lors de la conception de la composition des compétences, vous devez maintenir des relations de dépendance unidirectionnelles.

Correct: One-way dependency Correct: One-way dependency pdf-reading summarizer docx-writer Incorrect: Circular dependency Incorrect: Circular dependency Skill A Skill B ← Mutually dependent, cannot start

Fonctions utilitaires partagées

Les fonctions communes nécessaires à plusieurs compétences peuvent être extraites dans un module partagé afin d’éviter une implémentation en double.

Exemple

# Chemin du fichier : /home/claude/shared/file_utils.py
# Fonctions utilitaires de fichiers partagées par plusieurs Skills

import os

UPLOAD_DIR = "/mnt/user-data/uploads"
OUTPUT_DIR = "/mnt/user-data/outputs"

def resolve_upload(filename: str) -> str:
    """Analyse un nom de fichier pour obtenir le chemin de téléversement complet"""
    if os.path.isabs(filename):
        return filename  # Déjà un chemin absolu
    return os.path.join(UPLOAD_DIR, filename)

def resolve_output(filename: str) -> str:
    """Analyse un nom de fichier pour obtenir le chemin de sortie complet et s'assure que le répertoire existe"""
    if os.path.isabs(filename):
        path = filename
    else:
        path = os.path.join(OUTPUT_DIR, filename)
    os.makedirs(os.path.dirname(path), exist_ok=True)
    return path

# Utilisé dans chaque script Skill :
# import sys; sys.path.insert(0, "/home/claude/shared")
# from file_utils import resolve_upload, resolve_output
Autres extensions