본문 바로가기
WIKI 기술 지식 베이스

Adding search templates

원문 보기 위키 갱신

출처: 문서

본문

검색 템플릿 추가하기

QueryPlanningTool은 등록 시 검색 템플릿 목록을 받을 수 있어요. 검색을 수행할 때 QueryPlanningTool이 사용자 질문과 템플릿 설명을 바탕으로 적절한 검색 템플릿을 선택하고, 대형 언어 모델(LLM)이 선택된 검색 템플릿을 기반으로 쿼리를 생성해요.

이 접근 방식을 활용하면 LLM 혼자서는 다루기 어려운 복잡한 사용 사례도 해결할 수 있어요:

  • 에이전틱 검색에서 쿼리 응답의 일관성을 높여요. 쿼리 도메인 특화 언어(DSL) 쿼리의 대부분은 검색 템플릿이 제공하고, LLM은 일부 조각이나 플레이스홀더만 제공해요.
  • LLM이 올바른 쿼리를 생성하는 데 어려움을 겪는 복잡한 사용 사례를 처리해요.
  • 예측 가능한 쿼리 구조와 네이밍 컨벤션을 보장해요.

모범 사례

에이전틱 검색용 검색 템플릿을 만들 때는 다음 가이드라인을 따르세요:

  • LLM이 적절하게 선택할 수 있도록 각 템플릿에 상세한 설명을 작성하세요.
  • 무엇을 채워야 하는지 명확히 드러나는 설명적인 플레이스홀더 이름을 사용하세요.
  • 평소 자주 쓰는 다양한 쿼리 패턴에 맞춰 템플릿을 만드세요.
  • 배포하기 전에 다양한 입력으로 템플릿이 올바르게 동작하는지 검증하세요.

1단계: 인덱스 생성하기

복잡한 집계 시나리오를 시연하기 위해 중첩된 인벤토리 데이터를 담은 stores 인덱스를 생성하세요:

PUT /stores
{
  "mappings": {
    "properties": {
      "store_id": { "type": "keyword" },
      "name": { "type": "text", "fields": { "keyword": { "type": "keyword", "ignore_above": 256 } } },
      "address": {
        "properties": {
          "city": { "type": "keyword" },
          "state": { "type": "keyword" }
        }
      },
      "location": { "type": "geo_point" },
      "inventory": {
        "type": "nested",
        "properties": {
          "sku": { "type": "keyword" },
          "qty": { "type": "integer" }
        }
      }
    }
  }
}

{% include copy-curl.html %}

2단계: 문서 수집하기

여러 도시와 제품의 인벤토리 데이터가 담긴 샘플 매장 문서를 추가하세요:

POST /_bulk
{ "index": { "_index": "stores", "_id": "S-SEA-001" } }
{ "store_id": "S-SEA-001", "name": "Downtown Seattle", "address": { "city": "Seattle", "state": "WA" }, "location": { "lat": 47.608, "lon": -122.335 }, "inventory": [ { "sku": "iphone_17_air", "qty": 12 }, { "sku": "iphone_17", "qty": 11 }, { "sku": "vision_pro", "qty": 3 } ] }
{ "index": { "_index": "stores", "_id": "S-SEA-002" } }
{ "store_id": "S-SEA-002", "name": "Capitol Hill", "address": { "city": "Seattle", "state": "WA" }, "location": { "lat": 47.623, "lon": -122.319 }, "inventory": [ { "sku": "iphone_17_air", "qty": 5 }, { "sku": "iphone_17", "qty": 25 }, { "sku": "vision_pro", "qty": 4 } ] }
{ "index": { "_index": "stores", "_id": "S-SEA-003" } }
{ "store_id": "S-SEA-003", "name": "South Lake Union", "address": { "city": "Seattle", "state": "WA" }, "location": { "lat": 47.626, "lon": -122.338 }, "inventory": [ { "sku": "iphone_17_air", "qty": 6 }, { "sku": "iphone_17", "qty": 9 }, { "sku": "vision_pro", "qty": 20 } ] }
{ "index": { "_index": "stores", "_id": "S-BEL-001" } }
{ "store_id": "S-BEL-001", "name": "Bellevue Square", "address": { "city": "Bellevue", "state": "WA" }, "location": { "lat": 47.616, "lon": -122.203 }, "inventory": [ { "sku": "iphone_17_air", "qty": 14 }, { "sku": "iphone_17", "qty": 4 }, { "sku": "vision_pro", "qty": 1 } ] }
{ "index": { "_index": "stores", "_id": "S-SEA-004" } }
{ "store_id": "S-SEA-004", "name": "Ballard", "address": { "city": "Seattle", "state": "WA" }, "location": { "lat": 47.668, "lon": -122.382 }, "inventory": [ { "sku": "iphone_17_air", "qty": 9 }, { "sku": "iphone_17", "qty": 7 }, { "sku": "vision_pro", "qty": 12 } ] }

