TL;DR
- This guide shows how to build a governed AI assistant on AISquared’s UNIFI platform that translates natural-language questions into live database queries. Using a movie dataset example, it walks through guardrails for topic scope, conditional routing, human-in-the-loop approval for sensitive data, Text-to-SQL execution, and LLM synthesis.
- The same pattern applies to finance, healthcare, or government data, balancing direct access with full control.
Most enterprise data sits in databases. The challenge is not storing it, it is making it accessible in a safe and controlled way.
Natural language interfaces make it easy for users to ask questions. But without the right controls, they can also expose sensitive data, generate incorrect queries, or operate outside defined boundaries.
This guide shows how to build a governed AI assistant using AISquared’s UNIFI platform. The assistant translates user questions into live database queries, applies guardrails to keep responses in scope, and adds approval steps for sensitive requests.
The example uses a movie dataset, but the same pattern applies to real business systems: finance, operations, healthcare, or government data.
The goal is simple: give users direct access to data through AI, while keeping full control over what can be asked, what can be accessed, and how decisions are made.
Overview
This workflow builds a governed movie lookup assistant backed by a live database query. User input is first screened by a Guardrails node scoped to movie-related content — off-topic queries are rejected immediately. A Conditional node then evaluates the screened query: questions containing ‘box office’ are routed through a Human-in-the-Loop authorization gate before proceeding, while all other movie queries go directly to Text to SQL. The Text to SQL node translates the natural language question into a SQL query, executes it against the movie database, and returns the results. A Prompt node aggregates the query results with the original question and passes them to an LLM, which synthesizes a readable answer for the Chat Output.
| Use case: A movie information assistant that answers questions from a live relational database — genres, directors, cast, ratings, and more — with box office revenue queries gated behind human reviewer sign-off to control exposure of commercially sensitive data. |
Workflow Diagram
The diagram below shows the full node graph as configured in the UNIFI AI Workflows canvas.

