Skip to content

Commit 2559fb4

Browse files
committed
improvement(azure-data-explorer): warn that ingest-from-query matches columns by position
Kusto aligns an ingested query result to the target table on column type and order, never on column name, so a query projecting the right columns in the wrong order lands data in the wrong columns without erroring. Surfaces that in the tool description and param the model reads, in the wand prompt that generates the query, in the rollup skill's steps, and in the docs. Also verifies the target schema first rather than after.
1 parent 3a04d0a commit 2559fb4

6 files changed

Lines changed: 23 additions & 8 deletions

File tree

apps/docs/content/docs/en/integrations/azure_data_explorer.mdx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ A few things worth knowing before you build:
4141
- **Results are capped at 10,000 rows.** Every result reports `rowCount`, `totalRowCount`, and `truncated`, so a query that returned more than the cap says so rather than quietly looking complete. Aggregate with `summarize` or bound the query with `take` instead of pulling raw rows.
4242
- **Ingest Rows Inline is for small batches.** It is ideal for tens or hundreds of rows from a workflow run. For continuous or high-volume loading, use Azure Data Explorer's queued or streaming ingestion instead.
4343
- **Ingest From Query defaults to `set-or-append`**, which adds to an existing table. `set-or-replace` discards everything already in the target table — pick it only when you mean to rebuild the rollup from scratch. For a large backfill, turn on the background option and poll Show Operations with the operation ID it returns.
44+
- **Ingest From Query matches columns by position, not by name.** Kusto aligns the query result to the target table on column type and order, so a query that projects the right columns in the wrong order ingests data into the wrong columns without erroring. End the query with an explicit `project` in the table's column order, and confirm with Show Table Schema first.
4445
- **Drop Table is permanent.** It deletes the table and its data. Give an agent the `viewers` role rather than `admins` unless a workflow genuinely needs to change schema.
4546
{/* MANUAL-CONTENT-END */}
4647

@@ -332,7 +333,7 @@ Push rows directly into an Azure Data Explorer table with .ingest inline. Data i
332333

333334
### Azure Data Explorer Ingest From Query
334335

335-
Materialize the result of a KQL query into a table with .set, .append, .set-or-append, or .set-or-replace. Use this to build rollup or summary tables instead of pushing rows from a workflow.
336+
Materialize the result of a KQL query into a table with .set, .append, .set-or-append, or .set-or-replace. Use this to build rollup or summary tables instead of pushing rows from a workflow. Kusto matches the query result to the target table by column type and position, NOT by column name, so project the columns in exactly the table's order or the data lands in the wrong columns.
336337

337338
#### Input
338339

@@ -346,7 +347,7 @@ Materialize the result of a KQL query into a table with .set, .append, .set-or-a
346347
| `database` | string | Yes | Database containing the target table |
347348
| `table` | string | Yes | Table to ingest the query result into |
348349
| `mode` | string | No | set \(create, fail if it exists\), append \(add to an existing table\), set-or-append \(default\), or set-or-replace \(replace all data\) |
349-
| `sourceQuery` | string | Yes | KQL query whose result becomes the ingested data \(e.g., LogsTable \| where Level == "Error" \| where Timestamp > ago\(1h\)\) |
350+
| `sourceQuery` | string | Yes | KQL query whose result becomes the ingested data \(e.g., LogsTable \| where Level == "Error" \| where Timestamp > ago\(1h\)\). Project the columns in the target table\'s order — matching is positional, not by name |
350351
| `async` | boolean | No | Return immediately with an OperationId and keep ingesting in the background. Check progress with Show Operations |
351352
| `ingestionProperties` | string | No | Optional ingestion properties clause contents, e.g. distributed=true, tags=\"\[''daily''\]\" |
352353

apps/docs/content/docs/en/integrations/table.mdx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,7 @@ Query rows from a table with filtering, sorting, and pagination
226226
| `totalCount` | number | Total rows matching filter |
227227
| `limit` | number | Limit used in query |
228228
| `offset` | number | Offset used in query |
229+
| `nextCursor` | string | Non-null when more rows match past this page. A page can end early at the byte budget, so this — not a short rowCount — is what says whether more remain. To page, advance offset by rowCount and stop when this is null. |
229230

230231
### Get Row
231232