{% include copy-curl.html %}

3단계: 검색 템플릿 등록하기

세 개 SKU에 걸친 합산 인벤토리가 최소 기준을 충족하는 도시의 매장을 반환하는 검색 템플릿을 등록하세요:

POST /_scripts/store_sum_skus
{
  "script": {
    "lang": "mustache",
    "source": {
      "size": 0,
      "query": { "term": { "address.city": "{% raw %}{{city}}{% endraw %}" } },
      "aggs": {
        "by_store": {
          "terms": {
            "field": "store_id",
            "size": "{% raw %}{{bucket_size}}{{^bucket_size}}200{{/bucket_size}}{% endraw %}",
            "order": { "inv>skus>q": "desc" }
          },
          "aggs": {
            "inv": {
              "nested": { "path": "inventory" },
              "aggs": {
                "skus": {
                  "filter": { "terms": { "inventory.sku": ["{% raw %}{{sku1}}{% endraw %}","{% raw %}{{sku2}}{% endraw %}","{% raw %}{{sku3}}{% endraw %}"] } },
                  "aggs": { "q": { "sum": { "field": "inventory.qty" } } }
                }
              }
            },
            "keep": {
              "bucket_selector": {
                "buckets_path": { "t": "inv>skus>q" },
                "script": { "source": "params.t >= {% raw %}{{min_total}}{{^min_total}}30{{/min_total}}{% endraw %}" }
              }
            },
            "store": {
              "top_hits": {
                "size": 1,
                "_source": { "includes": ["store_id","name","address.city"] }
              }
            }
          }
        }
      }
    }
  }
}

{% include copy-curl.html %}

특정 SKU를 최소 수량 이상 보유한 도시 내 매장 수를 세는 검색 템플릿을 등록하세요:

POST /_scripts/stores_with_give_sku
{
  "script": {
    "lang": "mustache",
    "source": {
      "size": 0,
      "query": { "term": { "address.city": "{% raw %}{{city}}{% endraw %}" } },
      "aggs": {
        "s": {
          "terms": {
            "field": "store_id",
            "size": "{% raw %}{{bs}}{{^bs}}200{{/bs}}{% endraw %}"
          },
          "aggs": {
            "i": {
              "nested": { "path": "inventory" },
              "aggs": {
                "f": {
                  "filter": { "term": { "inventory.sku": "{% raw %}{{sku}}{% endraw %}" } },
                  "aggs": { "q": { "sum": { "field": "inventory.qty" } } }
                }
              }
            },
            "m": {
              "bucket_script": {
                "buckets_path": { "x": "i>f>q" },
                "script": { "source": "{% raw %}params.x >= {{min}}{{^min}}10{{/min}} ? 1 : 0{% endraw %}" }
              }
            }
          }
        },
        "cnt": { "sum_bucket": { "buckets_path": "s>m" } }
      }
    }
  }
}

{% include copy-curl.html %}

4단계: QueryPlanningTool을 포함한 에이전트 등록하기

다음으로 QueryPlanningTool을 포함한 에이전트를 등록하고, 이 도구가 여러분의 검색 템플릿을 사용하도록 설정하세요.

4(a)단계: 에이전트와 QueryPlanningTool용 모델 생성하기

대화형 에이전트와 QueryPlanningTool 양쪽에서 사용할 모델을 등록하세요:

POST /_plugins/_ml/models/_register
{
  "name": "My OpenAI model: gpt-5",
  "function_name": "remote",
  "description": "Model for agentic search with templates",
  "connector": {
    "name": "My openai connector: gpt-5",
    "description": "The connector to openai chat model",
    "version": 1,
    "protocol": "http",
    "parameters": {
      "model": "gpt-5"
    },
    "credential": {
      "openAI_key": "<OPEN AI KEY>"
    },
    "actions": [
      {
        "action_type": "predict",
        "method": "POST",
        "url": "https://api.openai.com/v1/chat/completions",
        "headers": {
          "Authorization": "Bearer ${credential.openAI_key}"
        },
        "request_body": "{ \"model\": \"${parameters.model}\", \"messages\": [{\"role\":\"developer\",\"content\":\"${parameters.system_prompt}\"},${parameters._chat_history:-}{\"role\":\"user\",\"content\":\"${parameters.user_prompt}\"}${parameters._interactions:-}], \"reasoning_effort\":\"low\"${parameters.tool_configs:-}}"
      }
    ]
  }
}

