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 +
lfxonly. 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.ioentry per question (name= stable JSON key,display_name= label,info= help,options/value/requiredas needed) plus the hiddenform_valuesplumbing input (reserved name — never use it for a field). - Branching:
[visible_if feld=Wert]markers ininfo(&for AND, quotes for spaces). Any depth; trigger types: Dropdown/Textconst, Bool, Multiselectcontains, 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")returningData. Do not touchcollect()— 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), thenform_data → … → ChatOutput. - Reorder template keys to question order (lfx emits alphabetical; RJSF
renders insertion order, and
ui:orderfollows it). - The script writes fixtures + canonical files;
git checkoutthe churn on unrelated flows, keep canonical files CRLF like the repo. - Custom logic code (e.g. salary math) lives as a
*_CODEconstant 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 thepulledreport, 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-schemashowsfallback: nullwith 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.