Lotus.AI (Lotus v1.0.0)

Copy Markdown View Source

AI-powered query generation for Lotus.

Configuration

Configure AI in your application config:

config :lotus,
  ai: [
    enabled: true,
    model: "anthropic:claude-opus-4",
    api_key: {:system, "ANTHROPIC_API_KEY"}
  ]

The model key accepts any model string supported by ReqLLM, e.g.:

  • "openai:gpt-4o" (default)
  • "anthropic:claude-opus-4"
  • "google:gemini-2.0-flash"
  • "groq:llama-3.3-70b-versatile"
  • Any other provider supported by ReqLLM

Usage

{:ok, result} = Lotus.AI.generate_query(
  prompt: "Show me all users who signed up last month",
  data_source: "postgres"
)

# Returns:
# %{
#   statement: "SELECT * FROM users WHERE created_at >= ...",
#   variables: [],
#   model: "openai:gpt-4o",
#   usage: %{total_tokens: 150}
# }

Error Handling

The generate_query/1 function returns structured error tuples that clients can pattern match on for custom handling and internationalization (i18n):

  • {:ok, result} - Successfully generated statement
  • {:error, :not_configured} - AI features not enabled in config
  • {:error, :api_key_not_configured} - API key missing or invalid
  • {:error, {:unable_to_generate, reason}} - LLM refused (not a data question)
  • {:error, term} - Other errors (API failures, network issues, etc.)

Summary

Types

The set of AI features an adapter may support per-source.

Functions

Check if AI features are enabled and configured.

Get an AI-powered plain-language explanation of a query.

Generate a query statement from a natural language prompt.

Generate a query statement from a natural language prompt, with conversation context.

Get the configured AI model string.

Get AI-powered optimization suggestions for a statement.

Check whether a specific AI feature is supported for the given source.

Return the adapter-declared (or generic fallback) reason a feature is unsupported for this source, or nil when supported.

Types

feature()

@type feature() :: :generation | :optimization | :explanation

The set of AI features an adapter may support per-source.

Functions

enabled?()

@spec enabled?() :: boolean()

Check if AI features are enabled and configured.

Returns true if AI is properly configured, false otherwise.

Examples

Lotus.AI.enabled?()
# => true

explain_query(opts)

@spec explain_query(keyword()) :: {:ok, map()} | {:error, term()}

Get an AI-powered plain-language explanation of a query.

Supports explaining a full query or a selected fragment. When a fragment is provided, the full query is sent as context so the AI can explain even isolated terms accurately.

Options

  • :statement (required) - The full query statement
  • :fragment (optional) - A selected portion of the query to explain
  • :data_source (required) - Name of the data source to resolve schema context

Returns

  • {:ok, result} - Map with :explanation, :model, and :usage
  • {:error, term} - Structured error tuple

Examples

# Explain a full query
{:ok, result} = Lotus.AI.explain_query(
  statement: "SELECT d.name, COUNT(o.id) FROM departments d LEFT JOIN orders o ...",
  data_source: "postgres"
)

result.explanation
# => "This query shows departments ranked by total order count..."

# Explain a selected fragment
{:ok, result} = Lotus.AI.explain_query(
  statement: "SELECT d.name FROM departments d LEFT JOIN employees e ON e.department_id = d.id",
  fragment: "LEFT JOIN employees e ON e.department_id = d.id",
  data_source: "postgres"
)

generate_query(opts)

@spec generate_query(keyword()) :: {:ok, map()} | {:error, term()}

Generate a query statement from a natural language prompt.

Uses the globally configured AI provider from application config.

Options

  • :prompt (required) - Natural language description of desired query
  • :data_source (required) - Name of the data source to query against
  • :read_only (optional) - When true (default), the AI only generates read-only queries. Set to false to allow the AI to generate write queries.

Returns

  • {:ok, result} - Successfully generated statement with metadata
  • {:error, term} - Structured error tuple (see module docs for error types)