Node Reference
This workflow uses eight nodes in a mostly linear sequence, with one branch point at the Conditional node. Both branches converge at the Text to SQL node.
| Category | Node Name | Role |
|---|---|---|
| Input / Output | Chat Input | Entry point — receives the user’s movie question |
| Execution | Guardrails | Scope screen — enforces topic alignment; only movie-related queries pass; all others are rejected with an out-of-scope message |
| Execution | Conditional | Routes box office queries to HITL for authorization; all other movie queries proceed directly to Text to SQL |
| Execution | Human in the Loop | Authorization gate for box office queries — Approve proceeds to Text to SQL; Reject returns a denial message |
| Data | Text to SQL | Translates the approved natural language question into SQL, executes it against the movie database, and returns Query Results, Generated SQL, and Schema |
| Model | Prompt | Aggregates the original user question and Text to SQL query results into a structured prompt for the LLM |
| Model | LLM | Synthesizes a natural language answer grounded in the query results |
| Input / Output | Chat Output | Returns the LLM response to the user |
Step 1 — Register Sources
Two sources must be registered in UNIFI before building the canvas: the movie database (used by Text to SQL) and the AI/ML model endpoint (used by both the Text to SQL node and the LLM node).
1.1 Movie Database Source
Sources → Add Data Source → PostgreSQL
| Field | Value / Notes |
|---|---|
| Host | Hostname or IP of your PostgreSQL server |
| Port | 5432 (default) |
| Database | Name of the movie database |
| Schema | Schema containing the movie tables (e.g. public) |
| Username | Must have SELECT access on the movie tables |
| Password | Stored encrypted by UNIFI |
| Tip: The Text to SQL node auto-introspects the database schema at runtime — it reads table and column names to inform SQL generation. Verify that the connecting user has SELECT access on all tables you want the assistant to query. |
1.2 AI/ML Source
Sources → AI/ML Sources → Add AI/ML Source
Register the LLM that powers both the Text to SQL node (for SQL generation) and the LLM node (for natural language synthesis). The same source can be used for both, or different providers can be selected per node.
| Field | Value / Notes |
|---|---|
| Provider | Anthropic or OpenAI (any supported provider) |
| API Key | Obtain from the provider’s console |
| HTTP Timeout | 30 seconds (default) |
| Request Format | Sample request JSON for the selected provider and model |
| Response Format | Sample response JSON — used to map the output field |
| Model Inputs | Map the dynamic message/content field as a model input |
| Model Outputs | Map the response text field as the model output |
Step 2 — Build the Canvas Workflow
In UNIFI: AI Workflows → New Workflow → open the canvas editor
2.1 Chat Input
- Add a Chat Input node as the workflow entry point
- Connect Chat Input → Message to Guardrails → input
2.2 Guardrails — Scope Screen
Category: Execution — enforces topic alignment to movie-related queries only
This Guardrails node is configured as a scope guard rather than a PII screen. Its purpose is to keep the assistant on-topic — only movie-related questions pass through. This prevents the Text to SQL node from generating queries against unintended tables or domains.
- Connect Chat Input → Message to Guardrails → input
- Configure scope rules — enable topic/relevance enforcement set to the movies domain; reject questions about unrelated subjects (sports, finance, medical, etc.)
- Connect Guardrails → Pass to Conditional → input
- Connect Guardrails → Fail to a Chat Output node with an out-of-scope message, e.g. ‘I can only answer questions about movies. Please ask about a film, director, genre, or cast.’
| Guardrails Rule Type | Purpose in This Workflow |
|---|---|
| Topic/Scope Enforcement | Restricts queries to the movies domain — blocks off-topic questions before SQL generation |
| PII Detection (optional) | Can also be enabled to prevent user personal data from being embedded in SQL queries |
| Important: Always wire the Guardrails Fail path. An unconnected Fail output results in a silent failure — the user receives no response. For a scope guard specifically, the Fail message should clearly tell the user what the assistant is designed to answer. |
2.3 Conditional — Box Office Routing
The Conditional node checks whether the screened query mentions box office revenue or earnings. Box office data is commercially sensitive, so these queries are routed through Human-in-the-Loop before SQL is generated. All other movie queries proceed directly to Text to SQL.
| Output | Condition | Destination |
|---|---|---|
| Condition 1 | Input contains ‘box office’, ‘revenue’, ‘gross’, or ‘earnings’ | Human in the Loop → Input Text |
| Condition 2 | (optional) Input asks about ticket sales or financial performance | Human in the Loop → Input Text |
| Default | All other movie queries | Text to SQL → Question (bypasses HITL) |
- Configure Condition 1: operator contains, value box office — add additional conditions for ‘revenue’, ‘gross’, ‘earnings’ if needed
- Set Default Input to pass so the query is forwarded unchanged on the Default path
- Connect Conditional → Condition 1 to Human in the Loop → Input Text
- Connect Conditional → Default to Text to SQL → Question
| Tip: The Conditional conditions are evaluated in order — Condition 1 is checked first. If the query matches, it is routed to HITL regardless of other conditions. The Default path fires only when no condition matches, ensuring all box office queries are consistently captured. |
2.4 Human in the Loop — Box Office Authorization
Category: Execution — pauses the workflow for a reviewer to authorize box office queries
Box office and revenue data is financially sensitive. The Human in the Loop node sends the query to a designated reviewer before the Text to SQL node is allowed to generate and execute a query against that data.
- Connect Conditional → Condition 1 to Human in the Loop → Input Text
- Configure reviewer assignment — specify which user(s) or role(s) receive the approval request
- Set a timeout policy — define behavior if no reviewer responds within the window (e.g. auto-reject after a set duration)
- Connect Human in the Loop → Approve to Text to SQL → Question — approved queries join the same path as general movie queries
- Connect Human in the Loop → Reject to a Chat Output node with a denial message, e.g. ‘Box office data access was not approved for this request.’
| Important: Always wire both Approve and Reject. The Human in the Loop node pauses asynchronously — the user receives no response until a reviewer acts. Set clear expectations in the welcome message that box office queries require review and may take additional time. |
2.5 Text to SQL Node
Category: Data — translates natural language into SQL and executes it against the movie database
The Text to SQL node connects to the movie database registered in Step 1.1. It auto-introspects the schema, generates a SQL SELECT query from the natural language input, executes it, and returns three outputs.
- Both Conditional → Default and Human in the Loop → Approve connect to Text to SQL → Question
- In the node configuration, select the movie database source registered in Step 1.1
- Select the AI/ML source registered in Step 1.2 — this model powers the SQL generation step
- The node will introspect the database schema automatically — verify the correct tables are visible
| Output | Description |
|---|---|
| Query Results | The rows returned by the executed SQL — passed to the Prompt node for LLM synthesis |
| Generated SQL | The SQL statement produced by the node — useful for debugging and audit logging |
| Schema | The database schema as interpreted by the node — useful for debugging incorrect query generation |
- Connect Text to SQL → Query Results to Prompt → Query Results
| Tip: During development, wire the Generated SQL output to a secondary Chat Output to inspect the queries being produced. If the LLM’s final answer is wrong or incomplete, the Generated SQL is the fastest way to diagnose whether the query retrieved the right data. |
2.6 Prompt Node
Category: Model — aggregates the user question and database results for the LLM
- Connect Chat Input → Message to Prompt → Input Message — the original question provides context for the LLM’s answer
- Connect Text to SQL → Query Results to Prompt → Query Results
- Write a prompt template that clearly structures both inputs:
| You are a movie information assistant.Answer the user’s question using only the database results below.If the results are empty or insufficient, say so explicitly. User question: {{Input Message}} Database results:{{Query Results}} |
- Connect Prompt → Rendered Prompt Output to LLM → Input Message
2.7 LLM Node
Category: Model — synthesizes the final natural language response
- Connect Prompt → Rendered Prompt Output to LLM → Input Message
- Select the AI/ML source registered in Step 1.2
- Connect LLM → Response to Chat Output → Text
2.8 Chat Output Node
- Add a Chat Output node and connect LLM → Response to its Text input
- Ensure all failure/rejection paths are also wired to Chat Output nodes:
| Failure Path | Suggested Message |
|---|---|
| Guardrails → Fail | ‘I can only answer questions about movies. Please ask about a film, director, genre, or cast.’ |
| Human in the Loop → Reject | ‘Box office data access was not approved for this request.’ |
| Conditional → Default (if needed) | Only if Default is used as a second rejection path rather than forwarding to Text to SQL |
Pipeline Flow Reference
The table below maps every connection. Both branches of the Conditional (Default and HITL Approve) converge at Text to SQL. Chat Input → Message is also forwarded directly to Prompt → Input Message so the original question is available for the final LLM synthesis.
| From | → | To | Data / Notes |
|---|---|---|---|
| Chat Input → Message | → | Guardrails → input | Raw user question |
| Chat Input → Message | → | Prompt → Input Message | Original question carried forward for LLM context |
| Guardrails → Pass | → | Conditional → input | Scope-verified movie query |
| Guardrails → Fail | → | Chat Output (out-of-scope) | Non-movie query — rejected before any SQL generation |
| Conditional → Condition 1 | → | Human in the Loop → Input Text | Box office / revenue query — requires authorization |
| Conditional → Default | → | Text to SQL → Question | General movie query — proceeds directly |
| Human in the Loop → Approve | → | Text to SQL → Question | Authorized box office query joins general query path |
| Human in the Loop → Reject | → | Chat Output (HITL rejection) | Reviewer denied — no SQL generated |
| Text to SQL → Query Results | → | Prompt → Query Results | Database rows returned by generated SQL |
| Prompt → Rendered Prompt Output | → | LLM → Input Message | Aggregated prompt: question + query results |
| LLM → Response | → | Chat Output → Text | Final natural language answer |
Step 3 — Test in the Playground
| Test Case | Sample Input | Expected Behavior |
|---|---|---|
| General movie query | ‘Who directed Inception?’ | Guardrails pass → Conditional Default → Text to SQL generates and runs query → Prompt → LLM → Chat Output |
| Genre query | ‘List all sci-fi movies in the database’ | Guardrails pass → Conditional Default → Text to SQL queries genre column → LLM synthesizes list |
| Box office (approved) | ‘What was the box office revenue for Avatar?’ | Conditional → Condition 1 → HITL → Reviewer approves → Text to SQL → Prompt → LLM → Chat Output |
| Box office (rejected) | ‘What was the box office revenue for Avatar?’ | Conditional → Condition 1 → HITL → Reviewer rejects → denial Chat Output; no SQL generated |
| Out-of-scope query | ‘What is the score of tonight’s game?’ | Guardrails → Fail → out-of-scope Chat Output; Conditional and Text to SQL never reached |
| Empty results | Query about a film not in the database | Text to SQL returns empty rows; verify LLM Prompt template handles empty Query Results without hallucinating |
| Generated SQL check | Any query during development | Wire Generated SQL to secondary Chat Output to inspect the produced query and debug mismatch between question and results |
| Tip: Test the empty results case specifically — a well-written Prompt template should instruct the LLM to say ‘No results found for that query’ rather than speculating. If the LLM invents movie data, tighten the template with an explicit instruction not to answer beyond the provided database results. |
Step 4 — Configure Interface and Export
4.1 Interface Settings
| Setting | Recommended Value |
|---|---|
| Assistant Name | ‘Movie Lookup’ or your preferred display name |
| Feedback Options | Enable thumbs up/down for query quality tracking |
| Welcome Message | e.g. ‘Ask me anything about movies in our database — directors, cast, genres, ratings, and more. Box office queries require reviewer approval.’ |
| Placeholder Text | e.g. ‘Ask about a movie, director, genre, or cast…’ |
4.2 Save, Export, and Embed
Click Save, then Export or Embed to generate the embeddable assistant snippet. Copy the dataAppId and dataAppUseCaseId from the export panel and insert them into the HTML template below:
| <html> <body> <h2>Movie Lookup</h2> <div id=”movie-lookup-container”></div></body> <script src=”https://api.squared.ai/enterprise/api/v1/data_apps_runner.js”></script><script> if (window.DataApp) { const dataApp = new window.DataApp({ dataAppId: ‘0’, dataAppUseCaseId: ‘YOUR_USE_CASE_ID_HERE’ }); dataApp.runDataApp(); }</script> </html> |
| Field | Description |
|---|---|
| dataAppId | Numeric Data App ID from the UNIFI export panel |
| dataAppUseCaseId | Unique use case ID string from the UNIFI export panel — replace ‘YOUR_USE_CASE_ID_HERE’ |
| data_apps_runner.js | Self-contained UNIFI script — includes rendering, data fetching, and authentication. No additional dependencies needed. |
| <div id=”…”> | Widget mount point — place anywhere in the page body; the chat UI renders inside this element |
| Tip: The embed snippet always connects to the latest published version of the workflow. Updating guardrail rules, the prompt template, or reviewer assignments in UNIFI does not require changing the HTML embed code. |
Quick Reference — Workflow Summary
| Step | Action | Key Detail |
|---|---|---|
| 1a | Add movie DB source | Sources → PostgreSQL; host, port, database, schema, username, password; SELECT access required |
| 1b | Add AI/ML source | Sources → AI/ML Sources; provider, API key, request/response format, model inputs/outputs |
| 2a | Chat Input | Entry point; Message → Guardrails AND Prompt (original question needed for LLM context) |
| 2b | Guardrails (scope) | Topic enforcement — movies only; Pass → Conditional; Fail → out-of-scope output; describe scope clearly in Fail message |
| 2c | Conditional | Condition 1 (box office/revenue) → HITL; Default (all other movie queries) → Text to SQL directly |
| 2d | Human in the Loop | Box office authorization; Approve → Text to SQL; Reject → denial output; configure reviewer + timeout; wire both paths |
| 2e | Text to SQL | Select movie DB source + AI/ML source; auto-introspects schema; Query Results → Prompt; wire Generated SQL during dev |
| 2f | Prompt + LLM | Input Message (from Chat Input) + Query Results → Prompt template; Rendered Prompt → LLM → Response → Chat Output |
| 3 | Test 7 cases | General query, genre query, box office approved, box office rejected, out-of-scope, empty results, Generated SQL inspection |
| 4 | Configure + Export + Embed | Interface Settings (name, feedback, welcome message) → Save → Export → copy IDs → HTML embed template → deploy |