| name | structsense-skills |
|---|---|
| version | 0.4.0 |
| description | Extract structured information (named entities, key terms, resources like tools/datasets/models/benchmarks, or any target JSON schema) from unstructured text and PDFs using a model-agnostic multi-stage pipeline (extract → align to ontologies → judge → optional human feedback). Use this skill when the user asks to do NER, pull resources out of papers, convert documents to a target JSON schema (e.g. ReproSchema), or map terms to ontologies (BioPortal, OLS, OBO, BTO, CL, UBERON, NCBITaxon, etc.). Works with any LLM (Claude, GPT, Gemini, Pi, local Ollama/vLLM) — no library dependency. |
| license | Apache-2.0 |
Skill version 0.4.0. Concept mapping is now mandatory and tool-only. The pipeline refuses to silently skip alignment; it cascades local hybrid → BioPortal → ask the user for an alternate URL → hard-stop. Items with
concept_mapping_provenance: "llm_knowledge"are automatically demoted tounmappedbecause hallucinated IRIs are not acceptable output. The pipeline now also tracksvalidationcounts (passed / demoted / by_reason) and surfaces a prominenttotalsblock instats. Legacy outputs can be brought up to spec viapython -m scripts.normalize_result <file> --input <text> --llm-model <model>(idempotent). See CHANGELOG.md.
A reusable methodology for turning unstructured text and PDFs into clean, schema-conformant JSON, with optional ontology grounding and quality scoring. The patterns here are model-agnostic: they work with Claude, GPT, Gemini, Pi, or any local model.
Trigger when the user asks to:
- Extract named entities + key terms (NER) from biomedical, neuroscience, or scientific text.
- Pull resources out of papers — tools, datasets, models, benchmarks, leaderboards.
- Convert a document into a target JSON schema (e.g. ReproSchema, Croissant, a custom schema the user supplies).
- Map extracted terms to ontologies (BioPortal, OLS, OBO, BTO, CL, UBERON, NCBITaxon, MESH, …).
- Score or judge the quality of an existing extraction.
- Process a long document that needs chunking and parallel runs.
Four cooperating roles, run sequentially. Each role's output is the next role's input. Any role can use a different model.
┌───────────┐ raw text ┌────────────┐ extracted ┌───────┐ aligned ┌──────────────┐
│ EXTRACTOR │ ──────────────►│ ALIGNMENT │──────────────►│ JUDGE │────────────►│ HUMAN FB │
│ (LLM) │ │ (LLM+tool) │ │ (LLM) │ │ (optional) │
└───────────┘ └────────────┘ └───────┘ └──────────────┘
strict JSON + ontology fields + judge_score + corrections
+ provenance + remarks + revised JSON
| Stage | Job | Reads | Writes |
|---|---|---|---|
| Extractor | Find entities/resources/fields. Output strict JSON. | raw text | items with entity/name, label/type, sentence, start, end (etc.) |
| Alignment | Map each item to an ontology IRI. | extractor output | adds ontology_id, ontology_label, ontology, concept_mapping_provenance (tool or llm_knowledge) |
| Judge | Score quality of each item (0–1). | alignment output | adds judge_score, remarks |
| Human feedback | Apply corrections from a human reviewer. | judge output + user feedback | revised JSON |
You can run any subset — see references/pipeline-pattern.md.
- What kind of extraction?
- Entities + key terms (NER) → load
references/ner-extraction.md, then pick the extractor prompt by domain:- General-domain text (news, finance, biographies, generic web pages, mixed text) →
prompts/extractor-ner-general.md. - Neuroscience text — broad (behavior + systems + cellular + molecular + computational) →
prompts/extractor-ner-neuroscience.md. - CNS-cell-focused text (cell atlases, patch-seq, scRNA-seq cell typing, BICCN-style cell census — anything where cell types + markers + morphology + ephys are the subject) →
prompts/extractor-ner-cns-cells.md.
- General-domain text (news, finance, biographies, generic web pages, mixed text) →
- Tools / datasets / models / benchmarks → load
references/resource-extraction.mdandprompts/extractor-resource.md. - User has a target JSON schema → load
references/structured-extraction.mdandprompts/extractor-structured.md.
- Entities + key terms (NER) → load
- Want exhaustive recall? (almost always yes for NER) → after pass-1 extraction, run the mask-recall pass with
prompts/mask-recall-pass.md+scripts/mask_pass.py. Optionally also run mask-verify (prompts/mask-verify-pass.md) for per-item label sanity. Seereferences/ner-extraction.md→ "Two-pass strategy: mask-mode". 2b. Biomedical text? Enable the HuggingFace NER ensemble. Pass--ner-profile biomedical_broad(orcns_cells/pharmacology/genetic/clinical/minimal/all) to run specialist models alongside the LLM extractor. Every mention carries asource_modelfield; the grouped view recordsconsensus_count(how many models agreed). Seereferences/ner-models.md. Skip the ensemble for non-biomedical text or whentransformersisn't installed. - Need ontology mapping? → load
references/ontology-mapping.md. Default cascade: local hybrid athttp://localhost:8000(verify at/docs) → BioPortal → ask the user for an alternative URL → skip alignment only if declined. Don't hardcode the URL; the port and host vary across deployments. - Long document (>10 pages or > model context)? → load
references/chunking-strategy.md. Chunk → run extractor in parallel → merge → run downstream stages. - Need quality scoring? → load
prompts/judge.md. - Multiple models for cost? Use the cheapest capable model for extraction (often a small open model), a stronger model for alignment if you don't have a mapping tool, and a fast model for judging. See
references/model-selection.md.
These prevent the most common failures.
- Strict JSON output, no markdown fences. Every prompt must include
"Output strict JSON only. No prose. No markdown fences."in the system message. Settemperature: 0for extraction and alignment. - Extract EXHAUSTIVELY. For NER, emit every occurrence of every mention as a distinct item with its own
start/end. Never deduplicate by surface form. A multi-page neuroscience paper should yield hundreds to thousands of entity items, not a few hundred. If yield feels low, run the mask-recall pass (prompts/mask-recall-pass.md+scripts/mask_pass.py) — typical recovery is +30–80%. - Preserve fields downstream. Alignment, judge, and human-feedback stages add fields. They never remove existing fields and never re-key existing items.
- Record provenance. Every mapped item carries
concept_mapping_provenance: "tool" | "llm_knowledge". Never hide where a mapping came from. - Chunk and merge for inputs longer than the model's context window (or
> 25,000chars for safety on 128k models). Always re-merge by stable identifiers (sentence + char span, or itemid). - Don't invent placeholders. The agent communication contract is: extractor input is the raw text; alignment input is the extractor's JSON; judge input is the alignment's JSON. Pipe outputs cleanly — don't re-wrap or paraphrase between stages.
- Validate before returning. Parse the JSON; if parsing fails, repair-then-retry (see
references/json-output-discipline.md). Validate against the task's JSON schema inschemas/. - Always emit a
statsblock. Every final result must embed astatsblock at the top level (totals, label histogram, alignment provenance, judge score buckets, per-stage elapsed times) and print a human-readable summary to stderr. Usescripts/stats.py. This is the answer to "did the run do what it was supposed to?" — a healthy NER run on a paper has hundreds-to-thousands of entity mentions andmentions_per_unique > 1. A summary with 230 mentions andmentions_per_unique ≈ 1is the symptom of surface-form deduplication; re-run with the mask-recall pass and double-check no upstream step is collapsing duplicates. - Final-result filename convention. When writing the result to disk, name it
<input_stem>_final.json(e.g.paper.pdf→paper_final.json,note.txt→note_final.json). Honor an explicit--outonly when the user provides one. The reference helper isscripts/pipeline.py::default_output_path. - Concept-mapping cascade — and you MUST probe before declaring unavailable.
Default mapper is the local hybrid service at
http://localhost:8000. Before saying "no mapper available" you MUST run at least one probe in your current runtime:If the probe returns 200, USE the mapper. The real API schema iscurl -s -o /dev/null -w '%{http_code}\n' http://localhost:8000/docs{ "max_results": N, "text": [{"text": "...", "context": "..."}] }— NOTterms:[...]. Seeprompts/alignment-via-http.mdfor a turnkey curl + jq pipeline.- On connection refused: try BioPortal (
BIOPORTAL_API_KEY). - On further failure: ask the user for an alternative URL (ports 8001 / 8080 / 9000 / reverse-proxied paths are common) — do not give up silently.
- Only after the user declines should you skip alignment (
concept_mapping_provenance: "skipped"). - If your runtime can't reach the user's
localhost(claude.ai web app, Anthropic Skills hosted, ChatGPT cloud), say so explicitly and direct the user to the MCP bridge or tunnel options inconnecting/mcp-server.md. Don't pretend the service is unreachable when the user has it running — be explicit that the runtime is the constraint.
- On connection refused: try BioPortal (
- Always tag
source_modelprovenance. Every entity item carriessource_model(HF model id liked4data/biomedical-ner-all, orllm_ner:<model>for LLM-extracted items). The grouped view (entities_grouped[]) lists all contributing models per entity and theconsensus_count. Never strip or merge these fields. Seereferences/ner-models.md. - Document metadata lives at the top, not on every entity.
paper_title/doi/source_pathgo into a top-levelsource_metadatablock — ONCE per run.paper_location(section / page / paragraph) stays per-entity because it varies. Never repeat document-level metadata on hundreds of items. - Always emit both
entities[](raw, one per occurrence) andentities_grouped[](canonical, with merged sentences from every location). The raw list is the authoritative record for exhaustive extraction; the grouped list is what downstream consumers navigate by. Usescripts/group_by_entity.py:attach_grouped_views— it's automatic inpipeline.run(). - Canonical-shape guarantee via normalizer.
scripts/normalize_result.pyruns automatically before every save and produces the canonical shape regardless of what the LLM emitted: top-levelsource_metadata, stripped per-entitypaper_title/doi, taggedsource_modelon every item,entities_groupedattached,statsembedded. Idempotent — safe to run on already-canonical results. It's also exposed as a CLI to fix legacy result files in place. If you ever see legacy output, do not panic and do not edit by hand — runpython -m scripts.normalize_result <file>and the file is brought up to spec. - Concept mapping is MANDATORY and TOOL-ONLY. Zero hallucination.
- The pipeline never silently skips alignment. Default cascade: local hybrid (
http://localhost:8000, verify at/docs) → BioPortal (BIOPORTAL_API_KEY) → ask the user for an alternate URL → hard-stop with a clear error. OLS is no longer in the auto-cascade (it has no gene coverage); the user must opt in explicitly via--allow-ols-fallback. concept_mapping_provenance: "llm_knowledge"is strictly forbidden in canonical output. Any item carrying it is automatically demoted tounmappedbyscripts/iri_validation.pyand markedalignment_method: "validation_failed". The item itself is preserved (exhaustive extraction is not compromised); only the fabricated mapping is dropped.- Every
ontology_idis structurally validated against known IRI patterns (OBO PURLs, identifiers.org, BioPortal PURLs, EBI OLS, semanticweb.org, generic<NS>_<NUM>OWL IRIs). Malformed strings get demoted too. - The output's
stats.validationblock reportspassed/demotedcounts and the breakdown by failure reason — so you can tell at a glance whether the LLM tried to hallucinate. - If you cannot reach a real mapping tool, say so to the user and let them decide. Never invent IRIs to make the run "look complete."
- The pipeline never silently skips alignment. Default cascade: local hybrid (
The files below are intentionally separated so you only load what the current task needs.
pipeline-pattern.md— multi-stage agent pattern, how to chain stages, when to skip, resume from a saved stage.ner-extraction.md— NER methodology: entity types, output keys, edge cases, exhaustive extraction, mask-recall pass, grouped view.ner-models.md— HuggingFace + LLM ensemble: model roster, profiles by domain,source_modelprovenance, consensus count, when to skip.resource-extraction.md— resource extraction methodology (tools/datasets/models/benchmarks/leaderboards).structured-extraction.md— generic schema-driven extraction (PDF → user-supplied JSON schema).ontology-mapping.md— BioPortal REST API, OLS REST API, embedding-based hybrid retrieval, LLM-only fallback. Picking and combining backends.chunking-strategy.md— sentence-aligned chunking, parallel extraction, merge by stable key, context window math.json-output-discipline.md— schema-locked prompting, JSON repair, validation.model-selection.md— picking models per stage; OpenRouter / Ollama / vLLM / Claude / GPT / Gemini configuration.human-feedback.md— designing the human-in-the-loop review step.
extractor-ner-general.md— general-domain NER (Person, Org, Location, Product, Event, Date, …).extractor-ner-neuroscience.md— broad neuroscience NER (BrainRegion, CellType, Gene, Protein, Drug, Behavior, Disease, Method, Stimulus, Measurement, …).extractor-ner-cns-cells.md— CNS-cell-focused NER (CellClass / CellType / CellSubtype with lineage markers, morphology, ephys, layer, projection, atlas references, profiling method).mask-recall-pass.md— pass-2 that surfaces mentions pass-1 missed. Run on any of the three NER prompts above. Big recall booster (typical +30–80%).mask-verify-pass.md— per-item label sanity check via cloze (mask one entity, predict label from context). Optional precision booster.extractor-resource.md— research resource extractor (one primary Model/Dataset/Tool/Benchmark per source, withmentionsfor secondaries).extractor-structured.md— schema-driven extractor (PDF → user-supplied JSON Schema).alignment.md— ontology alignment (LLM + concept-mapping tool).alignment-via-http.md— turnkey curl + jq pipeline for calling the local hybrid/map/batchendpoint directly. Use when you have Bash + network access but no Python client (Claude Code is the common case).judge.md— per-item quality judge.humanfeedback.md— apply human reviewer edits.
ner-output.schema.json— JSON Schema for NER output.resource-output.schema.json— JSON Schema for resource output.aligned-item.schema.json— fragment schema for any aligned item (adds ontology + provenance fields).judged-item.schema.json— fragment schema for any judged item (adds judge_score + remarks).
chunking.py— sentence-aligned chunking, span re-anchoring, deduplication.json_repair.py— four-tier JSON repair (strict → fences → json-repair → truncate-to-balanced) + schema-driven LLM repair.span_validator.py— validatetext[start:end] == entity, repair from sentence context.mask_pass.py— build masked text for the mask-recall pass (offset-preserving placeholders), translate offsets back, and mask single items for the mask-verify pass.stats.py— compute thestatsblock (totals, label histogram, by-source-model histogram, alignment provenance, judge score buckets, per-stage timings) and a human-readable summary.group_by_entity.py— collapse raw mentions intoentities_grouped[]keyed by canonical(entity, label): collects allmentions, deduplicatessentencesacross locations, lists every contributingsource_model, and reportsconsensus_count. Also exposesunify_ontology_across_entities()— when the same surface form gets different ontology IDs from different chunks/models, pick the best one (tool-mapped > LLM > unmapped) and apply it to every occurrence. Both run automatically inattach_grouped_views().ner_models.py— HuggingFace NER ensemble (biomedical + clinical specialists). Default roster: d4data, BC5CDR-chem, NCBI-disease, BioBERT-genetic, plus the BENT-PubMedBERT family (Gene / Chemical / Disease / Anatomical / Cell-Type / Cell-Line / Organism / Bioprocess). Picked by profile:biomedical_broad/cns_cells/pharmacology/genetic/clinical/minimal/all. Every emitted item carries asource_modelprovenance field.normalize_result.py— idempotent post-processor. Lifts per-entitypaper_title/doiinto top-levelsource_metadata, strips the per-entity dupes, tags missingsource_model+alignment_method, inferstask_type, runs strict IRI validation (demotesllm_knowledgeand malformed IRIs), attachesentities_grouped[], and computesstats(with prominenttotalsblock at the top). Runs automatically inpipeline.pybefore saving. Also exposed as a CLI:python -m scripts.normalize_result legacy.json --input paper.txt --llm-model …. This is the safety net that guarantees the canonical shape even when the LLM ignores the prompt.iri_validation.py— strict IRI validator. Per-ontology regex patterns + permissive structural fallback. Rejectsconcept_mapping_provenance: "llm_knowledge"outright (zero hallucination policy), demotes malformed IRIs tounmapped, accepts legitimate cross-ontology mappings (e.g. CIDO results that reuse HP IRIs). Addsresult["validation"]withpassed/demotedcounts anddemoted_by_reasonbreakdown.input_loader.py— PDF / CSV / TXT / MD ingestion. PDF backends in order: GROBID (if reachable) → PyMuPDF (fitz) → pdfminer.six. Graceful fallback so it works with whichever library is installed. Also writes a sibling<stem>.txtso subsequent NER stages have stable character offsets. CLI:python -m scripts.input_loader paper.pdf [--no-grobid] [--grobid-url …].task_detection.py— auto-detect task type from a free-text task description: heuristic regex first (fast, no LLM), then LLM fallback viallm_client. Returns aTaskDetectionwithtask_type(ner/resource/structured_extraction/relation_extraction/keyphrase_extraction/…),confidence,labels,rationale.model_context.py— model context-window registry (~50 model families, longest-match wins) + token-awarecompute_downstream_chunk_size(...)for sizing alignment/judge/humanfeedback chunks. CLI:python -m scripts.model_context openrouter/anthropic/claude-sonnet-4-6 --items 2000 --workers 8.bioportal_map.py— throttled + LRU-cached BioPortal client.ols_map.py— EBI OLS client (no API key).local_hybrid_map.py— client for a self-hosted BM25+dense mapping service (one POST, many terms).llm_client.py— provider-agnostic LLM call (OpenAI / OpenRouter / Anthropic / Ollama / Gemini).pipeline.py— reference end-to-end pipeline (extract → align → judge) wiring the helpers together.
ner-example.md— end-to-end NER worked example.resource-example.md— end-to-end resource extraction worked example.reproschema-example.md— end-to-end PDF → ReproSchema worked example.
claude-code.md— install as a Claude Code skill (~/.claude/skills/or.claude/skills/). Auto-discovery via theSKILL.mdfrontmatter.pi-dev.md— install as a Pi skill (~/.pi/agent/skills/,~/.agents/skills/, or.pi/skills/). Pi is a CLI coding agent with native Agent Skills support and a built-inbashtool, so it runs the pipeline directly — same story as Claude Code.claude-desktop.md— Claude Desktop has a split execution model: chat UI on your machine, code interpreter in Anthropic's cloud sandbox (so it cannot reach yourlocalhost:8000directly). Use the MCP server config in this guide to bridge.claude-skills.md— upload as a hosted Anthropic Skill on claude.ai or use with the Claude Agent SDK.custom-gpt.md— wire as an OpenAI Custom GPT (Instructions + Knowledge files + optional server-side Action).mcp-server.md— expose the pipeline as an MCP server so any MCP-aware client (Claude Code, Cursor, ChatGPT desktop, custom agents) can call it.
If you remember nothing else, remember this:
Three short, focused prompts in sequence (extract, align, judge), each emitting strict JSON that the next prompt parses. Add a concept-mapping tool call inside alignment when you need real ontology IRIs. Chunk long inputs at sentence boundaries and merge by stable identifiers. Validate against a JSON schema.
That's the entire skill. The references and prompts here are the careful version of that one sentence.