kirche-einfach/docs/new_workflow_module.md

5 KiB

New workflow module: form flow end to end

How to add a form-based workflow (web form → run → result), learned from building intake, Bauvorhaben, and Mitarbeiter-Einstellen. Three layers: component (Langflow palette), flow (canvas), backend parsing (automatic).

1. Form component (backend/components/kirche/<name>_form.py)

Copy mitarbeiter_form.py (small) or bauvorhaben_form.py (branching). Rename the class with a CustomInput* prefix (e.g. CustomInputMitarbeiter) — the backend parser matches that prefix; a plain CustomComponent name additionally needs a display name ending in "Form".

Rules, all load-bearing:

  • Self-contained: stdlib + lfx only. NEVER import from sibling form modules — LFX executes the embedded module source without our package, and Langflow round-trips the code. Duplicated helpers are structural, not laziness.
  • Inputs: one lfx.io entry per question (name = stable JSON key, display_name = label, info = help, options/value/required as needed) plus the hidden form_values plumbing input (reserved name — never use it for a field).
  • Branching: [visible_if feld=Wert] markers in info (& for AND, quotes for spaces). Any depth; trigger types: Dropdown/Text const, Bool, Multiselect contains, Int/Float exact. Stale markers, cycles, and unknown triggers fail open (always visible).
  • Required sentinels: required fields with empty values carry " " ([" "] for Multiselect — a plain string crashes LFX at build, [] fails validation). The parser treats them as "no default".
  • Outputs: exactly Output(name="form_data", method="collect") returning Data. Do not touch collect() — it is fully dynamic (asked + visible defaults, submitted values win).
  • Export in backend/components/kirche/__init__.py.

Then: docker compose restart langflow (components load at startup), check the palette via GET /api/v1/all (or sidebar search, hard-refresh the browser tab if the entry is missing — the API is source of truth).

2. Flow JSON (backend/flows/<name>.json)

Never hand-edit: add build_<name>() in backend/scripts/make_flow_fixtures.py and run it (from backend/).

  • Form node via kirche_node(node_id, component_cls=..., code_override=...).
  • Logic nodes via custom_node(CODE, ...) (keep return annotations like -> Message: edge handles derive output types from them).
  • Always wire ChatInput → form_values (run envelope), then form_data → … → ChatOutput.
  • Reorder template keys to question order (lfx emits alphabetical; RJSF renders insertion order, and ui:order follows it).
  • The script writes fixtures + canonical files; git checkout the churn on unrelated flows, keep canonical files CRLF like the repo.
  • Custom logic code (e.g. salary math) lives as a *_CODE constant in the script — one owner for template and code.

3. Backend parsing (automatic, know the contract)

app/flows/input_schema.py turns the node template into {json_schema, ui_schema} per version: name → key (verbatim; empty skipped, collisions suffixed), display_name/info/value/options → title/description/default/enum, show: false skipped, unknown types degrade to text. Markers become allOf/if/then branches; branch-required applies only while visible.

Runs carry an envelope (payload, fields = asked names from the cached schema, run context). collect() emits defaults only for asked AND visible fields; submitted values always win; hidden values are kept but never inventoried. Raw dicts and the Langflow playground use all visible declared defaults.

4. Langflow round-trip (where flows go to die, if careless)

  • Node type names must be distinct per component — Langflow normalizes same-type nodes to the installed class on import and silently discards variant templates.
  • Upload via POST /api/v1/flows/upload/, then fetch the flow back and assert node type, template fields, and code are intact.
  • Restart re-seeds backend/flows/ into Langflow; the sync button pulls Langflow → files matched by flow name, overwriting silently. Commit before syncing, review the pulled report, never rename or repurpose flows casually.
  • Experiments that must stay out: list their stem in backend/flows/.flowsignore (skipped by sync, deploy, validation, frontend, and future pulls).

5. Verify before reporting done

  • uv run pytest (full suite incl. real-LFX deploy+run e2e) green.
  • uv run ruff check . clean, uv run ruff format --check . clean.
  • uv run lfx validate --level 4 tests/fixtures/flows/ all pass.
  • Live: GET /api/flows/{id}/input-schema shows fallback: null with expected properties (cached schemas refresh only on new versions — unchanged files keep their cached schema).
  • Browser click-through in Workflows, including a branched path and an empty submit (branch-required errors).

New behavior ships with tests; schema changes ship as Alembic revisions (never edit applied migrations); commit on feat/<short>, never directly on main.