Skip to content

Commit 9687fd7

Browse files
authored
Adding support for custom fallback query for agentic search (#4729)
Signed-off-by: Joshua Palis <jpalis@amazon.com>
1 parent b8dd0aa commit 9687fd7

File tree

3 files changed

+345
-61
lines changed

3 files changed

+345
-61
lines changed

ml-algorithms/src/main/java/org/opensearch/ml/engine/tools/QueryPlanningPromptTemplate.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
public class QueryPlanningPromptTemplate {
44

55
public static final String DEFAULT_QUERY = "{\"size\":10,\"query\":{\"match_all\":{}}}";
6+
public static final String FALLBACK_QUERY_PROMPT_PLACEHOLDER = "{{FALLBACK_QUERY}}";
67

78
// ==== RULES ====
89
public static final String QUERY_TYPE_RULES = "Use only fields present in the provided mapping; never invent names.\n"
@@ -89,7 +90,7 @@ public class QueryPlanningPromptTemplate {
8990
+ "- Harvest candidates from the question (entities, attributes, constraints).\n"
9091
+ "- From query_fields (that exist) and the index mapping, choose fields that map to those candidates and the user intent—even if only loosely (use reasonable proxies).\n"
9192
+ "- Ignore other fields that don’t help answer the question.\n"
92-
+ "- Micro Self-Check (silent): verify chosen fields exist; if any don’t, swap to the closest mapped proxy and continue. Only if no remotely relevant fields exist at all, use the default match_all query.\n";
93+
+ "- Micro Self-Check (silent): verify chosen fields exist; if any don’t, swap to the closest mapped proxy and continue. Only if no remotely relevant fields exist at all, use the default query.\n";
9394

9495
public static final String PROMPT_PREFIX = "==== PURPOSE ====\n"
9596
+ "You are an OpenSearch DSL expert. Convert a natural-language question into a strict JSON OpenSearch query body.\n\n"
@@ -112,7 +113,7 @@ public class QueryPlanningPromptTemplate {
112113
+ "- Do NOT wrap in quotes or prose: no single quotes ('), no smart quotes (’ “ ”), no angle brackets (< >), no XML/HTML, no lists, no headers, no ellipses.\n"
113114
+ "- Use valid JSON only: standard double quotes (\") for all keys/strings; no comments; no trailing commas.\n"
114115
+ "- If the request truly cannot be fulfilled because no remotely relevant fields exist, return EXACTLY:\n"
115-
+ DEFAULT_QUERY
116+
+ FALLBACK_QUERY_PROMPT_PLACEHOLDER
116117
+ "\n";
117118

118119
// ==== EXAMPLES ==== (Field selection lines included only where they clarify proxies vs. distractors)
@@ -179,7 +180,7 @@ public class QueryPlanningPromptTemplate {
179180
+ "Input: List satellites with periapsis above 400km.\n"
180181
+ "Mapping: { \"properties\": { \"name\": { \"type\": \"text\" }, \"color\": { \"type\": \"keyword\" } } }\n"
181182
+ "Output: "
182-
+ DEFAULT_QUERY
183+
+ FALLBACK_QUERY_PROMPT_PLACEHOLDER
183184
+ "\n";
184185

185186
public static final String EXAMPLE_12 = "Example 12 — neural preferred with safe fallback (merged)\n"

ml-algorithms/src/main/java/org/opensearch/ml/engine/tools/QueryPlanningTool.java

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -92,11 +92,14 @@ public class QueryPlanningTool implements WithModelTool {
9292
private static final String TOOL_CONFIGS_FIELD = "tool_configs";
9393
private static final Set<String> AGENT_CONTEXT_EXCLUDED_PARAMS = Set
9494
.of(CHAT_HISTORY_FIELD, TOOLS_FIELD, INTERACTIONS_FIELD, TOOL_CONFIGS_FIELD);
95+
public static final String FALLBACK_QUERY_FIELD = "fallback_query";
9596

9697
@Getter
9798
private final String generationType;
9899
@Getter
99100
private final String searchTemplates;
101+
@Getter
102+
private final String fallbackQuery;
100103
@Setter
101104
@Getter
102105
private String name = TYPE;
@@ -132,11 +135,18 @@ public class QueryPlanningTool implements WithModelTool {
132135
@Getter
133136
private Parser outputParser;
134137

135-
public QueryPlanningTool(String generationType, MLModelTool queryGenerationTool, Client client, String searchTemplates) {
138+
public QueryPlanningTool(
139+
String generationType,
140+
MLModelTool queryGenerationTool,
141+
Client client,
142+
String searchTemplates,
143+
String fallbackQuery
144+
) {
136145
this.generationType = generationType;
137146
this.queryGenerationTool = queryGenerationTool;
138147
this.client = client;
139148
this.searchTemplates = searchTemplates;
149+
this.fallbackQuery = fallbackQuery;
140150
this.attributes = new HashMap<>(DEFAULT_ATTRIBUTES);
141151
}
142152

@@ -230,9 +240,19 @@ public <T> void run(Map<String, String> originalParameters, ActionListener<T> li
230240
@SuppressWarnings("unchecked")
231241
private <T> void executeQueryPlanning(Map<String, String> parameters, ActionListener<T> listener) {
232242
try {
243+
244+
// Replace fallback query in system prompt
245+
String effectiveFallbackQuery = (fallbackQuery != null) ? fallbackQuery : DEFAULT_QUERY;
246+
parameters.put(FALLBACK_QUERY_FIELD, effectiveFallbackQuery);
247+
248+
String escapedFallbackQuery = gson.toJson(effectiveFallbackQuery);
249+
escapedFallbackQuery = escapedFallbackQuery.substring(1, escapedFallbackQuery.length() - 1);
250+
251+
String systemPrompt = parameters.getOrDefault(QUERY_PLANNER_SYSTEM_PROMPT_FIELD, DEFAULT_QUERY_PLANNING_SYSTEM_PROMPT);
252+
systemPrompt = systemPrompt.replace(QueryPlanningPromptTemplate.FALLBACK_QUERY_PROMPT_PLACEHOLDER, escapedFallbackQuery);
253+
233254
// Execute Query Planning, replace System and User prompt fields
234-
parameters
235-
.put(SYSTEM_PROMPT_FIELD, parameters.getOrDefault(QUERY_PLANNER_SYSTEM_PROMPT_FIELD, DEFAULT_QUERY_PLANNING_SYSTEM_PROMPT));
255+
parameters.put(SYSTEM_PROMPT_FIELD, systemPrompt);
236256

237257
parameters.put(USER_PROMPT_FIELD, parameters.getOrDefault(QUERY_PLANNER_USER_PROMPT_FIELD, DEFAULT_QUERY_PLANNING_USER_PROMPT));
238258

@@ -254,9 +274,9 @@ private <T> void executeQueryPlanning(Map<String, String> parameters, ActionList
254274
try {
255275
String queryString = (String) r;
256276
if (queryString == null || queryString.isBlank() || queryString.equals("null")) {
257-
log.debug("Model failed to generate the DSL query, returning the Default match all query");
277+
log.debug("Model failed to generate the DSL query, returning the fallback query");
258278
StringSubstitutor substitutor = new StringSubstitutor(parameters, "${parameters.", "}");
259-
String defaultQueryString = substitutor.replace(DEFAULT_QUERY);
279+
String defaultQueryString = substitutor.replace(effectiveFallbackQuery);
260280
listener.onResponse((T) defaultQueryString);
261281
} else {
262282
listener.onResponse((T) (outputParser != null ? outputParser.parse(queryString) : queryString));
@@ -455,7 +475,9 @@ public QueryPlanningTool create(Map<String, Object> params) {
455475
}
456476
}
457477

458-
QueryPlanningTool queryPlanningTool = new QueryPlanningTool(type, queryGenerationTool, client, searchTemplates);
478+
String fallbackQuery = params.containsKey(FALLBACK_QUERY_FIELD) ? (String) params.get(FALLBACK_QUERY_FIELD) : null;
479+
480+
QueryPlanningTool queryPlanningTool = new QueryPlanningTool(type, queryGenerationTool, client, searchTemplates, fallbackQuery);
459481

460482
// Create parser with default extract_json processor + any custom processors
461483
queryPlanningTool.setOutputParser(createParserWithDefaultExtractJson(params));

0 commit comments

Comments
 (0)