UltipaDocs
Products
Solutions
Resources
Company
Start Free Trial
UltipaDocs
Start Free Trial
  • Overview
  • Provider Configuration
  • AI Completion
  • Vectors
  • Vector Index
  1. Docs
  2. /
  3. AI & Vectors

AI Completion

AI completion functions use a large language model to generate or execute GQL queries from natural language.

NOTE

Use ai.set_completion_provider() to set the active completion AI provider.

Example Graph

GQL
INSERT (alice:Person {_id: 'U1', name: 'Alice', age: 28, city: 'New York'}),
       (bob:Person {_id: 'U2', name: 'Bob', age: 32, city: 'New York'}),
       (carol:Person {_id: 'U3', name: 'Carol', age: 25, city: 'Boston'}),
       (david:Person {_id: 'U4', name: 'David', age: 30, city: 'Boston'}),
       (eve:Person {_id: 'U5', name: 'Eve', age: 27, city: 'Seattle'}),
       (alice)-[:FRIEND]->(bob), (alice)-[:FRIEND]->(carol),
       (bob)-[:FRIEND]->(david), (carol)-[:FRIEND]->(david),
       (david)-[:FRIEND]->(eve)

NL-to-GQL: ai.gql()

ai.gql() converts a natural language question into a GQL query using the configured completion provider. It is registered in two forms:

FormReturnsWhen to use
Function: RETURN ai.gql()The generated GQL string or a {gql, rows, count} mapYou only need the query string (and optionally its result rows), and don't care about per-stage timing, token counts, or tool calls.
Procedure: CALL ai.gql()One row per pipeline stageYou want a live trace: latency per stage, token usage, which tools the model called, what it tried before settling.

Function Form

The function form supports four positional arguments:

ArgumentTypeDefaultDescription
nlSTRING/Required. The natural-language question to translate.
instructionSTRING""Extra guidance for the LLM on top of the auto-loaded schema.
timeout_msINTEGER0Per-call timeout bounding the entire pipeline. 0 means no timeout.
executeBOOLEANfalseWhen true, the generated query is also executed and the result is returned as a {gql, rows, count} map. Only read-only queries are allowed; mutating queries are rejected.

Simplest: NL in, GQL string out.

GQL
RETURN ai.gql("Names of Alice's friends")

Result:

ai.gql("Names of Alice's friends")
MATCH (a:Person)-[:FRIEND]->(b:Person) WHERE a.name = 'Alice' RETURN b.name

Generate and execute:

GQL
RETURN ai.gql("Names of Alice's friends", "", 0, true)

Result:

JSON
{
  "gql": "MATCH (a:Person)-[:FRIEND]->(b:Person) WHERE a.name = 'Alice' RETURN b.name",
  "rows": [
    { "b.name": "Bob" },
    { "b.name": "Carol" }
  ],
  "count": 2
}

Procedure Form

Parameters are passed as a single map literal. The procedure form is a superset of the function form, it accepts the same nl / instruction / timeout_ms / execute arguments, plus three procedure-only parameters:

ParameterTypeDefaultDescription
nlSee Function Form above.
instruction
timeout_ms
execute
conversation_idSTRING/Thread id for multi-turn refinement. Prior turns sharing this id are surfaced in the LLM prompt; successful turns are appended back automatically, so follow-ups like "now filter to Bologna" refine the previous query instead of starting fresh.
dry_runBOOLEANfalseOnly meaningful with execute: true. Runs grounding + generation + validation but skips execution; the final event carries the generated GQL with data.dry_run = true. Use as a plan-before-execute preview.
max_rows_scannedINTEGER0Only meaningful with execute: true. Cost gate: if the preflight estimate of rows the query would touch exceeds this cap, the pipeline emits an error stage row and does not execute. 0 = no cap.
GQL
CALL ai.gql({nl: "Names of Alice's friends"})
YIELD stage, detail, elapsed_ms, tokens_input, tokens_output, tokens_cached, data

Result (one row per pipeline stage):