Examples

{:ok, result} = Lotus.AI.generate_query(
  prompt: "Count active users by signup month",
  data_source: "postgres"
)

result.statement
# => "SELECT DATE_TRUNC('month', created_at) as month, COUNT(*) FROM users WHERE status = 'active' GROUP BY month"

result.model
# => "openai:gpt-4o"

result.usage
# => %{prompt_tokens: 150, completion_tokens: 50, total_tokens: 200}

# Error handling with pattern matching
{:error, :not_configured} = Lotus.AI.generate_query(
  prompt: "some query",
  data_source: "postgres"
)

generate_query_with_context(opts)

@spec generate_query_with_context(keyword()) :: {:ok, map()} | {:error, term()}

Generate a query statement from a natural language prompt, with conversation context.

Enables multi-turn conversations by accepting conversation history. The AI can refine queries, fix errors, and provide iterative improvements.

Options

  • :prompt (required) - Natural language description of desired query
  • :data_source (required) - Name of the data source to query against
  • :conversation (optional) - Conversation struct with message history
  • :read_only (optional) - When true (default), the AI only generates read-only queries. Set to false to allow the AI to generate write queries.

Returns

  • {:ok, result} - Successfully generated statement with metadata
  • {:error, term} - Structured error tuple (see module docs for error types)

Examples

# Simple single-turn (same as generate_query/1)
{:ok, result} = Lotus.AI.generate_query_with_context(
  prompt: "Show active users",
  data_source: "postgres"
)

# Multi-turn with conversation history
conversation = Conversation.new()
conversation = Conversation.add_user_message(conversation, "Show active users")

{:ok, result} = Lotus.AI.generate_query_with_context(
  prompt: "Show active users",
  data_source: "postgres",
  conversation: conversation
)

# If query fails, add error to conversation
conversation = Conversation.add_query_result(conversation, {:error, "column 'status' not found"})

# AI can now fix the error with full context
{:ok, fixed_result} = Lotus.AI.generate_query_with_context(
  prompt: "Fix the error",
  data_source: "postgres",
  conversation: conversation
)

model()

@spec model() :: {:ok, String.t()} | {:error, :not_configured}

Get the configured AI model string.

Returns the full model string (e.g. "openai:gpt-4o") if configured.

Examples

Lotus.AI.model()
# => {:ok, "openai:gpt-4o"}

suggest_optimizations(opts)

@spec suggest_optimizations(keyword()) :: {:ok, map()} | {:error, term()}

Get AI-powered optimization suggestions for a statement.

Runs the adapter's prepare_for_analysis/2 + query_plan/3 to get an execution plan (when the engine exposes one), then asks the AI to review the statement and the plan for potential improvements. Adapters that can't produce a plan still get structural suggestions.

Options

  • :statement (required) — a %Lotus.Query.Statement{} to review.
  • :data_source (required) — name of the data source.
  • :search_path (optional) — Postgres search path.

Returns

  • {:ok, result} — map with :suggestions, :model, :usage.
  • {:error, :ai_not_supported_for_source} — the adapter opted out of AI via its ai_context/1 callback.
  • {:error, term} — other failure.

supports?(source_name, feature)

@spec supports?(String.t(), feature()) :: boolean()

Check whether a specific AI feature is supported for the given source.

Reads ai_context.capabilities[feature] via Lotus.Source.Adapter.ai_context/1. Returns false if the adapter opts out of AI entirely (returns {:error, _} from ai_context/1).

feature is one of :generation, :optimization, :explanation.

UIs should call this before rendering each AI button to hide or disable options the data source doesn't support.

unsupported_reason(source_name, feature)

@spec unsupported_reason(String.t(), feature()) :: String.t() | nil

Return the adapter-declared (or generic fallback) reason a feature is unsupported for this source, or nil when supported.

Reasons from untrusted adapters are replaced with a generic fallback at the dispatch layer — UIs can surface this string verbatim.