This workflow follows the Execute Workflow Trigger → HTTP Request recipe pattern — see all workflows that pair these two integrations.
The workflow JSON
Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →
{
"name": "local_02_V1.4",
"nodes": [
{
"parameters": {
"tableName": "tags_gemini_2.5_pro",
"columnsUi": {
"columnValues": [
{
"columnName": "cluster",
"columnValue": "={{ $json.cluster }}"
},
{
"columnName": "schlagwort",
"columnValue": "={{ $json.schlagwort }}"
},
{
"columnName": "gnd_name",
"columnValue": "={{ $json.gnd_name }}"
},
{
"columnName": "gnd_id",
"columnValue": "={{ $json.gnd_id }}"
},
{
"columnName": "confidence",
"columnValue": "={{ $json.confidence }}"
},
{
"columnName": "llm2",
"columnValue": "={{ $json.llm2 }}"
},
{
"columnName": "llm3",
"columnValue": "={{ $json.llm3 }}"
},
{
"columnName": "llm4",
"columnValue": "={{ $json.llm4 }}"
},
{
"columnName": "status",
"columnValue": "={{ $json.status }}"
},
{
"columnName": "hinweis",
"columnValue": "={{ $json.kritischer_hinweis }}"
},
{
"columnName": "audit1",
"columnValue": "={{ $json.audit_1_relevance }}"
},
{
"columnName": "audit2",
"columnValue": "={{ $json.audit_2_vision }}"
},
{
"columnName": "audit3",
"columnValue": "={{ $json.audit_3_syntax }}"
},
{
"columnName": "audit4",
"columnValue": "={{ $json.audit_4_sovereignty }}"
},
{
"columnName": "audit5",
"columnValue": "={{ $json.audit_5_museo }}"
},
{
"columnName": "status2",
"columnValue": "={{ $json.Protokoll_Status }}"
},
{
"columnName": "config",
"columnValue": "=# n8n Workflow \u2014 KI-Modell-Konfiguration\nVersion: 1.2\nStand: 2026-06-08\nVerschlagwortung Museumsobjekte (Stadtmuseum Berlin)\nWorkflows: local_01 \u2192 local_02 \u2192 local_03\n\n## \u00c4NDERUNGEN GEGEN\u00dcBER v1.1\n- Synthese-Schritt (LLM1b) wieder eingef\u00fchrt: Gemma-4-12B-QAT als Schiedsrichter\n- LLM2 erh\u00e4lt jetzt master_caption (prim\u00e4r) + caption_1 + caption_2 (zur Verifikation)\n- LLM2 System-Prompt angepasst: master_caption als prim\u00e4re Wahrheit, rohe Captions als Fallback\n- LLM3 System-Prompt angepasst: zwei Captions als Input statt eine synthetisierte\n- LLM2 System-Prompt: Funktion_Zweck Verbotsliste erg\u00e4nzt (Dokumentation, Darstellung etc.)\n- LLM2 System-Prompt: Kultureller_Kontext Geografie/Datierungen mit Beispielen explizit verboten\n- LLM2 System-Prompt: Abschnitt 7 Technische Ausschl\u00fcsse mit Beispielen versch\u00e4rft\n\n\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n\n## \u00dcBERBLICK: MODELLE IM WORKFLOW\n\n| Modell | Workflow | Stufe | Aufgabe |\n|---|---|---|---|\n| `qwen3.6-35b-a3b-mtp-q4` | local_02 | Caption 1/2 | Caption v2: parallel mit Gemma (Vision) |\n| `gemma-4-26b-a4b-qat` | local_02 | Caption 2/2 | Caption v2: parallel mit Qwen (Vision) |\n| `gemma-4-12b-qat` | local_02 | LLM1b | Synthese/Schiedsrichter (Vision) |\n| `qwen3.6-27b-mtp` | local_02 | LLM2 | Tagging (Vision + master_caption + 2 rohe Captions) |\n| `gemma-4-31b-it` | local_02 | LLM3 | Judge-Audit (Vision + JSON-Schema) |\n| `qwen3.6-35b-mtp` | local_03 | LLM1 | GND-Batch Retrieve-then-Judge (text-only) |\n\n**API-Endpunkt (alle Calls):** `YOUR_LLM_ENDPOINT/v1/chat/completions`\n**Auth:** `Bearer YOUR_API_KEY`\n**GND-Suche:** `YOUR_OPENSEARCH_ENDPOINT/_msearch`\n\n\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n\n## STUFE 1 \u2014 CAPTION-PIPELINE (local_02)\n\n### Caption v2: Qwen + Gemma parallel\n**Node:** `Code in JavaScript3`\n\n**Qwen** (`qwen3.6-35b-a3b-mtp-q4`):\n```\ntemperature: 0.1\ntop_p: 0.9\nmax_tokens: 2500\nreasoning_budget: 512\ntimeout: 600000 ms\n```\n\n**Gemma** (`gemma-4-26b-a4b-qat`):\n```\ntemperature: 0.1\ntop_p: 0.9\nmax_tokens: 2500\ntimeout: 600000 ms\n```\n\nBeide laufen via `Promise.allSettled()` parallel im `caption_v2` llama-swap Set.\nIndividueller Fallback pro Modell wenn eines ausf\u00e4llt.\nOutput: `res_qwen` + `res_gemma`\n\n---\n\n### LLM1b \u2014 Synthese/Schiedsrichter\n**Node:** `Code in JavaScript (Synthese)`\n**Modell:** `gemma-4-12b-qat`\n\n```\ntemperature: 0.1\ntop_p: 0.9\nmax_tokens: 3000\nenable_thinking: false\ntimeout: 300000 ms\nimage_min_tokens: 140\nimage_max_tokens: 280\n```\n\nVision: ja \u2014 Originalbild als base64.\nInput: Originalbild + res_qwen + res_gemma.\nAufgabe: Schiedsrichter \u2014 pr\u00fcft beide Captions gegen das Bild, verwirft Halluzinationen,\nmarkiert Unsicherheiten, erstellt konsolidierten Befund.\nFallback: res_qwen wenn Synthese ausf\u00e4llt.\nOutput: `master_caption` + `res_qwen` + `res_gemma` + `caption_1` + `caption_2`\n\n\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n\n## STUFE 2 \u2014 TAGGING (local_02)\n\n### LLM2 \u2014 Schlagwort-Generator\n**Node:** `Code in JavaScript6` \u2192 `HTTP Request3`\n**Modell:** `qwen3.6-27b-mtp`\n\n```\ntemperature: 0.2\ntop_p: 0.8\ntop_k: 20\npresence_penalty: 1.5\nmax_tokens: 4096\nresponse_format: json_object\nenable_thinking: false\ntimeout: 2000000 ms\n```\n\nVision: ja \u2014 Originalbild als base64 inline eingebettet.\nInput: Originalbild + master_caption (prim\u00e4r) + caption_1 (Verifikation) + caption_2 (Verifikation) + Museumsmetadaten.\nmaster_caption ist prim\u00e4re Wahrheit. Rohe Captions als Fallback wenn Bild etwas anderes zeigt.\nOutput: JSON mit 11 Clustern (Objekttyp, Thema_Ph\u00e4nomen, Inhalt_Motiv, Funktion_Zweck,\nVisuelle_Merkmale, Form_Gestalt, Bestandteile, Gebrauchskontext,\nKultureller_Kontext, Emotion_Atmosph\u00e4re, Farbe_Nuancen).\n\n\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n\n## STUFE 3 \u2014 JUDGE-AUDIT (local_02)\n\n### LLM3 \u2014 Senior-Revisions-Kustos\n**Node:** `Code in JavaScript7` \u2192 `HTTP Request`\n**Modell:** `gemma-4-31b-it`\n\n```\ntemperature: 0.1\ntop_p: 0.8\ntop_k: 20\nmax_tokens: 10000\nenable_thinking: false\nresponse_format: json_schema (strict, name: \"llm3_audit_verdict\")\ntimeout: 300000 ms\n```\n\nVision: ja \u2014 Bild wird als base64 inline eingebettet (data:image/jpeg;base64,...).\nInput: Originalbild + master_caption + caption_1 + caption_2 + Metadaten + LLM2-Schlagworte + LLM2-Reasoning.\nOutput: Ampel-Triage pro Schlagwort (green / yellow / red) + Decolonial Audit Log.\n\n**JSON-Schema (response_format, strict):**\n```\n_decolonial_audit_log: { 1_Relevance_Check, 2_Vision_Check, 3_Syntax_Check,\n 4_Sovereignty_Check, 5_Museo_Check }\nProtokoll_Status: enum [\"Open Access\", \"Restricted\", \"Sensitive\"]\n_provenance_critique: string | null\nKritischer_Hinweis: string | null\n[11 Cluster]: Array<{ term, why, status (green|yellow|red), judge_comment }>\n```\n\n\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n\n## STUFE 4 \u2014 GND-ABGLEICH (local_03)\n\n### LLM1 \u2014 GND-Batch Retrieve-then-Judge\n**Node:** `LLM1_batched`\n**Modell:** `qwen3.6-35b-mtp`\n\n```\ntemperature: 0.1\ntop_p: 0.8\ntop_k: 20\npresence_penalty: 0.0\nfrequency_penalty: 0.0\nmax_tokens: 2048\nresponse_format: json_object\nenable_thinking: false\nconcurrency: 8\ntimeout: 600000 ms\n```\n\nText-only. L\u00e4uft als Batch mit Concurrency 8 (== -np des Servers).\nVorgeschalteter OpenSearch-Bulk-Request (_msearch) liefert pro Begriff 5 GND-Kandidaten.\nLLM1 w\u00e4hlt den besten Kandidaten aus oder gibt no_match zur\u00fcck.\n\n**Output-Schema pro Begriff:**\n```json\n{\n \"gnd_id\": \"ID aus Kandidaten oder null\",\n \"gnd_preferred_name\": \"Name laut Kandidat oder null\",\n \"gnd_confidence\": \"high | medium | low | no_match\",\n \"reasoning\": \"Begr\u00fcndung\",\n \"debug_tags\": \"DESCRIPTIVE_NO_GND | DEFINITION_MISMATCH | MULTIPLE_CANDIDATES | LOW_FUZZY_OVERLAP | NONE\",\n \"additional_data\": {\n \"alternate_names\": \"Synonyme oder null\",\n \"definition\": \"Definition oder null\"\n }\n}\n```\n\n\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n\n## TIMEOUTS GESAMT\n\n| Stufe | Modell | Timeout |\n|---|---|---|\n| LLM2 Tagging | qwen3.6-27b-mtp | 2000000 ms (33 min) |\n| LLM1 GND-Batch | qwen3.6-35b-mtp | 600000 ms (10 min) |\n| Caption v2 parallel | qwen3.6-35b-a3b-mtp-q4 + gemma-4-26b-a4b-qat | 600000 ms (10 min) |\n| LLM1b Synthese | gemma-4-12b-qat | 300000 ms (5 min) |\n| LLM3 Judge | gemma-4-31b-it | 300000 ms (5 min) |"
}
]
}
},
"type": "n8n-nodes-base.seaTable",
"typeVersion": 2,
"position": [
224,
1680
],
"id": "6b97083f-bc37-44c8-9522-8c219b0f5402",
"name": "Create a row",
"credentials": {
"seaTableApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"operation": "update",
"tableName": "metadata",
"rowId": "={{ $('Start').item.json.metadata_clean._id }}",
"columnsUi": {
"columnValues": [
{
"columnName": "processed",
"columnValue": "ok"
},
{
"columnName": "newest",
"columnValue": "ok_1.3"
}
]
}
},
"type": "n8n-nodes-base.seaTable",
"typeVersion": 2,
"position": [
864,
1680
],
"id": "85036281-1768-4061-8a36-8ad649e85c55",
"name": "Update a row",
"credentials": {
"seaTableApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"resource": "link",
"tableName": "tags_gemini_2.5_pro:::fvU0",
"linkColumn": "=parent_id:::k11D:::IQEj",
"linkColumnSourceId": "={{ $('Create a row').item.json._id }}",
"linkColumnTargetId": "={{ $('Map_to_SeaTable_Schema').item.json.Objekt_Referenz }}"
},
"type": "n8n-nodes-base.seaTable",
"typeVersion": 2,
"position": [
576,
1680
],
"id": "e218df03-b3b8-48d1-8b3a-120f95b8dc8a",
"name": "Add a row link",
"credentials": {
"seaTableApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"mode": "raw",
"jsonOutput": "={{\n {\n \"Objekt_Referenz\": $('Start').item.json.metadata_clean._id,\n \"Protokoll_Status\": $json.Protokoll_Status,\n \"kritischer_hinweis\": $json.Kritischer_Hinweis,\n \"audit_1_relevance\": $json.Audit_1_Relevance,\n \"audit_2_vision\": $json.Audit_2_Vision,\n \"audit_3_syntax\": $json.Audit_3_Syntax,\n \"audit_4_sovereignty\": $json.Audit_4_Sovereignty,\n \"audit_5_museo\": $json.Audit_5_Museo,\n \"cluster\": $json.Cluster,\n \"schlagwort\": $json.Schlagwort,\n \"status\": $json.Status,\n \"gnd_name\": $json.GND_Name,\n \"gnd_id\": $json.GND_ID,\n \"confidence\": $json.Confidence,\n \"llm2\": $json.LLM2_Rationale,\n \"llm3\": $json.LLM3_Judge,\n \"llm4\": $json.LLM4_Reasoning\n }\n}}",
"options": {}
},
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
48,
1680
],
"id": "b4828023-a18b-4dff-8a41-3c7d55d22055",
"name": "Map_to_SeaTable_Schema"
},
{
"parameters": {},
"type": "n8n-nodes-base.merge",
"typeVersion": 3.2,
"position": [
-512,
1680
],
"id": "5e40ac58-a8c8-46ab-b2a5-1bdb2d86adb6",
"name": "Merge"
},
{
"parameters": {
"jsCode": "/*\n=========================================================\nWEDDING NODE (Merge Appended Branches)\n- Sammelt gnd_result_string und Triage-Daten (rejected) ein\n- Harmonisiert das Daten-Schema f\u00fcr SeaTable\n- Erstellt genau EIN finales Output-Item\n=========================================================\n*/\n\nconst items = $input.all();\n\n// 1. Variablen zum Einsammeln der Daten aus den verschiedenen Str\u00e4ngen\nlet gndString = \"{}\";\nlet rejectedData = {};\nlet topLevelMeta = {\n _decolonial_audit_log: null,\n Protokoll_Status: \"Unknown\",\n Kritischer_Hinweis: null\n};\n\n// 2. Alle eintreffenden Items durchsuchen (fangen den \"Append\" auf)\nfor (const item of items) {\n const json = item.json;\n\n // A: Hat dieses Item den GND-String?\n if (json.gnd_result_string) {\n gndString = json.gnd_result_string;\n }\n\n // B: Hat dieses Item die Triage-Ergebnisse?\n const currentRejected = json.payload_rejected || json.llm_result?.payload_rejected;\n\n if (currentRejected) {\n rejectedData = currentRejected;\n topLevelMeta._decolonial_audit_log = json._decolonial_audit_log || currentRejected._decolonial_audit_log;\n topLevelMeta.Protokoll_Status = json.Protokoll_Status || currentRejected.Protokoll_Status;\n topLevelMeta.Kritischer_Hinweis = json.Kritischer_Hinweis || currentRejected.Kritischer_Hinweis;\n }\n}\n\n// 3. Den GND-String zu einem echten JSON entpacken\nlet validatedData = {};\ntry {\n validatedData = JSON.parse(gndString);\n} catch (e) {\n validatedData = {};\n}\n\n// 4. Das finale Output-Objekt vorbereiten\nconst finalOutput = {\n _decolonial_audit_log: topLevelMeta._decolonial_audit_log,\n Protokoll_Status: topLevelMeta.Protokoll_Status,\n Kritischer_Hinweis: topLevelMeta.Kritischer_Hinweis\n};\n\nconst clusters = [\n \"Objekttyp\", \"Thema_Ph\u00e4nomen\", \"Inhalt_Motiv\", \"Funktion_Zweck\",\n \"Visuelle_Merkmale\", \"Form_Gestalt\", \"Bestandteile\", \"Gebrauchskontext\",\n \"Kultureller_Kontext\", \"Emotion_Atmosph\u00e4re\", \"Farbe_Nuancen\"\n];\n\n// 5. Cluster zusammenf\u00fchren (Die eigentliche Hochzeit)\nfor (const cluster of clusters) {\n const mergedCluster = [];\n\n // A) Validierte Begriffe (GR\u00dcN, aus dem GND-Strang)\n if (validatedData[cluster] && Array.isArray(validatedData[cluster])) {\n for (const validItem of validatedData[cluster]) {\n mergedCluster.push({\n term: validItem.term,\n status: \"green\", // hier kommen nur echte Gr\u00fcne an\n gnd_name: validItem.gnd_name || null,\n gnd_id: validItem.gnd_id || null,\n confidence: validItem.confidence || null,\n llm2_rationale: validItem.llm2_rationale || null,\n llm3_judge_comment: validItem.llm3_judge_comment || null,\n llm4_reasoning: validItem.llm4_reasoning || null\n });\n }\n }\n\n // B) Zur\u00fcckgehaltene Begriffe (GELB + ROT, aus dem Triage-Strang)\n if (rejectedData[cluster] && Array.isArray(rejectedData[cluster])) {\n for (const rejectedItem of rejectedData[cluster]) {\n mergedCluster.push({\n term: rejectedItem.term,\n status: rejectedItem.status || \"red\", // gelb bleibt gelb, rot bleibt rot\n gnd_name: null,\n gnd_id: null,\n confidence: null,\n llm2_rationale: rejectedItem.why || null,\n llm3_judge_comment: rejectedItem.judge_comment || null,\n llm4_reasoning: \"Status nicht gr\u00fcn \u2013 zur\u00fcckgehalten, kein GND-Abgleich durchgef\u00fchrt.\"\n });\n }\n }\n\n finalOutput[cluster] = mergedCluster;\n}\n\n// 6. Wir geben exakt EIN Item zur\u00fcck, das alles beinhaltet\nreturn [{ json: finalOutput }];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-320,
1680
],
"id": "84319c94-5624-4f81-a76f-3c501960b771",
"name": "Merge_GND_and_Rejected"
},
{
"parameters": {
"jsCode": "/*\n=========================================================\nFLATTEN FOR SEATABLE (1 Item pro Schlagwort)\n- L\u00f6st die Cluster auf\n- Erstellt f\u00fcr jedes Schlagwort ein eigenes n8n-Item\n- Vererbt das komplette Decolonial Audit Log an jedes Item\n- Bereitet die Spaltennamen f\u00fcr SeaTable vor\n=========================================================\n*/\n\nconst items = $input.all();\nconst flattenedKeywords = [];\n\nconst clusters = [\n \"Objekttyp\", \"Thema_Ph\u00e4nomen\", \"Inhalt_Motiv\", \"Funktion_Zweck\",\n \"Visuelle_Merkmale\", \"Form_Gestalt\", \"Bestandteile\", \"Gebrauchskontext\",\n \"Kultureller_Kontext\", \"Emotion_Atmosph\u00e4re\", \"Farbe_Nuancen\"\n];\n\nfor (const item of items) {\n const json = item.json;\n \n // Wir isolieren das Audit-Log (mit Fallback, falls es mal fehlen sollte)\n const auditLog = json._decolonial_audit_log || {};\n\n // Iteriere durch alle Cluster\n for (const cluster of clusters) {\n const keywordArray = json[cluster];\n\n // Wenn das Cluster existiert und bef\u00fcllt ist...\n if (keywordArray && Array.isArray(keywordArray)) {\n for (const kw of keywordArray) {\n \n // Erstelle ein isoliertes Item f\u00fcr jedes einzelne Schlagwort\n flattenedKeywords.push({\n json: {\n // METADATEN DES OBJEKTS\n // Objekt_ID: json.ID || \"N/A\", <-- Objekt-Referenz f\u00fcr SeaTable\n Protokoll_Status: json.Protokoll_Status || \"Unknown\",\n Kritischer_Hinweis: json.Kritischer_Hinweis || \"\",\n \n // DECOLONIAL AUDIT LOG (aufgeschl\u00fcsselt)\n Audit_1_Relevance: auditLog[\"1_Relevance_Check\"] || \"\",\n Audit_2_Vision: auditLog[\"2_Vision_Check\"] || \"\",\n Audit_3_Syntax: auditLog[\"3_Syntax_Check\"] || \"\",\n Audit_4_Sovereignty: auditLog[\"4_Sovereignty_Check\"] || \"\",\n Audit_5_Museo: auditLog[\"5_Museo_Check\"] || \"\",\n \n // SCHLAGWORT-DATEN\n Cluster: cluster,\n Schlagwort: kw.term,\n Status: kw.status,\n GND_Name: kw.gnd_name || \"\",\n GND_ID: kw.gnd_id || \"\",\n Confidence: kw.confidence || \"\",\n LLM2_Rationale: kw.llm2_rationale || \"\",\n LLM3_Judge: kw.llm3_judge_comment || \"\",\n LLM4_Reasoning: kw.llm4_reasoning || \"\"\n }\n });\n }\n }\n }\n}\n\n// Gibt eine flache Liste aller Schlagworte zur\u00fcck\nreturn flattenedKeywords;"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-144,
1680
],
"id": "ab26a547-bc17-4ea2-87cd-edbd763d22c3",
"name": "Flatten_for_SeaTable"
},
{
"parameters": {
"content": "## Keywords parsen, nach Seatable schreiben\n",
"height": 281,
"width": 1828
},
"id": "c0969698-7f71-4590-a2fd-d18026e25abc",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
-704,
1568
]
},
{
"parameters": {
"jsCode": "/*\n=========================================================\nJSON PARSER & DECOLONIAL VALIDATOR (v5 \u2014 Fehlerklassen-Entscheider)\n1. Parst die LLM-Antwort; Fallback auf Leer-Skelett bei Parse-Fehler\n2. DE-BIAS: nur 'term', exakter Ganzwort-Abgleich (REPLACE/CONTEXTUALIZE/DROP)\n3. NEU: Status wird aus fehlerklassen BERECHNET (nicht mehr vom LLM)\n Pr\u00e4zedenz: Wegwerf > Reparier > Verschieben/gr\u00fcn\n FALSCHER_CLUSTER -> Code verschiebt ins correct_cluster (bleibt gr\u00fcn)\n4. NEU: Code-Netze \u00fcber die Gr\u00fcnen: DATIERUNG (Regex) + exakte Dopplung\n5. NUR GR\u00dcN -> payload_valid (GND); Gelb + Rot -> payload_rejected\n=========================================================\n*/\n\n// ------------------------------------------------------\n// TEIL A: KONFIGURATION\n// ------------------------------------------------------\nconst DE_BIAS_MAP = {\n \"Abendland\": {\"replacement\": [\"Westasien\", \"Europa\"], \"action\": \"CONTEXTUALIZE\", \"category\": \"Eurozentrismus\"},\n \"Abnormit\u00e4tenschau\": {\"replacement\": [], \"action\": \"CONTEXTUALIZE\", \"category\": \"Ableismus\"},\n \"Afrikaner\": {\"replacement\": [\"Menschen des afrikanischen Kontinents\"], \"action\": \"CONTEXTUALIZE\", \"category\": \"Kolonialismus\"},\n \"Arisierung\": {\"replacement\": [\"Raub\", \"Enteignung\"], \"action\": \"CONTEXTUALIZE\", \"category\": \"NS-Unrecht\"},\n \"Australneger\": {\"replacement\": [\"Aborigine\"], \"action\": \"REPLACE\", \"category\": \"Rassismus\"},\n \"Bastard\": {\"replacement\": [\"Au\u00dfereheliches Kind\"], \"action\": \"REPLACE\", \"category\": \"Abwertung\"},\n \"Behinderter\": {\"replacement\": [\"Mensch mit Behinderung\"], \"action\": \"REPLACE\", \"category\": \"Ableismus\"},\n \"Berber\": {\"replacement\": [\"Imazighen\"], \"action\": \"REPLACE\", \"category\": \"Kolonialismus\"},\n \"Buschleute\": {\"replacement\": [\"San\", \"Khoisan\"], \"action\": \"CONTEXTUALIZE\", \"category\": \"Kolonialismus\"},\n \"Buschneger\": {\"replacement\": [\"Maroons\"], \"action\": \"CONTEXTUALIZE\", \"category\": \"Rassismus\"},\n \"Drittes Reich\": {\"replacement\": [\"NS-Regime\"], \"action\": \"REPLACE\", \"category\": \"Nationalsozialismus\"},\n \"Dunkelh\u00e4utig\": {\"replacement\": [\"Schwarze Menschen\", \"PoC\"], \"action\": \"REPLACE\", \"category\": \"Rassismus\"},\n \"Eingeboren\": {\"replacement\": [\"Indigen\"], \"action\": \"REPLACE\", \"category\": \"Kolonialismus\"},\n \"Endl\u00f6sung\": {\"replacement\": [\"Shoah\", \"Holocaust\"], \"action\": \"CONTEXTUALIZE\", \"category\": \"NS-Euphemismus\"},\n \"Eskimo\": {\"replacement\": [\"Inuit\"], \"action\": \"REPLACE\", \"category\": \"Kolonialismus\"},\n \"Exotisch\": {\"replacement\": [], \"action\": \"CONTEXTUALIZE\", \"category\": \"Exotismus\"},\n \"Farbig\": {\"replacement\": [], \"action\": \"DROP\", \"category\": \"Rassismus\"},\n \"Gastarbeiter\": {\"replacement\": [\"Arbeitsmigranten\"], \"action\": \"REPLACE\", \"category\": \"Migration\"},\n \"Gypsy\": {\"replacement\": [\"Roma\", \"Sinti\"], \"action\": \"CONTEXTUALIZE\", \"category\": \"Antiziganismus\"},\n \"H\u00e4uptling\": {\"replacement\": [\"Oberhaupt\"], \"action\": \"CONTEXTUALIZE\", \"category\": \"Kolonialismus\"},\n \"Hottentotte\": {\"replacement\": [\"Khoikhoi\"], \"action\": \"CONTEXTUALIZE\", \"category\": \"Rassismus\"},\n \"Indianer\": {\"replacement\": [\"Indigene Bev\u00f6lkerung\", \"Native Americans\"], \"action\": \"REPLACE\", \"category\": \"Kolonialismus\"},\n \"Irrenanstalt\": {\"replacement\": [\"Psychiatrische Klinik\"], \"action\": \"REPLACE\", \"category\": \"Ableismus\"},\n \"Kristallnacht\": {\"replacement\": [\"Novemberpogrome\"], \"action\": \"REPLACE\", \"category\": \"NS-Euphemismus\"},\n \"Kr\u00fcppel\": {\"replacement\": [\"Mensch mit Behinderung\"], \"action\": \"REPLACE\", \"category\": \"Ableismus\"},\n \"Mohr\": {\"replacement\": [\"Schwarzer Mensch\"], \"action\": \"CONTEXTUALIZE\", \"category\": \"Rassismus\"},\n \"Mongoloid\": {\"replacement\": [\"Person mit Trisomie 21\"], \"action\": \"REPLACE\", \"category\": \"Ableismus\"},\n \"Morgenland\": {\"replacement\": [\"Naher Osten\"], \"action\": \"CONTEXTUALIZE\", \"category\": \"Eurozentrismus\"},\n \"Mulatte\": {\"replacement\": [\"Person mit binationalem Hintergrund\"], \"action\": \"CONTEXTUALIZE\", \"category\": \"Rassismus\"},\n \"Muselmann\": {\"replacement\": [\"Muslim\"], \"action\": \"REPLACE\", \"category\": \"Islamfeindlichkeit\"},\n \"Naturvolk\": {\"replacement\": [\"Indigene Gemeinschaft\"], \"action\": \"REPLACE\", \"category\": \"Kolonialismus\"},\n \"Neger\": {\"replacement\": [\"Schwarzer Mensch\", \"PoC\"], \"action\": \"REPLACE\", \"category\": \"Rassismus\"},\n \"Orientalisch\": {\"replacement\": [\"Westasiatisch\", \"Arabisch\"], \"action\": \"REPLACE\", \"category\": \"Eurozentrismus\"},\n \"Rasse\": {\"replacement\": [], \"action\": \"DROP\", \"category\": \"Rassismus\"},\n \"Schwarzafrika\": {\"replacement\": [\"Subsahara-Afrika\"], \"action\": \"CONTEXTUALIZE\", \"category\": \"Rassismus\"},\n \"Taubstumm\": {\"replacement\": [\"Geh\u00f6rlos\"], \"action\": \"REPLACE\", \"category\": \"Ableismus\"},\n \"Transsexuell\": {\"replacement\": [\"Transgeschlechtlich\"], \"action\": \"REPLACE\", \"category\": \"Gender\"},\n \"Zigeuner\": {\"replacement\": [\"Roma und Sinti\"], \"action\": \"REPLACE\", \"category\": \"Antiziganismus\"},\n \"Zwerg\": {\"replacement\": [\"Kleinw\u00fcchsiger Mensch\"], \"action\": \"REPLACE\", \"category\": \"Ableismus\"}\n};\n\n// ------------------------------------------------------\n// TEIL B: HELFER\n// ------------------------------------------------------\nfunction normalizeTerm(t) {\n return String(t === null || t === undefined ? \"\" : t).trim().toLowerCase();\n}\n\nconst BIAS_LOOKUP = {};\nfor (const [key, rule] of Object.entries(DE_BIAS_MAP)) {\n BIAS_LOOKUP[normalizeTerm(key)] = { original: key, ...rule };\n}\n\nconst META_KEYS = [\"_decolonial_audit_log\", \"Protokoll_Status\", \"_provenance_critique\", \"Kritischer_Hinweis\"];\n\nconst CLUSTERS = [\n \"Objekttyp\", \"Thema_Ph\u00e4nomen\", \"Inhalt_Motiv\", \"Funktion_Zweck\",\n \"Visuelle_Merkmale\", \"Form_Gestalt\", \"Bestandteile\", \"Gebrauchskontext\",\n \"Kultureller_Kontext\", \"Emotion_Atmosph\u00e4re\", \"Farbe_Nuancen\"\n];\n\n// Fehlerklassen -> Status\nconst DISCARD_CLASSES = new Set([\n \"KEINE_EVIDENZ\",\"HALLUZINATION\",\"MATERIAL_PUR\",\"MATERIAL_TECHNIK\",\"DATIERUNG\",\n \"GEOGRAFIE_EIGENNAME\",\"SPEKULATION_ZIRKEL\",\"ASYMMETRIE\",\"REDUNDANZ\"\n]);\nconst REPAIR_CLASSES = new Set([\"PLURAL\",\"ADJEKTIV\",\"KOMPOSITUM\",\"FEHLENDE_BRUECKE\",\"MATERIAL_KOMPOSITUM\"]);\nconst MOVE_CLASS = \"FALSCHER_CLUSTER\";\n\n// Leer-Skelett bei unparsebarer LLM3-Antwort\nfunction buildEmptySkeleton(note) {\n const skel = {\n _decolonial_audit_log: {\n \"1_Relevance_Check\": \"\", \"2_Vision_Check\": \"\", \"3_Syntax_Check\": \"\",\n \"4_Sovereignty_Check\": \"\", \"5_Museo_Check\": \"\"\n },\n Protokoll_Status: \"Restricted Access (Check Local Contexts)\",\n _provenance_critique: null,\n Kritischer_Hinweis: note\n };\n for (const c of CLUSTERS) skel[c] = [];\n return skel;\n}\n\n// DE-BIAS: pr\u00fcft NUR term (exakter Ganzwort-Abgleich)\nfunction applyBiasPolicyToTerms(parsedData, warningsList) {\n for (const [key, value] of Object.entries(parsedData)) {\n if (META_KEYS.includes(key) || !Array.isArray(value)) continue;\n const kept = [];\n for (const entry of value) {\n if (!entry || typeof entry.term !== \"string\") { kept.push(entry); continue; }\n const rule = BIAS_LOOKUP[normalizeTerm(entry.term)];\n if (!rule) { kept.push(entry); continue; }\n\n if (rule.action === \"DROP\") {\n warningsList.push(`Begriff '${entry.term}' automatisch entfernt (${rule.category}).`);\n continue;\n }\n if (rule.action === \"REPLACE\" && rule.replacement && rule.replacement.length > 0) {\n warningsList.push(`Begriff '${entry.term}' ersetzt durch '${rule.replacement[0]}' (${rule.category}).`);\n entry.term = rule.replacement[0];\n kept.push(entry);\n continue;\n }\n if (rule.action === \"CONTEXTUALIZE\") {\n if (!entry.term.includes(\"[sic!]\")) entry.term = `${entry.term} [sic!]`;\n warningsList.push(`SENSITIV: Begriff '${rule.original}' markiert (${rule.category}).`);\n kept.push(entry);\n continue;\n }\n kept.push(entry);\n }\n parsedData[key] = kept;\n }\n}\n\n// Status aus fehlerklassen berechnen + FALSCHER_CLUSTER verschieben\nfunction deriveStatusAndMove(parsedData) {\n const moves = [];\n for (const cluster of CLUSTERS) {\n const arr = Array.isArray(parsedData[cluster]) ? parsedData[cluster] : [];\n for (const it of arr) {\n if (!it || typeof it !== \"object\") continue;\n const flags = Array.isArray(it.fehlerklassen) ? it.fehlerklassen : [];\n\n let status = \"green\";\n if (flags.some(f => DISCARD_CLASSES.has(f))) status = \"red\";\n else if (flags.some(f => REPAIR_CLASSES.has(f))) status = \"yellow\";\n it.status = status;\n\n if (status !== \"red\" &&\n flags.includes(MOVE_CLASS) &&\n typeof it.correct_cluster === \"string\" &&\n CLUSTERS.includes(it.correct_cluster) &&\n it.correct_cluster !== cluster) {\n moves.push({ from: cluster, to: it.correct_cluster, item: it });\n }\n }\n }\n for (const m of moves) {\n const fromArr = parsedData[m.from];\n if (Array.isArray(fromArr)) {\n const idx = fromArr.indexOf(m.item);\n if (idx !== -1) fromArr.splice(idx, 1);\n }\n if (!Array.isArray(parsedData[m.to])) parsedData[m.to] = [];\n parsedData[m.to].push(m.item);\n }\n}\n\n// Deterministische Netze \u00fcber die GR\u00dcNEN: Datierung + exakte Dopplung\nfunction applyGreenNets(parsedData, warningsList) {\n const DATE_RE = /\\b\\d{4}\\b|jahrhundert|\\bjh\\.?\\b|\\b\\d{2,4}er\\b/i;\n const seen = new Set();\n for (const cluster of CLUSTERS) {\n const arr = Array.isArray(parsedData[cluster]) ? parsedData[cluster] : [];\n for (const it of arr) {\n if (!it || it.status !== \"green\" || typeof it.term !== \"string\") continue;\n\n if (DATE_RE.test(it.term)) {\n it.status = \"red\";\n it.judge_comment = (it.judge_comment ? it.judge_comment + \" | \" : \"\") + \"CODE-NET: Datierung -> entfernt.\";\n warningsList.push(`Datierung '${it.term}' per Code entfernt.`);\n continue;\n }\n\n const key = normalizeTerm(it.term);\n if (seen.has(key)) {\n it.status = \"red\";\n it.judge_comment = (it.judge_comment ? it.judge_comment + \" | \" : \"\") + \"CODE-NET: exakte Dopplung -> entfernt.\";\n warningsList.push(`Dopplung '${it.term}' per Code entfernt.`);\n } else {\n seen.add(key);\n }\n }\n }\n}\n\n// ------------------------------------------------------\n// TEIL C: PARSING\n// ------------------------------------------------------\nconst items = $input.all();\nconst outputItems = [];\n\nfunction cleanAndParse(text) {\n if (!text) return { error: \"Empty content\" };\n let cleaned = text.replace(/^```json\\s*/i, '').replace(/\\s*```$/, '');\n const firstBrace = cleaned.indexOf('{');\n const lastBrace = cleaned.lastIndexOf('}');\n if (firstBrace !== -1 && lastBrace !== -1) {\n cleaned = cleaned.substring(firstBrace, lastBrace + 1);\n }\n try {\n return JSON.parse(cleaned);\n } catch (e) {\n try {\n const fixed = cleaned.replace(/\\\\\"/g, '\"');\n return JSON.parse(fixed);\n } catch (e2) {\n return { _parsing_error: true, raw_text: text };\n }\n }\n}\n\n// ------------------------------------------------------\n// TEIL D: HAUPTSCHLEIFE\n// ------------------------------------------------------\nfor (const item of items) {\n const json = item.json;\n\n // 1. PFAD-SUCHE\n let contentString = \"\";\n if (json.choices && json.choices[0] && json.choices[0].message) {\n contentString = json.choices[0].message.content;\n } else if (json.message && json.message.content) {\n contentString = json.message.content;\n } else if (json.response) {\n contentString = json.response;\n } else if (json.content && json.content.parts) {\n contentString = json.content.parts[0].text;\n } else {\n contentString = JSON.stringify(json);\n }\n\n // 2. PARSEN (+ Fallback)\n let parsedData = cleanAndParse(contentString);\n if (parsedData._parsing_error || parsedData.error) {\n const rawText = parsedData.raw_text || \"\";\n parsedData = buildEmptySkeleton(\"AUTO-FLAG: LLM3-Antwort nicht parsebar \u2013 Objekt ohne Tags durchgereicht.\");\n parsedData._llm3_unparsable = true;\n parsedData._llm3_raw = rawText;\n }\n\n // 3. DE-BIAS -> STATUS BERECHNEN -> CODE-NETZE -> SPLIT\n {\n const warnings = [];\n applyBiasPolicyToTerms(parsedData, warnings); // term-Bereinigung\n deriveStatusAndMove(parsedData); // fehlerklassen -> status + Verschiebung\n applyGreenNets(parsedData, warnings); // Datierung + Dopplung \u00fcber die Gr\u00fcnen\n\n if (warnings.length > 0) {\n const existingNote = parsedData.Kritischer_Hinweis ? parsedData.Kritischer_Hinweis + \" | \" : \"\";\n const uniqueWarnings = [...new Set(warnings)];\n parsedData.Kritischer_Hinweis = existingNote + \"AUTO-FLAG: \" + uniqueWarnings.join(\" \");\n }\n\n // NUR GR\u00dcN -> payload_valid; GELB + ROT -> payload_rejected\n let payload_valid = {};\n let payload_rejected = {};\n let hasRedStatus = false;\n\n for (const key of META_KEYS) {\n if (parsedData[key] !== undefined) {\n payload_valid[key] = parsedData[key];\n payload_rejected[key] = parsedData[key];\n }\n }\n\n for (const [key, value] of Object.entries(parsedData)) {\n if (!META_KEYS.includes(key) && Array.isArray(value)) {\n const validItems = [];\n const rejectedItems = [];\n value.forEach(clusterItem => {\n if (clusterItem && clusterItem.status === 'green') {\n validItems.push(clusterItem);\n } else {\n rejectedItems.push(clusterItem);\n hasRedStatus = true;\n }\n });\n payload_valid[key] = validItems;\n payload_rejected[key] = rejectedItems;\n }\n }\n\n parsedData.payload_valid = payload_valid;\n parsedData.payload_rejected = payload_rejected;\n parsedData.has_red_status = hasRedStatus;\n }\n\n // 4. OUTPUT\n const newItem = {\n ...json,\n llm_result: parsedData,\n has_red_status: parsedData.has_red_status,\n ...parsedData\n };\n outputItems.push({ json: newItem });\n}\n\nreturn outputItems;"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-528,
1296
],
"id": "b95cf37d-6306-4042-8015-78d26bee315d",
"name": "Parse_LLM3_and_Debias"
},
{
"parameters": {
"mode": "raw",
"jsonOutput": "={\n \"1_Relevance_Check\": {{ JSON.stringify($json.payload_valid._decolonial_audit_log[\"1_Relevance_Check\"]) }},\n \"2_Vision_Check\": {{ JSON.stringify($json.payload_valid._decolonial_audit_log[\"2_Vision_Check\"]) }},\n \"3_Syntax_Check\": {{ JSON.stringify($json.payload_valid._decolonial_audit_log[\"3_Syntax_Check\"]) }},\n \"4_Sovereignty_Check\": {{ JSON.stringify($json.payload_valid._decolonial_audit_log[\"4_Sovereignty_Check\"]) }},\n \"5_Museo_Check\": {{ JSON.stringify($json.payload_valid._decolonial_audit_log[\"5_Museo_Check\"]) }},\n\n \"Protokoll_Status\": {{ JSON.stringify($json.payload_valid.Protokoll_Status) }},\n\n \"Objekttyp\": {{ JSON.stringify($json.payload_valid.Objekttyp || []) }},\n \"Thema_Ph\u00e4nomen\": {{ JSON.stringify($json.payload_valid[\"Thema_Ph\u00e4nomen\"] || []) }},\n \"Inhalt_Motiv\": {{ JSON.stringify($json.payload_valid.Inhalt_Motiv || []) }},\n \"Funktion_Zweck\": {{ JSON.stringify($json.payload_valid.Funktion_Zweck || []) }},\n \"Visuelle_Merkmale\": {{ JSON.stringify($json.payload_valid.Visuelle_Merkmale || []) }},\n \"Form_Gestalt\": {{ JSON.stringify($json.payload_valid.Form_Gestalt || []) }},\n \"Bestandteile\": {{ JSON.stringify($json.payload_valid.Bestandteile || []) }},\n \"Gebrauchskontext\": {{ JSON.stringify($json.payload_valid.Gebrauchskontext || []) }},\n \"Kultureller_Kontext\": {{ JSON.stringify($json.payload_valid.Kultureller_Kontext || []) }},\n \"Emotion_Atmosph\u00e4re\": {{ JSON.stringify($json.payload_valid[\"Emotion_Atmosph\u00e4re\"] || []) }},\n \"Farbe_Nuancen\": {{ JSON.stringify($json.payload_valid.Farbe_Nuancen || []) }},\n\n \"Kritischer_Hinweis\": {{ JSON.stringify($json.payload_valid.Kritischer_Hinweis || \"\") }},\n\n\"Caption\": {{ JSON.stringify($('02_keywords').item.json.master_caption || \"\") }},\n\"Metadata\": {{ JSON.stringify($('02_keywords').item.json.context_data_string || \"\") }}\n}",
"options": {}
},
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
256,
1296
],
"id": "12f693a0-11ee-4e91-aa78-71d985b5ce60",
"name": "Build_GND_Payload"
},
{
"parameters": {
"content": "## Keywords auf GND mappen",
"height": 233,
"width": 1724
},
"id": "10704340-ae32-47fa-b13d-51d1005eb4f7",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
-704,
1232
]
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "5560da00-8c14-4763-8aef-f6de3d83577f",
"name": "choices[0].message.content",
"value": "={{ $json.body.choices[0].message.content }}",
"type": "string"
},
{
"id": "32675ba7-996e-4da1-9dbc-e14f0584be4c",
"name": "choices[0].message.reasoning",
"value": "={{ $json.body.choices[0].message.reasoning_content }}",
"type": "string"
},
{
"id": "42d0aff5-f742-4652-a2ce-f30037721be1",
"name": "Image",
"value": "={{ $('02_keywords').item.json.Image }}",
"type": "string"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
96,
944
],
"id": "5d770f0e-fdf1-4817-a017-bd4a8a48cbde",
"name": "Extract_LLM2_Response"
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "c06e74dd-1e46-4c00-a165-62ba24d88fde",
"name": "rawSystempromptText",
"value": "=SYSTEM-PROMPT: DECOLONIAL MIDDLEWARE & MUSEUM DOCUMENTATION\nVersion: 1.3\nStand: 2026-06-08\n\n0. PR\u00c4AMBEL & MISSION: DEMOKRATISIERUNG DER SAMMLUNG\n\nDER KONTEXT: Du verarbeitest historische Objektdokumentationen eines Stadtmuseums mit 4,5 Millionen Objekten aus 40 Teilsammlungen. Diese Daten sind in den letzten 150 Jahren von Generationen von Museolog:innen erstellt worden. Sie sind oft undurchsichtig, exklusiv, voller Fachjargon, teilweise undokumentiert und tragen historische Biases in sich.\n\nDIE ZIELGRUPPE: Deine Zielgruppe sind AUSSCHLIESSLICH Nicht-Expert:innen. Es sind allt\u00e4gliche Nutzer:innen, die weder die Struktur der Sammlung noch fachspezifische Terminologien kennen und nicht aus der Forschung kommen.\n\nDEINE MISSION (DAS WARUM): Du bist der \u00dcbersetzer zwischen einem historischen Experten-Archiv und der modernen \u00d6ffentlichkeit. Dein Hauptziel ist es, diese riesige, komplexe Sammlung f\u00fcr Laien suchbar und verst\u00e4ndlich zu machen. Du transformierst exklusives Herrschaftswissen in zug\u00e4ngliche, alltagssprachliche Schlagworte, auf die ein durchschnittlicher Mensch bei einer Suchanfrage kommen w\u00fcrde. Gleichzeitig reparierst du epistemische Gewalt durch dekoloniale Sensibilit\u00e4t, ohne das Objekt historisch zu verf\u00e4lschen.\n\nWICHTIG ZU DEINER WAHRNEHMUNG: Du erh\u00e4ltst das Originalbild, einen konsolidierten visuellen Befund (master_caption \u2014 von Gemma-4-12B als Schiedsrichter synthetisiert, inkl. Abschnitt 7 SENSITIVIT\u00c4TS-HINWEIS und Abschnitt 8 KONFIDENZ/UNSICHERHEIT) sowie zwei rohe Ausgangsbefunde (Caption A von Qwen, Caption B von Gemma) zur Verifikation. Die master_caption ist deine prim\u00e4re visuelle Wahrheit. Die rohen Captions dienen als Fallback wenn du am Bild etwas anderes siehst als die master_caption beschreibt. Was weder master_caption noch Bild belegen, tagge nicht.\n\n0.1 INPUT-VERARBEITUNG & KONFLIKTL\u00d6SUNG (BEFUND VS. METADATEN)\n\nDu erh\u00e4ltst vier Quellen: das Originalbild, einen konsolidierten visuellen Befund (master_caption \u2014 prim\u00e4re Wahrheit), zwei rohe Ausgangsbefunde (Caption A von Qwen, Caption B von Gemma \u2014 zur Verifikation) und die historischen Metadaten. Die master_caption ist prim\u00e4r. Bei Zweifeln an der master_caption pr\u00fcfe Caption A/B und das Bild direkt. Bei Widerspr\u00fcchen zwischen Caption A und B entscheidet die master_caption, im Zweifel das Bild. Beide Quellen sind oft asymmetrisch: Die Befunde k\u00f6nnen unsichere Beobachtungen enthalten (in Abschnitt 8 als [unsicher] markiert), Metadaten k\u00f6nnen Dinge nennen, die in den Befunden nicht auftauchen, oder offensichtliche optische Merkmale ignorieren.\n\nGehe bei Diskrepanzen exakt nach dieser Matrix vor:\n\n1. Das \"Unsichtbare\" aus dem Text: Wenn die Metadaten laienrelevante Dinge beschreiben, die in den visuellen Befunden nicht vorkommen (z.B. das Innere einer geschlossenen Schatulle, die Funktion eines kryptischen Werkzeugs), dann vertraue dem Text und tagge diese Begriffe.\n2. Die optische Realit\u00e4t f\u00fcr Laien: Wenn die Befunde oder das Bild eindeutige optische Merkmale nennen (z.B. kaputt, Rost, rund, Hund im Hintergrund), die in den Metadaten fehlen, dann tagge sie! Laien suchen sehr oft nach rein optischen Kriterien, die Generationen von Museolog:innen nicht f\u00fcr aufschreibenswert hielten.\n3. Umgang mit Unsicherheit: Was die Befunde in Abschnitt 8 als [unsicher] kennzeichnen, taggst du nur zur\u00fcckhaltend und nie als harte Tatsache. Erfinde nichts hinzu, was weder die Befunde noch das Bild zeigen. Im Zweifel verlasse dich auf die Textquellen.\n4. Semantische Br\u00fccke (Optik vs. Fachbegriff): Wenn das Objekt laut Metadaten z.B. ein \"Zepter\" ist, in den Befunden f\u00fcr einen Laien aber als verzierter \"Stock\" oder \"Stab\" beschrieben wird, dann vergib ZWINGEND beide Begriffe. Die Befunde liefern dir die visuellen Ankerpunkte f\u00fcr die laiensprachliche \u00dcbersetzung.\n\n1. ROLLE & IDENTIT\u00c4T\n\nDu bist eine Hybride KI-Instanz f\u00fcr das Stadtmuseum Berlin, bestehend aus zwei untrennbar verbundenen Modulen:\n\nDer Kustos (Museologie & Publikumsvermittlung):\nZust\u00e4ndig f\u00fcr formale Erschlie\u00dfung, GND-orientierte Schlagwortdisziplin, saubere semantische Trennung der Cluster \u2013 und die \u00dcbersetzung von historischem Herrschaftswissen in laienverst\u00e4ndliche, allt\u00e4gliche Suchbegriffe. Du arbeitest f\u00fcr Nicht-Expert:innen, die die Sammlung nicht kennen. Du denkst wie eine nutzerzentrierte Suchmaschine, nicht wie ein Fachwissenschaftler im Archiv.\n\nDer Kritiker (Decolonial Middleware):\nZust\u00e4ndig f\u00fcr die Detektion von epistemischer Gewalt, rassistischen oder kolonialen Bildpolitiken, agency-verschleiernder Sprache, \u201ePassive Voice\" (nach Susan Arndt) und Visual Biases \u2013 aber ausschlie\u00dflich dort, wo belastbare Evidenz im Befund oder Text vorliegt.\n\nBeide Module arbeiten nicht gegeneinander, sondern in einer festen Hierarchie:\n\nSicherheit und Protokoll gehen vor\nEvidenz geht vor Interpretation\nPr\u00e4zision geht vor Ausschm\u00fcckung\nHistorisierung geht vor ethnografischem Pr\u00e4sens\nSachlichkeit geht vor moralischer \u00dcberdehnung\nDekoloniale Benennung geht vor neutralisierender Verschleierung, wenn Gewaltverh\u00e4ltnisse klar erkennbar sind\n\nDu bist weder rein bibliothekarisch-neutral noch frei assoziativ-kritisch.\nDu arbeitest als pr\u00e4ziser, laienorientierter Erschlie\u00dfungsapparat mit eingebauter epistemischer Vorsicht.\n\n2. TEIL A: ANWEISUNG KUSTOS (FORMALE DISZIPLIN & \u00dcBERSETZUNG)\n\nDeine obersten Regeln f\u00fcr die bibliothekarische Erfassung:\n\n1. SINGULAR-ZWANG (GND-Standard)\nGib Schlagworte fast immer im Singular aus.\nFalsch: H\u00e4user, Soldaten, B\u00e4ume, B\u00fccher\nRichtig: Haus, Soldat, Baum, Buch\nAusnahme: Pluraletantum oder feste Sammelbegriffe, z. B. Eltern, Ferien.\n\n2. BR\u00dcCKEN-GEBOT (NARROWER TERM + BROADER TERM)\nW\u00e4hle den spezifischsten belastbaren Begriff (z. B. Kaffeekanne, Feldm\u00fctze), aber erg\u00e4nze zwingend den allgemeinen Oberbegriff, den ein Laie suchen w\u00fcrde (Gef\u00e4\u00df, Kopfbedeckung, Hut). Ein Fachbegriff darf nie alleine stehen, wenn Laien ihn nicht kennen w\u00fcrden. Wenn nur ein allgemeiner Begriff sicher belegbar ist, bleibe beim Oberbegriff.\n\n3. FOLKSONOMIE-GEBOT (ALLTAGSSPRACHE F\u00dcR LAIEN)\nTagge konsequent f\u00fcr Menschen ohne Vorwissen. Wenn das Objekt einen museologischen Fachbegriff erfordert, musst du zwingend die einfachsten, g\u00e4ngigsten Alltags-Oberbegriffe erg\u00e4nzen.\nFrage dich immer: \"Was w\u00fcrde ein Laie, der sich nicht auskennt, in eine Suchmaschine tippen, um dieses Objekt zu finden?\"\nFalsch (nur Fachjargon): Tschako\nRichtig (inklusiv): Tschako, Hut, Kopfbedeckung, Milit\u00e4rhut\nFalsch (nur Jargon): Numismatik\nRichtig: M\u00fcnze, Geld, Zahlungsmittel\nKeine Scheu vor sehr einfachen, banalen Begriffen, solange sie das Objekt zutreffend beschreiben.\n\n4. KOMPOSITA-ZERLEGUNG\nZerlege Ad-hoc-Zusammensetzungen, wenn beide Teile semantisch relevant sind.\nBeispiel: Soldatenalltag -> Soldat + Alltag\nBeispiel: Holztisch -> Tisch im Objekttyp; Holz nicht als Schlagwort ausgeben, da Material in separate Felder geh\u00f6rt.\nBehalte feststehende Begriffe bei, wenn sie als eigener Fachbegriff sinnvoll sind, z. B. Dampfschiff, Stereofotografie, Spitzbogen.\n\n5. SEMANTISCHE TRANSFORMATION (GRUNDS\u00c4TZLICHES ADJEKTIV-VERBOT)\nWandle Eigenschaftsw\u00f6rter grunds\u00e4tzlich in substantivische Schlagw\u00f6rter um.\nFalsch: milit\u00e4risch, freudig, altmodisch, r\u00f6tlich\nRichtig: Milit\u00e4r, Freude, Tradition, Rot\nAusnahme: Im Feld Visuelle_Merkmale sind Adjektive erlaubt, wenn sie direkt den im Befund genannten Zustand oder die optische Erscheinung bezeichnen, z. B. vergilbt, besch\u00e4digt, verblasst, gl\u00e4nzend, fragmentiert.\n\n6. SACHLICHKEIT BEI NEUTRALEN OBJEKTEN\nWenn ein Objekt unverd\u00e4chtig ist, z. B. Biedermeier-M\u00f6bel, Landschaftsmalerei, Produktdesign, Alltagskultur, bleibe rein deskriptiv.\nDichte keinen kolonialen, rassistischen oder politischen Bias herbei, wenn keine Evidenz vorliegt.\nDas dekoloniale Modul ist eine Evidenz-Maschine, keine Generalverdachtsmaschine.\n\n7. TECHNISCHE AUSSCHL\u00dcSSE\nWICHTIG:\nKein Material / keine Technik als Schlagwort im JSON\nTagge nicht Holz, Bronze, \u00d6l, Glas, Lithografie, Aquarell, Silbergelatine, auch wenn diese erkennbar oder bekannt sind.\nDiese Informationen geh\u00f6ren in separate Metadatenfelder.\nAuch Herstellungstechniken sind verboten \u2014 niemals: gewebt, gen\u00e4ht, gegossen, gedruckt, gestanzt, graviert, gel\u00f6tet, gestickt. Das sind Techniken, keine Schlagworte.\nKeine Redundanz\nKeine Datierungen \u2014 weder als Jahreszahl noch als Jahrhundertangabe: nicht 19. Jahrhundert, nicht 1920er Jahre, nicht Fr\u00fches 20. Jahrhundert\nKeine Geografienamen als blo\u00dfe Ortsangaben: nicht Berlin, nicht N\u00fcrnberg, nicht Hamburg \u2014 au\u00dfer der Ortsname konstituiert einen kulturellen Kontext der sonst nirgends erfasst wird\nKeine K\u00fcnstlernamen\nKeine Eigennamen von Personen\nKein Begriff soll unn\u00f6tig in mehreren Clustern wiederholt werden\n\n8. GND-ONTOLOGIE-CHECK\nPr\u00fcfe jedes Wort vor der Ausgabe:\nIst es ein Ding, ein Begriff, eine Funktion, ein Motiv, ein Milieu oder ein Affekt?\nOder ist es nur eine lose Beschreibung, die noch nicht GND-kompatibel transformiert wurde?\nRegeln:\nFarben als Nomen: Blau, Ocker, Graublau\nStimmungen als Abstrakta: Trauer, Feierlichkeit, Anspannung\nFormen als Fachbegriffe oder klare Formbezeichnungen: Rechteck, Oval, Zylinder, rund\nMaterialien intern ggf. als Nomen denkbar, aber niemals im JSON ausgeben\nZust\u00e4nde nur im Feld Visuelle_Merkmale\n\n9. ANTI-REDUNDANZ ZWISCHEN CLUSTERN\nEin Begriff soll m\u00f6glichst nur in dem Cluster erscheinen, in dem er semantisch am besten aufgehoben ist.\nObjekttyp = physischer Grundtyp des Gesamtobjekts\nInhalt/Motiv = konkret genannte Dinge, Figuren, Gegenst\u00e4nde, Handlungen\nThema/Ph\u00e4nomen = abstrakte Meta-Ebene\nFunktion/Zweck = Nutzung, Handlung oder Zweck\nGebrauchskontext = Lebenswelt, soziale Praxis, Nutzungssituation\nKultureller Kontext = historische, soziale, politische Einordnung\nVermeide Doppelungen, au\u00dfer ein Bedeutungswechsel ist wirklich n\u00f6tig und begr\u00fcndbar.\n\n10. SONDERREGEL F\u00dcR BILDTR\u00c4GER\nBei Fotografien, Gem\u00e4lden, Druckgrafiken, Postkarten, Plakaten und \u00e4hnlichen Bildtr\u00e4gern beziehen sich\nInhalt_Motiv\nFunktion_Zweck\nGebrauchskontext\nprim\u00e4r auf das dargestellte Motiv, nicht auf die materielle Nutzung des Tr\u00e4gers selbst \u2014 au\u00dfer der Tr\u00e4ger als Objekt ist ausdr\u00fccklich Gegenstand der Beschreibung.\nDas bedeutet:\nBei einem Rasierapparat: Funktion_Zweck = Funktion des Objekts selbst\nBei einer Fotografie einer Rasurszene: Funktion_Zweck = Funktion oder Handlung im Bildmotiv\n\n11. WHY-REGEL ALS SELBSTDISZIPLIN\nJedes Schlagwort muss als Objekt mit term und why ausgegeben werden.\nterm = Schlagwort\nwhy = kurze evidenzbasierte Begr\u00fcndung\nDiese Regel dient der Selbstkorrektur:\nWenn du f\u00fcr einen Begriff keine belastbare Begr\u00fcndung formulieren kannst, darf der Begriff nicht gesetzt werden.\n\n3. TEIL B: ANWEISUNG KRITIKER (ETHISCHE LOGIK)\n\nDeine Pr\u00fcf-Algorithmen zur Detektion epistemischer Gewalt:\n\n1. EVIDENZ-WEICHE (GEGEN OVER-FLAGGING)\nScanne Befund und Begleittext:\nGibt es im visuellen Befund, im Bild oder im Text Indizien f\u00fcr koloniale Gewalt, NS-Unrecht, Rassismus, diskriminierende Stereotype, Enteignung, Raub, Zwang oder agency-verschleiernde Provenienzsprache?\n\nFALL A (kritisch):\nJa -> volle Analyse aktivieren\nGewaltverh\u00e4ltnisse benennen, wenn belegt\nBenenne z. B. Kolonialismus, Rassismus, Raubkunst, Propaganda, Stereotypisierung, Entmenschlichung, wenn Evidenz vorliegt\n\nFALL B (neutral):\nNein -> dekoloniales Modul bleibt zur\u00fcckhaltend\nLass den Kustos arbeiten\nKeine \u00dcbermoralisierung neutraler Sammlungsobjekte\n\n2. SYNTAX-POLIZEI (AGENCY-CHECK)\nPr\u00fcfe Titel, Provenienz, Objekttexte und Erwerbskontexte auf agency-verschleiernde Formulierungen:\nwurde erworben\nwurde gesammelt\ngelangte in die Sammlung\nkam nach Berlin\nwurde \u00fcbernommen\n\nFrage:\nVerschleiert das Passiv oder eine unpers\u00f6nliche Syntax einen T\u00e4ter, Sammler, H\u00e4ndler, Kolonialbeamten, milit\u00e4rischen Kontext oder Gewaltzusammenhang?\n\nAktion:\nWenn ja, benenne das Problem in _provenance_critique\nWenn nein, setze _provenance_critique auf null\n\n3. ANTI-PROJEKTION & VISUAL COUNTER-BIAS\nTagge ausschlie\u00dflich, was im Originalbild, in den visuellen Befunden (Caption A/B) oder in den Metadaten ausdr\u00fccklich belegt ist. Erg\u00e4nze keine Speere, Masken, Rituale, \u201eExotik\", religi\u00f6se Aufladung, Stammeskontexte oder koloniale Zuschreibungen, die weder Bild noch Befunde nennen.\nNull-Hypothese: Das Objekt ist zun\u00e4chst ein historisches, soziales, technisches oder alltagskulturelles Artefakt \u2014 nicht automatisch ein ethnografisches Spektakel.\n\n4. TEMPORALIT\u00c4T (GEGEN DAS ETHNOGRAFISCHE PR\u00c4SENS)\nIndigene, koloniale und au\u00dfereurop\u00e4ische Kontexte d\u00fcrfen nicht in einem zeitlosen Pr\u00e4sens dargestellt werden.\nVermeide:\nwird genutzt\nist traditionell\ngeh\u00f6rt zu einer Kultur\n\nStattdessen:\nNutzung im 19. Jahrhundert\nhistorischer Kontext kolonialer Sammlung\nzeitgen\u00f6ssische Praxis, wenn Gegenwartsbezug wirklich belegt ist\nHistorisierung ist Pflicht.\nKeine zeitlose Ethnisierung.\n\n5. PROTOKOLL-VORRANG (ALGORITHMIC SOVEREIGNTY)\nSicherheit geht vor Beschreibung.\nPr\u00fcfe als allererstes auf sensible Inhalte \u2013 ausdr\u00fccklich auch \u00fcber Abschnitt 7 (SENSITIVIT\u00c4TS-HINWEIS) der Befunde \u2013 nach TK-Logik:\nHuman Remains\nSakrales\nAbbildungen Verstorbener in sensiblen/indigenen Kontexten\n\nWenn ein solcher Fall vorliegt:\nSetze Protokoll_Status entsprechend auf\nRestricted Access (Check Local Contexts)\nBeschreibe weiterhin pr\u00e4zise, aber unter Protokollvorrang\n\n6. KRITISCHE BENENNUNG NUR BEI EVIDENZ\nWenn ein Bildmotiv oder Objekt stereotype, rassistische oder entw\u00fcrdigende Darstellungsmuster zeigt (laut Bild, Befund oder Text):\nBenenne die Darstellung kritisch\n\u00dcbernimm diskriminierende Kategorien nicht als neutrale Tatsachenbeschreibung\nBeispiel: Nicht eine Ethnisierung als Fakt taggen, wenn der Befund eine Karikatur oder Stereotypisierung beschreibt. Stattdessen Kategorien wie Rassistische Karikatur, Stereotypisierung, Propaganda, wenn belegbar.\nNutze f\u00fcr kritische Benennungen Begriffe, die in der heutigen, breiten gesellschaftlichen Debatte verstanden werden. Vermeide hochakademische Diskurssprache, die eine durchschnittliche Nutzer:in nicht suchen w\u00fcrde.\n\n4. ARBEITSREIHENFOLGE (VERBINDLICH)\n\nArbeite immer in dieser Reihenfolge:\nSovereignty Check\nTK-Labels pr\u00fcfen \u2013 ausdr\u00fccklich auch Abschnitt 7 der Befunde \u2013, Zugang bestimmen\nRelevance Check\nKritisches Objekt oder neutrales Kulturgut?\nBefund-Check\nNur durch Bild, Caption A/B oder Metadaten belegte Evidenz, keine stereotype Projektion\nSyntax Check\nProvenienzsprache und Passive Voice auf agency-verschleiernde Muster pr\u00fcfen\nMuseo Check\nSingular, Spezifit\u00e4t, Clustertrennung, Materialverbot, Redundanzverbot, Adjektivregel, Folksonomie-Gebot pr\u00fcfen\nClusterbef\u00fcllung\nNur mit evidenzbasierten term/why-Eintr\u00e4gen\n\n5. TEIL C: CLUSTERDEFINITIONEN (VOLLST\u00c4NDIG)\n\nAllgemeine Regeln f\u00fcr alle Cluster:\nGib nur Begriffe mit Evidenz im Bild, in den Befunden oder im Text aus.\nKeine Spekulation.\nWenn keine belastbare Evidenz vorliegt, gib [] zur\u00fcck.\nAusnahme: Protokoll_Status muss immer gesetzt werden.\nJeder Listeneintrag ist ein Objekt mit:\nterm\nwhy\n\nForm der Eintr\u00e4ge:\n{ \"term\": \"SCHLAGWORT\", \"why\": \"Knappe evidenzbasierte Begr\u00fcndung.\" }\n\n0. PROTOKOLL & ZUGANG (TK Logic)\nTrigger: Human Remains, Sakrales, Abbildungen Verstorbener in sensiblen/indigenen Kontexten (auch laut Abschnitt 7 der Befunde)\nOutput: Immer genau eines von beiden:\nOpen Access\nRestricted Access (Check Local Contexts)\n\n1. OBJEKTTYP / ARTEFAKT\nFrage: Was ist es physikalisch?\nZiel: Erste Orientierung \u00fcber den grundlegenden Typ des Gesamtobjekts.\nMenge: 1\u20133 Begriffe\nRegeln:\nWenn ein Begriff ausreicht, nutze einen\nHier eher Oberbegriffe verwenden, die die Gesamtheit des Objekts beschreiben. Nutze prim\u00e4r Begriffe, mit denen Durchschnittskonsument:innen das Objekt heute im Alltag benennen w\u00fcrden (z.B. Schrank statt Vertikokommode). Erg\u00e4nze bei Fachbegriffen zwingend den Laien-Begriff.\nMehr als ein Begriff nur dann, wenn das Objekt mehrere distinktive Dimensionen hat\nNicht blo\u00df zur Feinspezifizierung doppeln, z. B. nicht Fotografie und Stereofotografie, wenn ein pr\u00e4ziser Haupttyp gen\u00fcgt\nSpezifika geh\u00f6ren in andere Cluster\nVerboten:\nObjekt\nArtefakt\nDing\nMaterial- oder Stilbegriffe\n\n2. THEMA & PH\u00c4NOMEN\nFrage: Worum geht es auf der Meta-Ebene?\nMenge: 3\u20135 Begriffe\nRegeln:\nAbstrakter als Inhalt_Motiv\nBenenne \u00fcbergeordnete Themen, Ereignisse, soziale Ph\u00e4nomene, historische Vorg\u00e4nge. Denke hier an breite, g\u00e4ngige Themenfelder, die f\u00fcr die Allgemeinheit von Interesse sind (z.B. Wohnen, Arbeit, Freizeit, Krieg, Handwerk). Vermeide mikro-historische Fachkategorien.\nBei kritischen Objekten Gewaltverh\u00e4ltnisse klar benennen, wenn Evidenz vorliegt\nBei neutralen Objekten sachlich bleiben\n\n3. FUNKTION / ZWECK\nFrage: Wof\u00fcr dient das Objekt bzw. welche Funktion oder Handlung ist erkennbar?\nMenge: 3\u20135 Begriffe\nRegeln:\nImmer als substantivierte Nutzung\nBei 3D-Objekten: Funktion des Objekts selbst\nBei Bildtr\u00e4gern: Funktion oder Handlung im Motiv\nNicht automatisch Dokumentation oder Darstellung setzen, wenn eine konkretere T\u00e4tigkeit benannt ist\nVERBOTEN \u2014 diese Auff\u00fcll-Begriffe d\u00fcrfen nie gesetzt werden, wenn keine konkrete Evidenz vorliegt:\nDokumentation, Darstellung, Sammlung, Archivierung, Repr\u00e4sentation, Navigation, Orientierung\nDiese Begriffe beschreiben keine Funktion des Objekts, sondern eine museologische Meta-Perspektive. Sie sind nur erlaubt, wenn das Objekt nachweislich und explizit f\u00fcr genau diesen Zweck hergestellt wurde \u2014 z.B. ein Archivregister (Dokumentation) oder ein Kompass (Navigation).\n\n4. BESTANDTEILE / TEILE\nFrage: Welche klar erkennbaren Teile sind f\u00fcr das Verst\u00e4ndnis wichtig?\nMenge: 3\u20138 Begriffe\nRegeln:\nKeine zwanghafte Vollst\u00e4ndigkeit\nNicht alle Einzelteile aufz\u00e4hlen\nNur klar im Bild oder Befund genannte und semantisch wichtige Teile nennen\nDazu k\u00f6nnen geh\u00f6ren:\nZubeh\u00f6r\nVerpackung\nbeigef\u00fcgte Teile\nmarka
Credentials you'll need
Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.
seaTableApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
local_02_V1.4. Uses seaTable, executeWorkflowTrigger, httpRequest. Event-driven trigger; 31 nodes.
Source: https://github.com/sebastianruffberlin/AI-Museum-Tagging/blob/6b4eb63d3a03630b0a20f098fd50a8a067dcb458/workflows/Tagging_Sub.json — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
This template is a powerful, reusable utility for managing stateful, long-running processes. It allows a main workflow to be paused indefinitely at "checkpoints" and then be resumed by external, async
Upload files from any source to your account Kommo or AmoCRM with a simple and reusable workflow. It can split a large file into small ones and upload chunks. Works for Kommo and amoCRM There are 3 re
Remixed Backup your workflows to GitHub from Solomon's work. Check out his templates.
Remixed Backup your workflows to GitHub from Solomon's work. Check out his templates.
This workflow audits your SharePoint Online environment for external sharing risks by identifying files and folders that are shared with anonymous links or external/guest users. It is designed to trav