stagedetailelapsed_mstokens_inputtokens_outputtokens_cacheddata
startNames of Alice's friends0000{require_read: false}
routingMATCH intent — using LLM path0000{}
grounding1 labels, 1 patterns selected925000{pattern_count: 1, node_labels: ["Person"], edge_labels: ["FRIEND"]}
generationMATCH (a:Person)-[:FRIEND]->(b:Person) WHERE a.name = 'Alice' RETURN b.name21655263272816{tool_calls: 0, duration_ms: 1240, has_text: true, step: 0}
generationfinal candidate: MATCH (a:Person)-[:FRIEND]->(b:Person) WHERE a.name = 'Alice' RETURN b.name21655263272816{candidate: "MATCH (a:Person)-[:FRIEND]->(b:Person) WHERE a.name = 'Alice' RETURN b.name", steps: 1, termination: "final"}
validationpassed21665263272816{passed: true, class: "read"}
finalMATCH (a:Person)-[:FRIEND]->(b:Person) WHERE a.name = 'Alice' RETURN b.name21665263272816{gql: "MATCH (a:Person)-[:FRIEND]->(b:Person) WHERE a.name = 'Alice' RETURN b.name"}

With extra guidance and a 15-second cap, filtering down to the final GQL string:

GQL
CALL ai.gql({
  nl: "Friends of friends of David",
  instruction: "FRIEND edges are bidirectional even though stored directed; consider both directions",
  timeout_ms: 15000
})
YIELD stage, data
FILTER stage = "final"
RETURN data.gql

Result:

data.gql
MATCH (a:Person {name: 'Alice'})-[:FRIEND]-()-[:FRIEND]-(fof:Person) WHERE fof <> a RETURN DISTINCT fof.name

ai.gql() produces multiple rows of output as the NL-to-GQL pipeline executes. The pipeline automatically includes the current graph's schema (labels, properties, edge patterns) as context for the LLM.

Pipeline Stages

StageDescription
startInitiated.
routingIntent classification.
groundingSchema selection.
generationLLM query generation. May repeat across refinement loops.
toolLLM invoked an agent tool (validate_gql, sample_query, show_algorithms, ...).
validationAST check on the candidate query.
refinementValidation failed; asking the LLM to repair (loops back to generation).
finalSuccess; data.gql holds the generated query.
errorTerminal failure. Can replace final after any preceding stage.

The happy path is start → routing → grounding → generation → (tool ↔ generation)* → validation → final. On failure at any step the stream emits an error row and ends; if a refinement budget remains, the pipeline first tries refinement → generation before giving up.

Each returned row carries the stage plus six telemetry columns:

  • detail (one-line human summary)
  • elapsed_ms (wall time since CALL began)
  • tokens_input / tokens_output / tokens_cached (cumulative LLM token counters)
  • data (stage-specific payload — e.g. the generated GQL on final, tool args + result on tool).

You can omit any column you don't care about. Common patterns:

GQL
-- Just the final query
CALL ai.gql({nl: "Names of Alice's friends"}) YIELD stage, data
FILTER stage = "final"
RETURN data.gql

-- Stage trace only, no payload
CALL ai.gql({nl: "Names of Alice's friends"}) 
YIELD stage, detail, elapsed_ms

-- Just token accounting
CALL ai.gql({nl: "..."}) YIELD stage, tokens_input, tokens_output, tokens_cached
FILTER stage = "final"
RETURN tokens_input, tokens_output, tokens_cached

Agent Tools

During the generation stage the LLM can call any of the following tools. Each call surfaces as a tool row in the stream with data.name, data.args, and data.result:

ToolPurpose
get_label_propertyLook up the properties defined on a specific node or edge label.
get_overviewGet the current graph's label counts and edge patterns.
classify_queryAsk the read-only classifier whether a candidate query is read or write.
validate_gqlParse + validate a candidate query against the current schema.
sample_queryExecute a candidate read-only query against the graph with a hard LIMIT injected and a short timeout — lets the model verify shape and approximate result size before committing.
show_algorithmsList the 77 built-in algorithms (optionally filtered by category). Returns the same payload as SHOW ALGOS.
describe_algorithmLook up signature, parameters, and YIELD columns for a specific algorithm.

Common Intents

The system prompt steers algorithm-shaped questions to CALL algo.<name>(...) YIELD ... rather than a naive MATCH ... RETURN. Common mappings:

IntentAlgorithm
Most important / influential nodesalgo.pagerank, algo.betweenness
Communities / clustersalgo.louvain, algo.wcc
Shortest pathalgo.shortestpath
Neighborhood expansionalgo.khop_fast
Similar nodesalgo.similarity, algo.knn
Embeddingsalgo.node2vec, algo.fastrp