{% include copy-curl.html %}

4(b)단계: 검색 템플릿을 사용하는 에이전트 등록하기

여러분의 검색 템플릿을 사용하도록 설정된 QueryPlanningTool을 포함한 에이전트를 등록하세요:

POST /_plugins/_ml/agents/_register
{
  "name": "Store Search Agent with Templates",
  "type": "conversational",
  "description": "Agent for store inventory searches using templates",
  "llm": {
    "model_id": "your-model-id-from-step-4a",
    "parameters": {
      "max_iteration": 15
    }
  },
  "memory": {
    "type": "conversation_index"
  },
  "parameters": {
    "_llm_interface": "openai/v1/chat/completions"
  },
  "tools": [
    {
      "type": "QueryPlanningTool",
      "parameters": {
        "model_id": "your-model-id-from-step-4a",
        "generation_type": "user_templates",
        "search_templates": [
          {
            "template_id": "store_sum_skus",
            "template_description": "Return stores in a given city where the combined quantity across a list of SKUs meets or exceeds a threshold."
          },
          {
            "template_id": "stores_with_give_sku",
            "template_description": "List stores in a given city that have at least min_qty units of a specific SKU."
          }
        ]
      }
    }
  ],
  "app_type": "os_chat"
}

{% include copy-curl.html %}

5단계: 검색 파이프라인 생성하기

검색 템플릿과 함께 여러분의 에이전트를 사용하는 검색 파이프라인을 생성하세요:

PUT _search/pipeline/agentic-pipeline
{
    "request_processors": [
        {
            "agentic_query_translator": {
                "agent_id": "your-agent-id-from-step-4b"
            }
        }
    ],
    "response_processors": [
        {
            "agentic_context": {
                "agent_steps_summary": true,
                "dsl_query": true
            }
        }
    ]
}

{% include copy-curl.html %}

6단계: 복잡한 질문 테스트하기

고급 집계가 필요한 복잡한 쿼리를 전송해 보세요:

POST /stores/_search?search_pipeline=agentic-pipeline
{
  "query": {
    "agentic": {
      "query_text": "List all stores in Seattle that have at least 30 combined units across these SKUs: iphone_17_air, iphone_17, and vision_pro."
    }
  }
}

{% include copy-curl.html %}

검색 템플릿이 없으면, 고급 집계와 스크립트가 얽힌 복잡한 쿼리는 LLM이 올바른 문법을 생성하지 못해 자주 실패해요. 예를 들어 4(b)단계에서 에이전트를 만들 때 검색 템플릿을 추가하지 않았다면, 앞선 요청은 다음과 비슷한 스크립트 실행 오류를 반환해요:

오류 응답 {: .text-delta}
{
  "error": {
    "root_cause": [
      {
        "type": "script_exception",
        "reason": "runtime error",
        "script_stack": [
          "for (item in params._source.inventory) { ",
          "                           ^---- HERE"
        ],
        "script": "int total = 0; for (item in params._source.inventory) { if (params.skus.contains(item.sku)) { if (item.qty instanceof Integer || item.qty instanceof Long) { total += (int)item.qty; } else if (item.qty instanceof String) { try { total += Integer.parseInt(it ...",
        "lang": "painless",
        "position": {
          "offset": 42,
          "start": 15,
          "end": 56
        }
      }
    ],
    "type": "search_phase_execution_exception",
    "reason": "all shards failed",
    "phase": "query",
    "grouped": true,
    "failed_shards": [
      {
        "shard": 0,
        "index": "stores",
        "node": "u3NEXA8PS8W8EJcT_9suGg",
        "reason": {
          "type": "script_exception",
          "reason": "runtime error",
          "script_stack": [
            "for (item in params._source.inventory) { ",
            "                           ^---- HERE"
          ],
          "script": "int total = 0; for (item in params._source.inventory) { if (params.skus.contains(item.sku)) { if (item.qty instanceof Integer || item.qty instanceof Long) { total += (int)item.qty; } else if (item.qty instanceof String) { try { total += Integer.parseInt(it ...",
          "lang": "painless",
          "position": {
            "offset": 42,
            "start": 15,
            "end": 56
          },
          "caused_by": {
            "type": "null_pointer_exception",
            "reason": "Cannot invoke \"Object.getClass()\" because \"callArgs[0]\" is null"
          }
        }
      }
    ]
  },
  "status": 400
}

