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 YAML config. Access individual fields with dot notation.

Use locals to call once and access multiple fields:

locals:
  review: 'complete("review", "Review: " + name)'
args:
  - 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 YAML section. Each tool has a name, system prompt, and JSON Schema properties:

complete:
  tools:
    - name: 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.
      properties:
        review_text:
          type: string
          description: "A 2-3 sentence product review from a customer"
        sentiment:
          type: string
          enum: [positive, negative, neutral]
          description: "Overall sentiment of the review"
        rating:
          type: integer
          description: "Star rating from 1 to 5"
      required: [review_text, sentiment, rating]

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:
  - name: insert_review
    type: exec
    locals:
      review: 'complete("review", "Review: " + ref_rand("product_catalog").name)'
    args:
      - ref_rand("product_catalog").name
      - local("review").review_text
      - local("review").sentiment
      - local("review").rating
    query: |-
      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:
  - name: populate_review
    type: exec_batch
    count: 20
    size: 5
    locals:
      review: 'complete("review", "Review: " + ref_each(product_catalog).name)'
    args:
      - ref_each(product_catalog).name
      - local("review").review_text
      - local("review").sentiment
      - local("review").rating
    query: |-
      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:
  - name: populate_review
    type: exec_batch
    count: products
    size: products
    locals:
      reviews: 'complete_array("review", "Generate " + string(products) + " product reviews", products)'
    args:
      - ref_each(product_catalog).name
      - ref_each(local("reviews")).review_text
      - ref_each(local("reviews")).sentiment
      - ref_each(local("reviews")).rating
    query: |-
      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/.

globals:
  products: 20
  batch_size: 5

reference:
  product_catalog: !include products.yaml

complete:
  tools:
    - name: 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.
      properties:
        review_text:
          type: string
          description: "A 2-3 sentence product review"
        sentiment:
          type: string
          enum: [positive, negative, neutral]
        rating:
          type: integer
          description: "Star rating from 1 to 5"
      required: [review_text, sentiment, rating]

seed:
  - name: populate_review
    type: exec_batch
    count: products
    size: batch_size
    locals:
      review: 'complete("review", "Review: " + ref_each(product_catalog).name + " - " + ref_each(product_catalog).description)'
    args:
      - ref_each(product_catalog).name
      - local("review").review_text
      - local("review").sentiment
      - local("review").rating
    query: |-
      INSERT INTO review (product_name, review_text, sentiment, rating)
      __values__

run:
  - name: insert_review
    type: exec
    locals:
      review: 'complete("review", "Review: " + ref_rand("product_catalog").name)'
    args:
      - ref_rand("product_catalog").name
      - local("review").review_text
      - local("review").sentiment
      - local("review").rating
    query: |-
      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.