@arcgis/ai-components 5.2.0-next.0 → 5.2.0-next.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,9 +1,9 @@
1
1
  /* COPYRIGHT Esri - https://js.arcgis.com/5.2/LICENSE.txt */
2
2
  import { StateGraph as b, START as V, END as v } from "@langchain/langgraph/web";
3
- import { g as T, O as R, P as C, t as N, Q as k, f as y, R as D, r as z, S as $, T as P } from "./adapter.js";
3
+ import { g as T, O as R, P as C, t as N, Q as k, f as h, R as D, r as z, S as $, T as P } from "./adapter.js";
4
4
  import "@arcgis/core/identity/IdentityManager.js";
5
5
  import "@arcgis/core/portal/Portal.js";
6
- import { s as l, j as A, h as Q } from "./generateLayerDescriptions.js";
6
+ import { s as c, j as A, h as Q } from "./generateLayerDescriptions.js";
7
7
  import { AIMessage as j } from "@langchain/core/messages";
8
8
  import "@langchain/openai";
9
9
  import "@arcgis/core/core/reactiveUtils.js";
@@ -14,9 +14,9 @@ import { h as I } from "./toolCallResponse.js";
14
14
  import { ToolNode as O } from "@langchain/langgraph/prebuilt";
15
15
  import G from "@arcgis/core/smartMapping/statistics/summaryStatistics.js";
16
16
  import U from "@arcgis/core/smartMapping/statistics/uniqueValues.js";
