Complete#

complete(tool_name, prompt) generates structured data via an LLM using OpenAI-compatible chat completion with tool calling. The LLM is forced to return data matching a tool schema defined in the config. Access individual fields with dot notation.

Use locals to call once and access multiple fields:

let review = complete("review", "Review: " + name)
(local("review").review_text, local("review").sentiment, local("review").rating)

Locals are re-evaluated per row in batch mode. Query-level locals override transaction locals when both exist.

Configuration#

FlagEnv VarDefault
--complete-api-keyEDG_COMPLETE_API_KEY(required)
--complete-urlEDG_COMPLETE_URLhttps://api.openai.com/v1/chat/completions
--complete-modelEDG_COMPLETE_MODELgpt-4o

Any OpenAI-compatible API works (Ollama, vLLM, Azure OpenAI, etc.) - set --complete-url to point at your endpoint.

Tool Schema#

Tools are defined in the complete section. Each tool has a name, system prompt, and JSON Schema properties:

complete {
  tool review(system: "You are a product review generator. Generate realistic reviews for consumer electronics. IMPORTANT: vary ratings across the full 1-5 range. Most products should get 2-4 stars. 5-star and 1-star reviews should be rare. Match sentiment and review_text to the rating you choose.") {
    review_text(type: string, required: true, description: "A 2-3 sentence product review from a customer")
    sentiment(type: string, enum: [positive, negative, neutral], required: true, description: "Overall sentiment of the review")
    rating(type: integer, required: true, description: "Star rating from 1 to 5")
  }
}

You can define multiple tools in the same config. Each tool name is referenced in the complete() call.

Execution Modes#

Immediate mode (exec/query)#

For single-row queries, complete() calls the API directly. Memoization ensures that multiple field accesses with the same tool and prompt within a row make only one API call:

run {
  insert_review {
    let review = complete("review", "Review: " + ref("product_catalog").name)
    (ref("product_catalog").name, local("review").review_text, local("review").sentiment, local("review").rating)
    `INSERT INTO review (product_name, review_text, sentiment, rating)
     VALUES ($1, $2, $3, $4)`
  }
}

Deferred mode (exec_batch/query_batch)#

For batch queries, all complete() calls are collected as placeholders during row evaluation, then resolved concurrently (up to 8 parallel requests) after all rows are generated. Placeholders are replaced in the formatted SQL before execution.

seed {
  populate_review(count: 20, size: 5) {
    let review = complete("review", "Review: " + ref_each(product_catalog).name)
    (ref_each(product_catalog).name, local("review").review_text, local("review").sentiment, local("review").rating)
    `INSERT INTO review (product_name, review_text, sentiment, rating)
     __values__`
  }
}

Array mode (complete_array)#

complete_array(tool_name, prompt, count) generates N items in a single API call. The tool schema is automatically wrapped in an items array request. Use ref_each() to iterate through the results:

seed {
  populate_review(count: products, size: products) {
    let reviews = complete_array("review", "Generate " + string(products) + " product reviews", products)
    (ref_each(product_catalog).name, ref_each(local("reviews")).review_text, ref_each(local("reviews")).sentiment, ref_each(local("reviews")).rating)
    `INSERT INTO review (product_name, review_text, sentiment, rating)
     __values__`
  }
}

This is more efficient than complete() in a loop because all N items are generated in 1 API call. The result is memoized by (tool_name, prompt, count) so multiple field accesses within a row make only one call.

A full working example is available at examples/complete_array/.

Example#

A full working example is available at examples/complete/.

let products = 20
let batch_size = 5

ref product_catalog include 'products.edg'

complete {
  tool review(system: "You are a product review generator. Generate realistic reviews for consumer electronics. IMPORTANT: vary ratings across the full 1-5 range. Most products should get 2-4 stars. 5-star and 1-star reviews should be rare. Match sentiment and review_text to the rating you choose.") {
    review_text(type: string, required: true, description: "A 2-3 sentence product review")
    sentiment(type: string, enum: [positive, negative, neutral], required: true)
    rating(type: integer, required: true, description: "Star rating from 1 to 5")
  }
}

seed {
  populate_review(count: products, size: batch_size) {
    let review = complete("review", "Review: " + ref_each(product_catalog).name + " - " + ref_each(product_catalog).description)
    (ref_each(product_catalog).name, local("review").review_text, local("review").sentiment, local("review").rating)
    `INSERT INTO review (product_name, review_text, sentiment, rating)
     __values__`
  }
}

run {
  insert_review {
    let review = complete("review", "Review: " + ref("product_catalog").name)
    (ref("product_catalog").name, local("review").review_text, local("review").sentiment, local("review").rating)
    `INSERT INTO review (product_name, review_text, sentiment, rating)
     VALUES ($1, $2, $3, $4)`
  }
}

Reliability#

Each LLM request retries up to 3 times with backoff (500ms, 1s) when:

  • The model returns no tool call
  • The model echoes the schema definition instead of generating values (e.g. returning {"type": "integer", "description": "..."} where an integer was expected)

Responses are validated against the tool schema. Property types (string, integer, number, boolean) must match. Invalid responses trigger a retry.

The per-request HTTP timeout is 120 seconds, which accommodates slower local models.

Using with Ollama#

edg seed \
  --complete-api-key ollama \
  --complete-url http://localhost:11434/v1/chat/completions \
  --complete-model qwen3:8b \
  ...

Tool calling support varies by model. Use models with reliable function calling support. qwen3:8b (5.2 GB) is a good default if you can prompt around the inherent sycophantism of smaller models. Larger models like qwen3:32b are more reliable but heavier. The built-in retry logic handles intermittent failures from smaller models.