반면 검색 템플릿이 있으면, 에이전트는 적절한 템플릿을 선택하고 파라미터를 채우는 방식으로 복잡한 쿼리를 처리할 수 있어요. LLM은 store_sum_skus 템플릿을 정확히 식별해 사용하고, 템플릿 파라미터(예: city: "Seattle", sku1: "iphone_17_air")를 채운 뒤 중첩 집계와 버킷 셀렉터가 포함된 유효한 쿼리를 생성해요. 응답에는 합산 인벤토리가 ≥ 30개인 매장(S-SEA-002, S-SEA-003)이 포함되어 있어요:

{
  "took": 21658,
  "timed_out": false,
  "terminated_early": true,
  "_shards": {
    "total": 1,
    "successful": 1,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": {
      "value": 4,
      "relation": "eq"
    },
    "max_score": null,
    "hits": []
  },
  "aggregations": {
    "by_store": {
      "doc_count_error_upper_bound": 0,
      "sum_other_doc_count": 0,
      "buckets": [
        {
          "key": "S-SEA-002",
          "doc_count": 1,
          "inv": {
            "doc_count": 3,
            "skus": {
              "doc_count": 3,
              "sum_qty": {
                "value": 34.0
              }
            }
          },
          "store": {
            "hits": {
              "total": {
                "value": 1,
                "relation": "eq"
              },
              "max_score": 1.0,
              "hits": [
                {
                  "_index": "stores",
                  "_id": "S-SEA-002",
                  "_score": 1.0,
                  "_source": {
                    "store_id": "S-SEA-002",
                    "address": {
                      "city": "Seattle"
                    },
                    "name": "Capitol Hill"
                  }
                }
              ]
            }
          }
        },
        {
          "key": "S-SEA-003",
          "doc_count": 1,
          "inv": {
            "doc_count": 3,
            "skus": {
              "doc_count": 3,
              "sum_qty": {
                "value": 35.0
              }
            }
          },
          "store": {
            "hits": {
              "total": {
                "value": 1,
                "relation": "eq"
              },
              "max_score": 1.0,
              "hits": [
                {
                  "_index": "stores",
                  "_id": "S-SEA-003",
                  "_score": 1.0,
                  "_source": {
                    "store_id": "S-SEA-003",
                    "address": {
                      "city": "Seattle"
                    },
                    "name": "South Lake Union"
                  }
                }
              ]
            }
          }
        }
      ]
    }
  },
  "ext": {
    "agent_steps_summary": "I have these tools available: [ListIndexTool, IndexMappingTool, query_planner_tool]\nFirst I used: query_planner_tool — qpt.question: \"List all stores in Seattle that have at least a combined total of 30 units across the following SKUs: \\\"iphone_17_air\\\", \\\"iphone_17\\\", and \\\"vision_pro\\\". The location must be Seattle. Sum the inventory counts for only these three SKUs per store and return stores where the sum is greater than or equal to 30.\"; index_name_provided: \"stores\"\nValidation: qpt output is valid JSON; adjusted numeric literals to integers and sizes to integers.",
    "memory_id": "-BxpmJkB-5P992SCQ-qU",
    "dsl_query":"{\"size\":0.0,\"query\":{\"term\":{\"address.city\":\"Seattle\"}},\"aggs\":{\"by_store\":{\"terms\":{\"field\":\"store_id\",\"size\":200.0},\"aggs\":{\"inv\":{\"nested\":{\"path\":\"inventory\"},\"aggs\":{\"skus\":{\"filter\":{\"terms\":{\"inventory.sku\":[\"iphone_17_air\",\"iphone_17\",\"vision_pro\"]}},\"aggs\":{\"sum_qty\":{\"sum\":{\"field\":\"inventory.qty\"}}}}}},\"keep\":{\"bucket_selector\":{\"buckets_path\":{\"total\":\"inv\>skus\>sum_qty\"},\"script\":{\"source\":\"params.total \>\= 30\"}}},\"store\":{\"top_hits\":{\"size\":1.0,\"_source\":{\"includes\":[\"store_id\",\"name\",\"address.city\"]}}}}}}}"
  }
}

관련 문서

더 알아보기 (Learn more)