17
- const W = async (t, e) => (await l({ text: "Exiting Data Exploration agent" }, e), t), B = async (t, e) => {
18
- await l({ text: "Requesting LLM for layer filter results" }, e);
19
- const c = await T("data_explore_filter_prompt");
17
+ const W = async (t, e) => (await c({ text: "Exiting Data Exploration agent" }, e), t), B = async (t, e) => {
18
+ await c({ text: "Requesting LLM for layer filter results" }, e);
19
+ const d = await T("data_explore_filter_prompt");
20
20
  if (!e?.configurable)
21
21
  throw new Error("config.configurable is required for layer filter tools");
22
22
  const { userTimezone: n, userTimezoneOffset: r } = R(), o = {
@@ -28,61 +28,70 @@ const W = async (t, e) => (await l({ text: "Exiting Data Exploration agent" }, e
28
28
  userRequest: t.agentExecutionContext.userRequest,
29
29
  priorSteps: t.agentExecutionContext.priorSteps
30
30
  }, s = await A({
31
- promptText: c,
31
+ promptText: d,
32
32
  modelTier: "advanced",
33
33
  messages: t.dataExplorationMessages,
34
34
  inputVariables: o,
35
35
  tools: C
36
36
  }), i = [...t.dataExplorationMessages, s];
37
37
  if (!((s.tool_calls?.length ?? 0) > 0))
38
- return await l({ text: "LLM determined no filter changes needed" }, e), {
38
+ return await c({ text: "LLM determined no filter changes needed" }, e), {
39
39
  ...t,
40
40
  dataExplorationMessages: i
41
41
  // Don't overwrite outputMessage if query already set it
42
42
  };
43
- const m = [...i, s], g = s.content.toString();
44
- return await I(s, e), { ...t, dataExplorationMessages: m, outputMessage: g };
43
+ const m = [...i, s], p = s.content.toString();
44
+ return await I(s, e), { ...t, dataExplorationMessages: m, outputMessage: p };
45
45
  }, H = async (t, e) => {
46
- await l({ text: "Requesting LLM for layer query results" }, e);
47
- const c = await T("data_explore_query_prompt");
46
+ await c({ text: "Requesting LLM for layer query results" }, e);
47
+ const d = await T("data_explore_query_prompt");
48
48
  if (!e?.configurable)
49
49
  throw new Error("config.configurable is required for layer query tools");
50
- const { userTimezone: n, userTimezoneOffset: r } = R(), o = {
50
+ const { userTimezone: n, userTimezoneOffset: r } = R(), s = t.agentExecutionContext.sharedState.lastNavigatedFeatures, i = s && typeof s == "object" && "value" in s ? s.value : s;
51
+ let l = "none";
52
+ if (i != null)
53
+ try {
54
+ l = JSON.stringify(i);
55
+ } catch {
56
+ l = "none";
57
+ }
58
+ const m = {
51
59
  layerFieldInfo: t.layerFieldInfo,
52
60
  userTimezone: n,
53
61
  userTimezoneOffset: r,
54
62
  assignedTask: t.agentExecutionContext.assignedTask,
55
63
  userRequest: t.agentExecutionContext.userRequest,
56
- priorSteps: t.agentExecutionContext.priorSteps
57
- }, s = await A({
58
- promptText: c,
64
+ priorSteps: t.agentExecutionContext.priorSteps,
65
+ lastNavigatedFeatures: l
66
+ }, p = await A({
67
+ promptText: d,
59
68
  modelTier: "advanced",
60
69
  messages: t.dataExplorationMessages,
61
- inputVariables: o,
70
+ inputVariables: m,
62
71
  tools: k
63
- }), i = s.content.toString();
64
- return await I(s, e), {
72
+ }), y = p.content.toString();
73
+ return await I(p, e), {
65
74
  ...t,
66
- dataExplorationMessages: [...t.dataExplorationMessages, s],
67
- outputMessage: i,
75
+ dataExplorationMessages: [...t.dataExplorationMessages, p],
76
+ outputMessage: y,
68
77
  status: "success",
69
- summary: i ? N(i) : "Query executed."
78
+ summary: y ? N(y) : "Query executed."
70
79
  };
71
- }, K = async (t, e) => {
80
+ }, J = async (t, e) => {
72
81
  try {
73
- await l({ text: "Requesting LLM for summary on query results" }, e);
74
- const c = await T("summarize_query_response_prompt"), n = {
82
+ await c({ text: "Requesting LLM for summary on query results" }, e);
83
+ const d = await T("summarize_query_response_prompt"), n = {
75
84
  queryResponse: t.queryResponse,
76
85
  assignedTask: t.agentExecutionContext.assignedTask,
77
86
  userRequest: t.agentExecutionContext.userRequest,
78
87
  priorSteps: t.agentExecutionContext.priorSteps
79
88
  }, r = await Q({
80
- promptText: c,
89
+ promptText: d,
81
90
  modelTier: "fast",
82
91
  messages: t.dataExplorationMessages,
83
92
  inputVariables: n
84
93
  }), o = typeof r == "string" ? r : r.content, s = new j(o);
85
- await l({ text: `Received response from LLM: ${o}` }, e);
94
+ await c({ text: `Received response from LLM: ${o}` }, e);
86
95
  const i = o;
87
96
  return {
88
97
  ...t,
@@ -92,28 +101,28 @@ const W = async (t, e) => (await l({ text: "Exiting Data Exploration agent" }, e
92
101
  dataExplorationMessages: [...t.dataExplorationMessages, s]
93
102
  };
94
103
  } catch (a) {
95
- throw await l({ text: "Error during filter LLM request" }, e), new Error(`Error during filter LLM request: ${a instanceof Error ? a.message : String(a)}`);
104
+ throw await c({ text: "Error during filter LLM request" }, e), new Error(`Error during filter LLM request: ${a instanceof Error ? a.message : String(a)}`);
96
105
  }
97
106
  };
98
- async function J(t, e) {
99
- const c = await new O(C).invoke(
107
+ async function K(t, e) {
108
+ const d = await new O(C).invoke(
100
109
  {
101
110
  messages: t.dataExplorationMessages
102
111
  },
103
112
  e
104
113
  );
105
- return await l(
106
- { text: `Finished executing layer filter tool: ${c.messages.map((n) => n.content).join(", ")}` },
114
+ return await c(
115
+ { text: `Finished executing layer filter tool: ${d.messages.map((n) => n.content).join(", ")}` },
107
116
  e
108
117
  ), { ...t };
109
118
  }
110
- const X = 10, Y = ["string", "small-integer", "integer"], Z = async (t, e, { includeSummaryStatistics: a = !0, includeUniqueValues: c = !0 } = {}) => {
119
+ const X = 10, Y = ["string", "small-integer", "integer"], Z = async (t, e, { includeSummaryStatistics: a = !0, includeUniqueValues: d = !0 } = {}) => {
111
120
  let n = null, r = null;
112
121
  try {
113
122
  if (e.type !== "geometry" && e.type !== "oid" && e.type !== "global-id") {
114
123
  a && (n = await G({ layer: t, field: e.name }));
115
124
  const o = e.domain?.type === "coded-value" ? e.domain : null;
116
- c && (Y.includes(e.type) || o) && (r = (await U({ layer: t, field: e.name })).uniqueValueInfos.sort((s, i) => i.count - s.count).slice(0, X), o && (r = r.map((s) => ({
125
+ d && (Y.includes(e.type) || o) && (r = (await U({ layer: t, field: e.name })).uniqueValueInfos.sort((s, i) => i.count - s.count).slice(0, X), o && (r = r.map((s) => ({
117
126
  ...s,
118
127
  value: o.getName(s.value) ?? s.value
119
128
  }))));
@@ -129,49 +138,49 @@ const X = 10, Y = ["string", "small-integer", "integer"], Z = async (t, e, { inc
129
138
  function ee(t, e) {
130
139
  return ["string", "small-integer", "integer"].includes(t) || e === "coded-value";
131
140
  }
132
- async function te(t, e, a, c = !0) {
141
+ async function te(t, e, a, d = !0) {
133
142
  const n = [], r = [], o = [];
134
143
  for (const s of t) {
135
- let i = function(p) {
136
- const d = e.get(p)?.layerItem;
137
- return d ? [
138
- d.name && `Name: ${d.name}`,
139
- d.title && `Title: ${d.title}`,
140
- d.description && `Description: ${d.description}`
141
- ].filter(Boolean).join(" | ") : p;
144
+ let i = function(g) {
145
+ const u = e.get(g)?.layerItem;
146
+ return u ? [
147
+ u.name && `Name: ${u.name}`,
148
+ u.title && `Title: ${u.title}`,
149
+ u.description && `Description: ${u.description}`
150
+ ].filter(Boolean).join(" | ") : g;
142
151
  };
143
- const { layerId: u, results: m } = s, g = a.map?.allLayers.find((p) => p.id === u), S = e.get(u)?.fieldRegistry;
144
- if (!S)
152
+ const { layerId: l, results: m } = s, p = a.map?.allLayers.find((g) => g.id === l), y = e.get(l)?.fieldRegistry;
153
+ if (!y)
145
154
  continue;
146
- let h = n.find((p) => p.layerId === u);
147
- h || (h = {
148
- layerId: u,
149
- layerSummary: i(u),
155
+ let f = n.find((g) => g.layerId === l);
156
+ f || (f = {
157
+ layerId: l,
158
+ layerSummary: i(l),
150
159
  fieldInfos: []
151
- }, n.push(h));
152
- for (const p of m) {
153
- const d = S.get(p.name);
154
- if (!d)
160
+ }, n.push(f));
161
+ for (const g of m) {
162
+ const u = y.get(g.name);
163
+ if (!u)
155
164
  continue;
156
- const f = d.statistics, E = c && !f?.summaryStatistics, w = ee(d.type, d.domain?.type) && !f?.uniqueValues, q = E || w;
165
+ const x = u.statistics, E = d && !x?.summaryStatistics, w = ee(u.type, u.domain?.type) && !x?.uniqueValues, q = E || w;
157
166
  if (o.push({
158
- layerId: u,
159
- fieldName: d.name,
167
+ layerId: l,
168
+ fieldName: u.name,
160
169
  didFetchStatistics: q
161
170
  }), q) {
162
- const _ = Z(g, d, {
171
+ const _ = Z(p, u, {
163
172
  includeSummaryStatistics: E,
164
173
  includeUniqueValues: w
165
- }).then((M) => {
166
- const x = {
167
- summaryStatistics: f?.summaryStatistics ?? null,
168
- uniqueValues: f?.uniqueValues ?? null
174
+ }).then((F) => {
175
+ const S = {
176
+ summaryStatistics: x?.summaryStatistics ?? null,
177
+ uniqueValues: x?.uniqueValues ?? null
169
178
  };
170
- E && (x.summaryStatistics = M.summaryStatistics), w && (x.uniqueValues = M.uniqueValues), S.set(d.name, { ...d, statistics: x }), d.statistics = x;
179
+ E && (S.summaryStatistics = F.summaryStatistics), w && (S.uniqueValues = F.uniqueValues), y.set(u.name, { ...u, statistics: S }), u.statistics = S;
171
180
  });
172
181
  r.push(_);
173
182
  }
174
- h.fieldInfos.push(d);
183
+ f.fieldInfos.push(u);
175
184
  }
176
185
  }
177
186
  return await Promise.all(r), {
@@ -180,79 +189,79 @@ async function te(t, e, a, c = !0) {
180
189
  fieldStatisticsFetchStatus: o
181
190
  };
182
191
  }
183
- const F = /\b(average|avg|mean|median|max(?:imum)?|min(?:imum)?|sum|total|count|std(?:dev|\s*deviation)|variance|null\s*count|missing\s*values?|range)\b/iu;
192
+ const M = /\b(average|avg|mean|median|max(?:imum)?|min(?:imum)?|sum|total|count|std(?:dev|\s*deviation)|variance|null\s*count|missing\s*values?|range)\b/iu;
184
193
  function ae(t, e) {
185
- return F.test(t) || F.test(e);
194
+ return M.test(t) || M.test(e);
186
195
  }
187
196
  const se = async (t, e) => {
188
197
  try {
189
- await l({ text: "Preparing field information for vector search results" }, e);
190
- const a = y(e, "layersAndFieldsRegistry"), { mapView: c } = D(e), { assignedTask: n, userRequest: r } = t.agentExecutionContext, o = ae(n, r), { layerFieldInfo: s, didFetchStatistics: i, fieldStatisticsFetchStatus: u } = await te(
198
+ await c({ text: "Preparing field information for vector search results" }, e);
199
+ const a = h(e, "layersAndFieldsRegistry"), { mapView: d } = D(e), { assignedTask: n, userRequest: r } = t.agentExecutionContext, o = ae(n, r), { layerFieldInfo: s, didFetchStatistics: i, fieldStatisticsFetchStatus: l } = await te(
191
200
  t.vectorSearchFieldResults,
192
201
  a,
193
- c,
202
+ d,
194
203
  o
195
204
  );
196
- i ? await l({ text: "Statistics fetched" }, e) : await l({ text: "Statistics skipped" }, e);
197
- for (const m of u)
198
- await l(
205
+ i ? await c({ text: "Statistics fetched" }, e) : await c({ text: "Statistics skipped" }, e);
206
+ for (const m of l)
207
+ await c(
199
208
  {
200
209
  text: ` - ${m.fieldName} - stats ${m.didFetchStatistics ? "fetched" : "skipped"}`
201
210
  },
202
211
  e
203
212
  );
204
- return await l({ text: "Field information prepared" }, e), { ...t, layerFieldInfo: s };
213
+ return await c({ text: "Field information prepared" }, e), { ...t, layerFieldInfo: s };
205
214
  } catch (a) {
206
- throw await l({ text: "Error during fetching statistics" }, e), new Error(`Error during fetching statistics: ${a instanceof Error ? a.message : String(a)}`);
215
+ throw await c({ text: "Error during fetching statistics" }, e), new Error(`Error during fetching statistics: ${a instanceof Error ? a.message : String(a)}`);
207
216
  }
208
217
  }, L = 0.7, re = 10, oe = async (t, e) => {
209
218
  try {
210
- await l({ text: "Similarity search to find fields" }, e);
211
- const a = y(e, "fieldSearch"), c = y(e, "layersAndFieldsRegistry"), n = y(e, "embeddingCache"), r = await a.searchFields({
219
+ await c({ text: "Similarity search to find fields" }, e);
220
+ const a = h(e, "fieldSearch"), d = h(e, "layersAndFieldsRegistry"), n = h(e, "embeddingCache"), r = await a.searchFields({
212
221
  text: t.agentExecutionContext.assignedTask,
213
222
  layerIds: t.vectorSearchLayerIds,
214
223
  minScore: L,
215
224
  topResults: re,
216
225
  embeddingCache: n
217
- }), o = r.map(({ layerId: i, results: u }) => {
218
- const m = u.map((g) => ` - ${g.name} (${g.score.toFixed(2)})`).join(`
226
+ }), o = r.map(({ layerId: i, results: l }) => {
227
+ const m = l.map((p) => ` - ${p.name} (${p.score.toFixed(2)})`).join(`
219
228
  `);
220
- return `${c.get(i)?.layerItem.name ?? i}:
229
+ return `${d.get(i)?.layerItem.name ?? i}:
221
230
  ${m}`;
222
231
  }).join(`
223
232
  `);
224
233
  let s;
225
234
  return r.length > 0 ? s = `Vector search completed. Matching layers and fields with scores:
226
- ${o}` : s = `No vector search results found for score over ${L}.`, await l({ text: s }, e), {
235
+ ${o}` : s = `No vector search results found for score over ${L}.`, await c({ text: s }, e), {
227
236
  ...t,
228
237
  vectorSearchFieldResults: r
229
238
  };
230
239
  } catch (a) {
231
- throw await l(
240
+ throw await c(
232
241
  { text: `Error during vector search: ${a instanceof Error ? a.message : String(a)}` },
233
242
  e
234
243
  ), new Error(`Vector search failed: ${a instanceof Error ? a.message : String(a)}`);
235
244
  }
236
245
  }, ie = 0.7, ne = async (t, e) => {
237
246
  try {
238
- await l(
247
+ await c(
239
248
  { text: `Similarity search to find layers: ${t.agentExecutionContext.assignedTask}` },
240
249
  e
241
250
  );
242
- const a = y(e, "layerSearch"), c = y(e, "layersAndFieldsRegistry"), n = y(e, "embeddingCache"), r = await a.searchLayers({
251
+ const a = h(e, "layerSearch"), d = h(e, "layersAndFieldsRegistry"), n = h(e, "embeddingCache"), r = await a.searchLayers({
243
252
  text: t.agentExecutionContext.assignedTask,
244
253
  minScore: ie,
245
254
  embeddingCache: n
246
- }), o = r.map((u) => u.id), s = r.map(({ id: u, score: m }) => `${c.get(u)?.layerItem.name ?? u} (${m.toFixed(2)})`).join(`
255
+ }), o = r.map((l) => l.id), s = r.map(({ id: l, score: m }) => `${d.get(l)?.layerItem.name ?? l} (${m.toFixed(2)})`).join(`
247
256
  `);
248
257
  let i;
249
258
  return o.length > 0 ? i = `Vector search completed. Matching layers with scores:
250
- ${s}` : i = "Vector search completed. No matching layers found.", await l({ text: i }, e), {
259
+ ${s}` : i = "Vector search completed. No matching layers found.", await c({ text: i }, e), {
251
260
  ...t,
252
261
  vectorSearchLayerIds: o
253
262
  };
254
263
  } catch (a) {
255
- throw await l(
264
+ throw await c(
256
265
  { text: `Error during vector search: ${a instanceof Error ? a.message : String(a)}` },
257
266
  e
258
267
  ), new Error(`Vector search failed: ${a instanceof Error ? a.message : String(a)}`);
@@ -260,7 +269,7 @@ ${s}` : i = "Vector search completed. No matching layers found.", await l({ text
260
269
  }, le = (t, e) => z(["layerSearch", "fieldSearch", "layersAndFieldsRegistry"], "Data Exploration Agent")(
261
270
  t,
262
271
  e
263
- ), ce = () => new b($).addNode("requireDataExplorationServices", le).addNode("vectorSearchLayers", ne).addNode("vectorSearchFields", oe).addNode("fieldStatistics", se).addNode("queryAgent", H).addNode("queryTools", P).addNode("summarizeQueryResponseLLM", K).addNode("filterAgent", B).addNode("filterTools", J).addNode("earlyExit", W).addEdge(V, "requireDataExplorationServices").addEdge("requireDataExplorationServices", "vectorSearchLayers").addConditionalEdges(
272
+ ), ce = () => new b($).addNode("requireDataExplorationServices", le).addNode("vectorSearchLayers", ne).addNode("vectorSearchFields", oe).addNode("fieldStatistics", se).addNode("queryAgent", H).addNode("queryTools", P).addNode("summarizeQueryResponseLLM", J).addNode("filterAgent", B).addNode("filterTools", K).addNode("earlyExit", W).addEdge(V, "requireDataExplorationServices").addEdge("requireDataExplorationServices", "vectorSearchLayers").addConditionalEdges(
264
273
  "vectorSearchLayers",
265
274
  (e) => e.vectorSearchLayerIds.length ? "vectorSearchFields" : "earlyExit"
266
275
  ).addConditionalEdges(
@@ -277,7 +286,7 @@ ${s}` : i = "Vector search completed. No matching layers found.", await l({ text
277
286
  This also includes questions that ask which feature meets a given condition or where a particular feature in the data is located (e.g., “Where is the spring with the highest elevation?”). However, this agent does not handle addresses.
278
287
  _Example: “How many features are there?”_
279
288
  _Example: “What’s the average population?”_
280
- _Example: “Which values are in the status field?”_`, Fe = {
289
+ _Example: “Which values are in the status field?”_`, Me = {
281
290
  id: "dataExploration",
282
291
  name: "Data Exploration Agent",
283
292
  description: de,
@@ -285,5 +294,5 @@ ${s}` : i = "Vector search completed. No matching layers found.", await l({ text
285
294
  workspace: $
286
295
  };
287
296
  export {
288
- Fe as D
297
+ Me as D
289
298
  };
@@ -36,11 +36,26 @@ Latest user request:
36
36
  Prior steps:
37
37
  {priorSteps}
38
38
 
39
+ Last navigated features from shared state (if available):
40
+ {lastNavigatedFeatures}
41
+
39
42
  Use the assigned task as the primary instruction for the current query.
40
43
  Use the latest user request as supporting context when needed.
41
44
  Use prior steps only when the assigned task or user request clearly depends on earlier results.
45
+ Use lastNavigatedFeatures as authoritative context for map-navigation references when present.
42
46
  Use chat history only when necessary to understand conversational references, and do not override the assigned task.
43
47
 
48
+ ### Anchor selection rule (strict, single source of truth)
49
+
50
+ When the request is referential (for example: "this", "it", "that", "those", "them", "of those", "the same one(s)"), choose exactly one anchor using this priority order:
51
+
52
+ 1. Explicit entity named in the current request (if present)
53
+ 2. \`lastNavigatedFeatures\` (if present)
54
+ 3. The immediately previous successful relevant step from \`priorSteps\`
55
+
56
+ After selecting the anchor, keep that anchor for this query and do not switch to an older entity.
57
+ If \`lastNavigatedFeatures\` is used as the anchor, reuse its \`layerId\` and \`where\` for query scope unless the current request explicitly overrides it.
58
+
44
59
  ### Fresh WHERE clause rule
45
60
 
46
61
  Start with a fresh WHERE clause for each new question. Do not inherit filters from previous questions UNLESS the assigned task explicitly references previous results (see "Referencing Previously Identified Features" below).
@@ -63,11 +78,9 @@ Start with a fresh WHERE clause for each new question. Do not inherit filters fr
63
78
 
64
79
  This is an exception to the "fresh WHERE clause" rule above.
65
80
 
66
- When the assigned task uses words like "these", "those", "them", "the same ones" referring to features from a previous turn:
67
-
68
- - If the previous query used useCurrentExtent: true → use useCurrentExtent: true with where: "1=1"
69
- - If the previous assistant response mentions specific objectIds or feature IDs → use {{objectIdField}} IN (id1, id2, ...) to scope to those features
70
- - If neither is available, use the same WHERE clause and layer from the previous query turn to re-select the same features
81
+ - If the anchored context indicates \`useCurrentExtent: true\`, use \`useCurrentExtent: true\` with \`where: "1=1"\`.
82
+ - If the anchored context includes specific feature IDs/objectIds, scope to those IDs.
83
+ - Otherwise, use the anchored \`layerId\` + \`where\` combination.
71
84
 
72
85
  ### Mixed Questions
73
86
 
@@ -117,7 +117,6 @@ Do not reinterpret the request into a different form.
117
117
  - \`requiresFollowUp\` should indicate a likely next agent step, not uncertainty about the current request
118
118
  - Set \`requiresFollowUp\` to \`true\` only when another orchestration step will likely be needed after this agent succeeds
119
119
  - Set \`requiresFollowUp\` to \`false\` when a successful result from this agent would likely complete the user-visible task
120
- - Default to \`false\`
121
120
  `;
122
121
  export {
123
122
  e as default
@@ -58,6 +58,7 @@ In all cases:
58
58
 
59
59
  - Focus on **notable attribute values**, **commonalities or differences**, and **interesting trends**.
60
60
  - If the feature list is empty or not meaningful, state that clearly.
61
+ - Only use fields present in query results. Do not make guesses.
61
62
 
62
63
  ### STRICT OUTPUT RULES
63
64