Tuyen Pham

Designing Context-Aware AI Workflows for Data-Intensive Products

August 17, 2026 - 8 min read0 views

AI chat looks simple from the outside: send a prompt, stream a response, render the result. In a data-intensive product, that is only the visible layer.

The difficult part is making sure the assistant understands which data the user means, what the user is allowed to access, and whether the answer still belongs to the current interaction.

While working on an enterprise analytical platform, I learned that reliable AI experiences depend less on the prompt box and more on the contracts surrounding it. This article describes the engineering patterns that made those workflows safer and more predictable.

The real problem is context

A general-purpose chatbot can treat each message as plain text. An analytical assistant cannot.

A question such as:

Show me the highest-value intervals and explain the anomalies.

is incomplete without additional context:

  • Which tenant is active?
  • Which project and database are selected?
  • Which report, dashboard, or analytical application is open?
  • Which datasets may the user query?
  • Which version of the conversation is current?
  • What should happen if the user changes context while a response is streaming?

If those decisions remain implicit, the system may produce an answer that is technically valid but belongs to the wrong dataset, project, or user session.

The first design principle is therefore simple:

Context is application state, not prompt decoration.

Model context as an explicit contract

Instead of assembling context from scattered UI variables, define a single request model shared by the frontend and backend.

A simplified TypeScript model might look like this:

type AssistantContext = { tenantId: string projectId: string databaseId: string resourceIds: string[] activeArtifactId?: string conversationId: string requestVersion: number }

The exact fields will differ between products, but the important properties remain the same:

  1. The context is explicit.
  2. Required identifiers are validated.
  3. Optional identifiers have documented behavior.
  4. Every request carries enough information to be authorized independently.
  5. A version distinguishes current work from stale work.

This object becomes a boundary. The UI creates it, the API validates it, authorization narrows it, and the AI orchestration layer consumes only the authorized result.

Keep display state separate from execution state

One subtle source of bugs is treating whatever appears selected in the interface as authoritative execution state.

For example, the user may change databases while a previous request is still running. The interface now displays Database B, but the active response was started with Database A.

A robust client stores an immutable context snapshot for each request:

const request = { message, context: createContextSnapshot(currentSelection), version: nextVersion(), }

The visible selection can continue changing without mutating the request already in progress. When streamed events arrive, the client compares their conversation and version identifiers before applying them.

This prevents a late response from appearing under the wrong dataset or replacing a newer answer.

Treat streaming as a lifecycle

Streaming is often implemented as a sequence of text chunks. In production, it is better understood as a state machine.

A useful lifecycle includes:

  • queued — accepted but not yet executing
  • preparing — validating access and resolving data context
  • running — executing tools or analytical queries
  • streaming — delivering provisional output
  • completed — final answer and metadata are available
  • cancelled — intentionally stopped by the user
  • failed — ended with a recoverable or terminal error

Each event should include stable identifiers:

type StreamEvent = { conversationId: string responseId: string version: number status: string payload?: unknown }

With this structure, the interface can reject stale events, show meaningful progress, and distinguish a cancelled response from a failed one.

Cancellation requires backend cooperation

A stop button that only hides the loading indicator is not real cancellation.

The frontend should signal intent, but the backend must also stop downstream work where possible:

  1. Mark the response as cancellation-requested.
  2. Abort the active model stream.
  3. Cancel tool execution or database work.
  4. Stop publishing new events.
  5. Persist a clear terminal state.

Not every external operation can be interrupted immediately. In those cases, the system should still prevent late output from becoming the active response.

Versioning provides a second layer of protection. Even if old work finishes, its version no longer matches the conversation's current version and the client ignores it.

Authorization belongs before AI orchestration

An AI assistant should never decide whether a user may access a resource.

Authorization must happen before prompts, tools, SQL, or document retrieval are constructed. A reliable request pipeline looks like this:

