"Build me an e-commerce platform with real-time inventory and payment processing."
A model can turn that sentence into boxes and arrows. The harder job is making the result editable, explainable, and safe to reject when it is wrong. That is the engineering problem behind Codelit's architecture workflow.
is a system that converts a natural-language description of software into a schema-validated graph of nodes and edges, positions that graph automatically, and renders it as an editable diagram.
The stages below are a simplified design model and teaching examples, not a line-for-line description of every current generation route. Codelit's implementation keeps evolving; the boundaries are the part worth reusing.
What Are the Five Stages of a Prompt-to-Diagram Pipeline?
A prompt-to-diagram workflow can be understood through five responsibilities: interpreting intent, choosing a topology, generating structure, calculating layout, and rendering an editor. They need not be five separate model calls. Each responsibility has a distinct failure mode and a contract worth testing.
| Stage | What goes in | What comes out | Where the difficulty sits |
|---|---|---|---|
| 1. Intent parsing | Raw prompt text | Component list plus modifiers | The prompt is vague and the defaults have to be defensible |
| 2. Pattern selection | Component list | A topology (microservices, monolith, event-driven) | Catching cues like "keep it simple" that change the whole shape |
| 3. Structured generation | Components plus pattern | Node and edge JSON | Making an LLM emit valid, schema-conformant output every time |
| 4. Layout computation | Node and edge JSON | Coordinates for every node | Looking like an architecture diagram, not a plate of spaghetti |
| 5. Canvas rendering | Positioned graph | Interactive React Flow canvas | Keeping every node and edge type distinct and still editable |
Let me walk through them.
Stage 1: How Does Codelit Know What an E-Commerce Platform Needs?
An intent parser can map a domain phrase like "e-commerce platform" onto candidate components, then apply explicit modifiers from the prompt. Candidate is the important word: catalog, cart, checkout, payments, orders, and auth are reasonable starting points, not requirements the user necessarily supplied.
"Build me an e-commerce platform" is vague. Does it include a mobile app? Admin dashboard? Analytics? Recommendation engine?
I don't try to read minds. The parser extracts explicit requirements and infers reasonable defaults. Modifiers change the output from there. "With real-time inventory" adds a WebSocket layer and an inventory service with event-driven updates. "And payment processing" adds a payment gateway (Stripe-like) with webhook handling.
A two-pass design keeps those responsibilities separate:
Pass 1: Keyword extraction and domain mapping. Deterministic rules cover familiar terms without a model call.
Pass 2: Send ambiguous elements to the LLM for classification. Only fires when Pass 1 can't resolve the intent confidently. This keeps the common cases fast and cheap.
Stage 2: Why Does the Diagram Include Services Nobody Asked For?
Generated diagrams can add infrastructure because a model or template assumes a particular topology. That assumption should be visible and reversible. An API gateway, broker, and load balancer are not mandatory just because an application has several business domains.
Once I know the components, I need to decide how they connect. This is where architectural knowledge gets encoded. I maintain a library of patterns, monolith, microservices, event-driven, CQRS, serverless, hybrid, and each pattern defines a topology: how services communicate, where data flows, what infrastructure sits between them.
E-commerce can fit a modular monolith or a distributed architecture. Team ownership, scaling needs, and operational capacity should drive the choice. "Keep it simple" is not a cosmetic modifier; it can remove entire services from the proposed design.
How do you get schema-valid JSON out of an LLM?
Structured output needs an explicit contract and a failure path. Use native schema-constrained generation where supported, validate the result locally, and bound any repair attempts. A model response is untrusted input even when it is syntactically valid JSON.
This is where most of the engineering effort lives. The LLM needs to produce a JSON object defining every node (type, label, properties, tier) and every edge (source, target, label, type). Getting LLMs to produce consistent, valid, schema-conformant JSON is genuinely hard. The progression is useful to understand:
| Approach | How well it worked | Where it broke | Verdict |
|---|---|---|---|
| Free-form prose, parsed afterwards | Brittle from day one | The model used different names for the same service across paragraphs, and the parser couldn't handle the variation | Scrapped after two weeks |
| JSON schema described in the prompt | An instruction, not enforcement | Extra commentary, incorrect nesting, and unexpected fields | Validate every response |
| Schema-constrained output plus local validation | A stronger structural contract | Refusals, truncation, invalid references, and semantic errors still need handling | Bound retries and surface failure |
Provider APIs differ, so keep their request details in adapters. The application-side shape can stay small. This simplified Zod example validates structure, not whether the proposed architecture makes sense:
After shape validation, check that node IDs are unique and every edge endpoint exists. Reject or repair missing references with a bounded retry budget. A serverless monolith is not inherently contradictory, and model failure should never be blamed on the prompt by default.
Here's What the Output Actually Looks Like
Stage 3 returns a flat JSON object with a nodes array and an edges array. Every node carries a type and a tier. Every edge carries a source, a target, and a type. For the e-commerce prompt, simplified, it looks like this:
That typed graph drives everything downstream: layout, rendering, styling, interactivity. It's also the graph chaos mode walks when it propagates a cascading failure, which only works because every edge already knows whether it carries a synchronous call or an event.
How does the graph get its screen coordinates?
Codelit's current architecture canvas finds root nodes, walks outgoing edges to assign depth, and groups nodes into rows. Type-based fallbacks place nodes the traversal did not visit. That keeps layout local and inspectable, separate from the model's description of the system.
A layout library such as Dagre offers another approach. Whatever computes the rows, architectural conventions can inform the result:
- Databases may sit below the services that use them
- Load balancers can center above the services they front
- Message queues can sit between producers and consumers
- External services can sit outside the primary system boundary
This is the same problem I hit converting Mermaid syntax into an interactive canvas: the source tells you what connects to what and never where anything sits. Whatever invents the geometry has to be deterministic, same input, same output. If you regenerate the same architecture and the boxes land somewhere else, you stop believing the diagram means anything.
Stage 5: Rendering on the React Flow Canvas
React Flow renders the positioned graph, and the node and edge types from Stage 3 map straight onto visual treatments. The schema value decides the shape, the stroke and the animation, so nothing on the canvas is decorative.
| Element | Schema type | How it renders on the canvas |
|---|---|---|
| Node | database | Cylinder |
| Node | service | Rounded rectangle |
| Node | queue | Distinctive queue icon |
| Node | external | Cloud boundary |
| Edge | sync | Solid arrow |
| Edge | async | Dashed line |
| Edge | data-flow | Thicker line |
| Edge | event | Animated dashes |
Every element is interactive: click, drag, right-click to modify. The custom node components, the animated edges and the layout wiring are a subject of their own, and I pulled them apart in building the interactive architecture canvas with React Flow. The diagram is a starting point, not a finished product.
The Iteration Problem That Nearly Broke Everything
When a user asks to add caching, the product should make the proposed change inspectable without losing the architecture they already refined. A patch-oriented contract is one way to do that. A replacement response with version history is another, with a different preservation burden.
The naive approach is to generate a completely new diagram. That throws away every manual adjustment, repositioned nodes, renamed services, added annotations, and users notice immediately. Correct behaviour is narrow: add a Redis node, connect it to the relevant services, leave everything else untouched.
Context management is critical here. A 30+ node architecture with full properties eats a massive chunk of the context window. I aggressively summarise: the LLM doesn't need every property, just the structural skeleton. If details are omitted from model context, the application must decide explicitly whether to preserve, replace, or invalidate them.
Conversation history handles reference resolution across turns. "Make that database a cluster," which database? The one from the last turn. "Add a cache in front of it," in front of what? The service we just discussed. That context flows into every prompt.
Why support more than one generation provider?
Multiple providers give a generation workflow recovery options, but fallback is a policy choice, not a universal guarantee. A supported general-generation route may switch providers; a request pinned to the user's own key must respect that choice. Failures can still exhaust every permitted route.
I maintain provider-specific prompt variants. One model tends to over-generate and adds components nobody asked for. Another under-specifies edges, creating nodes but forgetting to connect them. The prompt variants compensate for those tendencies. The routing itself, and why a single provider is a liability, is a longer story I told in how Codelit's multi-provider fallback works.
A common validation boundary keeps provider-specific response shapes away from the renderer. It does not remove differences in reasoning, latency, or architectural quality. A well-formed graph still needs review.
What I'd Do Differently
I would define the graph contract and edit-preservation rules before polishing the first generation. Those decisions determine what can be validated, what a follow-up is allowed to change, and how the user recovers when the proposed update is wrong.
I wasted months wrestling with inconsistent LLM output before realising that enforcing a structured contract is an engineering problem, not an AI problem. Retrofitting statefulness onto a pipeline that assumed every generation was independent was the same lesson in a different costume.
For a complex system, staged generation is worth evaluating: establish the data boundary, then the application responsibilities, then the external interfaces. I would compare it against a fixed set of tasks before claiming it produces better architecture.
The same structured-output backbone drives two more canvases now: product boards and agent workflows. A generated agent workflow is a design to inspect, including model routing and approval gates. Real execution needs separate provider access, runtime checks, and outcome evidence. If you want the rest of that story, how Codelit got built starts with a Lucidchart rage-quit.
Try it yourself at codelit.io and check out the full feature set. The useful output is not the first plausible diagram. It is a draft you can inspect, correct, and keep editing without losing your work.
Questions people actually ask
- How does AI turn a text prompt into an architecture diagram?
- In five stages. An intent parser maps the prompt onto a known set of components, a pattern selector picks a topology such as microservices or a modular monolith, a language model generates node and edge JSON against a fixed schema, a layout engine assigns coordinates to every node, and a canvas library renders the result as an editable diagram. Each stage hands a stricter data structure to the next.
- How do you stop an LLM from returning invalid JSON?
- Use a provider's structured-output mode where supported, then validate the returned data before rendering it. Validation should check the object shape, unique node IDs, and valid edge references. A bounded repair attempt can handle some failures; refusals, truncation, and unavailable providers still need an explicit error path.
- How long does it take to generate an architecture diagram from a prompt?
- There is no useful universal number. Response time depends on the selected model, provider load, graph size, and whether validation requires another request. Measure the time to a usable, validated diagram separately from time to first token. Local layout usually contributes a different kind of cost than remote generation.
- Can you edit an AI generated architecture diagram without regenerating it?
- A diagram editor can support follow-up changes against the current architecture, but preserving manual work needs an explicit contract. The application must distinguish replacement from a patch, retain stable IDs, and validate the proposed change. Version history helps recovery; it does not by itself preserve every manual adjustment.
- What layout algorithm positions the nodes in a generated diagram?
- Codelit's current architecture canvas computes depth from root nodes, groups nodes into rows, and falls back to component-type ordering where needed. Dagre is a separate layout option discussed in this article, not the current implementation. In either case, positions come from application code rather than another model call.