apps/sim/blocks/blocks/azure_data_explorer.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,19 @@ Examples:
9090
9191
Return ONLY the schema - no explanations, no parentheses, no markdown fences.`
9292

93+
/**
94+
* Kusto matches an ingested query result to the target table positionally, by
95+
* column type — never by column name — so the prompt has to make column order
96+
* explicit or the generated query quietly fills the wrong columns.
97+
*/
98+
const INGEST_QUERY_WAND_PROMPT = `${KQL_WAND_PROMPT.replace('Return ONLY the KQL query - no explanations, no markdown fences.', '')}
99+
This query's result is ingested into an existing table. Kusto matches columns by
100+
position and type, NOT by name, so the projected columns must come out in the
101+
target table's column order. Use an explicit \`project\` listing the columns in
102+
that order as the final operator.
103+
104+
Return ONLY the KQL query - no explanations, no markdown fences.`
105+
93106
/** Switch and dropdown values arrive as booleans or their string form. */
94107
function toBoolean(value: unknown): true | undefined {
95108
return value === true || value === 'true' ? true : undefined
@@ -303,7 +316,7 @@ export const AzureDataExplorerBlock: BlockConfig<AzureDataExplorerTableResponse>
303316
required: { field: 'operation', value: 'azure_data_explorer_ingest_from_query' },
304317
wandConfig: {
305318
enabled: true,
306-
prompt: KQL_WAND_PROMPT,
319+
prompt: INGEST_QUERY_WAND_PROMPT,
307320
placeholder: 'Describe the rows you want to materialize into the table',
308321
},
309322
},
@@ -625,7 +638,7 @@ export const AzureDataExplorerBlockMeta = {
625638
description:
626639
'Build or refresh a summary table in Azure Data Explorer from a query over raw data.',
627640
content:
628-
'# Materialize A Rollup Table\n\nTurn an expensive query over raw telemetry into a small table that is cheap to read.\n\n## Steps\n1. Run Show Table Details on the source table to see how much data the query will scan.\n2. Write the aggregating KQL — `summarize` the raw rows into the shape you want, bucketing time with `bin()` where relevant.\n3. Run Ingest From Query against the target table. Use `set-or-append` to add the new window to an existing rollup, `set-or-replace` to rebuild it from scratch, and `set` only for the first run.\n4. For a large backfill, enable the background option and poll Show Operations with the returned operation ID until the state is Completed.\n5. Run Query against the rollup to confirm the row count and time range look right.\n\n## Output\nReport the target table, the mode used, the rows or extents produced, and the verification query result. Call out that `set-or-replace` discards the existing data in the table.',
641+
'# Materialize A Rollup Table\n\nTurn an expensive query over raw telemetry into a small table that is cheap to read.\n\n## Steps\n1. Run Show Table Details on the source table to see how much data the query will scan.\n2. Run Show Table Schema on the **target** table. Kusto matches an ingested query result to the table by column position and type, never by name, so you need its exact column order before writing the query.\n3. Write the aggregating KQL — `summarize` the raw rows into the shape you want, bucketing time with `bin()` where relevant — and end it with an explicit `project` listing the columns in the target table order.\n4. Run Ingest From Query against the target table. Use `set-or-append` to add the new window to an existing rollup, `set-or-replace` to rebuild it from scratch, and `set` only for the first run.\n5. For a large backfill, enable the background option and poll Show Operations with the returned operation ID until the state is Completed.\n6. Run Query against the rollup and spot-check a few rows to confirm each column holds what it should, not just that the row count is plausible.\n\n## Output\nReport the target table, the mode used, the rows or extents produced, and the verification query result. Call out that `set-or-replace` discards the existing data in the table, and that a mismatched column order corrupts data silently rather than failing.',
629642
},
630643
{
631644
name: 'debug-ingestion-failures',

apps/sim/lib/integrations/integrations.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2261,7 +2261,7 @@
22612261
},
22622262
{
22632263
"name": "Ingest From Query",
2264-
"description": "Materialize the result of a KQL query into a table with .set, .append, .set-or-append, or .set-or-replace. Use this to build rollup or summary tables instead of pushing rows from a workflow."
2264+
"description": "Materialize the result of a KQL query into a table with .set, .append, .set-or-append, or .set-or-replace. Use this to build rollup or summary tables instead of pushing rows from a workflow. Kusto matches the query result to the target table by column type and position, NOT by column name, so project the columns in exactly the table's order or the data lands in the wrong columns."
22652265
},
22662266
{
22672267
"name": "Create Table",

apps/sim/tools/azure_data_explorer/ingest_from_query.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ export const azureDataExplorerIngestFromQueryTool: ToolConfig<
1919
id: 'azure_data_explorer_ingest_from_query',
2020
name: 'Azure Data Explorer Ingest From Query',
2121
description:
22-
'Materialize the result of a KQL query into a table with .set, .append, .set-or-append, or .set-or-replace. Use this to build rollup or summary tables instead of pushing rows from a workflow.',
22+
"Materialize the result of a KQL query into a table with .set, .append, .set-or-append, or .set-or-replace. Use this to build rollup or summary tables instead of pushing rows from a workflow. Kusto matches the query result to the target table by column type and position, NOT by column name, so project the columns in exactly the table's order or the data lands in the wrong columns.",
2323
version: '1.0.0',
2424
params: {
2525
clusterUri: {
@@ -76,7 +76,7 @@ export const azureDataExplorerIngestFromQueryTool: ToolConfig<
7676
required: true,
7777
visibility: 'user-or-llm',
7878
description:
79-
'KQL query whose result becomes the ingested data (e.g., LogsTable | where Level == "Error" | where Timestamp > ago(1h))',
79+
'KQL query whose result becomes the ingested data (e.g., LogsTable | where Level == "Error" | where Timestamp > ago(1h)). Project the columns in the target table\'s order — matching is positional, not by name',
8080
},
8181
async: {
8282
type: 'boolean',

apps/sim/tools/generated/tool-metadata.ts

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)