Example — asking for "the most influential people" emits:

GQL
CALL algo.pagerank() YIELD nodeId, score
ORDER BY score DESC LIMIT 10

Pipeline Inspection

These functions let you re-run, replay, or audit the NL-to-GQL pipeline.

ai.explain()

Runs the NL-to-GQL pipeline and returns the generated query alongside a reasoning trace (schema used, tool calls, refinements). Does not execute the query.

Syntaxai.explain(<question>)
ArgumentsNameTypeDescription
<question>STRINGA natural language question
Return TypeRECORD

The returned map contains:

FieldTypeDescription
gqlSTRINGThe generated GQL query.
traceMAPPipeline details including schema used, generation steps, tool calls, validation result, and token usage.
GQL
RETURN ai.explain("Find ALL of Alice's friends")

Result:

JSON
{
  "gql": "MATCH (a:Person {name: 'Alice'})-[:FRIEND]->(f:Person) RETURN f",
  "trace": {
    "total_duration_ms": 4753,
    "schema": {
      "label_count": 1,
      "pattern_count": 1,
      "node_labels": ["Person"],
      "edge_labels": ["FRIEND"]
    },
    "generation": {
      "used_tool_use": true,
      "termination": "final",
      "steps": [
        {
          "assistant_text": "",
          "duration_ms": 1517,
          "tool_calls": [
            {
              "name": "validate_gql",
              "args": {"query": "MATCH (a:Person {name: 'Alice'})-[:FRIEND]->(f:Person) RETURN f"},
              "result": {
                "class": "read",
                "construct": "",
                "valid": true
              },
              "is_error": false,
              "duration_ms": 1
            }
          ]
        },
        {
          "assistant_text": "MATCH (a:Person {name: 'Alice'})-[:FRIEND]->(f:Person) RETURN f",
          "duration_ms": 1837,
          "tool_calls": []
        }
      ]
    },
    "validation": {
      "passed": true,
      "class": "read",
      "construct": "",
      "parse_err": ""
    },
    "token_usage": {
      "cached_input_tokens": 6144,
      "total_tokens": 12674,
      "calls": 2,
      "by_model": {
        "openai/gpt-4o-mini": {
          "provider": "openai",
          "model": "gpt-4o-mini",
          "input_tokens": 12629,
          "output_tokens": 45
        }
      }
    },
    "nl": "Find ALL of Alice's friends",
    "termination": "success",
    "examples": [],
    "refinements": []
  }
}

ai.trace()

Returns the most recent NL-to-GQL pipeline trace recorded by ai.gql() or ai.explain(). Returns NULL if no pipeline call has run in the current session. Useful for debugging when a generated query is incorrect — you can inspect which labels were selected, what tool calls were made, and where the pipeline went wrong.

Syntaxai.trace()
ArgumentsNone
Return TypeRECORD
GQL
RETURN ai.trace()

ai.traces()

Returns the most recent n pipeline traces as a list of maps (newest first). When n is 0 or omitted, returns the full retained history (capped at 32). Use this to compare runs side-by-side rather than only seeing the latest trace.

Syntaxai.traces([<n>])
ArgumentsNameTypeDescription
<n>INTOptional. Maximum number of traces to return. Must be non-negative.
Return TypeLIST<RECORD>
GQL
RETURN ai.traces(3)

Feedback

ai.rate()

ai.rate() is the feedback hook into per-graph query memory. It acts on the most recent pipeline trace (the one ai.trace() would return) and does three things:

  1. Stamps the trace: attaches your 1–5 rating and optional comment, so future ai.trace() / ai.traces() calls show which runs were judged good or bad.
  2. Auto-purges bad examples: if rating ≤ 2 and the run was a success and query memory is enabled, the (NL, GQL) pair is removed from memory. A wrong answer never gets recalled as a few-shot example for similar future prompts. Ratings of 3, 4, 5 don't change memory, they're decoration only.
  3. Returns a BOOL: true when a trace was found and rated, false when no trace exists yet or the rating is out of range.

Without query memory enabled (ai.set_ai_config('query_memory_enabled', true)), step 2 is a no-op and ai.rate() only stamps the trace. The function earns its keep when query memory is on.