Authenticated identity → tenant membership → role and permission checks → project access → resource filtering → validated AI context → model and tool execution

This is especially important in multi-tenant systems. A resource identifier supplied by the client is only a request, not proof of access.

The backend should derive the allowed resource set from authoritative permissions and intersect it with the requested context. The model receives only the filtered result.

Validate generated analytical work

When an assistant can generate SQL or invoke analytical tools, model output becomes executable input.

Useful safeguards include:

  • Allow only approved statement types.
  • Reject multiple statements when they are unnecessary.
  • Enforce read-only execution credentials.
  • Apply time, row, and cost limits.
  • Validate database and schema identifiers.
  • Record the query separately from the natural-language response.
  • Return structured errors that the UI can explain.

Validation should be deterministic. Asking the model whether its own query is safe is not an authorization or security control.

Make tenant-level limits visible

AI workloads introduce costs that ordinary CRUD features do not. Token limits, model availability, and API credentials may vary by tenant.

A good quota system answers three questions:

  1. Can this request begin?
  2. How much capacity did it consume?
  3. What should the user see when capacity is exhausted?

The quota check should occur before expensive work, while usage should be recorded from the final provider response whenever possible.

The interface also needs useful feedback. “Something went wrong” is a poor experience when the real issue is an exhausted quota or a missing tenant configuration.

Observability needs domain identifiers

Traditional logs containing an HTTP path and status code are not enough for AI workflows.

Useful traces connect:

  • tenant and project identifiers
  • conversation and response identifiers
  • selected resources
  • request version
  • model and tool calls
  • token usage and estimated cost
  • cancellation and retry events
  • final status and duration

Sensitive prompts, credentials, and customer data should not be logged by default. Observability should explain the workflow without creating another data-exposure surface.

Design concurrency deliberately

Analytical platforms frequently synchronize catalogs, register resources, or start background jobs. Two users—or one user double-clicking—can trigger the same operation concurrently.

UI button disabling helps, but it is not a consistency guarantee.

Depending on the workflow, the backend may need:

  • database uniqueness constraints
  • idempotency keys
  • transactions
  • advisory or distributed locks
  • compare-and-set version checks
  • retry rules for transient conflicts

The goal is not to eliminate concurrency. It is to make repeated execution produce a predictable result.

Test transitions, not just endpoints

The highest-value tests cover state transitions across system boundaries.

Examples include:

  • A user without project access cannot start a conversation with that project's resources.
  • Changing the active database does not mutate an in-flight request.
  • A stopped response cannot overwrite a newer response.
  • A quota failure occurs before model execution.
  • Concurrent resource registration produces one valid record.
  • A malformed analytical query never reaches the execution service.
  • Stream reconnection does not duplicate completed content.

Unit tests are useful for validation and state reducers. Integration tests are essential where permissions, persistence, background work, and event delivery interact.

A practical review checklist

Before shipping an AI-assisted analytical workflow, I now ask:

Context

  • Is every required identifier explicit?
  • Is request context immutable after execution begins?
  • Can stale responses be detected?

Security

  • Is access validated server-side?
  • Does the model receive only authorized resources?
  • Is generated executable output deterministically validated?

Interaction

  • Are loading, progress, cancellation, failure, and completion distinct states?
  • Can users recover without restarting the entire workflow?
  • Are error messages specific enough to act on?

Reliability

  • Are repeated operations idempotent?
  • Are concurrent writes protected?
  • Can background work finish safely after the client disconnects?

Operations

  • Can one request be traced across services?
  • Are usage and cost measurable per tenant?
  • Are sensitive values excluded from logs?

Final takeaway

The quality of an AI product is not determined only by the model's best answer. It is determined by whether the system consistently gives the right user an answer from the right data, at the right moment, under the right permissions.

Once context, authorization, lifecycle management, observability, and concurrency are treated as product architecture—not supporting details—the chat interface becomes much easier to trust.