diff --git a/resources/prompts/aggregate-query-prompt-template.md b/resources/prompts/aggregate-query-prompt-template.md new file mode 100644 index 000000000..3b7dc46f8 --- /dev/null +++ b/resources/prompts/aggregate-query-prompt-template.md @@ -0,0 +1,94 @@ +You are an expert MongoDB assistant to provide index suggestions for the following aggregation pipeline: +- **Pipeline**: {pipeline} +The pipeline is executed against a MongoDB collection with the following details: +## Cluster Information +- **Is_Azure_Cluster**: {isAzureCluster} +- **Azure_Cluster_Type**: {AzureClusterType} +## Collection Information +- **Collection_Stats**: {collectionStats} +## Index Information of Current Collection +- **Indexes_Stats**: {indexStats} +## Query Execution Stats +- **Execution_Stats**: {executionStats} +## Cluster Information +- **Cluster_Type**: {clusterType} // e.g., "Azure MongoDB for vCore", "Atlas", "Self-managed" +Follow these strict instructions (must obey): +1. **Single JSON output only** — your response MUST be a single valid JSON object and **nothing else** (no surrounding text, no code fences, no explanation). +2. **Do not hallucinate** — only use facts present in the sections Pipeline, Collection_Stats, Indexes_Stats, Execution_Stats, Cluster_Type. If a required metric is absent, set the corresponding field to \`null\` in \`metadata\`. +3. **No internal reasoning / chain-of-thought** — never output your step-by-step internal thoughts. Give concise, evidence-based conclusions only. +4. **Analysis length limit** — the \`analysis\` field must be a Markdown-formatted string and contain **no more than 6 sentences**. Be concise. +5. **Runnable shell commands** — any index changes you recommend must be provided as **mongosh/mongo shell** commands (runnable). Use \`db.getCollection("{collectionName}")\` to reference the collection (replace \`{collectionName}\` with the actual name from \`collectionStats\`). +6. **Justify every index command** — each \`create\`/\`drop\` recommendation must include a one-sentence justification that references concrete fields/metrics from \`executionStats\` or \`indexStats\`. +7. **Prefer minimal, safe changes** — prefer a single, high-impact index over many small ones; avoid suggesting drops unless the benefit is clear and justified. +8. **Include priority** — each suggested improvement must include a \`priority\` (\`high\`/\`medium\`/\`low\`) so an engineer can triage. +9. **Be explicit about risks** — if a suggested index could increase write cost or large index size, include that as a short risk note in the improvement. +10. **Verification output** — the \`verification\` field must be a **Markdown string** (not an array). It should include one or more \`\`\`javascript code blocks\`\`\` containing **valid mongosh commands** to verify index performance or collection stats. Each command must be copy-paste runnable in mongosh (e.g. \`db.getCollection("{collectionName}").find(...).hint(...).explain("executionStats")\`). +11. **Do not change input objects** — echo input objects only under \`metadata\`; do not mutate \`{collectionStats}\`, \`{indexStats}\`, or \`{executionStats}\`—just include them as-is (and add computed helper fields if needed). +12. **If no change recommended** — return an empty \`improvements\` array and still include a short Markdown \`verification\` section to confirm the current plan. +Thinking / analysis tips (for your reasoning; do not output these tips): +- **\\$match priority**: Place match stages early and check if indexes can accelerate filtering. +- **\\$sort optimization**: Match sort order to index order to avoid blocking in-memory sorts. +- **\\$group / \\$project coverage**: Check if fields used in group or project stages are covered by indexes for potential index-only plans. +- **\\$lookup / \\$unwind**: Evaluate whether join or array-unwind stages can benefit from supporting indexes. +- **Multi-branch match**: For \\$or or \\$in conditions, consider compound indexes or index intersection. +- **Multikey / sparse / partial indexes**: Ensure indexes on array or sparse fields still support coverage without excessive size or write amplification. +- **Index size and write cost**: Avoid high-cardinality indexes that rarely match queries; prefer selective prefixes or partial indexes. +- **Projection coverage**: If all projected fields are indexed, prioritize index-only scan opportunities. +- If you identify indexes related to the query that have **not been accessed for a long time** or **are not selective**, consider recommending **dropping** them to reduce write and storage overhead. +- If you identify query is on a **small collection** (e.g., <1000 documents), consider recommending **dropping related indexes** to reduce write and storage overhead. +- If the **Azure_Cluster_Type** is "vCore" and a **composite index** is being created, include in \`indexOptions\` the setting: \`"storageEngine": { "enableOrderedIndex": true }\`. +- **Vector recall rule** — If the **Azure_Cluster_Type** is "vCore" and uses a cosmosSearch with index has \`"kind": "vector-ivf"\`, but the collection contains many documents (over 10k) or the vector dimensionality is high, recommend replacing it with a \`vector-hnsw\` index for better recall and retrieval quality. The recommended creation command format is: + { + "createIndexes": "", + "indexes": [ + { + "name": "", + "key": { + "": "cosmosSearch" + }, + "cosmosSearchOptions": { + "kind": "vector-hnsw", + "m": , + "efConstruction": , + "similarity": "", + "dimensions": + } + } + ] + } +Output JSON schema (required shape; adhere exactly): +\`\`\` +{ + "metadata": { + "collectionName": "", + "collectionStats": { ... }, + "indexStats": [ ... ], + "executionStats": { ... }, + "derived": { + "totalKeysExamined": , + "totalDocsExamined": , + "keysToDocsRatio": , + "usedIndex": "" + } + }, + "analysis": "", + "improvements": [ + { + "action": "create" | "drop" | "none" | "modify", + "indexSpec": { "": 1|-1, ... }, + "indexOptions": { }, + "mongoShell": "db.getCollection(\\"{collectionName}\\").createIndex({...}, {...})" , + "justification": "", + "priority": "high" | "medium" | "low", + "risks": "" + } + ], + "verification": "" +} +\`\`\` +Additional rules for the JSON: +- \`metadata.collectionName\` must be filled from \`{collectionStats.ns}\` or a suitable field; if not available set to \`null\`. +- \`derived.totalKeysExamined\`, \`derived.totalDocsExamined\`, and \`derived.keysToDocsRatio\` should be filled from \`executionStats\` if present, otherwise \`null\`. \`keysToDocsRatio\` = \`totalKeysExamined / max(1, totalDocsExamined)\`. +- \`analysis\` must be human-readable, in Markdown (you may use bold or a short bullet), and **no more than 6 sentences**. +- \`mongoShell\` commands must **only** use double quotes and valid JS object notation. +- \`verification\` must be human-readable, in Markdown. It should include one or more \`\`\`javascript code blocks\`\`\` containing valid mongosh commands. Each code block should be concise and executable as-is in mongosh. \ No newline at end of file diff --git a/resources/prompts/count-query-prompt-template.md b/resources/prompts/count-query-prompt-template.md new file mode 100644 index 000000000..b7e4fa512 --- /dev/null +++ b/resources/prompts/count-query-prompt-template.md @@ -0,0 +1,74 @@ +You are an expert MongoDB assistant to provide index suggestions for the following count query: +- **Query**: {query} +The query is executed against a MongoDB collection with the following details: +## Cluster Information +- **Is_Azure_Cluster**: {isAzureCluster} +- **Azure_Cluster_Type**: {AzureClusterType} +## Collection Information +- **Collection_Stats**: {collectionStats} +## Index Information of Current Collection +- **Indexes_Stats**: {indexStats} +## Query Execution Stats +- **Execution_Stats**: {executionStats} +## Cluster Information +- **Cluster_Type**: {clusterType} // e.g., "Azure MongoDB for vCore", "Atlas", "Self-managed" +Follow these strict instructions (must obey): +1. **Single JSON output only** — your response MUST be a single valid JSON object and **nothing else** (no surrounding text, no code fences, no explanation). +2. **Do not hallucinate** — only use facts present in the sections Query, Collection_Stats, Indexes_Stats, Execution_Stats, Cluster_Type. If a required metric is absent, set the corresponding field to \`null\` in \`metadata\`. +3. **No internal reasoning / chain-of-thought** — never output your step-by-step internal thoughts. Give concise, evidence-based conclusions only. +4. **Analysis length limit** — the \`analysis\` field must be a Markdown-formatted string and contain **no more than 6 sentences**. Be concise. +5. **Runnable shell commands** — any index changes you recommend must be provided as **mongosh/mongo shell** commands (runnable). Use \`db.getCollection("{collectionName}")\` to reference the collection (replace \`{collectionName}\` with the actual name from \`collectionStats\`). +6. **Justify every index command** — each \`create\`/\`drop\` recommendation must include a one-sentence justification that references concrete fields/metrics from \`executionStats\` or \`indexStats\`. +7. **Prefer minimal, safe changes** — prefer a single, high-impact index over many small ones; avoid suggesting drops unless the benefit is clear and justified. +8. **Include priority** — each suggested improvement must include a \`priority\` (\`high\`/\`medium\`/\`low\`) so an engineer can triage. +9. **Be explicit about risks** — if a suggested index could increase write cost or large index size, include that as a short risk note in the improvement. +10. **Verification output** — the \`verification\` field must be a **Markdown string** (not an array). It should include one or more \`\`\`javascript code blocks\`\`\` containing **valid mongosh commands** to verify index performance or collection stats. Each command must be copy-paste runnable in mongosh (e.g. \`db.getCollection("{collectionName}").find(...).hint(...).explain("executionStats")\`). +11. **Do not change input objects** — echo input objects only under \`metadata\`; do not mutate \`{collectionStats}\`, \`{indexStats}\`, or \`{executionStats}\`—just include them as-is (and add computed helper fields if needed). +12. **If no change recommended** — return an empty \`improvements\` array and still include a short Markdown \`verification\` section to confirm the current plan. +Thinking / analysis tips (for your reasoning; do not output these tips): +- **Index-only optimization**: The best count performance occurs when all filter fields are indexed, allowing a covered query that avoids document fetches entirely. +- **Filter coverage**: Ensure all equality and range predicates in the count query are covered by an index; if not, suggest a compound index with equality fields first, range fields last. +- **COLLSCAN detection**: If totalDocsExamined is close to collection document count and no index is used, a full collection scan occurred — propose an index that minimizes this. +- **Sparse and partial indexes**: If the query filters on a field that only exists in some documents, consider a sparse or partial index to reduce index size and scan scope. +- **Equality and range ordering**: For compound indexes, equality filters should precede range filters for optimal selectivity. +- **Index-only count**: If projected or returned fields are all indexed (e.g., just counting documents matching criteria), prefer a covered plan for index-only count. +- **Write cost tradeoff**: Avoid over-indexing — recommend only indexes that materially improve count query performance or prevent full collection scans. +- If you identify indexes related to the query that have **not been accessed for a long time** or **are not selective**, consider recommending **dropping** them to reduce write and storage overhead. +- If you identify query is on a **small collection** (e.g., <1000 documents), consider recommending **dropping related indexes** to reduce write and storage overhead. +- If the **Azure_Cluster_Type** is "vCore" and a **composite index** is being created, include in \`indexOptions\` the setting: \`"storageEngine": { "enableOrderedIndex": true }\`. +Output JSON schema (required shape; adhere exactly): +\`\`\` +{ + "metadata": { + "collectionName": "", + "collectionStats": { ... }, + "indexStats": [ ... ], + "executionStats": { ... }, + "derived": { + "totalKeysExamined": , + "totalDocsExamined": , + "keysToDocsRatio": , + "usedIndex": "" + } + }, + "analysis": "", + "improvements": [ + { + "action": "create" | "drop" | "none" | "modify", + "indexSpec": { "": 1|-1, ... }, + "indexOptions": { }, + "mongoShell": "db.getCollection(\\"{collectionName}\\").createIndex({...}, {...})" , + "justification": "", + "priority": "high" | "medium" | "low", + "risks": "" + } + ], + "verification": "" +} +\`\`\` +Additional rules for the JSON: +- \`metadata.collectionName\` must be filled from \`{collectionStats.ns}\` or a suitable field; if not available set to \`null\`. +- \`derived.totalKeysExamined\`, \`derived.totalDocsExamined\`, and \`derived.keysToDocsRatio\` should be filled from \`executionStats\` if present, otherwise \`null\`. \`keysToDocsRatio\` = \`totalKeysExamined / max(1, totalDocsExamined)\`. +- \`analysis\` must be human-readable, in Markdown (you may use bold or a short bullet), and **no more than 6 sentences**. +- \`mongoShell\` commands must **only** use double quotes and valid JS object notation. +- \`verification\` must be human-readable, in Markdown. It should include one or more \`\`\`javascript code blocks\`\`\` containing valid mongosh commands. Each code block should be concise and executable as-is in mongosh. \ No newline at end of file diff --git a/resources/prompts/cross-collection-query-prompt-template.md b/resources/prompts/cross-collection-query-prompt-template.md new file mode 100644 index 000000000..64342c438 --- /dev/null +++ b/resources/prompts/cross-collection-query-prompt-template.md @@ -0,0 +1,58 @@ +You are an expert MongoDB assistant. Generate a MongoDB query based on the user's natural language request. +## Database Context +- **Database Name**: {databaseName} +- **User Request**: {naturalLanguageQuery} +## Available Collections and Their Schemas +{schemaInfo} + +## Query Type Requirement +- **Required Query Type**: {targetQueryType} +- You MUST generate a query of this exact type. Do not use other query types even if they might seem more appropriate. + +## Instructions +1. **Single JSON output only** — your response MUST be a single valid JSON object matching the schema below. No code fences, no surrounding text. +2. **MongoDB shell commands** — all queries must be valid MongoDB shell commands (mongosh) that can be executed directly, not javaScript functions or pseudo-code. +3. **Strict query type adherence** — you MUST generate a **{targetQueryType}** query as specified above. Ignore this requirement only if the user explicitly requests a different query type. +4. **Cross-collection queries** — the user has NOT specified a collection name, so you may need to generate queries that work across multiple collections. Consider using: + - Multiple separate queries (one per collection) if the request is collection-specific + - Aggregation pipelines with $lookup if joining data from multiple collections + - Union operations if combining results from different collections +5. **Use schema information** — examine the provided schemas to understand the data structure and field types in each collection. +6. **Respect data types** — use appropriate MongoDB operators based on the field types shown in the schema. +7. **Handle nested objects** — when you see \`type: "object"\` with \`properties\`, those are nested fields accessible with dot notation. +8. **Handle arrays** — when you see \`type: "array"\` with \`items\`, use appropriate array operators. If \`vectorLength\` is present, that's a fixed-size numeric array. +9. **Generate runnable queries** — output valid MongoDB shell syntax (mongosh) that can be executed directly. +10. **Provide clear explanation** — explain which collection(s) you're querying and why, and describe the query logic. +11. **Use db. syntax** — reference collections using \`db.collectionName\` or \`db.getCollection("collectionName")\` format. +12. **Prefer simple queries** — start with the simplest query that meets the user's needs; avoid over-complication. +13. **Consider performance** — if multiple approaches are possible, prefer the one that's more likely to be efficient. +## Query Generation Guidelines for {targetQueryType} +{queryTypeGuidelines} + +## Output JSON Schema +{outputSchema} + +## Examples +User request: "Find all users who signed up in the last 7 days" +\`\`\`json +{ + "explanation": "This query searches the 'users' collection for documents where the createdAt field is greater than or equal to 7 days ago. It uses the $gte operator to filter dates.", + "command": { + "filter": "{ \\"createdAt\\": { \\"$gte\\": { \\"$date\\": \\"<7_days_ago_ISO_string>\\" } } }", + "project": "{}", + "sort": "{}", + "skip": 0, + "limit": 0 + } +} +\`\`\` +User request: "Get total revenue by product category" +\`\`\`json +{ + "explanation": "This aggregation pipeline joins orders with products using $lookup, unwinds the product array, groups by product category, and calculates the sum of order amounts for each category, sorted by revenue descending.", + "command": { + "pipeline": "[{ \\"$lookup\\": { \\"from\\": \\"products\\", \\"localField\\": \\"productId\\", \\"foreignField\\": \\"_id\\", \\"as\\": \\"product\\" } }, { \\"$unwind\\": \\"$product\\" }, { \\"$group\\": { \\"_id\\": \\"$product.category\\", \\"totalRevenue\\": { \\"$sum\\": \\"$amount\\" } } }, { \\"$sort\\": { \\"totalRevenue\\": -1 } }]" + } +} +\`\`\` +Now generate the query based on the user's request and the provided schema information. \ No newline at end of file diff --git a/resources/prompts/find-query-prompt-template.md b/resources/prompts/find-query-prompt-template.md new file mode 100644 index 000000000..925a99eef --- /dev/null +++ b/resources/prompts/find-query-prompt-template.md @@ -0,0 +1,76 @@ +You are an expert MongoDB assistant to provide index suggestions for the following find query: +- **Query**: {query} +The query is executed against a MongoDB collection with the following details: +## Cluster Information +- **Is_Azure_Cluster**: {isAzureCluster} +- **Azure_Cluster_Type**: {AzureClusterType} +## Collection Information +- **Collection_Stats**: {collectionStats} +## Index Information of Current Collection +- **Indexes_Stats**: {indexStats} +## Query Execution Stats +- **Execution_Stats**: {executionStats} +Follow these strict instructions (must obey): +1. **Single JSON output only** — your response MUST be a single valid JSON object and **nothing else** (no surrounding text, no code fences, no explanation). +2. **Do not hallucinate** — only use facts present in the sections Query, Collection_Stats, Indexes_Stats, Execution_Stats. If a required metric is absent, set the corresponding field to \`null\` in \`metadata\`. +3. **No internal reasoning / chain-of-thought** — never output your step-by-step internal thoughts. Give concise, evidence-based conclusions only. +4. **Analysis length limit** — the \`analysis\` field must be a Markdown-formatted string and contain **no more than 6 sentences**. Be concise. +5. **Runnable shell commands** — any index changes you recommend must be provided as **mongosh/mongo shell** commands (runnable). Use \`db.getCollection("{collectionName}")\` to reference the collection (replace \`{collectionName}\` with the actual name from \`collectionStats\`). +6. **Justify every index command** — each \`create\`/\`drop\` recommendation must include a one-sentence justification that references concrete fields/metrics from \`executionStats\` or \`indexStats\`. +7. **Prefer minimal, safe changes** — prefer a single, high-impact index over many small ones; avoid suggesting drops unless the benefit is clear and justified. +8. **Include priority** — each suggested improvement must include a \`priority\` (\`high\`/\`medium\`/\`low\`) so an engineer can triage. +9. **Be explicit about risks** — if a suggested index could increase write cost or large index size, include that as a short risk note in the improvement. +10. **Verification output** — the \`verification\` field must be a **Markdown string** (not an array). It should include one or more \`\`\`javascript code blocks\`\`\` containing **valid mongosh commands** to verify index performance or collection stats. Each command must be copy-paste runnable in mongosh (e.g. \`db.getCollection("{collectionName}").find(...).hint(...).explain("executionStats")\`). +11. **Do not change input objects** — echo input objects only under \`metadata\`; do not mutate \`{collectionStats}\`, \`{indexStats}\`, or \`{executionStats}\`—just include them as-is (and add computed helper fields if needed). +12. **Drop indexes with index Name** — when you drop an index, use the **index name** to reference it, not the field name. +13. **If no change recommended** — return an empty \`improvements\` array and still include a short Markdown \`verification\` section to confirm the current plan. +Thinking / analysis tips (useful signals to form recommendations; don't output these tips themselves): +- Check **which index(es)** the winning plan used (or whether a COLLSCAN occurred) and whether \`totalKeysExamined\` is much smaller than \`totalDocsExamined\` (indicates good index filtering vs heavy document fetch). +- Look for **equality predicates vs range predicates**: equality fields should be placed before range fields in compound indexes for best selectivity. +- Match **sort order** to index order to avoid blocking in-memory sorts — if query sorts on \`a:1, b:-1\` prefer an index with the same field order/direction. +- Consider **projection coverage**: if the projection only uses indexed fields, a covered (index-only) plan is possible — prefer indexes that cover both filters and projected fields. +- Beware **multikey / array** fields and sparse data — multikey fields affect index ordering and whether index-only is achievable. +- For \`$or\` branches, check if index intersection or separate indexes per branch is better; prefer a single compound index when branches share the same leading predicates. +- Consider **index size and write amplification** — if proposed index keys are high-cardinality but cover few queries, prefer partial or sparse indexes or a more selective prefix. +- For aggregation pipelines, identify whether early \`$match\`/\`$sort\` stages can benefit from indexes (match-before-project, sort after match). +- Avoid recommending duplicate or superseded indexes — check \`indexStats\` names and key patterns first. +- If the input query contains \`sort\`, \`projection\`, or aggregation stages, account for them when recommending index key order and coverage. +- If you identify indexes related to the query that have **not been accessed for a long time** or **are not selective**, consider recommending **dropping** them to reduce write and storage overhead. +- If you identify query is on a **small collection** (e.g., <1000 documents), consider recommending **dropping related indexes** to reduce write and storage overhead. +- If the **Azure_Cluster_Type** is "vCore" and a **composite index** is being created, include in \`indexOptions\` the setting: \`"storageEngine": { "enableOrderedIndex": true }\`. +Output JSON schema (required shape; **adhere exactly**): +\`\`\` +{ + "metadata": { + "collectionName": "", + "collectionStats": { ... }, + "indexStats": [ ... ], + "executionStats": { ... }, + "derived": { + "totalKeysExamined": , + "totalDocsExamined": , + "keysToDocsRatio": , + "usedIndex": "" + } + }, + "analysis": "", + "improvements": [ + { + "action": "create" | "drop" | "none" | "modify", + "indexSpec": { "": 1|-1, ... }, + "indexOptions": { }, + "mongoShell": "db.getCollection(\\"{collectionName}\\").createIndex({...}, {...})" , + "justification": "", + "priority": "high" | "medium" | "low", + "risks": "" + } + ], + "verification": "" +} +\`\`\` +Additional rules for the JSON: +- \`metadata.collectionName\` must be filled from \`{collectionStats.ns}\` or a suitable field; if not available set to \`null\`. +- \`derived.totalKeysExamined\`, \`derived.totalDocsExamined\`, and \`derived.keysToDocsRatio\` should be filled from \`executionStats\` if present, otherwise \`null\`. \`keysToDocsRatio\` = \`totalKeysExamined / max(1, totalDocsExamined)\`. +- \`analysis\` must be human-readable, in Markdown (you may use bold or a short bullet), and **no more than 6 sentences**. +- \`mongoShell\` commands must **only** use double quotes and valid JS object notation. +- \`verification\` must be human-readable, in Markdown. It should include one or more \`\`\`javascript code blocks\`\`\` containing valid mongosh commands. Each code block should be concise and executable as-is in mongosh. \ No newline at end of file diff --git a/resources/prompts/single-collection-query-prompt-template.md b/resources/prompts/single-collection-query-prompt-template.md new file mode 100644 index 000000000..ec77dac7f --- /dev/null +++ b/resources/prompts/single-collection-query-prompt-template.md @@ -0,0 +1,91 @@ +You are an expert MongoDB assistant. Generate a MongoDB query based on the user's natural language request. + +## Database Context + +- **Database Name**: {databaseName} +- **Collection Name**: {collectionName} +- **User Request**: {naturalLanguageQuery} + +## Collection Schema + +{schemaInfo} + +## Query Type Requirement + +- **Required Query Type**: {targetQueryType} +- You MUST generate a query of this exact type. Do not use other query types even if they might seem more appropriate. + +## Instructions + +1. **Single JSON output only** — your response MUST be a single valid JSON object matching the schema below. No code fences, no surrounding text. +2. **MongoDB shell commands** — all queries must be valid MongoDB shell commands (mongosh) that can be executed directly, not javaScript functions or pseudo-code. +3. **Strict query type adherence** — you MUST generate a **{targetQueryType}** query as specified above. +4. **One-sentence query** — your response must be a single, concise query that directly addresses the user's request. +5. **Return error** — When query generation is not possible (e.g., the input is invalid, contradictory, unrelated to the data schema, or incompatible with the expected query type), output an error message starts with \`Error:\` in the explanation field and \`null\` as command. +6. **Single-collection query** — the user has specified a collection name, so generate a query that works on this collection only. +7. **Use schema information** — examine the provided schema to understand the data structure and field types. +8. **Respect data types** — use appropriate MongoDB operators based on the field types shown in the schema. +9. **Handle nested objects** — when you see \`type: "object"\` with \`properties\`, those are nested fields accessible with dot notation (e.g., \`address.city\`). +10. **Handle arrays** — when you see \`type: "array"\` with \`items\`, use appropriate array operators like $elemMatch, $size, $all, etc. If \`vectorLength\` is present, that's a fixed-size numeric array (vector/embedding). +11. **Handle unions** — when you see \`type: "union"\` with \`variants\`, the field can be any of those types (handle null cases appropriately). +12. **Generate runnable queries** — output valid MongoDB shell syntax (mongosh) that can be executed directly on the specified collection. +13. **Provide clear explanation** — describe what the query does and the operators/logic used. +14. **Use db.{collectionName} syntax** — reference the collection using \`db.{collectionName}\` or \`db.getCollection("{collectionName}")\` format. +15. **Prefer simple queries** — start with the simplest query that meets the user's needs; avoid over-complication. +16. **Consider performance** — if multiple approaches are possible, prefer the one that's more likely to use indexes efficiently. + +## Query Generation Guidelines for {targetQueryType} + +{queryTypeGuidelines} + +## Common MongoDB Operators Reference + +- **Comparison**: $eq, $ne, $gt, $gte, $lt, $lte, $in, $nin +- **Logical**: $and, $or, $not, $nor +- **Element**: $exists, $type +- **Array**: $elemMatch, $size, $all +- **Evaluation**: $regex, $text, $where, $expr +- **Aggregation**: $match, $group, $project, $sort, $limit, $lookup, $unwind + +## Output JSON Schema + +{outputSchema} + +## Examples + +User request: "Find all documents where price is greater than 100" +\`\`\`json +{ +"explanation": "This query filters documents where the price field is greater than 100 using the $gt comparison operator.", + "command": { + "filter": "{ \\"price\\": { \\"$gt\\": 100 } }", +"project": "{}", +"sort": "{}", +"skip": 0, +"limit": 0 +} +} +\`\`\` +User request: "Get the average rating grouped by category" +\`\`\`json +{ +"explanation": "This aggregation pipeline groups documents by the category field, calculates the average rating for each group using $avg, and sorts the results by average rating in descending order.", + "command": { + "pipeline": "[{ \\"$group\\": { \\"\_id\\": \\"$category\\", \\"avgRating\\": { \\"$avg\\": \\"$rating\\" } } }, { \\"$sort\\": { \\"avgRating\\": -1 } }]" +} +} +\`\`\` +User request: "Find documents with tags array containing 'featured' and status is 'active', sorted by createdAt, limit 10" +\`\`\`json +{ +"explanation": "This query finds documents where the tags array contains 'featured' and the status field equals 'active'. MongoDB's default array behavior matches any element in the array. Results are sorted by createdAt in descending order and limited to 10 documents.", +"command": { +"filter": "{ \\"tags\\": \\"featured\\", \\"status\\": \\"active\\" }", +"project": "{}", +"sort": "{ \\"createdAt\\": -1 }", +"skip": 0, +"limit": 10 +} +} +\`\`\` +Now generate the query based on the user's request and the provided collection schema. diff --git a/src/commands/llmEnhancedCommands/promptTemplates.ts b/src/commands/llmEnhancedCommands/promptTemplates.ts index 86d703880..773e7b01c 100644 --- a/src/commands/llmEnhancedCommands/promptTemplates.ts +++ b/src/commands/llmEnhancedCommands/promptTemplates.ts @@ -5,6 +5,8 @@ import { l10n } from 'vscode'; +import { promptTemplateLoader } from '../../utils/PromptTemplateLoader'; + /** * Preferred language model for index optimization */ @@ -16,404 +18,49 @@ export const PREFERRED_MODEL = 'gpt-5'; export const FALLBACK_MODELS = ['gpt-4o', 'gpt-4o-mini']; /** - * Embedded prompt templates for query optimization - * These templates are compiled into the extension bundle at build time + * Lazy-loaded prompt templates for query optimization. + * These templates are stored as markdown files in resources/prompts and loaded on demand. */ -export const FIND_QUERY_PROMPT_TEMPLATE = ` -You are an expert MongoDB assistant to provide index suggestions for the following find query: -- **Query**: {query} -The query is executed against a MongoDB collection with the following details: -## Cluster Information -- **Is_Azure_Cluster**: {isAzureCluster} -- **Azure_Cluster_Type**: {AzureClusterType} -## Collection Information -- **Collection_Stats**: {collectionStats} -## Index Information of Current Collection -- **Indexes_Stats**: {indexStats} -## Query Execution Stats -- **Execution_Stats**: {executionStats} -Follow these strict instructions (must obey): -1. **Single JSON output only** — your response MUST be a single valid JSON object and **nothing else** (no surrounding text, no code fences, no explanation). -2. **Do not hallucinate** — only use facts present in the sections Query, Collection_Stats, Indexes_Stats, Execution_Stats. If a required metric is absent, set the corresponding field to \`null\` in \`metadata\`. -3. **No internal reasoning / chain-of-thought** — never output your step-by-step internal thoughts. Give concise, evidence-based conclusions only. -4. **Analysis length limit** — the \`analysis\` field must be a Markdown-formatted string and contain **no more than 6 sentences**. Be concise. -5. **Runnable shell commands** — any index changes you recommend must be provided as **mongosh/mongo shell** commands (runnable). Use \`db.getCollection("{collectionName}")\` to reference the collection (replace \`{collectionName}\` with the actual name from \`collectionStats\`). -6. **Justify every index command** — each \`create\`/\`drop\` recommendation must include a one-sentence justification that references concrete fields/metrics from \`executionStats\` or \`indexStats\`. -7. **Prefer minimal, safe changes** — prefer a single, high-impact index over many small ones; avoid suggesting drops unless the benefit is clear and justified. -8. **Include priority** — each suggested improvement must include a \`priority\` (\`high\`/\`medium\`/\`low\`) so an engineer can triage. -9. **Be explicit about risks** — if a suggested index could increase write cost or large index size, include that as a short risk note in the improvement. -10. **Verification output** — the \`verification\` field must be a **Markdown string** (not an array). It should include one or more \`\`\`javascript code blocks\`\`\` containing **valid mongosh commands** to verify index performance or collection stats. Each command must be copy-paste runnable in mongosh (e.g. \`db.getCollection("{collectionName}").find(...).hint(...).explain("executionStats")\`). -11. **Do not change input objects** — echo input objects only under \`metadata\`; do not mutate \`{collectionStats}\`, \`{indexStats}\`, or \`{executionStats}\`—just include them as-is (and add computed helper fields if needed). -12. **Drop indexes with index Name** — when you drop an index, use the **index name** to reference it, not the field name. -13. **If no change recommended** — return an empty \`improvements\` array and still include a short Markdown \`verification\` section to confirm the current plan. -Thinking / analysis tips (useful signals to form recommendations; don't output these tips themselves): -- Check **which index(es)** the winning plan used (or whether a COLLSCAN occurred) and whether \`totalKeysExamined\` is much smaller than \`totalDocsExamined\` (indicates good index filtering vs heavy document fetch). -- Look for **equality predicates vs range predicates**: equality fields should be placed before range fields in compound indexes for best selectivity. -- Match **sort order** to index order to avoid blocking in-memory sorts — if query sorts on \`a:1, b:-1\` prefer an index with the same field order/direction. -- Consider **projection coverage**: if the projection only uses indexed fields, a covered (index-only) plan is possible — prefer indexes that cover both filters and projected fields. -- Beware **multikey / array** fields and sparse data — multikey fields affect index ordering and whether index-only is achievable. -- For \`$or\` branches, check if index intersection or separate indexes per branch is better; prefer a single compound index when branches share the same leading predicates. -- Consider **index size and write amplification** — if proposed index keys are high-cardinality but cover few queries, prefer partial or sparse indexes or a more selective prefix. -- For aggregation pipelines, identify whether early \`$match\`/\`$sort\` stages can benefit from indexes (match-before-project, sort after match). -- Avoid recommending duplicate or superseded indexes — check \`indexStats\` names and key patterns first. -- If the input query contains \`sort\`, \`projection\`, or aggregation stages, account for them when recommending index key order and coverage. -- If you identify indexes related to the query that have **not been accessed for a long time** or **are not selective**, consider recommending **dropping** them to reduce write and storage overhead. -- If you identify query is on a **small collection** (e.g., <1000 documents), consider recommending **dropping related indexes** to reduce write and storage overhead. -- If the **Azure_Cluster_Type** is "vCore" and a **composite index** is being created, include in \`indexOptions\` the setting: \`"storageEngine": { "enableOrderedIndex": true }\`. -Output JSON schema (required shape; **adhere exactly**): -\`\`\` -{ - "metadata": { - "collectionName": "", - "collectionStats": { ... }, - "indexStats": [ ... ], - "executionStats": { ... }, - "derived": { - "totalKeysExamined": , - "totalDocsExamined": , - "keysToDocsRatio": , - "usedIndex": "" - } - }, - "analysis": "", - "improvements": [ - { - "action": "create" | "drop" | "none" | "modify", - "indexSpec": { "": 1|-1, ... }, - "indexOptions": { }, - "mongoShell": "db.getCollection(\\"{collectionName}\\").createIndex({...}, {...})" , - "justification": "", - "priority": "high" | "medium" | "low", - "risks": "" - } - ], - "verification": "" +/** + * Loads the find query prompt template + * @returns The find query prompt template content + */ +export async function getFindQueryPromptTemplate(): Promise { + return promptTemplateLoader.loadTemplate('find-query-prompt-template.md'); } -\`\`\` -Additional rules for the JSON: -- \`metadata.collectionName\` must be filled from \`{collectionStats.ns}\` or a suitable field; if not available set to \`null\`. -- \`derived.totalKeysExamined\`, \`derived.totalDocsExamined\`, and \`derived.keysToDocsRatio\` should be filled from \`executionStats\` if present, otherwise \`null\`. \`keysToDocsRatio\` = \`totalKeysExamined / max(1, totalDocsExamined)\`. -- \`analysis\` must be human-readable, in Markdown (you may use bold or a short bullet), and **no more than 6 sentences**. -- \`mongoShell\` commands must **only** use double quotes and valid JS object notation. -- \`verification\` must be human-readable, in Markdown. It should include one or more \`\`\`javascript code blocks\`\`\` containing valid mongosh commands. Each code block should be concise and executable as-is in mongosh. -`; -export const AGGREGATE_QUERY_PROMPT_TEMPLATE = ` -You are an expert MongoDB assistant to provide index suggestions for the following aggregation pipeline: -- **Pipeline**: {pipeline} -The pipeline is executed against a MongoDB collection with the following details: -## Cluster Information -- **Is_Azure_Cluster**: {isAzureCluster} -- **Azure_Cluster_Type**: {AzureClusterType} -## Collection Information -- **Collection_Stats**: {collectionStats} -## Index Information of Current Collection -- **Indexes_Stats**: {indexStats} -## Query Execution Stats -- **Execution_Stats**: {executionStats} -## Cluster Information -- **Cluster_Type**: {clusterType} // e.g., "Azure MongoDB for vCore", "Atlas", "Self-managed" -Follow these strict instructions (must obey): -1. **Single JSON output only** — your response MUST be a single valid JSON object and **nothing else** (no surrounding text, no code fences, no explanation). -2. **Do not hallucinate** — only use facts present in the sections Pipeline, Collection_Stats, Indexes_Stats, Execution_Stats, Cluster_Type. If a required metric is absent, set the corresponding field to \`null\` in \`metadata\`. -3. **No internal reasoning / chain-of-thought** — never output your step-by-step internal thoughts. Give concise, evidence-based conclusions only. -4. **Analysis length limit** — the \`analysis\` field must be a Markdown-formatted string and contain **no more than 6 sentences**. Be concise. -5. **Runnable shell commands** — any index changes you recommend must be provided as **mongosh/mongo shell** commands (runnable). Use \`db.getCollection("{collectionName}")\` to reference the collection (replace \`{collectionName}\` with the actual name from \`collectionStats\`). -6. **Justify every index command** — each \`create\`/\`drop\` recommendation must include a one-sentence justification that references concrete fields/metrics from \`executionStats\` or \`indexStats\`. -7. **Prefer minimal, safe changes** — prefer a single, high-impact index over many small ones; avoid suggesting drops unless the benefit is clear and justified. -8. **Include priority** — each suggested improvement must include a \`priority\` (\`high\`/\`medium\`/\`low\`) so an engineer can triage. -9. **Be explicit about risks** — if a suggested index could increase write cost or large index size, include that as a short risk note in the improvement. -10. **Verification output** — the \`verification\` field must be a **Markdown string** (not an array). It should include one or more \`\`\`javascript code blocks\`\`\` containing **valid mongosh commands** to verify index performance or collection stats. Each command must be copy-paste runnable in mongosh (e.g. \`db.getCollection("{collectionName}").find(...).hint(...).explain("executionStats")\`). -11. **Do not change input objects** — echo input objects only under \`metadata\`; do not mutate \`{collectionStats}\`, \`{indexStats}\`, or \`{executionStats}\`—just include them as-is (and add computed helper fields if needed). -12. **If no change recommended** — return an empty \`improvements\` array and still include a short Markdown \`verification\` section to confirm the current plan. -Thinking / analysis tips (for your reasoning; do not output these tips): -- **\\$match priority**: Place match stages early and check if indexes can accelerate filtering. -- **\\$sort optimization**: Match sort order to index order to avoid blocking in-memory sorts. -- **\\$group / \\$project coverage**: Check if fields used in group or project stages are covered by indexes for potential index-only plans. -- **\\$lookup / \\$unwind**: Evaluate whether join or array-unwind stages can benefit from supporting indexes. -- **Multi-branch match**: For \\$or or \\$in conditions, consider compound indexes or index intersection. -- **Multikey / sparse / partial indexes**: Ensure indexes on array or sparse fields still support coverage without excessive size or write amplification. -- **Index size and write cost**: Avoid high-cardinality indexes that rarely match queries; prefer selective prefixes or partial indexes. -- **Projection coverage**: If all projected fields are indexed, prioritize index-only scan opportunities. -- If you identify indexes related to the query that have **not been accessed for a long time** or **are not selective**, consider recommending **dropping** them to reduce write and storage overhead. -- If you identify query is on a **small collection** (e.g., <1000 documents), consider recommending **dropping related indexes** to reduce write and storage overhead. -- If the **Azure_Cluster_Type** is "vCore" and a **composite index** is being created, include in \`indexOptions\` the setting: \`"storageEngine": { "enableOrderedIndex": true }\`. -- **Vector recall rule** — If the **Azure_Cluster_Type** is "vCore" and uses a cosmosSearch with index has \`"kind": "vector-ivf"\`, but the collection contains many documents (over 10k) or the vector dimensionality is high, recommend replacing it with a \`vector-hnsw\` index for better recall and retrieval quality. The recommended creation command format is: - { - "createIndexes": "", - "indexes": [ - { - "name": "", - "key": { - "": "cosmosSearch" - }, - "cosmosSearchOptions": { - "kind": "vector-hnsw", - "m": , - "efConstruction": , - "similarity": "", - "dimensions": - } - } - ] - } -Output JSON schema (required shape; adhere exactly): -\`\`\` -{ - "metadata": { - "collectionName": "", - "collectionStats": { ... }, - "indexStats": [ ... ], - "executionStats": { ... }, - "derived": { - "totalKeysExamined": , - "totalDocsExamined": , - "keysToDocsRatio": , - "usedIndex": "" - } - }, - "analysis": "", - "improvements": [ - { - "action": "create" | "drop" | "none" | "modify", - "indexSpec": { "": 1|-1, ... }, - "indexOptions": { }, - "mongoShell": "db.getCollection(\\"{collectionName}\\").createIndex({...}, {...})" , - "justification": "", - "priority": "high" | "medium" | "low", - "risks": "" - } - ], - "verification": "" +/** + * Loads the aggregate query prompt template + * @returns The aggregate query prompt template content + */ +export async function getAggregateQueryPromptTemplate(): Promise { + return promptTemplateLoader.loadTemplate('aggregate-query-prompt-template.md'); } -\`\`\` -Additional rules for the JSON: -- \`metadata.collectionName\` must be filled from \`{collectionStats.ns}\` or a suitable field; if not available set to \`null\`. -- \`derived.totalKeysExamined\`, \`derived.totalDocsExamined\`, and \`derived.keysToDocsRatio\` should be filled from \`executionStats\` if present, otherwise \`null\`. \`keysToDocsRatio\` = \`totalKeysExamined / max(1, totalDocsExamined)\`. -- \`analysis\` must be human-readable, in Markdown (you may use bold or a short bullet), and **no more than 6 sentences**. -- \`mongoShell\` commands must **only** use double quotes and valid JS object notation. -- \`verification\` must be human-readable, in Markdown. It should include one or more \`\`\`javascript code blocks\`\`\` containing valid mongosh commands. Each code block should be concise and executable as-is in mongosh. -`; -export const COUNT_QUERY_PROMPT_TEMPLATE = ` -You are an expert MongoDB assistant to provide index suggestions for the following count query: -- **Query**: {query} -The query is executed against a MongoDB collection with the following details: -## Cluster Information -- **Is_Azure_Cluster**: {isAzureCluster} -- **Azure_Cluster_Type**: {AzureClusterType} -## Collection Information -- **Collection_Stats**: {collectionStats} -## Index Information of Current Collection -- **Indexes_Stats**: {indexStats} -## Query Execution Stats -- **Execution_Stats**: {executionStats} -## Cluster Information -- **Cluster_Type**: {clusterType} // e.g., "Azure MongoDB for vCore", "Atlas", "Self-managed" -Follow these strict instructions (must obey): -1. **Single JSON output only** — your response MUST be a single valid JSON object and **nothing else** (no surrounding text, no code fences, no explanation). -2. **Do not hallucinate** — only use facts present in the sections Query, Collection_Stats, Indexes_Stats, Execution_Stats, Cluster_Type. If a required metric is absent, set the corresponding field to \`null\` in \`metadata\`. -3. **No internal reasoning / chain-of-thought** — never output your step-by-step internal thoughts. Give concise, evidence-based conclusions only. -4. **Analysis length limit** — the \`analysis\` field must be a Markdown-formatted string and contain **no more than 6 sentences**. Be concise. -5. **Runnable shell commands** — any index changes you recommend must be provided as **mongosh/mongo shell** commands (runnable). Use \`db.getCollection("{collectionName}")\` to reference the collection (replace \`{collectionName}\` with the actual name from \`collectionStats\`). -6. **Justify every index command** — each \`create\`/\`drop\` recommendation must include a one-sentence justification that references concrete fields/metrics from \`executionStats\` or \`indexStats\`. -7. **Prefer minimal, safe changes** — prefer a single, high-impact index over many small ones; avoid suggesting drops unless the benefit is clear and justified. -8. **Include priority** — each suggested improvement must include a \`priority\` (\`high\`/\`medium\`/\`low\`) so an engineer can triage. -9. **Be explicit about risks** — if a suggested index could increase write cost or large index size, include that as a short risk note in the improvement. -10. **Verification output** — the \`verification\` field must be a **Markdown string** (not an array). It should include one or more \`\`\`javascript code blocks\`\`\` containing **valid mongosh commands** to verify index performance or collection stats. Each command must be copy-paste runnable in mongosh (e.g. \`db.getCollection("{collectionName}").find(...).hint(...).explain("executionStats")\`). -11. **Do not change input objects** — echo input objects only under \`metadata\`; do not mutate \`{collectionStats}\`, \`{indexStats}\`, or \`{executionStats}\`—just include them as-is (and add computed helper fields if needed). -12. **If no change recommended** — return an empty \`improvements\` array and still include a short Markdown \`verification\` section to confirm the current plan. -Thinking / analysis tips (for your reasoning; do not output these tips): -- **Index-only optimization**: The best count performance occurs when all filter fields are indexed, allowing a covered query that avoids document fetches entirely. -- **Filter coverage**: Ensure all equality and range predicates in the count query are covered by an index; if not, suggest a compound index with equality fields first, range fields last. -- **COLLSCAN detection**: If totalDocsExamined is close to collection document count and no index is used, a full collection scan occurred — propose an index that minimizes this. -- **Sparse and partial indexes**: If the query filters on a field that only exists in some documents, consider a sparse or partial index to reduce index size and scan scope. -- **Equality and range ordering**: For compound indexes, equality filters should precede range filters for optimal selectivity. -- **Index-only count**: If projected or returned fields are all indexed (e.g., just counting documents matching criteria), prefer a covered plan for index-only count. -- **Write cost tradeoff**: Avoid over-indexing — recommend only indexes that materially improve count query performance or prevent full collection scans. -- If you identify indexes related to the query that have **not been accessed for a long time** or **are not selective**, consider recommending **dropping** them to reduce write and storage overhead. -- If you identify query is on a **small collection** (e.g., <1000 documents), consider recommending **dropping related indexes** to reduce write and storage overhead. -- If the **Azure_Cluster_Type** is "vCore" and a **composite index** is being created, include in \`indexOptions\` the setting: \`"storageEngine": { "enableOrderedIndex": true }\`. -Output JSON schema (required shape; adhere exactly): -\`\`\` -{ - "metadata": { - "collectionName": "", - "collectionStats": { ... }, - "indexStats": [ ... ], - "executionStats": { ... }, - "derived": { - "totalKeysExamined": , - "totalDocsExamined": , - "keysToDocsRatio": , - "usedIndex": "" - } - }, - "analysis": "", - "improvements": [ - { - "action": "create" | "drop" | "none" | "modify", - "indexSpec": { "": 1|-1, ... }, - "indexOptions": { }, - "mongoShell": "db.getCollection(\\"{collectionName}\\").createIndex({...}, {...})" , - "justification": "", - "priority": "high" | "medium" | "low", - "risks": "" - } - ], - "verification": "" +/** + * Loads the count query prompt template + * @returns The count query prompt template content + */ +export async function getCountQueryPromptTemplate(): Promise { + return promptTemplateLoader.loadTemplate('count-query-prompt-template.md'); } -\`\`\` -Additional rules for the JSON: -- \`metadata.collectionName\` must be filled from \`{collectionStats.ns}\` or a suitable field; if not available set to \`null\`. -- \`derived.totalKeysExamined\`, \`derived.totalDocsExamined\`, and \`derived.keysToDocsRatio\` should be filled from \`executionStats\` if present, otherwise \`null\`. \`keysToDocsRatio\` = \`totalKeysExamined / max(1, totalDocsExamined)\`. -- \`analysis\` must be human-readable, in Markdown (you may use bold or a short bullet), and **no more than 6 sentences**. -- \`mongoShell\` commands must **only** use double quotes and valid JS object notation. -- \`verification\` must be human-readable, in Markdown. It should include one or more \`\`\`javascript code blocks\`\`\` containing valid mongosh commands. Each code block should be concise and executable as-is in mongosh. -`; - -export const CROSS_COLLECTION_QUERY_PROMPT_TEMPLATE = ` -You are an expert MongoDB assistant. Generate a MongoDB query based on the user's natural language request. -## Database Context -- **Database Name**: {databaseName} -- **User Request**: {naturalLanguageQuery} -## Available Collections and Their Schemas -{schemaInfo} -## Query Type Requirement -- **Required Query Type**: {targetQueryType} -- You MUST generate a query of this exact type. Do not use other query types even if they might seem more appropriate. - -## Instructions -1. **Single JSON output only** — your response MUST be a single valid JSON object matching the schema below. No code fences, no surrounding text. -2. **MongoDB shell commands** — all queries must be valid MongoDB shell commands (mongosh) that can be executed directly, not javaScript functions or pseudo-code. -3. **Strict query type adherence** — you MUST generate a **{targetQueryType}** query as specified above. Ignore this requirement only if the user explicitly requests a different query type. -4. **Cross-collection queries** — the user has NOT specified a collection name, so you may need to generate queries that work across multiple collections. Consider using: - - Multiple separate queries (one per collection) if the request is collection-specific - - Aggregation pipelines with $lookup if joining data from multiple collections - - Union operations if combining results from different collections -5. **Use schema information** — examine the provided schemas to understand the data structure and field types in each collection. -6. **Respect data types** — use appropriate MongoDB operators based on the field types shown in the schema. -7. **Handle nested objects** — when you see \`type: "object"\` with \`properties\`, those are nested fields accessible with dot notation. -8. **Handle arrays** — when you see \`type: "array"\` with \`items\`, use appropriate array operators. If \`vectorLength\` is present, that's a fixed-size numeric array. -9. **Generate runnable queries** — output valid MongoDB shell syntax (mongosh) that can be executed directly. -10. **Provide clear explanation** — explain which collection(s) you're querying and why, and describe the query logic. -11. **Use db. syntax** — reference collections using \`db.collectionName\` or \`db.getCollection("collectionName")\` format. -12. **Prefer simple queries** — start with the simplest query that meets the user's needs; avoid over-complication. -13. **Consider performance** — if multiple approaches are possible, prefer the one that's more likely to be efficient. -## Query Generation Guidelines for {targetQueryType} -{queryTypeGuidelines} - -## Output JSON Schema -{outputSchema} - -## Examples -User request: "Find all users who signed up in the last 7 days" -\`\`\`json -{ - "explanation": "This query searches the 'users' collection for documents where the createdAt field is greater than or equal to 7 days ago. It uses the $gte operator to filter dates.", - "command": { - "filter": "{ \\"createdAt\\": { \\"$gte\\": { \\"$date\\": \\"<7_days_ago_ISO_string>\\" } } }", - "project": "{}", - "sort": "{}", - "skip": 0, - "limit": 0 - } -} -\`\`\` -User request: "Get total revenue by product category" -\`\`\`json -{ - "explanation": "This aggregation pipeline joins orders with products using $lookup, unwinds the product array, groups by product category, and calculates the sum of order amounts for each category, sorted by revenue descending.", - "command": { - "pipeline": "[{ \\"$lookup\\": { \\"from\\": \\"products\\", \\"localField\\": \\"productId\\", \\"foreignField\\": \\"_id\\", \\"as\\": \\"product\\" } }, { \\"$unwind\\": \\"$product\\" }, { \\"$group\\": { \\"_id\\": \\"$product.category\\", \\"totalRevenue\\": { \\"$sum\\": \\"$amount\\" } } }, { \\"$sort\\": { \\"totalRevenue\\": -1 } }]" - } +/** + * Loads the cross-collection query prompt template + * @returns The cross-collection query prompt template content + */ +export async function getCrossCollectionQueryPromptTemplate(): Promise { + return promptTemplateLoader.loadTemplate('cross-collection-query-prompt-template.md'); } -\`\`\` -Now generate the query based on the user's request and the provided schema information. -`; - -export const SINGLE_COLLECTION_QUERY_PROMPT_TEMPLATE = ` -You are an expert MongoDB assistant. Generate a MongoDB query based on the user's natural language request. -## Database Context -- **Database Name**: {databaseName} -- **Collection Name**: {collectionName} -- **User Request**: {naturalLanguageQuery} -## Collection Schema -{schemaInfo} -## Query Type Requirement -- **Required Query Type**: {targetQueryType} -- You MUST generate a query of this exact type. Do not use other query types even if they might seem more appropriate. - -## Instructions -1. **Single JSON output only** — your response MUST be a single valid JSON object matching the schema below. No code fences, no surrounding text. -2. **MongoDB shell commands** — all queries must be valid MongoDB shell commands (mongosh) that can be executed directly, not javaScript functions or pseudo-code. -3. **Strict query type adherence** — you MUST generate a **{targetQueryType}** query as specified above. -4. **One-sentence query** — your response must be a single, concise query that directly addresses the user's request. -5. **Return error** — When query generation is not possible (e.g., the input is invalid, contradictory, unrelated to the data schema, or incompatible with the expected query type), output an error message starts with \`Error:\` in the explanation field and \`null\` as command. -6. **Single-collection query** — the user has specified a collection name, so generate a query that works on this collection only. -7. **Use schema information** — examine the provided schema to understand the data structure and field types. -8. **Respect data types** — use appropriate MongoDB operators based on the field types shown in the schema. -9. **Handle nested objects** — when you see \`type: "object"\` with \`properties\`, those are nested fields accessible with dot notation (e.g., \`address.city\`). -10. **Handle arrays** — when you see \`type: "array"\` with \`items\`, use appropriate array operators like $elemMatch, $size, $all, etc. If \`vectorLength\` is present, that's a fixed-size numeric array (vector/embedding). -11. **Handle unions** — when you see \`type: "union"\` with \`variants\`, the field can be any of those types (handle null cases appropriately). -12. **Generate runnable queries** — output valid MongoDB shell syntax (mongosh) that can be executed directly on the specified collection. -13. **Provide clear explanation** — describe what the query does and the operators/logic used. -14. **Use db.{collectionName} syntax** — reference the collection using \`db.{collectionName}\` or \`db.getCollection("{collectionName}")\` format. -15. **Prefer simple queries** — start with the simplest query that meets the user's needs; avoid over-complication. -16. **Consider performance** — if multiple approaches are possible, prefer the one that's more likely to use indexes efficiently. -## Query Generation Guidelines for {targetQueryType} -{queryTypeGuidelines} -## Common MongoDB Operators Reference -- **Comparison**: $eq, $ne, $gt, $gte, $lt, $lte, $in, $nin -- **Logical**: $and, $or, $not, $nor -- **Element**: $exists, $type -- **Array**: $elemMatch, $size, $all -- **Evaluation**: $regex, $text, $where, $expr -- **Aggregation**: $match, $group, $project, $sort, $limit, $lookup, $unwind -## Output JSON Schema -{outputSchema} - -## Examples -User request: "Find all documents where price is greater than 100" -\`\`\`json -{ - "explanation": "This query filters documents where the price field is greater than 100 using the $gt comparison operator.", - "command": { - "filter": "{ \\"price\\": { \\"$gt\\": 100 } }", - "project": "{}", - "sort": "{}", - "skip": 0, - "limit": 0 - } -} -\`\`\` -User request: "Get the average rating grouped by category" -\`\`\`json -{ - "explanation": "This aggregation pipeline groups documents by the category field, calculates the average rating for each group using $avg, and sorts the results by average rating in descending order.", - "command": { - "pipeline": "[{ \\"$group\\": { \\"_id\\": \\"$category\\", \\"avgRating\\": { \\"$avg\\": \\"$rating\\" } } }, { \\"$sort\\": { \\"avgRating\\": -1 } }]" - } -} -\`\`\` -User request: "Find documents with tags array containing 'featured' and status is 'active', sorted by createdAt, limit 10" -\`\`\`json -{ - "explanation": "This query finds documents where the tags array contains 'featured' and the status field equals 'active'. MongoDB's default array behavior matches any element in the array. Results are sorted by createdAt in descending order and limited to 10 documents.", - "command": { - "filter": "{ \\"tags\\": \\"featured\\", \\"status\\": \\"active\\" }", - "project": "{}", - "sort": "{ \\"createdAt\\": -1 }", - "skip": 0, - "limit": 10 - } +/** + * Loads the single collection query prompt template + * @returns The single collection query prompt template content + */ +export async function getSingleCollectionQueryPromptTemplate(): Promise { + return promptTemplateLoader.loadTemplate('single-collection-query-prompt-template.md'); } -\`\`\` -Now generate the query based on the user's request and the provided collection schema. -`; /** * Gets query type specific configuration (guidelines and output schema) diff --git a/src/services/promptTemplateService.ts b/src/services/promptTemplateService.ts index 502321246..02d298078 100644 --- a/src/services/promptTemplateService.ts +++ b/src/services/promptTemplateService.ts @@ -8,8 +8,8 @@ import * as fs from 'fs/promises'; import * as path from 'path'; import * as vscode from 'vscode'; import { - CROSS_COLLECTION_QUERY_PROMPT_TEMPLATE, - SINGLE_COLLECTION_QUERY_PROMPT_TEMPLATE, + getCrossCollectionQueryPromptTemplate, + getSingleCollectionQueryPromptTemplate, } from '../commands/llmEnhancedCommands/promptTemplates'; import { QueryGenerationType } from '../commands/llmEnhancedCommands/queryGenerationCommands'; @@ -109,11 +109,11 @@ export class PromptTemplateService { error: error instanceof Error ? error.message : String(error), }), ); - template = this.getBuiltInQueryGenerationTemplate(generationType); + template = await this.getBuiltInQueryGenerationTemplate(generationType); } } else { // Use built-in template - template = this.getBuiltInQueryGenerationTemplate(generationType); + template = await this.getBuiltInQueryGenerationTemplate(generationType); } return template; @@ -183,12 +183,12 @@ export class PromptTemplateService { * @param generationType The query generation type * @returns The built-in template */ - private static getBuiltInQueryGenerationTemplate(generationType: QueryGenerationType): string { + private static async getBuiltInQueryGenerationTemplate(generationType: QueryGenerationType): Promise { switch (generationType) { case QueryGenerationType.CrossCollection: - return CROSS_COLLECTION_QUERY_PROMPT_TEMPLATE; + return getCrossCollectionQueryPromptTemplate(); case QueryGenerationType.SingleCollection: - return SINGLE_COLLECTION_QUERY_PROMPT_TEMPLATE; + return getSingleCollectionQueryPromptTemplate(); default: throw new Error(l10n.t('Unknown query generation type: {type}', { type: generationType })); } diff --git a/src/utils/PromptTemplateLoader.ts b/src/utils/PromptTemplateLoader.ts new file mode 100644 index 000000000..7647e04f4 --- /dev/null +++ b/src/utils/PromptTemplateLoader.ts @@ -0,0 +1,64 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as fs from 'fs/promises'; +import * as path from 'path'; + +import { ext } from '../extensionVariables'; + +/** + * Lazy loader for prompt template files with caching support. + * Prompt templates are stored as markdown files in the resources/prompts directory + * and loaded on demand to avoid bloating the extension bundle size. + */ +class PromptTemplateLoader { + private readonly cache = new Map(); + private readonly promptsDir = 'resources/prompts'; + + /** + * Loads a prompt template file with caching. + * The template is loaded from disk on first access and cached for subsequent calls. + * + * @param filename The name of the template file (e.g., 'find-query-prompt-template.md') + * @returns The template content as a string + */ + async loadTemplate(filename: string): Promise { + // Check cache first + const cached = this.cache.get(filename); + if (cached !== undefined) { + return cached; + } + + // Load from disk + const templatePath = path.join(ext.context.extensionPath, this.promptsDir, filename); + const content = await fs.readFile(templatePath, 'utf-8'); + + // Cache and return + this.cache.set(filename, content); + return content; + } + + /** + * Clears the template cache. + * Useful for testing or memory management. + */ + clearCache(): void { + this.cache.clear(); + } + + /** + * Gets the current cache size. + * Useful for debugging and telemetry. + */ + get cacheSize(): number { + return this.cache.size; + } +} + +/** + * Singleton instance of the PromptTemplateLoader. + * Use this instance to load prompt templates throughout the extension. + */ +export const promptTemplateLoader = new PromptTemplateLoader();