-
Notifications
You must be signed in to change notification settings - Fork 7
Scaffold DAK AI Skill Library infrastructure with BPMN support #288
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
7252166
fc517b2
dd6bca0
03effd5
d03e79f
6020568
e169208
4051f58
3f3f4be
7d0ac86
efb28e4
ac83148
e91966d
c87fd25
057c33a
4dc6239
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| # DAK Skill Library — local config. Copy to .env (never commit .env). | ||
| # | ||
| # LLM features (authoring, error interpretation, classification): | ||
| # Leave blank → LLM steps skipped, structural validation still runs. | ||
| # Billed to YOUR account, not WHO. | ||
| # | ||
| # ─── API KEY ──────────────────────────────────────────────────────────── | ||
| # Get a key from your LLM provider: | ||
| # OpenAI: https://platform.openai.com/api-keys → starts with sk- | ||
| # Anthropic: https://console.anthropic.com/settings/keys → starts with sk-ant- | ||
| # Google AI: https://aistudio.google.com/app/apikey → starts with AI... | ||
| # Azure: Azure Portal → your OpenAI resource → Keys | ||
| # | ||
| # LiteLLM routes to the right provider based on the model name below. | ||
| # Leave blank to skip all LLM steps (structural validation still runs). | ||
| DAK_LLM_API_KEY= | ||
|
|
||
| # ─── MODEL ────────────────────────────────────────────────────────────── | ||
| # LiteLLM model identifier. Format: [provider/]model-name | ||
| # | ||
| # Popular options (as of 2025): | ||
| # OpenAI: gpt-4o, gpt-4o-mini, gpt-4-turbo, o1-mini | ||
| # Anthropic: claude-sonnet-4-20250514, claude-3-5-haiku-20241022 | ||
| # Google: gemini/gemini-2.0-flash, gemini/gemini-1.5-pro | ||
| # Azure: azure/your-deployment-name | ||
| # | ||
| # Master list of all supported models and provider prefixes: | ||
| # https://docs.litellm.ai/docs/providers | ||
| # | ||
| # Default: gpt-4o (requires OpenAI key above) | ||
| DAK_LLM_MODEL=gpt-4o | ||
|
|
||
| # ─── IG PUBLISHER (optional) ─────────────────────────────────────────── | ||
| # Custom FHIR terminology server. Leave blank for default (tx.fhir.org). | ||
| DAK_TX_SERVER= |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| # DAK Skill Library — Local Development Image | ||
| # Mirrors ghbuild.yml CI environment exactly. | ||
| # Base: hl7fhir/ig-publisher-base (Jekyll, Ruby, Java 17, Node.js) | ||
|
|
||
| FROM hl7fhir/ig-publisher-base:latest | ||
|
|
||
| LABEL org.opencontainers.image.title="DAK Skill Library" | ||
| LABEL org.opencontainers.image.source="https://github.com/WorldHealthOrganization/smart-base" | ||
|
|
||
| # Python packages — identical to ghbuild.yml | ||
| # --break-system-packages is required because the base image uses Debian's | ||
| # externally-managed Python; a venv is unnecessary inside a disposable container. | ||
| RUN apt-get update && apt-get install -y --no-install-recommends \ | ||
| python3 python3-pip python3-venv \ | ||
| && ln -sf /usr/bin/python3 /usr/bin/python \ | ||
| && pip3 install --break-system-packages \ | ||
| "GitPython>=3.1.40" \ | ||
| "PyYAML>=6.0" \ | ||
| "requests>=2.28.0" \ | ||
| "lxml" \ | ||
| "litellm>=1.0.0" \ | ||
| "pdfplumber" \ | ||
| "pandas" \ | ||
| && rm -rf /var/lib/apt/lists/* | ||
|
|
||
| # SUSHI — identical to ghbuild.yml | ||
| RUN npm install -g fsh-sushi | ||
|
|
||
| # IG Publisher jar — pre-baked so local runs don't need network | ||
| # Override: -v /local/publisher.jar:/app/publisher.jar | ||
| RUN mkdir -p /app/input-cache \ | ||
| && curl -L \ | ||
| https://github.com/HL7/fhir-ig-publisher/releases/latest/download/publisher.jar \ | ||
| -o /app/input-cache/publisher.jar | ||
|
|
||
| # DAK skill library | ||
| COPY . /app/skills/ | ||
|
|
||
| # Workspace — mount IG repo here: -v $(pwd):/workspace | ||
| WORKDIR /workspace | ||
|
|
||
| ENV PUBLISHER_JAR=/app/input-cache/publisher.jar | ||
| ENV DAK_IG_ROOT=/workspace | ||
|
|
||
| ENTRYPOINT ["python3", "/app/skills/cli/dak_skill.py"] | ||
| CMD ["--help"] | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,120 @@ | ||
| # DAK Skill Library | ||
|
|
||
| The DAK Skill Library provides AI-assisted and structural validation tools | ||
| for authoring WHO Digital Adaptation Kit (DAK) content. | ||
|
|
||
| ## Quick Start | ||
|
|
||
| ### Local Development (Docker) | ||
|
|
||
| ```bash | ||
| # 1. Build the image | ||
| docker build -t dak-skill .github/skills/ | ||
|
|
||
| # 2. Copy environment template | ||
| cp .env.example .env | ||
| # Edit .env to add your LLM API key (optional — structural validation works without it) | ||
|
|
||
| # 3. Run skills | ||
| docker compose -f .github/skills/docker-compose.yml run --rm validate | ||
| docker compose -f .github/skills/docker-compose.yml run --rm validate-ig | ||
| docker compose -f .github/skills/docker-compose.yml run --rm import-bpmn | ||
| docker compose -f .github/skills/docker-compose.yml run --rm shell | ||
|
|
||
| # Shortcut alias: | ||
| alias dak='docker compose -f .github/skills/docker-compose.yml run --rm' | ||
| dak validate | ||
| dak import-bpmn | ||
| ``` | ||
|
|
||
| ### CI (GitHub Actions) | ||
|
|
||
| Skills run automatically via GitHub Actions workflows: | ||
|
|
||
| | Trigger | Workflow | What it does | | ||
| |---|---|---| | ||
| | Issue opened/edited | `classify-issue.yml` | Auto-labels issues with `content:L1/L2/L3/translation` | | ||
| | Label `content:L1` | `skill-l1-review.yml` | L1 guideline review (placeholder) | | ||
| | Label `content:L2` | `skill-l2-dak.yml` | L2 DAK content authoring | | ||
| | Label `content:L3` | `skill-l3-review.yml` | L3 adaptation review (placeholder) | | ||
| | Label `content:translation` | `skill-translation.yml` | Translation management (placeholder) | | ||
| | PR comment `/validate` | `pr-validate-slash.yml` | Structural + IG validation | | ||
|
|
||
| ## One-Time Repository Setup | ||
|
|
||
| ``` | ||
| 1. Create labels (Issues → Labels → New label): | ||
| content:L1 #0075ca "WHO source guideline content" | ||
| content:L2 #e4e669 "DAK FHIR assets" | ||
| content:L3 #d73a4a "Implementation adaptations" | ||
| content:translation #0e8a16 "Translation of any content layer" | ||
| (Label definitions also stored in .github/skills/labels/*.json for reference.) | ||
|
|
||
| 2. Add secret (Settings → Secrets and variables → Actions → New repository secret): | ||
| DAK_LLM_API_KEY = sk-... | ||
|
|
||
| 3. Add variable (Settings → Secrets and variables → Variables → New variable): | ||
| DAK_LLM_MODEL = gpt-4o (or gpt-4o-mini to reduce cost) | ||
| See .env.example for the full list of supported model identifiers, | ||
| or https://docs.litellm.ai/docs/providers for the master list. | ||
|
|
||
| 4. Build local Docker image (optional, for local development): | ||
| docker build -t dak-skill .github/skills/ | ||
| ``` | ||
|
|
||
| ## Security Model | ||
|
|
||
| - **API keys MUST NOT appear** in dispatch inputs, issue comments, PR comments, or any user-visible UI | ||
| - Two legitimate locations only: **repo secret** (CI) or **local `.env` file** (Docker/local) | ||
| - LLM steps skip gracefully when no key present — non-LLM validation always runs | ||
| - **Zero WHO infrastructure cost; zero WHO AI cost** | ||
|
|
||
| ### Graceful Degradation | ||
|
|
||
| | Skill | No key | With key | | ||
| |---|---|---| | ||
| | BPMN structure validation | ✅ runs | ✅ runs | | ||
| | Swimlane ↔ ActorDef validation | ✅ runs | ✅ runs | | ||
| | IG Publisher build/validate | ✅ runs | ✅ runs | | ||
| | Issue classification | keyword fallback | LLM classification | | ||
| | LLM BPMN authoring | ⚠️ skipped | ✅ runs | | ||
| | LLM error interpretation | ⚠️ skipped | ✅ runs | | ||
|
|
||
| ## Directory Structure | ||
|
|
||
| ``` | ||
| .github/skills/ | ||
| ├── Dockerfile # FROM hl7fhir/ig-publisher-base — mirrors CI | ||
| ├── docker-compose.yml # Service aliases: validate, author, import, shell | ||
| ├── README.md # This file | ||
| ├── skills_registry.yaml # All registered skills | ||
| ├── cli/ | ||
| │ └── dak_skill.py # CLI entry point | ||
| ├── common/ | ||
| │ ├── llm_utils.py # LLM helpers — thin wrappers around LiteLLM | ||
| │ ├── prompt_loader.py # load_prompt() — .md templates with {variable} | ||
| │ ├── ig_errors.py # FATAL/ERROR/WARNING/INFORMATION format | ||
| │ ├── fsh_utils.py # FSH file utilities | ||
| │ ├── ig_publisher_iface.py | ||
| │ └── prompts/ # Shared prompt templates | ||
| ├── bpmn_author/ # Author/edit BPMN | ||
| ├── bpmn_import/ # Import BPMN → FSH, validate lanes | ||
| ├── ig_publisher/ # IG Publisher validation and build | ||
| ├── dak_authoring/ # Issue classification and L2 content review/authoring | ||
| │ ├── actions/ | ||
| │ │ ├── classify_issue_action.py # Keyword + LLM issue classifier | ||
| │ │ └── dak_authoring_action.py # L2 content review skill (→ content:L2 label) | ||
| │ └── prompts/ | ||
| ├── labels/ # GitHub label definitions (JSON, for reference) | ||
| ├── l1_review/ # (placeholder v0.2) | ||
| ├── l3_review/ # (placeholder v0.3) | ||
| └── translation/ # (placeholder v0.3) | ||
| ``` | ||
|
|
||
| ## LLM Provider | ||
|
|
||
| LLM features use [LiteLLM](https://github.com/BerriAI/litellm) (MIT License) — | ||
| a well-maintained multi-provider library (OpenAI, Anthropic, Google, etc.) | ||
| with 20k+ GitHub stars. The `common/llm_utils.py` module adds only DAK-specific | ||
| environment variable bridging and JSON-extraction helpers on top of LiteLLM; | ||
| there is no custom LLM facade to maintain. |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,73 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||||||||||
| BPMN Author action — creates or edits BPMN files via LLM, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| then validates the result structurally. | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| Environment variables: | ||||||||||||||||||||||||||||||||||||||||||||||||||
| DAK_LLM_API_KEY — LLM API key (optional; LLM steps skipped if absent) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| DAK_LLM_MODEL — LLM model name (default: gpt-4o) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| GITHUB_TOKEN — GitHub API token for issue/PR interaction | ||||||||||||||||||||||||||||||||||||||||||||||||||
| ISSUE_NUMBER — GitHub issue number | ||||||||||||||||||||||||||||||||||||||||||||||||||
| ISSUE_TITLE — Issue title | ||||||||||||||||||||||||||||||||||||||||||||||||||
| ISSUE_BODY — Issue body text | ||||||||||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| import os | ||||||||||||||||||||||||||||||||||||||||||||||||||
| import sys | ||||||||||||||||||||||||||||||||||||||||||||||||||
| from pathlib import Path | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| # Ensure the skills root is on sys.path | ||||||||||||||||||||||||||||||||||||||||||||||||||
| _SKILLS_ROOT = Path(__file__).resolve().parent.parent.parent | ||||||||||||||||||||||||||||||||||||||||||||||||||
| if str(_SKILLS_ROOT) not in sys.path: | ||||||||||||||||||||||||||||||||||||||||||||||||||
| sys.path.insert(0, str(_SKILLS_ROOT)) | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| from common.ig_errors import format_issues, has_errors | ||||||||||||||||||||||||||||||||||||||||||||||||||
| from bpmn_author.validators.bpmn_xml_validator import validate_bpmn_xml | ||||||||||||||||||||||||||||||||||||||||||||||||||
| from bpmn_author.validators.swimlane_validator import validate_swimlanes | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| def main() -> None: | ||||||||||||||||||||||||||||||||||||||||||||||||||
| api_key = os.environ.get("DAK_LLM_API_KEY", "") | ||||||||||||||||||||||||||||||||||||||||||||||||||
| if not api_key: | ||||||||||||||||||||||||||||||||||||||||||||||||||
| print("⚠️ DAK_LLM_API_KEY not set — LLM step skipped (structural validation still runs)") | ||||||||||||||||||||||||||||||||||||||||||||||||||
| sys.exit(0) | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| from common.llm_utils import dak_completion | ||||||||||||||||||||||||||||||||||||||||||||||||||
| from common.prompt_loader import load_prompt | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| issue_title = os.environ.get("ISSUE_TITLE", "") | ||||||||||||||||||||||||||||||||||||||||||||||||||
| issue_body = os.environ.get("ISSUE_BODY", "") | ||||||||||||||||||||||||||||||||||||||||||||||||||
| model = os.environ.get("DAK_LLM_MODEL", "gpt-4o") | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| # Load additional prompt components required by the create_or_edit_bpmn template | ||||||||||||||||||||||||||||||||||||||||||||||||||
| _prompts_dir = _SKILLS_ROOT / "common" / "prompts" | ||||||||||||||||||||||||||||||||||||||||||||||||||
| dak_bpmn_constraints = (_prompts_dir / "dak_bpmn_constraints.md").read_text(encoding="utf-8") | ||||||||||||||||||||||||||||||||||||||||||||||||||
| bpmn_xml_schema = (_prompts_dir / "bpmn_xml_schema.md").read_text(encoding="utf-8") | ||||||||||||||||||||||||||||||||||||||||||||||||||
| actor_context = (_prompts_dir / "actor_context.md").read_text(encoding="utf-8") | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| prompt = load_prompt( | ||||||||||||||||||||||||||||||||||||||||||||||||||
| "bpmn_author", "create_or_edit_bpmn", | ||||||||||||||||||||||||||||||||||||||||||||||||||
| user_request=f"{issue_title}\n\n{issue_body}", | ||||||||||||||||||||||||||||||||||||||||||||||||||
| current_bpmn="(none — creating new BPMN)", | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+47
to
+50
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| prompt = load_prompt( | |
| "bpmn_author", "create_or_edit_bpmn", | |
| user_request=f"{issue_title}\n\n{issue_body}", | |
| current_bpmn="(none — creating new BPMN)", | |
| # Load additional prompt components required by the create_or_edit_bpmn template | |
| dak_bpmn_constraints_path = _SKILLS_ROOT / "common" / "prompts" / "dak_bpmn_constraints.md" | |
| bpmn_xml_schema_path = _SKILLS_ROOT / "common" / "prompts" / "bpmn_xml_schema.md" | |
| actor_context_path = _SKILLS_ROOT / "common" / "prompts" / "actor_context.md" | |
| with dak_bpmn_constraints_path.open(encoding="utf-8") as f: | |
| dak_bpmn_constraints = f.read() | |
| with bpmn_xml_schema_path.open(encoding="utf-8") as f: | |
| bpmn_xml_schema = f.read() | |
| with actor_context_path.open(encoding="utf-8") as f: | |
| actor_context = f.read() | |
| prompt = load_prompt( | |
| "bpmn_author", | |
| "create_or_edit_bpmn", | |
| user_request=f"{issue_title}\n\n{issue_body}", | |
| current_bpmn="(none — creating new BPMN)", | |
| dak_bpmn_constraints=dak_bpmn_constraints, | |
| bpmn_xml_schema=bpmn_xml_schema, | |
| actor_context=actor_context, |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| # Create or Edit BPMN | ||
|
|
||
| You are a BPMN 2.0 authoring assistant for WHO Digital Adaptation Kits (DAKs). | ||
|
|
||
| ## Your Task | ||
|
|
||
| {user_request} | ||
|
|
||
| ## Constraints | ||
|
|
||
| {dak_bpmn_constraints} | ||
|
|
||
| ## BPMN XML Schema | ||
|
|
||
| {bpmn_xml_schema} | ||
|
|
||
| ## Actor Context | ||
|
|
||
| {actor_context} | ||
|
|
||
| ## Current BPMN (if editing) | ||
|
|
||
| ```xml | ||
| {current_bpmn} | ||
| ``` | ||
|
|
||
| ## Instructions | ||
|
|
||
| 1. Generate valid BPMN 2.0 XML following the constraints above. | ||
| 2. Use meaningful lane IDs that can serve as FSH instance identifiers. | ||
| 3. Ensure every task is assigned to exactly one lane. | ||
| 4. Include sequence flows connecting all elements. | ||
| 5. Return ONLY the BPMN XML — no explanation, no markdown fences. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| # Validate BPMN | ||
|
|
||
| Review the following BPMN XML for compliance with WHO DAK constraints. | ||
|
|
||
| ## BPMN XML | ||
|
|
||
| ```xml | ||
| {bpmn_xml} | ||
| ``` | ||
|
|
||
| ## Validation Results (structural) | ||
|
|
||
| {validation_results} | ||
|
|
||
| ## Instructions | ||
|
|
||
| Summarize the validation findings. For each issue: | ||
| 1. Explain what is wrong and why it matters for DAK compliance. | ||
| 2. Suggest a specific fix. | ||
|
|
||
| If there are no issues, confirm the BPMN is valid. | ||
| Return your analysis as JSON: | ||
| ```json | ||
| {{ | ||
| "valid": true/false, | ||
| "summary": "...", | ||
| "issues": [ | ||
| {{"code": "...", "severity": "...", "message": "...", "fix": "..."}} | ||
| ] | ||
| }} | ||
| ``` |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| # bpmn_author skill | ||
| name: bpmn_author | ||
| version: "0.1.0" | ||
| description: Author and edit standard BPMN 2.0 XML for DAK business processes | ||
|
|
||
| commands: | ||
| - name: create-bpmn | ||
| description: Create a new BPMN file from a natural-language description | ||
| requires_llm: true | ||
| - name: edit-bpmn | ||
| description: Edit an existing BPMN file based on instructions | ||
| requires_llm: true | ||
| - name: validate-bpmn | ||
| description: Validate BPMN structure and DAK constraints (no LLM needed) | ||
| requires_llm: false | ||
|
|
||
| validators: | ||
| - bpmn_xml_validator | ||
| - swimlane_validator | ||
|
|
||
| prompts: | ||
| - create_or_edit_bpmn | ||
| - validate_bpmn |
Uh oh!
There was an error while loading. Please reload this page.