Syntaxai.rate(<rating> [, <comment>])
ArgumentsNameTypeDescription
<rating>INTScore from 1 (worst) to 5 (best).
<comment>STRINGOptional free-text note attached to the trace.
Return TypeBOOL

Typical loop:

GQL
-- 1. Generate a query and inspect the result
CALL ai.gql({nl: "How many friends does Alice have?"}) YIELD stage, data
FILTER stage = "final"
RETURN data.gql

-- 2. Rate it — a low rating purges this NL/GQL pair from memory
RETURN ai.rate(2, "model returned the friend list instead of a count")

Now any future "how many ..." prompt won't recall this bad answer. If you'd rated it 5, the pair would stick around and be surfaced as a positive example.

Skills

A skill is a named natural-language template that you can recall later and pipe into ai.gql(). Useful for capturing prompts that worked well so you (or other sessions) can reuse them by name.

ai.save_skill()

Registers a named NL template. Passing an empty NL deletes the skill (idempotent). Returns true.

Syntaxai.save_skill(<name>, <nl_template>)
ArgumentsNameTypeDescription
<name>STRINGIdentifier for the skill.
<nl_template>STRINGNatural-language template (or empty string to delete).
Return TypeBOOL
GQL
RETURN ai.save_skill("top_authors", "Who are the top 5 most-cited authors?")

ai.list_skills()

Returns every saved skill as a list of {name, nl, created_at} records.

Syntaxai.list_skills()
ArgumentsNone
Return TypeLIST<RECORD>
GQL
RETURN ai.list_skills()

Result:

JSON
[
  {
    "name": "top_authors", 
    "nl": "Who are the top 5 most-cited authors?", 
    "created_at": "2026-05-28T11:42:03Z"
  }
]

ai.drop_skill()

Removes a saved skill by name. Returns true when a skill was deleted, false when the name was unknown.

Syntaxai.drop_skill(<name>)
ArgumentsNameTypeDescription
<name>STRINGIdentifier of the skill to delete.
Return TypeBOOL
GQL
RETURN ai.drop_skill("top_authors")

ai.skill_nl()

Returns the NL template of a saved skill so it can be piped into ai.gql(). Returns NULL when the name is unknown.

Syntaxai.skill_nl(<name>)
ArgumentsNameTypeDescription
<name>STRINGIdentifier of the saved skill.
Return TypeSTRING
GQL
CALL ai.gql({nl: ai.skill_nl("top_authors")})
YIELD stage, data
FILTER stage = "final"
RETURN data.gql

Pipeline Configuration

ai.ai_config()

Returns the current NL-to-GQL pipeline configuration.

Syntaxai.ai_config()
ArgumentsNone
Return TypeRECORD
GQL
RETURN ai.ai_config()

Result:

JSON
{
  "max_steps": 4,
  "max_refinements": 2,
  "schema_top_k": 8,
  "patterns_top_k": 8,
  "examples_top_k": 3,
  "query_memory_enabled": false,
  "query_memory_size": 256,
  "pipeline": "agentic"
}

ai.set_ai_config()

Sets a configuration parameter for the NL-to-GQL pipeline.

Syntaxai.set_ai_config(<key>, <value>)
ArgumentsNameTypeDescription
<key>STRINGConfiguration key
<value>STRING / INT / FLOAT / BOOLConfiguration value
Return TypeBOOL

Available configuration keys:

KeyTypeDescription
max_stepsINTMaximum number of pipeline steps
max_refinementsINTMaximum query refinement attempts
schema_top_kINTNumber of top schema elements to include
patterns_top_kINTNumber of top edge patterns to include
examples_top_kINTNumber of similar query examples to include
query_memory_enabledBOOLEnable per-graph in-memory query memory. Successful (NL, GQL) pairs are kept in a ring buffer and recalled as few-shot examples for similar future questions. Recall uses cosine similarity when an embedder is configured, token overlap otherwise. Off by default.
query_memory_sizeINTMaximum number of remembered queries per graph (ring-buffer size).
pipelineSTRINGPipeline mode to use
GQL
RETURN ai.set_ai_config("max_refinements", 3)

Enable per-graph query memory:

GQL
RETURN ai.set_ai_config("query_memory_enabled", true)